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 }