8166568: Add jmod extract subcommand
8169492: jdk.internal.jmod.JmodFile.JMOD_MAGIC_NUMBER is a mutable array Reviewed-by: alanb, anazarov, dfuchs, mchung
This commit is contained in:
parent
37e807c698
commit
6d2dd6f483
@ -28,8 +28,10 @@ package jdk.internal.jmod;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
@ -39,9 +41,9 @@ import java.util.zip.ZipFile;
|
||||
*/
|
||||
public class JmodFile implements AutoCloseable {
|
||||
// jmod magic number and version number
|
||||
public static final int JMOD_MAJOR_VERSION = 0x01;
|
||||
public static final int JMOD_MINOR_VERSION = 0x00;
|
||||
public static final byte[] JMOD_MAGIC_NUMBER = {
|
||||
private static final int JMOD_MAJOR_VERSION = 0x01;
|
||||
private static final int JMOD_MINOR_VERSION = 0x00;
|
||||
private static final byte[] JMOD_MAGIC_NUMBER = {
|
||||
0x4A, 0x4D, /* JM */
|
||||
JMOD_MAJOR_VERSION, JMOD_MINOR_VERSION, /* version 1.0 */
|
||||
};
|
||||
@ -175,6 +177,10 @@ public class JmodFile implements AutoCloseable {
|
||||
this.zipfile = new ZipFile(file.toFile());
|
||||
}
|
||||
|
||||
public static void writeMagicNumber(OutputStream os) throws IOException {
|
||||
os.write(JMOD_MAGIC_NUMBER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Entry} for a resource in a JMOD file section
|
||||
* or {@code null} if not found.
|
||||
|
@ -36,6 +36,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import jdk.internal.jmod.JmodFile;
|
||||
|
||||
import static jdk.internal.jmod.JmodFile.*;
|
||||
|
||||
@ -57,7 +58,7 @@ class JmodOutputStream extends OutputStream implements AutoCloseable {
|
||||
private JmodOutputStream(OutputStream out) {
|
||||
this.zos = new ZipOutputStream(out);
|
||||
try {
|
||||
out.write(JMOD_MAGIC_NUMBER);
|
||||
JmodFile.writeMagicNumber(out);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
@ -138,6 +138,8 @@ public class JmodTask {
|
||||
private static final String PROGNAME = "jmod";
|
||||
private static final String MODULE_INFO = "module-info.class";
|
||||
|
||||
private static final Path CWD = Paths.get("");
|
||||
|
||||
private Options options;
|
||||
private PrintWriter out = new PrintWriter(System.out, true);
|
||||
void setLog(PrintWriter out, PrintWriter err) {
|
||||
@ -153,6 +155,7 @@ public class JmodTask {
|
||||
|
||||
enum Mode {
|
||||
CREATE,
|
||||
EXTRACT,
|
||||
LIST,
|
||||
DESCRIBE,
|
||||
HASH
|
||||
@ -178,6 +181,7 @@ public class JmodTask {
|
||||
Pattern modulesToHash;
|
||||
boolean dryrun;
|
||||
List<PathMatcher> excludes;
|
||||
Path extractDir;
|
||||
}
|
||||
|
||||
public int run(String[] args) {
|
||||
@ -202,6 +206,9 @@ public class JmodTask {
|
||||
case CREATE:
|
||||
ok = create();
|
||||
break;
|
||||
case EXTRACT:
|
||||
ok = extract();
|
||||
break;
|
||||
case LIST:
|
||||
ok = list();
|
||||
break;
|
||||
@ -248,6 +255,32 @@ public class JmodTask {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean extract() throws IOException {
|
||||
Path dir = options.extractDir != null ? options.extractDir : CWD;
|
||||
try (JmodFile jf = new JmodFile(options.jmodFile)) {
|
||||
jf.stream().forEach(e -> {
|
||||
try {
|
||||
ZipEntry entry = e.zipEntry();
|
||||
String name = entry.getName();
|
||||
int index = name.lastIndexOf("/");
|
||||
if (index != -1) {
|
||||
Path p = dir.resolve(name.substring(0, index));
|
||||
if (Files.notExists(p))
|
||||
Files.createDirectories(p);
|
||||
}
|
||||
|
||||
try (OutputStream os = Files.newOutputStream(dir.resolve(name))) {
|
||||
jf.getInputStream(e).transferTo(os);
|
||||
}
|
||||
} catch (IOException x) {
|
||||
throw new UncheckedIOException(x);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hashModules() {
|
||||
return new Hasher(options.moduleFinder).run();
|
||||
}
|
||||
@ -1019,8 +1052,6 @@ public class JmodTask {
|
||||
static class ClassPathConverter implements ValueConverter<Path> {
|
||||
static final ValueConverter<Path> INSTANCE = new ClassPathConverter();
|
||||
|
||||
private static final Path CWD = Paths.get("");
|
||||
|
||||
@Override
|
||||
public Path convert(String value) {
|
||||
try {
|
||||
@ -1044,8 +1075,6 @@ public class JmodTask {
|
||||
static class DirPathConverter implements ValueConverter<Path> {
|
||||
static final ValueConverter<Path> INSTANCE = new DirPathConverter();
|
||||
|
||||
private static final Path CWD = Paths.get("");
|
||||
|
||||
@Override
|
||||
public Path convert(String value) {
|
||||
try {
|
||||
@ -1065,6 +1094,33 @@ public class JmodTask {
|
||||
@Override public String valuePattern() { return "path"; }
|
||||
}
|
||||
|
||||
static class ExtractDirPathConverter implements ValueConverter<Path> {
|
||||
|
||||
@Override
|
||||
public Path convert(String value) {
|
||||
try {
|
||||
Path path = CWD.resolve(value);
|
||||
if (Files.exists(path)) {
|
||||
if (!Files.isDirectory(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;
|
||||
} catch (InvalidPathException x) {
|
||||
throw new CommandException("err.path.not.valid", value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Class<Path> valueType() { return Path.class; }
|
||||
|
||||
@Override public String valuePattern() { return "path"; }
|
||||
}
|
||||
|
||||
static class ModuleVersionConverter implements ValueConverter<Version> {
|
||||
@Override
|
||||
public Version convert(String value) {
|
||||
@ -1158,6 +1214,7 @@ public class JmodTask {
|
||||
|
||||
builder.append(getMessage("main.opt.mode")).append("\n ");
|
||||
builder.append(getMessage("main.opt.mode.create")).append("\n ");
|
||||
builder.append(getMessage("main.opt.mode.extract")).append("\n ");
|
||||
builder.append(getMessage("main.opt.mode.list")).append("\n ");
|
||||
builder.append(getMessage("main.opt.mode.describe")).append("\n ");
|
||||
builder.append(getMessage("main.opt.mode.hash")).append("\n\n");
|
||||
@ -1203,6 +1260,11 @@ public class JmodTask {
|
||||
.withValuesSeparatedBy(File.pathSeparatorChar)
|
||||
.withValuesConvertedBy(DirPathConverter.INSTANCE);
|
||||
|
||||
OptionSpec<Path> dir
|
||||
= parser.accepts("dir", getMessage("main.opt.extractDir"))
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new ExtractDirPathConverter());
|
||||
|
||||
OptionSpec<Void> dryrun
|
||||
= parser.accepts("dry-run", getMessage("main.opt.dry-run"));
|
||||
|
||||
@ -1303,6 +1365,8 @@ public class JmodTask {
|
||||
options.cmds = opts.valuesOf(cmds);
|
||||
if (opts.has(config))
|
||||
options.configs = opts.valuesOf(config);
|
||||
if (opts.has(dir))
|
||||
options.extractDir = opts.valueOf(dir);
|
||||
if (opts.has(dryrun))
|
||||
options.dryrun = true;
|
||||
if (opts.has(excludes))
|
||||
@ -1347,7 +1411,8 @@ public class JmodTask {
|
||||
if (options.mode.equals(Mode.CREATE) && Files.exists(path))
|
||||
throw new CommandException("err.file.already.exists", path);
|
||||
else if ((options.mode.equals(Mode.LIST) ||
|
||||
options.mode.equals(Mode.DESCRIBE))
|
||||
options.mode.equals(Mode.DESCRIBE) ||
|
||||
options.mode.equals((Mode.EXTRACT)))
|
||||
&& Files.notExists(path))
|
||||
throw new CommandException("err.jmod.not.found", path);
|
||||
|
||||
|
@ -24,11 +24,11 @@
|
||||
#
|
||||
|
||||
main.usage.summary=\
|
||||
Usage: {0} (create|list|describe|hash) <OPTIONS> <jmod-file>\n\
|
||||
Usage: {0} (create|extract|list|describe|hash) <OPTIONS> <jmod-file>\n\
|
||||
use --help for a list of possible options
|
||||
|
||||
main.usage=\
|
||||
Usage: {0} (create|list|describe|hash) <OPTIONS> <jmod-file>\n\
|
||||
Usage: {0} (create|extract|list|describe|hash) <OPTIONS> <jmod-file>\n\
|
||||
|
||||
error.prefix=Error:
|
||||
warn.prefix=Warning:
|
||||
@ -37,6 +37,8 @@ main.opt.mode=\
|
||||
\Main operation modes:
|
||||
main.opt.mode.create=\
|
||||
\create - Creates a new jmod archive
|
||||
main.opt.mode.extract=\
|
||||
\extract - Extracts all the files from the archive
|
||||
main.opt.mode.list=\
|
||||
\list - Prints the names of all the entries
|
||||
main.opt.mode.describe=\
|
||||
@ -50,6 +52,7 @@ main.opt.class-path=Application jar files|dir containing classes
|
||||
main.opt.libs=Location of native libraries
|
||||
main.opt.cmds=Location of native commands
|
||||
main.opt.config=Location of user-editable config files
|
||||
main.opt.extractDir=Target directory for extract
|
||||
main.opt.dry-run=Dry run of hash mode
|
||||
main.opt.exclude=Exclude files matching the supplied comma separated pattern\
|
||||
\ list, each element using one the following forms: <glob-pattern>,\
|
||||
@ -75,8 +78,9 @@ main.opt.cmdfile=Read options from the specified file
|
||||
|
||||
module.hashes.recorded=Hashes are recorded in module {0}
|
||||
|
||||
err.missing.mode=one of create, list, describe, or hash must be specified
|
||||
err.invalid.mode=mode must be one of create, list, describe, or hash: {0}
|
||||
err.missing.mode=one of create, extract, list, describe, or hash must be specified
|
||||
err.invalid.mode=mode must be one of create, extract, list, describe, or hash: {0}
|
||||
err.cannot.create.dir=cannot create directory {0}
|
||||
err.classpath.must.be.specified=--class-path must be specified
|
||||
err.jmod.must.be.specified=jmod-file must be specified
|
||||
err.invalid.version=invalid module version {0}
|
||||
|
@ -82,7 +82,7 @@ public class JmodNegativeTest {
|
||||
jmod()
|
||||
.assertFailure()
|
||||
.resultChecker(r ->
|
||||
assertContains(r.output, "Error: one of create, list, describe, or hash must be specified")
|
||||
assertContains(r.output, "Error: one of create, extract, list, describe, or hash must be specified")
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ public class JmodNegativeTest {
|
||||
jmod("badAction")
|
||||
.assertFailure()
|
||||
.resultChecker(r ->
|
||||
assertContains(r.output, "Error: mode must be one of create, list, describe, or hash")
|
||||
assertContains(r.output, "Error: mode must be one of create, extract, list, describe, or hash")
|
||||
);
|
||||
|
||||
jmod("--badOption")
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8142968
|
||||
* @bug 8142968 8166568
|
||||
* @summary Basic test for jmod
|
||||
* @library /lib/testlibrary
|
||||
* @modules jdk.compiler
|
||||
@ -134,6 +134,70 @@ public class JmodTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCWD() throws IOException {
|
||||
Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
|
||||
jmod("create",
|
||||
"--class-path", cp.toString(),
|
||||
MODS_DIR.resolve("fooExtractCWD.jmod").toString())
|
||||
.assertSuccess();
|
||||
|
||||
jmod("extract",
|
||||
MODS_DIR.resolve("fooExtractCWD.jmod").toString())
|
||||
.assertSuccess()
|
||||
.resultChecker(r -> {
|
||||
// module-info should exist, but jmod will have added its Packages attr.
|
||||
assertTrue(Files.exists(Paths.get("classes/module-info.class")));
|
||||
assertSameContent(cp.resolve("jdk/test/foo/Foo.class"),
|
||||
Paths.get("classes/jdk/test/foo/Foo.class"));
|
||||
assertSameContent(cp.resolve("jdk/test/foo/internal/Message.class"),
|
||||
Paths.get("classes/jdk/test/foo/internal/Message.class"));
|
||||
assertSameContent(cp.resolve("jdk/test/foo/resources/foo.properties"),
|
||||
Paths.get("classes/jdk/test/foo/resources/foo.properties"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractDir() throws IOException {
|
||||
if (Files.exists(Paths.get("extractTestDir")))
|
||||
FileUtils.deleteFileTreeWithRetry(Paths.get("extractTestDir"));
|
||||
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");
|
||||
|
||||
jmod("create",
|
||||
"--conf", cf.toString(),
|
||||
"--cmds", bp.toString(),
|
||||
"--libs", lp.toString(),
|
||||
"--class-path", cp.toString(),
|
||||
MODS_DIR.resolve("fooExtractDir.jmod").toString())
|
||||
.assertSuccess();
|
||||
|
||||
jmod("extract",
|
||||
"--dir", "extractTestDir",
|
||||
MODS_DIR.resolve("fooExtractDir.jmod").toString())
|
||||
.assertSuccess();
|
||||
|
||||
jmod("extract",
|
||||
"--dir", "extractTestDir",
|
||||
MODS_DIR.resolve("fooExtractDir.jmod").toString())
|
||||
.assertSuccess()
|
||||
.resultChecker(r -> {
|
||||
// check a sample of the extracted files
|
||||
Path p = Paths.get("extractTestDir");
|
||||
assertTrue(Files.exists(p.resolve("classes/module-info.class")));
|
||||
assertSameContent(cp.resolve("jdk/test/foo/Foo.class"),
|
||||
p.resolve("classes/jdk/test/foo/Foo.class"));
|
||||
assertSameContent(bp.resolve("first"),
|
||||
p.resolve(CMDS_PREFIX).resolve("first"));
|
||||
assertSameContent(lp.resolve("first.so"),
|
||||
p.resolve(LIBS_PREFIX).resolve("second.so"));
|
||||
assertSameContent(cf.resolve("second.cfg"),
|
||||
p.resolve(CONFIGS_PREFIX).resolve("second.cfg"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainClass() throws IOException {
|
||||
Path jmod = MODS_DIR.resolve("fooMainClass.jmod");
|
||||
@ -532,6 +596,16 @@ public class JmodTest {
|
||||
}
|
||||
}
|
||||
|
||||
static void assertSameContent(Path p1, Path p2) {
|
||||
try {
|
||||
byte[] ba1 = Files.readAllBytes(p1);
|
||||
byte[] ba2 = Files.readAllBytes(p2);
|
||||
assertEquals(ba1, ba2);
|
||||
} catch (IOException x) {
|
||||
throw new UncheckedIOException(x);
|
||||
}
|
||||
}
|
||||
|
||||
static JmodResult jmod(String... args) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintStream ps = new PrintStream(baos);
|
||||
|
Loading…
x
Reference in New Issue
Block a user