diff --git a/docs/api.md b/docs/api.md index 3105e80e..95edae1d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -77,7 +77,6 @@ the template file: * `{version}`: Replaced by the maven coordinates version. * `{type}`: Replaced by the maven coordinates type, if present (defaults to "jar") * `{scope}`: Replaced by the maven coordinates type, if present (defaults to "compile") - * `{parent}`: Replaced by a ``, ``, and `` set of tags. * `{dependencies}`: Replaced by a list of maven dependencies directly relied upon by java_library targets within the artifact. @@ -118,41 +117,60 @@ Generated rules: ## maven_bom
-maven_bom(name, maven_coordinates, java_exports, tags, testonly, visibility)
+maven_bom(name, maven_coordinates, java_exports, bom_pom_template, dependencies_maven_coordinates,
+          dependencies_pom_template, tags, testonly, visibility)
 
-Generates a Maven BOM `pom.xml` file. +Generates a Maven BOM `pom.xml` file and an optional "dependencies" `pom.xml`. -The generated BOM will contain maven dependencies that are shared between two -or more of the `java_exports`. This will also generate `pom.xml` files for -each of the `java_exports`. Within those `pom.xml`s, only dependencies that are -unique to the `java_export` will have the `version` tag. Dependencies which are -listed in the BOM will omit the `version` tag. +The generated BOM will contain a list of all the coordinates of the +`java_export` targets in the `java_exports` parameters. An optional +dependencies artifact will be created if the parameter +`dependencies_maven_coordinates` is set. + +Both the BOM and dependencies artifact can be templatised to support +customisation, but a sensible default template will be used if none is +provided. The template used is derived from the (optional) +`pom_template` argument, and the following substitutions are performed on +the template file: + + * `{groupId}`: Replaced with the maven coordinates group ID. + * `{artifactId}`: Replaced with the maven coordinates artifact ID. + * `{version}`: Replaced by the maven coordinates version. + * `{dependencies}`: Replaced by a list of maven dependencies directly relied upon + by java_library targets within the artifact. + +To publish, call the implicit `*.publish` target(s). The maven repository may accessed locally using a `file://` URL, or remotely using an `https://` URL. The following flags may be set using `--define`: - gpg_sign: Whether to sign artifacts using GPG - maven_repo: A URL for the repo to use. May be "https" or "file". - maven_user: The user name to use when uploading to the maven repository. - maven_password: The password to use when uploading to the maven repository. + * `gpg_sign`: Whether to sign artifacts using GPG + * `maven_repo`: A URL for the repo to use. May be "https" or "file". + * `maven_user`: The user name to use when uploading to the maven repository. + * `maven_password`: The password to use when uploading to the maven repository. When signing with GPG, the current default key is used. - Args: - name: A unique name for this rule. - maven_coordinates: The maven coordinates of this BOM in `groupId:artifactId:version` form. - java_exports: A list of `java_export` targets that are used to generate the BOM. +Generated rules: + * `name`: The BOM file itself. + * `name.publish`: To be executed by `bazel run` to publish the BOM to a maven repo + * `name-dependencies`: The BOM file for the dependencies `pom.xml`. Only generated if `dependencies_maven_coordinates` is set. + * `name-dependencies.publish`: To be executed by `bazel run` to publish the dependencies `pom.xml` to a maven rpo. Only generated if `dependencies_maven_coordinates` is set. + **PARAMETERS** | Name | Description | Default Value | | :------------- | :------------- | :------------- | -| name |

-

| none | -| maven_coordinates |

-

| none | -| java_exports |

-

| none | +| name | A unique name for this rule. | none | +| maven_coordinates | The maven coordinates of this BOM in groupId:artifactId:version form. | none | +| java_exports | A list of java_export targets that are used to generate the BOM. | none | +| bom_pom_template | A template used for generating the pom.xml of the BOM at maven_coordinates (optional) | None | +| dependencies_maven_coordinates | The maven coordinates of a dependencies artifact to generate in GAV format. If empty, none will be generated. (optional) | None | +| dependencies_pom_template | A template used for generating the pom.xml of the dependencies artifact at dependencies_maven_coordinates (optional) | None | | tags |

