1 /*
   2  *  Copyright (c) 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.
   8  *
   9  *  This code is distributed in the hope that it will be useful, but WITHOUT
  10  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  *  version 2 for more details (a copy is included in the LICENSE file that
  13  *  accompanied this code).
  14  *
  15  *  You should have received a copy of the GNU General Public License version
  16  *  2 along with this work; if not, write to the Free Software Foundation,
  17  *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  *  Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  *  or visit www.oracle.com if you need additional information or have any
  21  *  questions.
  22  */
  23 
  24 package com.sun.tools.jextract;
  25 
  26 import java.io.IOException;
  27 import java.io.UncheckedIOException;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.util.HashSet;
  32 import java.util.List;
  33 import java.util.Optional;
  34 import java.util.Set;
  35 import java.util.logging.Level;
  36 import java.util.spi.ToolProvider;
  37 import java.util.logging.Logger;
  38 
  39 // Utility class to generate a .jmod file
  40 public final class JModWriter {
  41     private final Context ctx;
  42     private final Writer writer;
  43 
  44     private static ToolProvider findTool(String name) {
  45         Optional<ToolProvider> tp = ToolProvider.findFirst(name);
  46         if (!tp.isPresent()) {
  47             throw new RuntimeException("Cannot find " + name);
  48         }
  49 
  50         return tp.get();
  51     }
  52 
  53     private static final ToolProvider JAVAC = findTool("javac");
  54     private static final ToolProvider JMOD = findTool("jmod");
  55 
  56     public JModWriter(Context ctx, Writer writer) {
  57         this.ctx = ctx;
  58         this.writer = writer;
  59     }
  60 
  61     public void writeJModFile(Path jmodFile, String[] args) throws IOException {
  62         if (ctx.options.targetPackage == null || ctx.options.targetPackage.isEmpty()) {
  63             throw new IllegalArgumentException("no --target-package specified");
  64         }
  65 
  66         ctx.log.print(Level.INFO, () -> "Collecting jmod file " + jmodFile);
  67 
  68         String modName = jmodFile.getFileName().toString();
  69         modName = modName.substring(0, modName.length() - 5 /* ".jmod".length() */);
  70         // FIXME: validate modName
  71 
  72         Path jmodRootDir = Files.createTempDirectory("jextract.jmod");
  73         jmodRootDir.toFile().deleteOnExit();
  74 
  75         ctx.log.print(Level.INFO, () -> "Writing .class files");
  76         // write .class files
  77         Path modClassesDir = jmodRootDir.resolve(modName);
  78         writer.writeClassFiles(modClassesDir, args);
  79 
  80         ctx.log.print(Level.INFO, () -> "Generating module-info.class");
  81         // generate module-info.class
  82         generateModuleInfoClass(jmodRootDir, modClassesDir, modName);
  83 
  84         // copy libraries
  85         Path libsDir = jmodRootDir.resolve("libs");
  86         copyNativeLibraries(libsDir);
  87 
  88         // generate .jmod file
  89         generateJMod(modClassesDir, libsDir, jmodFile);
  90     }
  91 
  92     private void generateModuleInfoClass(Path jmodRootDir, Path modClassesDir, String modName) throws IOException {
  93         // collect package names
  94         final Set<String> packages = new HashSet<>();
  95         for (String cls : writer.results().keySet()) {
  96             int idx = cls.lastIndexOf(".");
  97             packages.add(cls.substring(0, idx));
  98         }
  99 
 100         // module-info.java source code string
 101         StringBuilder modInfoCode = new StringBuilder();
 102         modInfoCode.append("module ");
 103         modInfoCode.append(modName);
 104         modInfoCode.append(" {\n");
 105         for (String pkg : packages) {
 106             modInfoCode.append("    exports ");
 107             modInfoCode.append(pkg);
 108             modInfoCode.append(";\n");
 109         }
 110         modInfoCode.append("}");
 111 
 112         // write module-info.java source in module directory
 113         Files.write(modClassesDir.resolve("module-info.java"), List.of(modInfoCode.toString()));
 114 
 115         // compile module-info.java
 116         int exitCode = JAVAC.run(ctx.log.getOut(), ctx.log.getErr(),
 117             "--module-source-path", jmodRootDir.toString(),
 118             "-d", jmodRootDir.toString(),
 119             modClassesDir.resolve("module-info.java").toString());
 120 
 121         if (exitCode != 0) {
 122             throw new RuntimeException("module-info.class generation failed: " + exitCode);
 123         }
 124     }
 125 
 126     private void copyNativeLibraries(Path libsDir) throws IOException {
 127         Files.createDirectory(libsDir);
 128         if (!ctx.options.libraryNames.isEmpty()) {
 129             if (ctx.options.libraryPaths.isEmpty()) {
 130                 ctx.log.printWarning("warn.no.library.paths.specified");
 131                 return;
 132             }
 133             ctx.log.print(Level.INFO, () -> "Copying native libraries");
 134             Path[] paths = ctx.options.libraryPaths.stream().map(Paths::get).toArray(Path[]::new);
 135             ctx.options.libraryNames.forEach(libName -> {
 136                 Optional<Path> absPath = Utils.findLibraryPath(paths, libName);
 137                 if (absPath.isPresent()) {
 138                     Path libPath = absPath.get();
 139                     try {
 140                         Files.copy(absPath.get(), libsDir.resolve(libPath.getFileName()));
 141                     } catch (IOException ioExp) {
 142                         throw new UncheckedIOException(ioExp);
 143                     }
 144                 } else {
 145                     ctx.log.printWarning("warn.library.not.copied", libName);
 146                 }
 147             });
 148         }
 149     }
 150 
 151     private void generateJMod(Path classesDir, Path libsDir, Path jmodFile)
 152             throws IOException {
 153         ctx.log.print(Level.INFO, () -> "Generating jmod file: " + jmodFile);
 154         int exitCode = JMOD.run(ctx.log.getOut(), ctx.log.getErr(), "create",
 155             "--class-path", classesDir.toString(),
 156             "--libs", libsDir.toString(),
 157             jmodFile.toString());
 158 
 159         if (exitCode != 0) {
 160             throw new RuntimeException("jmod generation failed: " + exitCode);
 161         }
 162     }
 163 }