changeset 39103:91a64ec5b970

8159749: Update toolbox ModuleBuilder for doc comments Reviewed-by: ksrini
author jjg
date Fri, 17 Jun 2016 17:40:01 -0700
parents 5a820f7e00b9
children 61c5b5f8fd8c
files langtools/test/tools/javac/modules/GraphsTest.java langtools/test/tools/javac/modules/ModulePathTest.java langtools/test/tools/javac/modules/PackageConflictTest.java langtools/test/tools/javac/modules/UpgradeModulePathTest.java langtools/test/tools/javac/modules/UsesTest.java langtools/test/tools/javac/modules/XModuleTest.java langtools/test/tools/lib/toolbox/ModuleBuilder.java
diffstat 7 files changed, 211 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/langtools/test/tools/javac/modules/GraphsTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/GraphsTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -73,6 +73,7 @@
     @Test
     public void diamond(Path base) throws Exception {
 
+        Path modSrc = Files.createDirectories(base.resolve("modSrc"));
         Path modules = Files.createDirectories(base.resolve("modules"));
 
         new ModuleBuilder(tb, "J")
@@ -94,19 +95,19 @@
                 .requiresPublic("J", jarModules)
                 .classes("package openO; public class O { openJ.J j; }")
                 .classes("package closedO; public class O { }")
-                .build(modules);
+                .build(modSrc, modules);
         new ModuleBuilder(tb, "N")
                 .requiresPublic("O", modules, jarModules)
                 .exports("openN")
                 .classes("package openN; public class N { }")
                 .classes("package closedN; public class N { }")
-                .build(modules);
+                .build(modSrc, modules);
         new ModuleBuilder(tb, "L")
                 .requiresPublic("O", modules, jarModules)
                 .exports("openL")
                 .classes("package openL; public class L { }")
                 .classes("package closedL; public class L { }")
-                .build(modules);
+                .build(modSrc, modules);
         ModuleBuilder m = new ModuleBuilder(tb, "M");
         //positive case
         Path positiveSrc = m
@@ -140,14 +141,14 @@
             throw new Exception("Expected output not found");
         }
         //multi module mode
-        m.write(modules);
+        m.write(modSrc);
         List<String> out = new JavacTask(tb)
                 .options("-XDrawDiagnostics",
-                        "-modulesourcepath", modules + "/*/src",
+                        "-modulesourcepath", modSrc.toString(),
                         "-mp", jarModules.toString()
                 )
                 .outdir(Files.createDirectories(base.resolve("negative")))
-                .files(findJavaFiles(modules))
+                .files(findJavaFiles(modSrc))
                 .run(Task.Expect.FAIL)
                 .writeAll()
                 .getOutputLines(Task.OutputKind.DIRECT);
@@ -179,23 +180,23 @@
     */
     @Test
     public void reexportOfQualifiedExport(Path base) throws Exception {
-        Path modules = base.resolve("modules");
+        Path modSrc = base.resolve("modSrc");
         new ModuleBuilder(tb, "M")
                 .requiresPublic("N")
-                .write(modules);
+                .write(modSrc);
         new ModuleBuilder(tb, "N")
                 .exportsTo("pack", "M")
                 .classes("package pack; public class Clazz { }")
-                .write(modules);
+                .write(modSrc);
         new ModuleBuilder(tb, "L")
                 .requires("M")
                 .classes("package p; public class A { A(pack.Clazz cl){} } ")
-                .write(modules);
+                .write(modSrc);
         String log = new JavacTask(tb)
                 .options("-XDrawDiagnostics",
-                        "-modulesourcepath", modules + "/*/src")
+                        "-modulesourcepath", modSrc.toString())
                 .outdir(Files.createDirectories(base.resolve("negative")))
-                .files(findJavaFiles(modules))
+                .files(findJavaFiles(modSrc))
                 .run(Task.Expect.FAIL)
                 .writeAll()
                 .getOutput(Task.OutputKind.DIRECT);
