Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: olafurpg/setup-scala@v10
- uses: actions/setup-go@v2
with:
go-version: '^1.13.1'
go-version: "^1.13.1"
- run: go get github.com/sourcegraph/lsif-semanticdb/cmd/lsif-semanticdb
- run: sbt test
check:
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/mdoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Website
on:
push:
branches: [master, documentation-website]
tags: ["*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: olafurpg/setup-scala@v10
- run: sbt docs/docusaurusPublishGhpages
env:
GIT_DEPLOY_KEY: ${{ secrets.GIT_DEPLOY_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: sbt nativeImage
shell: bash
run: |
sbt cli/nativeImage "cli/nativeImageRun --cwd tests/gradle-example"
sbt cli/nativeImage "cli/nativeImageRun --cwd tests/gradle-example index"
- uses: actions/upload-artifact@master
with:
path: ${{ matrix.local_path }}
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Contributing guide

See https://sourcegraph.github.io/lsif-java/docs/contributing.html
184 changes: 9 additions & 175 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,175 +1,9 @@
# Java LSIF indexer ![](https://img.shields.io/badge/status-development-yellow?style=flat)

Visit https://lsif.dev/ to learn about LSIF.

## Usage

⚠️ This project is under development so there is nothing to try out at the
moment.

### Supported tools and versions

Currently, only Java 8 with the build tool sbt is supported. We hope to increase
compatibility with more Java language versions and build tools as the project
evolves.

| Language version | Support |
| ---------------- | --------------------------------- |
| Java 7 | ❌ |
| Java 8 | ✅ |
| Java 11 | ✅ |
| Java 12 | Not tested in CI, but should work |
| Java 13 | Not tested in CI, but should work |
| Java 14 | Not tested in CI, but should work |
| Java 15 | ✅ |
| Java 16 | Not tested in CI, but should work |
| Java 17 | Not tested in CI, but should work |

| Build tool | Support |
| ---------- | ------- |
| Gradle | ✅ |
| Maven | ✅ |
| Bazel | ❌ |
| Buck | ❌ |
| sbt | ✅ |

## Overview

This project is implemented as a
[Java compiler plugin](https://docs.oracle.com/en/java/javase/11/docs/api/jdk.compiler/com/sun/source/util/Plugin.html)
that generates one
[SemanticDB](https://scalameta.org/docs/semanticdb/specification.html) file for
every `*.java` source file. After compilation completes, the SemanticDB files
are processed to produce LSIF.

![A three stage pipeline that starts with a list of Java sources, creates a list of SemanticDB files that then become a single LSIF index.](docs/img/semanticdb-javac-pipeline.svg)

### Why Java compiler plugin?

There are several benefits to implementing lsif-java as a compiler plugin:

- **Simple installation**: compiler plugins are enabled with the `-Xplugin`
compiler option. All Java build tools support a way to customize compiler
options, simplifying installation.
- **Language fidelity**: by using the Java compiler to produce semantic
information, we ensure that the produced LSIF data is accurate even as new
Java language versions with new language features are released.
- **Environment fidelity**: by hooking into the compilation process of the build
tool, we minimize the risk of diverging from the CI build environment such as
installed system dependencies, custom compiler options and custom annotation
processors.

### Why SemanticDB?

SemanticDB is Protobuf schema for information about symbols and types in Java
programs, Scala programs and other languages. There are several benefits to
using SemanticDB as an intermediary representation for LSIF:

- **Simplicity**: It's easy to translate a single Java source file into a single
SemanticDB file inside a compiler plugin. It's more complicated to produce
LSIF because compiler plugins does not have access to a project-wide context,
which is necessary to produce accurate definitions and hovers in multi-module
projects with external library dependencies.
- **Performance**: SemanticDB is fast to write and read. Each compilation unit
can be processed independently to keep memory usage low. The final conversion
from SemanticDB to LSIF can be safely parallelized.
- **Cross-language**: SemanticDB has a
[spec](https://scalameta.org/docs/semanticdb/specification.html) for Java and
Scala enabling cross-language navigation in hybrid Java/Scala codebases.
- **Cross-repository**: Compiler plugins have access to both source code and the
classpath (compiled bytecode of upstream dependencies). SemanticDB has been
designed so that it's also possible to generate spec-compliant symbols from
the classpath alone (no source code) and from the syntax tree of an individual
source file (no classpath). This flexibility allows the
[Metals](https://scalameta.org/metals/) language server to index codebases
from a variety of different inputs, and will be helpful for lsif-java in the
future to unblock cross-repository navigation.

## Contributing

The following sections provide tips on how to contribute to this codebase.

### System dependencies

- `java`: any version should work
- `git`: any version should work
- `lsif-semanticdb`:
`go get github.com/sourcegraph/lsif-semanticdb/cmd/lsif-semanticdb`
- `gradle`: `brew install gradle`, or see
[general installation guide](https://gradle.org/install/).
- `mvn`: `brew install maven`, or see
[general installation guide](https://www.baeldung.com/install-maven-on-windows-linux-mac).

### Project structure

These are the main components of the project.

- `semanticdb-javac/src/main/java`: the Java compiler plugin that creates
SemanticDB files.
- `tests/minimized`: minimized Java source files that reproduce interesting test
cases.
- `tests/unit`: fast running unit tests that are helpful for local edit-and-test
workflows.
- `tests/snapshots`: slow running
["snapshot tests"](https://jestjs.io/docs/en/snapshot-testing) that index a
corpus of published Java libraries.
- `build.sbt`: the sbt build definition.
- `project/plugins.sbt`: plugins for the sbt build.

### Helpful commands

| Command | Where | Description |
| ------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
| `./sbt` | terminal | Start interactive sbt shell with Java 11. Takes a while to load on the first run. |
| `unit/test` | sbt | Run fast unit tests. |
| `~unit/test` | sbt | Start watch mode to run tests on file save, good for local edit-and-test workflows. |
| `buildTools/test` | sbt | Run slow build tool tests (Gradle, Maven). |
| `snapshots/testOnly tests.MinimizedSnapshotSuite` | sbt | Runs fast snapshot tests. Indexes a small set of files under `tests/minimized`. |
| `snapshots/testOnly tests.MinimizedSnapshotSuite -- *InnerClasses*` | sbt | Runs only individual tests cases matching the name "InnerClasses". |
| `snapshots/testOnly tests.LibrarySnapshotSuite` | sbt | Runs slow snapshot tests. Indexes a corpus of external Java libraries. |
| `snapshots/test` | sbt | Runs all snapshot tests. |
| `snapshots/run` | sbt | Update snapshot tests. Use this command after you have fixed a bug. |
| `cli/run --cwd DIRECTORY` | sbt | Run `lsif-java` command-line tool against a given Gradle/Maven build. |
| `fixAll` | sbt | Run Scalafmt, Scalafix and Javafmt on all sources. Run this before opening a PR. |

### Import the project into IntelliJ

It's recommended to use IntelliJ when editing code in this codebase.

First, install the
[IntelliJ Community Edition](https://www.jetbrains.com/idea/download/). The
community edition is
[open source](https://github.com/JetBrains/intellij-community) and free to use.

Next, install the IntelliJ Scala plugin.

Finally, run "File > Project From Existing Sources" to import the sbt build into
IntelliJ. Select the "sbt" option if it asks you to choose between
sbt/BSP/Bloop.

It's best to run tests from the sbt shell, not from the IntelliJ UI.

### Don't use VS Code/Vim/Sublime Text/Emacs

If you want to use completions and precise code navigation, it's not recommended
to use other editors than IntelliJ. IntelliJ is the only IDE that properly
supports hybrid Java/Scala codebases at the moment, although that may change
soon thanks to lsif-java :)

### Tests are written in Scala

This codebases uses the Scala library [MUnit](https://scalameta.org/munit/) to
write tests because:

- MUnit has built-in assertions that print readable multiline diffs in color.
- MUnit makes it easy to implement
[snapshot testing](https://jestjs.io/docs/en/snapshot-testing), which is a
testing technique that's heavily used in this codebase.
- Multiline literal strings in Scala make it easy to write unit tests for source
code (which is always multiline). Modern versions of Java support multiline
string literals, but they're not supported in Java 8, which is supported by
lsif-java.

## Benchmarks

See [docs/benchmarks.md] for benchmark results.
# Java indexer for the Language Server Index Format (LSIF) ![](https://img.shields.io/badge/status-development-yellow?style=flat)

| Documentation | Link |
| -------------------- | ---------------------------------------------------------------------- |
| Landing page | https://sourcegraph.github.io/lsif-java |
| Getting started | https://sourcegraph.github.io/lsif-java/docs/getting-started.html |
| Manual configuration | https://sourcegraph.github.io/lsif-java/docs/manual-configuration.html |
| Contributing | https://sourcegraph.github.io/lsif-java/docs/contributing.html |
| Design | https://sourcegraph.github.io/lsif-java/docs/design.html |
59 changes: 57 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _}
import scala.xml.transform.{RewriteRule, RuleTransformer}
import java.io.File
import java.util.Properties
import scala.collection.mutable.ListBuffer
Expand Down Expand Up @@ -59,7 +61,7 @@ commands +=
commands +=
Command.command("checkAll") { s =>
"scalafmtCheckAll" :: "scalafmtSbtCheck" :: "scalafixAll --check" ::
"javafmtCheckAll" :: "publishLocal" :: s
"javafmtCheckAll" :: "publishLocal" :: "docs/docusaurusCreateSite" :: s
}

lazy val agent = project
Expand Down Expand Up @@ -97,6 +99,16 @@ lazy val plugin = project
old.withEnabled(false)
},
fatjarPackageSettings,
assemblyShadeRules.in(assembly) :=
Seq(
ShadeRule
.rename(
"com.google.**" -> "com.sourcegraph.shaded.com.google.@1",
"google.**" -> "com.sourcegraph.shaded.google.@1",
"org.relaxng.**" -> "com.sourcegraph.shaded.relaxng.@1"
)
.inAll
),
crossPaths := false,
PB.targets.in(Compile) :=
Seq(PB.gens.java -> (Compile / sourceManaged).value)
Expand Down Expand Up @@ -280,7 +292,7 @@ lazy val fatjarPackageSettings = List[Def.Setting[_]](
case PathList("sun", _ @_*) =>
MergeStrategy.discard
case PathList("META-INF", "versions", "9", "module-info.class") =>
MergeStrategy.first
MergeStrategy.discard
case x =>
val oldStrategy = (assemblyMergeStrategy in assembly).value
oldStrategy(x)
Expand All @@ -291,5 +303,48 @@ lazy val fatjarPackageSettings = List[Def.Setting[_]](
val _ = assembly.value
IO.copyFile(fatJar, slimJar, CopyOptions().withOverwrite(true))
slimJar
},
packagedArtifact.in(Compile).in(packageBin) := {
val (art, slimJar) = packagedArtifact.in(Compile).in(packageBin).value
val fatJar =
new File(crossTarget.value + "/" + assemblyJarName.in(assembly).value)
val _ = assembly.value
IO.copy(List(fatJar -> slimJar), CopyOptions().withOverwrite(true))
(art, slimJar)
},
pomPostProcess := { node =>
new RuleTransformer(
new RewriteRule {
private def isAbsorbedDependency(node: XmlNode): Boolean = {
node.label == "dependency" &&
node.child.exists(child => child.label == "artifactId")
}
override def transform(node: XmlNode): XmlNodeSeq =
node match {
case e: Elem if isAbsorbedDependency(node) =>
Comment(
"the dependency that was here has been absorbed via sbt-assembly"
)
case _ =>
node
}
}
).transform(node).head
}
)

lazy val docs = project
.in(file("lsif-java-docs"))
.settings(
mdocOut :=
baseDirectory.in(ThisBuild).value / "website" / "target" / "docs",
fork := false,
mdocVariables :=
Map[String, String](
"VERSION" -> version.value,
"SCALA_VERSION" -> scalaVersion.value,
"STABLE_VERSION" -> version.value.replaceFirst("\\-.*", "")
)
)
.dependsOn(unit)
.enablePlugins(DocusaurusPlugin)
22 changes: 18 additions & 4 deletions cli/src/main/scala/com/sourcegraph/lsif_java/IndexCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import java.nio.file.Paths

import com.sourcegraph.lsif_java.buildtools.BuildTool
import moped.annotations.Description
import moped.annotations.ExampleUsage
import moped.annotations.ExampleValue
import moped.annotations.Inline
import moped.annotations.TrailingArguments
import moped.annotations.Usage
import moped.cli.Application
import moped.cli.Command
import moped.cli.CommandParser
Expand All @@ -18,7 +20,12 @@ import os.Inherit
import os.Shellable

@Description(
"Generates an LSIF index for the Java build of a provided workspace directory."
"Automatically generate an LSIF index in the current working directory."
)
@Usage("lsif-java index [OPTIONS ...] -- [TRAILING_ARGUMENTS ...]")
@ExampleUsage(
"""|# Running the `index` command with no flags should work most of the time.
|$ lsif-java index""".stripMargin
)
case class IndexCommand(
@Description("The path where to generate the LSIF index.") output: Path =
Expand All @@ -41,12 +48,12 @@ case class IndexCommand(
)
@ExampleValue("Gradle") buildTool: Option[String] = None,
@Description(
"Whether to enable remove generated temporary files on exit."
"Whether to remove generated temporary files on exit."
) cleanup: Boolean = true,
@Description(
"The build command to use to compile all sources. " +
"Optional. The build command to use to compile all sources. " +
"Defaults to a build-specific command. For example, the default command for Maven command is 'clean verify -DskipTests'." +
"To override the default, pass in the build command after a double dash: 'lsif-java -- compile'"
"To override the default, pass in the build command after a double dash: 'lsif-java index -- compile test:compile'"
)
@TrailingArguments() buildCommand: List[String] = Nil,
@Inline
Expand Down Expand Up @@ -79,6 +86,7 @@ case class IndexCommand(
def workingDirectory: Path = AbsolutePath.of(app.env.workingDirectory)
def finalTargetroot(default: Path): Path =
AbsolutePath.of(targetroot.getOrElse(default), workingDirectory)
def finalOutput: Path = AbsolutePath.of(output, workingDirectory)
def finalBuildCommand(default: List[String]): List[String] =
if (buildCommand.isEmpty)
default
Expand Down Expand Up @@ -142,8 +150,14 @@ case class IndexCommand(
} else {
val generateLsifResult = process(
"lsif-semanticdb",
s"--out=${finalOutput}",
s"--semanticdbDir=${tool.targetroot}"
)
if (
generateLsifResult.exitCode == 0 && Files.isRegularFile(finalOutput)
) {
app.info(finalOutput.toAbsolutePath().toString())
}
generateSemanticdbResult.exitCode + generateLsifResult.exitCode
}
case many =>
Expand Down
Loading