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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.taskdefs.Javac;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PropertySet;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.ant.util.SourceFileScanner;
Expand All @@ -54,6 +57,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -233,6 +237,12 @@ public class Groovyc extends MatchingTask {
private String scriptBaseClass;
private String configscript;

// GROOVY-11995: forked-mode JVM args / system properties
private final Commandline jvmArgs = new Commandline();
private final Environment sysProperties = new Environment();
private final List<PropertySet> sysPropertySets = new ArrayList<>(0);
private boolean inheritAll;

private Set<String> scriptExtensions = new LinkedHashSet<>();

/**
Expand Down Expand Up @@ -702,6 +712,56 @@ public void addConfiguredJavac(final Javac javac) {
jointCompilation = true;
}

/**
* Adds a JVM argument to be passed to the forked compiler. Only takes effect
* when {@code fork} is true. Use a nested {@code <jvmarg>} element, e.g.
* {@code <jvmarg value="--add-opens=java.base/java.lang=ALL-UNNAMED"/>}.
*
* @return a new {@code Commandline.Argument} that can be configured by Ant
* @since 6.0.0
*/
public Commandline.Argument createJvmarg() {
return jvmArgs.createArgument();
}

/**
* Adds a system property to be passed to the forked compiler as
* {@code -Dkey=value}. Only takes effect when {@code fork} is true.
*
* @param sysp the system property
* @since 6.0.0
*/
public void addSysproperty(Environment.Variable sysp) {
sysProperties.addVariable(sysp);
}

/**
* Adds a set of system properties (Ant {@code <syspropertyset>}) to be
* passed to the forked compiler. Only takes effect when {@code fork} is true.
*
* @param sysp the property set
* @since 6.0.0
*/
public void addSyspropertyset(PropertySet sysp) {
sysPropertySets.add(sysp);
}

/**
* If true, pass all properties from the parent Ant project to the forked JVM
* as system properties so they can be read via {@code System.getProperty(name)}.
* Only takes effect when {@code fork} is true. Defaults to false.
* <p>
* For fine-grained control, use a nested {@code <sysproperty>} or
* {@code <syspropertyset>} instead. Both may be combined; explicit nested
* entries take precedence on name collision.
*
* @param inheritAll true to inherit all Ant properties into the forked JVM
* @since 6.0.0
*/
public void setInheritAll(boolean inheritAll) {
this.inheritAll = inheritAll;
}

/**
* Enable compiler to report stack trace information if a problem occurs
* during compilation. Default is false.
Expand Down Expand Up @@ -1092,7 +1152,8 @@ private List<String> extractJointOptions(Path classpath) {
return jointOptions;
}

private void doForkCommandLineList(List<String> commandLineList, Path classpath, String separator) {
// package-private (was private) so tests can verify GROOVY-11995 wiring end-to-end
void doForkCommandLineList(List<String> commandLineList, Path classpath, String separator) {
if (forkedExecutable != null && !forkedExecutable.isEmpty()) {
commandLineList.add(forkedExecutable);
} else {
Expand Down Expand Up @@ -1142,6 +1203,39 @@ private void doForkCommandLineList(List<String> commandLineList, Path classpath,
tmpExtension = tmpExtension.substring(1);
commandLineList.add("-Dgroovy.default.scriptExtension=" + tmpExtension);
}
// GROOVY-11995: user-supplied JVM args / system properties
String[] userJvmArgs = jvmArgs.getArguments();
for (String arg : userJvmArgs) {
if ("-classpath".equals(arg) || "-cp".equals(arg) || "--class-path".equals(arg)
|| arg.startsWith("--class-path=")) {
throw new BuildException("Setting the JVM classpath via <jvmarg> is not supported "
+ "(would override the forked compiler's bootstrap classpath). Use the "
+ "<classpath> nested element instead.", getLocation());
}
}
Collections.addAll(commandLineList, userJvmArgs);
Set<String> sysPropertyNames = new HashSet<>();
Comment thread
paulk-asert marked this conversation as resolved.
for (Environment.Variable v : sysProperties.getVariablesVector()) {
sysPropertyNames.add(v.getKey());
commandLineList.add("-D" + v.getKey() + "=" + v.getValue());
}
for (PropertySet ps : sysPropertySets) {
for (Map.Entry<Object, Object> entry : ps.getProperties().entrySet()) {
String key = entry.getKey().toString();
if (sysPropertyNames.add(key)) {
commandLineList.add("-D" + key + "=" + entry.getValue());
}
}
}
if (inheritAll) {
for (Map.Entry<String, Object> entry : getProject().getProperties().entrySet()) {
Object value = entry.getValue();
if (value == null) continue;
if (sysPropertyNames.add(entry.getKey())) {
commandLineList.add("-D" + entry.getKey() + "=" + value);
}
}
}

commandLineList.add(FileSystemCompilerFacade.class.getName());
commandLineList.add("--classpath");
Expand Down
32 changes: 32 additions & 0 deletions subprojects/groovy-ant/src/spec/doc/groovyc-ant-task.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ memory setting. (Examples: 83886080, 81920k, or 80m) |No
VM, if using fork mode; ignored otherwise. Defaults to the standard VM
memory setting. (Examples: 83886080, 81920k, or 80m) |No

|inheritAll |If enabled and `fork` is true, all properties from the parent Ant
project are passed to the forked compiler JVM as system properties. For selective
passing, use a nested `<sysproperty>` or `<syspropertyset>` instead. Defaults to false. |No

|failonerror |Indicates whether compilation errors will fail the build;
defaults to true. |No

Expand Down Expand Up @@ -170,6 +174,9 @@ the compilation fails. |No
|src |a path structure |Yes (unless srcdir is used) |srcdir
|classpath |a path structure |No |classpath or classpathref
|javac |javac task |No |N/A
|jvmarg |https://ant.apache.org/manual/using.html#arg[command line argument] |No |N/A
|sysproperty |an Ant https://ant.apache.org/manual/Tasks/java.html[`<sysproperty>`] |No |N/A
|syspropertyset |an Ant https://ant.apache.org/manual/Types/propertyset.html[`<propertyset>`] |No |N/A
|==========================================================

*Notes:*
Expand Down Expand Up @@ -209,3 +216,28 @@ Joint compilation is enabled by using an embedded `javac` element, as shown in t
-----------------------------------------------------------------------

More details about joint compilation can be found in the <<{tools-groovyc}#section-jointcompilation,joint compilation>> section.

[[groovyc-ant-task-forked-jvm-args]]
=== Passing JVM arguments and system properties to a forked compiler

When `fork="true"`, you can attach arbitrary JVM arguments and system
properties to the spawned compiler JVM via nested `<jvmarg>`,
`<sysproperty>`, and `<syspropertyset>` elements (mirroring Ant's
https://ant.apache.org/manual/Tasks/java.html[`<java>`] task). This is
useful for compile-time configuration consumed by AST transforms, the
parser/lexer, or annotation processors.

[source,xml]
-----------------------------------------------------------------------
<groovyc srcdir="src" destdir="build/classes" fork="true">
<jvmarg value="--add-opens=java.base/java.lang=ALL-UNNAMED"/>
<sysproperty key="my.compile.flag" value="true"/>
<syspropertyset>
<propertyref prefix="myapp."/>
</syspropertyset>
</groovyc>
-----------------------------------------------------------------------

To pass _every_ parent project property at once, set `inheritAll="true"`.
Explicit `<sysproperty>` and `<syspropertyset>` entries take precedence
on name collision.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PropertySet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -380,4 +383,90 @@ void testBadJavacArgument() {
var e = assertThrows(BuildException.class, () -> project.executeTarget("badJavacArgument"));
assertTrue(e.getCause().getMessage().contains("invalid flag: -Xlint:xxx"));
}

// GROOVY-11995

private static Groovyc newForkedGroovyc(Project project) {
Groovyc gc = new Groovyc();
gc.setProject(project);
gc.setFork(true);
return gc;
}

private static java.util.List<String> buildForkCommandLine(Groovyc gc) {
java.util.List<String> cmd = new java.util.ArrayList<>();
gc.doForkCommandLineList(cmd, new Path(gc.getProject()), File.separator);
return cmd;
}

@Test
void testJvmargAddedToForkedCommandLine() {
Groovyc gc = newForkedGroovyc(project);
gc.createJvmarg().setValue("--add-opens=java.base/java.lang=ALL-UNNAMED");
assertTrue(buildForkCommandLine(gc).contains("--add-opens=java.base/java.lang=ALL-UNNAMED"));
}

@Test
void testSyspropertyAddedToForkedCommandLine() {
Groovyc gc = newForkedGroovyc(project);
Environment.Variable v = new Environment.Variable();
v.setKey("my.compile.flag");
v.setValue("true");
gc.addSysproperty(v);
assertTrue(buildForkCommandLine(gc).contains("-Dmy.compile.flag=true"));
}

@Test
void testSyspropertysetAddedToForkedCommandLine() {
project.setProperty("myapp.url", "https://example.com");
project.setProperty("myapp.timeout", "30");
project.setProperty("other.value", "ignored");
Groovyc gc = newForkedGroovyc(project);
PropertySet ps = new PropertySet();
ps.setProject(project);
PropertySet.PropertyRef ref = new PropertySet.PropertyRef();
ref.setPrefix("myapp.");
ps.addPropertyref(ref);
gc.addSyspropertyset(ps);
java.util.List<String> cmd = buildForkCommandLine(gc);
assertTrue(cmd.contains("-Dmyapp.url=https://example.com"));
assertTrue(cmd.contains("-Dmyapp.timeout=30"));
assertTrue(cmd.stream().noneMatch(s -> s.startsWith("-Dother.value=")));
}

@Test
void testInheritAllPassesProjectProperties() {
project.setProperty("alpha", "1");
project.setProperty("beta", "two");
Groovyc gc = newForkedGroovyc(project);
gc.setInheritAll(true);
java.util.List<String> cmd = buildForkCommandLine(gc);
assertTrue(cmd.contains("-Dalpha=1"));
assertTrue(cmd.contains("-Dbeta=two"));
}

@Test
void testExplicitSyspropertyTakesPrecedenceOverInheritAll() {
project.setProperty("shared", "fromProject");
Groovyc gc = newForkedGroovyc(project);
Environment.Variable explicit = new Environment.Variable();
explicit.setKey("shared");
explicit.setValue("fromSysproperty");
gc.addSysproperty(explicit);
gc.setInheritAll(true);
java.util.List<String> cmd = buildForkCommandLine(gc);
long count = cmd.stream().filter(s -> s.startsWith("-Dshared=")).count();
assertEquals(1, count);
assertTrue(cmd.contains("-Dshared=fromSysproperty"));
}

@Test
void testJvmargClasspathRejected() {
for (String forbidden : new String[]{"-classpath", "-cp", "--class-path", "--class-path=foo"}) {
Groovyc gc = newForkedGroovyc(project);
gc.createJvmarg().setValue(forbidden);
BuildException e = assertThrows(BuildException.class, () -> buildForkCommandLine(gc));
assertTrue(e.getMessage().contains("classpath"), "expected classpath error for " + forbidden + ", got: " + e.getMessage());
}
}
}
Loading