--- a/langtools/test/tools/javac/modules/ModulePathTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/ModulePathTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -303,7 +303,7 @@
 
     @Test
     public void relativePath(Path base) throws Exception {
-        final Path modules = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1").build(modules);
 
         Path src = base.resolve("src");
@@ -319,7 +319,7 @@
 
     @Test
     public void duplicatePaths_1(Path base) throws Exception {
-        final Path modules = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1").build(modules);
 
         Path src = base.resolve("src");
@@ -335,7 +335,7 @@
 
     @Test
     public void duplicatePaths_2(Path base) throws Exception {
-        final Path modules = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1").build(modules);
 
         Path src = base.resolve("src");
@@ -352,24 +352,25 @@
 
     @Test
     public void oneModuleHidesAnother(Path base) throws Exception {
-        final Path module = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .exports("pkg1")
                 .classes("package pkg1; public class E { }")
-                .build(module);
+                .build(modules);
 
-        final Path deepModuleDir = module.resolve("deepModuleDir");
+        Path deepModuleDirSrc = base.resolve("deepModuleDirSrc");
+        Path deepModuleDir = modules.resolve("deepModuleDir");
         new ModuleBuilder(tb, "m1")
                 .exports("pkg2")
                 .classes("package pkg2; public class E { }")
-                .build(deepModuleDir);
+                .build(deepModuleDirSrc, deepModuleDir);
 
         Path src = base.resolve("src");
         tb.writeJavaFiles(src, "module m2 { requires m1; }", " package p; class A { void main() { pkg2.E.class.getName(); } }");
 
         new JavacTask(tb, Task.Mode.CMDLINE)
                 .options("-XDrawDiagnostics",
-                        "-modulepath", deepModuleDir + PATH_SEP + module)
+                        "-modulepath", deepModuleDir + PATH_SEP + modules)
                 .files(findJavaFiles(src))
                 .run()
                 .writeAll();
@@ -377,7 +378,7 @@
 
     @Test
     public void modulesInDifferentContainers(Path base) throws Exception {
-        final Path modules = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .exports("one")
                 .classes("package one; public class A { }")
--- a/langtools/test/tools/javac/modules/PackageConflictTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/PackageConflictTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -125,6 +125,7 @@
 
     @Test
     public void testSimple2(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
         Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "N")
                 .exports("pack")
@@ -133,12 +134,12 @@
         new ModuleBuilder(tb, "M")
                 .requires("N")
                 .classes("package pack; public class B { pack.A f; }")
-                .write(modules);
+                .write(modSrc);
 
         String log = new JavacTask(tb)
                 .options("-XDrawDiagnostics", "-mp", modules.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules.resolve("M")))
+                .files(findJavaFiles(modSrc.resolve("M")))
                 .run(Task.Expect.FAIL)
                 .writeAll()
                 .getOutput(Task.OutputKind.DIRECT);
@@ -149,21 +150,21 @@
 
     @Test
     public void testPrivateConflict(Path base) throws Exception {
-        Path modules = base.resolve("modules");
+        Path modSrc = base.resolve("modSrc");
         new ModuleBuilder(tb, "N")
                 .exports("publ")
                 .classes("package pack; public class A { }")
                 .classes("package publ; public class B { }")
-                .write(modules);
+                .write(modSrc);
         new ModuleBuilder(tb, "M")
                 .requires("N")
                 .classes("package pack; public class C { publ.B b; }")
-                .write(modules);
+                .write(modSrc);
 
         String log = new JavacTask(tb)
-                .options("-XDrawDiagnostics", "-modulesourcepath", modules + "/*/src")
+                .options("-XDrawDiagnostics", "-modulesourcepath", modSrc.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules))
+                .files(findJavaFiles(modSrc))
                 .run(Task.Expect.SUCCESS)
                 .writeAll()
                 .getOutput(Task.OutputKind.DIRECT);
@@ -175,6 +176,7 @@
 
     @Test
     public void testPrivateConflictOnModulePath(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
         Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "N")
                 .exports("publ")
@@ -184,12 +186,12 @@
         new ModuleBuilder(tb, "M")
                 .requires("N")
                 .classes("package pack; public class C { publ.B b; }")
-                .write(modules);
+                .write(modSrc);
 
         String log = new JavacTask(tb)
                 .options("-XDrawDiagnostics", "-mp", modules.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules.resolve("M")))
+                .files(findJavaFiles(modSrc.resolve("M")))
                 .run(Task.Expect.SUCCESS)
                 .writeAll()
                 .getOutput(Task.OutputKind.DIRECT);
@@ -201,6 +203,7 @@
 
     @Test
     public void testRequiresConflictExports(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
         Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "M")
                 .exports("pack")
@@ -214,12 +217,12 @@
                 .requires("M")
                 .requires("N")
                 .classes("package pkg; public class C { pack.A a; pack.B b; }")
-                .write(modules);
+                .write(modSrc);
 
         List<String> log = new JavacTask(tb)
                 .options("-XDrawDiagnostics", "-mp", modules.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules.resolve("K")))
+                .files(findJavaFiles(modSrc.resolve("K")))
                 .run(Task.Expect.FAIL)
                 .writeAll()
                 .getOutputLines(Task.OutputKind.DIRECT);
@@ -232,39 +235,39 @@
     }
 
     @Test
