|  | # 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") |