| # 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 Skylark: | 
 | # 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) | 
 |  | 
 | def _file_exists(ctx, filename): | 
 |     return _execute(ctx, "[[ -f %s ]] && exit 0 || exit 1" % filename).return_code == 0 | 
 |  | 
 | # 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 compatability 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") |