-    public void testQulifiedExportsToDifferentModules(Path base) throws Exception {
-        Path modules = base.resolve("modules");
-        new ModuleBuilder(tb, "U").write(modules);
+    public void testQualifiedExportsToDifferentModules(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
+        new ModuleBuilder(tb, "U").write(modSrc);
         new ModuleBuilder(tb, "M")
                 .exports("pkg to U")
                 .classes("package pkg; public class A { public static boolean flagM; }")
-                .write(modules);
+                .write(modSrc);
         new ModuleBuilder(tb, "N")
                 .exports("pkg to K")
                 .classes("package pkg; public class A { public static boolean flagN; }")
-                .write(modules);
+                .write(modSrc);
         ModuleBuilder moduleK = new ModuleBuilder(tb, "K");
         moduleK.requires("M")
                 .requires("N")
                 .classes("package p; public class DependsOnN { boolean f = pkg.A.flagN; } ")
-                .write(modules);
+                .write(modSrc);
         new JavacTask(tb)
-                .options("-modulesourcepath", modules + "/*/src")
+                .options("-modulesourcepath", modSrc.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules.resolve("K")))
+                .files(findJavaFiles(modSrc.resolve("K")))
                 .run(Task.Expect.SUCCESS)
                 .writeAll();
 
         //negative case
         moduleK.classes("package pkg; public class DuplicatePackage { } ")
                 .classes("package p; public class DependsOnM { boolean f = pkg.A.flagM; } ")
-                .write(modules);
+                .write(modSrc);
 
         List<String> output = new JavacTask(tb)
                 .options("-XDrawDiagnostics",
-                        "-modulesourcepath", modules + "/*/src")
+                        "-modulesourcepath", modSrc.toString())
                 .outdir(Files.createDirectories(base.resolve("classes")))
-                .files(findJavaFiles(modules.resolve("K")))
+                .files(findJavaFiles(modSrc.resolve("K")))
                 .run(Task.Expect.FAIL)
                 .writeAll()
                 .getOutputLines(Task.OutputKind.DIRECT);
