diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c642c4356..5ea69719a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,26 +17,15 @@ jobs:
java: [8, 11, 17, 21]
steps:
- uses: actions/checkout@v4
+
- uses: actions/setup-java@v4
with:
distribution: "temurin"
cache: "sbt"
java-version: ${{ matrix.java }}
- - name: Main project tests
- run: sbt test
- benchmarks-test:
- runs-on: ubuntu-latest
- name: Benchmark tests
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
- with:
- distribution: "temurin"
- cache: "sbt"
- java-version: 17
- - name: Run sample benchmarks
- run: sbt 'bench/Jmh/run -i 1 -f1 -t1 -foe true'
+ - name: Main project tests
+ run: sbt test
docker_test:
runs-on: ${{ matrix.os }}
@@ -94,8 +83,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: yarn global add @bazel/bazelisk
- - run: sbt cli/pack
- - run: echo "$PWD/scip-java/target/pack/bin" >> $GITHUB_PATH
+ - run: sbt build
+ - run: echo "$PWD/out/bin" >> $GITHUB_PATH
- name: Auto-index scip-java codebase
run: |
scip-java index --build-tool=bazel --bazel-scip-java-binary=$(which scip-java)
@@ -116,4 +105,48 @@ jobs:
distribution: "temurin"
java-version: 17
cache: "sbt"
+
- run: sbt checkAll
+
+ - name: Run sample benchmarks
+ run: sbt 'bench/Jmh/run -i 1 -f1 -t1 -foe true'
+
+
+ maven:
+ runs-on: ubuntu-latest
+ name: Maven tests
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [8, 11, 17, 21]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-java@v4
+ with:
+ distribution: "temurin"
+ cache: "sbt"
+ java-version: ${{ matrix.java }}
+
+
+ - run: |
+ sbt build publishM2 publishLocal dumpScipJavaVersion
+ echo "SCIP_JAVA_VERSION=$(cat VERSION)" >> $GITHUB_ENV
+ echo "SCIP_JAVA_CLI=$PWD/out/bin/scip-java" >> $GITHUB_ENV
+
+ - run: |
+ mvn clean verify -DskipTests -Dscip-java.version=$SCIP_JAVA_VERSION sourcegraph:sourcegraphDependencies
+ working-directory: examples/maven-example
+
+ - run: $SCIP_JAVA_CLI index-semanticdb target/semanticdb-targetroot
+ working-directory: examples/maven-example
+
+ - run: |
+ set -e
+ grep org.hamcrest target/semanticdb-targetroot/*dependencies.txt
+ grep $PWD/src/main/java target/semanticdb-targetroot/*dependencies.txt
+ working-directory: examples/maven-example
+
+ - run: du -h index.scip
+ working-directory: examples/maven-example
+
diff --git a/build.sbt b/build.sbt
index 0c678b4f5..7549aee0e 100644
--- a/build.sbt
+++ b/build.sbt
@@ -205,6 +205,7 @@ lazy val scipProto = project
lazy val scip = project
.in(file("scip-semanticdb"))
.settings(
+ publishMavenStyle := true,
moduleName := "scip-semanticdb",
javaToolchainVersion := "8",
javaOnlySettings,
@@ -214,6 +215,36 @@ lazy val scip = project
)
.dependsOn(semanticdb, scipProto)
+lazy val mavenPlugin = project
+ .in(file("maven-plugin"))
+ .settings(
+ moduleName := "maven-plugin",
+ javaToolchainVersion := "8",
+ javaOnlySettings,
+ libraryDependencies ++=
+ Seq(
+ "org.apache.maven" % "maven-plugin-api" % "3.6.3",
+ "org.apache.maven.plugin-tools" % "maven-plugin-annotations" % "3.6.4" %
+ Provided,
+ "org.apache.maven" % "maven-project" % "2.2.1"
+ ),
+ Compile / resourceGenerators +=
+ Def.task {
+ val dir = (Compile / managedResourceDirectories).value.head /
+ "META-INF" / "maven"
+ IO.createDirectory(dir)
+ val file = dir / "plugin.xml"
+ val template = IO.read(
+ (Compile / resourceDirectory).value / "META-INF" / "maven" /
+ "plugin.template.xml"
+ )
+
+ IO.write(file, template.replace("@VERSION@", version.value))
+
+ Seq(file)
+ }
+ )
+
lazy val cli = project
.in(file("scip-java"))
.settings(
@@ -602,3 +633,13 @@ dumpScipJavaVersion := {
IO.write((ThisBuild / baseDirectory).value / "VERSION", versionValue)
}
+
+lazy val build = taskKey[Unit](
+ "Build `scip-java` CLI and place it in the out/bin/scip-java. "
+)
+
+build := {
+ val source = (cli / pack).value
+ val destination = (ThisBuild / baseDirectory).value / "out"
+ IO.copyDirectory(source, destination)
+}
diff --git a/docs/manual-configuration.md b/docs/manual-configuration.md
index 3b1c1c033..607448837 100644
--- a/docs/manual-configuration.md
+++ b/docs/manual-configuration.md
@@ -156,6 +156,9 @@ index.scip: JSON data
## Step 5 (optional): Enable cross-repository navigation
+Cross-repository navigation is a feature that allows "goto definition" and "find
+references" to show results from multiple repositories.
+
By default, the `index.scip` file only enables navigation within the local
repository. You can optionally enable cross-repository navigation by creating
one of the following files in the SemanticDB _targetroot_ directory (the path in
@@ -193,5 +196,35 @@ one of the following files in the SemanticDB _targetroot_ directory (the path in
your Sourcegraph instance has another repository that defines that symbol, the
cross-repository navigation should succeed.
+### Maven plugin
+
+To simplify setting up cross-repo navigation for Maven projects, we provide a
+plugin that can dump the project's dependencies in a format that scip-java understands.
+
+You can either use it directly from commandline:
+
+```
+$ mvn com.sourcegraph:maven-plugin:@STABLE_VERSION@:sourcegraphDependencies
+```
+
+Or add it to your build like any other maven plugin:
+
+```xml
+
+ com.sourcegraph
+ maven-plugin
+ @STABLE_VERSION@
+
+
+
+ sourcegraphDependencies
+
+
+
+
+```
+
+Which allows you to invoke it by simply running `mvn sourcegraph:sourcegraphDependencies`.
+
Cross-repository navigation is a feature that allows "goto definition" and "find
references" to show results from multiple repositories.
diff --git a/examples/maven-example/pom.xml b/examples/maven-example/pom.xml
new file mode 100644
index 000000000..b61149a71
--- /dev/null
+++ b/examples/maven-example/pom.xml
@@ -0,0 +1,101 @@
+
+
+
+ 4.0.0
+
+ com.sourcegraph
+ example
+ ${revision}
+
+ example
+
+ http://www.example.com
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.0.0-SNAPSHOT
+
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ com.sourcegraph
+ semanticdb-javac
+ ${scip-java.version}
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ -Xplugin:semanticdb -sourceroot:${session.executionRootDirectory} -targetroot:${session.executionRootDirectory}/target/semanticdb-targetroot
+
+
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+ com.sourcegraph
+ maven-plugin
+ ${scip-java.version}
+
+
+
+ sourcegraphDependencies
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/maven-example/src/main/java/App.java b/examples/maven-example/src/main/java/App.java
new file mode 100644
index 000000000..b8e9695d7
--- /dev/null
+++ b/examples/maven-example/src/main/java/App.java
@@ -0,0 +1,13 @@
+package test;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/examples/maven-example/src/test/java/AppTest.java b/examples/maven-example/src/test/java/AppTest.java
new file mode 100644
index 000000000..fc84e0f6b
--- /dev/null
+++ b/examples/maven-example/src/test/java/AppTest.java
@@ -0,0 +1,20 @@
+package test;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+{
+ /**
+ * Rigorous Test :-)
+ */
+ @Test
+ public void shouldAnswerWithTrue()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/maven-plugin/src/main/java/com/sourcegraph/maven/DependencyWriterMojo.java b/maven-plugin/src/main/java/com/sourcegraph/maven/DependencyWriterMojo.java
new file mode 100644
index 000000000..2b1dbd6bc
--- /dev/null
+++ b/maven-plugin/src/main/java/com/sourcegraph/maven/DependencyWriterMojo.java
@@ -0,0 +1,99 @@
+package com.sourcegraph.maven;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.*;
+import org.apache.maven.project.MavenProject;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Set;
+
+@Mojo(
+ name = "sourcegraphDependencies",
+ defaultPhase = LifecyclePhase.COMPILE,
+ requiresDependencyResolution = ResolutionScope.TEST,
+ requiresProject = true)
+public class DependencyWriterMojo extends AbstractMojo {
+ @Parameter(defaultValue = "${project}", required = true, readonly = true)
+ MavenProject project;
+
+ @Parameter(
+ property = "semanticdb.targetRoot",
+ defaultValue = "${session.executionRootDirectory}/target/semanticdb-targetroot")
+ private String targetRoot;
+
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ String sanitisedProjectId = project.getId().replaceAll("[^0-9_a-zA-Z()%\\-.]", "_");
+ Set artifacts = project.getArtifacts();
+ StringBuilder builder = new StringBuilder();
+
+ String groupID = project.getGroupId();
+ String artifactID = project.getArtifactId();
+ String version = project.getVersion();
+ List sourceRoots = project.getCompileSourceRoots();
+
+ if (groupID == null || artifactID == null) {
+ getLog()
+ .warn(
+ "Failed to extract groupID and artifactID from the project.\n"
+ + "This will not prevent a SCIP index from being created, but the symbols \n"
+ + "extracted from this project won't be available for cross-repository navigation,\n"
+ + "as this project doesn't define any Maven coordinates by which it can be referred back to.\n"
+ + "See here for more details: https://sourcegraph.github.io/scip-java/docs/manual-configuration.html#step-5-optional-enable-cross-repository-navigation\n");
+ } else {
+ for (Object root : sourceRoots) {
+ if (root instanceof String) {
+ String rootString = (String) root;
+ builder.append(
+ String.format("%s\t%s\t%s\t%s\n", groupID, artifactID, version, rootString));
+ }
+ }
+ }
+
+ for (Object dep : artifacts) {
+ if (dep instanceof Artifact) {
+ Artifact artifact = (Artifact) dep;
+ if (artifact.getFile() != null) {
+ builder.append(
+ String.format(
+ "%s\t%s\t%s\t%s\n",
+ artifact.getGroupId(),
+ artifact.getArtifactId(),
+ artifact.getVersion(),
+ artifact.getFile()));
+ } else {
+ getLog()
+ .warn(
+ "Dependency "
+ + summariseArtifact(artifact)
+ + " does not have a resolved file, so it won't be added to the dependencies.txt");
+ }
+ }
+ }
+
+ Path dependenciesFile = Paths.get(targetRoot).resolve(sanitisedProjectId + ".dependencies.txt");
+
+ try {
+ Files.createDirectories(dependenciesFile.getParent());
+ try (BufferedWriter writer = Files.newBufferedWriter(dependenciesFile)) {
+ writer.write(builder.toString());
+ }
+ } catch (IOException e) {
+ throw new MojoFailureException("Failed to write dependencies to file " + dependenciesFile, e);
+ }
+
+ getLog().info("Dependencies were written to " + dependenciesFile.toAbsolutePath());
+ }
+
+ private String summariseArtifact(Artifact artifact) {
+ return String.format(
+ "%s:%s:%s", artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
+ }
+}
diff --git a/maven-plugin/src/main/resources/META-INF/maven/plugin.template.xml b/maven-plugin/src/main/resources/META-INF/maven/plugin.template.xml
new file mode 100644
index 000000000..e0cb1c27f
--- /dev/null
+++ b/maven-plugin/src/main/resources/META-INF/maven/plugin.template.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Sourcegraph scip-java Maven plugin
+ A Maven plugin which exports your project's dependencies in a format scip-java can understand
+ com.sourcegraph
+ maven-plugin
+ @VERSION@
+ sourcegraph
+ false
+ true
+ 1.8
+ 3.9.5
+
+
+ sourcegraphDependencies
+ false
+ true
+ false
+ false
+ false
+ true
+ generate-resources
+ com.sourcegraph.maven.DependencyWriterMojo
+ java
+ per-lookup
+ once-per-session
+ test
+ true
+
+
+ project
+ org.apache.maven.project.MavenProject
+ true
+ false
+ The maven project.
+
+
+ targetRoot
+ java.lang.String
+ false
+ true
+ Location where `dependencies.txt` file will be written (should match the Semanticdb targetroot option)
+
+
+
+ ${project}
+ ${session.executionRootDirectory}/target/semanticdb-targetroot
+
+
+
+
+
+
+
+
diff --git a/project/build.properties b/project/build.properties
index 04267b14a..ee4c672cd 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.9.9
+sbt.version=1.10.1
diff --git a/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/ClasspathEntry.scala b/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/ClasspathEntry.scala
index b7caebcd1..061e5e7f6 100644
--- a/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/ClasspathEntry.scala
+++ b/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/ClasspathEntry.scala
@@ -38,6 +38,9 @@ object ClasspathEntry {
* - javacopts.txt: line-separated list of Java compiler options.
* - dependencies.txt: line-separated list of dependency information.
*
+ * Note that the targetroot can contain several files with names ending in
+ * "dependencies.txt" - for example if they come from a multi-module build.
+ *
* @param targetroot
* @return
*/
@@ -46,18 +49,34 @@ object ClasspathEntry {
sourceroot: Path
): List[ClasspathEntry] = {
val javacopts = targetroot.resolve("javacopts.txt")
- val dependencies = targetroot.resolve("dependencies.txt")
- if (Files.isRegularFile(dependencies)) {
- fromDependencies(dependencies)
- } else if (Files.isRegularFile(javacopts)) {
+ if (Files.isRegularFile(javacopts))
fromJavacopts(javacopts, sourceroot)
- } else {
- Nil
- }
+ else
+ discoverDependenciesFromFiles(targetroot)
+ }
+
+ /**
+ * Discover all files that end in "dependencies.txt" directly under
+ * targetroot. There can be many files because we will be writing dependencies
+ * for multiple projects.
+ *
+ * @param targetroot
+ * @return classpath entries read from the discovered files
+ */
+ private def discoverDependenciesFromFiles(
+ targetroot: Path
+ ): List[ClasspathEntry] = {
+ os.list
+ .stream(os.Path(targetroot))
+ .filter(p => os.isFile(p) && p.last.endsWith("dependencies.txt"))
+ .map(path => fromDependencies(path.toNIO))
+ .toList
+ .flatten
+ .distinct
}
/**
- * Parses ClasspathEntry from a "dependencies.txt" file in the targetroot.
+ * Parses ClasspathEntry from a "dependencies.txt" file
*
* Every line of the file is a tab separated value with the following columns:
* groupId, artifactId, version, path to the jar file OR classes directory