8208608: Update --module-source-path to allow explicit source paths for specific modules
authorjjg
Fri, 31 Aug 2018 14:54:42 -0700
changeset 51615 afbb33428df7
parent 51614 36773a4fe3e7
child 51616 b071f4fff1f1
8208608: Update --module-source-path to allow explicit source paths for specific modules Reviewed-by: jlahoda
src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java
src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
test/langtools/tools/javac/diags/examples.not-yet.txt
test/langtools/tools/javac/file/SetLocationForModule.java
test/langtools/tools/javac/modules/ModuleSourcePathTest.java
test/langtools/tools/javac/modules/PatchModulesTest.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Fri Aug 31 16:29:49 2018 -0400
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Fri Aug 31 14:54:42 2018 -0700
@@ -97,11 +97,8 @@
 import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
 
 import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
-import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
-import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
 import static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
 import static com.sun.tools.javac.main.Option.EXTDIRS;
-import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
 
@@ -1533,7 +1530,62 @@
             return true;
         }
 
+        /**
+         * Initializes the module table, based on a string containing the composition
+         * of a series of command-line options.
+         * At most one pattern to initialize a series of modules can be given.
+         * At most one module-specific search path per module can be given.
+         *
+         * @param value a series of values, separated by NUL.
+         */
         void init(String value) {
+            Pattern moduleSpecificForm = Pattern.compile("([\\p{Alnum}$_.]+)=(.*)");
+            List<String> pathsForModules = new ArrayList<>();
+            String modulePattern = null;
+            for (String v : value.split("\0")) {
+                if (moduleSpecificForm.matcher(v).matches()) {
+                    pathsForModules.add(v);
+                } else {
+                    modulePattern = v;
+                }
+            }
+            // set the general module pattern first, if given
+            if (modulePattern != null) {
+                initFromPattern(modulePattern);
+            }
+            pathsForModules.forEach(this::initForModule);
+        }
+
+        /**
+         * Initializes a module-specific override, using {@code setPathsForModule}.
+         *
+         * @param value a string of the form: module-name=search-path
+         */
+        void initForModule(String value) {
+            int eq = value.indexOf('=');
+            String name = value.substring(0, eq);
+            List<Path> paths = new ArrayList<>();
+            for (String v : value.substring(eq + 1).split(File.pathSeparator)) {
+                try {
+                    paths.add(Paths.get(v));
+                } catch (InvalidPathException e) {
+                    throw new IllegalArgumentException("invalid path: " + v, e);
+                }
+            }
+            try {
+                setPathsForModule(name, paths);
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new IllegalArgumentException("cannot set path for module " + name, e);
+            }
+        }
+
+        /**
+         * Initializes the module table based on a custom option syntax.
+         *
+         * @param value the value such as may be given to a --module-source-path option
+         */
+        void initFromPattern(String value) {
             Collection<String> segments = new ArrayList<>();
             for (String s: value.split(File.pathSeparator)) {
                 expandBraces(s, segments);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java	Fri Aug 31 16:29:49 2018 -0400
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java	Fri Aug 31 14:54:42 2018 -0700
@@ -182,7 +182,48 @@
 
     SOURCE_PATH("--source-path -sourcepath", "opt.arg.path", "opt.sourcepath", STANDARD, FILEMANAGER),
 
-    MODULE_SOURCE_PATH("--module-source-path", "opt.arg.mspath", "opt.modulesourcepath", STANDARD, FILEMANAGER),
+    MODULE_SOURCE_PATH("--module-source-path", "opt.arg.mspath", "opt.modulesourcepath", STANDARD, FILEMANAGER) {
+        // The deferred filemanager diagnostics mechanism assumes a single value per option,
+        // but --module-source-path-module can be used multiple times, once in the old form
+        // and once per module in the new form.  Therefore we compose an overall value for the
+        // option containing the individual values given on the command line, separated by NULL.
+        // The standard file manager code knows to split apart the NULL-separated components.
+        @Override
+        public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
+            if (arg.isEmpty()) {
+                throw helper.newInvalidValueException(Errors.NoValueForOption(option));
+            }
+            Pattern moduleSpecificForm = getPattern();
+            String prev = helper.get(MODULE_SOURCE_PATH);
+            if (prev == null) {
+                super.process(helper, option, arg);
+            } else  if (moduleSpecificForm.matcher(arg).matches()) {
+                String argModule = arg.substring(0, arg.indexOf('='));
+                boolean isRepeated = Arrays.stream(prev.split("\0"))
+                        .filter(s -> moduleSpecificForm.matcher(s).matches())
+                        .map(s -> s.substring(0, s.indexOf('=')))
+                        .anyMatch(s -> s.equals(argModule));
+                if (isRepeated) {
+                    throw helper.newInvalidValueException(Errors.RepeatedValueForModuleSourcePath(argModule));
+                } else {
+                    super.process(helper, option, prev + '\0' + arg);
+                }
+            } else {
+                boolean isPresent = Arrays.stream(prev.split("\0"))
+                        .anyMatch(s -> !moduleSpecificForm.matcher(s).matches());
+                if (isPresent) {
+                    throw helper.newInvalidValueException(Errors.MultipleValuesForModuleSourcePath);
+                } else {
+                    super.process(helper, option, prev + '\0' + arg);
+                }
+            }
+        }
+
+        @Override
+        public Pattern getPattern() {
+            return Pattern.compile("([\\p{Alnum}$_.]+)=(.*)");
+        }
+    },
 
     MODULE_PATH("--module-path -p", "opt.arg.path", "opt.modulepath", STANDARD, FILEMANAGER),
 
@@ -194,7 +235,7 @@
         // The deferred filemanager diagnostics mechanism assumes a single value per option,
         // but --patch-module can be used multiple times, once per module. Therefore we compose
         // a value for the option containing the last value specified for each module, and separate
-        // the the module=path pairs by an invalid path character, NULL.
+        // the module=path pairs by an invalid path character, NULL.
         // The standard file manager code knows to split apart the NULL-separated components.
         @Override
         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Fri Aug 31 16:29:49 2018 -0400
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Fri Aug 31 14:54:42 2018 -0700
@@ -3417,7 +3417,14 @@
 
 # 0: string
 compiler.err.repeated.value.for.patch.module=\
-    --patch-module specified more than once for {0}
+    --patch-module specified more than once for module {0}
+
+# 0: string
+compiler.err.repeated.value.for.module.source.path=\
+    --module-source-path specified more than once for module {0}
+
+compiler.err.multiple.values.for.module.source.path=\
+    --module-source-path specified more than once with a pattern argument
 
 # 0: string
 compiler.err.unmatched.quote=\
--- a/test/langtools/tools/javac/diags/examples.not-yet.txt	Fri Aug 31 16:29:49 2018 -0400
+++ b/test/langtools/tools/javac/diags/examples.not-yet.txt	Fri Aug 31 14:54:42 2018 -0700
@@ -174,12 +174,14 @@
 compiler.err.invalid.profile
 compiler.err.invalid.source
 compiler.err.invalid.target
+compiler.err.multiple.values.for.module.source.path
 compiler.err.no.source.files.classes
 compiler.err.no.value.for.option
 compiler.err.option.not.allowed.with.target
 compiler.err.option.too.many
 compiler.err.profile.bootclasspath.conflict
 compiler.err.release.bootclasspath.conflict
+compiler.err.repeated.value.for.module.source.path
 compiler.err.repeated.value.for.patch.module
 compiler.err.req.arg
 compiler.err.sourcepath.modulesourcepath.conflict
--- a/test/langtools/tools/javac/file/SetLocationForModule.java	Fri Aug 31 16:29:49 2018 -0400
+++ b/test/langtools/tools/javac/file/SetLocationForModule.java	Fri Aug 31 14:54:42 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -160,8 +160,7 @@
             Path src1 = Files.createDirectories(base.resolve("src1"));
             Path src1_m = src1.resolve("m");
             tb.writeJavaFiles(src1_m, "module m { }");
-//            fm.setLocationFromPaths(locn, List.of(src1));
-            fm.handleOption("--module-source-path", List.of(src1.toString()).iterator());
+            fm.setLocationFromPaths(locn, List.of(src1));
 
             Location m = fm.getLocationForModule(locn, "m");
             checkEqual("default setting",
@@ -186,8 +185,7 @@
             Path src2 = Files.createDirectories(base.resolve("src2"));
             Path src2_m = src2.resolve("m");
             tb.writeJavaFiles(src2_m, "module m { }");
-//            fm.setLocationFromPaths(locn, List.of(src2));
-            fm.handleOption("--module-source-path", List.of(src2.toString()).iterator());
+            fm.setLocationFromPaths(locn, List.of(src2));
 
             m = fm.getLocationForModule(locn, "m");
 
--- a/test/langtools/tools/javac/modules/ModuleSourcePathTest.java	Fri Aug 31 16:29:49 2018 -0400
+++ b/test/langtools/tools/javac/modules/ModuleSourcePathTest.java	Fri Aug 31 14:54:42 2018 -0700
@@ -519,11 +519,175 @@
         }
     }
 