--- a/langtools/test/tools/javac/modules/UpgradeModulePathTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/UpgradeModulePathTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -49,25 +49,25 @@
 
     @Test
     public void simpleUsage(Path base) throws Exception {
-        final Path module = base.resolve("modules");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .exports("pkg1")
                 .classes("package pkg1; public class E { }")
-                .build(module);
+                .build(modules);
 
-        final Path upgradeModule = base.resolve("upgradeModule");
+        final Path upgradeModules = base.resolve("upgradeModules");
         new ModuleBuilder(tb, "m1")
                 .exports("pkg2")
                 .classes("package pkg2; public class E { }")
-                .build(upgradeModule);
+                .build(upgradeModules);
 
         Path src = base.resolve("src");
         tb.writeJavaFiles(src, "module m2 { requires m1; }",
                 "package p; class A { void main() { pkg2.E.class.getName(); } }");
 
         new JavacTask(tb, Task.Mode.CMDLINE)
-                .options("-modulepath", module.toString(),
-                        "-upgrademodulepath", upgradeModule.toString())
+                .options("-modulepath", modules.toString(),
+                        "-upgrademodulepath", upgradeModules.toString())
                 .files(findJavaFiles(src))
                 .run()
                 .writeAll();
--- a/langtools/test/tools/javac/modules/UsesTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/UsesTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -163,6 +163,7 @@
 
     @Test
     public void testMultiOnModulePath(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
         Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .exports("p")
@@ -171,18 +172,19 @@
         new ModuleBuilder(tb, "m2")
                 .requires("m1")
                 .uses("p.C")
-                .write(modules);
+                .write(modSrc);
 
         new JavacTask(tb)
                 .options("-mp", modules.toString())
                 .outdir(modules)
-                .files(findJavaFiles(modules.resolve("m2")))
+                .files(findJavaFiles(modSrc.resolve("m2")))
                 .run(Task.Expect.SUCCESS)
                 .writeAll();
     }
 
     @Test
     public void testMultiOnModulePathInner(Path base) throws Exception {
+        Path modSrc = base.resolve("modSrc");
         Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .exports("p")
@@ -191,12 +193,12 @@
         new ModuleBuilder(tb, "m2")
                 .requires("m1")
                 .uses("p.C.Inner")
-                .write(modules);
+                .write(modSrc);
 
         new JavacTask(tb)
                 .options("-mp", modules.toString())
                 .outdir(modules)
-                .files(findJavaFiles(modules.resolve("m2")))
+                .files(findJavaFiles(modSrc.resolve("m2")))
                 .run(Task.Expect.SUCCESS)
                 .writeAll();
     }
--- a/langtools/test/tools/javac/modules/XModuleTest.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/javac/modules/XModuleTest.java	Fri Jun 17 17:40:01 2016 -0700
@@ -237,16 +237,17 @@
 
     @Test
     public void testWithModulePath(Path base) throws Exception {
-        Path module = base.resolve("modules");
+        Path modSrc = base.resolve("modSrc");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .classes("package pkg1; public interface E { }")
-                .build(module);
+                .build(modSrc, modules);
 
         Path src = base.resolve("src");
         tb.writeJavaFiles(src, "package p; interface A extends pkg1.E { }");
 
         new JavacTask(tb, Task.Mode.CMDLINE)
-                .options("-modulepath", module.toString(),
+                .options("-modulepath", modules.toString(),
                         "-Xmodule:m1")
                 .files(findJavaFiles(src))
                 .run()
@@ -255,14 +256,14 @@
         //checks module bounds still exist
         new ModuleBuilder(tb, "m2")
                 .classes("package pkg2; public interface D { }")
-                .build(module);
+                .build(modSrc, modules);
 
         Path src2 = base.resolve("src2");
         tb.writeJavaFiles(src2, "package p; interface A extends pkg2.D { }");
 
         List<String> log = new JavacTask(tb, Task.Mode.CMDLINE)
                 .options("-XDrawDiagnostics",
-                        "-modulepath", module.toString(),
+                        "-modulepath", modules.toString(),
                         "-Xmodule:m1")
                 .files(findJavaFiles(src2))
                 .run(Task.Expect.FAIL)
@@ -278,21 +279,23 @@
 
     @Test
     public void testWithUpgradeModulePath(Path base) throws Exception {
-        Path module = base.resolve("modules");
+        Path modSrc = base.resolve("modSrc");
+        Path modules = base.resolve("modules");
         new ModuleBuilder(tb, "m1")
                 .classes("package pkg1; public interface E { }")
-                .build(module);
+                .build(modSrc, modules);
 
+        Path upgrSrc = base.resolve("upgradeSrc");
         Path upgrade = base.resolve("upgrade");
         new ModuleBuilder(tb, "m1")
                 .classes("package pkg1; public interface D { }")
-                .build(upgrade);
+                .build(upgrSrc, upgrade);
 
         Path src = base.resolve("src");
         tb.writeJavaFiles(src, "package p; interface A extends pkg1.D { }");
 
         new JavacTask(tb, Task.Mode.CMDLINE)
-                .options("-modulepath", module.toString(),
+                .options("-modulepath", modules.toString(),
                         "-upgrademodulepath", upgrade.toString(),
                         "-Xmodule:m1")
                 .files(findJavaFiles(src))
--- a/langtools/test/tools/lib/toolbox/ModuleBuilder.java	Fri Jun 17 17:09:21 2016 -0600
+++ b/langtools/test/tools/lib/toolbox/ModuleBuilder.java	Fri Jun 17 17:40:01 2016 -0700
@@ -27,83 +27,185 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
+/**
+ * Builder for module declarations.
+ */
 public class ModuleBuilder {
 
     private final ToolBox tb;
     private final String name;
-    private String requires = "";
-    private String exports = "";
-    private String uses = "";
-    private String provides = "";
-    private String modulePath = "";
+    private String comment = "";
+    private List<String> requires = new ArrayList<>();
+    private List<String> exports = new ArrayList<>();
+    private List<String> uses = new ArrayList<>();
+    private List<String> provides = new ArrayList<>();
     private List<String> content = new ArrayList<>();
+    private Set<Path> modulePath = new LinkedHashSet<>();
 
+    /**
+     * Creates a builder for a module.
+     * @param tb a Toolbox that can be used to compile the module declaration.
+     * @param name the name of the module to be built
+     */
     public ModuleBuilder(ToolBox tb, String name) {
         this.tb = tb;
         this.name = name;
     }
 
-    public ModuleBuilder requiresPublic(String requires, Path... modulePath) {
-        return requires("public " + requires, modulePath);
-    }
-
-    public ModuleBuilder requires(String requires, Path... modulePath) {
-        this.requires += "    requires " + requires + ";\n";
-        this.modulePath += Arrays.stream(modulePath)
-                .map(Path::toString)
-                .collect(Collectors.joining(File.pathSeparator));
+    /**
+     * Sets the doc comment for the declaration.
+     * @param comment the content of the comment, excluding the initial
+     *  '/**', leading whitespace and asterisks, and the final trailing '&#02a;/'.
+     * @return this builder
+     */
+    public ModuleBuilder comment(String comment) {
+        this.comment = comment;
         return this;
     }
 
-    public ModuleBuilder exportsTo(String pkg, String module) {
-        return exports(pkg + " to " + module);
-    }
-
-    public ModuleBuilder exports(String pkg) {
-        this.exports += "    exports " + pkg + ";\n";
+    /**
+     * Adds a "requires public" directive to the declaration.
+     * @param requires the name of the module that is required
+     * @param modulePath a path in which to locate the modules
+     *    if the declaration is compiled
+     * @return this builder
+     */
+    public ModuleBuilder requiresPublic(String requires, Path... modulePath) {
+        this.requires.add("requires public " + requires + ";");
+        this.modulePath.addAll(Arrays.asList(modulePath));
         return this;
     }
 
-    public ModuleBuilder uses(String uses) {
-        this.uses += "    uses " + uses + ";\n";
+    /**
+     * Adds a "requires" directive to the declaration.
+     * @param requires the name of the module that is required
+     * @param modulePath a path in while to locate the modules
+     *    if the declaration is compiled
+     * @return this builder
+     */
+    public ModuleBuilder requires(String requires, Path... modulePath) {
+        this.requires.add("requires " + requires + ";");
+        this.modulePath.addAll(Arrays.asList(modulePath));
         return this;
     }
 
-    public ModuleBuilder provides(String service, String implementation) {
-        this.provides += "    provides " + service + " with " + implementation + ";\n";
+    /**
+     * Adds a qualified "exports" directive to the declaration.
+     * @param pkg the name of the package to be exported
+     * @param module the name of the module to which it is to be exported
+     * @return this builder
+     */
+    public ModuleBuilder exportsTo(String pkg, String module) {
+        this.exports.add("exports " + pkg + " to " + module + ";");
         return this;
     }
 
+    /**
+     * Adds an unqualified "exports" directive to the declaration.
+     * @param pkg the name of the package to be exported
+     * @param module the name of the module to which it is to be exported
+     * @return this builder
+     */
+    public ModuleBuilder exports(String pkg) {
+        this.exports.add("exports " + pkg + ";");
+        return this;
+    }
+
+    /**
+     * Adds a "uses" directive to the declaration.
+     * @param service the name of the service type
+     * @return this builder
+     */
+    public ModuleBuilder uses(String service) {
+        this.uses.add("uses " + service + ";");
+        return this;
+    }
+
+    /**
+     * Adds a "provides" directive to the declaration.
+     * @param service the name of the service type
+     * @param implementation the name of the implementation type
+     * @return this builder
+     */
+    public ModuleBuilder provides(String service, String implementation) {
+        this.provides.add("provides " + service + " with " + implementation + ";");
+        return this;
+    }
+
+    /**
+     * Adds type definitions to the module.
+     * @param content a series of strings, each representing the content of
+     *  a compilation unit to be included with the module
+     * @return this builder
+     */
     public ModuleBuilder classes(String... content) {
         this.content.addAll(Arrays.asList(content));
         return this;
     }
 
-    public Path write(Path where) throws IOException {
-        Files.createDirectories(where);
+    /**
+     * Writes the module declaration and associated additional compilation
+     * units to a module directory within a given directory.
+     * @param srcDir the directory in which a directory will be created
+     *  to contain the source files for the module
+     * @return the directory containing the source files for the module
+     */
+    public Path write(Path srcDir) throws IOException {
+        Files.createDirectories(srcDir);
         List<String> sources = new ArrayList<>();
-        sources.add("module " + name + "{"
-                + requires
-                + exports
-                + uses
-                + provides
-                + "}");
+        StringBuilder sb = new StringBuilder();
+        if (!comment.isEmpty()) {
+            sb.append("/**\n").append(comment.replace("\n", " *")).append(" */\n");
+        }
+        sb.append("module ").append(name).append(" {");
+        requires.forEach(r -> sb.append("    " + r + "\n"));
+        exports.forEach(e -> sb.append("    " + e + "\n"));
+        uses.forEach(u -> sb.append("    " + u + "\n"));
+        provides.forEach(p -> sb.append("    " + p + "\n"));
+        sb.append("}");
+        sources.add(sb.toString());
         sources.addAll(content);
-        Path moduleSrc = where.resolve(name + "/src");
+        Path moduleSrc = srcDir.resolve(name);
         tb.writeJavaFiles(moduleSrc, sources.toArray(new String[]{}));
         return moduleSrc;
     }
 
-    public void build(Path where) throws IOException {
-        Path moduleSrc = write(where);
+    /**
+     * Writes the source files for the module to an interim directory,
+     * and then compiles them to a given directory.
+     * @param modules the directory in which a directory will be created
+     *    to contain the compiled class files for the module
+     * @throws IOException if an error occurs while compiling the files
+     */
+    public void build(Path modules) throws IOException {
+        build(Paths.get(modules + "Src"), modules);
+    }
+
+    /**
+     * Writes the source files for the module to a specified directory,
+     * and then compiles them to a given directory.
+     * @param srcDir the directory in which a directory will be created
+     *  to contain the source files for the module
+     * @param modules the directory in which a directory will be created
+     *    to contain the compiled class files for the module
+     * @throws IOException if an error occurs while compiling the files
+     */
+    public void build(Path src, Path modules) throws IOException {
+        Path moduleSrc = write(src);
+        String mp = modulePath.stream()
+                .map(Path::toString)
+                .collect(Collectors.joining(File.pathSeparator));
         new JavacTask(tb)
-                .outdir(where.resolve(name))
-                .options("-mp", modulePath)
+                .outdir(Files.createDirectories(modules.resolve(name)))
+                .options("-mp", mp)
                 .files(tb.findJavaFiles(moduleSrc))
                 .run()
                 .writeAll();