--- a/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java Tue Oct 18 13:27:00 2016 -0700
@@ -71,7 +71,9 @@
NATIVE_LIBS("native"),
NATIVE_CMDS("bin"),
CLASSES("classes"),
- CONFIG("conf");
+ CONFIG("conf"),
+ HEADER_FILES("include"),
+ MAN_PAGES("man");
private final String jmodDir;
private Section(String jmodDir) {
@@ -151,6 +153,10 @@
return Section.CLASSES;
case "conf":
return Section.CONFIG;
+ case "include":
+ return Section.HEADER_FILES;
+ case "man":
+ return Section.MAN_PAGES;
default:
throw new IllegalArgumentException("invalid section: " + s);
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java Tue Oct 18 13:27:00 2016 -0700
@@ -37,17 +37,17 @@
import java.io.UncheckedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.module.ModuleDescriptor;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -55,7 +55,8 @@
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
-import java.util.stream.Collectors;
+import static java.util.stream.Collectors.*;
+
import jdk.tools.jlink.internal.BasicImageWriter;
import jdk.tools.jlink.internal.plugins.FileCopierPlugin.SymImageFile;
import jdk.tools.jlink.internal.ExecutableImage;
@@ -171,15 +172,36 @@
Properties release = releaseProperties(files);
Path bin = root.resolve("bin");
- files.entries().forEach(f -> {
- if (!f.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
+ // check any duplicated resource files
+ Map<Path, Set<String>> duplicates = new HashMap<>();
+ files.entries()
+ .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
+ .collect(groupingBy(this::entryToImagePath,
+ mapping(ResourcePoolEntry::moduleName, toSet())))
+ .entrySet()
+ .stream()
+ .filter(e -> e.getValue().size() > 1)
+ .forEach(e -> duplicates.put(e.getKey(), e.getValue()));
+
+ // write non-classes resource files to the image
+ files.entries()
+ .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
+ .forEach(f -> {
try {
accept(f);
+ } catch (FileAlreadyExistsException e) {
+ // error for duplicated entries
+ Path path = entryToImagePath(f);
+ UncheckedIOException x =
+ new UncheckedIOException(path + " duplicated in " +
+ duplicates.get(path), e);
+ x.addSuppressed(e);
+ throw x;
} catch (IOException ioExp) {
throw new UncheckedIOException(ioExp);
}
- }
- });
+ });
+
files.moduleView().modules().forEach(m -> {
// Only add modules that contain packages
if (!m.packages().isEmpty()) {
@@ -226,7 +248,7 @@
version().
stream().
map(Object::toString).
- collect(Collectors.joining("."));
+ collect(joining("."));
}
private static String quote(String str) {
@@ -344,28 +366,69 @@
}
}
+ /**
+ * Returns the file name of this entry
+ */
+ private String entryToFileName(ResourcePoolEntry entry) {
+ if (entry.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
+ throw new IllegalArgumentException("invalid type: " + entry);
+
+ String module = "/" + entry.moduleName() + "/";
+ String filename = entry.path().substring(module.length());
+ // Remove radical native|config|...
+ return filename.substring(filename.indexOf('/') + 1);
+ }
+
+ /**
+ * Returns the path of the given entry to be written in the image
+ */
+ private Path entryToImagePath(ResourcePoolEntry entry) {
+ switch (entry.type()) {
+ case NATIVE_LIB:
+ String filename = entryToFileName(entry);
+ return Paths.get(nativeDir(filename), filename);
+ case NATIVE_CMD:
+ return Paths.get("bin", entryToFileName(entry));
+ case CONFIG:
+ return Paths.get("conf", entryToFileName(entry));
+ case HEADER_FILE:
+ return Paths.get("include", entryToFileName(entry));
+ case MAN_PAGE:
+ return Paths.get("man", entryToFileName(entry));
+ case TOP:
+ return Paths.get(entryToFileName(entry));
+ case OTHER:
+ return Paths.get("other", entryToFileName(entry));
+ default:
+ throw new IllegalArgumentException("invalid type: " + entry);
+ }
+ }
+
private void accept(ResourcePoolEntry file) throws IOException {
- String fullPath = file.path();
- String module = "/" + file.moduleName() + "/";
- String filename = fullPath.substring(module.length());
- // Remove radical native|config|...
- filename = filename.substring(filename.indexOf('/') + 1);
try (InputStream in = file.content()) {
switch (file.type()) {
case NATIVE_LIB:
- writeEntry(in, destFile(nativeDir(filename), filename));
+ Path dest = root.resolve(entryToImagePath(file));
+ writeEntry(in, dest);
break;
case NATIVE_CMD:
- Path path = destFile("bin", filename);
- writeEntry(in, path);
- path.toFile().setExecutable(true);
+ Path p = root.resolve(entryToImagePath(file));
+ writeEntry(in, p);
+ p.toFile().setExecutable(true);
break;
case CONFIG:
- writeEntry(in, destFile("conf", filename));
+ writeEntry(in, root.resolve(entryToImagePath(file)));
+ break;
+ case HEADER_FILE:
+ writeEntry(in, root.resolve(entryToImagePath(file)));
+ break;
+ case MAN_PAGE:
+ writeEntry(in, root.resolve(entryToImagePath(file)));
break;
case TOP:
break;
case OTHER:
+ String filename = entryToFileName(file);
if (file instanceof SymImageFile) {
SymImageFile sym = (SymImageFile) file;
Path target = root.resolve(sym.getTargetPath());
@@ -379,15 +442,11 @@
}
break;
default:
- throw new InternalError("unexpected entry: " + fullPath);
+ throw new InternalError("unexpected entry: " + file.path());
}
}
}
- private Path destFile(String dir, String filename) {
- return root.resolve(dir).resolve(filename);
- }
-
private void writeEntry(InputStream in, Path dstFile) throws IOException {
Objects.requireNonNull(in);
Objects.requireNonNull(dstFile);
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java Tue Oct 18 13:27:00 2016 -0700
@@ -44,9 +44,11 @@
public static enum EntryType {
MODULE_NAME,
CLASS_OR_RESOURCE,
+ CONFIG,
NATIVE_LIB,
NATIVE_CMD,
- CONFIG,
+ HEADER_FILE,
+ MAN_PAGE,
SERVICE;
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ArchiveEntryResourcePoolEntry.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ArchiveEntryResourcePoolEntry.java Tue Oct 18 13:27:00 2016 -0700
@@ -73,6 +73,10 @@
return ResourcePoolEntry.Type.NATIVE_CMD;
case NATIVE_LIB:
return ResourcePoolEntry.Type.NATIVE_LIB;
+ case HEADER_FILE:
+ return ResourcePoolEntry.Type.HEADER_FILE;
+ case MAN_PAGE:
+ return ResourcePoolEntry.Type.MAN_PAGE;
default:
return ResourcePoolEntry.Type.OTHER;
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java Tue Oct 18 13:27:00 2016 -0700
@@ -213,8 +213,8 @@
}
return EXIT_OK;
- } catch (UncheckedIOException | PluginException | IllegalArgumentException |
- IOException | ResolutionException e) {
+ } catch (PluginException | IllegalArgumentException |
+ UncheckedIOException |IOException | ResolutionException e) {
log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage());
if (DEBUG) {
e.printStackTrace(log);
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java Tue Oct 18 13:27:00 2016 -0700
@@ -128,12 +128,16 @@
switch (section) {
case CLASSES:
return EntryType.CLASS_OR_RESOURCE;
+ case CONFIG:
+ return EntryType.CONFIG;
case NATIVE_LIBS:
return EntryType.NATIVE_LIB;
case NATIVE_CMDS:
return EntryType.NATIVE_CMD;
- case CONFIG:
- return EntryType.CONFIG;
+ case HEADER_FILES:
+ return EntryType.HEADER_FILE;
+ case MAN_PAGES:
+ return EntryType.MAN_PAGE;
default:
throw new InternalError("unexpected entry: " + section);
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java Tue Oct 18 13:27:00 2016 -0700
@@ -49,6 +49,8 @@
import jdk.internal.module.ConfigurableModuleFinder;
import jdk.internal.module.ConfigurableModuleFinder.Phase;
+import jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin;
+import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.Plugin.Category;
import jdk.tools.jlink.builder.DefaultImageBuilder;
@@ -322,6 +324,20 @@
addArgumentMap(plugin);
}, "-G");
mainOptions.add(plugOption);
+ } else if (plugin instanceof ExcludeJmodSectionPlugin) {
+ plugOption = new PlugOption(false, (task, opt, arg) -> {
+ Map<String, String> m = addArgumentMap(plugin);
+ m.put(ExcludeJmodSectionPlugin.NAME,
+ ExcludeJmodSectionPlugin.MAN_PAGES);
+ }, "--no-man-pages");
+ mainOptions.add(plugOption);
+
+ plugOption = new PlugOption(false, (task, opt, arg) -> {
+ Map<String, String> m = addArgumentMap(plugin);
+ m.put(ExcludeJmodSectionPlugin.NAME,
+ ExcludeJmodSectionPlugin.INCLUDE_HEADER_FILES);
+ }, "--no-header-files");
+ mainOptions.add(plugOption);
}
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ExcludeJmodSectionPlugin.java Tue Oct 18 13:27:00 2016 -0700
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.internal.plugins;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import jdk.tools.jlink.plugin.Plugin;
+import jdk.tools.jlink.plugin.PluginException;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolBuilder;
+import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
+
+/**
+ *
+ * A plugin to exclude a JMOD section such as man pages or header files
+ */
+public final class ExcludeJmodSectionPlugin implements Plugin {
+
+ public static final String NAME = "exclude-jmod-section";
+ public static final String MAN_PAGES = "man";
+ public static final String INCLUDE_HEADER_FILES = "headers";
+
+ private final Set<Type> filters = new HashSet<>();
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public void configure(Map<String, String> config) {
+ String arg = config.get(NAME);
+ if (arg.isEmpty()) {
+ throw new PluginException("Section name must be specified");
+ }
+
+ switch (arg) {
+ case MAN_PAGES:
+ filters.add(Type.MAN_PAGE);
+ break;
+ case INCLUDE_HEADER_FILES:
+ filters.add(Type.HEADER_FILE);
+ break;
+ default:
+ throw new PluginException("Invalid section name: " + arg);
+ }
+ }
+
+ @Override
+ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
+ in.transformAndCopy(entry -> {
+ // filter entries whose type corresponds to the specified JMOD section
+ if (filters.contains(entry.type())) {
+ entry = null;
+ }
+ return entry;
+ }, out);
+ return out.build();
+ }
+
+ @Override
+ public Category getType() {
+ return Category.FILTER;
+ }
+
+ @Override
+ public String getDescription() {
+ return PluginsResourceBundle.getDescription(NAME);
+ }
+
+ @Override
+ public boolean hasArguments() {
+ return true;
+ }
+
+ @Override
+ public String getArgumentsDescription() {
+ return PluginsResourceBundle.getArgument(NAME);
+ }
+}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolEntry.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolEntry.java Tue Oct 18 13:27:00 2016 -0700
@@ -24,13 +24,12 @@
*/
package jdk.tools.jlink.plugin;
-import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
-import java.nio.file.Files;
import java.nio.file.Path;
+
import jdk.tools.jlink.internal.ResourcePoolEntryFactory;
/**
@@ -64,6 +63,8 @@
CONFIG,
NATIVE_CMD,
NATIVE_LIB,
+ HEADER_FILE,
+ MAN_PAGE,
TOP,
OTHER
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties Tue Oct 18 13:27:00 2016 -0700
@@ -68,6 +68,12 @@
exclude-resources.description=\
Specify resources to exclude. e.g.: **.jcov,glob:**/META-INF/**
+exclude-jmod-section.argument=<section-name>\n\
+where <section-name> is \"man\" or \"headers".
+
+exclude-jmod-section.description=\
+Specify a JMOD section to exclude
+
generate-jli-classes.argument=<none|@filename>
generate-jli-classes.description=\
@@ -145,6 +151,12 @@
plugin.opt.G=\
\ -G, --strip-debug Strip debug information
+plugin.opt.no-man-pages=\
+\ --no-man-pages Exclude man pages
+
+plugin.opt.no-header-files=\
+\ --no-header-files Exclude include header files
+
main.plugin.name=\
\Plugin Name
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java Tue Oct 18 13:27:00 2016 -0700
@@ -165,6 +165,8 @@
List<Path> cmds;
List<Path> configs;
List<Path> libs;
+ List<Path> headerFiles;
+ List<Path> manPages;
ModuleFinder moduleFinder;
Version moduleVersion;
String mainClass;
@@ -346,6 +348,9 @@
final List<Path> libs = options.libs;
final List<Path> configs = options.configs;
final List<Path> classpath = options.classpath;
+ final List<Path> headerFiles = options.headerFiles;
+ final List<Path> manPages = options.manPages;
+
final Version moduleVersion = options.moduleVersion;
final String mainClass = options.mainClass;
final String osName = options.osName;
@@ -369,6 +374,9 @@
processSection(out, Section.NATIVE_CMDS, cmds);
processSection(out, Section.NATIVE_LIBS, libs);
processSection(out, Section.CONFIG, configs);
+ processSection(out, Section.HEADER_FILES, headerFiles);
+ processSection(out, Section.MAN_PAGES, manPages);
+
}
/**
@@ -596,7 +604,7 @@
return "";
}
- void processClasses(JmodOutputStream zos, List<Path> classpaths)
+ void processClasses(JmodOutputStream out, List<Path> classpaths)
throws IOException
{
if (classpaths == null)
@@ -604,24 +612,24 @@
for (Path p : classpaths) {
if (Files.isDirectory(p)) {
- processSection(zos, Section.CLASSES, p);
+ processSection(out, Section.CLASSES, p);
} else if (Files.isRegularFile(p) && p.toString().endsWith(".jar")) {
try (JarFile jf = new JarFile(p.toFile())) {
- JarEntryConsumer jec = new JarEntryConsumer(zos, jf);
+ JarEntryConsumer jec = new JarEntryConsumer(out, jf);
jf.stream().filter(jec).forEach(jec);
}
}
}
}
- void processSection(JmodOutputStream zos, Section section, List<Path> paths)
+ void processSection(JmodOutputStream out, Section section, List<Path> paths)
throws IOException
{
if (paths == null)
return;
for (Path p : paths)
- processSection(zos, section, p);
+ processSection(out, section, p);
}
void processSection(JmodOutputStream out, Section section, Path top)
@@ -1195,6 +1203,12 @@
= parser.acceptsAll(Set.of("h", "help"), getMessage("main.opt.help"))
.forHelp();
+ OptionSpec<Path> headerFiles
+ = parser.accepts("header-files", getMessage("main.opt.header-files"))
+ .withRequiredArg()
+ .withValuesSeparatedBy(File.pathSeparatorChar)
+ .withValuesConvertedBy(DirPathConverter.INSTANCE);
+
OptionSpec<Path> libs
= parser.accepts("libs", getMessage("main.opt.libs"))
.withRequiredArg()
@@ -1206,6 +1220,12 @@
.withRequiredArg()
.describedAs(getMessage("main.opt.main-class.arg"));
+ OptionSpec<Path> manPages
+ = parser.accepts("man-pages", getMessage("main.opt.man-pages"))
+ .withRequiredArg()
+ .withValuesSeparatedBy(File.pathSeparatorChar)
+ .withValuesConvertedBy(DirPathConverter.INSTANCE);
+
OptionSpec<Path> modulePath
= parser.acceptsAll(Set.of("p", "module-path"),
getMessage("main.opt.module-path"))
@@ -1272,6 +1292,10 @@
options.excludes = opts.valuesOf(excludes);
if (opts.has(libs))
options.libs = opts.valuesOf(libs);
+ if (opts.has(headerFiles))
+ options.headerFiles = opts.valuesOf(headerFiles);
+ if (opts.has(manPages))
+ options.manPages = opts.valuesOf(manPages);
if (opts.has(modulePath)) {
Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]);
options.moduleFinder = ModuleFinder.of(dirs);
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties Tue Oct 18 13:27:00 2016 -0700
@@ -54,9 +54,11 @@
main.opt.exclude=Exclude files matching the supplied comma separated pattern\
\ list, each element using one the following forms: <glob-pattern>,\
\ glob:<glob-pattern> or regex:<regex-pattern>
+main.opt.header-files=Location of header files
main.opt.module-version= Module version
main.opt.main-class=Main class
main.opt.main-class.arg=class-name
+main.opt.man-pages=Location of man pages
main.opt.os-name=Operating system name
main.opt.os-name.arg=os-name
main.opt.os-arch=Operating system architecture
--- a/jdk/src/jdk.jlink/share/classes/module-info.java Tue Oct 18 20:28:58 2016 +0200
+++ b/jdk/src/jdk.jlink/share/classes/module-info.java Tue Oct 18 13:27:00 2016 -0700
@@ -38,6 +38,7 @@
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.StripDebugPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ExcludePlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin;
+ provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.SystemModuleDescriptorPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.StripNativeCommandsPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.OrderResourcesPlugin;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/plugins/ExcludeJmodSectionPluginTest.java Tue Oct 18 13:27:00 2016 -0700
@@ -0,0 +1,340 @@
+/**
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Test --no-man-pages and --no-header-files
+ * @library /lib/testlibrary
+ * @modules jdk.compiler
+ * jdk.jlink
+ * @build CompilerUtils
+ * @run testng ExcludeJmodSectionPluginTest
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class ExcludeJmodSectionPluginTest {
+ static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
+ .orElseThrow(() ->
+ new RuntimeException("jmod tool not found")
+ );
+
+ static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
+ .orElseThrow(() ->
+ new RuntimeException("jlink tool not found")
+ );
+
+ static final Path MODULE_PATH = Paths.get(System.getProperty("java.home"), "jmods");
+ static final Path SRC_DIR = Paths.get("src");
+ static final Path MODS_DIR = Paths.get("mods");
+ static final Path JMODS_DIR = Paths.get("jmods");
+ static final Path MAN_DIR = Paths.get("man");
+ static final Path INCLUDE_DIR = Paths.get("include");
+ static final Path IMAGES_DIR = Paths.get("images");
+
+ @BeforeTest
+ private void setup() throws Exception {
+ // build jmod files
+ JmodFileBuilder m1 = new JmodFileBuilder("m1");
+ m1.headerFile("m1a.h");
+ m1.headerFile("m1b.h");
+ m1.build();
+
+ JmodFileBuilder m2 = new JmodFileBuilder("m2");
+ m2.headerFile("m2.h");
+ m2.manPage("tool2.1");
+ m2.build();
+
+ JmodFileBuilder m3 = new JmodFileBuilder("m3");
+ m3.manPage("tool3.1");
+ m3.build();
+ }
+
+ private String imageDir(String dir) {
+ return IMAGES_DIR.resolve(dir).toString();
+ }
+
+
+ @DataProvider(name = "jlinkoptions")
+ public Object[][] jlinkoptions() {
+ // options and expected header files & man pages
+ return new Object[][] {
+ { new String [] {
+ "test1",
+ "--exclude-files=/java.base/include/**,/java.base/man/**",
+ },
+ List.of("include/m1a.h",
+ "include/m1b.h",
+ "include/m2.h",
+ "man/tool2.1",
+ "man/tool3.1")
+ },
+
+ { new String [] {
+ "test2",
+ "--no-man-pages",
+ "--no-header-files",
+ },
+ List.of()
+ },
+
+ { new String[] {
+ "test3",
+ "--no-header-files",
+ "--exclude-files=/java.base/man/**"
+ },
+ List.of("man/tool2.1",
+ "man/tool3.1") },
+
+ { new String [] {
+ "test4",
+ "--no-man-pages",
+ "--exclude-files=/java.base/include/**,/m2/include/**",
+ },
+ List.of("include/m1a.h",
+ "include/m1b.h")
+ },
+
+ { new String [] {
+ "test5",
+ "--no-header-files",
+ "--exclude-files=/java.base/man/**,/m2/man/**"
+ },
+ List.of("man/tool3.1")
+ },
+ };
+ }
+
+ @Test(dataProvider = "jlinkoptions")
+ public void test(String[] opts, List<String> expectedFiles) throws Exception {
+ if (Files.notExists(MODULE_PATH)) {
+ // exploded image
+ return;
+ }
+
+ String dir = opts[0];
+ List<String> options = new ArrayList<>();
+ for (int i = 1; i < opts.length; i++) {
+ options.add(opts[i]);
+ }
+
+ String mpath = MODULE_PATH.toString() + File.pathSeparator +
+ JMODS_DIR.toString();
+ Stream.of("--module-path", mpath,
+ "--add-modules", "m1,m2,m3",
+ "--output", imageDir(dir))
+ .forEach(options::add);
+
+ Path image = createImage(dir, options, expectedFiles);
+
+ // check if any unexpected header file or man page
+ Set<Path> extraFiles = Files.walk(image, Integer.MAX_VALUE)
+ .filter(p -> Files.isRegularFile(p))
+ .filter(p -> p.getParent().endsWith("include") ||
+ p.getParent().endsWith("man"))
+ .filter(p -> {
+ String fn = String.format("%s/%s",
+ p.getParent().getFileName().toString(),
+ p.getFileName().toString());
+ return !expectedFiles.contains(fn);
+ })
+ .collect(Collectors.toSet());
+
+ if (extraFiles.size() > 0) {
+ System.out.println("Unexpected files: " + extraFiles.toString());
+ assertTrue(extraFiles.isEmpty());
+ }
+ }
+
+ /**
+ * Test java.base's include header files
+ */
+ @Test
+ public void testJavaBase() {
+ if (Files.notExists(MODULE_PATH)) {
+ // exploded image
+ return;
+ }
+ List<String> options = List.of("--module-path",
+ MODULE_PATH.toString(),
+ "--add-modules", "java.base",
+ "--output", imageDir("base"));
+ createImage("base", options,
+ List.of("include/jni.h", "include/jvmti.h"));
+
+ }
+
+ private Path createImage(String outputDir, List<String> options,
+ List<String> expectedFiles) {
+ System.out.println("jlink " + options.toString());
+ int rc = JLINK_TOOL.run(System.out, System.out,
+ options.toArray(new String[0]));
+ assertTrue(rc == 0);
+
+ Path d = IMAGES_DIR.resolve(outputDir);
+ for (String fn : expectedFiles) {
+ Path path = d.resolve(fn);
+ if (Files.notExists(path)) {
+ throw new RuntimeException(path + " not found");
+ }
+ }
+ return d;
+ }
+
+ private void deleteDirectory(Path dir) throws IOException {
+ Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException
+ {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+ throws IOException
+ {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ /**
+ * Builder to create JMOD file
+ */
+ class JmodFileBuilder {
+
+ final String name;
+ final Set<String> manPages = new HashSet<>();
+ final Set<String> headerFiles = new HashSet<>();
+
+ JmodFileBuilder(String name) throws IOException {
+ this.name = name;
+
+ Path msrc = SRC_DIR.resolve(name);
+ if (Files.exists(msrc)) {
+ deleteDirectory(msrc);
+ }
+ }
+
+ JmodFileBuilder manPage(String filename) {
+ manPages.add(filename);
+ return this;
+ }
+
+ JmodFileBuilder headerFile(String filename) {
+ headerFiles.add(filename);
+ return this;
+ }
+
+ Path build() throws IOException {
+ compileModule();
+ // create man pages
+ Path mdir = MAN_DIR.resolve(name);
+ for (String filename : manPages) {
+ Files.createDirectories(mdir);
+ Files.createFile(mdir.resolve(filename));
+ }
+ // create header files
+ mdir = INCLUDE_DIR.resolve(name);
+ for (String filename : headerFiles) {
+ Files.createDirectories(mdir);
+ Files.createFile(mdir.resolve(filename));
+ }
+ return createJmodFile();
+ }
+
+ void compileModule() throws IOException {
+ Path msrc = SRC_DIR.resolve(name);
+ Files.createDirectories(msrc);
+ Path minfo = msrc.resolve("module-info.java");
+ try (BufferedWriter bw = Files.newBufferedWriter(minfo);
+ PrintWriter writer = new PrintWriter(bw)) {
+ writer.format("module %s { }%n", name);
+ }
+
+ assertTrue(CompilerUtils.compile(msrc, MODS_DIR,
+ "--module-source-path",
+ SRC_DIR.toString()));
+ }
+
+ Path createJmodFile() throws IOException {
+ Path mclasses = MODS_DIR.resolve(name);
+ Files.createDirectories(JMODS_DIR);
+ Path outfile = JMODS_DIR.resolve(name + ".jmod");
+ List<String> args = new ArrayList<>();
+ args.add("create");
+ // add classes
+ args.add("--class-path");
+ args.add(mclasses.toString());
+ // man pages
+ if (manPages.size() > 0) {
+ args.add("--man-pages");
+ args.add(MAN_DIR.resolve(name).toString());
+ }
+ // header files
+ if (headerFiles.size() > 0) {
+ args.add("--header-files");
+ args.add(INCLUDE_DIR.resolve(name).toString());
+ }
+ args.add(outfile.toString());
+
+ if (Files.exists(outfile))
+ Files.delete(outfile);
+
+ System.out.println("jmod " +
+ args.stream().collect(Collectors.joining(" ")));
+
+ int rc = JMOD_TOOL.run(System.out, System.out,
+ args.toArray(new String[args.size()]));
+ if (rc != 0) {
+ throw new AssertionError("jmod failed: rc = " + rc);
+ }
+ return outfile;
+ }
+ }
+}