diff --git a/manual/Tasks/javac.html b/manual/Tasks/javac.html index ce542463d..cfddb5821 100644 --- a/manual/Tasks/javac.html +++ b/manual/Tasks/javac.html @@ -135,7 +135,7 @@ invoking the compiler.

srcdir Location of the java files. (See the note below.) - Yes, unless nested <src> elements are present. + Yes, unless nested <src> elements or modulesourcepath attribute or elements are present. destdir @@ -459,6 +459,51 @@ invoking the compiler.

No - default is "true" + + modulepath + + Specify where to find application modules. A list of directories of modules, module files or exploded modules. + since Ant 1.9.7 + + No + + + modulepathref + + The modulepath to use, given as reference to a PATH defined elsewhere. + since Ant 1.9.7 + No + + + modulesourcepath + + Specify where to find input source files for multiple module compilation. + since Ant 1.9.7 + + Yes, unless srcdir attribute or nested <src> elements are present + + + modulesourcepathref + + The modulesourcepath to use, given as reference to a PATH defined elsewhere. + since Ant 1.9.7 + No + + + upgrademodulepath + + Specify the location of modules that replace upgradeable modules in the runtime image. + since Ant 1.9.7 + + No + + + upgrademodulepathref + + The upgrademodulepath to use, given as reference to a PATH defined elsewhere. + since Ant 1.9.7 + No +

Parameters specified as nested elements

@@ -468,17 +513,23 @@ supports most attributes of <fileset> <include>, <exclude> and <patternset> elements.

srcdir, classpath, sourcepath, -bootclasspath and extdirs

+bootclasspath, modulepath, modulesourcepath, +upgrademodulepath and extdirs

<javac>'s srcdir, classpath, -sourcepath, bootclasspath, and -extdirs attributes are +sourcepath, bootclasspath, +extdirs, modulepath, modulesourcepath, +and upgrademodulepath attributes are path-like structures and can also be set via nested <src> (note the different name!), <classpath>, <sourcepath>, -<bootclasspath> and -<extdirs> elements, respectively.

+<bootclasspath>, +<extdirs>, +<modulepath>, +<modulesourcepath> and +<upgrademodulepath> +elements, respectively.

compilerarg

@@ -710,6 +761,31 @@ the <compilerarg> element:

in which case your compiler adapter can support attributes and nested elements of its own.

+
  <javac srcdir="${src}"
+         destdir="${build}"
+         includeantruntime="false"
+         modulepath="modules"
+         source="9"
+  />
+

compiles all .java files in a single module under the ${src} directory, + and stores the .class files in the ${build} directory. The compilation uses + application modules located in modules folder.The source level is 9 to enable modules.

+ +
  <javac modulesourcepath="${src}/*/{gen,lin{32,64}}/classes"
+         destdir="${build}"
+         includeantruntime="false"
+         modulepath="modules"
+         source="9"
+  />
+

compiles all .java files in gen/classes, lin32/classes and + lin64/classes in all source modules under the ${src} directory. + Generates module directories in the ${build} directory. Each generated module directory under + the ${build} directory contains .class files from corresponding source module. + The * is a token representing the name of any of the modules in the compilation module set. + The { ... , ... } express alternates for expansion. + The compilation uses application modules located in modules folder.The source level is + 9 to enable modules.

+

Jikes Notes

You need Jikes 1.15 or later.

