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 }