src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JLinkBundlerHelper.java
branchJDK-8200758-branch
changeset 58994 b09ba68c6a19
parent 58992 7249e95cc439
equal deleted inserted replaced
58993:b5e1baa9d2c3 58994:b09ba68c6a19
       
     1 /*
       
     2  * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.incubator.jpackage.internal;
       
    27 
       
    28 import java.io.File;
       
    29 import java.io.IOException;
       
    30 import java.io.StringReader;
       
    31 import java.io.PrintWriter;
       
    32 import java.io.StringWriter;
       
    33 import java.nio.file.Files;
       
    34 import java.nio.file.Path;
       
    35 import java.text.MessageFormat;
       
    36 import java.util.ArrayList;
       
    37 import java.util.Collection;
       
    38 import java.util.Collections;
       
    39 import java.util.EnumSet;
       
    40 import java.util.HashMap;
       
    41 import java.util.HashSet;
       
    42 import java.util.Iterator;
       
    43 import java.util.LinkedHashMap;
       
    44 import java.util.LinkedHashSet;
       
    45 import java.util.List;
       
    46 import java.util.Map;
       
    47 import java.util.Properties;
       
    48 import java.util.ResourceBundle;
       
    49 import java.util.Set;
       
    50 import java.util.Optional;
       
    51 import java.util.Arrays;
       
    52 import java.util.stream.Collectors;
       
    53 import java.util.stream.Stream;
       
    54 import java.util.regex.Matcher;
       
    55 import java.util.spi.ToolProvider;
       
    56 import java.util.jar.JarFile;
       
    57 import java.lang.module.Configuration;
       
    58 import java.lang.module.ResolvedModule;
       
    59 import java.lang.module.ModuleDescriptor;
       
    60 import java.lang.module.ModuleFinder;
       
    61 import java.lang.module.ModuleReference;
       
    62 import jdk.internal.module.ModulePath;
       
    63 
       
    64 
       
    65 final class JLinkBundlerHelper {
       
    66 
       
    67     private static final ResourceBundle I18N = ResourceBundle.getBundle(
       
    68             "jdk.incubator.jpackage.internal.resources.MainResources");
       
    69 
       
    70     static final ToolProvider JLINK_TOOL =
       
    71             ToolProvider.findFirst("jlink").orElseThrow();
       
    72 
       
    73     static File getMainJar(Map<String, ? super Object> params) {
       
    74         File result = null;
       
    75         RelativeFileSet fileset =
       
    76                 StandardBundlerParam.MAIN_JAR.fetchFrom(params);
       
    77 
       
    78         if (fileset != null) {
       
    79             String filename = fileset.getIncludedFiles().iterator().next();
       
    80             result = fileset.getBaseDirectory().toPath().
       
    81                     resolve(filename).toFile();
       
    82 
       
    83             if (result == null || !result.exists()) {
       
    84                 String srcdir =
       
    85                     StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
       
    86 
       
    87                 if (srcdir != null) {
       
    88                     result = new File(srcdir + File.separator + filename);
       
    89                 }
       
    90             }
       
    91         }
       
    92 
       
    93         return result;
       
    94     }
       
    95 
       
    96     static String getMainClassFromModule(Map<String, ? super Object> params) {
       
    97         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
       
    98         if (mainModule != null)  {
       
    99 
       
   100             int index = mainModule.indexOf("/");
       
   101             if (index > 0) {
       
   102                 return mainModule.substring(index + 1);
       
   103             } else {
       
   104                 ModuleDescriptor descriptor =
       
   105                         JLinkBundlerHelper.getMainModuleDescription(params);
       
   106                 if (descriptor != null) {
       
   107                     Optional<String> mainClass = descriptor.mainClass();
       
   108                     if (mainClass.isPresent()) {
       
   109                         Log.verbose(MessageFormat.format(I18N.getString(
       
   110                                     "message.module-class"),
       
   111                                     mainClass.get(),
       
   112                                     JLinkBundlerHelper.getMainModule(params)));
       
   113                         return mainClass.get();
       
   114                     }
       
   115                 }
       
   116             }
       
   117         }
       
   118         return null;
       
   119     }
       
   120 
       
   121     static String getMainModule(Map<String, ? super Object> params) {
       
   122         String result = null;
       
   123         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
       
   124 
       
   125         if (mainModule != null) {
       
   126             int index = mainModule.indexOf("/");
       
   127 
       
   128             if (index > 0) {
       
   129                 result = mainModule.substring(0, index);
       
   130             } else {
       
   131                 result = mainModule;
       
   132             }
       
   133         }
       
   134 
       
   135         return result;
       
   136     }
       
   137 
       
   138     static void execute(Map<String, ? super Object> params,
       
   139             AbstractAppImageBuilder imageBuilder)
       
   140             throws IOException, Exception {
       
   141 
       
   142         List<Path> modulePath =
       
   143                 StandardBundlerParam.MODULE_PATH.fetchFrom(params);
       
   144         Set<String> addModules =
       
   145                 StandardBundlerParam.ADD_MODULES.fetchFrom(params);
       
   146         Set<String> limitModules =
       
   147                 StandardBundlerParam.LIMIT_MODULES.fetchFrom(params);
       
   148         Path outputDir = imageBuilder.getRuntimeRoot();
       
   149         File mainJar = getMainJar(params);
       
   150         ModFile.ModType mainJarType = ModFile.ModType.Unknown;
       
   151 
       
   152         if (mainJar != null) {
       
   153             mainJarType = new ModFile(mainJar).getModType();
       
   154         } else if (StandardBundlerParam.MODULE.fetchFrom(params) == null) {
       
   155             // user specified only main class, all jars will be on the classpath
       
   156             mainJarType = ModFile.ModType.UnnamedJar;
       
   157         }
       
   158 
       
   159         boolean bindServices =
       
   160                 StandardBundlerParam.BIND_SERVICES.fetchFrom(params);
       
   161 
       
   162         // Modules
       
   163         String mainModule = getMainModule(params);
       
   164         if (mainModule == null) {
       
   165             if (mainJarType == ModFile.ModType.UnnamedJar) {
       
   166                 if (addModules.isEmpty()) {
       
   167                     // The default for an unnamed jar is ALL_DEFAULT
       
   168                     addModules.add(ModuleHelper.ALL_DEFAULT);
       
   169                 }
       
   170             } else if (mainJarType == ModFile.ModType.Unknown ||
       
   171                     mainJarType == ModFile.ModType.ModularJar) {
       
   172                 addModules.add(ModuleHelper.ALL_DEFAULT);
       
   173             }
       
   174         }
       
   175 
       
   176         Set<String> modules = new ModuleHelper(
       
   177                 modulePath, addModules, limitModules).modules();
       
   178 
       
   179         if (mainModule != null) {
       
   180             modules.add(mainModule);
       
   181         }
       
   182 
       
   183         runJLink(outputDir, modulePath, modules, limitModules,
       
   184                 new HashMap<String,String>(), bindServices);
       
   185 
       
   186         imageBuilder.prepareApplicationFiles(params);
       
   187     }
       
   188 
       
   189 
       
   190     // Returns the path to the JDK modules in the user defined module path.
       
   191     static Path findPathOfModule( List<Path> modulePath, String moduleName) {
       
   192 
       
   193         for (Path path : modulePath) {
       
   194             Path moduleNamePath = path.resolve(moduleName);
       
   195 
       
   196             if (Files.exists(moduleNamePath)) {
       
   197                 return path;
       
   198             }
       
   199         }
       
   200 
       
   201         return null;
       
   202     }
       
   203 
       
   204     static ModuleDescriptor getMainModuleDescription(Map<String, ? super Object> params) {
       
   205         boolean hasModule = params.containsKey(StandardBundlerParam.MODULE.getID());
       
   206         if (hasModule) {
       
   207             List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params);
       
   208             if (!modulePath.isEmpty()) {
       
   209                 ModuleFinder finder = ModuleFinder.of(modulePath.toArray(new Path[0]));
       
   210                 String mainModule = JLinkBundlerHelper.getMainModule(params);
       
   211                 Optional<ModuleReference> omref = finder.find(mainModule);
       
   212                 if (omref.isPresent()) {
       
   213                     return omref.get().descriptor();
       
   214                 }
       
   215             }
       
   216         }
       
   217 
       
   218         return null;
       
   219     }
       
   220 
       
   221     /*
       
   222      * Returns the set of modules that would be visible by default for
       
   223      * a non-modular-aware application consisting of the given elements.
       
   224      */
       
   225     private static Set<String> getDefaultModules(
       
   226             Collection<Path> paths, Collection<String> addModules) {
       
   227 
       
   228         // the modules in the run-time image that export an API
       
   229         Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream()
       
   230                 .map(ModuleReference::descriptor)
       
   231                 .filter(JLinkBundlerHelper::exportsAPI)
       
   232                 .map(ModuleDescriptor::name);
       
   233 
       
   234         Set<String> roots = Stream.concat(systemRoots,
       
   235                  addModules.stream()).collect(Collectors.toSet());
       
   236 
       
   237         ModuleFinder finder = createModuleFinder(paths);
       
   238 
       
   239         return Configuration.empty()
       
   240                 .resolveAndBind(finder, ModuleFinder.of(), roots)
       
   241                 .modules()
       
   242                 .stream()
       
   243                 .map(ResolvedModule::name)
       
   244                 .collect(Collectors.toSet());
       
   245     }
       
   246 
       
   247     /*
       
   248      * Returns true if the given module exports an API to all module.
       
   249      */
       
   250     private static boolean exportsAPI(ModuleDescriptor descriptor) {
       
   251         return descriptor.exports()
       
   252                 .stream()
       
   253                 .anyMatch(e -> !e.isQualified());
       
   254     }
       
   255 
       
   256     private static ModuleFinder createModuleFinder(Collection<Path> modulePath) {
       
   257         return ModuleFinder.compose(
       
   258                 ModulePath.of(JarFile.runtimeVersion(), true,
       
   259                         modulePath.toArray(Path[]::new)),
       
   260                 ModuleFinder.ofSystem());
       
   261     }
       
   262 
       
   263     private static class ModuleHelper {
       
   264         // The token for "all modules on the module path".
       
   265         private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
       
   266 
       
   267         // The token for "all valid runtime modules".
       
   268         static final String ALL_DEFAULT = "ALL-DEFAULT";
       
   269 
       
   270         private final Set<String> modules = new HashSet<>();
       
   271         ModuleHelper(List<Path> paths, Set<String> addModules,
       
   272                 Set<String> limitModules) {
       
   273             boolean addAllModulePath = false;
       
   274             boolean addDefaultMods = false;
       
   275 
       
   276             for (Iterator<String> iterator = addModules.iterator();
       
   277                     iterator.hasNext();) {
       
   278                 String module = iterator.next();
       
   279 
       
   280                 switch (module) {
       
   281                     case ALL_MODULE_PATH:
       
   282                         iterator.remove();
       
   283                         addAllModulePath = true;
       
   284                         break;
       
   285                     case ALL_DEFAULT:
       
   286                         iterator.remove();
       
   287                         addDefaultMods = true;
       
   288                         break;
       
   289                     default:
       
   290                         this.modules.add(module);
       
   291                 }
       
   292             }
       
   293 
       
   294             if (addAllModulePath) {
       
   295                 this.modules.addAll(getModuleNamesFromPath(paths));
       
   296             } else if (addDefaultMods) {
       
   297                 this.modules.addAll(getDefaultModules(
       
   298                         paths, addModules));
       
   299             }
       
   300         }
       
   301 
       
   302         Set<String> modules() {
       
   303             return modules;
       
   304         }
       
   305 
       
   306         private static Set<String> getModuleNamesFromPath(List<Path> paths) {
       
   307 
       
   308             return createModuleFinder(paths)
       
   309                     .findAll()
       
   310                     .stream()
       
   311                     .map(ModuleReference::descriptor)
       
   312                     .map(ModuleDescriptor::name)
       
   313                     .collect(Collectors.toSet());
       
   314         }
       
   315     }
       
   316 
       
   317     private static void runJLink(Path output, List<Path> modulePath,
       
   318             Set<String> modules, Set<String> limitModules,
       
   319             HashMap<String, String> user, boolean bindServices)
       
   320             throws PackagerException {
       
   321 
       
   322         // This is just to ensure jlink is given a non-existant directory
       
   323         // The passed in output path should be non-existant or empty directory
       
   324         try {
       
   325             IOUtils.deleteRecursive(output.toFile());
       
   326         } catch (IOException ioe) {
       
   327             throw new PackagerException(ioe);
       
   328         }
       
   329 
       
   330         ArrayList<String> args = new ArrayList<String>();
       
   331         args.add("--output");
       
   332         args.add(output.toString());
       
   333         if (modulePath != null && !modulePath.isEmpty()) {
       
   334             args.add("--module-path");
       
   335             args.add(getPathList(modulePath));
       
   336         }
       
   337         if (modules != null && !modules.isEmpty()) {
       
   338             args.add("--add-modules");
       
   339             args.add(getStringList(modules));
       
   340         }
       
   341         if (limitModules != null && !limitModules.isEmpty()) {
       
   342             args.add("--limit-modules");
       
   343             args.add(getStringList(limitModules));
       
   344         }
       
   345         if (user != null && !user.isEmpty()) {
       
   346             for (Map.Entry<String, String> entry : user.entrySet()) {
       
   347                 args.add(entry.getKey());
       
   348                 args.add(entry.getValue());
       
   349             }
       
   350         } else {
       
   351             args.add("--strip-native-commands");
       
   352             args.add("--strip-debug");
       
   353             args.add("--no-man-pages");
       
   354             args.add("--no-header-files");
       
   355             if (bindServices) {
       
   356                 args.add("--bind-services");
       
   357             }
       
   358         }
       
   359 
       
   360         StringWriter writer = new StringWriter();
       
   361         PrintWriter pw = new PrintWriter(writer);
       
   362 
       
   363         Log.verbose("jlink arguments: " + args);
       
   364         int retVal = JLINK_TOOL.run(pw, pw, args.toArray(new String[0]));
       
   365         String jlinkOut = writer.toString();
       
   366 
       
   367         if (retVal != 0) {
       
   368             throw new PackagerException("error.jlink.failed" , jlinkOut);
       
   369         } else if (jlinkOut.length() > 0) {
       
   370             Log.verbose("jlink output: " + jlinkOut);
       
   371         }
       
   372     }
       
   373 
       
   374     private static String getPathList(List<Path> pathList) {
       
   375         String ret = null;
       
   376         for (Path p : pathList) {
       
   377             String s =  Matcher.quoteReplacement(p.toString());
       
   378             if (ret == null) {
       
   379                 ret = s;
       
   380             } else {
       
   381                 ret += File.pathSeparator +  s;
       
   382             }
       
   383         }
       
   384         return ret;
       
   385     }
       
   386 
       
   387     private static String getStringList(Set<String> strings) {
       
   388         String ret = null;
       
   389         for (String s : strings) {
       
   390             if (ret == null) {
       
   391                 ret = s;
       
   392             } else {
       
   393                 ret += "," + s;
       
   394             }
       
   395         }
       
   396         return (ret == null) ? null : Matcher.quoteReplacement(ret);
       
   397     }
       
   398 }