8168149: Examine the behavior of jmod command-line options - repeating vs last one wins

Reviewed-by: mchung
This commit is contained in:
Chris Hegarty 2017-01-12 11:41:51 +00:00
parent 4ec1878401
commit 186fec9a8a
2 changed files with 145 additions and 56 deletions

View File

@ -1088,34 +1088,58 @@ public class JmodTask {
} }
} }
static class ClassPathConverter implements ValueConverter<Path> { /**
static final ValueConverter<Path> INSTANCE = new ClassPathConverter(); * An abstract converter that given a string representing a list of paths,
* separated by the File.pathSeparator, returns a List of java.nio.Path's.
* Specific subclasses should do whatever validation is required on the
* individual path elements, if any.
*/
static abstract class AbstractPathConverter implements ValueConverter<List<Path>> {
@Override
public List<Path> convert(String value) {
List<Path> paths = new ArrayList<>();
String[] pathElements = value.split(File.pathSeparator);
for (String pathElement : pathElements) {
paths.add(toPath(pathElement));
}
return paths;
}
@SuppressWarnings("unchecked")
@Override
public Class<List<Path>> valueType() {
return (Class<List<Path>>)(Object)List.class;
}
@Override public String valuePattern() { return "path"; }
abstract Path toPath(String path);
}
static class ClassPathConverter extends AbstractPathConverter {
static final ValueConverter<List<Path>> INSTANCE = new ClassPathConverter();
@Override @Override
public Path convert(String value) { public Path toPath(String value) {
try { try {
Path path = CWD.resolve(value); Path path = CWD.resolve(value);
if (Files.notExists(path)) if (Files.notExists(path))
throw new CommandException("err.path.not.found", path); throw new CommandException("err.path.not.found", path);
if (! (Files.isDirectory(path) || if (!(Files.isDirectory(path) ||
(Files.isRegularFile(path) && path.toString().endsWith(".jar")))) (Files.isRegularFile(path) && path.toString().endsWith(".jar"))))
throw new CommandException("err.invalid.class.path.entry", path); throw new CommandException("err.invalid.class.path.entry", path);
return path; return path;
} catch (InvalidPathException x) { } catch (InvalidPathException x) {
throw new CommandException("err.path.not.valid", value); throw new CommandException("err.path.not.valid", value);
} }
} }
@Override public Class<Path> valueType() { return Path.class; }
@Override public String valuePattern() { return "path"; }
} }
static class DirPathConverter implements ValueConverter<Path> { static class DirPathConverter extends AbstractPathConverter {
static final ValueConverter<Path> INSTANCE = new DirPathConverter(); static final ValueConverter<List<Path>> INSTANCE = new DirPathConverter();
@Override @Override
public Path convert(String value) { public Path toPath(String value) {
try { try {
Path path = CWD.resolve(value); Path path = CWD.resolve(value);
if (Files.notExists(path)) if (Files.notExists(path))
@ -1127,10 +1151,6 @@ public class JmodTask {
throw new CommandException("err.path.not.valid", value); throw new CommandException("err.path.not.valid", value);
} }
} }
@Override public Class<Path> valueType() { return Path.class; }
@Override public String valuePattern() { return "path"; }
} }
static class ExtractDirPathConverter implements ValueConverter<Path> { static class ExtractDirPathConverter implements ValueConverter<Path> {
@ -1142,12 +1162,6 @@ public class JmodTask {
if (Files.exists(path)) { if (Files.exists(path)) {
if (!Files.isDirectory(path)) if (!Files.isDirectory(path))
throw new CommandException("err.cannot.create.dir", path); throw new CommandException("err.cannot.create.dir", path);
} else {
try {
Files.createDirectories(path);
} catch (IOException ioe) {
throw new CommandException("err.cannot.create.dir", path);
}
} }
return path; return path;
} catch (InvalidPathException x) { } catch (InvalidPathException x) {
@ -1316,22 +1330,19 @@ public class JmodTask {
options = new Options(); options = new Options();
parser.formatHelpWith(new JmodHelpFormatter(options)); parser.formatHelpWith(new JmodHelpFormatter(options));
OptionSpec<Path> classPath OptionSpec<List<Path>> classPath
= parser.accepts("class-path", getMessage("main.opt.class-path")) = parser.accepts("class-path", getMessage("main.opt.class-path"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(ClassPathConverter.INSTANCE); .withValuesConvertedBy(ClassPathConverter.INSTANCE);
OptionSpec<Path> cmds OptionSpec<List<Path>> cmds
= parser.accepts("cmds", getMessage("main.opt.cmds")) = parser.accepts("cmds", getMessage("main.opt.cmds"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Path> config OptionSpec<List<Path>> config
= parser.accepts("config", getMessage("main.opt.config")) = parser.accepts("config", getMessage("main.opt.config"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Path> dir OptionSpec<Path> dir
@ -1359,22 +1370,19 @@ public class JmodTask {
OptionSpec<Void> helpExtra OptionSpec<Void> helpExtra
= parser.accepts("help-extra", getMessage("main.opt.help-extra")); = parser.accepts("help-extra", getMessage("main.opt.help-extra"));
OptionSpec<Path> headerFiles OptionSpec<List<Path>> headerFiles
= parser.accepts("header-files", getMessage("main.opt.header-files")) = parser.accepts("header-files", getMessage("main.opt.header-files"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Path> libs OptionSpec<List<Path>> libs
= parser.accepts("libs", getMessage("main.opt.libs")) = parser.accepts("libs", getMessage("main.opt.libs"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Path> legalNotices OptionSpec<List<Path>> legalNotices
= parser.accepts("legal-notices", getMessage("main.opt.legal-notices")) = parser.accepts("legal-notices", getMessage("main.opt.legal-notices"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
@ -1383,17 +1391,15 @@ public class JmodTask {
.withRequiredArg() .withRequiredArg()
.describedAs(getMessage("main.opt.main-class.arg")); .describedAs(getMessage("main.opt.main-class.arg"));
OptionSpec<Path> manPages OptionSpec<List<Path>> manPages
= parser.accepts("man-pages", getMessage("main.opt.man-pages")) = parser.accepts("man-pages", getMessage("main.opt.man-pages"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Path> modulePath OptionSpec<List<Path>> modulePath
= parser.acceptsAll(Set.of("p", "module-path"), = parser.acceptsAll(Set.of("p", "module-path"),
getMessage("main.opt.module-path")) getMessage("main.opt.module-path"))
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(File.pathSeparatorChar)
.withValuesConvertedBy(DirPathConverter.INSTANCE); .withValuesConvertedBy(DirPathConverter.INSTANCE);
OptionSpec<Version> moduleVersion OptionSpec<Version> moduleVersion
@ -1452,48 +1458,48 @@ public class JmodTask {
} }
if (opts.has(classPath)) if (opts.has(classPath))
options.classpath = opts.valuesOf(classPath); options.classpath = getLastElement(opts.valuesOf(classPath));
if (opts.has(cmds)) if (opts.has(cmds))
options.cmds = opts.valuesOf(cmds); options.cmds = getLastElement(opts.valuesOf(cmds));
if (opts.has(config)) if (opts.has(config))
options.configs = opts.valuesOf(config); options.configs = getLastElement(opts.valuesOf(config));
if (opts.has(dir)) if (opts.has(dir))
options.extractDir = opts.valueOf(dir); options.extractDir = getLastElement(opts.valuesOf(dir));
if (opts.has(dryrun)) if (opts.has(dryrun))
options.dryrun = true; options.dryrun = true;
if (opts.has(excludes)) if (opts.has(excludes))
options.excludes = opts.valuesOf(excludes); options.excludes = opts.valuesOf(excludes); // excludes is repeatable
if (opts.has(libs)) if (opts.has(libs))
options.libs = opts.valuesOf(libs); options.libs = getLastElement(opts.valuesOf(libs));
if (opts.has(headerFiles)) if (opts.has(headerFiles))
options.headerFiles = opts.valuesOf(headerFiles); options.headerFiles = getLastElement(opts.valuesOf(headerFiles));
if (opts.has(manPages)) if (opts.has(manPages))
options.manPages = opts.valuesOf(manPages); options.manPages = getLastElement(opts.valuesOf(manPages));
if (opts.has(legalNotices)) if (opts.has(legalNotices))
options.legalNotices = opts.valuesOf(legalNotices); options.legalNotices = getLastElement(opts.valuesOf(legalNotices));
if (opts.has(modulePath)) { if (opts.has(modulePath)) {
Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]); Path[] dirs = getLastElement(opts.valuesOf(modulePath)).toArray(new Path[0]);
options.moduleFinder = new ModulePath(Runtime.version(), true, dirs); options.moduleFinder = new ModulePath(Runtime.version(), true, dirs);
} }
if (opts.has(moduleVersion)) if (opts.has(moduleVersion))
options.moduleVersion = opts.valueOf(moduleVersion); options.moduleVersion = getLastElement(opts.valuesOf(moduleVersion));
if (opts.has(mainClass)) if (opts.has(mainClass))
options.mainClass = opts.valueOf(mainClass); options.mainClass = getLastElement(opts.valuesOf(mainClass));
if (opts.has(osName)) if (opts.has(osName))
options.osName = opts.valueOf(osName); options.osName = getLastElement(opts.valuesOf(osName));
if (opts.has(osArch)) if (opts.has(osArch))
options.osArch = opts.valueOf(osArch); options.osArch = getLastElement(opts.valuesOf(osArch));
if (opts.has(osVersion)) if (opts.has(osVersion))
options.osVersion = opts.valueOf(osVersion); options.osVersion = getLastElement(opts.valuesOf(osVersion));
if (opts.has(warnIfResolved)) if (opts.has(warnIfResolved))
options.moduleResolution = opts.valueOf(warnIfResolved); options.moduleResolution = getLastElement(opts.valuesOf(warnIfResolved));
if (opts.has(doNotResolveByDefault)) { if (opts.has(doNotResolveByDefault)) {
if (options.moduleResolution == null) if (options.moduleResolution == null)
options.moduleResolution = ModuleResolution.empty(); options.moduleResolution = ModuleResolution.empty();
options.moduleResolution = options.moduleResolution.withDoNotResolveByDefault(); options.moduleResolution = options.moduleResolution.withDoNotResolveByDefault();
} }
if (opts.has(hashModules)) { if (opts.has(hashModules)) {
options.modulesToHash = opts.valueOf(hashModules); options.modulesToHash = getLastElement(opts.valuesOf(hashModules));
// if storing hashes then the module path is required // if storing hashes then the module path is required
if (options.moduleFinder == null) if (options.moduleFinder == null)
throw new CommandException("err.modulepath.must.be.specified") throw new CommandException("err.modulepath.must.be.specified")
@ -1531,6 +1537,13 @@ public class JmodTask {
throw new CommandException("err.classpath.must.be.specified").showUsage(true); throw new CommandException("err.classpath.must.be.specified").showUsage(true);
if (options.mainClass != null && !isValidJavaIdentifier(options.mainClass)) if (options.mainClass != null && !isValidJavaIdentifier(options.mainClass))
throw new CommandException("err.invalid.main-class", options.mainClass); throw new CommandException("err.invalid.main-class", options.mainClass);
if (options.mode.equals(Mode.EXTRACT) && options.extractDir != null) {
try {
Files.createDirectories(options.extractDir);
} catch (IOException ioe) {
throw new CommandException("err.cannot.create.dir", options.extractDir);
}
}
} catch (OptionException e) { } catch (OptionException e) {
throw new CommandException(e.getMessage()); throw new CommandException(e.getMessage());
} }
@ -1558,6 +1571,12 @@ public class JmodTask {
return true; return true;
} }
static <E> E getLastElement(List<E> list) {
if (list.size() == 0)
throw new InternalError("Unexpected 0 list size");
return list.get(list.size() - 1);
}
private void reportError(String message) { private void reportError(String message) {
out.println(getMessage("error.prefix") + " " + message); out.println(getMessage("error.prefix") + " " + message);
} }

View File

@ -23,7 +23,7 @@
/* /*
* @test * @test
* @bug 8142968 8166568 8166286 8170618 * @bug 8142968 8166568 8166286 8170618 8168149
* @summary Basic test for jmod * @summary Basic test for jmod
* @library /lib/testlibrary * @library /lib/testlibrary
* @modules jdk.compiler * @modules jdk.compiler
@ -458,6 +458,76 @@ public class JmodTest {
); );
} }
@Test
public void testLastOneWins() throws IOException {
Path workDir = Paths.get("lastOneWins");
if (Files.exists(workDir))
FileUtils.deleteFileTreeWithRetry(workDir);
Files.createDirectory(workDir);
Path jmod = MODS_DIR.resolve("lastOneWins.jmod");
FileUtils.deleteFileIfExistsWithRetry(jmod);
Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
Path shouldNotBeAdded = workDir.resolve("shouldNotBeAdded");
Files.createDirectory(shouldNotBeAdded);
Files.write(shouldNotBeAdded.resolve("aFile"), "hello".getBytes(UTF_8));
// Pairs of options. For options with required arguments the last one
// should win ( first should be effectively ignored, but may still be
// validated ).
jmod("create",
"--conf", shouldNotBeAdded.toString(),
"--conf", cf.toString(),
"--cmds", shouldNotBeAdded.toString(),
"--cmds", bp.toString(),
"--libs", shouldNotBeAdded.toString(),
"--libs", lp.toString(),
"--class-path", shouldNotBeAdded.toString(),
"--class-path", cp.toString(),
"--main-class", "does.NotExist",
"--main-class", "jdk.test.foo.Foo",
"--module-version", "00001",
"--module-version", "5.4.3",
"--do-not-resolve-by-default",
"--do-not-resolve-by-default",
"--warn-if-resolved=incubating",
"--warn-if-resolved=deprecated",
MODS_DIR.resolve("lastOneWins.jmod").toString())
.assertSuccess()
.resultChecker(r -> {
ModuleDescriptor md = getModuleDescriptor(jmod);
Optional<String> omc = md.mainClass();
assertTrue(omc.isPresent());
assertEquals(omc.get(), "jdk.test.foo.Foo");
Optional<Version> ov = md.version();
assertTrue(ov.isPresent());
assertEquals(ov.get().toString(), "5.4.3");
try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p);
Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p);
Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) {
Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2),
Stream.concat(s3, s4))
.collect(toSet());
assertJmodContent(jmod, expectedFilenames);
}
});
jmod("extract",
"--dir", "blah",
"--dir", "lastOneWinsExtractDir",
jmod.toString())
.assertSuccess()
.resultChecker(r -> {
assertTrue(Files.exists(Paths.get("lastOneWinsExtractDir")));
assertTrue(Files.notExists(Paths.get("blah")));
});
}
@Test @Test
public void testPackagesAttribute() throws IOException { public void testPackagesAttribute() throws IOException {
Path jmod = MODS_DIR.resolve("foo.jmod"); Path jmod = MODS_DIR.resolve("foo.jmod");