Make generate_workspace dump transitive dependencies for Maven artifacts
This moves the WORKSPACE-parsing code out of WorkspaceFileFunction.java so
generate_workspace can parse a WORKSPACE into an ExternalPackage without having
to muck with Skyframe.
Addresses issue #89.
--
MOS_MIGRATED_REVID=96777708
diff --git a/site/docs/external.md b/site/docs/external.md
index 9527898..a6e57ed 100644
--- a/site/docs/external.md
+++ b/site/docs/external.md
@@ -29,23 +29,51 @@
file size, but hopefully limits the chances of having one library include _C_
at version 1.0 and another include _C_ at 2.0.
-# Converting existing projects
+Bazel provides a tool to help generate these expansive _WORKSPACE_ files, called
+`generate_workspace`. Run the following to build the tool and see usage:
-To convert a Maven project, first run the `generate_workspace` tool:
-
-```bash
-$ bazel run src/main/java/com/google/devtools/build/workspace:generate_workspace /path/to/your/maven/project >> WORKSPACE
+```
+bazel run src/main/java/com/google/devtools/build/workspace:generate_workspace
```
-This will parse the _pom.xml_ file and discover project dependencies. All of
-these dependencies will be written in
-[`maven_jar`](http://bazel.io/docs/build-encyclopedia.html#maven_jar) format to
-stdout, which can be redirected or copied to the _WORKSPACE_ file.
+You can either specify directories containing Bazel projects (i.e., _WORKSPACE_
+files) or Maven projects (i.e., _pom.xml_ files). For example:
-At the moment, `generate_workspace` will only include direct dependencies.
+```bash
+$ bazel run src/main/java/com/google/devtools/build/workspace:generate_workspace \
+> --maven_project=/path/to/my/project \
+> --bazel_project=/path/to/skunkworks \
+> --bazel_project=/path/to/teleporter/project
+# --------------------
+# The following dependencies were calculated from:
+# /path/to/my/project/pom.xml
+# /path/to/skunkworks/WORKSPACE
+# /path/to/teleporter/project/WORKSPACE
-You will still need to manually add these libraries as dependencies of your
-`java_` targets.
+
+# com.example.some-project:a:1.2.3
+# com.example.another-project:b:3.2.1 wanted version 2.4
+maven_jar(
+ name = "javax/servlet/servlet-api",
+ artifact = "javax.servlet:servlet-api:2.5",
+)
+
+[Other dependencies]
+# --------------------
+
+WARNING /path/to/my/project/pom.xml:1: javax.servlet:servlet-api already processed for version 2.5 but com.example.another-project:b:3.2.1 wants version 2.4, ignoring.
+```
+Everything after the second `--------------------` is printed to stderr, not
+stdout. This is where any errors or warnings are printed. You may need to edit
+the versions that `generate_workspace` automatically chooses for the artifacts.
+
+If you specify multiple Bazel or Maven projects, they will all be combined into
+one _WORKSPACE_ file (e.g., if the Bazel project depends on junit and the Maven
+project also depends on junit, junit will only appear once as a dependency in
+the output).
+
+Once these `maven_jar`s have been added to your _WORKSPACE_ file, you will still
+need to add the jars as dependencies of your `java_` targets in _BUILD_ files.
# Types of external dependencies
diff --git a/src/main/java/BUILD b/src/main/java/BUILD
index 561d4ae..aa8a0f6 100644
--- a/src/main/java/BUILD
+++ b/src/main/java/BUILD
@@ -288,7 +288,10 @@
"com/google/devtools/build/lib/server/**/*.java",
"com/google/devtools/build/lib/standalone/*.java",
"com/google/devtools/build/lib/webstatusserver/**/*.java",
- "com/google/devtools/build/lib/worker/*.java",
+ "com/google/devtools/build/lib/worker/**",
+ ],
+ exclude = [
+ "com/google/devtools/build/lib/bazel/repository/MavenConnector.java",
],
),
resources = glob([
@@ -301,7 +304,10 @@
"//third_party:d3-js",
"//third_party:jquery",
],
- visibility = ["//src/test/java:__subpackages__"],
+ visibility = [
+ "//src/main/java/com/google/devtools/build/workspace:__pkg__",
+ "//src/test/java:__subpackages__",
+ ],
runtime_deps = [
"//third_party:aether",
"//third_party:apache_commons_logging",
@@ -320,6 +326,7 @@
":events",
":genquery",
":graph",
+ ":maven-connector",
":options",
":packages",
":query2",
@@ -347,6 +354,15 @@
)
java_library(
+ name = "maven-connector",
+ srcs = ["com/google/devtools/build/lib/bazel/repository/MavenConnector.java"],
+ deps = [
+ "//third_party:aether",
+ "//third_party:maven_model",
+ ],
+)
+
+java_library(
name = "java-toolchain-parser",
testonly = 1,
srcs = glob([
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelMain.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelMain.java
index 89840d9..318e6ac 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelMain.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelMain.java
@@ -30,7 +30,7 @@
public final class BazelMain {
private static final String BUILD_DATA_PROPERTIES = "/build-data.properties";
- private static final List<Class<? extends BlazeModule>> BAZEL_MODULES = ImmutableList.of(
+ public static final List<Class<? extends BlazeModule>> BAZEL_MODULES = ImmutableList.of(
com.google.devtools.build.lib.bazel.BazelShutdownLoggerModule.class,
com.google.devtools.build.lib.bazel.BazelWorkspaceStatusModule.class,
com.google.devtools.build.lib.bazel.BazelDiffAwarenessModule.class,
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenConnector.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenConnector.java
new file mode 100644
index 0000000..60addc6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenConnector.java
@@ -0,0 +1,68 @@
+// Copyright 2014 Google Inc. 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.
+
+package com.google.devtools.build.lib.bazel.repository;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.AbstractRepositoryListener;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transfer.AbstractTransferListener;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+
+/**
+ * Connections to Maven repositories.
+ */
+public class MavenConnector {
+ private static final String MAVEN_CENTRAL_URL = "http://central.maven.org/maven2/";
+
+ private final String localRepositoryPath;
+
+ public MavenConnector(String localRepositoryPath) {
+ this.localRepositoryPath = localRepositoryPath;
+ }
+
+ public RepositorySystemSession newRepositorySystemSession(RepositorySystem system) {
+ DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+ LocalRepository localRepo = new LocalRepository(localRepositoryPath);
+ session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
+ session.setTransferListener(new AbstractTransferListener() {});
+ session.setRepositoryListener(new AbstractRepositoryListener() {});
+ return session;
+ }
+
+ public RepositorySystem newRepositorySystem() {
+ DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+ locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
+ locator.addService(TransporterFactory.class, FileTransporterFactory.class);
+ locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
+ return locator.getService(RepositorySystem.class);
+ }
+
+ /**
+ * How is this not a built-in for aether?
+ */
+ public static RemoteRepository getMavenCentral() {
+ return new RemoteRepository.Builder(
+ "central", "default", MAVEN_CENTRAL_URL).build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenJarFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenJarFunction.java
index 8af654f..fce3328 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenJarFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/MavenJarFunction.java
@@ -36,25 +36,14 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
-import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
-import org.eclipse.aether.AbstractRepositoryListener;
-import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
-import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
-import org.eclipse.aether.impl.DefaultServiceLocator;
-import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
-import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
-import org.eclipse.aether.spi.connector.transport.TransporterFactory;
-import org.eclipse.aether.transfer.AbstractTransferListener;
-import org.eclipse.aether.transport.file.FileTransporterFactory;
-import org.eclipse.aether.transport.http.HttpTransporterFactory;
import java.io.IOException;
import java.util.List;
@@ -82,8 +71,7 @@
MavenDownloader createMavenDownloader(AttributeMap mapper) {
String name = mapper.getName();
Path outputDirectory = getExternalRepositoryDirectory().getRelative(name);
- MavenDownloader downloader = new MavenDownloader(name, mapper, outputDirectory);
- return downloader;
+ return new MavenDownloader(name, mapper, outputDirectory);
}
SkyValue createOutputTree(MavenDownloader downloader, Environment env)
@@ -93,7 +81,7 @@
return null;
}
- Path repositoryJar = null;
+ Path repositoryJar;
try {
repositoryJar = downloader.download();
} catch (IOException e) {
@@ -136,8 +124,6 @@
* This downloader creates a connection to one or more Maven repositories and downloads a jar.
*/
static class MavenDownloader {
- private static final String MAVEN_CENTRAL_URL = "http://central.maven.org/maven2/";
-
private final String name;
private final String artifact;
private final Path outputDirectory;
@@ -172,9 +158,7 @@
"user-defined repository " + repositories.size(), "default", repositoryUrl).build());
}
} else {
- this.repositories = Lists.newArrayList();
- this.repositories.add(new RemoteRepository.Builder(
- "central", "default", MAVEN_CENTRAL_URL).build());
+ this.repositories = ImmutableList.of(MavenConnector.getMavenCentral());
}
}
@@ -196,8 +180,9 @@
* Download the Maven artifact to the output directory. Returns the path to the jar.
*/
public Path download() throws IOException {
- RepositorySystem system = newRepositorySystem();
- RepositorySystemSession session = newRepositorySystemSession(system);
+ MavenConnector connector = new MavenConnector(outputDirectory.getPathString());
+ RepositorySystem system = connector.newRepositorySystem();
+ RepositorySystemSession session = connector.newRepositorySystemSession(system);
ArtifactRequest artifactRequest = new ArtifactRequest();
Artifact artifact;
@@ -228,23 +213,6 @@
}
return downloadPath;
}
-
- private RepositorySystemSession newRepositorySystemSession(RepositorySystem system) {
- DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
- LocalRepository localRepo = new LocalRepository(outputDirectory.getPathString());
- session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
- session.setTransferListener(new AbstractTransferListener() {});
- session.setRepositoryListener(new AbstractRepositoryListener() {});
- return session;
- }
-
- private RepositorySystem newRepositorySystem() {
- DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
- locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
- locator.addService(TransporterFactory.class, FileTransporterFactory.class);
- locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
- return locator.getService(RepositorySystem.class);
- }
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
new file mode 100644
index 0000000..a304c6d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -0,0 +1,151 @@
+// Copyright 2015 Google Inc. 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.
+
+package com.google.devtools.build.lib.packages;
+
+import static com.google.devtools.build.lib.syntax.Environment.NONE;
+
+import com.google.devtools.build.lib.cmdline.LabelValidator;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.ExternalPackage.Binding;
+import com.google.devtools.build.lib.packages.ExternalPackage.Builder;
+import com.google.devtools.build.lib.syntax.BaseFunction;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.FunctionSignature;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+
+import java.io.File;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Parser for WORKSPACE files. Fills in an ExternalPackage.Builder
+ */
+public class WorkspaceFactory {
+ private final Builder builder;
+ private final Environment environment;
+
+ public WorkspaceFactory(Builder builder, RuleClassProvider ruleClassProvider) {
+ this(builder, ruleClassProvider, null);
+ }
+
+ public WorkspaceFactory(
+ Builder builder, RuleClassProvider ruleClassProvider, @Nullable String installDir) {
+ this.builder = builder;
+ this.environment = createWorkspaceEnv(builder, ruleClassProvider, installDir);
+ }
+
+ public void parse(ParserInputSource source)
+ throws InterruptedException {
+ StoredEventHandler localReporter = new StoredEventHandler();
+ BuildFileAST buildFileAST;
+ buildFileAST = BuildFileAST.parseBuildFile(source, localReporter, null, false);
+ if (buildFileAST.containsErrors()) {
+ localReporter.handle(Event.error("WORKSPACE file could not be parsed"));
+ } else {
+ if (!buildFileAST.exec(environment, localReporter)) {
+ localReporter.handle(Event.error("Error evaluating WORKSPACE file " + source.getPath()));
+ }
+ }
+
+ builder.addEvents(localReporter.getEvents());
+ if (localReporter.hasErrors()) {
+ builder.setContainsErrors();
+ }
+ }
+
+ // TODO(bazel-team): use @SkylarkSignature annotations on a BuiltinFunction.Factory
+ // for signature + documentation of this and other functions in this file.
+ private static BuiltinFunction newWorkspaceNameFunction(final Builder builder) {
+ return new BuiltinFunction("workspace",
+ FunctionSignature.namedOnly("name"), BuiltinFunction.USE_LOC) {
+ public Object invoke(String name, Location loc) throws EvalException {
+ String errorMessage = LabelValidator.validateTargetName(name);
+ if (errorMessage != null) {
+ throw new EvalException(loc, errorMessage);
+ }
+ builder.setWorkspaceName(name);
+ return NONE;
+ }
+ };
+ }
+
+ private static BuiltinFunction newBindFunction(final Builder builder) {
+ return new BuiltinFunction("bind",
+ FunctionSignature.namedOnly("name", "actual"), BuiltinFunction.USE_LOC) {
+ public Object invoke(String name, String actual, Location loc)
+ throws EvalException, InterruptedException {
+ Label nameLabel = null;
+ try {
+ nameLabel = Label.parseAbsolute("//external:" + name);
+ builder.addBinding(nameLabel, new Binding(Label.parseAbsolute(actual), loc));
+ } catch (Label.SyntaxException e) {
+ throw new EvalException(loc, e.getMessage());
+ }
+ return NONE;
+ }
+ };
+ }
+
+ /**
+ * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the
+ * specified package context.
+ */
+ private static BuiltinFunction newRuleFunction(
+ final RuleFactory ruleFactory, final Builder builder, final String ruleClassName) {
+ return new BuiltinFunction(ruleClassName,
+ FunctionSignature.KWARGS, BuiltinFunction.USE_AST) {
+ public Object invoke(Map<String, Object> kwargs, FuncallExpression ast)
+ throws EvalException {
+ try {
+ RuleClass ruleClass = ruleFactory.getRuleClass(ruleClassName);
+ builder.createAndAddRepositoryRule(ruleClass, kwargs, ast);
+ } catch (RuleFactory.InvalidRuleException | Package.NameConflictException |
+ Label.SyntaxException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ return NONE;
+ }
+ };
+ }
+
+ private Environment createWorkspaceEnv(
+ Builder builder, RuleClassProvider ruleClassProvider, String installDir) {
+ Environment workspaceEnv = new Environment();
+
+ RuleFactory ruleFactory = new RuleFactory(ruleClassProvider);
+ for (String ruleClass : ruleFactory.getRuleClassNames()) {
+ BaseFunction ruleFunction = newRuleFunction(ruleFactory, builder, ruleClass);
+ workspaceEnv.update(ruleClass, ruleFunction);
+ }
+
+ if (installDir != null) {
+ workspaceEnv.update("__embedded_dir__", installDir);
+ }
+ File jreDirectory = new File(System.getProperty("java.home"));
+ workspaceEnv.update("DEFAULT_SERVER_JAVABASE", jreDirectory.getParentFile().toString());
+
+ workspaceEnv.update("bind", newBindFunction(builder));
+ workspaceEnv.update("workspace", newWorkspaceNameFunction(builder));
+ return workspaceEnv;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
index cab94da..fa770b0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
@@ -14,30 +14,13 @@
package com.google.devtools.build.lib.skyframe;
-import static com.google.devtools.build.lib.syntax.Environment.NONE;
-
import com.google.devtools.build.lib.analysis.BlazeDirectories;
-import com.google.devtools.build.lib.cmdline.LabelValidator;
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.events.StoredEventHandler;
-import com.google.devtools.build.lib.packages.ExternalPackage.Binding;
import com.google.devtools.build.lib.packages.ExternalPackage.Builder;
import com.google.devtools.build.lib.packages.ExternalPackage.Builder.NoSuchBindingException;
-import com.google.devtools.build.lib.packages.Package.NameConflictException;
import com.google.devtools.build.lib.packages.PackageFactory;
-import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClassProvider;
-import com.google.devtools.build.lib.packages.RuleFactory;
-import com.google.devtools.build.lib.packages.Type.ConversionException;
-import com.google.devtools.build.lib.syntax.BaseFunction;
-import com.google.devtools.build.lib.syntax.BuildFileAST;
-import com.google.devtools.build.lib.syntax.BuiltinFunction;
+import com.google.devtools.build.lib.packages.WorkspaceFactory;
import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.syntax.FuncallExpression;
-import com.google.devtools.build.lib.syntax.FunctionSignature;
-import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.Label.SyntaxException;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -48,17 +31,13 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
-import java.io.File;
import java.io.IOException;
-import java.util.Map;
/**
* A SkyFunction to parse WORKSPACE files.
*/
public class WorkspaceFileFunction implements SkyFunction {
- private static final String BIND = "bind";
-
private final PackageFactory packageFactory;
private final Path installDir;
private final RuleClassProvider ruleClassProvider;
@@ -83,21 +62,22 @@
Path repoWorkspace = workspaceRoot.getRoot().getRelative(workspaceRoot.getRelativePath());
Builder builder = new Builder(repoWorkspace);
- parseWorkspaceFile(
- ParserInputSource.create(
- ruleClassProvider.getDefaultWorkspaceFile(), new PathFragment("DEFAULT.WORKSPACE")),
- builder);
+ WorkspaceFactory parser = new WorkspaceFactory(
+ builder, packageFactory.getRuleClassProvider(), installDir.getPathString());
+ parser.parse(ParserInputSource.create(
+ ruleClassProvider.getDefaultWorkspaceFile(), new PathFragment("DEFAULT.WORKSPACE")));
if (!workspaceFileValue.exists()) {
return new PackageValue(builder.build());
}
+
try {
- ParserInputSource repoWorkspaceSource = ParserInputSource.create(repoWorkspace);
- parseWorkspaceFile(repoWorkspaceSource, builder);
+ parser.parse(ParserInputSource.create(repoWorkspace));
} catch (IOException e) {
throw new WorkspaceFileFunctionException(e, Transience.TRANSIENT);
}
+
try {
- builder.resolveBindTargets(packageFactory.getRuleClass(BIND));
+ builder.resolveBindTargets(packageFactory.getRuleClass("bind"));
} catch (NoSuchBindingException e) {
throw new WorkspaceFileFunctionException(
new EvalException(e.getLocation(), e.getMessage()));
@@ -108,108 +88,11 @@
return new PackageValue(builder.build());
}
- private void parseWorkspaceFile(ParserInputSource source, Builder builder)
- throws WorkspaceFileFunctionException, InterruptedException {
- StoredEventHandler localReporter = new StoredEventHandler();
- BuildFileAST buildFileAST;
- buildFileAST = BuildFileAST.parseBuildFile(source, localReporter, null, false);
- if (buildFileAST.containsErrors()) {
- localReporter.handle(Event.error("WORKSPACE file could not be parsed"));
- } else {
- if (!evaluateWorkspaceFile(buildFileAST, builder, localReporter)) {
- localReporter.handle(Event.error("Error evaluating WORKSPACE file " + source.getPath()));
- }
- }
-
- builder.addEvents(localReporter.getEvents());
- if (localReporter.hasErrors()) {
- builder.setContainsErrors();
- }
- }
-
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
- // TODO(bazel-team): use @SkylarkSignature annotations on a BuiltinFunction.Factory
- // for signature + documentation of this and other functions in this file.
- private static BuiltinFunction newWorkspaceNameFunction(final Builder builder) {
- return new BuiltinFunction("workspace",
- FunctionSignature.namedOnly("name"), BuiltinFunction.USE_LOC) {
- public Object invoke(String name,
- Location loc) throws EvalException {
- String errorMessage = LabelValidator.validateTargetName(name);
- if (errorMessage != null) {
- throw new EvalException(loc, errorMessage);
- }
- builder.setWorkspaceName(name);
- return NONE;
- }
- };
- }
-
- private static BuiltinFunction newBindFunction(final Builder builder) {
- return new BuiltinFunction(BIND,
- FunctionSignature.namedOnly("name", "actual"), BuiltinFunction.USE_LOC) {
- public Object invoke(String name, String actual,
- Location loc) throws EvalException, ConversionException, InterruptedException {
- Label nameLabel = null;
- try {
- nameLabel = Label.parseAbsolute("//external:" + name);
- builder.addBinding(nameLabel, new Binding(Label.parseAbsolute(actual), loc));
- } catch (SyntaxException e) {
- throw new EvalException(loc, e.getMessage());
- }
- return NONE;
- }
- };
- }
-
- /**
- * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the
- * specified package context.
- */
- private static BuiltinFunction newRuleFunction(final RuleFactory ruleFactory,
- final Builder builder, final String ruleClassName) {
- return new BuiltinFunction(ruleClassName,
- FunctionSignature.KWARGS, BuiltinFunction.USE_AST) {
- public Object invoke(Map<String, Object> kwargs,
- FuncallExpression ast) throws EvalException {
- try {
- RuleClass ruleClass = ruleFactory.getRuleClass(ruleClassName);
- builder.createAndAddRepositoryRule(ruleClass, kwargs, ast);
- } catch (RuleFactory.InvalidRuleException | NameConflictException | SyntaxException e) {
- throw new EvalException(ast.getLocation(), e.getMessage());
- }
- return NONE;
- }
- };
- }
-
- public boolean evaluateWorkspaceFile(BuildFileAST buildFileAST, Builder builder,
- StoredEventHandler eventHandler) throws InterruptedException {
- // Environment is defined in SkyFunction and the syntax package.
- com.google.devtools.build.lib.syntax.Environment workspaceEnv =
- new com.google.devtools.build.lib.syntax.Environment();
-
- RuleFactory ruleFactory = new RuleFactory(packageFactory.getRuleClassProvider());
- for (String ruleClass : ruleFactory.getRuleClassNames()) {
- BaseFunction ruleFunction = newRuleFunction(ruleFactory, builder, ruleClass);
- workspaceEnv.update(ruleClass, ruleFunction);
- }
-
- workspaceEnv.update("__embedded_dir__", this.installDir.toString());
- // TODO(kchodorow): Get all the toolchain rules and load this from there.
- File jreDirectory = new File(System.getProperty("java.home"));
- workspaceEnv.update("DEFAULT_SERVER_JAVABASE", jreDirectory.getParentFile().toString());
-
- workspaceEnv.update(BIND, newBindFunction(builder));
- workspaceEnv.update("workspace", newWorkspaceNameFunction(builder));
-
- return buildFileAST.exec(workspaceEnv, eventHandler);
- }
-
private static final class WorkspaceFileFunctionException extends SkyFunctionException {
public WorkspaceFileFunctionException(IOException e, Transience transience) {
super(e, transience);
diff --git a/src/main/java/com/google/devtools/build/workspace/BUILD b/src/main/java/com/google/devtools/build/workspace/BUILD
index c0f35dd..919b431 100644
--- a/src/main/java/com/google/devtools/build/workspace/BUILD
+++ b/src/main/java/com/google/devtools/build/workspace/BUILD
@@ -1,10 +1,34 @@
java_binary(
name = "generate_workspace",
- srcs = ["WorkspaceFileGenerator.java"],
- main_class = "com.google.devtools.build.workspace.WorkspaceFileGenerator",
+ srcs = [
+ "GenerateWorkspace.java",
+ "GenerateWorkspaceOptions.java",
+ ],
+ main_class = "com.google.devtools.build.workspace.GenerateWorkspace",
visibility = ["//visibility:public"],
deps = [
+ ":workspace",
+ "//src/main/java:bazel-core",
+ "//src/main/java:common",
"//src/main/java:events",
+ "//src/main/java:options",
+ "//src/main/java:packages",
+ "//src/main/java:vfs",
"//src/main/java/com/google/devtools/build/workspace/maven",
],
)
+
+java_library(
+ name = "workspace",
+ srcs = ["Resolver.java"],
+ deps = [
+ "//src/main/java:analysis-exec-rules-skyframe",
+ "//src/main/java:bazel-core",
+ "//src/main/java:events",
+ "//src/main/java:packages",
+ "//src/main/java:vfs",
+ "//src/main/java/com/google/devtools/build/workspace/maven",
+ "//src/main/java/com/google/devtools/build/workspace/maven:rule",
+ "//third_party:guava",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/workspace/GenerateWorkspace.java b/src/main/java/com/google/devtools/build/workspace/GenerateWorkspace.java
new file mode 100644
index 0000000..7000279
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/workspace/GenerateWorkspace.java
@@ -0,0 +1,95 @@
+// Copyright 2015 Google Inc. 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.
+
+package com.google.devtools.build.workspace;
+
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.ExternalPackage;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.UnixFileSystem;
+import com.google.devtools.common.options.OptionsParser;
+
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Generates a WORKSPACE file for Bazel from other types of dependency trackers.
+ */
+public class GenerateWorkspace {
+
+ private final StoredEventHandler handler;
+ private final FileSystem fileSystem;
+ private final com.google.devtools.build.workspace.maven.Resolver resolver;
+
+ public static void main(String[] args) {
+ OptionsParser parser = OptionsParser.newOptionsParser(GenerateWorkspaceOptions.class);
+ parser.parseAndExitUponError(args);
+ GenerateWorkspaceOptions options = parser.getOptions(GenerateWorkspaceOptions.class);
+ if (options.mavenProjects.isEmpty() && options.bazelProjects.isEmpty()) {
+ printUsage(parser);
+ return;
+ }
+
+ GenerateWorkspace workspaceFileGenerator = new GenerateWorkspace();
+ workspaceFileGenerator.generateFromWorkspace(options.bazelProjects);
+ workspaceFileGenerator.generateFromPom(options.mavenProjects);
+ workspaceFileGenerator.print();
+ }
+
+ private static void printUsage(OptionsParser parser) {
+ System.out.println("Usage: generate_workspace (-b PATH|-m PATH)+\n\n"
+ + "Generates a workspace file from the given projects. At least one bazel_project or "
+ + "maven_project must be specified.\n");
+ System.out.println(parser.describeOptions(Collections.<String, String>emptyMap(),
+ OptionsParser.HelpVerbosity.LONG));
+ }
+
+ private GenerateWorkspace() {
+ this.handler = new StoredEventHandler();
+ this.fileSystem = OS.getCurrent() == OS.WINDOWS
+ ? new JavaIoFileSystem() : new UnixFileSystem();
+ this.resolver = new com.google.devtools.build.workspace.maven.Resolver(handler, fileSystem);
+ }
+
+ private void generateFromWorkspace(List<String> projects) {
+ for (String project : projects) {
+ Resolver workspaceResolver = new Resolver(resolver, handler);
+ java.nio.file.Path cwd = Paths.get(System.getProperty("user.dir"));
+ Path projectPath = fileSystem.getPath(cwd.resolve(project).toString());
+ ExternalPackage externalPackage =
+ workspaceResolver.parse(projectPath.getRelative("WORKSPACE"));
+ workspaceResolver.resolveTransitiveDependencies(externalPackage);
+ }
+ }
+
+ private void generateFromPom(List<String> projects) {
+ for (String project : projects) {
+ resolver.resolvePomDependencies(project);
+ }
+ }
+
+ private void print() {
+ resolver.writeDependencies(System.out);
+ resolver.cleanup();
+
+ for (Event event : handler.getEvents()) {
+ System.err.println(event);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/workspace/GenerateWorkspaceOptions.java b/src/main/java/com/google/devtools/build/workspace/GenerateWorkspaceOptions.java
new file mode 100644
index 0000000..0db735f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/workspace/GenerateWorkspaceOptions.java
@@ -0,0 +1,51 @@
+// Copyright 2015 Google Inc. 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.
+
+package com.google.devtools.build.workspace;
+
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+import java.util.List;
+
+/**
+ * Command-line options for generate_workspace tool.
+ */
+public class GenerateWorkspaceOptions extends OptionsBase {
+ @Option(
+ name = "help",
+ abbrev = 'h',
+ help = "Prints usage info.",
+ defaultValue = "true"
+ )
+ public boolean help;
+
+ @Option(
+ name = "bazel_project",
+ abbrev = 'b',
+ help = "Directory contains a Bazel project.",
+ allowMultiple = true,
+ defaultValue = ""
+ )
+ public List<String> bazelProjects;
+
+ @Option(
+ name = "maven_project",
+ abbrev = 'm',
+ help = "Directory containing a Maven project.",
+ allowMultiple = true,
+ defaultValue = ""
+ )
+ public List<String> mavenProjects;
+}
diff --git a/src/main/java/com/google/devtools/build/workspace/Resolver.java b/src/main/java/com/google/devtools/build/workspace/Resolver.java
new file mode 100644
index 0000000..4229fc4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/workspace/Resolver.java
@@ -0,0 +1,122 @@
+// Copyright 2015 Google Inc. 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.
+
+package com.google.devtools.build.workspace;
+
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.bazel.BazelMain;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.ExternalPackage;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.packages.WorkspaceFactory;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.workspace.maven.Rule;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Finds the transitive dependencies of a WORKSPACE file.
+ */
+public class Resolver {
+
+ private final RuleClassProvider ruleClassProvider;
+ private final EventHandler handler;
+ private final com.google.devtools.build.workspace.maven.Resolver resolver;
+
+ Resolver(com.google.devtools.build.workspace.maven.Resolver resolver, EventHandler handler) {
+ this.resolver = resolver;
+ this.handler = handler;
+ ConfiguredRuleClassProvider.Builder ruleClassBuilder =
+ new ConfiguredRuleClassProvider.Builder();
+ List<BlazeModule> blazeModules = BlazeRuntime.createModules(BazelMain.BAZEL_MODULES);
+ for (BlazeModule blazeModule : blazeModules) {
+ blazeModule.initializeRuleClasses(ruleClassBuilder);
+ }
+ this.ruleClassProvider = ruleClassBuilder.build();
+ }
+
+ /**
+ * Converts the WORKSPACE file content into an ExternalPackage.
+ */
+ public ExternalPackage parse(Path workspacePath) {
+ resolver.addHeader(workspacePath.getPathString());
+ ExternalPackage.Builder builder = new ExternalPackage.Builder(workspacePath);
+ WorkspaceFactory parser = new WorkspaceFactory(builder, ruleClassProvider);
+ try {
+ parser.parse(ParserInputSource.create(workspacePath));
+ } catch (IOException | InterruptedException e) {
+ handler.handle(Event.error(Location.fromFile(workspacePath), e.getMessage()));
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Calculates transitive dependencies of the given //external package.
+ */
+ public void resolveTransitiveDependencies(ExternalPackage externalPackage) {
+ Location location = Location.fromFile(externalPackage.getFilename());
+ for (Target target : externalPackage.getTargets()) {
+ // Targets are //external:foo.
+ if (target.getTargetKind().startsWith("bind")
+ || target.getTargetKind().startsWith("source ")) {
+ continue;
+ } else if (target.getTargetKind().startsWith("maven_jar ")) {
+ PackageIdentifier.RepositoryName repositoryName;
+ try {
+ repositoryName = PackageIdentifier.RepositoryName.create("@" + target.getName());
+ } catch (Label.SyntaxException e) {
+ handler.handle(Event.error(location, "Invalid repository name for " + target + ": "
+ + e.getMessage()));
+ return;
+ }
+ com.google.devtools.build.lib.packages.Rule workspaceRule =
+ externalPackage.getRepositoryInfo(repositoryName);
+
+ AttributeMap attributeMap = AggregatingAttributeMapper.of(workspaceRule);
+ Rule rule;
+ try {
+ if (attributeMap.has("artifact", Type.STRING)
+ && !attributeMap.get("artifact", Type.STRING).isEmpty()) {
+ rule = new Rule(attributeMap.get("artifact", Type.STRING));
+ } else {
+ rule = new Rule(
+ attributeMap.get("artifact_id", Type.STRING),
+ attributeMap.get("group_id", Type.STRING),
+ attributeMap.get("version", Type.STRING));
+ }
+ } catch (Rule.InvalidRuleException e) {
+ handler.handle(Event.error(location, e.getMessage()));
+ return;
+ }
+ resolver.getArtifactDependencies(rule, location);
+ } else {
+ handler.handle(Event.warn(location, "Cannot fetch transitive dependencies for " + target
+ + " yet, skipping"));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/workspace/WorkspaceFileGenerator.java b/src/main/java/com/google/devtools/build/workspace/WorkspaceFileGenerator.java
deleted file mode 100644
index 8561910..0000000
--- a/src/main/java/com/google/devtools/build/workspace/WorkspaceFileGenerator.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2015 Google Inc. 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.
-
-package com.google.devtools.build.workspace;
-
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.StoredEventHandler;
-import com.google.devtools.build.workspace.maven.Resolver;
-
-import java.io.File;
-
-/**
- * Generates a WORKSPACE file for Bazel from other types of dependency trackers.
- */
-public class WorkspaceFileGenerator {
-
- public static void main(String[] args) {
- String directory;
- if (args.length == 1) {
- directory = args[0];
- } else {
- directory = System.getProperty("user.dir");
- }
- StoredEventHandler handler = new StoredEventHandler();
- Resolver connector = new Resolver(new File(directory), handler);
- connector.writeDependencies(System.out);
- if (handler.hasErrors()) {
- for (Event event : handler.getEvents()) {
- System.err.println(event);
- }
- }
- }
-
-}
diff --git a/src/main/java/com/google/devtools/build/workspace/maven/BUILD b/src/main/java/com/google/devtools/build/workspace/maven/BUILD
index 3b0e2f5..74d1132 100644
--- a/src/main/java/com/google/devtools/build/workspace/maven/BUILD
+++ b/src/main/java/com/google/devtools/build/workspace/maven/BUILD
@@ -15,6 +15,8 @@
deps = [
":rule",
"//src/main/java:events",
+ "//src/main/java:maven-connector",
+ "//src/main/java:vfs",
"//third_party:aether",
"//third_party:guava",
"//third_party:maven_model",
@@ -25,5 +27,11 @@
java_library(
name = "rule",
srcs = ["Rule.java"],
- deps = ["//third_party:auto_value"],
+ visibility = [
+ "//src/main/java/com/google/devtools/build:__subpackages__",
+ ],
+ deps = [
+ "//third_party:aether",
+ "//third_party:guava",
+ ],
)
diff --git a/src/main/java/com/google/devtools/build/workspace/maven/Resolver.java b/src/main/java/com/google/devtools/build/workspace/maven/Resolver.java
index 58cd6cc..b1ff699 100644
--- a/src/main/java/com/google/devtools/build/workspace/maven/Resolver.java
+++ b/src/main/java/com/google/devtools/build/workspace/maven/Resolver.java
@@ -14,9 +14,15 @@
package com.google.devtools.build.workspace.maven;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import com.google.devtools.build.lib.bazel.repository.MavenConnector;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.vfs.FileSystem;
+
import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.DefaultModelBuilderFactory;
@@ -26,54 +32,100 @@
import org.apache.maven.model.building.ModelBuildingResult;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.model.locator.DefaultModelLocator;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import java.io.File;
+import java.io.IOException;
import java.io.PrintStream;
-import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Resolves Maven dependencies.
*/
public class Resolver {
- private final File projectDirectory;
- private final Map<String, Rule> deps;
+ private final MavenConnector connector;
+ private final File localRepository;
+ private final FileSystem fileSystem;
private final EventHandler handler;
- public Resolver(File projectDirectory, EventHandler handler) {
- this.projectDirectory = projectDirectory;
- deps = new HashMap<String, Rule>();
+ private final List<String> headers;
+ // Mapping of maven_jar name to Rule.
+ private final Map<String, Rule> deps;
+
+ public Resolver(EventHandler handler, FileSystem fileSystem) {
this.handler = handler;
+ this.fileSystem = fileSystem;
+ this.localRepository = Files.createTempDir();
+ this.connector = new MavenConnector(localRepository.getPath());
+ headers = Lists.newArrayList();
+ deps = Maps.newHashMap();
}
/**
- * Find the pom.xml, parse it, and write the equivalent WORKSPACE file to the provided
- * outputStream.
+ * Writes all resolved dependencies in WORKSPACE file format to the outputStream.
*/
public void writeDependencies(PrintStream outputStream) {
- resolveDependencies();
+ outputStream.println("# --------------------\n"
+ + "# The following dependencies were calculated from:");
+ for (String header : headers) {
+ outputStream.println("# " + header);
+ }
+ outputStream.print("\n\n");
for (Rule rule : deps.values()) {
- outputStream.println(rule.toString() + "\n");
+ outputStream.println(rule + "\n");
+ }
+ outputStream.println("# --------------------\n");
+ }
+
+ public void addHeader(String header) {
+ headers.add(header);
+ }
+
+ /**
+ * Remove the temporary directory storing pom files.
+ */
+ public void cleanup() {
+ try {
+ for (File file : Files.fileTreeTraverser().postOrderTraversal(localRepository)) {
+ java.nio.file.Files.delete(file.toPath());
+ }
+ } catch (IOException e) {
+ handler.handle(Event.error(Location.fromFile(fileSystem.getPath(localRepository.getPath())),
+ "Could not create local repository directory " + localRepository + ": "
+ + e.getMessage()));
}
}
- private void resolveDependencies() {
+ /**
+ * Resolves all dependencies from a pom.xml file in the given directory.
+ */
+ public void resolvePomDependencies(String project) {
DefaultModelProcessor processor = new DefaultModelProcessor();
processor.setModelLocator(new DefaultModelLocator());
processor.setModelReader(new DefaultModelReader());
- File pom = processor.locatePom(projectDirectory);
+ File pom = processor.locatePom(new File(project));
+ Location pomLocation = Location.fromFile(fileSystem.getPath(pom.getPath()));
+ addHeader(pom.getAbsolutePath());
DefaultModelBuilderFactory factory = new DefaultModelBuilderFactory();
DefaultModelBuildingRequest request = new DefaultModelBuildingRequest();
request.setPomFile(pom);
- Model model = null;
+ Model model;
try {
ModelBuildingResult result = factory.newInstance().build(request);
model = result.getEffectiveModel();
} catch (ModelBuildingException e) {
- System.err.println("Unable to resolve Maven model from " + pom + ": " + e.getMessage());
+ handler.handle(Event.error(pomLocation,
+ "Unable to resolve Maven model from " + pom + ": " + e.getMessage()));
return;
}
@@ -84,22 +136,79 @@
}
for (org.apache.maven.model.Dependency dependency : model.getDependencies()) {
- Rule rule = new Rule(
- dependency.getArtifactId(), dependency.getGroupId(), dependency.getVersion());
- if (deps.containsKey(rule.name())) {
- Rule existingDependency = deps.get(rule.name());
- // Check that the versions are the same.
- if (!existingDependency.version().equals(dependency.getVersion())) {
- handler.handle(new Event(EventKind.ERROR, null, dependency.getGroupId() + ":"
- + dependency.getArtifactId() + " already processed for version "
- + existingDependency.version() + " but " + model + " wants version "
- + dependency.getVersion() + ", ignoring."));
- }
- // If it already exists at the right version, we're done.
- } else {
- deps.put(rule.name(), rule);
- // TODO(kchodorow): fetch transitive dependencies.
+ try {
+ Rule artifactRule = new Rule(
+ dependency.getArtifactId(), dependency.getGroupId(), dependency.getVersion());
+ addArtifact(artifactRule, model.toString(), pomLocation);
+ getArtifactDependencies(artifactRule, pomLocation);
+ } catch (Rule.InvalidRuleException e) {
+ handler.handle(Event.error(pomLocation, e.getMessage()));
}
}
}
+
+ /**
+ * Adds transitive dependencies of the given artifact.
+ */
+ public void getArtifactDependencies(Rule artifactRule, Location location) {
+ Artifact artifact = artifactRule.getArtifact();
+
+ RepositorySystem system = connector.newRepositorySystem();
+ RepositorySystemSession session = connector.newRepositorySystemSession(system);
+
+ ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
+ descriptorRequest.setArtifact(artifact);
+ descriptorRequest.addRepository(MavenConnector.getMavenCentral());
+
+ ArtifactDescriptorResult descriptorResult;
+ try {
+ descriptorResult = system.readArtifactDescriptor(session, descriptorRequest);
+ if (descriptorResult == null) {
+ return;
+ }
+ } catch (ArtifactDescriptorException e) {
+ handler.handle(Event.error(location, e.getMessage()));
+ return;
+ }
+
+ for (Dependency dependency : descriptorResult.getDependencies()) {
+ Artifact depArtifact = dependency.getArtifact();
+ try {
+ Rule rule = new Rule(
+ depArtifact.getArtifactId(), depArtifact.getGroupId(), depArtifact.getVersion());
+ if (addArtifact(rule, artifactRule.toMavenArtifactString(), location)) {
+ getArtifactDependencies(rule, location);
+ }
+ } catch (Rule.InvalidRuleException e) {
+ handler.handle(Event.error(location, e.getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Adds the artifact to the list of deps, if it is not already there. Returns if the artifact
+ * was already in the list. If the artifact was in the list at a different version, adds an
+ * error event to the event handler.
+ */
+ private boolean addArtifact(Rule dependency, String parent, Location pomLocation) {
+ String artifactName = dependency.name();
+ if (deps.containsKey(artifactName)) {
+ Rule existingDependency = deps.get(artifactName);
+ // Check that the versions are the same.
+ if (!existingDependency.version().equals(dependency.version())) {
+ handler.handle(Event.warn(pomLocation, dependency.groupId() + ":"
+ + dependency.artifactId() + " already processed for version "
+ + existingDependency.version() + " but " + parent + " wants version "
+ + dependency.version() + ", ignoring."));
+ existingDependency.addParent(parent + " wanted version " + dependency.version());
+ } else {
+ existingDependency.addParent(parent);
+ }
+ return false;
+ }
+
+ deps.put(artifactName, dependency);
+ dependency.addParent(parent);
+ return true;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/workspace/maven/Rule.java b/src/main/java/com/google/devtools/build/workspace/maven/Rule.java
index 33a78eb..65fa88c 100644
--- a/src/main/java/com/google/devtools/build/workspace/maven/Rule.java
+++ b/src/main/java/com/google/devtools/build/workspace/maven/Rule.java
@@ -14,43 +14,87 @@
package com.google.devtools.build.workspace.maven;
+import com.google.common.collect.Lists;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+import java.util.List;
+
/**
* A struct representing the fields of maven_jar to be written to the WORKSPACE file.
*/
public final class Rule {
- private final String artifactId;
- private final String groupId;
- private final String version;
+ private final Artifact artifact;
+ private final List<String> parents;
- public Rule(String artifactId, String groupId, String version) {
- this.artifactId = artifactId;
- this.groupId = groupId;
- this.version = version;
+ public Rule(String artifactStr) throws InvalidRuleException {
+ try {
+ this.artifact = new DefaultArtifact(artifactStr);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidRuleException(e.getMessage());
+ }
+ this.parents = Lists.newArrayList();
+ }
+
+ public Rule(String artifactId, String groupId, String version)
+ throws InvalidRuleException {
+ this(groupId + ":" + artifactId + ":" + version);
+ }
+
+ public void addParent(String parent) {
+ parents.add(parent);
}
public String artifactId() {
- return artifactId;
+ return artifact.getArtifactId();
}
public String groupId() {
- return groupId;
+ return artifact.getGroupId();
}
public String version() {
- return version;
+ return artifact.getVersion();
}
+ /**
+ * A unique name for this artifact to use in maven_jar's name attribute.
+ */
String name() {
return (groupId() + "/" + artifactId()).replaceAll("\\.", "/");
}
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ public String toMavenArtifactString() {
+ return groupId() + ":" + artifactId() + ":" + version();
+ }
+
+ /**
+ * The way this jar should be stringified for the WORKSPACE file.
+ */
@Override
public String toString() {
- return "maven_jar(\n"
+ StringBuilder builder = new StringBuilder();
+ for (String parent : parents) {
+ builder.append("# " + parent + "\n");
+ }
+ builder.append("maven_jar(\n"
+ " name = \"" + name() + "\",\n"
- + " artifact_id = \"" + artifactId() + "\",\n"
- + " group_id = \"" + groupId() + "\",\n"
- + " version = \"" + version() + "\",\n"
- + ")";
+ + " artifact = \"" + toMavenArtifactString() + "\",\n"
+ + ")");
+ return builder.toString();
+ }
+
+ /**
+ * Exception thrown if the rule could not be created.
+ */
+ public static class InvalidRuleException extends Exception {
+ InvalidRuleException(String message) {
+ super(message);
+ }
}
}
diff --git a/src/test/shell/bazel/workspace_test.sh b/src/test/shell/bazel/workspace_test.sh
index 0fd3f15..00bfd85d 100755
--- a/src/test/shell/bazel/workspace_test.sh
+++ b/src/test/shell/bazel/workspace_test.sh
@@ -55,11 +55,9 @@
function test_minimal_pom() {
write_pom
- ${bazel_data}/src/main/java/com/google/devtools/build/workspace/generate_workspace &> $TEST_log || \
- fail "generating workspace failed"
- expect_log "artifact_id = \"x\","
- expect_log "group_id = \"com.y.z\","
- expect_log "version = \"3.2.1\","
+ ${bazel_data}/src/main/java/com/google/devtools/build/workspace/generate_workspace \
+ --maven_project=$(pwd) &> $TEST_log || fail "generating workspace failed"
+ expect_log "artifact = \"com.y.z:x:3.2.1\","
}
function test_parent_pom_inheritence() {
@@ -86,15 +84,13 @@
</project>
EOF
- ${bazel_data}/src/main/java/com/google/devtools/build/workspace/generate_workspace my-module &> $TEST_log || \
+ ${bazel_data}/src/main/java/com/google/devtools/build/workspace/generate_workspace \
+ --maven_project=$(pwd)/my-module &> $TEST_log || \
fail "generating workspace failed"
expect_log "name = \"com/y/z/x\","
- expect_log "artifact_id = \"x\","
- expect_log "group_id = \"com.y.z\","
- expect_log "version = \"3.2.1\","
+ expect_log "artifact = \"com.y.z:x:3.2.1\","
expect_log "name = \"com/z/w/x\","
- expect_log "group_id = \"com.z.w\","
- expect_log "version = \"1.2.3\","
+ expect_log "artifact = \"com.z.w:x:1.2.3\","
}
run_suite "workspace tests"