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.Optional;
  33 import java.util.Set;
  34 import java.util.logging.Level;
  35 import java.util.spi.ToolProvider;
  36 import java.util.logging.Logger;
  37 import jdk.internal.org.objectweb.asm.ClassWriter;
  38 import jdk.internal.org.objectweb.asm.ModuleVisitor;
  39 import jdk.internal.org.objectweb.asm.Opcodes;
  40 
  41 // Utility class to generate a .jmod file
  42 public final class JModWriter {
  43     private final Options options;
  44     private final Log log;
  45     private final Writer writer;
  46 
  47     private static ToolProvider findTool(String name) {
  48         Optional<ToolProvider> tp = ToolProvider.findFirst(name);
  49         if (!tp.isPresent()) {
  50             throw new RuntimeException("Cannot find " + name);
  51         }
  52 
  53         return tp.get();
  54     }
  55 
  56     private static final ToolProvider JMOD = findTool("jmod");
  57 
  58     public JModWriter(Context ctx, Writer writer) {
  59         this.options = ctx.options;
  60         this.log = ctx.log;
  61         this.writer = writer;
  62     }
  63 
  64     public void writeJModFile(Path jmodFile, String[] args) throws IOException {
  65         if (options.targetPackage == null || options.targetPackage.isEmpty()) {
  66             throw new IllegalArgumentException("no --target-package specified");
  67         }
  68 
  69         log.print(Level.INFO, () -> "Collecting jmod file " + jmodFile);
  70 
  71         String modName = jmodFile.getFileName().toString();
  72         modName = modName.substring(0, modName.length() - 5 /* ".jmod".length() */);
  73         // FIXME: validate modName
  74 
  75         Path jmodRootDir = Files.createTempDirectory("jextract.jmod");
  76         jmodRootDir.toFile().deleteOnExit();
  77 
  78         log.print(Level.INFO, () -> "Writing .class files");
  79         // write .class files
  80         Path modClassesDir = jmodRootDir.resolve(modName);
  81         writer.writeClassFiles(modClassesDir, args);
  82 
  83         log.print(Level.INFO, () -> "Generating module-info.class");
  84         // generate module-info.class
  85         generateModuleInfoClass(modClassesDir, modName);
  86 
  87         // copy libraries
  88         Path libsDir = jmodRootDir.resolve("libs");
  89         copyNativeLibraries(libsDir);
  90 
  91         // generate .jmod file
  92         generateJMod(modClassesDir, libsDir, jmodFile);
  93     }
  94 
  95     private void generateModuleInfoClass(Path modClassesDir, String modName) throws IOException {
  96         // collect package names
  97         final Set<String> packages = new HashSet<>();
  98         for (String cls : writer.results().keySet()) {
  99             int idx = cls.lastIndexOf("/");
 100             packages.add(cls.substring(0, idx));
 101         }
 102 
 103         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 104         cw.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null);
 105 
 106         ModuleVisitor mv = cw.visitModule(modName, Opcodes.ACC_MANDATED, null);
 107         mv.visitRequire("java.base", Opcodes.ACC_MANDATED, null);
 108         for (String pkg : packages) {
 109             mv.visitExport(pkg, Opcodes.ACC_MANDATED);
 110         }
 111         mv.visitEnd();
 112 
 113         cw.visitEnd();
 114 
 115         // write module-info.class source in module directory
 116         Files.write(modClassesDir.resolve("module-info.class"), cw.toByteArray());
 117     }
 118 
 119     private void copyNativeLibraries(Path libsDir) throws IOException {
 120         Files.createDirectory(libsDir);
 121         if (!options.libraryNames.isEmpty()) {
 122             if (options.libraryPaths.isEmpty()) {
 123                 log.printWarning("warn.no.library.paths.specified");
 124                 return;
 125             }
 126             log.print(Level.INFO, () -> "Copying native libraries");
 127             Path[] paths = options.libraryPaths.stream().map(Paths::get).toArray(Path[]::new);
 128             options.libraryNames.forEach(libName -> {
 129                 Optional<Path> absPath = Utils.findLibraryPath(paths, libName);
 130                 if (absPath.isPresent()) {
 131                     Path libPath = absPath.get();
 132                     try {
 133                         Files.copy(absPath.get(), libsDir.resolve(libPath.getFileName()));
 134                     } catch (IOException ioExp) {
 135                         throw new UncheckedIOException(ioExp);
 136                     }
 137                 } else {
 138                     log.printWarning("warn.library.not.copied", libName);
 139                 }
 140             });
 141         }
 142     }
 143 
 144     private void generateJMod(Path classesDir, Path libsDir, Path jmodFile)
 145             throws IOException {
 146         log.print(Level.INFO, () -> "Generating jmod file: " + jmodFile);
 147         int exitCode = JMOD.run(log.getOut(), log.getErr(), "create",
 148             "--class-path", classesDir.toString(),
 149             "--libs", libsDir.toString(),
 150             jmodFile.toString());
 151 
 152         if (exitCode != 0) {
 153             throw new RuntimeException("jmod generation failed: " + exitCode);
 154         }
 155     }
 156 }