Path: blob/master/test/jdk/tools/jlink/plugins/StripNativeDebugSymbolsPlugin/StripNativeDebugSymbolsPluginTest.java
41155 views
/*1* Copyright (c) 2019, Red Hat, Inc.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24import java.io.BufferedWriter;25import java.io.File;26import java.io.IOException;27import java.io.InputStream;28import java.io.PrintWriter;29import java.io.StringWriter;30import java.nio.file.FileVisitResult;31import java.nio.file.Files;32import java.nio.file.NoSuchFileException;33import java.nio.file.Path;34import java.nio.file.Paths;35import java.nio.file.SimpleFileVisitor;36import java.nio.file.attribute.BasicFileAttributes;37import java.util.ArrayList;38import java.util.Arrays;39import java.util.List;40import java.util.Map;41import java.util.Scanner;42import java.util.spi.ToolProvider;43import java.util.stream.Collectors;44import java.util.stream.Stream;4546import jdk.test.lib.compiler.CompilerUtils;47import jdk.tools.jlink.internal.ResourcePoolManager;48import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin;49import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin.ObjCopyCmdBuilder;50import jdk.tools.jlink.plugin.ResourcePool;51import jdk.tools.jlink.plugin.ResourcePoolEntry;5253/*54* @test55* @requires os.family == "linux"56* @bug 821479657* @summary Test --strip-native-debug-symbols plugin58* @library /test/lib59* @modules jdk.compiler60* jdk.jlink/jdk.tools.jlink.internal.plugins61* jdk.jlink/jdk.tools.jlink.internal62* jdk.jlink/jdk.tools.jlink.plugin63* @build jdk.test.lib.compiler.CompilerUtils FakeObjCopy64* @run main/othervm -Xmx1g StripNativeDebugSymbolsPluginTest65*/66public class StripNativeDebugSymbolsPluginTest {6768private static final String OBJCOPY = "objcopy";69private static final String DEFAULT_OBJCOPY_CMD = OBJCOPY;70private static final String PLUGIN_NAME = "strip-native-debug-symbols";71private static final String MODULE_NAME_WITH_NATIVE = "fib";72private static final String JAVA_HOME = System.getProperty("java.home");73private static final String NATIVE_LIB_NAME = "libFib.so";74private static final Path JAVA_LIB_PATH = Paths.get(System.getProperty("java.library.path"));75private static final Path LIB_FIB_SRC = JAVA_LIB_PATH.resolve(NATIVE_LIB_NAME);76private static final String FIBJNI_CLASS_NAME = "FibJNI.java";77private static final Path JAVA_SRC_DIR = Paths.get(System.getProperty("test.src"))78.resolve("src")79.resolve(MODULE_NAME_WITH_NATIVE);80private static final Path FIBJNI_JAVA_CLASS = JAVA_SRC_DIR.resolve(FIBJNI_CLASS_NAME);81private static final String DEBUG_EXTENSION = "debug";82private static final long ORIG_LIB_FIB_SIZE = LIB_FIB_SRC.toFile().length();83private static final String FAKE_OBJ_COPY_LOG_FILE = "objcopy.log";84private static final String OBJCOPY_ONLY_DEBUG_SYMS_OPT = "-g";85private static final String OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT = "--only-keep-debug";86private static final String OBJCOPY_ADD_DEBUG_LINK_OPT = "--add-gnu-debuglink";8788///////////////////////////////////////////////////////////////////////////89//90// Tests which do NOT rely on objcopy being present on the test system91//92///////////////////////////////////////////////////////////////////////////9394public void testPluginLoaded() {95List<String> output =96JLink.run("--list-plugins").output();97if (output.stream().anyMatch(s -> s.contains(PLUGIN_NAME))) {98System.out.println("DEBUG: " + PLUGIN_NAME + " plugin loaded as expected.");99} else {100throw new AssertionError("strip-native-debug-symbols plugin not in " +101"--list-plugins output.");102}103}104105public void testConfigureFakeObjCopy() throws Exception {106configureConflictingOptions();107configureObjcopyWithOmit();108configureObjcopyWithKeep();109configureUnknownOptions();110configureMultipleTimesSamePlugin();111System.out.println("Test testConfigureFakeObjCopy() PASSED!");112}113114private void configureMultipleTimesSamePlugin() throws Exception {115Map<String, String> keepDebug = Map.of(116StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files"117);118Map<String, String> excludeDebug = Map.of(119StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"120);121StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(keepDebug);122try {123plugin.doConfigure(false, excludeDebug);124throw new AssertionError("should have thrown IAE for broken config: " +125keepDebug + " and " + excludeDebug);126} catch (IllegalArgumentException e) {127// pass128System.out.println("DEBUG: test threw IAE " + e.getMessage() +129" as expected.");130}131}132133private void configureUnknownOptions() throws Exception {134Map<String, String> config = Map.of(135StripNativeDebugSymbolsPlugin.NAME, "foobar"136);137doConfigureUnknownOption(config);138config = Map.of(139StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",140"foo", "bar" // unknown value141);142doConfigureUnknownOption(config);143}144145private void doConfigureUnknownOption(Map<String, String> config) throws Exception {146try {147createAndConfigPlugin(config);148throw new AssertionError("should have thrown IAE for broken config: " + config);149} catch (IllegalArgumentException e) {150// pass151System.out.println("DEBUG: test threw IAE " + e.getMessage() +152" as expected.");153}154}155156private void configureObjcopyWithKeep() throws Exception {157String objcopyPath = "foobar";158String debugExt = "debuginfo"; // that's the default value159Map<String, String> config = Map.of(160StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",161"objcopy", objcopyPath162);163doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);164// Do it again combining options the other way round165debugExt = "testme";166config = Map.of(167StripNativeDebugSymbolsPlugin.NAME, "objcopy=" + objcopyPath,168"keep-debuginfo-files", debugExt169);170doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);171System.out.println("DEBUG: configureObjcopyWithKeep() PASSED!");172}173174private void configureObjcopyWithOmit() throws Exception {175String objcopyPath = "something-non-standard";176Map<String, String> config = Map.of(177StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",178"objcopy", objcopyPath179);180doOmitDebugInfoFakeObjCopyTest(config, objcopyPath);181System.out.println("DEBUG: configureObjcopyWithOmit() PASSED!");182}183184private void configureConflictingOptions() throws Exception {185Map<String, String> config = Map.of(186StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",187"keep-debuginfo-files", "foo-ext"188);189doConfigureConflictingOptions(config);190config = Map.of(191StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files=bar",192"keep-debuginfo-files", "foo-ext"193);194doConfigureConflictingOptions(config);195}196197private void doConfigureConflictingOptions(Map<String, String> config) throws Exception {198try {199createAndConfigPlugin(config);200throw new AssertionError("keep-debuginfo-files and exclude-debuginfo-files " +201" should have conflicted!");202} catch (IllegalArgumentException e) {203// pass204if (e.getMessage().contains("keep-debuginfo-files") &&205e.getMessage().contains("exclude-debuginfo-files")) {206System.out.println("DEBUG: test threw IAE " + e.getMessage() +207" as expected.");208} else {209throw new AssertionError("Unexpected IAE", e);210}211}212}213214public void testTransformFakeObjCopyNoDebugInfoFiles() throws Exception {215Map<String, String> defaultConfig = Map.of(216StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"217);218doOmitDebugInfoFakeObjCopyTest(defaultConfig, DEFAULT_OBJCOPY_CMD);219System.out.println("testTransformFakeObjCopyNoDebugInfoFiles() PASSED!");220}221222private void doOmitDebugInfoFakeObjCopyTest(Map<String, String> config,223String expectedObjCopy) throws Exception {224StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);225String binFile = "mybin";226String path = "/fib/bin/" + binFile;227ResourcePoolEntry debugEntry = createMockEntry(path,228ResourcePoolEntry.Type.NATIVE_CMD);229ResourcePoolManager inResources = new ResourcePoolManager();230ResourcePoolManager outResources = new ResourcePoolManager();231inResources.add(debugEntry);232ResourcePool output = plugin.transform(233inResources.resourcePool(),234outResources.resourcePoolBuilder());235// expect entry to be present236if (output.findEntry(path).isPresent()) {237System.out.println("DEBUG: File " + path + " present as exptected.");238} else {239throw new AssertionError("Test failed. Binary " + path +240" not present after stripping!");241}242verifyFakeObjCopyCalled(binFile);243}244245public void testTransformFakeObjCopyKeepDebugInfoFiles() throws Exception {246Map<String, String> defaultConfig = Map.of(247StripNativeDebugSymbolsPlugin.NAME,248"keep-debuginfo-files=" + DEBUG_EXTENSION249);250doKeepDebugInfoFakeObjCopyTest(defaultConfig,251DEBUG_EXTENSION,252DEFAULT_OBJCOPY_CMD);253System.out.println("testTransformFakeObjCopyKeepDebugInfoFiles() PASSED!");254}255256private void doKeepDebugInfoFakeObjCopyTest(Map<String, String> config,257String debugExt,258String expectedObjCopy) throws Exception {259StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);260String sharedLib = "myLib.so";261String path = "/fib/lib/" + sharedLib;262ResourcePoolEntry debugEntry = createMockEntry(path,263ResourcePoolEntry.Type.NATIVE_LIB);264ResourcePoolManager inResources = new ResourcePoolManager();265ResourcePoolManager outResources = new ResourcePoolManager();266inResources.add(debugEntry);267ResourcePool output = plugin.transform(268inResources.resourcePool(),269outResources.resourcePoolBuilder());270// expect entry + debug info entry to be present271String debugPath = path + "." + debugExt;272if (output.findEntry(path).isPresent() &&273output.findEntry(debugPath).isPresent()) {274System.out.println("DEBUG: Files " + path + "{,." + debugExt +275"} present as exptected.");276} else {277throw new AssertionError("Test failed. Binary files " + path +278"{,." + debugExt +"} not present after " +279"stripping!");280}281verifyFakeObjCopyCalledMultiple(sharedLib, debugExt);282}283284///////////////////////////////////////////////////////////////////////////285//286// Tests which DO rely on objcopy being present on the test system.287// Skipped otherwise.288//289///////////////////////////////////////////////////////////////////////////290291public void testStripNativeLibraryDefaults() throws Exception {292if (!hasJmods()) return;293294Path libFibJmod = createLibFibJmod();295296Path imageDir = Paths.get("stripped-native-libs");297JLink.run("--output", imageDir.toString(),298"--verbose",299"--module-path", modulePathWith(libFibJmod),300"--add-modules", MODULE_NAME_WITH_NATIVE,301"--strip-native-debug-symbols=exclude-debuginfo-files").output();302Path libDir = imageDir.resolve("lib");303Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);304long postStripSize = postStripLib.toFile().length();305306if (postStripSize == 0) {307throw new AssertionError("Lib file size 0. Test error?!");308}309// Heuristic: libLib.so is smaller post debug info stripping310if (postStripSize >= ORIG_LIB_FIB_SIZE) {311throw new AssertionError("Expected native library stripping to " +312"reduce file size. Expected < " +313ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);314} else {315System.out.println("DEBUG: File size of " + postStripLib.toString() +316" " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );317}318verifyFibModule(imageDir); // Sanity check fib module which got libFib.so stripped319System.out.println("DEBUG: testStripNativeLibraryDefaults() PASSED!");320}321322public void testOptionsInvalidObjcopy() throws Exception {323if (!hasJmods()) return;324325Path libFibJmod = createLibFibJmod();326327String notExists = "/do/not/exist/objcopy";328329Path imageDir = Paths.get("invalid-objcopy-command");330String[] jlinkCmdArray = new String[] {331JAVA_HOME + File.separator + "bin" + File.separator + "jlink",332"--output", imageDir.toString(),333"--verbose",334"--module-path", modulePathWith(libFibJmod),335"--add-modules", MODULE_NAME_WITH_NATIVE,336"--strip-native-debug-symbols", "objcopy=" + notExists,337};338List<String> jlinkCmd = Arrays.asList(jlinkCmdArray);339System.out.println("Debug: command: " + jlinkCmd.stream().collect(340Collectors.joining(" ")));341ProcessBuilder builder = new ProcessBuilder(jlinkCmd);342Process p = builder.start();343int status = p.waitFor();344if (status == 0) {345throw new AssertionError("Expected jlink to fail!");346} else {347verifyInvalidObjcopyError(p.getInputStream(), notExists);348System.out.println("DEBUG: testOptionsInvalidObjcopy() PASSED!");349}350}351352public void testStripNativeLibsDebugSymsIncluded() throws Exception {353if (!hasJmods()) return;354355Path libFibJmod = createLibFibJmod();356357Path imageDir = Paths.get("stripped-native-libs-with-debug");358JLink.run("--output", imageDir.toString(),359"--verbose",360"--module-path", modulePathWith(libFibJmod),361"--add-modules", MODULE_NAME_WITH_NATIVE,362"--strip-native-debug-symbols",363"keep-debuginfo-files=" + DEBUG_EXTENSION);364365Path libDir = imageDir.resolve("lib");366Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);367long postStripSize = postStripLib.toFile().length();368369if (postStripSize == 0) {370throw new AssertionError("Lib file size 0. Test error?!");371}372// Heuristic: libLib.so is smaller post debug info stripping373if (postStripSize >= ORIG_LIB_FIB_SIZE) {374throw new AssertionError("Expected native library stripping to " +375"reduce file size. Expected < " +376ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);377} else {378System.out.println("DEBUG: File size of " + postStripLib.toString() +379" " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );380}381// stripped with option to preserve debug symbols file382verifyDebugInfoSymbolFilePresent(imageDir);383System.out.println("DEBUG: testStripNativeLibsDebugSymsIncluded() PASSED!");384}385386private void verifyFakeObjCopyCalledMultiple(String expectedFile,387String dbgExt) throws Exception {388// transform of the StripNativeDebugSymbolsPlugin created objcopy.log389// with our stubbed FakeObjCopy. See FakeObjCopy.java390List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));391if (allLines.size() != 3) {392throw new AssertionError("Expected 3 calls to objcopy");393}394// 3 calls to objcopy are as follows:395// 1. Only keep debug symbols396// 2. Strip debug symbols397// 3. Add debug link to stripped file398String onlyKeepDebug = allLines.get(0);399String stripSymbolsLine = allLines.get(1);400String addGnuDebugLink = allLines.get(2);401System.out.println("DEBUG: Inspecting fake objcopy calls: " + allLines);402boolean passed = stripSymbolsLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);403passed &= stripSymbolsLine.endsWith(expectedFile);404String[] tokens = onlyKeepDebug.split("\\s");405passed &= tokens[0].equals(OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT);406passed &= tokens[1].endsWith(expectedFile);407passed &= tokens[2].endsWith(expectedFile + "." + dbgExt);408tokens = addGnuDebugLink.split("\\s");409String[] addDbgTokens = tokens[0].split("=");410passed &= addDbgTokens[1].equals(expectedFile + "." + dbgExt);411passed &= addDbgTokens[0].equals(OBJCOPY_ADD_DEBUG_LINK_OPT);412passed &= tokens[1].endsWith(expectedFile);413if (!passed) {414throw new AssertionError("Test failed! objcopy not properly called " +415"with expected options!");416}417}418419private void verifyFakeObjCopyCalled(String expectedFile) throws Exception {420// transform of the StripNativeDebugSymbolsPlugin created objcopy.log421// with our stubbed FakeObjCopy. See FakeObjCopy.java422List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));423if (allLines.size() != 1) {424throw new AssertionError("Expected 1 call to objcopy only");425}426String optionLine = allLines.get(0);427System.out.println("DEBUG: Inspecting fake objcopy arguments: " + optionLine);428boolean passed = optionLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);429passed &= optionLine.endsWith(expectedFile);430if (!passed) {431throw new AssertionError("Test failed! objcopy not called with " +432"expected options!");433}434}435436private ResourcePoolEntry createMockEntry(String path,437ResourcePoolEntry.Type type) {438byte[] mockContent = new byte[] { 0, 1, 2, 3 };439ResourcePoolEntry entry = ResourcePoolEntry.create(440path,441type,442mockContent);443return entry;444}445446private StripNativeDebugSymbolsPlugin createAndConfigPlugin(447Map<String, String> config,448String expectedObjcopy)449throws IOException {450TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder(expectedObjcopy);451return createAndConfigPlugin(config, cmdBuilder);452}453454private StripNativeDebugSymbolsPlugin createAndConfigPlugin(455Map<String, String> config) throws IOException {456TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder();457return createAndConfigPlugin(config, cmdBuilder);458}459460private StripNativeDebugSymbolsPlugin createAndConfigPlugin(461Map<String, String> config,462TestObjCopyCmdBuilder builder) throws IOException {463StripNativeDebugSymbolsPlugin plugin =464new StripNativeDebugSymbolsPlugin(builder);465plugin.doConfigure(false, config);466return plugin;467}468469// Create the jmod with the native library470private Path createLibFibJmod() throws IOException {471JmodFileBuilder jmodBuilder = new JmodFileBuilder(MODULE_NAME_WITH_NATIVE);472jmodBuilder.javaClass(FIBJNI_JAVA_CLASS);473jmodBuilder.nativeLib(LIB_FIB_SRC);474return jmodBuilder.build();475}476477private String modulePathWith(Path jmod) {478return Paths.get(JAVA_HOME, "jmods").toString() +479File.pathSeparator + jmod.getParent().toString();480}481482private boolean hasJmods() {483if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {484System.err.println("Test skipped. NO jmods directory");485return false;486}487return true;488}489490private void verifyInvalidObjcopyError(InputStream errInput, String match) {491boolean foundMatch = false;492try (Scanner scanner = new Scanner(errInput)) {493while (scanner.hasNextLine()) {494String line = scanner.nextLine();495System.out.println("DEBUG: >>>> " + line);496if (line.contains(match)) {497foundMatch = true;498break;499}500}501}502if (!foundMatch) {503throw new AssertionError("Expected to find " + match +504" in error stream.");505} else {506System.out.println("DEBUG: Found string " + match + " as expected.");507}508}509510private void verifyDebugInfoSymbolFilePresent(Path image)511throws IOException, InterruptedException {512Path debugSymsFile = image.resolve("lib/libFib.so.debug");513if (!Files.exists(debugSymsFile)) {514throw new AssertionError("Expected stripped debug info file " +515debugSymsFile.toString() + " to exist.");516}517long debugSymsSize = debugSymsFile.toFile().length();518if (debugSymsSize <= 0) {519throw new AssertionError("sanity check for fib.FibJNI failed " +520"post-stripping!");521} else {522System.out.println("DEBUG: Debug symbols stripped from libFib.so " +523"present (" + debugSymsFile.toString() + ") as expected.");524}525}526527private void verifyFibModule(Path image)528throws IOException, InterruptedException {529System.out.println("DEBUG: sanity checking fib module...");530Path launcher = image.resolve("bin/java");531List<String> args = new ArrayList<>();532args.add(launcher.toString());533args.add("--add-modules");534args.add(MODULE_NAME_WITH_NATIVE);535args.add("fib.FibJNI");536args.add("7");537args.add("13"); // fib(7) == 13538System.out.println("DEBUG: [command] " +539args.stream().collect(Collectors.joining(" ")));540Process proc = new ProcessBuilder(args).inheritIO().start();541int status = proc.waitFor();542if (status == 0) {543System.out.println("DEBUG: sanity checking fib module... PASSED!");544} else {545throw new AssertionError("sanity check for fib.FibJNI failed post-" +546"stripping!");547}548}549550private static boolean isObjcopyPresent() throws Exception {551String[] objcopyVersion = new String[] {552OBJCOPY, "--version",553};554List<String> command = Arrays.asList(objcopyVersion);555try {556ProcessBuilder builder = new ProcessBuilder(command);557builder.inheritIO();558Process p = builder.start();559int status = p.waitFor();560if (status != 0) {561System.out.println("Debug: objcopy binary doesn't seem to be " +562"present or functional.");563return false;564}565} catch (IOException e) {566System.out.println("Debug: objcopy binary doesn't seem to be present " +567"or functional.");568return false;569}570return true;571}572573public static void main(String[] args) throws Exception {574StripNativeDebugSymbolsPluginTest test = new StripNativeDebugSymbolsPluginTest();575if (isObjcopyPresent()) {576test.testStripNativeLibraryDefaults();577test.testStripNativeLibsDebugSymsIncluded();578test.testOptionsInvalidObjcopy();579} else {580System.out.println("DEBUG: objcopy binary not available. " +581"Running reduced set of tests.");582}583test.testTransformFakeObjCopyNoDebugInfoFiles();584test.testTransformFakeObjCopyKeepDebugInfoFiles();585test.testConfigureFakeObjCopy();586test.testPluginLoaded();587}588589static class JLink {590static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")591.orElseThrow(() ->592new RuntimeException("jlink tool not found")593);594595static JLink run(String... options) {596JLink jlink = new JLink();597if (jlink.execute(options) != 0) {598throw new AssertionError("Jlink expected to exit with 0 return code");599}600return jlink;601}602603final List<String> output = new ArrayList<>();604private int execute(String... options) {605System.out.println("jlink " +606Stream.of(options).collect(Collectors.joining(" ")));607608StringWriter writer = new StringWriter();609PrintWriter pw = new PrintWriter(writer);610int rc = JLINK_TOOL.run(pw, pw, options);611System.out.println(writer.toString());612Stream.of(writer.toString().split("\\v"))613.map(String::trim)614.forEach(output::add);615return rc;616}617618boolean contains(String s) {619return output.contains(s);620}621622List<String> output() {623return output;624}625}626627/**628* Builder to create JMOD file629*/630private static class JmodFileBuilder {631632private static final ToolProvider JMOD_TOOL = ToolProvider633.findFirst("jmod")634.orElseThrow(() ->635new RuntimeException("jmod tool not found")636);637private static final Path SRC_DIR = Paths.get("src");638private static final Path MODS_DIR = Paths.get("mod");639private static final Path JMODS_DIR = Paths.get("jmods");640private static final Path LIBS_DIR = Paths.get("libs");641642private final String name;643private final List<Path> nativeLibs = new ArrayList<>();644private final List<Path> javaClasses = new ArrayList<>();645646private JmodFileBuilder(String name) throws IOException {647this.name = name;648649deleteDirectory(MODS_DIR);650deleteDirectory(SRC_DIR);651deleteDirectory(LIBS_DIR);652deleteDirectory(JMODS_DIR);653Path msrc = SRC_DIR.resolve(name);654if (Files.exists(msrc)) {655deleteDirectory(msrc);656}657}658659JmodFileBuilder nativeLib(Path libFileSrc) {660nativeLibs.add(libFileSrc);661return this;662}663664JmodFileBuilder javaClass(Path srcPath) {665javaClasses.add(srcPath);666return this;667}668669Path build() throws IOException {670compileModule();671return createJmodFile();672}673674private void compileModule() throws IOException {675Path msrc = SRC_DIR.resolve(name);676Files.createDirectories(msrc);677// copy class using native lib to expected path678if (javaClasses.size() > 0) {679for (Path srcPath: javaClasses) {680Path targetPath = msrc.resolve(srcPath.getFileName());681Files.copy(srcPath, targetPath);682}683}684// generate module-info file.685Path minfo = msrc.resolve("module-info.java");686try (BufferedWriter bw = Files.newBufferedWriter(minfo);687PrintWriter writer = new PrintWriter(bw)) {688writer.format("module %s { }%n", name);689}690691if (!CompilerUtils.compile(msrc, MODS_DIR,692"--module-source-path",693SRC_DIR.toString())) {694695}696}697698private Path createJmodFile() throws IOException {699Path mclasses = MODS_DIR.resolve(name);700Files.createDirectories(JMODS_DIR);701Path outfile = JMODS_DIR.resolve(name + ".jmod");702List<String> args = new ArrayList<>();703args.add("create");704// add classes705args.add("--class-path");706args.add(mclasses.toString());707// native libs708if (nativeLibs.size() > 0) {709// Copy the JNI library to the expected path710Files.createDirectories(LIBS_DIR);711for (Path srcLib: nativeLibs) {712Path targetLib = LIBS_DIR.resolve(srcLib.getFileName());713Files.copy(srcLib, targetLib);714}715args.add("--libs");716args.add(LIBS_DIR.toString());717}718args.add(outfile.toString());719720if (Files.exists(outfile)) {721Files.delete(outfile);722}723724System.out.println("jmod " +725args.stream().collect(Collectors.joining(" ")));726727int rc = JMOD_TOOL.run(System.out, System.out,728args.toArray(new String[args.size()]));729if (rc != 0) {730throw new AssertionError("jmod failed: rc = " + rc);731}732return outfile;733}734735private static void deleteDirectory(Path dir) throws IOException {736try {737Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {738@Override739public FileVisitResult visitFile(Path file,740BasicFileAttributes attrs)741throws IOException742{743Files.delete(file);744return FileVisitResult.CONTINUE;745}746747@Override748public FileVisitResult postVisitDirectory(Path dir,749IOException exc)750throws IOException751{752Files.delete(dir);753return FileVisitResult.CONTINUE;754}755});756} catch (NoSuchFileException e) {757// ignore non-existing files758}759}760}761762private static class TestObjCopyCmdBuilder implements ObjCopyCmdBuilder {763764private final String expectedObjCopy;765private final String logFile;766767TestObjCopyCmdBuilder() {768this(DEFAULT_OBJCOPY_CMD);769}770TestObjCopyCmdBuilder(String exptectedObjCopy) {771Path logFilePath = Paths.get(FAKE_OBJ_COPY_LOG_FILE);772try {773Files.deleteIfExists(logFilePath);774} catch (Exception e) {775e.printStackTrace();776}777this.logFile = logFilePath.toFile().getAbsolutePath();778this.expectedObjCopy = exptectedObjCopy;779}780781@Override782public List<String> build(String objCopy, String... options) {783if (!expectedObjCopy.equals(objCopy)) {784throw new AssertionError("Expected objcopy to be '" +785expectedObjCopy + "' but was '" +786objCopy);787}788List<String> fakeObjCopy = new ArrayList<>();789fakeObjCopy.add(JAVA_HOME + File.separator + "bin" + File.separator + "java");790fakeObjCopy.add("-cp");791fakeObjCopy.add(System.getProperty("test.classes"));792fakeObjCopy.add("FakeObjCopy");793// Note that adding the gnu debug link changes the PWD of the794// java process calling FakeObjCopy. As such we need to pass in the795// log file path this way. Relative paths won't work as it would be796// relative to the temporary directory which gets deleted post797// adding the debug link798fakeObjCopy.add(logFile);799if (options.length > 0) {800fakeObjCopy.addAll(Arrays.asList(options));801}802return fakeObjCopy;803}804805}806}807808809