-

| None | | testonly |

-

| None | | visibility |

-

| None | diff --git a/examples/java-export/BUILD b/examples/java-export/BUILD index 54a591a4..954a4820 100644 --- a/examples/java-export/BUILD +++ b/examples/java-export/BUILD @@ -22,4 +22,5 @@ maven_bom( "//src/main/java/com/github/bazelbuild/rulesjvmexternal/example/io", ], maven_coordinates = "com.example:bazel-example-bom:0.0.1", + dependencies_maven_coordinates = "com.example:bazel-example-dependencies:0.0.1", ) diff --git a/examples/java-export/maven_install.json b/examples/java-export/maven_install.json index 98a84f58..7af3cfea 100644 --- a/examples/java-export/maven_install.json +++ b/examples/java-export/maven_install.json @@ -1,7 +1,7 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -457276106, + "__INPUT_ARTIFACTS_HASH": 1869915681, "__RESOLVED_ARTIFACTS_HASH": -383075934, "conflict_resolution": {}, "dependencies": [ @@ -13,6 +13,11 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" ], + "packages": [ + "javax.annotation", + "javax.annotation.concurrent", + "javax.annotation.meta" + ], "sha256": "766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7", "url": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" }, @@ -24,6 +29,7 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar" ], + "packages": [], "sha256": "1c9e85e272d0708c6a591dc74828c71603053b48cc75ae83cce56912a2aa063b", "url": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar" }, @@ -35,6 +41,10 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" ], + "packages": [ + "com.google.errorprone.annotations", + "com.google.errorprone.annotations.concurrent" + ], "sha256": "baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c", "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" }, @@ -46,6 +56,7 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4-sources.jar" ], + "packages": [], "sha256": "0b1011d1e2ea2eab35a545cffd1cff3877f131134c8020885e8eaf60a7d72f91", "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4-sources.jar" }, @@ -57,6 +68,9 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" ], + "packages": [ + "com.google.common.util.concurrent.internal" + ], "sha256": "a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26", "url": "https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" }, @@ -68,31 +82,52 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar" ], + "packages": [], "sha256": "092346eebbb1657b51aa7485a246bf602bb464cc0b0e2e1c7e7201fadce1e98f", "url": "https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar" }, { "coord": "com.google.guava:guava:29.0-jre", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", "com.google.errorprone:error_prone_annotations:2.3.4", "com.google.guava:failureaccess:1.0.1", + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", "org.checkerframework:checker-qual:2.11.1" ], "directDependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", "com.google.errorprone:error_prone_annotations:2.3.4", "com.google.guava:failureaccess:1.0.1", + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", "org.checkerframework:checker-qual:2.11.1" ], "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre.jar", "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre.jar" ], + "packages": [ + "com.google.common.annotations", + "com.google.common.base", + "com.google.common.base.internal", + "com.google.common.cache", + "com.google.common.collect", + "com.google.common.escape", + "com.google.common.eventbus", + "com.google.common.graph", + "com.google.common.hash", + "com.google.common.html", + "com.google.common.io", + "com.google.common.math", + "com.google.common.net", + "com.google.common.primitives", + "com.google.common.reflect", + "com.google.common.util.concurrent", + "com.google.common.xml", + "com.google.thirdparty.publicsuffix" + ], "sha256": "b22c5fb66d61e7b9522531d04b2f915b5158e80aa0b40ee7282c8bfb07b0da25", "url": "https://repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre.jar" }, @@ -100,24 +135,25 @@ "coord": "com.google.guava:guava:jar:sources:29.0-jre", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "org.checkerframework:checker-qual:jar:sources:2.11.1", "com.google.errorprone:error_prone_annotations:jar:sources:2.3.4", + "com.google.guava:failureaccess:jar:sources:1.0.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:failureaccess:jar:sources:1.0.1" + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:2.11.1" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "org.checkerframework:checker-qual:jar:sources:2.11.1", "com.google.errorprone:error_prone_annotations:jar:sources:2.3.4", + "com.google.guava:failureaccess:jar:sources:1.0.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:failureaccess:jar:sources:1.0.1" + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:2.11.1" ], "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre-sources.jar", "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre-sources.jar" ], + "packages": [], "sha256": "cfcbe29dd5125f5b360370b4ecd7f7ef44fba68f4f037e90bce7315682afc0ea", "url": "https://repo1.maven.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre-sources.jar" }, @@ -129,9 +165,16 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" ], + "packages": [], "sha256": "b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99", "url": "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" }, + { + "coord": "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "dependencies": [], + "directDependencies": [], + "file": null + }, { "coord": "com.google.j2objc:j2objc-annotations:1.3", "dependencies": [], @@ -140,6 +183,9 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" ], + "packages": [ + "com.google.j2objc.annotations" + ], "sha256": "21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b", "url": "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" }, @@ -151,6 +197,7 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar" ], + "packages": [], "sha256": "ba4df669fec153fa4cd0ef8d02c6d3ef0702b7ac4cabe080facf3b6e490bb972", "url": "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar" }, @@ -162,6 +209,10 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.11.3/protobuf-java-3.11.3.jar" ], + "packages": [ + "com.google.protobuf", + "com.google.protobuf.compiler" + ], "sha256": "4e567f364f5608606616ef764e801d66a52e5241577ad7405f519a3a8a6802bb", "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.11.3/protobuf-java-3.11.3.jar" }, @@ -173,6 +224,7 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.11.3/protobuf-java-3.11.3-sources.jar" ], + "packages": [], "sha256": "9d2ee817e71c63f197271d425b11dac1414926302eea1d2eaae5e4fd2ca31d5d", "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.11.3/protobuf-java-3.11.3-sources.jar" }, @@ -184,6 +236,40 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.11.1/checker-qual-2.11.1.jar" ], + "packages": [ + "org.checkerframework.checker.compilermsgs.qual", + "org.checkerframework.checker.fenum.qual", + "org.checkerframework.checker.formatter", + "org.checkerframework.checker.formatter.qual", + "org.checkerframework.checker.guieffect.qual", + "org.checkerframework.checker.i18n.qual", + "org.checkerframework.checker.i18nformatter", + "org.checkerframework.checker.i18nformatter.qual", + "org.checkerframework.checker.index.qual", + "org.checkerframework.checker.initialization.qual", + "org.checkerframework.checker.interning.qual", + "org.checkerframework.checker.lock.qual", + "org.checkerframework.checker.nullness", + "org.checkerframework.checker.nullness.qual", + "org.checkerframework.checker.optional.qual", + "org.checkerframework.checker.propkey.qual", + "org.checkerframework.checker.regex", + "org.checkerframework.checker.regex.qual", + "org.checkerframework.checker.signature.qual", + "org.checkerframework.checker.signedness", + "org.checkerframework.checker.signedness.qual", + "org.checkerframework.checker.tainting.qual", + "org.checkerframework.checker.units", + "org.checkerframework.checker.units.qual", + "org.checkerframework.common.aliasing.qual", + "org.checkerframework.common.reflection.qual", + "org.checkerframework.common.subtyping.qual", + "org.checkerframework.common.util.report.qual", + "org.checkerframework.common.value.qual", + "org.checkerframework.dataflow.qual", + "org.checkerframework.framework.qual", + "org.checkerframework.framework.util" + ], "sha256": "015224a4b1dc6de6da053273d4da7d39cfea20e63038169fc45ac0d1dc9c5938", "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.11.1/checker-qual-2.11.1.jar" }, @@ -195,14 +281,9 @@ "mirror_urls": [ "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.11.1/checker-qual-2.11.1-sources.jar" ], + "packages": [], "sha256": "7d3b990687be9b980a9dc7853f4b0f279eb437e28efe3c9903acaf20450f55b5", "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.11.1/checker-qual-2.11.1-sources.jar" - }, - { - "coord": "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "dependencies": [], - "directDependencies": [], - "file": null } ], "version": "0.1.0" diff --git a/private/rules/java_export.bzl b/private/rules/java_export.bzl index 24eb2240..355e67fe 100644 --- a/private/rules/java_export.bzl +++ b/private/rules/java_export.bzl @@ -35,7 +35,6 @@ def java_export( * `{version}`: Replaced by the maven coordinates version. * `{type}`: Replaced by the maven coordinates type, if present (defaults to "jar") * `{scope}`: Replaced by the maven coordinates type, if present (defaults to "compile") - * `{parent}`: Replaced by a ``, ``, and `` set of tags. * `{dependencies}`: Replaced by a list of maven dependencies directly relied upon by java_library targets within the artifact. @@ -188,9 +187,9 @@ def maven_export( name = "%s.bom-fragment" % name, maven_coordinates = maven_coordinates, artifact = ":%s" % lib_name, - src_artifact = "%s-maven-source" % name, - javadoc_artifact = None if "no-javadocs" in tags else "%s-docs" % name, - pom_template = pom_template, + src_artifact = ":%s-maven-source" % name, + javadoc_artifact = None if "no-javadocs" in tags else ":%s-docs" % name, + pom = ":%s-pom" % name, testonly = testonly, tags = tags, visibility = visibility, diff --git a/private/rules/maven_bom.bzl b/private/rules/maven_bom.bzl index fbcdc303..3a9af789 100644 --- a/private/rules/maven_bom.bzl +++ b/private/rules/maven_bom.bzl @@ -1,5 +1,5 @@ load(":maven_bom_fragment.bzl", "MavenBomFragmentInfo") -load(":maven_publish.bzl", "MavenPublishInfo") +load(":maven_publish.bzl", "maven_publish") load(":maven_utils.bzl", "generate_pom", "unpack_coordinates") def _label(label_or_string): @@ -23,132 +23,23 @@ def _label(label_or_string): def _maven_bom_impl(ctx): fragments = [f[MavenBomFragmentInfo] for f in ctx.attr.fragments] - # We only want to include an entry in the BOM if it's a dependency of - # more than one `java_export` we're wrapping. - - # Begin by construct a mapping between a particular dependency and "things that depend upon it" - dep2export = {} - for fragment in fragments: - for dep in fragment.maven_info.maven_deps.to_list(): - existing = dep2export.get(dep, []) - existing.append(fragment.coordinates) - dep2export[dep] = existing - - # Next, gather those dependencies that have more than one "things that depend upon it" - shared_deps = [dep for (dep, list) in dep2export.items() if len(list) > 1] - coordinates_to_exclude = [f.coordinates for f in fragments] - shared_deps = [dep for dep in shared_deps if dep not in coordinates_to_exclude] - - # And, finally, let's generate the publishing script - maven_repo = ctx.var.get("maven_repo", "''") - gpg_sign = ctx.var.get("gpg_sign", "'false'") - user = ctx.var.get("maven_user", "''") - password = ctx.var.get("maven_password", "''") - - upload_script = "#!/usr/bin/env bash\nset -eufo pipefail\n\n" + combined_deps = depset(transitive = [f.maven_info.maven_deps for f in fragments]) bom = generate_pom( ctx, coordinates = ctx.attr.maven_coordinates, - versioned_dep_coordinates = shared_deps, + versioned_dep_coordinates = [f[MavenBomFragmentInfo].coordinates for f in ctx.attr.fragments], pom_template = ctx.file.pom_template, out_name = "%s.xml" % ctx.label.name, - indent = 12, ) - files = [bom] - upload_script += """echo "Uploading {coordinates} to {maven_repo}" -{uploader} {maven_repo} {gpg_sign} {user} {password} {coordinates} {pom} '' '' '' -""".format( - uploader = ctx.executable._uploader.short_path, - coordinates = ctx.attr.maven_coordinates, - gpg_sign = gpg_sign, - maven_repo = maven_repo, - password = password, - user = user, - pom = bom.short_path, - ) - - # Now generate a `pom.xml` for each `java_export` we've been given - poms = {} - for fragment in fragments: - info = fragment.maven_info - versioned = [] - unversioned = [] - for dep in fragment.maven_info.maven_deps.to_list(): - if dep in shared_deps: - unversioned.append(dep) - else: - versioned.append(dep) - - unpacked = unpack_coordinates(fragment.coordinates) - - pom = generate_pom( - ctx, - coordinates = fragment.coordinates, - parent = ctx.attr.maven_coordinates, - versioned_dep_coordinates = versioned, - unversioned_dep_coordinates = unversioned, - pom_template = fragment.pom_template, - out_name = "%s-%s-pom.xml" % (unpacked.groupId, unpacked.artifactId), - ) - poms.update({"%s-pom" % fragment.coordinates: pom}) - - javadocs_short_path = fragment.javadocs.short_path if fragment.javadocs else "''" - - upload_script += """echo "Uploading {coordinates} to {maven_repo}" -{uploader} {maven_repo} {gpg_sign} {user} {password} {coordinates} {pom} {artifact_jar} {source_jar} {javadoc} -""".format( - uploader = ctx.executable._uploader.short_path, - coordinates = info.coordinates, - gpg_sign = gpg_sign, - maven_repo = maven_repo, - password = password, - user = user, - pom = pom.short_path, - artifact_jar = fragment.artifact.short_path, - source_jar = fragment.srcs.short_path, - javadoc = javadocs_short_path, - ) - files.extend([pom, fragment.artifact, fragment.srcs]) - if fragment.javadocs: - files.append(fragment.javadocs) - - executable = ctx.actions.declare_file("%s-upload.sh" % ctx.label.name) - ctx.actions.write( - output = executable, - is_executable = True, - content = upload_script, - ) - - pom2outputgroup = {coord: depset([pom]) for (coord, pom) in poms.items()} - return [ - DefaultInfo( - files = depset([bom] + poms.values() + [executable]), - executable = executable, - runfiles = ctx.runfiles( - files = files, - collect_data = True, - ).merge(ctx.attr._uploader[DefaultInfo].data_runfiles), - ), - MavenPublishInfo( - coordinates = ctx.attr.maven_coordinates, - artifact_jar = None, - javadocs = None, - source_jar = None, - pom = bom, - ), - OutputGroupInfo( - bom = [bom], - **pom2outputgroup - ), + DefaultInfo(files = depset([bom])), ] _maven_bom = rule( _maven_bom_impl, doc = """Create a Maven BOM file (`pom.xml`) for the given targets.""", - executable = True, attrs = { "maven_coordinates": attr.string( mandatory = True, @@ -158,12 +49,6 @@ _maven_bom = rule( default = "//private/templates:bom.tpl", allow_single_file = True, ), - "_uploader": attr.label( - executable = True, - cfg = "host", - default = "//private/tools/java/rules/jvm/external/maven:MavenPublisher", - allow_files = True, - ), "fragments": attr.label_list( providers = [ [MavenBomFragmentInfo], @@ -172,30 +57,100 @@ _maven_bom = rule( }, ) -def maven_bom(name, maven_coordinates, java_exports, tags = None, testonly = None, visibility = None): - """Generates a Maven BOM `pom.xml` file. +def _maven_dependencies_bom_impl(ctx): + fragments = [f[MavenBomFragmentInfo] for f in ctx.attr.fragments] + + combined_deps = depset(transitive = [f.maven_info.maven_deps for f in fragments]) + + unpacked = unpack_coordinates(ctx.attr.maven_coordinates) + dependencies_bom = generate_pom( + ctx, + coordinates = ctx.attr.maven_coordinates, + versioned_dep_coordinates = combined_deps.to_list() + ["%s:%s:pom:import:%s" % (unpacked.groupId, unpacked.artifactId, unpacked.version)], + pom_template = ctx.file.pom_template, + out_name = "%s.xml" % ctx.label.name, + indent = 12, + ) + + return [ + DefaultInfo(files = depset([dependencies_bom])), + ] + +_maven_dependencies_bom = rule( + _maven_dependencies_bom_impl, + doc = """Create a Maven dependencies `pom.xml` for the given targets.""", + attrs = { + "maven_coordinates": attr.string( + mandatory = True, + ), + "pom_template": attr.label( + doc = "Template file to use for the pom.xml", + default = "//private/templates:dependencies-bom.tpl", + allow_single_file = True, + ), + "fragments": attr.label_list( + providers = [ + [MavenBomFragmentInfo], + ], + ), + }, +) - The generated BOM will contain maven dependencies that are shared between two - or more of the `java_exports`. This will also generate `pom.xml` files for - each of the `java_exports`. Within those `pom.xml`s, only dependencies that are - unique to the `java_export` will have the `version` tag. Dependencies which are - listed in the BOM will omit the `version` tag. +def maven_bom( + name, + maven_coordinates, + java_exports, + bom_pom_template = None, + dependencies_maven_coordinates = None, + dependencies_pom_template = None, + tags = None, + testonly = None, + visibility = None): + """Generates a Maven BOM `pom.xml` file and an optional "dependencies" `pom.xml`. + + The generated BOM will contain a list of all the coordinates of the + `java_export` targets in the `java_exports` parameters. An optional + dependencies artifact will be created if the parameter + `dependencies_maven_coordinates` is set. + + Both the BOM and dependencies artifact can be templatised to support + customisation, but a sensible default template will be used if none is + provided. The template used is derived from the (optional) + `pom_template` argument, and the following substitutions are performed on + the template file: + + * `{groupId}`: Replaced with the maven coordinates group ID. + * `{artifactId}`: Replaced with the maven coordinates artifact ID. + * `{version}`: Replaced by the maven coordinates version. + * `{dependencies}`: Replaced by a list of maven dependencies directly relied upon + by java_library targets within the artifact. + + To publish, call the implicit `*.publish` target(s). The maven repository may accessed locally using a `file://` URL, or remotely using an `https://` URL. The following flags may be set using `--define`: - gpg_sign: Whether to sign artifacts using GPG - maven_repo: A URL for the repo to use. May be "https" or "file". - maven_user: The user name to use when uploading to the maven repository. - maven_password: The password to use when uploading to the maven repository. + * `gpg_sign`: Whether to sign artifacts using GPG + * `maven_repo`: A URL for the repo to use. May be "https" or "file". + * `maven_user`: The user name to use when uploading to the maven repository. + * `maven_password`: The password to use when uploading to the maven repository. When signing with GPG, the current default key is used. - Args: - name: A unique name for this rule. - maven_coordinates: The maven coordinates of this BOM in `groupId:artifactId:version` form. - java_exports: A list of `java_export` targets that are used to generate the BOM. + Generated rules: + * `name`: The BOM file itself. + * `name.publish`: To be executed by `bazel run` to publish the BOM to a maven repo + * `name-dependencies`: The BOM file for the dependencies `pom.xml`. Only generated if `dependencies_maven_coordinates` is set. + * `name-dependencies.publish`: To be executed by `bazel run` to publish the dependencies `pom.xml` to a maven rpo. Only generated if `dependencies_maven_coordinates` is set. + + Args: + name: A unique name for this rule. + maven_coordinates: The maven coordinates of this BOM in `groupId:artifactId:version` form. + bom_pom_template: A template used for generating the `pom.xml` of the BOM at `maven_coordinates` (optional) + dependencies_maven_coordinates: The maven coordinates of a dependencies artifact to generate in GAV format. If empty, none will be generated. (optional) + dependencies_pom_template: A template used for generating the `pom.xml` of the dependencies artifact at `dependencies_maven_coordinates` (optional) + java_exports: A list of `java_export` targets that are used to generate the BOM. """ fragments = [] labels = [_label(je) for je in java_exports] @@ -204,7 +159,38 @@ def maven_bom(name, maven_coordinates, java_exports, tags = None, testonly = Non _maven_bom( name = name, maven_coordinates = maven_coordinates, + pom_template = bom_pom_template, fragments = fragments, tags = tags, + testonly = testonly, visibility = visibility, ) + + maven_publish( + name = "%s.publish" % name, + coordinates = maven_coordinates, + pom = name, + tags = tags, + testonly = testonly, + visibility = visibility, + ) + + if dependencies_maven_coordinates: + _maven_dependencies_bom( + name = "%s-dependencies" % name, + maven_coordinates = dependencies_maven_coordinates, + pom_template = dependencies_pom_template, + fragments = fragments, + tags = tags, + testonly = testonly, + visibility = visibility, + ) + + maven_publish( + name = "%s-dependencies.publish" % name, + coordinates = dependencies_maven_coordinates, + pom = "%s-dependencies" % name, + tags = tags, + testonly = testonly, + visibility = visibility, + ) diff --git a/private/rules/maven_bom_fragment.bzl b/private/rules/maven_bom_fragment.bzl index 70bfd6fb..9c3c2d26 100644 --- a/private/rules/maven_bom_fragment.bzl +++ b/private/rules/maven_bom_fragment.bzl @@ -6,7 +6,7 @@ MavenBomFragmentInfo = provider( "artifact": "The `maven_project_jar` that forms the main artifact", "srcs": "The src-jar of the artifact", "javadocs": "The javadocs of the artifact. May be `None`", - "pom_template": "The `pom.xml` template file", + "pom": "The `pom.xml` template file", "maven_info": "The `MavenInfo` of `artifact`", }, ) @@ -18,7 +18,7 @@ def _maven_bom_fragment_impl(ctx): artifact = ctx.file.artifact, srcs = ctx.file.src_artifact, javadocs = ctx.file.javadoc_artifact, - pom_template = ctx.file.pom_template, + pom = ctx.file.pom, maven_info = ctx.attr.artifact[MavenInfo], ), ] @@ -50,10 +50,9 @@ maven_bom_fragment = rule( doc = """The javadoc jar generated from the `artifact`""", allow_single_file = True, ), - "pom_template": attr.label( - doc = """The template to use when generating the `pom.xml` file""", + "pom": attr.label( + doc = "The pom file of the generated `artifact`", allow_single_file = True, - default = "//private/templates:pom-with-parent.tpl", ), }, provides = [ diff --git a/private/rules/maven_publish.bzl b/private/rules/maven_publish.bzl index fa0d481e..22b24649 100644 --- a/private/rules/maven_publish.bzl +++ b/private/rules/maven_publish.bzl @@ -22,6 +22,8 @@ def _maven_publish_impl(ctx): user = ctx.var.get("maven_user", "''") password = ctx.var.get("maven_password", "''") + artifacts_short_path = ctx.file.artifact_jar.short_path if ctx.file.artifact_jar else "''" + source_short_path = ctx.file.source_jar.short_path if ctx.file.source_jar else "''" javadocs_short_path = ctx.file.javadocs.short_path if ctx.file.javadocs else "''" ctx.actions.write( @@ -35,17 +37,17 @@ def _maven_publish_impl(ctx): password = password, user = user, pom = ctx.file.pom.short_path, - artifact_jar = ctx.file.artifact_jar.short_path, - source_jar = ctx.file.source_jar.short_path, + artifact_jar = artifacts_short_path, + source_jar = source_short_path, javadoc = javadocs_short_path, ), ) - files = [ - ctx.file.artifact_jar, - ctx.file.pom, - ctx.file.source_jar, - ] + files = [ctx.file.pom] + if ctx.file.artifact_jar: + files.append(ctx.file.artifact_jar) + if ctx.file.source_jar: + files.append(ctx.file.source_jar) if ctx.file.javadocs: files.append(ctx.file.javadocs) @@ -95,16 +97,14 @@ When signing with GPG, the current default key is used. allow_single_file = True, ), "artifact_jar": attr.label( - mandatory = True, allow_single_file = True, ), "source_jar": attr.label( - mandatory = True, allow_single_file = True, ), "_uploader": attr.label( executable = True, - cfg = "host", + cfg = "exec", default = "//private/tools/java/rules/jvm/external/maven:MavenPublisher", allow_files = True, ), diff --git a/private/templates/bom.tpl b/private/templates/bom.tpl index dfce93b2..a430a007 100644 --- a/private/templates/bom.tpl +++ b/private/templates/bom.tpl @@ -7,7 +7,6 @@ {artifactId} {version} pom - import diff --git a/private/templates/pom-with-parent.tpl b/private/templates/dependencies-bom.tpl similarity index 76% rename from private/templates/pom-with-parent.tpl rename to private/templates/dependencies-bom.tpl index ce5634be..a430a007 100644 --- a/private/templates/pom-with-parent.tpl +++ b/private/templates/dependencies-bom.tpl @@ -3,15 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - -{parent} - - {groupId} {artifactId} {version} + pom - + + {dependencies} - + + diff --git a/tests/integration/maven_bom/BUILD.bazel b/tests/integration/maven_bom/BUILD.bazel index 02588d04..6f8dbc7d 100644 --- a/tests/integration/maven_bom/BUILD.bazel +++ b/tests/integration/maven_bom/BUILD.bazel @@ -3,6 +3,7 @@ load("//:defs.bzl", "artifact", "java_export", "maven_bom") maven_bom( name = "bom", + dependencies_maven_coordinates = "com.example:bom-deps:1.0.0", java_exports = [ ":one-dep", ":two-deps", @@ -10,40 +11,16 @@ maven_bom( maven_coordinates = "com.example:bom:1.0.0", ) -filegroup( - name = "generated-one-dep-pom", - srcs = [":bom"], - output_group = "com.example:one-dep:1.0.0-pom", -) - -diff_test( - name = "validate-one-dep", - file1 = ":generated-one-dep-pom", - file2 = "one-dep.golden.xml", -) - -filegroup( - name = "generated-two-deps-pom", - srcs = [":bom"], - output_group = "com.example:two-deps:1.0.0-pom", -) - diff_test( - name = "validate-two-deps", - file1 = ":generated-two-deps-pom", - file2 = "two-deps.golden.xml", -) - -filegroup( - name = "bom-file", - srcs = [":bom"], - output_group = "bom", + name = "validate-bom", + file1 = ":bom", + file2 = "bom.golden.xml", ) diff_test( - name = "validate-bom", - file1 = ":bom-file", - file2 = "bom.golden.xml", + name = "validate-dependencies-bom", + file1 = ":bom-dependencies", + file2 = "bom-dependencies.golden.xml", ) java_export( diff --git a/tests/integration/maven_bom/bom-dependencies.golden.xml b/tests/integration/maven_bom/bom-dependencies.golden.xml new file mode 100755 index 00000000..3888e0c4 --- /dev/null +++ b/tests/integration/maven_bom/bom-dependencies.golden.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + com.example + bom-deps + 1.0.0 + pom + + + + + com.example + bom-deps + 1.0.0 + pom + import + + + com.google.guava + guava + 27.0-jre + + + org.hamcrest + hamcrest + 2.1 + + + + diff --git a/tests/integration/maven_bom/bom.golden.xml b/tests/integration/maven_bom/bom.golden.xml index 2ccf43ac..908a020e 100755 --- a/tests/integration/maven_bom/bom.golden.xml +++ b/tests/integration/maven_bom/bom.golden.xml @@ -7,15 +7,19 @@ bom 1.0.0 pom - import - - com.google.guava - guava - 27.0-jre - + + com.example + one-dep + 1.0.0 + + + com.example + two-deps + 1.0.0 + diff --git a/tests/integration/maven_bom/one-dep.golden.xml b/tests/integration/maven_bom/one-dep.golden.xml deleted file mode 100755 index 8114e94a..00000000 --- a/tests/integration/maven_bom/one-dep.golden.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - 4.0.0 - - - com.example - bom - 1.0.0 - - - com.example - one-dep - 1.0.0 - - - - com.google.guava - guava - - - diff --git a/tests/integration/maven_bom/two-deps.golden.xml b/tests/integration/maven_bom/two-deps.golden.xml deleted file mode 100755 index 47a2322c..00000000 --- a/tests/integration/maven_bom/two-deps.golden.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - 4.0.0 - - - com.example - bom - 1.0.0 - - - com.example - two-deps - 1.0.0 - - - - org.hamcrest - hamcrest - 2.1 - - - com.google.guava - guava - - -