| # Copyright 2018 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. |
| |
| """ |
| 'jvm_import_external' offers additional functionality above what maven_jar has to offer. |
| In addition to downloading the jars, it allows to define this jar's dependencies. |
| thus it enables the explicit definition of the entire transitive dependency graph. |
| |
| The rule achieves this by writing 'import' build rules in BUILD files next to the downloaded jars. |
| The name of the underlying 'import' rule needs to be specified. |
| An optional 'load' statement can also be provided, along with any other relevant custom attribute. |
| These import rules must have the following attributes: |
| - "jars" |
| - "deps" |
| - "runtime_deps" |
| - "exports" |
| |
| the following macros are defined below that utilize jvm_import_external: |
| |
| - jvm_maven_import_external - offers a 'maven' like api for identifying jars using 'artifact' format |
| - java_import_external - uses `java_import` as the underlying build rule |
| """ |
| |
| _HEADER = "# DO NOT EDIT: generated by jvm_import_external()" |
| |
| _PASS_PROPS = ( |
| "neverlink", |
| "testonly_", |
| "visibility", |
| "exports", |
| "runtime_deps", |
| "deps", |
| "tags", |
| ) |
| |
| def _jvm_import_external(repository_ctx): |
| """Implementation of `java_import_external` rule.""" |
| if (repository_ctx.attr.generated_linkable_rule_name and |
| not repository_ctx.attr.neverlink): |
| fail("Only use generated_linkable_rule_name if neverlink is set") |
| name = repository_ctx.attr.generated_rule_name or repository_ctx.name |
| urls = repository_ctx.attr.artifact_urls |
| sha = repository_ctx.attr.artifact_sha256 |
| extension = repository_ctx.attr.rule_metadata["extension"] |
| file_extension = "." + extension |
| path = repository_ctx.name + file_extension |
| for url in urls: |
| if url.endswith(file_extension): |
| path = url[url.rindex("/") + 1:] |
| break |
| srcurls = repository_ctx.attr.srcjar_urls |
| srcsha = repository_ctx.attr.srcjar_sha256 |
| srcpath = repository_ctx.name + "-src.jar" if srcurls else "" |
| for url in srcurls: |
| if url.endswith(file_extension): |
| srcpath = url[url.rindex("/") + 1:].replace("-sources.jar", "-src.jar") |
| break |
| lines = [_HEADER, ""] |
| if repository_ctx.attr.rule_load: |
| lines.append(repository_ctx.attr.rule_load) |
| lines.append("") |
| if repository_ctx.attr.default_visibility: |
| lines.append("package(default_visibility = %s)" % ( |
| repository_ctx.attr.default_visibility |
| )) |
| lines.append("") |
| lines.append("licenses(%s)" % repr(repository_ctx.attr.licenses)) |
| lines.append("") |
| lines.extend(_serialize_given_rule_import( |
| name = name, |
| additional_rule_attrs = repository_ctx.attr.additional_rule_attrs, |
| attrs = repository_ctx.attr, |
| import_attr = repository_ctx.attr.rule_metadata["import_attr"], |
| path = path, |
| props = _PASS_PROPS, |
| rule_name = repository_ctx.attr.rule_name, |
| srcpath = srcpath, |
| )) |
| if (repository_ctx.attr.neverlink and |
| repository_ctx.attr.generated_linkable_rule_name): |
| lines.extend(_serialize_given_rule_import( |
| name = repository_ctx.attr.generated_linkable_rule_name, |
| additonal_rule_attrs = repository_ctx.attr.additional_rule_attrs, |
| attrs = repository_ctx.attr, |
| import_attr = repository_ctx.attr.rule_metadata["import_attr"], |
| path = path, |
| props = [p for p in _PASS_PROPS if p != "neverlink"], |
| rule_name = repository_ctx.attr.rule_name, |
| srcpath = srcpath, |
| )) |
| extra = repository_ctx.attr.extra_build_file_content |
| if extra: |
| lines.append(extra) |
| if not extra.endswith("\n"): |
| lines.append("") |
| repository_ctx.download(urls, path, sha) |
| if srcurls: |
| repository_ctx.download(srcurls, srcpath, srcsha) |
| repository_ctx.file("BUILD", "\n".join(lines)) |
| repository_ctx.file("%s/BUILD" % extension, "\n".join([ |
| _HEADER, |
| "", |
| "package(default_visibility = %r)" % ( |
| repository_ctx.attr.visibility or |
| repository_ctx.attr.default_visibility |
| ), |
| "", |
| "alias(", |
| " name = \"%s\"," % extension, |
| " actual = \"@%s\"," % repository_ctx.name, |
| ")", |
| "", |
| ])) |
| |
| # This method is public for usage in android.bzl macros |
| def convert_artifact_coordinate_to_urls(artifact, server_urls, packaging): |
| """This function converts a Maven artifact coordinate into URLs.""" |
| |
| parts = artifact.split(":") |
| group_id_part = parts[0].replace(".", "/") |
| artifact_id = parts[1] |
| version = parts[2] |
| classifier_part = "" |
| if len(parts) == 4: |
| packaging = parts[2] |
| version = parts[3] |
| elif len(parts) == 5: |
| packaging = parts[2] |
| classifier_part = "-" + parts[3] |
| version = parts[4] |
| |
| final_name = artifact_id + "-" + version + classifier_part + "." + packaging |
| url_suffix = group_id_part + "/" + artifact_id + "/" + version + "/" + final_name |
| urls = [] |
| for server_url in server_urls: |
| urls.append(_concat_with_needed_slash(server_url, url_suffix)) |
| return urls |
| |
| def _concat_with_needed_slash(server_url, url_suffix): |
| if server_url.endswith("/"): |
| return server_url + url_suffix |
| else: |
| return server_url + "/" + url_suffix |
| |
| def _serialize_given_rule_import(rule_name, import_attr, name, path, srcpath, attrs, props, additional_rule_attrs): |
| lines = [ |
| "%s(" % rule_name, |
| " name = %s," % repr(name), |
| " " + import_attr % repr(path) + ",", |
| ] |
| |
| if srcpath: |
| lines.append(" srcjar = %s," % repr(srcpath)) |
| for prop in props: |
| value = getattr(attrs, prop, None) |
| if value: |
| if prop.endswith("_"): |
| prop = prop[:-1] |
| lines.append(" %s = %s," % (prop, repr(value))) |
| for attr_key in additional_rule_attrs: |
| lines.append(" %s = %s," % (attr_key, additional_rule_attrs[attr_key])) |
| lines.append(")") |
| lines.append("") |
| return lines |
| |
| jvm_import_external = repository_rule( |
| attrs = { |
| "rule_name": attr.string(mandatory = True), |
| "licenses": attr.string_list( |
| mandatory = True, |
| allow_empty = False, |
| ), |
| "artifact_urls": attr.string_list( |
| mandatory = True, |
| allow_empty = False, |
| ), |
| "artifact_sha256": attr.string(), |
| "rule_metadata": attr.string_dict( |
| default = { |
| "extension": "jar", |
| "import_attr": "jars = [%s]", |
| }, |
| ), |
| "rule_load": attr.string(), |
| "additional_rule_attrs": attr.string_dict(), |
| "srcjar_urls": attr.string_list(), |
| "srcjar_sha256": attr.string(), |
| "deps": attr.string_list(), |
| "runtime_deps": attr.string_list(), |
| "testonly_": attr.bool(), |
| "exports": attr.string_list(), |
| "neverlink": attr.bool(), |
| "generated_rule_name": attr.string(), |
| "generated_linkable_rule_name": attr.string(), |
| "default_visibility": attr.string_list(default = ["//visibility:public"]), |
| "extra_build_file_content": attr.string(), |
| }, |
| implementation = _jvm_import_external, |
| ) |
| |
| def jvm_maven_import_external(artifact, server_urls, **kwargs): |
| jvm_import_external( |
| artifact_urls = convert_artifact_coordinate_to_urls( |
| artifact, |
| server_urls, |
| "jar", |
| ), |
| **kwargs |
| ) |