Skip to content

Commit

Permalink
Completely revamp how we support JVM compiler plugins. (#4287)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjyw committed Mar 3, 2017
1 parent 14cd3f2 commit f60a46d
Show file tree
Hide file tree
Showing 34 changed files with 868 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
unicode_literals, with_statement)

from pants.backend.jvm.tasks.jvm_compile.zinc.zinc_compile import BaseZincCompile
from pants.util.memo import memoized_property
from pants.util.memo import memoized_method

from pants.contrib.scalajs.targets.scala_js_target import ScalaJSTarget

Expand All @@ -17,6 +17,8 @@ class ScalaJSZincCompile(BaseZincCompile):
_name = 'scala-js'
_file_suffix = '.scala'

scalac_plugins = ['scalajs']

@classmethod
def register_options(cls, register):
super(ScalaJSZincCompile, cls).register_options(register)
Expand All @@ -27,23 +29,10 @@ def register_options(cls, register):
def product_types(cls):
return ['scala_js_ir']

@property
def javac_plugin_jars(self):
return []

def javac_plugin_args(self, exclude):
return []

@memoized_property
def scalac_plugin_jars(self):
@memoized_method
def extra_compile_time_classpath_elements(self):
return self.tool_classpath('scala-js-compiler')

@memoized_property
def scalac_plugin_args(self):
# filter the tool classpath to select only the compiler jar
return ['-S-Xplugin:{}'.format(jar) for jar in self.scalac_plugin_jars
if 'scalajs-compiler_' in jar]

