Skip to content

Commit

Permalink
maven_jar migration tool (#217)
Browse files Browse the repository at this point in the history
* maven_jar migration script WIP

* WIP: Java version of maven_jar migration tool

* Add automatic buildozer feature

* Remove shell script

* remove unused sh_binary

* Fix imports and refactor

* Fixed package

* Generate compat repositories and pin versions by default

* Add a note about maven_install.json

* Revert "Add a note about maven_install.json"

This reverts commit 4703d6e.

* Address laurent's comments

* Add note about buildozer prerequisite.
  • Loading branch information
jin committed Nov 5, 2019
1 parent e62e81b commit e545831
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 0 deletions.
3 changes: 3 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,6 @@ load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
# Use this as is if you are using the rbe_ubuntu16_04 container,
# otherwise refer to RBE docs.
rbe_autoconfig(name = "buildkite_config")

load("//migration:maven_jar_migrator_deps.bzl", "maven_jar_migrator_repositories")
maven_jar_migrator_repositories()
13 changes: 13 additions & 0 deletions migration/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
licenses(["notice"])

exports_files(["maven_jar_migrator_deps.bzl"])

java_binary(
name = "maven_jar",
srcs = ["java/rules/jvm/external/MavenJarMigrator.java"],
main_class = "rules.jvm.external.MavenJarMigrator",
resources = ["java/rules/jvm/external/resources/workspace_template.txt"],
deps = [
"@maven_jar_migrator//:com_google_guava_guava",
],
)
49 changes: 49 additions & 0 deletions migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# `maven_jar` migration tool

`maven_jar` will be removed from Bazel 2.0 onwards. See [this issue](https://github.com/bazelbuild/bazel/issues/6799) for more information.

This tool helps to automatically migrate your project from `maven_jar` to `maven_install` provided by `rules_jvm_external`.

These PRs were generated using this tool:

* https://github.com/google/copybara/pull/96
* https://github.com/kythe/kythe/pull/4180
* https://github.com/bazelbuild/rules_k8s/pull/463

## Prerequisites

* [Buildozer](https://github.com/bazelbuild/buildtools/releases). Ensure that the `buildozer` is available in your `PATH`.

## Usage

First, add the latest version of `rules_jvm_external` to your WORKSPACE by
following the instructions on the
[releases](https://github.com/bazelbuild/rules_jvm_external/releases) page.

Then, add the following to your WORKSPACE to load the migrator's
dependencies:

```python
load("@rules_jvm_external//:defs.bzl", "maven_install")
load("@rules_jvm_external//migration:maven_jar_migrator_deps.bzl", "maven_jar_migrator_repositories")
maven_jar_migrator_repositories()
```

Next, run this command in root of project workspace to generate the
`maven_install` WORKSPACE snippet:

```
$ bazel run @rules_jvm_external//migration:maven_jar
```

This command will also run the buildozer commands to migrate your project to
use the new `maven_install` labels.

If the snippet looks good, concatenate it to the end of your WORKSPACE file:

```
$ bazel run @rules_jvm_external//migration:maven_jar >> WORKSPACE
```

Finally, if the build continues to succeed, you can remove
`maven_jar_migrator_repositories` from your WORKSPACE file.
137 changes: 137 additions & 0 deletions migration/java/rules/jvm/external/MavenJarMigrator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package rules.jvm.external;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;

/** A migration tool from native.maven_jar to rules_jvm_external's maven_install. */
public class MavenJarMigrator {

// Defined by Bazel during a `bazel run`.
private static final String BUILD_WORKING_DIRECTORY = System.getenv("BUILD_WORKING_DIRECTORY");
// Converts a Maven coordinate to a valid maven_install target label.
// e.g. com.google.guava:guava:28.0-jre -> @maven//:com_google_guava_guava
private static final Function<String, String> convertCoordinateToNewLabel =
(String coordinate) -> {
coordinate = coordinate.replace("-", "_").replace(".", "_");
String[] parts = coordinate.split(":");
return "@maven//:" + Joiner.on("_").join(parts[0], parts[1]);
};

public static void main(String[] args) throws IOException, InterruptedException {
checkPrerequisites();

ImmutableList<String> coordinates = collectMavenJarAttributeValues("artifact");
// Phase 1: Run buildozer to convert old maven_jar labels to new maven_install labels.
convertLabels(coordinates);
// Phase 2: Print maven_install WORKSPACE snippet on stdout.
System.out.println(generateWorkspaceSnippet(coordinates));
}

private static void checkPrerequisites() throws InterruptedException, IOException {
// Assert that buildozer exists on PATH.
if (getProcessBuilder("buildozer", "-version").start().waitFor() != 0) {
throw new AssertionError("buildozer is not found on your PATH. Download "
+ "buildozer for your platform from https://github.com/bazelbuild/buildtools/releases "
+ "and put the executable in your PATH.");
}
}

/**
* Converts the old style maven_jar labels to the new style maven_install labels.
*
* <p>This implementation assumes that the sortedness output of bazel query --output=build is
* deterministic.
*
* @param coordinates A list of coordinates to derive the old and new labels.
* @throws IOException
* @throws InterruptedException
*/
private static void convertLabels(ImmutableList<String> coordinates)
throws IOException, InterruptedException {
ImmutableList<String> oldLabels =
collectMavenJarAttributeValues("name").stream()
.map((String name) -> String.format("@%s//jar", name))
.collect(ImmutableList.toImmutableList());
ImmutableList<String> newLabels =
coordinates.stream()
.map(convertCoordinateToNewLabel)
.collect(ImmutableList.toImmutableList());

assert (newLabels.size() == oldLabels.size());
Iterator<String> oldLabelIterator = oldLabels.iterator();
Iterator<String> newLabelIterator = newLabels.iterator();
while (oldLabelIterator.hasNext()) {
getProcessBuilder(
"buildozer",
"substitute * " + oldLabelIterator.next() + " " + newLabelIterator.next(),
"//...:*")
.start()
.waitFor();
}
}

private static String generateWorkspaceSnippet(ImmutableList<String> coordinates)
throws IOException {
InputStream stream =
MavenJarMigrator.class.getResourceAsStream("resources/workspace_template.txt");
String workspaceTemplate = new String(ByteStreams.toByteArray(stream), UTF_8);
return workspaceTemplate.replace(
"%maven_artifact_list%",
coordinates.stream()
.sorted()
.map((String line) -> " \"" + line + "\",")
.collect(Collectors.joining("\n")));
}

/**
* Aggregate information about an attribute on all maven_jar targets using bazel query.
*
* <p>The maven_jar declaration does not need to be in the local WORKSPACE. It can be a maven_jar
* declaration loaded from a remote repository's deps.bzl.
*
* @return A list of Maven artifact coordinates used by this workspace.
* @throws IOException
* @throws InterruptedException
*/
private static ImmutableList<String> collectMavenJarAttributeValues(String attribute)
throws IOException, InterruptedException {
ProcessBuilder processBuilder =
getProcessBuilder(
"bazel",
"query",
"kind(maven_jar, //external:all)",
"--output=build",
"--noshow_progress");

return readOutput(processBuilder.start()).stream()
.filter((String line) -> line.trim().startsWith(attribute))
.map((String line) -> line.split("\"")[1]) // get the value in a string attribute
.collect(ImmutableList.toImmutableList());
}

private static ProcessBuilder getProcessBuilder(String... args) {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.directory(new File(BUILD_WORKING_DIRECTORY));
processBuilder.command(args);
return processBuilder;
}

private static ImmutableList<String> readOutput(Process p) throws InterruptedException {
// ensure that the process is completed before reading the standard output.
p.waitFor();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
return br.lines().collect(ImmutableList.toImmutableList());
}
}
20 changes: 20 additions & 0 deletions migration/java/rules/jvm/external/resources/workspace_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("@rules_jvm_external//:defs.bzl", "maven_install")

maven_install(
name = "maven",
artifacts = [
%maven_artifact_list%
],
repositories = [
"https://jcenter.bintray.com",
"https://maven.google.com",
"https://repo1.maven.org/maven2",
],
fetch_sources = True,
version_conflict_policy = "pinned",
# See https://github.com/bazelbuild/rules_jvm_external/#repository-aliases
# This can be removed if none of your external dependencies uses `maven_jar`.
generate_compat_repositories = True,
)
load("@maven//:compat.bzl", "compat_repositories")
compat_repositories()
13 changes: 13 additions & 0 deletions migration/maven_jar_migrator_deps.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@rules_jvm_external//:defs.bzl", "maven_install")

def maven_jar_migrator_repositories():
maven_install(
name = "maven_jar_migrator",
artifacts = [
"com.google.guava:guava:28.0-jre",
],
repositories = [
"https://repo1.maven.org/maven2",
"https://jcenter.bintray.com",
],
)

0 comments on commit e545831

Please sign in to comment.