+    @Test
+    public void moduleSpecificFormsOnly(Path base) throws Exception {
+        // The dirs for the modules do not use a subdirectory named for the module,
+        // meaning they can only be used by the module-specific form of the option.
+        String[] srcDirs = {
+                "src0",         // m0x
+                "src1",         // m1x
+                "src2",         // m2x
+                "src3"          // m3x
+        };
+        generateModules(base, false, srcDirs);
+
+        final Path modules = base.resolve("modules");
+        tb.createDirectories(modules);
+
+        new JavacTask(tb, Task.Mode.CMDLINE)
+                .options("-XDrawDiagnostics",
+                        "--module-source-path", "m0x=" + base.resolve("src0"),
+                        "--module-source-path", "m1x=" + base.resolve("src1"),
+                        "--module-source-path", "m2x=" + base.resolve("src2"),
+                        "--module-source-path", "m3x=" + base.resolve("src3"))
+                .files(findJavaFiles(base.resolve(srcDirs[srcDirs.length - 1])))
+                .outdir(modules)
+                .run()
+                .writeAll();
+
+        for (int i = 0; i < srcDirs.length; i++) {
+            checkFiles(modules.resolve("m" + i + "x/module-info.class"));
+        }
+        checkFiles(modules.resolve("m3x/pkg3/A.class"));
+    }
+
+    @Test
+    public void modulePatternWithEquals(Path base) throws Exception {
+        // The dirs for the modules contain an '=' character, but
+        // the option should still be recognized as the module pattern form.
+        String[] srcDirs = {
+                "src=",         // m0x
+                "src=",         // m1x
+                "src=",         // m2x
+                "src="  // m3x
+        };
+        generateModules(base, true, srcDirs);
+
+        final Path modules = base.resolve("modules");
+        tb.createDirectories(modules);
+
+        new JavacTask(tb, Task.Mode.CMDLINE)
+                .options("-XDrawDiagnostics",
+                        "--module-source-path", base.resolve("src=").toString())
+                .files(findJavaFiles(base.resolve(srcDirs[srcDirs.length - 1])))
+                .outdir(modules)
+                .run()
+                .writeAll();
+
+        for (int i = 0; i < srcDirs.length; i++) {
+            checkFiles(modules.resolve("m" + i + "x/module-info.class"));
+        }
+        checkFiles(modules.resolve("m3x/pkg3/A.class"));
+    }
+
+    @Test
+    public void duplicateModuleSpecificForms(Path base) throws Exception {
+        // The dirs for the modules do not use a subdirectory named for the module,
+        // meaning they can only be used by the module-specific form of the option.
+        String[] srcDirs = {
+                "src0",         // m0x
+                "src1",         // m1x
+                "src2",         // m2x
+                "src3"          // m3x
+        };
+        generateModules(base, false, srcDirs);
+
+        final Path modules = base.resolve("modules");
+        tb.createDirectories(modules);
+
+        // in the following, it should not matter that src1 does not contain
+        // a definition of m0x; it is bad/wrong to specify the option for m0x twice.
+        String log = new JavacTask(tb, Task.Mode.CMDLINE)
+                .options("-XDrawDiagnostics",
+                        "--module-source-path", "m0x=" + base.resolve("src0"),
+                        "--module-source-path", "m0x=" + base.resolve("src1"))
+                .files(findJavaFiles(base.resolve(srcDirs[srcDirs.length - 1])))
+                .outdir(modules)
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutput(Task.OutputKind.DIRECT);
+
+        if (!log.contains("error: --module-source-path specified more than once for module m0x"))
+            throw new Exception("Expected error message not found");
+    }
+
+    @Test
+    public void duplicateModulePatternForms(Path base) throws Exception {
+        // module-specific subdirs are used to allow for use of module-pattern form
+        String[] srcDirs = {
+                "src",  // m0x
+                "src",  // m1x
+                "src",  // m2x
+                "src"   // m3x
+        };
+        generateModules(base, true, srcDirs);
+
+        final Path modules = base.resolve("modules");
+        tb.createDirectories(modules);
+
+        // in the following, it should not matter that the same pattern
+        // is used for both occurrences; it is bad/wrong to give any two patterns
+        String log = new JavacTask(tb, Task.Mode.CMDLINE)
+                .options("-XDrawDiagnostics",
+                        "--module-source-path", base.resolve("src").toString(),
+                        "--module-source-path", base.resolve("src").toString())
+                .files(findJavaFiles(base.resolve(srcDirs[srcDirs.length - 1])))
+                .outdir(modules)
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutput(Task.OutputKind.DIRECT);
+
+        if (!log.contains("error: --module-source-path specified more than once with a pattern argument"))
+            throw new Exception("Expected error message not found");
+    }
+
+    @Test
+    public void mixedOptionForms(Path base) throws Exception {
+        // The dirs for m0x, m2x use a subdirectory named for the module,
+        // meaning they can be used in the module pattern form of the option;
+        // the dirs for m1x, m3x do not use a subdirectory named for the module,
+        // meaning they can only be used by the module-specific form of the option
+        String[] srcDirs = {
+                "src/m0x",      // m0x
+                "src1",         // m1x
+                "src/m2x",      // m2x
+                "src3"          // m3x
+        };
+        generateModules(base, false, srcDirs);
+
+        final Path modules = base.resolve("modules");
+        tb.createDirectories(modules);
+
+        new JavacTask(tb, Task.Mode.CMDLINE)
+                .options("-XDrawDiagnostics",
+                        "--module-source-path", base.resolve("src").toString(), // for m0x, m2x
+                        "--module-source-path", "m1x=" + base.resolve("src1"),
+                        "--module-source-path", "m3x=" + base.resolve("src3"))
+                .files(findJavaFiles(base.resolve(srcDirs[srcDirs.length - 1])))
+                .outdir(modules)
+                .run()
+                .writeAll();
+
+        for (int i = 0; i < srcDirs.length; i++) {
+            checkFiles(modules.resolve("m" + i + "x/module-info.class"));
+        }
+        checkFiles(modules.resolve("m3x/pkg3/A.class"));
+    }
+
     private void generateModules(Path base, String... paths) throws IOException {
+        generateModules(base, true, paths);
+    }
+
+    private void generateModules(Path base, boolean useModuleSubdirs, String... paths)
+                throws IOException {
         for (int i = 0; i < paths.length; i++) {
             String moduleName = "m" + i + "x";
             String dependency = i > 0 ? "requires m" + (i - 1) + "x;" : "";
-            tb.writeJavaFiles(base.resolve(paths[i]).resolve(moduleName),
+            Path dir = base.resolve(paths[i]);
+            if (useModuleSubdirs) {
+                dir = dir.resolve(moduleName);
+            }
+            tb.writeJavaFiles(dir,
                     "module " + moduleName + " { " + dependency + " }",
                     "package pkg" + i + "; class A { }");
         }
--- a/test/langtools/tools/javac/modules/PatchModulesTest.java	Fri Aug 31 16:29:49 2018 -0400
+++ b/test/langtools/tools/javac/modules/PatchModulesTest.java	Fri Aug 31 14:54:42 2018 -0700
@@ -98,7 +98,7 @@
     @Test
     public void testDuplicates(Path base) throws Exception {
         test(asList("java.base=a", "java.compiler=b", "java.base=c"),
-            false, "error: --patch-module specified more than once for java.base");
+            false, "error: --patch-module specified more than once for module java.base");
     }
 
     @Test