Skip to content
This repository has been archived by the owner on Mar 14, 2019. It is now read-only.

Add java_library statements for providing a lib with transitive deps #9

Merged
merged 6 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,50 @@ android_binary(
)
```

### Detailed dependency information specifications

Although you can always give a dependency as a Maven coordinate string, occasionally special
handling is required in the form of additional directives to properly situate the artifact
in the dependency tree. For example, a given artifact may need to have one of its dependencies
excluded to prevent a conflict.

This situation is provided for by allowing the artifact to be specified as a map containing
all of the required information. This map can express more information than the coordinate
strings can, so internally the coordinate strings are parsed into the artifact map with default
values for the additional items. To assist in generating the maps, you can pull in the file
`specs.bzl` alongside `defs.bzl` and import the `maven` struct, which provides several helper
functions to assist in creating these maps. An example:

```python
load("@rules_maven//:defs.bzl", "artifact")
load("@rules_maven//:specs.bzl", "maven")

maven_install(
artifacts = [
maven.artifact(
group = "com.google.guava",
artifact = "guava",
version = "27.0-android",
exclusions = [
...
]
),
"junit:junit:4.12",
...
],
repositories = [
maven.repository(
"https://some.private.maven.re/po",
user = "bob",
password = "l0bl4w"
),
"https://repo1.maven.org/maven2",
...
],
)
```


## How it works

Note the lack of explicit packaging type (a la gmaven_rules). `coursier`
Expand All @@ -151,13 +195,19 @@ The repository rule then..
1. creates the repository "@maven"
1. symlinks the transitive artifacts from the central cache to the repository's
directory in the output_base
1. creates a single BUILD file with `java_import`/`aar_import` targets for each
transitive artifact (including the top level ones), and their respective deps
matching the `<dependencies>` element in the artifact's POM file.
1. creates a single BUILD file with `java_import`/`aar_import` and `java_library`
transitive library targets for each transitive artifact (including the top level
ones), and their respective deps matching the `<dependencies>` element in the
artifact's POM file.

The `artifact` macro used in the BUILD file translates the artifact fully
qualified name to the label of the top level `java_import`/`aar_import` target
in the `@maven` repository.
in the `@maven` repository. This macro will depend directly on the referenced jar, and
nothing else.

The `library` macro accepts the same arguments, but references the `java_library` target
for the arguments. The library target will contain the referenced jar *and* all of its
transitive dependencies.

For example, the generated BUILD file for `com.google.inject:guice:4.0` looks like this:

Expand Down
69 changes: 60 additions & 9 deletions coursier.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ def generate_imports(repository_ctx, dep_tree, srcs_dep_tree = None):
artifact_deps = artifact["dependencies"]
# Dedupe dependencies here. Sometimes coursier will return "x.y:z:aar:version" and "x.y:z:version" in the
# same list of dependencies.
seen_dep_labels = {}
target_import_labels = []
for dep in artifact_deps:
dep_target_label = _escape(_strip_packaging_and_classifier(dep))
if dep_target_label not in seen_dep_labels:
seen_dep_labels[dep_target_label] = True
target_import_string.append("\t\t\":%s\"," % dep_target_label)
target_import_string.append("\t],")
target_import_labels.append("\t\t\":%s\",\n" % dep_target_label)
target_import_labels = _deduplicate_list(target_import_labels)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!


# 5. Conclude.
target_import_string.append("".join(target_import_labels) + "\t],")

# 5. Finish the java_import rule.
#
# java_import(
# name = "org_hamcrest_hamcrest_library_1_3",
Expand All @@ -184,9 +184,45 @@ def generate_imports(repository_ctx, dep_tree, srcs_dep_tree = None):

all_imports.append("\n".join(target_import_string))

# Also create a versionless alias target
target_alias_label = _escape(_strip_packaging_and_classifier_and_version(artifact["coord"]))
all_imports.append("alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n)" % (target_alias_label, target_label))
# 6. Create a versionless alias target
#
# alias(
# name = "org_hamcrest_hamcrest_library",
# actual = "org_hamcrest_hamcrest_library_1_3",
# )
versionless_target_alias_label = _escape(_strip_packaging_and_classifier_and_version(artifact["coord"]))
all_imports.append("alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n)" % (versionless_target_alias_label, target_label))

# 7. Create java_library rule (includes transitive dependencies)
#
# java_library(
# name = "org_hamcrest_hamcrest_library_lib",
# exports = [
# ":org_hamcrest_hamcrest_core_1_3",
# ":org_hamcrest_hamcrest_library_1_3",
# ]
# )
transitive_dep_aliases = ["\t\t\":" + versionless_target_alias_label + "\""]
for transitive_dep_coord in artifact["dependencies"]:
transitive_dep = _find_dependency_by_coord(dep_tree, transitive_dep_coord)
if transitive_dep == None:
fail("Could not find transitive dependency of " + artifact["coord"] + ": " + transitive_dep_coord + "\n" +
"Parsed artifact data:" + repr(artifact) + "\n" +
"Parsed dependency output:" + repr(dep_tree))

transitive_dep_aliases.append("\t\t\":" + _escape(_strip_packaging_and_classifier_and_version(transitive_dep["coord"])) + "\"")

target_library = \
"""
java_library(
name = "{}",
exports = [
{}
]
)
""".lstrip()
transitive_dep_aliases = _deduplicate_list(transitive_dep_aliases)
all_imports.append(target_library.format(versionless_target_alias_label + "_lib", ",\n".join(transitive_dep_aliases)))


elif absolute_path_to_artifact == None:
Expand All @@ -197,6 +233,21 @@ def generate_imports(repository_ctx, dep_tree, srcs_dep_tree = None):

return ("\n".join(all_imports), checksums)

def _deduplicate_list(items):
seen_items = {}
unique_items = []
for item in items:
if item not in seen_items:
seen_items[item] = True
unique_items.append(item)
return unique_items

def _find_dependency_by_coord(coursier_report, dep_coord):
for artifact in coursier_report["dependencies"]:
if artifact["coord"] == dep_coord:
return artifact
return None

# Generate the base `coursier` command depending on the OS, JAVA_HOME or the
# location of `java`.
def _generate_coursier_command(repository_ctx):
Expand Down
7 changes: 7 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ def artifact(a, repository_name = REPOSITORY_NAME):
def maven_artifact(a):
return artifact(a, repository_name = REPOSITORY_NAME)

def library(a, repository_name = REPOSITORY_NAME):
artifact_obj = _parse_artifact_str(a) if type(a) == "string" else a
return "@%s//:%s" % (repository_name, _escape(artifact_obj["group"] + ":" + artifact_obj["artifact"] + "_lib"))

def maven_library(a):
return library(a, repository_name = REPOSITORY_NAME)

def _escape(string):
return string.replace(".", "_").replace("-", "_").replace(":", "_")

Expand Down