# HG changeset patch # User jjg # Date 1535752482 25200 # Node ID afbb33428df70d11b10a6abb2c1e2e4ddfad2795 # Parent 36773a4fe3e7f6bafb8344734321e562721cbe6b 8208608: Update --module-source-path to allow explicit source paths for specific modules Reviewed-by: jlahoda diff -r 36773a4fe3e7 -r afbb33428df7 src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.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 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 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 segments = new ArrayList<>(); for (String s: value.split(File.pathSeparator)) { expandBraces(s, segments); diff -r 36773a4fe3e7 -r afbb33428df7 src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java --- 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 { diff -r 36773a4fe3e7 -r afbb33428df7 src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties --- 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=\ diff -r 36773a4fe3e7 -r afbb33428df7 test/langtools/tools/javac/diags/examples.not-yet.txt --- 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 diff -r 36773a4fe3e7 -r afbb33428df7 test/langtools/tools/javac/file/SetLocationForModule.java --- 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"); diff -r 36773a4fe3e7 -r afbb33428df7 test/langtools/tools/javac/modules/ModuleSourcePathTest.java --- 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 { }"); } diff -r 36773a4fe3e7 -r afbb33428df7 test/langtools/tools/javac/modules/PatchModulesTest.java --- 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