blob: c046ca3af8c9767a87c0d1a61684659326ebe4d8 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
package com.android.tools.binaries;
import static com.google.common.base.Preconditions.checkNotNull;
import com.android.tools.maven.AetherUtils;
import com.android.tools.maven.HighestVersionSelector;
import com.android.tools.maven.MavenCoordinates;
import com.android.tools.maven.MavenRepository;
import com.android.tools.utils.WorkspaceUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
/**
* Binary that generates a BUILD file (most likely in //tools/base/third_party) which mimics the
* Maven dependency graph using java_libraries with exports.
*/
public class ThirdPartyBuildGenerator {
private static final String PREBUILTS_BAZEL_PACKAGE = "//prebuilts/tools/common/m2/repository/";
private static final String GENERATED_WARNING =
"#\n"
+ "# !!! ATTENTION !!!\n"
+ "#\n"
+ "# This BUILD file was generated by //tools/base/bazel:third_party_build_generator.\n"
+ "# Please do not edit manually, as your changes will be overwritten by the next dependency update.\n"
+ "#\n"
+ "# See //tools/base/bazel/README.md for details."
+ "\n";
/**
* Dependencies excluded from the resolution process.
*
* <p>We don't use asm-all (that some libraries depend on), instead we use the individual
* packages.
*/
static final ImmutableList<Exclusion> EXCLUSIONS =
ImmutableList.of(
new Exclusion("com.google.guava", "guava-jdk5", "*", "*"),
new Exclusion("org.ow2.asm", "asm-all", "*", "*"),
new Exclusion("org.ow2.asm", "asm-debug-all", "*", "*"));
public static void main(String[] argsArray) throws Exception {
List<String> args = Lists.newArrayList(argsArray);
Path buildFile;
Path localRepo;
if (!args.isEmpty() && !MavenCoordinates.isMavenCoordinate(args.get(0))) {
buildFile = Paths.get(args.get(0));
localRepo = Paths.get(args.get(1));
args.remove(0);
args.remove(0);
} else {
Path workspace = WorkspaceUtils.findWorkspace();
buildFile = workspace.resolve("tools/base/third_party/BUILD");
localRepo = WorkspaceUtils.findPrebuiltsRepository();
}
if (!Files.isDirectory(localRepo) || !Files.isRegularFile(buildFile)) {
usage();
}
Set<Artifact> artifacts;
if (!args.isEmpty()) {
artifacts = args.stream().map(DefaultArtifact::new).collect(Collectors.toSet());
} else {
Path dependenciesProperties =
WorkspaceUtils.findWorkspace()
.resolve("tools/buildSrc/base/dependencies.properties");
Properties dependencies = new Properties();
try(InputStream inputStream = Files.newInputStream(dependenciesProperties)) {
dependencies.load(inputStream);
}
artifacts = new HashSet<>();
for (String key: dependencies.stringPropertyNames()) {
artifacts.add(new DefaultArtifact(dependencies.getProperty(key)));
}
}
new ThirdPartyBuildGenerator(buildFile, localRepo).generateBuildFile(artifacts);
}
private static void usage() {
System.out.println(
"Usage: third_party_build_generator [path/to/BUILD path/to/m2/repository] com.example:foo:1.0 ...");
System.out.println("");
System.err.println(
"If the paths to m2 repo and BUILD are omitted, the ones from current WORKSPACE "
+ "will be used.");
System.exit(1);
}
private final Path mBuildFile;
private final MavenRepository mRepo;
private ThirdPartyBuildGenerator(Path buildFile, Path localRepo) {
mBuildFile = checkNotNull(buildFile);
mRepo = new MavenRepository(localRepo);
}
private void generateBuildFile(Set<Artifact> artifacts)
throws DependencyCollectionException, IOException, ArtifactDescriptorException,
DependencyResolutionException {
SortedMap<String, Artifact> versions = computeEffectiveVersions(artifacts);
Files.createDirectories(mBuildFile.getParent());
if (Files.exists(mBuildFile)) {
Files.delete(mBuildFile);
}
try (FileWriter fileWriter = new FileWriter(mBuildFile.toFile())) {
fileWriter.append(GENERATED_WARNING);
fileWriter.append(System.lineSeparator());
fileWriter.append("load(\"//tools/base/bazel:maven.bzl\", \"maven_java_library\")");
fileWriter.append(System.lineSeparator());
fileWriter.append(System.lineSeparator());
for (Map.Entry<String, Artifact> entry : versions.entrySet()) {
String ruleName = entry.getKey();
Artifact artifact = entry.getValue();
List<String> deps =
getDirectDependencies(artifact, JavaScopes.COMPILE, versions.keySet());
List<String> runtimeDeps =
getDirectDependencies(artifact, JavaScopes.RUNTIME, versions.keySet());
fileWriter.append("maven_java_library(");
fileWriter.append(String.format("name = \"%s\", ", ruleName));
fileWriter.append(
String.format("export_artifact = \"%s\", ", getJarTarget(artifact)));
fileWriter.append("visibility = [\"//visibility:public\"], ");
fileWriter.append(String.format("exports = [%s], ", formatAsBazelList(deps)));
if (!runtimeDeps.isEmpty()) {
fileWriter.append(
String.format("runtime_deps = [%s], ", formatAsBazelList(runtimeDeps)));
}
fileWriter.append(")\n");
}
}
}
private static String formatAsBazelList(List<String> deps) {
return deps.stream().map(s -> '"' + s + '"').collect(Collectors.joining(", "));
}
private List<String> getDirectDependencies(
Artifact artifact, String scope, Set<String> allArtifacts)
throws ArtifactDescriptorException {
ArtifactDescriptorResult descriptor = mRepo.readArtifactDescriptor(artifact);
return descriptor
.getDependencies()
.stream()
.filter(dependency -> scope.equals(dependency.getScope()))
.filter(dependency -> !dependency.isOptional())
// This can be false, if a library was excluded using Maven exclusions.
.filter(dependency -> allArtifacts.contains(getRuleName(dependency.getArtifact())))
.map(dependency -> ":" + getRuleName(dependency.getArtifact()))
.collect(Collectors.toList());
}
private String getJarTarget(Artifact artifact) {
Path jar = mRepo.getRelativePath(artifact);
return PREBUILTS_BAZEL_PACKAGE + jar.getParent() + ":" + JavaImportGenerator.JAR_RULE_NAME;
}
private SortedMap<String, Artifact> computeEffectiveVersions(Set<Artifact> artifacts)
throws DependencyCollectionException, DependencyResolutionException, IOException {
CollectRequest collectRequest = new CollectRequest();
collectRequest.setDependencies(
artifacts
.stream()
.map(artifact -> new Dependency(artifact, JavaScopes.COMPILE))
.collect(Collectors.toList()));
collectRequest.setRepositories(AetherUtils.REPOSITORIES);
DefaultRepositorySystemSession session = mRepo.getRepositorySystemSession();
session.setDependencyGraphTransformer(
new ConflictResolver(
new HighestVersionSelector(artifacts),
new JavaScopeSelector(),
new SimpleOptionalitySelector(),
new JavaScopeDeriver()));
session.setDependencySelector(AetherUtils.buildDependencySelector(EXCLUSIONS));
DependencyResult result = mRepo.resolveDependencies(new DependencyRequest(collectRequest, null));
SortedMap<String, Artifact> versions = new TreeMap<>();
JavaImportGenerator imports = new JavaImportGenerator(mRepo);
result.getRoot()
.accept(
new DependencyVisitor() {
@Override
public boolean visitEnter(DependencyNode node) {
Artifact artifact = node.getArtifact();
if (artifact != null) {
versions.put(getRuleName(artifact), artifact);
try {
imports.generateImportRules(artifact);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return true;
}
@Override
public boolean visitLeave(DependencyNode node) {
return true;
}
});
return versions;
}
private static String getRuleName(Artifact artifact) {
return artifact.getGroupId() + "_" + artifact.getArtifactId();
}
}