| # Copyright 2016 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # Implementations of Maven rules in Starlark: |
| # 1) maven_jar(name, artifact, repository, sha1, settings) |
| # The API of this is largely the same as the native maven_jar rule, |
| # except for the server attribute, which is not implemented. The optional |
| # settings supports passing a custom Maven settings.xml to download the JAR. |
| # 2) maven_aar(name, artifact, sha1, settings) |
| # The API of this rule is the same as maven_jar except that the artifact must |
| # be the Maven coordinate of an AAR and it does not support the historical |
| # repository and server attributes. |
| # 3) maven_dependency_plugin() |
| # This rule downloads the maven-dependency-plugin used internally |
| # for testing and the implementation for the fetching of artifacts. |
| # |
| # Maven coordinates are expected to be in this form: |
| # groupId:artifactId:version[:packaging][:classifier] |
| # |
| # Installation requirements prior to using this rule: |
| # 1) Maven binary: `mvn` |
| # 2) Maven plugin: `maven-dependency-plugin:2.10` |
| # Get it: $ mvn org.apache.maven.plugins:maven-dependency-plugin:2.10:get \ |
| # -Dartifact=org.apache.maven.plugins:maven-dependency-plugin:2.10 \ |
| # -Dmaven.repo.local=$HOME/.m2/repository # or specify your own local repository |
| |
| """Rules for retrieving Maven dependencies (experimental)""" |
| |
| MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2" |
| |
| # Binary dependencies needed for running the bash commands |
| DEPS = ["mvn", "openssl", "awk"] |
| |
| MVN_PLUGIN = "org.apache.maven.plugins:maven-dependency-plugin:2.10" |
| |
| def _execute(ctx, command): |
| return ctx.execute(["bash", "-c", """ |
| set -ex |
| %s""" % command]) |
| |
| # Fail fast |
| def _check_dependencies(ctx): |
| for dep in DEPS: |
| if ctx.which(dep) == None: |
| fail("%s requires %s as a dependency. Please check your PATH." % (ctx.name, dep)) |
| |
| def _validate_attr(ctx): |
| if hasattr(ctx.attr, "server") and (ctx.attr.server != None): |
| fail("%s specifies a 'server' attribute which is currently not supported." % ctx.name) |
| |
| def _artifact_dir(coordinates): |
| return "/".join(coordinates.group_id.split(".") + |
| [coordinates.artifact_id, coordinates.version]) |
| |
| # Creates a struct containing the different parts of an artifact's FQN. |
| # If the fully_qualified_name does not specify a packaging and the rule does |
| # not set a default packaging then JAR is assumed. |
| def _create_coordinates(fully_qualified_name, packaging = "jar"): |
| parts = fully_qualified_name.split(":") |
| classifier = None |
| |
| if len(parts) == 3: |
| group_id, artifact_id, version = parts |
| |
| # Updates the FQN with the default packaging so that the Maven plugin |
| # downloads the correct artifact. |
| fully_qualified_name = "%s:%s" % (fully_qualified_name, packaging) |
| elif len(parts) == 4: |
| group_id, artifact_id, version, packaging = parts |
| elif len(parts) == 5: |
| group_id, artifact_id, version, packaging, classifier = parts |
| else: |
| fail("Invalid fully qualified name for artifact: %s" % fully_qualified_name) |
| |
| return struct( |
| fully_qualified_name = fully_qualified_name, |
| group_id = group_id, |
| artifact_id = artifact_id, |
| packaging = packaging, |
| classifier = classifier, |
| version = version, |
| ) |
| |
| # NOTE: Please use this method to define ALL paths that the maven_* |
| # rules use. Doing otherwise will lead to inconsistencies and/or errors. |
| # |
| # CONVENTION: *_path refers to files, *_dir refers to directories. |
| def _create_paths(ctx, coordinates): |
| """Creates a struct that contains the paths to create the cache WORKSPACE""" |
| |
| # e.g. guava-18.0.jar |
| artifact_filename = "%s-%s" % ( |
| coordinates.artifact_id, |
| coordinates.version, |
| ) |
| if coordinates.classifier: |
| artifact_filename += "-" + coordinates.classifier |
| artifact_filename += "." + coordinates.packaging |
| sha1_filename = "%s.sha1" % artifact_filename |
| |
| # e.g. com/google/guava/guava/18.0 |
| relative_artifact_dir = _artifact_dir(coordinates) |
| |
| # The symlink to the actual artifact is stored in this dir, along with the |
| # BUILD file. The dir has the same name as the packaging to support syntax |
| # like @guava//jar and @google_play_services//aar. |
| symlink_dir = coordinates.packaging |
| |
| m2 = ".m2" |
| m2_repo = "/".join([m2, "repository"]) # .m2/repository |
| |
| return struct( |
| artifact_filename = artifact_filename, |
| sha1_filename = sha1_filename, |
| symlink_dir = ctx.path(symlink_dir), |
| |
| # e.g. external/com_google_guava_guava/ \ |
| # .m2/repository/com/google/guava/guava/18.0/guava-18.0.jar |
| artifact_path = ctx.path("/".join([m2_repo, relative_artifact_dir, artifact_filename])), |
| artifact_dir = ctx.path("/".join([m2_repo, relative_artifact_dir])), |
| sha1_path = ctx.path("/".join([m2_repo, relative_artifact_dir, sha1_filename])), |
| |
| # e.g. external/com_google_guava_guava/jar/guava-18.0.jar |
| symlink_artifact_path = ctx.path("/".join([symlink_dir, artifact_filename])), |
| ) |
| |
| _maven_jar_build_file_template = """ |
| # DO NOT EDIT: automatically generated BUILD file for maven_jar rule {rule_name} |
| |
| java_import( |
| name = 'jar', |
| jars = ['{artifact_filename}'], |
| deps = [ |
| {deps_string} |
| ], |
| visibility = ['//visibility:public'] |
| ) |
| |
| filegroup( |
| name = 'file', |
| srcs = ['{artifact_filename}'], |
| visibility = ['//visibility:public'] |
| )\n""" |
| |
| _maven_aar_build_file_template = """ |
| # DO NOT EDIT: automatically generated BUILD file for maven_aar rule {rule_name} |
| |
| aar_import( |
| name = 'aar', |
| aar = '{artifact_filename}', |
| deps = [ |
| {deps_string} |
| ], |
| visibility = ['//visibility:public'], |
| ) |
| |
| filegroup( |
| name = 'file', |
| srcs = ['{artifact_filename}'], |
| visibility = ['//visibility:public'] |
| )\n""" |
| |
| # Provides the syntax "@jar_name//jar" for dependencies |
| def _generate_build_file(ctx, template, paths): |
| deps_string = "\n".join(["'%s'," % dep for dep in ctx.attr.deps]) |
| contents = template.format( |
| rule_name = ctx.name, |
| artifact_filename = paths.artifact_filename, |
| deps_string = deps_string, |
| ) |
| ctx.file("%s/BUILD" % paths.symlink_dir, contents, False) |
| |
| # Constructs the maven command to retrieve the dependencies from remote |
| # repositories using the dependency plugin, and executes it. |
| def _mvn_download(ctx, paths, fully_qualified_name): |
| # If a custom settings file exists, we'll use that. If not, Maven will use the default settings. |
| mvn_flags = "" |
| if hasattr(ctx.attr, "settings") and ctx.attr.settings != None: |
| ctx.symlink(ctx.attr.settings, "settings.xml") |
| mvn_flags += "-s %s " % "settings.xml" |
| |
| # dependency:get step. Downloads the artifact into the local repository. |
| mvn_get = MVN_PLUGIN + ":get" |
| mvn_artifact = "-Dartifact=%s" % fully_qualified_name |
| mvn_transitive = "-Dtransitive=false" |
| if hasattr(ctx.attr, "repository") and ctx.attr.repository != "": |
| mvn_flags += "-Dmaven.repo.remote=%s " % ctx.attr.repository |
| command = " ".join(["mvn", mvn_flags, mvn_get, mvn_transitive, mvn_artifact]) |
| exec_result = _execute(ctx, command) |
| if exec_result.return_code != 0: |
| fail("%s\n%s\nFailed to fetch Maven dependency" % (exec_result.stdout, exec_result.stderr)) |
| |
| # dependency:copy step. Moves the artifact from the local repository into //external. |
| mvn_copy = MVN_PLUGIN + ":copy" |
| mvn_output_dir = "-DoutputDirectory=%s" % paths.artifact_dir |
| command = " ".join(["mvn", mvn_flags, mvn_copy, mvn_artifact, mvn_output_dir]) |
| exec_result = _execute(ctx, command) |
| if exec_result.return_code != 0: |
| fail("%s\n%s\nFailed to fetch Maven dependency" % (exec_result.stdout, exec_result.stderr)) |
| |
| def _check_sha1(ctx, paths, sha1): |
| actual_sha1 = _execute(ctx, "openssl sha1 %s | awk '{printf $2}'" % paths.artifact_path).stdout |
| |
| if sha1.lower() != actual_sha1.lower(): |
| fail(("{rule_name} has SHA-1 of {actual_sha1}, " + |
| "does not match expected SHA-1 ({expected_sha1})").format( |
| rule_name = ctx.name, |
| expected_sha1 = sha1, |
| actual_sha1 = actual_sha1, |
| )) |
| else: |
| _execute(ctx, "echo %s %s > %s" % (sha1, paths.artifact_path, paths.sha1_path)) |
| |
| def _maven_artifact_impl(ctx, default_rule_packaging, build_file_template): |
| # Ensure that we have all of the dependencies installed |
| _check_dependencies(ctx) |
| |
| # Provide warnings and errors about attributes |
| _validate_attr(ctx) |
| |
| # Create a struct to contain the different parts of the artifact FQN |
| coordinates = _create_coordinates(ctx.attr.artifact, default_rule_packaging) |
| |
| # Create a struct to store the relative and absolute paths needed for this rule |
| paths = _create_paths(ctx, coordinates) |
| |
| _generate_build_file( |
| ctx = ctx, |
| template = build_file_template, |
| paths = paths, |
| ) |
| |
| if _execute(ctx, "mkdir -p %s" % paths.symlink_dir).return_code != 0: |
| fail("%s: Failed to create dirs in execution root.\n" % ctx.name) |
| |
| # Download the artifact |
| _mvn_download( |
| ctx = ctx, |
| paths = paths, |
| fully_qualified_name = coordinates.fully_qualified_name, |
| ) |
| |
| if (ctx.attr.sha1 != ""): |
| _check_sha1( |
| ctx = ctx, |
| paths = paths, |
| sha1 = ctx.attr.sha1, |
| ) |
| |
| ctx.symlink(paths.artifact_path, paths.symlink_artifact_path) |
| |
| _common_maven_rule_attrs = { |
| "artifact": attr.string( |
| default = "", |
| mandatory = True, |
| ), |
| "sha1": attr.string(default = ""), |
| "settings": attr.label(default = None), |
| # Allow the user to specify deps for the generated java_import or aar_import |
| # since maven_jar and maven_aar do not automatically pull in transitive |
| # dependencies. |
| "deps": attr.label_list(), |
| } |
| |
| def _maven_jar_impl(ctx): |
| _maven_artifact_impl(ctx, "jar", _maven_jar_build_file_template) |
| |
| def _maven_aar_impl(ctx): |
| _maven_artifact_impl(ctx, "aar", _maven_aar_build_file_template) |
| |
| maven_jar = repository_rule( |
| implementation = _maven_jar_impl, |
| attrs = dict(_common_maven_rule_attrs.items() + { |
| # Needed for compatibility reasons with the native maven_jar rule. |
| "repository": attr.string(default = ""), |
| "server": attr.label(default = None), |
| }.items()), |
| local = False, |
| ) |
| |
| maven_aar = repository_rule( |
| implementation = _maven_aar_impl, |
| attrs = _common_maven_rule_attrs, |
| local = False, |
| ) |
| |
| def _maven_dependency_plugin_impl(ctx): |
| _BUILD_FILE = """ |
| # DO NOT EDIT: automatically generated BUILD file for maven_dependency_plugin |
| |
| filegroup( |
| name = 'files', |
| srcs = glob(['**']), |
| visibility = ['//visibility:public'] |
| ) |
| """ |
| ctx.file("BUILD", _BUILD_FILE, False) |
| |
| _SETTINGS_XML = """ |
| <!-- # DO NOT EDIT: automatically generated settings.xml for maven_dependency_plugin --> |
| <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 |
| https://maven.apache.org/xsd/settings-1.0.0.xsd"> |
| <localRepository>{localRepository}</localRepository> |
| <mirrors> |
| <mirror> |
| <id>central</id> |
| <url>{mirror}</url> |
| <mirrorOf>*,default</mirrorOf> |
| </mirror> |
| </mirrors> |
| </settings> |
| """.format( |
| localRepository = ctx.path("repository"), |
| mirror = MAVEN_CENTRAL_URL, |
| ) |
| settings_path = ctx.path("settings.xml") |
| ctx.file("%s" % settings_path, _SETTINGS_XML, False) |
| |
| # Download the plugin with transitive dependencies |
| mvn_flags = "-s %s" % settings_path |
| mvn_get = MVN_PLUGIN + ":get" |
| mvn_artifact = "-Dartifact=%s" % MVN_PLUGIN |
| command = " ".join(["mvn", mvn_flags, mvn_get, mvn_artifact]) |
| |
| exec_result = _execute(ctx, command) |
| if exec_result.return_code != 0: |
| fail("%s\nFailed to fetch Maven dependency" % exec_result.stderr) |
| |
| _maven_dependency_plugin = repository_rule( |
| implementation = _maven_dependency_plugin_impl, |
| ) |
| |
| def maven_dependency_plugin(): |
| _maven_dependency_plugin(name = "m2") |