def select(self, target):
if not isinstance(target, ScalaJSTarget):
return False
Expand Down
2 changes: 2 additions & 0 deletions examples/src/java/org/pantsbuild/example/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ page(
':3rdparty_jvm',
':from_maven',
':publish',
'examples/src/java/org/pantsbuild/example/javac/plugin:readme',
'examples/src/scala/org/pantsbuild/example:readme',
'examples/src/scala/org/pantsbuild/example/scalac/plugin:readme',
'src/docs:build_files',
'src/docs:first_concepts',
'src/docs:first_tutorial',
Expand Down
10 changes: 10 additions & 0 deletions examples/src/java/org/pantsbuild/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,16 @@ analysis, disabling summary mode (by passing the `--no-summary` flag) will outpu
for each dependency edge. This mode does no aggregation, so using it effectively usually means
doing analytics or graph analysis with an external tool.

Compiler Plugins
----------------

Pants has robust support for both developing and using compiler plugins for
javac and scalac. For more details:

- [[javac plugins with Pants|pants('examples/src/java/org/pantsbuild/example/javac/plugin:readme')]].
- [[scalac plugins with Pants|pants('examples/src/scala/org/pantsbuild/example/scalac/plugin:readme')]].


Further Reading
---------------

Expand Down
60 changes: 60 additions & 0 deletions examples/src/java/org/pantsbuild/example/javac/plugin/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

javac_plugin(
name = 'simple_javac_plugin',
sources = ['SimpleJavacPlugin.java'],
dependencies = [],
classname = 'org.pantsbuild.example.javac.plugin.SimpleJavacPlugin',
scope='compile',
)

# The plugin will only run on this target if told to via options.
java_library(
name = 'global',
sources = ['Global.java'],
dependencies = [
':simple_javac_plugin'
],
)

# The plugin will only run on this target if told to via options, but if it
# does run, it will use the args specified here.
java_library(
name = 'global_with_local_args',
sources = ['GlobalWithLocalArgs.java'],
dependencies = [
':simple_javac_plugin'
],
javac_plugin_args = {
'simple_javac_plugin': ['args', 'from', 'target', 'global_with_local_args']
}
)

# The plugin will always run on this target, with args specified via options.
java_library(
name = 'local_with_global_args',
sources = ['LocalWithGlobalArgs.java'],
dependencies = [
':simple_javac_plugin'
],
javac_plugins=['simple_javac_plugin']
)

# The plugin will always run on this target, with the args specified here.
java_library(
name = 'local',
sources = ['Local.java'],
dependencies = [
':simple_javac_plugin'
],
javac_plugins=['simple_javac_plugin'],
javac_plugin_args = {
'simple_javac_plugin': ['args', 'from', 'target', 'local']
}
)

page(
name='readme',
source='README.md',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.javac.plugin;


public class Global {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.javac.plugin;


public class GlobalWithLocalArgs {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.javac.plugin;


public class Local {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.javac.plugin;


public class LocalWithGlobalArgs {
}
117 changes: 117 additions & 0 deletions examples/src/java/org/pantsbuild/example/javac/plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
javac Plugins with Pants
========================

For Java versions 8 and higher, the Java compiler, javac, has a
[plugin](https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html) mechanism.

Plugins allow you to hook into the compiler while it's running, to perform various
tasks such as custom error checking, code analysis and so on.

Pants supports both building and using javac plugins.

Building javac plugins
----------------------

javac plugins are specified using a `javac_plugin` target:

```
javac_plugin(
name = 'plugin',
plugin = 'simple_javac_plugin',
sources = ['SimpleJavacPlugin.java'],
dependencies = [],
classname = 'org.pantsbuild.example.javac.plugin.SimpleJavacPlugin',
scope='compile',
)
```

A javac plugin target has the same fields as a `java_library` target,
plus two extra:

- `classname`: The name of the `Plugin` implementation class. Required.
- `plugin`: The logical name of the plugin, as returned by the `Plugin`
class's `getName()` method. If unspecified, this field defaults to
the target name.

Building a plugin target will, in addition to compiling the code, generate
the appropriate metadata into `META-INF/services/com.sun.source.util.Plugin`, so
that javac can load the plugin by name at runtime.

A plugin may be published for later consumption in any repo. It can also
be consumed (with some restrictions) from source, in the same repo.


Using javac plugins
-------------------

Plugins can be integrated in one of two ways:

- Global plugins: specified in `pants.ini` and used on all Java code.
- Per-target plugins: specified on a Java target and used only when compiling that target.

#### Global plugins

Global plugins are specified using the `javac_plugins` key in the `compile.zinc` section of `pants.ini`:

```
[compile.zinc]
javac_plugins: ['simple_javac_plugin']
```

Plugins can optionally take arguments, specified like this:

```
[compile.zinc]
javac_plugins: ['simple_javac_plugin]
javac_plugin_args: {
'simple_javac_plugin': ['arg1', 'arg2']
}
```


#### Per-target plugins

These are specified like this:

```
java_library(
...
javac_plugins: ['simple_javac_plugin'],
javac_plugin_args: {
'simple_javac_plugin': ['arg1', 'arg2']
}
)
```

#### Depending on plugins

In order to load a plugin, it has to be on javac's classpath.
This can be achieved in one of two ways:
- Have targets that must be compiled with a plugin depend (directly or indirectly)
either on the `javac_plugin` target, or on a `jar_library` pointing to a published version
of the plugin.
- Have a `javac-plugin-dep` target in `BUILD.tools`:

```
jar_library(name='java-plugin-dep',
jars = [jar(org='com.foo', name='foo_plugin', rev='1.2.3')],
```

Note that, as always with `BUILD.tools`, plugin locations specified via `java-plugin-dep`
must be published jars. They cannot be local `javac_plugin` targets.

Usually, it will make more sense to use `java-plugin-dep` with global plugins, to avoid
laborious repetition of that dependency, and to use target dependencies for per-target plugins,
to keep the dependencies selective.

Depending directly on the `javac_plugin` has the added advantage of allowing plugin changes
to be picked up when compiling the target that uses the plugin, with no need for an intermediate
publishing step.

Note that, to avoid a chicken-and-egg problem, an in-repo plugin will not be used when
compiling its own `javac_plugin` target, or any of that target's dependencies.
To use a plugin on its own code, you must publish it and consume the published plugin
via `java-plugin-dep`.


Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package org.pantsbuild.example.plugin;
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.javac.plugin;

import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
Expand Down
18 changes: 0 additions & 18 deletions examples/src/java/org/pantsbuild/example/plugin/BUILD

This file was deleted.

This file was deleted.

60 changes: 60 additions & 0 deletions examples/src/scala/org/pantsbuild/example/scalac/plugin/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

scalac_plugin(
name='simple_scalac_plugin',
plugin='simple_scalac_plugin',
classname='org.pantsbuild.example.scalac.plugin.SimpleScalacPlugin',
sources=['SimpleScalacPlugin.scala'],
scope='compile',
)

# The plugin will only run on this target if told to via options.
scala_library(
name = 'global',
sources = ['Global.scala'],
dependencies = [
':simple_scalac_plugin'
],
)

# The plugin will only run on this target if told to via options, but if it
# does run, it will use the args specified here.
scala_library(
name = 'global_with_local_args',
sources = ['GlobalWithLocalArgs.scala'],
dependencies = [
':simple_scalac_plugin'
],
scalac_plugin_args = {
'simple_scalac_plugin': ['args', 'from', 'target', 'global_with_local_args']
}
)

# The plugin will always run on this target, with args specified via options.
scala_library(
name = 'local_with_global_args',
sources = ['LocalWithGlobalArgs.scala'],
dependencies = [
':simple_scalac_plugin'
],
scalac_plugins=['simple_scalac_plugin']
)

# The plugin will always run on this target, with the args specified here.
scala_library(
name = 'local',
sources = ['Local.scala'],
dependencies = [
':simple_scalac_plugin'
],
scalac_plugins=['simple_scalac_plugin'],
scalac_plugin_args = {
'simple_scalac_plugin': ['args', 'from', 'target', 'local']
}
)

page(
name='readme',
source='README.md',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.scalac.plugin

class Global
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.example.scalac.plugin

class GlobalWithLocalArgs
Loading

0 comments on commit f60a46d

Please sign in to comment.