diff --git a/src/main/org/apache/tools/ant/taskdefs/Javac.java b/src/main/org/apache/tools/ant/taskdefs/Javac.java index 3d77f7c40..e5c96d630 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Javac.java +++ b/src/main/org/apache/tools/ant/taskdefs/Javac.java @@ -19,12 +19,17 @@ package org.apache.tools.ant.taskdefs; import java.io.File; +import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeMap; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; @@ -93,12 +98,20 @@ public class Javac extends MatchingTask { private static final String CLASSIC = "classic"; private static final String EXTJAVAC = "extJavac"; + private static final char GROUP_START_MARK = '{'; //modulesourcepath group start character + private static final char GROUP_END_MARK = '}'; //modulesourcepath group end character + private static final char GROUP_SEP_MARK = ','; //modulesourcepath group element separator character + private static final String MODULE_MARKER = "*"; //modulesourcepath module name marker + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); private Path src; private File destDir; private Path compileClasspath; + private Path modulepath; + private Path upgrademodulepath; private Path compileSourcepath; + private Path moduleSourcepath; private String encoding; private boolean debug = false; private boolean optimize = false; @@ -310,6 +323,49 @@ public class Javac extends MatchingTask { createSourcepath().setRefid(r); } + /** + * Set the modulesourcepath to be used for this compilation. + * @param msp the modulesourcepath + * @since 1.9.7 + */ + public void setModulesourcepath(final Path msp) { + if (moduleSourcepath == null) { + moduleSourcepath = msp; + } else { + moduleSourcepath.append(msp); + } + } + + /** + * Gets the modulesourcepath to be used for this compilation. + * @return the modulesourcepath + * @since 1.9.7 + */ + public Path getModulesourcepath() { + return moduleSourcepath; + } + + /** + * Adds a path to modulesourcepath. + * @return a modulesourcepath to be configured + * @since 1.9.7 + */ + public Path createModulesourcepath() { + if (moduleSourcepath == null) { + moduleSourcepath = new Path(getProject()); + } + return moduleSourcepath.createPath(); + } + + /** + * Adds a reference to a modulesourcepath defined elsewhere. + * @param r a reference to a modulesourcepath + * @since 1.9.7 + */ + public void setModulesourcepathRef(final Reference r) { + createModulesourcepath().setRefid(r); + } + /** * Set the classpath to be used for this compilation. * @@ -350,6 +406,92 @@ public class Javac extends MatchingTask { createClasspath().setRefid(r); } + /** + * Set the modulepath to be used for this compilation. + * @param mp an Ant Path object containing the modulepath. + * @since 1.9.7 + */ + public void setModulepath(final Path mp) { + if (modulepath == null) { + modulepath = mp; + } else { + modulepath.append(mp); + } + } + + /** + * Gets the modulepath to be used for this compilation. + * @return the modulepath + * @since 1.9.7 + */ + public Path getModulepath() { + return modulepath; + } + + /** + * Adds a path to the modulepath. + * @return a modulepath to be configured + * @since 1.9.7 + */ + public Path createModulepath() { + if (modulepath == null) { + modulepath = new Path(getProject()); + } + return modulepath.createPath(); + } + + /** + * Adds a reference to a modulepath defined elsewhere. + * @param r a reference to a modulepath + * @since 1.9.7 + */ + public void setModulepathRef(final Reference r) { + createModulepath().setRefid(r); + } + + /** + * Set the upgrademodulepath to be used for this compilation. + * @param ump an Ant Path object containing the upgrademodulepath. + * @since 1.9.7 + */ + public void setUpgrademodulepath(final Path ump) { + if (upgrademodulepath == null) { + upgrademodulepath = ump; + } else { + upgrademodulepath.append(ump); + } + } + + /** + * Gets the upgrademodulepath to be used for this compilation. + * @return the upgrademodulepath + * @since 1.9.7 + */ + public Path getUpgrademodulepath() { + return upgrademodulepath; + } + + /** + * Adds a path to the upgrademodulepath. + * @return an upgrademodulepath to be configured + * @since 1.9.7 + */ + public Path createUpgrademodulepath() { + if (upgrademodulepath == null) { + upgrademodulepath = new Path(getProject()); + } + return upgrademodulepath.createPath(); + } + + /** + * Adds a reference to the upgrademodulepath defined elsewhere. + * @param r a reference to an upgrademodulepath + * @since 1.9.7 + */ + public void setUpgrademodulepathRef(final Reference r) { + createUpgrademodulepath().setRefid(r); + } + /** * Sets the bootclasspath that will be used to compile the classes * against. @@ -918,19 +1060,45 @@ public class Javac extends MatchingTask { // scan source directories and dest directory to build up // compile lists - final String[] list = src.list(); - for (int i = 0; i < list.length; i++) { - final File srcDir = getProject().resolveFile(list[i]); - if (!srcDir.exists()) { - throw new BuildException("srcdir \"" - + srcDir.getPath() - + "\" does not exist!", getLocation()); - } + // Both src and modulesourcepath can be passed to javac, in this case + // the src becomes a part of the unnamed module. + if (hasPath(src)) { + final String[] list = src.list(); + for (int i = 0; i < list.length; i++) { + final File srcDir = getProject().resolveFile(list[i]); + if (!srcDir.exists()) { + throw new BuildException("srcdir \"" + + srcDir.getPath() + + "\" does not exist!", getLocation()); + } - final DirectoryScanner ds = this.getDirectoryScanner(srcDir); - final String[] files = ds.getIncludedFiles(); + final DirectoryScanner ds = this.getDirectoryScanner(srcDir); + final String[] files = ds.getIncludedFiles(); - scanDir(srcDir, destDir != null ? destDir : srcDir, files); + scanDir(srcDir, destDir != null ? destDir : srcDir, files); + } + } else { + assert hasPath(moduleSourcepath) : "Either srcDir or moduleSourcepath must be given"; + final FileUtils fu = FileUtils.getFileUtils(); + for (String pathElement : moduleSourcepath.list()) { + boolean valid = false; + for (Map.Entry> modules : resolveModuleSourcePathElement(getProject().getBaseDir(), pathElement).entrySet()) { + final String moduleName = modules.getKey(); + for (File srcDir : modules.getValue()) { + if (srcDir.exists()) { + valid = true; + final DirectoryScanner ds = getDirectoryScanner(srcDir); + final String[] files = ds.getIncludedFiles(); + scanDir(srcDir, fu.resolveFile(destDir, moduleName), files); + } + } + } + if (!valid) { + throw new BuildException("modulesourcepath \"" + + pathElement + + "\" does not exist!", getLocation()); + } + } } compile(); @@ -1106,13 +1274,23 @@ public class Javac extends MatchingTask { * @exception BuildException if an error occurs */ protected void checkParameters() throws BuildException { - if (src == null) { - throw new BuildException("srcdir attribute must be set!", - getLocation()); - } - if (src.size() == 0) { - throw new BuildException("srcdir attribute must be set!", + if (hasPath(src)) { + if (hasPath(moduleSourcepath)) { + throw new BuildException("modulesourcepath cannot be combined with srcdir attribute!", + getLocation()); + } + } else if (hasPath(moduleSourcepath)) { + if (hasPath(src) || hasPath(compileSourcepath)) { + throw new BuildException("modulesourcepath cannot be combined with srcdir or sourcepath !", + getLocation()); + } + if (destDir == null) { + throw new BuildException("modulesourcepath requires destdir attribute to be set!", getLocation()); + } + } else { + throw new BuildException("either srcdir or modulesourcepath attribute must be set!", + getLocation()); } if (destDir != null && !destDir.isDirectory()) { @@ -1251,6 +1429,226 @@ public class Javac extends MatchingTask { } } + /** + * Checks if a path exists and is non empty. + * @param path to be checked + * @return true if the path is non null and non empty. + * @since 1.9.7 + */ + private static boolean hasPath(final Path path) { + return path != null && path.size() > 0; + } + + /** + * Resolves the modulesourcepath element possibly containing groups + * and module marks to module names and source roots. + * @param projectDir the project directory + * @param element the modulesourcepath elemement + * @return a mapping from module name to module source roots + * @since 1.9.7 + */ + private static Map> resolveModuleSourcePathElement( + final File projectDir, + final String element) { + final Map> result = new TreeMap>(); + for (CharSequence resolvedElement : expandGroups(element)) { + findModules(projectDir, resolvedElement.toString(), result); + } + return result; + } + + /** + * Expands the groups in the modulesourcepath entry to alternatives. + *

+ * The '*' is a token representing the name of any of the modules in the compilation module set. + * The '{' ... ',' ... '}' express alternates for expansion. + * An example of the modulesourcepath entry is src/*/{linux,share}/classes + *

+ * @param element the entry to expand groups in + * @return the possible alternatives + * @since 1.9.7 + */ + private static Collection expandGroups( + final CharSequence element) { + List result = new ArrayList(); + result.add(new StringBuilder()); + StringBuilder resolved = new StringBuilder(); + for (int i = 0; i < element.length(); i++) { + final char c = element.charAt(i); + switch (c) { + case GROUP_START_MARK: + final int end = getGroupEndIndex(element, i); + if (end < 0) { + throw new BuildException(String.format( + "Unclosed group %s, starting at: %d", + element, + i)); + } + final Collection parts = resolveGroup(element.subSequence(i+1, end)); + switch (parts.size()) { + case 0: + break; + case 1: + resolved.append(parts.iterator().next()); + break; + default: + final List oldRes = result; + result = new ArrayList(oldRes.size() * parts.size()); + for (CharSequence part : parts) { + for (CharSequence prefix : oldRes) { + result.add(new StringBuilder(prefix).append(resolved).append(part)); + } + } + resolved = new StringBuilder(); + } + i = end; + break; + default: + resolved.append(c); + } + } + for (StringBuilder prefix : result) { + prefix.append(resolved); + } + return result; + } + + /** + * Resolves the group to alternatives. + * @param group the group to resolve + * @return the possible alternatives + * @since 1.9.7 + */ + private static Collection resolveGroup(final CharSequence group) { + final Collection result = new ArrayList(); + int start = 0; + int depth = 0; + for (int i = 0; i < group.length(); i++) { + final char c = group.charAt(i); + switch (c) { + case GROUP_START_MARK: + depth++; + break; + case GROUP_END_MARK: + depth--; + break; + case GROUP_SEP_MARK: + if (depth == 0) { + result.addAll(expandGroups(group.subSequence(start, i))); + start = i + 1; + } + break; + } + } + result.addAll(expandGroups(group.subSequence(start, group.length()))); + return result; + } + + /** + * Finds the index of an enclosing brace of the group. + * @param element the element to find the enclosing brace in + * @param start the index of the opening brace. + * @return return the index of an enclosing brace of the group or -1 if not found + * @since 1.9.7 + */ + private static int getGroupEndIndex( + final CharSequence element, + final int start) { + int depth = 0; + for (int i = start; i < element.length(); i++) { + final char c = element.charAt(i); + switch (c) { + case GROUP_START_MARK: + depth++; + break; + case GROUP_END_MARK: + depth--; + if (depth == 0) { + return i; + } + break; + } + } + return -1; + } + + /** + * Finds modules in the expanded modulesourcepath entry. + * @param root the project root + * @param pattern the expanded modulesourcepath entry + * @param collector the map to put modules into + * @since 1.9.7 + */ + private static void findModules( + final File root, + String pattern, + final Map> collector) { + pattern = pattern + .replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + final int startIndex = pattern.indexOf(MODULE_MARKER); + if (startIndex == -1) { + findModules(root, pattern, null, collector); + } else { + if (startIndex == 0) { + throw new BuildException("The modulesourcepath entry must be a folder."); + } + final int endIndex = startIndex + MODULE_MARKER.length(); + if (pattern.charAt(startIndex - 1) != File.separatorChar) { + throw new BuildException("The module mark must be preceded by separator"); + } + if (endIndex < pattern.length() && pattern.charAt(endIndex) != File.separatorChar) { + throw new BuildException("The module mark must be followed by separator"); + } + if (pattern.indexOf(MODULE_MARKER, endIndex) != -1) { + throw new BuildException("The modulesourcepath entry must contain at most one module mark"); + } + final String pathToModule = pattern.substring(0,startIndex); + final String pathInModule = endIndex == pattern.length() ? + null : + pattern.substring(endIndex+1); //+1 the separator + findModules(root, pathToModule, pathInModule, collector); + } + } + + /** + * Finds modules in the expanded modulesourcepath entry. + * @param root the project root + * @param pathToModule the path to modules folder + * @param pathInModule the path in module to source folder + * @param collector the map to put modules into + * @since 1.9.7 + */ + private static void findModules( + final File root, + final String pathToModule, + final String pathInModule, + final Map> collector) { + final FileUtils fu = FileUtils.getFileUtils(); + final File f = fu.resolveFile(root, pathToModule); + if (!f.isDirectory()) { + return; + } + final File[] modules = f.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + for (File module : modules) { + final String moduleName = module.getName(); + final File moduleSourceRoot = pathInModule == null ? + module : + new File(module, pathInModule); + Collection moduleRoots = collector.get(moduleName); + if (moduleRoots == null) { + moduleRoots = new ArrayList(); + collector.put(moduleName, moduleRoots); + } + moduleRoots.add(moduleSourceRoot); + } + } + private static final byte[] PACKAGE_INFO_CLASS_HEADER = { (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe, 0x00, 0x00, 0x00, 0x31, 0x00, 0x07, 0x07, 0x00, 0x05, 0x07, 0x00, 0x06, 0x01, 0x00, 0x0a, diff --git a/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java b/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java index 519163cc7..d1a9cb3cc 100644 --- a/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java +++ b/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java @@ -73,7 +73,10 @@ public abstract class DefaultCompilerAdapter protected Path bootclasspath; protected Path extdirs; protected Path compileClasspath; + protected Path modulepath; + protected Path upgrademodulepath; protected Path compileSourcepath; + protected Path moduleSourcepath; protected Project project; protected Location location; protected boolean includeAntRuntime; @@ -112,13 +115,20 @@ public abstract class DefaultCompilerAdapter extdirs = attributes.getExtdirs(); compileList = attributes.getFileList(); compileClasspath = attributes.getClasspath(); + modulepath = attributes.getModulepath(); + upgrademodulepath = attributes.getUpgrademodulepath(); compileSourcepath = attributes.getSourcepath(); + moduleSourcepath = attributes.getModulesourcepath(); project = attributes.getProject(); location = attributes.getLocation(); includeAntRuntime = attributes.getIncludeantruntime(); includeJavaRuntime = attributes.getIncludejavaruntime(); memoryInitialSize = attributes.getMemoryInitialSize(); memoryMaximumSize = attributes.getMemoryMaximumSize(); + if (moduleSourcepath != null && src == null && compileSourcepath == null) { + //Compatibility to prevent NPE from Jikes, Jvc, Kjc + compileSourcepath = new Path(getProject()); + } } /** @@ -182,6 +192,45 @@ public abstract class DefaultCompilerAdapter return classpath; } + /** + * Builds the modulepath. + * @return the modulepath + * @since 1.9.7 + */ + protected Path getModulepath() { + final Path mp = new Path(getProject()); + if (modulepath != null) { + mp.addExisting(modulepath); + } + return mp; + } + + /** + * Builds the upgrademodulepath. + * @return the upgrademodulepath + * @since 1.9.7 + */ + protected Path getUpgrademodulepath() { + final Path ump = new Path(getProject()); + if (upgrademodulepath != null) { + ump.addExisting(upgrademodulepath); + } + return ump; + } + + /** + * Builds the modulesourcepath for multi module compilation. + * @return the modulesourcepath + * @since 1.9.7 + */ + protected Path getModulesourcepath() { + final Path msp = new Path(getProject()); + if (moduleSourcepath != null) { + msp.add(moduleSourcepath); + } + return msp; + } + /** * Get the command line arguments for the switches. * @param cmd the command line @@ -350,6 +399,21 @@ public abstract class DefaultCompilerAdapter setImplicitSourceSwitch(cmd, t, adjustSourceValue(t)); } } + final Path msp = getModulesourcepath(); + if (msp.size() > 0) { + cmd.createArgument().setValue("-modulesourcepath"); + cmd.createArgument().setPath(msp); + } + final Path mp = getModulepath(); + if (mp.size() > 0) { + cmd.createArgument().setValue("-modulepath"); + cmd.createArgument().setPath(mp); + } + final Path ump = getUpgrademodulepath(); + if (ump.size() > 0) { + cmd.createArgument().setValue("-upgrademodulepath"); + cmd.createArgument().setPath(ump); + } return cmd; } diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/JavacTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/JavacTest.java index 12ceea286..67e31e9e8 100644 --- a/src/tests/junit/org/apache/tools/ant/taskdefs/JavacTest.java +++ b/src/tests/junit/org/apache/tools/ant/taskdefs/JavacTest.java @@ -18,6 +18,7 @@ package org.apache.tools.ant.taskdefs; +import java.io.File; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; @@ -28,10 +29,13 @@ import org.junit.Before; import org.junit.Test; import static org.apache.tools.ant.AntAssert.assertContains; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.Path; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Testcase for . @@ -244,4 +248,85 @@ public class JavacTest { javac.setTarget("1.5"); assertEquals("1.5", javac.getTarget()); } + + @Test + public void testModulesourcepathOrSrcDirRequired() { + try { + javac.checkParameters(); + fail("Build exception should have been thrown - neither srcDir nor modulesourcepath"); + } catch (BuildException e) { + //pass + } + } + + @Test + public void testModulesourcepathAndSrcDirForbidden() { + try { + javac.checkParameters(); + final Path p = new Path(project); + p.setPath("src"); + javac.setSrcdir(p); + final Path mp = new Path(project); + p.setPath("modsrc"); + javac.setModulesourcepath(mp); + fail("Build exception should have been thrown - neither srcDir nor modulesourcepath"); + } catch (BuildException e) { + //pass + } + } + + @Test + public void testModulesourcepathAndSourcepathForbidden() { + try { + javac.checkParameters(); + final Path p = new Path(project); + p.setPath("src"); + javac.setSourcepath(p); + final Path mp = new Path(project); + p.setPath("modsrc"); + javac.setModulesourcepath(mp); + fail("Build exception should have been thrown - neither srcDir nor modulesourcepath"); + } catch (BuildException e) { + //pass + } + } + + @Test + public void testSrcDir() { + final Path p = new Path(project); + p.setPath("src"); + javac.setSrcdir(p); + javac.checkParameters(); + } + + @Test + public void testModulesourcepath() { + final File tmp = new File(System.getProperty("java.io.tmpdir")); //NOI18N + final File destDir = new File(tmp, String.format("%stestMP%d", + getClass().getName(), + System.currentTimeMillis()/1000)); + destDir.mkdirs(); + try { + final Path p = new Path(project); + p.setPath("src"); + javac.setModulesourcepath(p); + javac.setDestdir(destDir); + javac.checkParameters(); + } finally { + destDir.delete(); + } + } + + @Test + public void testModulesourcepathRequiresDestdir() { + try { + final Path p = new Path(project); + p.setPath("src"); + javac.setModulesourcepath(p); + javac.checkParameters(); + fail("Build exception should have been thrown - modulesourcepath requires destdir"); + } catch (BuildException e) { + //pass + } + } } diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapterTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapterTest.java index 750098f99..2972cdb3c 100644 --- a/src/tests/junit/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapterTest.java +++ b/src/tests/junit/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapterTest.java @@ -18,12 +18,23 @@ package org.apache.tools.ant.taskdefs.compilers; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Javac; import org.apache.tools.ant.types.Commandline; import org.junit.Test; import static org.apache.tools.ant.AntAssert.assertContains; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; +import org.junit.Assert; import static org.junit.Assert.assertEquals; public class DefaultCompilerAdapterTest { @@ -174,6 +185,127 @@ public class DefaultCompilerAdapterTest { testSource(null, "javac1.9", "", "9"); } + @Test + public void testSingleModuleCompilation() throws IOException { + final File workDir = createWorkDir("testSMC"); + try { + final File src = new File(workDir, "src"); + src.mkdir(); + final File java1 = createFile(src,"org/apache/ant/tests/J1.java"); + final File java2 = createFile(src,"org/apache/ant/tests/J2.java"); + final File modules = new File(workDir, "modules"); + modules.mkdir(); + final Project prj = new Project(); + prj.setBaseDir(workDir); + final LogCapturingJavac javac = new LogCapturingJavac(); + javac.setProject(prj); + final Commandline[] cmd = new Commandline[1]; + final DefaultCompilerAdapter impl = new DefaultCompilerAdapter() { + @Override + public boolean execute() throws BuildException { + cmd[0] = setupModernJavacCommand(); + return true; + } + }; + final Path srcPath = new Path(prj); + srcPath.setLocation(src); + javac.setSrcdir(srcPath); + javac.createModulepath().setLocation(modules); + javac.setSource("9"); + javac.setTarget("9"); + javac.setIncludeantruntime(false); + javac.add(impl); + javac.execute(); + Assert.assertNotNull(cmd[0]); + final List cmdLine = Arrays.asList(cmd[0].getCommandline()); + //No modulesourcepath + assertEquals(-1, cmdLine.indexOf("-modulesourcepath")); + //The -sourcepath has to be followed by src + int index = cmdLine.indexOf("-sourcepath"); + Assert.assertTrue(index != -1 && index < cmdLine.size() - 1); + assertEquals(src.getAbsolutePath(), cmdLine.get(index + 1)); + //The -modulepath has to be followed by modules + index = cmdLine.indexOf("-modulepath"); + Assert.assertTrue(index != -1 && index < cmdLine.size() - 1); + assertEquals(modules.getAbsolutePath(), cmdLine.get(index + 1)); + //J1.java & J2.java has to be in files list + final Set expected = new TreeSet(); + Collections.addAll(expected, java1.getAbsolutePath(), java2.getAbsolutePath()); + final Set actual = new TreeSet(); + actual.addAll(cmdLine.subList(cmdLine.size() - 2, cmdLine.size())); + assertEquals(expected, actual); + } finally { + delete(workDir); + } + } + + @Test + public void testMultiModuleCompilation() throws IOException { + final File workDir = createWorkDir("testMMC"); + try { + final File src = new File(workDir, "src"); + src.mkdir(); + final File java1 = createFile(src,"main/m1/lin64/classes/org/apache/ant/tests/J1.java"); + final File java2 = createFile(src,"main/m2/lin32/classes/org/apache/ant/tests/J2.java"); + final File java3 = createFile(src,"main/m3/sol/classes/org/apache/ant/tests/J3.java"); + final File modules = new File(workDir, "modules"); + modules.mkdir(); + final File build = new File(workDir, "build"); + build.mkdirs(); + final Project prj = new Project(); + prj.setBaseDir(workDir); + final LogCapturingJavac javac = new LogCapturingJavac(); + javac.setProject(prj); + final Commandline[] cmd = new Commandline[1]; + final DefaultCompilerAdapter impl = new DefaultCompilerAdapter() { + @Override + public boolean execute() throws BuildException { + cmd[0] = setupModernJavacCommand(); + return true; + } + }; + final String moduleSrcPathStr = "src/main/*/{lin{32,64},sol}/classes"; + final Path moduleSourcePath = new Path(prj); + moduleSourcePath.setPath(moduleSrcPathStr); + javac.setModulesourcepath(moduleSourcePath); + javac.createModulepath().setLocation(modules); + javac.setSource("9"); + javac.setTarget("9"); + javac.setDestdir(build); + javac.setIncludeantruntime(false); + javac.add(impl); + javac.execute(); + Assert.assertNotNull(cmd[0]); + final List cmdLine = Arrays.asList(cmd[0].getCommandline()); + //No sourcepath + assertEquals(-1, cmdLine.indexOf("-sourcepath")); + //The -modulesourcepath has to be followed by the pattern + int index = cmdLine.indexOf("-modulesourcepath"); + Assert.assertTrue(index != -1 && index < cmdLine.size() - 1); + String expectedModSrcPath = String.format("%s/%s", + workDir.getAbsolutePath(), + moduleSrcPathStr) + .replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + assertEquals(expectedModSrcPath, cmdLine.get(index + 1)); + //The -modulepath has to be followed by modules + index = cmdLine.indexOf("-modulepath"); + Assert.assertTrue(index != -1 && index < cmdLine.size() - 1); + assertEquals(modules.getAbsolutePath(), cmdLine.get(index + 1)); + //J1.java, J2.java & J3.java has to be in files list + final Set expectedFiles = new TreeSet(); + Collections.addAll(expectedFiles, + java1.getAbsolutePath(), + java2.getAbsolutePath(), + java3.getAbsolutePath()); + final Set actualFiles = new TreeSet(); + actualFiles.addAll(cmdLine.subList(cmdLine.size() - 3, cmdLine.size())); + assertEquals(expectedFiles, actualFiles); + } finally { + delete(workDir); + } + } + private void commonSourceDowngrades(String javaVersion) { testSource("1.3", javaVersion, "If you specify -target 1.1 you now must also specify" @@ -220,4 +352,35 @@ public class DefaultCompilerAdapterTest { assertEquals(expectedSource, args[1]); } } + + private File createWorkDir(String testName) { + final File tmp = new File(System.getProperty("java.io.tmpdir")); //NOI18N + final File destDir = new File(tmp, String.format("%s%s%d", + getClass().getName(), + testName, + System.currentTimeMillis()/1000)); + destDir.mkdirs(); + return destDir; + } + + private File createFile(File folder, String relativePath) throws IOException { + final File file = new File( + folder, + relativePath.replace('/', File.separatorChar).replace('\\', File.separatorChar)); + FileUtils.getFileUtils().createNewFile(file, true); + return file; + } + + private void delete(File f) { + if (f.isDirectory()) { + final File[] clds = f.listFiles(); + if (clds != null) { + for (File cld : clds) { + delete(cld); + } + } + } + f.delete(); + } + }