8167558: Add new JMOD section for header files and man pages
authormchung
Tue, 18 Oct 2016 13:27:00 -0700
changeset 41561 0c6942d13f2e
parent 41560 a66e7ee16cf9
child 41562 1e040ccac110
8167558: Add new JMOD section for header files and man pages Reviewed-by: alanb
jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ArchiveEntryResourcePoolEntry.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ExcludeJmodSectionPlugin.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolEntry.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties
jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties
jdk/src/jdk.jlink/share/classes/module-info.java
jdk/test/tools/jlink/plugins/ExcludeJmodSectionPluginTest.java
--- 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;
+        }
+    }
+}