Skip to content

Commit 5f5bf92

Browse files
committed
GROOVY-11995: groovyc ant task could support passing through system properties or jvmargs
1 parent f208e2f commit 5f5bf92

3 files changed

Lines changed: 216 additions & 1 deletion

File tree

subprojects/groovy-ant/src/main/java/org/codehaus/groovy/ant/Groovyc.java

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
import org.apache.tools.ant.taskdefs.Execute;
3030
import org.apache.tools.ant.taskdefs.Javac;
3131
import org.apache.tools.ant.taskdefs.MatchingTask;
32+
import org.apache.tools.ant.types.Commandline;
33+
import org.apache.tools.ant.types.Environment;
3234
import org.apache.tools.ant.types.Path;
35+
import org.apache.tools.ant.types.PropertySet;
3336
import org.apache.tools.ant.types.Reference;
3437
import org.apache.tools.ant.util.GlobPatternMapper;
3538
import org.apache.tools.ant.util.SourceFileScanner;
@@ -54,6 +57,7 @@
5457
import java.util.ArrayList;
5558
import java.util.Arrays;
5659
import java.util.Collections;
60+
import java.util.HashSet;
5761
import java.util.LinkedHashSet;
5862
import java.util.List;
5963
import java.util.Map;
@@ -233,6 +237,12 @@ public class Groovyc extends MatchingTask {
233237
private String scriptBaseClass;
234238
private String configscript;
235239

240+
// GROOVY-11995: forked-mode JVM args / system properties
241+
private final Commandline jvmArgs = new Commandline();
242+
private final Environment sysProperties = new Environment();
243+
private final List<PropertySet> sysPropertySets = new ArrayList<>(0);
244+
private boolean inheritAll;
245+
236246
private Set<String> scriptExtensions = new LinkedHashSet<>();
237247

238248
/**
@@ -702,6 +712,56 @@ public void addConfiguredJavac(final Javac javac) {
702712
jointCompilation = true;
703713
}
704714

715+
/**
716+
* Adds a JVM argument to be passed to the forked compiler. Only takes effect
717+
* when {@code fork} is true. Use a nested {@code <jvmarg>} element, e.g.
718+
* {@code <jvmarg value="--add-opens=java.base/java.lang=ALL-UNNAMED"/>}.
719+
*
720+
* @return a new {@code Commandline.Argument} that can be configured by Ant
721+
* @since 6.0.0
722+
*/
723+
public Commandline.Argument createJvmarg() {
724+
return jvmArgs.createArgument();
725+
}
726+
727+
/**
728+
* Adds a system property to be passed to the forked compiler as
729+
* {@code -Dkey=value}. Only takes effect when {@code fork} is true.
730+
*
731+
* @param sysp the system property
732+
* @since 6.0.0
733+
*/
734+
public void addSysproperty(Environment.Variable sysp) {
735+
sysProperties.addVariable(sysp);
736+
}
737+
738+
/**
739+
* Adds a set of system properties (Ant {@code <syspropertyset>}) to be
740+
* passed to the forked compiler. Only takes effect when {@code fork} is true.
741+
*
742+
* @param sysp the property set
743+
* @since 6.0.0
744+
*/
745+
public void addSyspropertyset(PropertySet sysp) {
746+
sysPropertySets.add(sysp);
747+
}
748+
749+
/**
750+
* If true, pass all properties from the parent Ant project to the forked JVM
751+
* as system properties so they can be read via {@code System.getProperty(name)}.
752+
* Only takes effect when {@code fork} is true. Defaults to false.
753+
* <p>
754+
* For fine-grained control, use a nested {@code <sysproperty>} or
755+
* {@code <syspropertyset>} instead. Both may be combined; explicit nested
756+
* entries take precedence on name collision.
757+
*
758+
* @param inheritAll true to inherit all Ant properties into the forked JVM
759+
* @since 6.0.0
760+
*/
761+
public void setInheritAll(boolean inheritAll) {
762+
this.inheritAll = inheritAll;
763+
}
764+
705765
/**
706766
* Enable compiler to report stack trace information if a problem occurs
707767
* during compilation. Default is false.
@@ -1092,7 +1152,8 @@ private List<String> extractJointOptions(Path classpath) {
10921152
return jointOptions;
10931153
}
10941154

1095-
private void doForkCommandLineList(List<String> commandLineList, Path classpath, String separator) {
1155+
// package-private (was private) so tests can verify GROOVY-11995 wiring end-to-end
1156+
void doForkCommandLineList(List<String> commandLineList, Path classpath, String separator) {
10961157
if (forkedExecutable != null && !forkedExecutable.isEmpty()) {
10971158
commandLineList.add(forkedExecutable);
10981159
} else {
@@ -1142,6 +1203,39 @@ private void doForkCommandLineList(List<String> commandLineList, Path classpath,
11421203
tmpExtension = tmpExtension.substring(1);
11431204
commandLineList.add("-Dgroovy.default.scriptExtension=" + tmpExtension);
11441205
}
1206+
// GROOVY-11995: user-supplied JVM args / system properties
1207+
String[] userJvmArgs = jvmArgs.getArguments();
1208+
for (String arg : userJvmArgs) {
1209+
if ("-classpath".equals(arg) || "-cp".equals(arg) || "--class-path".equals(arg)
1210+
|| arg.startsWith("--class-path=")) {
1211+
throw new BuildException("Setting the JVM classpath via <jvmarg> is not supported "
1212+
+ "(would override the forked compiler's bootstrap classpath). Use the "
1213+
+ "<classpath> nested element instead.", getLocation());
1214+
}
1215+
}
1216+
Collections.addAll(commandLineList, userJvmArgs);
1217+
Set<String> sysPropertyNames = new HashSet<>();
1218+
for (Environment.Variable v : sysProperties.getVariablesVector()) {
1219+
sysPropertyNames.add(v.getKey());
1220+
commandLineList.add("-D" + v.getKey() + "=" + v.getValue());
1221+
}
1222+
for (PropertySet ps : sysPropertySets) {
1223+
for (Map.Entry<Object, Object> entry : ps.getProperties().entrySet()) {
1224+
String key = entry.getKey().toString();
1225+
if (sysPropertyNames.add(key)) {
1226+
commandLineList.add("-D" + key + "=" + entry.getValue());
1227+
}
1228+
}
1229+
}
1230+
if (inheritAll) {
1231+
for (Map.Entry<String, Object> entry : getProject().getProperties().entrySet()) {
1232+
Object value = entry.getValue();
1233+
if (value == null) continue;
1234+
if (sysPropertyNames.add(entry.getKey())) {
1235+
commandLineList.add("-D" + entry.getKey() + "=" + value);
1236+
}
1237+
}
1238+
}
11451239

11461240
commandLineList.add(FileSystemCompilerFacade.class.getName());
11471241
commandLineList.add("--classpath");

subprojects/groovy-ant/src/spec/doc/groovyc-ant-task.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ memory setting. (Examples: 83886080, 81920k, or 80m) |No
9292
VM, if using fork mode; ignored otherwise. Defaults to the standard VM
9393
memory setting. (Examples: 83886080, 81920k, or 80m) |No
9494
95+
|inheritAll |If enabled and `fork` is true, all properties from the parent Ant
96+
project are passed to the forked compiler JVM as system properties. For selective
97+
passing, use a nested `<sysproperty>` or `<syspropertyset>` instead. Defaults to false. |No
98+
9599
|failonerror |Indicates whether compilation errors will fail the build;
96100
defaults to true. |No
97101
@@ -170,6 +174,9 @@ the compilation fails. |No
170174
|src |a path structure |Yes (unless srcdir is used) |srcdir
171175
|classpath |a path structure |No |classpath or classpathref
172176
|javac |javac task |No |N/A
177+
|jvmarg |https://ant.apache.org/manual/using.html#arg[command line argument] |No |N/A
178+
|sysproperty |an Ant https://ant.apache.org/manual/Tasks/java.html[`<sysproperty>`] |No |N/A
179+
|syspropertyset |an Ant https://ant.apache.org/manual/Types/propertyset.html[`<propertyset>`] |No |N/A
173180
|==========================================================
174181
175182
*Notes:*
@@ -209,3 +216,28 @@ Joint compilation is enabled by using an embedded `javac` element, as shown in t
209216
-----------------------------------------------------------------------
210217
211218
More details about joint compilation can be found in the <<{tools-groovyc}#section-jointcompilation,joint compilation>> section.
219+
220+
[[groovyc-ant-task-forked-jvm-args]]
221+
=== Passing JVM arguments and system properties to a forked compiler
222+
223+
When `fork="true"`, you can attach arbitrary JVM arguments and system
224+
properties to the spawned compiler JVM via nested `<jvmarg>`,
225+
`<sysproperty>`, and `<syspropertyset>` elements (mirroring Ant's
226+
https://ant.apache.org/manual/Tasks/java.html[`<java>`] task). This is
227+
useful for compile-time configuration consumed by AST transforms, the
228+
parser/lexer, or annotation processors.
229+
230+
[source,xml]
231+
-----------------------------------------------------------------------
232+
<groovyc srcdir="src" destdir="build/classes" fork="true">
233+
<jvmarg value="--add-opens=java.base/java.lang=ALL-UNNAMED"/>
234+
<sysproperty key="my.compile.flag" value="true"/>
235+
<syspropertyset>
236+
<propertyref prefix="myapp."/>
237+
</syspropertyset>
238+
</groovyc>
239+
-----------------------------------------------------------------------
240+
241+
To pass _every_ parent project property at once, set `inheritAll="true"`.
242+
Explicit `<sysproperty>` and `<syspropertyset>` entries take precedence
243+
on name collision.

subprojects/groovy-ant/src/test/groovy/org/codehaus/groovy/ant/GroovycTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import org.apache.tools.ant.BuildException;
2222
import org.apache.tools.ant.Project;
2323
import org.apache.tools.ant.ProjectHelper;
24+
import org.apache.tools.ant.types.Environment;
25+
import org.apache.tools.ant.types.Path;
26+
import org.apache.tools.ant.types.PropertySet;
2427
import org.junit.jupiter.api.BeforeEach;
2528
import org.junit.jupiter.api.Disabled;
2629
import org.junit.jupiter.api.Test;
@@ -380,4 +383,90 @@ void testBadJavacArgument() {
380383
var e = assertThrows(BuildException.class, () -> project.executeTarget("badJavacArgument"));
381384
assertTrue(e.getCause().getMessage().contains("invalid flag: -Xlint:xxx"));
382385
}
386+
387+
// GROOVY-11995
388+
389+
private static Groovyc newForkedGroovyc(Project project) {
390+
Groovyc gc = new Groovyc();
391+
gc.setProject(project);
392+
gc.setFork(true);
393+
return gc;
394+
}
395+
396+
private static java.util.List<String> buildForkCommandLine(Groovyc gc) {
397+
java.util.List<String> cmd = new java.util.ArrayList<>();
398+
gc.doForkCommandLineList(cmd, new Path(gc.getProject()), File.separator);
399+
return cmd;
400+
}
401+
402+
@Test
403+
void testJvmargAddedToForkedCommandLine() {
404+
Groovyc gc = newForkedGroovyc(project);
405+
gc.createJvmarg().setValue("--add-opens=java.base/java.lang=ALL-UNNAMED");
406+
assertTrue(buildForkCommandLine(gc).contains("--add-opens=java.base/java.lang=ALL-UNNAMED"));
407+
}
408+
409+
@Test
410+
void testSyspropertyAddedToForkedCommandLine() {
411+
Groovyc gc = newForkedGroovyc(project);
412+
Environment.Variable v = new Environment.Variable();
413+
v.setKey("my.compile.flag");
414+
v.setValue("true");
415+
gc.addSysproperty(v);
416+
assertTrue(buildForkCommandLine(gc).contains("-Dmy.compile.flag=true"));
417+
}
418+
419+
@Test
420+
void testSyspropertysetAddedToForkedCommandLine() {
421+
project.setProperty("myapp.url", "https://example.com");
422+
project.setProperty("myapp.timeout", "30");
423+
project.setProperty("other.value", "ignored");
424+
Groovyc gc = newForkedGroovyc(project);
425+
PropertySet ps = new PropertySet();
426+
ps.setProject(project);
427+
PropertySet.PropertyRef ref = new PropertySet.PropertyRef();
428+
ref.setPrefix("myapp.");
429+
ps.addPropertyref(ref);
430+
gc.addSyspropertyset(ps);
431+
java.util.List<String> cmd = buildForkCommandLine(gc);
432+
assertTrue(cmd.contains("-Dmyapp.url=https://example.com"));
433+
assertTrue(cmd.contains("-Dmyapp.timeout=30"));
434+
assertTrue(cmd.stream().noneMatch(s -> s.startsWith("-Dother.value=")));
435+
}
436+
437+
@Test
438+
void testInheritAllPassesProjectProperties() {
439+
project.setProperty("alpha", "1");
440+
project.setProperty("beta", "two");
441+
Groovyc gc = newForkedGroovyc(project);
442+
gc.setInheritAll(true);
443+
java.util.List<String> cmd = buildForkCommandLine(gc);
444+
assertTrue(cmd.contains("-Dalpha=1"));
445+
assertTrue(cmd.contains("-Dbeta=two"));
446+
}
447+
448+
@Test
449+
void testExplicitSyspropertyTakesPrecedenceOverInheritAll() {
450+
project.setProperty("shared", "fromProject");
451+
Groovyc gc = newForkedGroovyc(project);
452+
Environment.Variable explicit = new Environment.Variable();
453+
explicit.setKey("shared");
454+
explicit.setValue("fromSysproperty");
455+
gc.addSysproperty(explicit);
456+
gc.setInheritAll(true);
457+
java.util.List<String> cmd = buildForkCommandLine(gc);
458+
long count = cmd.stream().filter(s -> s.startsWith("-Dshared=")).count();
459+
assertEquals(1, count);
460+
assertTrue(cmd.contains("-Dshared=fromSysproperty"));
461+
}
462+
463+
@Test
464+
void testJvmargClasspathRejected() {
465+
for (String forbidden : new String[]{"-classpath", "-cp", "--class-path", "--class-path=foo"}) {
466+
Groovyc gc = newForkedGroovyc(project);
467+
gc.createJvmarg().setValue(forbidden);
468+
BuildException e = assertThrows(BuildException.class, () -> buildForkCommandLine(gc));
469+
assertTrue(e.getMessage().contains("classpath"), "expected classpath error for " + forbidden + ", got: " + e.getMessage());
470+
}
471+
}
383472
}

0 commit comments

Comments
 (0)