From 82a21d7d8f19e1d1cdf459a77e4c910179ee4b4d Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Wed, 27 Oct 2021 12:05:33 -0400 Subject: [PATCH 01/19] proof of concept add-exports plugin to enable goethe --- .../palantir/baseline/plugins/Baseline.java | 1 + .../baseline/plugins/BaselineGoethe.java | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java index 91caf600c..0038f59ad 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java @@ -60,6 +60,7 @@ public void apply(Project project) { proj.getPluginManager().apply(BaselineJavaCompilerDiagnostics.class); proj.getPluginManager().apply(BaselineJavaParameters.class); proj.getPluginManager().apply(BaselineImmutables.class); + proj.getPluginManager().apply(BaselineGoethe.class); }); } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java new file mode 100644 index 000000000..1c734e7fc --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java @@ -0,0 +1,113 @@ +/* + * (c) Copyright 2021 Palantir Technologies 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.palantir.baseline.plugins; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.util.Collections; +import java.util.Objects; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.file.FileCollection; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; +import org.gradle.process.CommandLineArgumentProvider; + +public final class BaselineGoethe implements Plugin { + + private static final ImmutableList GOETHE_ARGS = ImmutableList.of( + "--add-exports", + "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); + + @Override + public void apply(Project project) { + project.getPluginManager().withPlugin("java", unused -> { + project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + project.getTasks() + .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + .get() + .getOptions() + .getCompilerArgumentProviders() + // Use an anonymous class because tasks with lambda inputs cannot be cached + .add(new CommandLineArgumentProvider() { + @Override + public Iterable asArguments() { + if (hasGoetheProcessor(project, sourceSet)) { + return GOETHE_ARGS; + } + return Collections.emptyList(); + } + }); + }); + + project.getTasks().withType(Test.class, new Action() { + + @Override + public void execute(Test test) { + test.getJvmArgumentProviders().add(new CommandLineArgumentProvider() { + + @Override + public Iterable asArguments() { + if (hasGoetheJar(test.getClasspath())) { + return GOETHE_ARGS; + } + return Collections.emptyList(); + } + }); + } + }); + }); + } + + private static boolean hasGoetheProcessor(Project project, SourceSet sourceSet) { + return project + .getConfigurations() + .getByName(sourceSet.getAnnotationProcessorConfigurationName()) + .getDependencies() + .stream() + .anyMatch(BaselineGoethe::isGoetheValue); + } + + private static boolean hasGoetheJar(FileCollection classpath) { + return !classpath + .filter(new Spec() { + @Override + public boolean isSatisfiedBy(File element) { + return element.getName().startsWith("goethe-") + && element.getName().endsWith(".jar"); + } + }) + .isEmpty(); + } + + private static boolean isGoetheValue(Dependency dep) { + return Objects.equals(dep.getGroup(), "com.palantir.goethe") && Objects.equals(dep.getName(), "goethe"); + } +} From 09d6946b7d0aa512b043b95fcfaba053bc05fc8a Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 10:18:13 -0400 Subject: [PATCH 02/19] generalized approach --- .../extensions/BaselineExportsExtension.java | 40 +++++ .../palantir/baseline/plugins/Baseline.java | 2 +- .../baseline/plugins/BaselineGoethe.java | 113 ------------- .../plugins/BaselineModuleJvmArgs.java | 153 ++++++++++++++++++ 4 files changed, 194 insertions(+), 114 deletions(-) create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java delete mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java new file mode 100644 index 000000000..37cd31286 --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java @@ -0,0 +1,40 @@ +/* + * (c) Copyright 2019 Palantir Technologies 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.palantir.baseline.extensions; + +import javax.inject.Inject; +import org.gradle.api.Project; +import org.gradle.api.provider.SetProperty; +import org.gradle.jvm.toolchain.JavaLanguageVersion; + +/** + * Extension to configure {@code --add-exports [VALUE]=ALL-UNNAMED} for the current module. + */ +public class BaselineExportsExtension { + + private final SetProperty exports; + + @Inject + public BaselineExportsExtension(Project project) { + exports = project.getObjects().setProperty(String.class); + } + + /** Target {@link JavaLanguageVersion} for compilation. */ + public final SetProperty exports() { + return exports; + } +} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java index 0038f59ad..260d73475 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/Baseline.java @@ -60,7 +60,7 @@ public void apply(Project project) { proj.getPluginManager().apply(BaselineJavaCompilerDiagnostics.class); proj.getPluginManager().apply(BaselineJavaParameters.class); proj.getPluginManager().apply(BaselineImmutables.class); - proj.getPluginManager().apply(BaselineGoethe.class); + proj.getPluginManager().apply(BaselineModuleJvmArgs.class); }); } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java deleted file mode 100644 index 1c734e7fc..000000000 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineGoethe.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * (c) Copyright 2021 Palantir Technologies 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.palantir.baseline.plugins; - -import com.google.common.collect.ImmutableList; -import java.io.File; -import java.util.Collections; -import java.util.Objects; -import org.gradle.api.Action; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.file.FileCollection; -import org.gradle.api.specs.Spec; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.compile.JavaCompile; -import org.gradle.api.tasks.testing.Test; -import org.gradle.process.CommandLineArgumentProvider; - -public final class BaselineGoethe implements Plugin { - - private static final ImmutableList GOETHE_ARGS = ImmutableList.of( - "--add-exports", - "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); - - @Override - public void apply(Project project) { - project.getPluginManager().withPlugin("java", unused -> { - project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - project.getTasks() - .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get() - .getOptions() - .getCompilerArgumentProviders() - // Use an anonymous class because tasks with lambda inputs cannot be cached - .add(new CommandLineArgumentProvider() { - @Override - public Iterable asArguments() { - if (hasGoetheProcessor(project, sourceSet)) { - return GOETHE_ARGS; - } - return Collections.emptyList(); - } - }); - }); - - project.getTasks().withType(Test.class, new Action() { - - @Override - public void execute(Test test) { - test.getJvmArgumentProviders().add(new CommandLineArgumentProvider() { - - @Override - public Iterable asArguments() { - if (hasGoetheJar(test.getClasspath())) { - return GOETHE_ARGS; - } - return Collections.emptyList(); - } - }); - } - }); - }); - } - - private static boolean hasGoetheProcessor(Project project, SourceSet sourceSet) { - return project - .getConfigurations() - .getByName(sourceSet.getAnnotationProcessorConfigurationName()) - .getDependencies() - .stream() - .anyMatch(BaselineGoethe::isGoetheValue); - } - - private static boolean hasGoetheJar(FileCollection classpath) { - return !classpath - .filter(new Spec() { - @Override - public boolean isSatisfiedBy(File element) { - return element.getName().startsWith("goethe-") - && element.getName().endsWith(".jar"); - } - }) - .isEmpty(); - } - - private static boolean isGoetheValue(Dependency dep) { - return Objects.equals(dep.getGroup(), "com.palantir.goethe") && Objects.equals(dep.getName(), "goethe"); - } -} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java new file mode 100644 index 000000000..d65f04481 --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -0,0 +1,153 @@ +/* + * (c) Copyright 2021 Palantir Technologies 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.palantir.baseline.plugins; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.palantir.baseline.extensions.BaselineExportsExtension; +import java.io.IOException; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.java.archives.Manifest; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.tasks.Jar; +import org.gradle.process.CommandLineArgumentProvider; + +/** + * This plugin reuses the {@code Add-Exports} manifest entry to propagate and collect required exports + * from transitive dependencies, and applies them to compilation (for annotation processors) and + * execution (tests, javaExec, etc) for runtime dependencies. + */ +public final class BaselineModuleJvmArgs implements Plugin { + + private static final String EXTENSION_NAME = "moduleJvmArgs"; + private static final String ADD_EXPORTS_ATTRIBUTE = "Add-Exports"; + + private static final Splitter EXPORT_SPLITTER = + Splitter.on(' ').trimResults().omitEmptyStrings(); + + @Override + public void apply(Project project) { + project.getPluginManager().withPlugin("java", unused -> { + BaselineExportsExtension extension = + project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); + project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + project.getTasks() + .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + .get() + .getOptions() + .getCompilerArgumentProviders() + // Use an anonymous class because tasks with lambda inputs cannot be cached + .add(new CommandLineArgumentProvider() { + @Override + public Iterable asArguments() { + // Annotation processors are executed at compile time + return collectAnnotationProcessorExports(project, extension, sourceSet); + } + }); + }); + + project.getTasks().withType(Test.class, new Action() { + + @Override + public void execute(Test test) { + test.getJvmArgumentProviders().add(new CommandLineArgumentProvider() { + + @Override + public Iterable asArguments() { + return collectClasspathExports(extension, test.getClasspath()); + } + }); + } + }); + + project.getTasks().withType(JavaExec.class, new Action() { + + @Override + public void execute(JavaExec javaExec) { + javaExec.getJvmArgumentProviders().add(new CommandLineArgumentProvider() { + + @Override + public Iterable asArguments() { + return collectClasspathExports(extension, javaExec.getClasspath()); + } + }); + } + }); + + project.getTasks().withType(Jar.class, new Action() { + @Override + public void execute(Jar jar) { + jar.manifest(new Action() { + @Override + public void execute(Manifest manifest) { + // Only locally defined exports are applied to jars + Set exports = extension.exports().get(); + if (!exports.isEmpty()) { + manifest.attributes(ImmutableMap.of(ADD_EXPORTS_ATTRIBUTE, String.join(" ", exports))); + } + } + }); + } + }); + }); + } + + private static ImmutableList collectAnnotationProcessorExports( + Project project, BaselineExportsExtension extension, SourceSet sourceSet) { + return collectClasspathExports( + extension, project.getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName())); + } + + private static ImmutableList collectClasspathExports( + BaselineExportsExtension extension, FileCollection classpath) { + return Stream.concat( + classpath.getFiles().stream().flatMap(file -> { + try { + if (file.getName().endsWith(".jar") && file.isFile()) { + JarFile jar = new JarFile(file); + String value = jar.getManifest() + .getMainAttributes() + .getValue(ADD_EXPORTS_ATTRIBUTE); + if (Strings.isNullOrEmpty(value)) { + return Stream.empty(); + } + return EXPORT_SPLITTER.splitToStream(value); + } + return Stream.empty(); + } catch (IOException e) { + return Stream.empty(); + } + }), + extension.exports().get().stream()) + .distinct() + .sorted() + .flatMap(modulePackagePair -> Stream.of("--add-exports", modulePackagePair + "=ALL-UNNAMED")) + .collect(ImmutableList.toImmutableList()); + } +} From e4b31c8112767e1d7c445ed994581d2f5852fefd Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 10:23:20 -0400 Subject: [PATCH 03/19] register plugin --- .../com.palantir.baseline-module-jvm-args.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 gradle-baseline-java/src/main/resources/META-INF/gradle-plugins/com.palantir.baseline-module-jvm-args.properties diff --git a/gradle-baseline-java/src/main/resources/META-INF/gradle-plugins/com.palantir.baseline-module-jvm-args.properties b/gradle-baseline-java/src/main/resources/META-INF/gradle-plugins/com.palantir.baseline-module-jvm-args.properties new file mode 100644 index 000000000..45ff1303c --- /dev/null +++ b/gradle-baseline-java/src/main/resources/META-INF/gradle-plugins/com.palantir.baseline-module-jvm-args.properties @@ -0,0 +1 @@ +implementation-class=com.palantir.baseline.plugins.BaselineModuleJvmArgs From ad1bb1ecf1c12f839edae9143361b527b2fec353 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 10:41:45 -0400 Subject: [PATCH 04/19] version check --- .../plugins/BaselineModuleJvmArgs.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index d65f04481..206c6936f 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -22,10 +22,12 @@ import com.google.common.collect.ImmutableMap; import com.palantir.baseline.extensions.BaselineExportsExtension; import java.io.IOException; +import java.util.Collections; import java.util.Set; import java.util.jar.JarFile; import java.util.stream.Stream; import org.gradle.api.Action; +import org.gradle.api.JavaVersion; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; @@ -57,9 +59,14 @@ public void apply(Project project) { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - project.getTasks() + JavaCompile javaCompile = project.getTasks() .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get() + .get(); + if (javaCompile.getSourceCompatibility().contains(".") + || Integer.parseInt(javaCompile.getSourceCompatibility()) < 16) { + return; + } + javaCompile .getOptions() .getCompilerArgumentProviders() // Use an anonymous class because tasks with lambda inputs cannot be cached @@ -80,7 +87,10 @@ public void execute(Test test) { @Override public Iterable asArguments() { - return collectClasspathExports(extension, test.getClasspath()); + if (test.getJavaVersion().isCompatibleWith(JavaVersion.VERSION_16)) { + return collectClasspathExports(extension, test.getClasspath()); + } + return Collections.emptyList(); } }); } @@ -94,7 +104,10 @@ public void execute(JavaExec javaExec) { @Override public Iterable asArguments() { - return collectClasspathExports(extension, javaExec.getClasspath()); + if (javaExec.getJavaVersion().isCompatibleWith(JavaVersion.VERSION_16)) { + return collectClasspathExports(extension, javaExec.getClasspath()); + } + return Collections.emptyList(); } }); } From 0f844392e985b2556c8ea71dfc422fc3dcf6807e Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 10:51:35 -0400 Subject: [PATCH 05/19] Revert "version check" This reverts commit ad1bb1ecf1c12f839edae9143361b527b2fec353. --- .../plugins/BaselineModuleJvmArgs.java | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 206c6936f..d65f04481 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -22,12 +22,10 @@ import com.google.common.collect.ImmutableMap; import com.palantir.baseline.extensions.BaselineExportsExtension; import java.io.IOException; -import java.util.Collections; import java.util.Set; import java.util.jar.JarFile; import java.util.stream.Stream; import org.gradle.api.Action; -import org.gradle.api.JavaVersion; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; @@ -59,14 +57,9 @@ public void apply(Project project) { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - JavaCompile javaCompile = project.getTasks() + project.getTasks() .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get(); - if (javaCompile.getSourceCompatibility().contains(".") - || Integer.parseInt(javaCompile.getSourceCompatibility()) < 16) { - return; - } - javaCompile + .get() .getOptions() .getCompilerArgumentProviders() // Use an anonymous class because tasks with lambda inputs cannot be cached @@ -87,10 +80,7 @@ public void execute(Test test) { @Override public Iterable asArguments() { - if (test.getJavaVersion().isCompatibleWith(JavaVersion.VERSION_16)) { - return collectClasspathExports(extension, test.getClasspath()); - } - return Collections.emptyList(); + return collectClasspathExports(extension, test.getClasspath()); } }); } @@ -104,10 +94,7 @@ public void execute(JavaExec javaExec) { @Override public Iterable asArguments() { - if (javaExec.getJavaVersion().isCompatibleWith(JavaVersion.VERSION_16)) { - return collectClasspathExports(extension, javaExec.getClasspath()); - } - return Collections.emptyList(); + return collectClasspathExports(extension, javaExec.getClasspath()); } }); } From de71cadb9d11b39348eef0fa3c01d66eb3c49479 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 10:56:05 -0400 Subject: [PATCH 06/19] log --- .../com/palantir/baseline/plugins/BaselineModuleJvmArgs.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index d65f04481..e346a6913 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -80,7 +80,9 @@ public void execute(Test test) { @Override public Iterable asArguments() { - return collectClasspathExports(extension, test.getClasspath()); + ImmutableList arguments = collectClasspathExports(extension, test.getClasspath()); + project.getLogger().error("Executing tests with additional arguments: {}", arguments); + return arguments; } }); } From c7fce7d48317626e6ed8145152429fa5ea31913b Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 11:22:01 -0400 Subject: [PATCH 07/19] no compiler args --- .../plugins/BaselineModuleJvmArgs.java | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index e346a6913..9ea2b5948 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -32,8 +32,6 @@ import org.gradle.api.java.archives.Manifest; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.tasks.Jar; import org.gradle.process.CommandLineArgumentProvider; @@ -56,21 +54,21 @@ public void apply(Project project) { project.getPluginManager().withPlugin("java", unused -> { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); - project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - project.getTasks() - .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get() - .getOptions() - .getCompilerArgumentProviders() - // Use an anonymous class because tasks with lambda inputs cannot be cached - .add(new CommandLineArgumentProvider() { - @Override - public Iterable asArguments() { - // Annotation processors are executed at compile time - return collectAnnotationProcessorExports(project, extension, sourceSet); - } - }); - }); + // project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + // project.getTasks() + // .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + // .get() + // .getOptions() + // .getCompilerArgumentProviders() + // // Use an anonymous class because tasks with lambda inputs cannot be cached + // .add(new CommandLineArgumentProvider() { + // @Override + // public Iterable asArguments() { + // // Annotation processors are executed at compile time + // return collectAnnotationProcessorExports(project, extension, sourceSet); + // } + // }); + // }); project.getTasks().withType(Test.class, new Action() { @@ -80,7 +78,8 @@ public void execute(Test test) { @Override public Iterable asArguments() { - ImmutableList arguments = collectClasspathExports(extension, test.getClasspath()); + ImmutableList arguments = + collectClasspathExports(project, extension, test.getClasspath()); project.getLogger().error("Executing tests with additional arguments: {}", arguments); return arguments; } @@ -96,7 +95,7 @@ public void execute(JavaExec javaExec) { @Override public Iterable asArguments() { - return collectClasspathExports(extension, javaExec.getClasspath()); + return collectClasspathExports(project, extension, javaExec.getClasspath()); } }); } @@ -123,11 +122,13 @@ public void execute(Manifest manifest) { private static ImmutableList collectAnnotationProcessorExports( Project project, BaselineExportsExtension extension, SourceSet sourceSet) { return collectClasspathExports( - extension, project.getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName())); + project, + extension, + project.getConfigurations().getByName(sourceSet.getAnnotationProcessorConfigurationName())); } private static ImmutableList collectClasspathExports( - BaselineExportsExtension extension, FileCollection classpath) { + Project project, BaselineExportsExtension extension, FileCollection classpath) { return Stream.concat( classpath.getFiles().stream().flatMap(file -> { try { @@ -143,6 +144,7 @@ private static ImmutableList collectClasspathExports( } return Stream.empty(); } catch (IOException e) { + project.getLogger().warn("Failed to check jar {} for manifest attributes", file, e); return Stream.empty(); } }), From 721458fc4a53f7923fe90ece3cb57200af507265 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 11:53:18 -0400 Subject: [PATCH 08/19] avoid setting a release at compile time --- .../baseline/plugins/BaselineJavaVersion.java | 1 + .../plugins/BaselineModuleJvmArgs.java | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java index 54c7460d2..572437ddd 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java @@ -90,6 +90,7 @@ public void execute(JavaCompile javaCompile) { javaCompile.getJavaCompiler().set(javaToolchainService.compilerFor(new Action() { @Override public void execute(JavaToolchainSpec javaToolchainSpec) { + javaCompile.getOptions().getRelease().set((Integer) null); javaToolchainSpec.getLanguageVersion().set(targetVersionProvider); } })); diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 9ea2b5948..2dd83e218 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -32,6 +32,8 @@ import org.gradle.api.java.archives.Manifest; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.tasks.Jar; import org.gradle.process.CommandLineArgumentProvider; @@ -54,21 +56,21 @@ public void apply(Project project) { project.getPluginManager().withPlugin("java", unused -> { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); - // project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - // project.getTasks() - // .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - // .get() - // .getOptions() - // .getCompilerArgumentProviders() - // // Use an anonymous class because tasks with lambda inputs cannot be cached - // .add(new CommandLineArgumentProvider() { - // @Override - // public Iterable asArguments() { - // // Annotation processors are executed at compile time - // return collectAnnotationProcessorExports(project, extension, sourceSet); - // } - // }); - // }); + project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + project.getTasks() + .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + .get() + .getOptions() + .getCompilerArgumentProviders() + // Use an anonymous class because tasks with lambda inputs cannot be cached + .add(new CommandLineArgumentProvider() { + @Override + public Iterable asArguments() { + // Annotation processors are executed at compile time + return collectAnnotationProcessorExports(project, extension, sourceSet); + } + }); + }); project.getTasks().withType(Test.class, new Action() { From 458b014ae9f33181c248147433af903f4feb458c Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 12:54:33 -0400 Subject: [PATCH 09/19] Revert "avoid setting a release at compile time" This reverts commit 721458fc4a53f7923fe90ece3cb57200af507265. --- .../baseline/plugins/BaselineJavaVersion.java | 1 - .../plugins/BaselineModuleJvmArgs.java | 32 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java index 572437ddd..54c7460d2 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineJavaVersion.java @@ -90,7 +90,6 @@ public void execute(JavaCompile javaCompile) { javaCompile.getJavaCompiler().set(javaToolchainService.compilerFor(new Action() { @Override public void execute(JavaToolchainSpec javaToolchainSpec) { - javaCompile.getOptions().getRelease().set((Integer) null); javaToolchainSpec.getLanguageVersion().set(targetVersionProvider); } })); diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 2dd83e218..9ea2b5948 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -32,8 +32,6 @@ import org.gradle.api.java.archives.Manifest; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.tasks.Jar; import org.gradle.process.CommandLineArgumentProvider; @@ -56,21 +54,21 @@ public void apply(Project project) { project.getPluginManager().withPlugin("java", unused -> { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); - project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - project.getTasks() - .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get() - .getOptions() - .getCompilerArgumentProviders() - // Use an anonymous class because tasks with lambda inputs cannot be cached - .add(new CommandLineArgumentProvider() { - @Override - public Iterable asArguments() { - // Annotation processors are executed at compile time - return collectAnnotationProcessorExports(project, extension, sourceSet); - } - }); - }); + // project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + // project.getTasks() + // .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + // .get() + // .getOptions() + // .getCompilerArgumentProviders() + // // Use an anonymous class because tasks with lambda inputs cannot be cached + // .add(new CommandLineArgumentProvider() { + // @Override + // public Iterable asArguments() { + // // Annotation processors are executed at compile time + // return collectAnnotationProcessorExports(project, extension, sourceSet); + // } + // }); + // }); project.getTasks().withType(Test.class, new Action() { From bf6e0b1371d0acab809159013841d596de3c443e Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 13:23:20 -0400 Subject: [PATCH 10/19] opt out of javac args for now --- .../plugins/BaselineModuleJvmArgs.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 9ea2b5948..de367f4c4 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -32,6 +32,8 @@ import org.gradle.api.java.archives.Manifest; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.tasks.Jar; import org.gradle.process.CommandLineArgumentProvider; @@ -54,21 +56,26 @@ public void apply(Project project) { project.getPluginManager().withPlugin("java", unused -> { BaselineExportsExtension extension = project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); - // project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - // project.getTasks() - // .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - // .get() - // .getOptions() - // .getCompilerArgumentProviders() - // // Use an anonymous class because tasks with lambda inputs cannot be cached - // .add(new CommandLineArgumentProvider() { - // @Override - // public Iterable asArguments() { - // // Annotation processors are executed at compile time - // return collectAnnotationProcessorExports(project, extension, sourceSet); - // } - // }); - // }); + + // javac isn't provided `--add-exports` args for the time being due to + // https://github.com/gradle/gradle/issues/18824 + if (project.hasProperty("add.exports.to.javac")) { + project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { + project.getTasks() + .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) + .get() + .getOptions() + .getCompilerArgumentProviders() + // Use an anonymous class because tasks with lambda inputs cannot be cached + .add(new CommandLineArgumentProvider() { + @Override + public Iterable asArguments() { + // Annotation processors are executed at compile time + return collectAnnotationProcessorExports(project, extension, sourceSet); + } + }); + }); + } project.getTasks().withType(Test.class, new Action() { From e2d8a696dd1c8c80e65b3f7d35d0e14f98dd0b2b Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 29 Oct 2021 13:30:00 -0400 Subject: [PATCH 11/19] logging --- .../plugins/BaselineModuleJvmArgs.java | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index de367f4c4..ab08a7de9 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -61,9 +61,10 @@ public void apply(Project project) { // https://github.com/gradle/gradle/issues/18824 if (project.hasProperty("add.exports.to.javac")) { project.getExtensions().getByType(SourceSetContainer.class).configureEach(sourceSet -> { - project.getTasks() + JavaCompile javaCompile = project.getTasks() .named(sourceSet.getCompileJavaTaskName(), JavaCompile.class) - .get() + .get(); + javaCompile .getOptions() .getCompilerArgumentProviders() // Use an anonymous class because tasks with lambda inputs cannot be cached @@ -71,7 +72,15 @@ public void apply(Project project) { @Override public Iterable asArguments() { // Annotation processors are executed at compile time - return collectAnnotationProcessorExports(project, extension, sourceSet); + ImmutableList arguments = + collectAnnotationProcessorExports(project, extension, sourceSet); + project.getLogger() + .debug( + "BaselineModuleJvmArgs compiling {} on {} with exports: {}", + javaCompile.getName(), + project, + arguments); + return arguments; } }); }); @@ -87,7 +96,12 @@ public void execute(Test test) { public Iterable asArguments() { ImmutableList arguments = collectClasspathExports(project, extension, test.getClasspath()); - project.getLogger().error("Executing tests with additional arguments: {}", arguments); + project.getLogger() + .debug( + "BaselineModuleJvmArgs executing {} on {} with exports: {}", + test.getName(), + project, + arguments); return arguments; } }); @@ -102,7 +116,15 @@ public void execute(JavaExec javaExec) { @Override public Iterable asArguments() { - return collectClasspathExports(project, extension, javaExec.getClasspath()); + ImmutableList arguments = + collectClasspathExports(project, extension, javaExec.getClasspath()); + project.getLogger() + .debug( + "BaselineModuleJvmArgs executing {} on {} with exports: {}", + javaExec.getName(), + project, + arguments); + return arguments; } }); } @@ -117,7 +139,19 @@ public void execute(Manifest manifest) { // Only locally defined exports are applied to jars Set exports = extension.exports().get(); if (!exports.isEmpty()) { + project.getLogger() + .debug( + "BaselineModuleJvmArgs adding manifest attributes to {} in {}: {}", + jar.getName(), + project, + exports); manifest.attributes(ImmutableMap.of(ADD_EXPORTS_ATTRIBUTE, String.join(" ", exports))); + } else { + project.getLogger() + .debug( + "BaselineModuleJvmArgs not adding manifest attributes to {} in {}", + jar.getName(), + project); } } }); @@ -147,6 +181,12 @@ private static ImmutableList collectClasspathExports( if (Strings.isNullOrEmpty(value)) { return Stream.empty(); } + project.getLogger() + .debug( + "Found manifest entry {}: {} in jar {}", + ADD_EXPORTS_ATTRIBUTE, + value, + file); return EXPORT_SPLITTER.splitToStream(value); } return Stream.empty(); From a5ac4b5dcf98a8af3806bc5f29929894d4894cd6 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Tue, 2 Nov 2021 12:56:54 -0400 Subject: [PATCH 12/19] tests --- .../plugins/BaselineModuleJvmArgs.java | 48 ++++---- ...aselineModuleJvmArgsIntegrationTest.groovy | 111 ++++++++++++++++++ 2 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index ab08a7de9..38bb3bb44 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -28,6 +28,7 @@ import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.java.archives.Manifest; import org.gradle.api.tasks.JavaExec; @@ -39,7 +40,8 @@ import org.gradle.process.CommandLineArgumentProvider; /** - * This plugin reuses the {@code Add-Exports} manifest entry to propagate and collect required exports + * This plugin reuses the {@code Add-Exports} manifest entry defined in + * JEP-261 to propagate and collect required exports * from transitive dependencies, and applies them to compilation (for annotation processors) and * execution (tests, javaExec, etc) for runtime dependencies. */ @@ -133,26 +135,32 @@ public Iterable asArguments() { project.getTasks().withType(Jar.class, new Action() { @Override public void execute(Jar jar) { - jar.manifest(new Action() { + jar.doFirst(new Action() { @Override - public void execute(Manifest manifest) { - // Only locally defined exports are applied to jars - Set exports = extension.exports().get(); - if (!exports.isEmpty()) { - project.getLogger() - .debug( - "BaselineModuleJvmArgs adding manifest attributes to {} in {}: {}", - jar.getName(), - project, - exports); - manifest.attributes(ImmutableMap.of(ADD_EXPORTS_ATTRIBUTE, String.join(" ", exports))); - } else { - project.getLogger() - .debug( - "BaselineModuleJvmArgs not adding manifest attributes to {} in {}", - jar.getName(), - project); - } + public void execute(Task task) { + jar.manifest(new Action() { + @Override + public void execute(Manifest manifest) { + // Only locally defined exports are applied to jars + Set exports = extension.exports().get(); + if (!exports.isEmpty()) { + project.getLogger() + .debug( + "BaselineModuleJvmArgs adding manifest attributes to {} in {}: {}", + jar.getName(), + project, + exports); + manifest.attributes( + ImmutableMap.of(ADD_EXPORTS_ATTRIBUTE, String.join(" ", exports))); + } else { + project.getLogger() + .debug( + "BaselineModuleJvmArgs not adding manifest attributes to {} in {}", + jar.getName(), + project); + } + } + }); } }); } diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy new file mode 100644 index 000000000..b24fd0d4b --- /dev/null +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy @@ -0,0 +1,111 @@ +/* + * (c) Copyright 2021 Palantir Technologies 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.palantir.baseline + +import com.google.common.collect.MoreCollectors +import nebula.test.IntegrationSpec +import nebula.test.functional.ExecutionResult + +import java.util.jar.JarFile + +class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { + + def standardBuildFile = ''' + plugins { + id 'java-library' + id 'application' + } + + apply plugin: 'com.palantir.baseline-java-versions' + apply plugin: 'com.palantir.baseline-module-jvm-args' + + javaVersions { + libraryTarget = 11 + runtime = 17 + } + + repositories { + mavenCentral() + } + '''.stripIndent(true) + + def setup() { + setFork(true) + buildFile << standardBuildFile + } + + def 'Runs with locally defined exports'() { + when: + buildFile << ''' + application { + mainClass = 'com.Example' + } + + moduleJvmArgs { + exports().add('java.management/sun.management') + } + '''.stripIndent(true) + writeJavaSourceFile(''' + package com; + public class Example { + public static void main(String[] args) { + System.out.println(String.join( + " ", + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments())); + } + } + '''.stripIndent(true)) + + then: + ExecutionResult result = runTasksSuccessfully('run') + // Gradle appears to normalize args, joining '--add-exports java.management/sun.management=ALL-UNNAMED' + // with an equals. + result.standardOutput.contains('--add-exports=java.management/sun.management=ALL-UNNAMED') + } + + def 'Adds locally defined exports to the jar manifest'() { + when: + buildFile << ''' + application { + mainClass = 'com.Example' + } + + moduleJvmArgs { + exports().add('java.management/sun.management') + } + '''.stripIndent(true) + writeJavaSourceFile(''' + package com; + public class Example { + public static void main(String[] args) { + System.out.println(String.join( + " ", + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments())); + } + } + '''.stripIndent(true)) + + then: + ExecutionResult result = runTasksSuccessfully('jar') + JarFile jarFile = Arrays.stream(directory("build/libs").listFiles()) + .filter(file -> file.name.endsWith(".jar")) + .map(JarFile::new) + .collect(MoreCollectors.onlyElement()) + String manifestValue = jarFile.getManifest().getMainAttributes().getValue('Add-Exports') + manifestValue == 'java.management/sun.management' + } +} From d05fb1def9654aa77235a1c0921baa93e8f116fb Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Tue, 2 Nov 2021 13:13:29 -0400 Subject: [PATCH 13/19] line len --- .../palantir/baseline/plugins/BaselineModuleJvmArgs.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 38bb3bb44..0e98ca9ce 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -146,7 +146,8 @@ public void execute(Manifest manifest) { if (!exports.isEmpty()) { project.getLogger() .debug( - "BaselineModuleJvmArgs adding manifest attributes to {} in {}: {}", + "BaselineModuleJvmArgs adding " + + "manifest attributes to {} in {}: {}", jar.getName(), project, exports); @@ -155,7 +156,8 @@ public void execute(Manifest manifest) { } else { project.getLogger() .debug( - "BaselineModuleJvmArgs not adding manifest attributes to {} in {}", + "BaselineModuleJvmArgs not adding " + + "manifest attributes to {} in {}", jar.getName(), project); } From b15b5bbe3a150146c545be8e71934c9db2d040ab Mon Sep 17 00:00:00 2001 From: svc-changelog Date: Tue, 2 Nov 2021 17:13:47 +0000 Subject: [PATCH 14/19] Add generated changelog entries --- changelog/@unreleased/pr-1944.v2.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/@unreleased/pr-1944.v2.yml diff --git a/changelog/@unreleased/pr-1944.v2.yml b/changelog/@unreleased/pr-1944.v2.yml new file mode 100644 index 000000000..25dcf5f5e --- /dev/null +++ b/changelog/@unreleased/pr-1944.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Plumb Add-Exports through to execution and annotation processing sites + links: + - https://github.com/palantir/gradle-baseline/pull/1944 From d9a7737ad5f9e862cdd673252955421e4b747cdc Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Tue, 2 Nov 2021 13:53:29 -0400 Subject: [PATCH 15/19] more tests --- ...aselineModuleJvmArgsIntegrationTest.groovy | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy index b24fd0d4b..502d57d18 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy @@ -20,7 +20,12 @@ import com.google.common.collect.MoreCollectors import nebula.test.IntegrationSpec import nebula.test.functional.ExecutionResult +import java.nio.charset.StandardCharsets +import java.util.jar.Attributes +import java.util.jar.JarEntry import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import java.util.jar.Manifest class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { @@ -100,7 +105,7 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { '''.stripIndent(true)) then: - ExecutionResult result = runTasksSuccessfully('jar') + runTasksSuccessfully('jar') JarFile jarFile = Arrays.stream(directory("build/libs").listFiles()) .filter(file -> file.name.endsWith(".jar")) .map(JarFile::new) @@ -108,4 +113,39 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { String manifestValue = jarFile.getManifest().getMainAttributes().getValue('Add-Exports') manifestValue == 'java.management/sun.management' } + + def 'Executes with externally defined exports'() { + when: + Manifest manifest = new Manifest() + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") + manifest.getMainAttributes().putValue('Add-Exports', 'java.management/sun.management') + File testJar = new File(getProjectDir(),"test.jar"); + testJar.withOutputStream { fos -> + new JarOutputStream(fos, manifest).close() + } + buildFile << ''' + application { + mainClass = 'com.Example' + } + dependencies { + implementation files('test.jar') + } + '''.stripIndent(true) + writeJavaSourceFile(''' + package com; + public class Example { + public static void main(String[] args) { + System.out.println(String.join( + " ", + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments())); + } + } + '''.stripIndent(true)) + + then: + ExecutionResult result = runTasksSuccessfully('run') + // Gradle appears to normalize args, joining '--add-exports java.management/sun.management=ALL-UNNAMED' + // with an equals. + result.standardOutput.contains('--add-exports=java.management/sun.management=ALL-UNNAMED') + } } From 1a052650da4af39680a7dd3f790a5b291086158e Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Tue, 2 Nov 2021 13:56:14 -0400 Subject: [PATCH 16/19] even more tests? --- ...aselineModuleJvmArgsIntegrationTest.groovy | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy index 502d57d18..0806f4dd5 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy @@ -20,9 +20,7 @@ import com.google.common.collect.MoreCollectors import nebula.test.IntegrationSpec import nebula.test.functional.ExecutionResult -import java.nio.charset.StandardCharsets import java.util.jar.Attributes -import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream import java.util.jar.Manifest @@ -148,4 +146,42 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { // with an equals. result.standardOutput.contains('--add-exports=java.management/sun.management=ALL-UNNAMED') } + + def 'Does not add externally defined exports to the jar manifest'() { + when: + Manifest manifest = new Manifest() + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") + manifest.getMainAttributes().putValue('Add-Exports', 'java.management/sun.management') + File testJar = new File(getProjectDir(),"test.jar"); + testJar.withOutputStream { fos -> + new JarOutputStream(fos, manifest).close() + } + buildFile << ''' + application { + mainClass = 'com.Example' + } + dependencies { + implementation files('test.jar') + } + '''.stripIndent(true) + writeJavaSourceFile(''' + package com; + public class Example { + public static void main(String[] args) { + System.out.println(String.join( + " ", + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments())); + } + } + '''.stripIndent(true)) + + then: + runTasksSuccessfully('jar') + JarFile jarFile = Arrays.stream(directory("build/libs").listFiles()) + .filter(file -> file.name.endsWith(".jar")) + .map(JarFile::new) + .collect(MoreCollectors.onlyElement()) + String manifestValue = jarFile.getManifest().getMainAttributes().getValue('Add-Exports') + manifestValue == null + } } From 760b3191c1b0658498b31d222735da444f967b42 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Wed, 3 Nov 2021 10:29:33 -0400 Subject: [PATCH 17/19] Close jarfile --- .../plugins/BaselineModuleJvmArgs.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 0e98ca9ce..412e695bf 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -184,20 +184,21 @@ private static ImmutableList collectClasspathExports( classpath.getFiles().stream().flatMap(file -> { try { if (file.getName().endsWith(".jar") && file.isFile()) { - JarFile jar = new JarFile(file); - String value = jar.getManifest() - .getMainAttributes() - .getValue(ADD_EXPORTS_ATTRIBUTE); - if (Strings.isNullOrEmpty(value)) { - return Stream.empty(); + try (JarFile jar = new JarFile(file)) { + String value = jar.getManifest() + .getMainAttributes() + .getValue(ADD_EXPORTS_ATTRIBUTE); + if (Strings.isNullOrEmpty(value)) { + return Stream.empty(); + } + project.getLogger() + .debug( + "Found manifest entry {}: {} in jar {}", + ADD_EXPORTS_ATTRIBUTE, + value, + file); + return EXPORT_SPLITTER.splitToStream(value); } - project.getLogger() - .debug( - "Found manifest entry {}: {} in jar {}", - ADD_EXPORTS_ATTRIBUTE, - value, - file); - return EXPORT_SPLITTER.splitToStream(value); } return Stream.empty(); } catch (IOException e) { From 953ea21ebfd89f9f44a8af18075281a867640f4e Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Wed, 3 Nov 2021 10:58:18 -0400 Subject: [PATCH 18/19] renames + validation --- .../extensions/BaselineExportsExtension.java | 40 ---------- .../BaselineModuleJvmArgsExtension.java | 73 +++++++++++++++++++ .../plugins/BaselineModuleJvmArgs.java | 14 ++-- ...aselineModuleJvmArgsIntegrationTest.groovy | 21 +++++- 4 files changed, 99 insertions(+), 49 deletions(-) delete mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java create mode 100644 gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java deleted file mode 100644 index 37cd31286..000000000 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineExportsExtension.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * (c) Copyright 2019 Palantir Technologies 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.palantir.baseline.extensions; - -import javax.inject.Inject; -import org.gradle.api.Project; -import org.gradle.api.provider.SetProperty; -import org.gradle.jvm.toolchain.JavaLanguageVersion; - -/** - * Extension to configure {@code --add-exports [VALUE]=ALL-UNNAMED} for the current module. - */ -public class BaselineExportsExtension { - - private final SetProperty exports; - - @Inject - public BaselineExportsExtension(Project project) { - exports = project.getObjects().setProperty(String.class); - } - - /** Target {@link JavaLanguageVersion} for compilation. */ - public final SetProperty exports() { - return exports; - } -} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java new file mode 100644 index 000000000..61d59e104 --- /dev/null +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java @@ -0,0 +1,73 @@ +/* + * (c) Copyright 2019 Palantir Technologies 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.palantir.baseline.extensions; + +import com.google.common.collect.ImmutableSet; +import javax.inject.Inject; +import org.gradle.api.Project; +import org.gradle.api.provider.SetProperty; + +/** + * Extension to configure {@code --add-exports [VALUE]=ALL-UNNAMED} for the current module. + */ +public class BaselineModuleJvmArgsExtension { + + private final SetProperty exports; + + @Inject + public BaselineModuleJvmArgsExtension(Project project) { + exports = project.getObjects().setProperty(String.class); + } + + /** + * Property describing all export values for this module. + * Exports take the form {@code MODULE_NAME/PACKAGE_NAME}, so to represent + * {@code --add-exports java.management/sun.management=ALL-UNNAMED} one would add the value + * {@code java.management/sun.management}. + */ + public final SetProperty getExports() { + return exports; + } + + public final void setExports(Iterable input) { + ImmutableSet immutableDeduplicatedCopy = ImmutableSet.copyOf(input); + for (String export : immutableDeduplicatedCopy) { + validateExport(export); + } + exports.set(immutableDeduplicatedCopy); + } + + private static void validateExport(String export) { + if (export.contains("=")) { + throw new IllegalArgumentException(String.format( + "Export '%s' must not contain an '=', e.g. 'java.management/sun.management'. " + + "Each export implies a '=ALL-UNNAMED' suffix", + export)); + } + if (export.contains(" ")) { + throw new IllegalArgumentException(String.format("Export '%s' must not contain whitespace", export)); + } + int firstSlash = export.indexOf('/'); + int lastSlash = export.lastIndexOf('/'); + if (firstSlash != lastSlash || firstSlash < 0) { + throw new IllegalArgumentException(String.format( + "Export '%s' must contain both a module name and package " + + "name separated by a single slash, e.g. 'java.management/sun.management'", + export)); + } + } +} diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 412e695bf..9500ec8b7 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -20,7 +20,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.palantir.baseline.extensions.BaselineExportsExtension; +import com.palantir.baseline.extensions.BaselineModuleJvmArgsExtension; import java.io.IOException; import java.util.Set; import java.util.jar.JarFile; @@ -56,8 +56,8 @@ public final class BaselineModuleJvmArgs implements Plugin { @Override public void apply(Project project) { project.getPluginManager().withPlugin("java", unused -> { - BaselineExportsExtension extension = - project.getExtensions().create(EXTENSION_NAME, BaselineExportsExtension.class, project); + BaselineModuleJvmArgsExtension extension = + project.getExtensions().create(EXTENSION_NAME, BaselineModuleJvmArgsExtension.class, project); // javac isn't provided `--add-exports` args for the time being due to // https://github.com/gradle/gradle/issues/18824 @@ -142,7 +142,7 @@ public void execute(Task task) { @Override public void execute(Manifest manifest) { // Only locally defined exports are applied to jars - Set exports = extension.exports().get(); + Set exports = extension.getExports().get(); if (!exports.isEmpty()) { project.getLogger() .debug( @@ -171,7 +171,7 @@ public void execute(Manifest manifest) { } private static ImmutableList collectAnnotationProcessorExports( - Project project, BaselineExportsExtension extension, SourceSet sourceSet) { + Project project, BaselineModuleJvmArgsExtension extension, SourceSet sourceSet) { return collectClasspathExports( project, extension, @@ -179,7 +179,7 @@ private static ImmutableList collectAnnotationProcessorExports( } private static ImmutableList collectClasspathExports( - Project project, BaselineExportsExtension extension, FileCollection classpath) { + Project project, BaselineModuleJvmArgsExtension extension, FileCollection classpath) { return Stream.concat( classpath.getFiles().stream().flatMap(file -> { try { @@ -206,7 +206,7 @@ private static ImmutableList collectClasspathExports( return Stream.empty(); } }), - extension.exports().get().stream()) + extension.getExports().get().stream()) .distinct() .sorted() .flatMap(modulePackagePair -> Stream.of("--add-exports", modulePackagePair + "=ALL-UNNAMED")) diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy index 0806f4dd5..edc1a8921 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineModuleJvmArgsIntegrationTest.groovy @@ -59,7 +59,7 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { } moduleJvmArgs { - exports().add('java.management/sun.management') + exports = ['java.management/sun.management'] } '''.stripIndent(true) writeJavaSourceFile(''' @@ -88,7 +88,7 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { } moduleJvmArgs { - exports().add('java.management/sun.management') + exports = ['java.management/sun.management'] } '''.stripIndent(true) writeJavaSourceFile(''' @@ -184,4 +184,21 @@ class BaselineModuleJvmArgsIntegrationTest extends IntegrationSpec { String manifestValue = jarFile.getManifest().getMainAttributes().getValue('Add-Exports') manifestValue == null } + + def 'Validates exports'() { + when: + buildFile << ''' + application { + mainClass = 'com.Example' + } + + moduleJvmArgs { + exports = ['java.management'] + } + '''.stripIndent(true) + + then: + ExecutionResult result = runTasksWithFailure('jar') + result.standardError.contains('separated by a single slash') + } } From 990cdc65836ab07b13740aa2748e9c574936009a Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Wed, 3 Nov 2021 11:28:52 -0400 Subject: [PATCH 19/19] varargs --- .../baseline/extensions/BaselineModuleJvmArgsExtension.java | 4 ++-- .../com/palantir/baseline/plugins/BaselineModuleJvmArgs.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java index 61d59e104..cac1b64f3 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineModuleJvmArgsExtension.java @@ -39,11 +39,11 @@ public BaselineModuleJvmArgsExtension(Project project) { * {@code --add-exports java.management/sun.management=ALL-UNNAMED} one would add the value * {@code java.management/sun.management}. */ - public final SetProperty getExports() { + public final SetProperty exports() { return exports; } - public final void setExports(Iterable input) { + public final void setExports(String... input) { ImmutableSet immutableDeduplicatedCopy = ImmutableSet.copyOf(input); for (String export : immutableDeduplicatedCopy) { validateExport(export); diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java index 9500ec8b7..36ae5732c 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineModuleJvmArgs.java @@ -142,7 +142,7 @@ public void execute(Task task) { @Override public void execute(Manifest manifest) { // Only locally defined exports are applied to jars - Set exports = extension.getExports().get(); + Set exports = extension.exports().get(); if (!exports.isEmpty()) { project.getLogger() .debug( @@ -206,7 +206,7 @@ private static ImmutableList collectClasspathExports( return Stream.empty(); } }), - extension.getExports().get().stream()) + extension.exports().get().stream()) .distinct() .sorted() .flatMap(modulePackagePair -> Stream.of("--add-exports", modulePackagePair + "=ALL-UNNAMED"))