8304006: jlink should create the jimage file in the native endian for the target platform

Co-authored-by: Mandy Chung <mchung@openjdk.org>
Reviewed-by: alanb, mchung, rriggs
This commit is contained in:
Jaikiran Pai 2023-07-16 07:11:39 +00:00
parent e8f66bf88c
commit 81c4e8f916
9 changed files with 247 additions and 64 deletions

View File

@ -64,7 +64,6 @@ import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
import jdk.tools.jlink.plugin.ResourcePoolModule;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
@ -145,16 +144,21 @@ public final class DefaultImageBuilder implements ImageBuilder {
private final Map<String, String> launchers;
private final Path mdir;
private final Set<String> modules = new HashSet<>();
private Platform platform;
private final Platform platform;
/**
* Default image builder constructor.
*
* @param root The image root directory.
* @param launchers mapping of launcher command name to their module/main class
* @param targetPlatform target platform of the image
* @throws IOException
* @throws NullPointerException If any of the params is null
*/
public DefaultImageBuilder(Path root, Map<String, String> launchers) throws IOException {
public DefaultImageBuilder(Path root, Map<String, String> launchers, Platform targetPlatform)
throws IOException {
this.root = Objects.requireNonNull(root);
this.platform = Objects.requireNonNull(targetPlatform);
this.launchers = Objects.requireNonNull(launchers);
this.mdir = root.resolve("lib");
Files.createDirectories(mdir);
@ -168,19 +172,6 @@ public final class DefaultImageBuilder implements ImageBuilder {
@Override
public void storeFiles(ResourcePool files) {
try {
String value = files.moduleView()
.findModule("java.base")
.map(ResourcePoolModule::targetPlatform)
.orElse(null);
if (value == null) {
throw new PluginException("ModuleTarget attribute is missing for java.base module");
}
try {
this.platform = Platform.parsePlatform(value);
} catch (IllegalArgumentException iae) {
throw new PluginException("ModuleTarget is malformed: " + iae.getMessage());
}
checkResourcePool(files);
Path bin = root.resolve(BIN_DIRNAME);

View File

@ -147,7 +147,6 @@ public final class Jlink {
private final Path output;
private final Set<String> modules;
private final ByteOrder endian;
private final ModuleFinder finder;
/**
@ -155,26 +154,16 @@ public final class Jlink {
*
* @param output Output directory, must not exist.
* @param modules The possibly-empty set of root modules to resolve
* @param endian Jimage byte order. Native order by default
* @param finder the ModuleFinder for this configuration
*/
public JlinkConfiguration(Path output,
Set<String> modules,
ByteOrder endian,
ModuleFinder finder) {
this.output = output;
this.modules = Objects.requireNonNull(modules);
this.endian = Objects.requireNonNull(endian);
this.finder = finder;
}
/**
* @return the byte ordering
*/
public ByteOrder getByteOrder() {
return endian;
}
/**
* @return the output
*/
@ -229,7 +218,6 @@ public final class Jlink {
modsBuilder.append(p).append(",");
}
builder.append("modules=").append(modsBuilder).append("\n");
builder.append("endian=").append(endian).append("\n");
return builder.toString();
}
}

View File

@ -60,6 +60,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.module.ModuleReferenceImpl;
import jdk.tools.jlink.internal.TaskHelper.BadArgs;
import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
@ -68,8 +69,6 @@ import jdk.tools.jlink.internal.TaskHelper.Option;
import jdk.tools.jlink.internal.TaskHelper.OptionsHelper;
import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.builder.DefaultImageBuilder;
import jdk.tools.jlink.plugin.Plugin;
import jdk.internal.opt.CommandLine;
import jdk.internal.module.ModulePath;
import jdk.internal.module.ModuleResolution;
@ -219,7 +218,7 @@ public class JlinkTask {
Path output;
final Map<String, String> launchers = new HashMap<>();
Path packagedModulesPath;
ByteOrder endian = ByteOrder.nativeOrder();
ByteOrder endian;
boolean ignoreSigning = false;
boolean bindServices = false;
boolean suggestProviders = false;
@ -355,6 +354,7 @@ public class JlinkTask {
null,
IGNORE_SIGNING_DEFAULT,
false,
null,
false,
null);
@ -394,7 +394,6 @@ public class JlinkTask {
return new JlinkConfiguration(options.output,
roots,
options.endian,
finder);
}
@ -408,16 +407,18 @@ public class JlinkTask {
}
// First create the image provider
ImageProvider imageProvider = createImageProvider(config,
options.packagedModulesPath,
options.ignoreSigning,
options.bindServices,
options.verbose,
log);
ImageHelper imageProvider = createImageProvider(config,
options.packagedModulesPath,
options.ignoreSigning,
options.bindServices,
options.endian,
options.verbose,
log);
// Then create the Plugin Stack
ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(
taskHelper.getPluginsConfig(options.output, options.launchers));
taskHelper.getPluginsConfig(options.output, options.launchers,
imageProvider.targetPlatform));
//Ask the stack to proceed
stack.operate(imageProvider);
@ -511,12 +512,13 @@ public class JlinkTask {
}
private static ImageProvider createImageProvider(JlinkConfiguration config,
Path retainModulesPath,
boolean ignoreSigning,
boolean bindService,
boolean verbose,
PrintWriter log)
private static ImageHelper createImageProvider(JlinkConfiguration config,
Path retainModulesPath,
boolean ignoreSigning,
boolean bindService,
ByteOrder endian,
boolean verbose,
PrintWriter log)
throws IOException
{
Configuration cf = bindService ? config.resolveAndBind()
@ -563,7 +565,22 @@ public class JlinkTask {
Map<String, Path> mods = cf.modules().stream()
.collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation));
return new ImageHelper(cf, mods, config.getByteOrder(), retainModulesPath, ignoreSigning);
// determine the target platform of the image being created
Platform targetPlatform = targetPlatform(cf, mods);
// if the user specified any --endian, then it must match the target platform's native
// endianness
if (endian != null && endian != targetPlatform.arch().byteOrder()) {
throw new IOException(
taskHelper.getMessage("err.target.endianness.mismatch", endian, targetPlatform));
}
if (verbose && log != null) {
Platform runtime = Platform.runtime();
if (runtime.os() != targetPlatform.os() || runtime.arch() != targetPlatform.arch()) {
log.format("Cross-platform image generation, using %s for target platform %s%n",
targetPlatform.arch().byteOrder(), targetPlatform);
}
}
return new ImageHelper(cf, mods, targetPlatform, retainModulesPath, ignoreSigning);
}
/*
@ -609,6 +626,63 @@ public class JlinkTask {
};
}
private static Platform targetPlatform(Configuration cf, Map<String, Path> modsPaths) throws IOException {
Path javaBasePath = modsPaths.get("java.base");
assert javaBasePath != null : "java.base module path is missing";
if (isJavaBaseFromDefaultModulePath(javaBasePath)) {
// this implies that the java.base module used for the target image
// will correspond to the current platform. So this isn't an attempt to
// build a cross-platform image. We use the current platform's endianness
// in this case
return Platform.runtime();
} else {
// this is an attempt to build a cross-platform image. We now attempt to
// find the target platform's arch and thus its endianness from the java.base
// module's ModuleTarget attribute
String targetPlatformVal = readJavaBaseTargetPlatform(cf);
try {
return Platform.parsePlatform(targetPlatformVal);
} catch (IllegalArgumentException iae) {
throw new IOException(
taskHelper.getMessage("err.unknown.target.platform", targetPlatformVal));
}
}
}
// returns true if the default module-path is the parent of the passed javaBasePath
private static boolean isJavaBaseFromDefaultModulePath(Path javaBasePath) throws IOException {
Path defaultModulePath = getDefaultModulePath();
if (defaultModulePath == null) {
return false;
}
// resolve, against the default module-path dir, the java.base module file used
// for image creation
Path javaBaseInDefaultPath = defaultModulePath.resolve(javaBasePath.getFileName());
if (Files.notExists(javaBaseInDefaultPath)) {
// the java.base module used for image creation doesn't exist in the default
// module path
return false;
}
return Files.isSameFile(javaBasePath, javaBaseInDefaultPath);
}
// returns the targetPlatform value from the ModuleTarget attribute of the java.base module.
// throws IOException if the targetPlatform cannot be determined.
private static String readJavaBaseTargetPlatform(Configuration cf) throws IOException {
Optional<ResolvedModule> javaBase = cf.findModule("java.base");
assert javaBase.isPresent() : "java.base module is missing";
ModuleReference ref = javaBase.get().reference();
if (ref instanceof ModuleReferenceImpl modRefImpl
&& modRefImpl.moduleTarget() != null) {
return modRefImpl.moduleTarget().targetPlatform();
}
// could not determine target platform
throw new IOException(
taskHelper.getMessage("err.cannot.determine.target.platform",
ref.location().map(URI::toString)
.orElse("java.base module")));
}
/*
* Returns a map of each service type to the modules that use it
* It will include services that are provided by a module but may not used
@ -772,7 +846,7 @@ public class JlinkTask {
}
private static class ImageHelper implements ImageProvider {
final ByteOrder order;
final Platform targetPlatform;
final Path packagedModulesPath;
final boolean ignoreSigning;
final Runtime.Version version;
@ -780,10 +854,11 @@ public class JlinkTask {
ImageHelper(Configuration cf,
Map<String, Path> modsPaths,
ByteOrder order,
Platform targetPlatform,
Path packagedModulesPath,
boolean ignoreSigning) throws IOException {
this.order = order;
Objects.requireNonNull(targetPlatform);
this.targetPlatform = targetPlatform;
this.packagedModulesPath = packagedModulesPath;
this.ignoreSigning = ignoreSigning;
@ -857,7 +932,8 @@ public class JlinkTask {
@Override
public ExecutableImage retrieve(ImagePluginStack stack) throws IOException {
ExecutableImage image = ImageFileCreator.create(archives, order, stack);
ExecutableImage image = ImageFileCreator.create(archives,
targetPlatform.arch().byteOrder(), stack);
if (packagedModulesPath != null) {
// copy the packaged modules to the given path
Files.createDirectories(packagedModulesPath);

View File

@ -50,10 +50,7 @@ public record Platform(OperatingSystem os, Architecture arch) {
OperatingSystem os = OperatingSystem.valueOf(osName.toUpperCase(Locale.ROOT));
archName = platformString.substring(index + 1);
// Alias architecture names, if needed
archName = archName.replace("amd64", "X64");
archName = archName.replace("s390x", "S390");
Architecture arch = Architecture.valueOf(archName.toUpperCase(Locale.ROOT));
Architecture arch = Architecture.lookupByName(archName);
return new Platform(os, arch);
}

View File

@ -409,8 +409,9 @@ public final class TaskHelper {
return null;
}
private PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers
) throws IOException, BadArgs {
private PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers,
Platform targetPlatform)
throws IOException, BadArgs {
if (output != null) {
if (Files.exists(output)) {
throw new IllegalArgumentException(PluginsResourceBundle.
@ -457,7 +458,7 @@ public final class TaskHelper {
// recreate or postprocessing don't require an output directory.
ImageBuilder builder = null;
if (output != null) {
builder = new DefaultImageBuilder(output, launchers);
builder = new DefaultImageBuilder(output, launchers, targetPlatform);
}
return new Jlink.PluginsConfiguration(pluginsList,
@ -708,9 +709,10 @@ public final class TaskHelper {
+ bundleHelper.getMessage(key, args));
}
public PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers)
public PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers,
Platform targetPlatform)
throws IOException, BadArgs {
return pluginOptions.getPluginsConfig(output, launchers);
return pluginOptions.getPluginsConfig(output, launchers, targetPlatform);
}
public void showVersion(boolean full) {

View File

@ -146,6 +146,10 @@ err.cannot.read.module.info=cannot read module descriptor from {0}
err.not.modular.format=selected module {0} ({1}) not in jmod or modular JAR format
err.signing=signed modular JAR {0} is currently not supported,\
\ use --ignore-signing-information to suppress error
err.cannot.determine.target.platform=cannot determine target platform from {0}
err.unknown.target.platform=unknown target platform {0}
err.target.endianness.mismatch=specified --endian {0} does not match endianness of target \
platform {1}
warn.signing=WARNING: signed modular JAR {0} is currently not supported
warn.invalid.arg=invalid classname or pathname not exist: {0}
warn.split.package=package {0} defined in {1} {2}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -41,6 +41,7 @@ import java.util.function.Function;
import jdk.tools.jlink.internal.Jlink;
import jdk.tools.jlink.internal.JlinkTask;
import jdk.tools.jlink.builder.DefaultImageBuilder;
import jdk.tools.jlink.internal.Platform;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.Plugin;
@ -160,7 +161,7 @@ public class IntegrationTest {
Set<String> limits = new HashSet<>();
limits.add("java.management");
JlinkConfiguration config = new Jlink.JlinkConfiguration(output,
mods, ByteOrder.nativeOrder(),
mods,
JlinkTask.newModuleFinder(modulePaths, limits, mods));
List<Plugin> lst = new ArrayList<>();
@ -189,7 +190,8 @@ public class IntegrationTest {
lst.add(new MyPostProcessor());
}
// Image builder
DefaultImageBuilder builder = new DefaultImageBuilder(output, Collections.emptyMap());
DefaultImageBuilder builder = new DefaultImageBuilder(output, Collections.emptyMap(),
Platform.runtime());
PluginsConfiguration plugins
= new Jlink.PluginsConfiguration(lst, builder, null);

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.spi.ToolProvider;
import jdk.internal.util.OperatingSystem;
/*
* @test
* @bug 8206890
* @summary verify that the image created through jlink uses the byte order of the target platform
* @modules java.base/jdk.internal.util
* @comment the test asserts the presence of locale specific error message in the test's output,
* so we explicitly use en_US locale
* @run main/othervm -Duser.language=en -Duser.country=US JLinkEndianTest
*/
public class JLinkEndianTest {
private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
.orElseThrow(() -> new RuntimeException("jlink tool not found"));
public static void main(final String[] args) throws Exception {
testEndianMismatch();
testCorrectEndian();
}
/**
* Launches jlink with "--endian" option whose value doesn't match the target platform.
* Asserts that the jlink process fails with an error.
*/
private static void testEndianMismatch() throws Exception {
// we use a --endian value which doesn't match the current platform's endianness.
// this should cause the jlink image generation against the current platform to fail
final String endianOptVal = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN
? "big" : "little";
final String[] args = new String[]{
"-v",
"--endian", endianOptVal,
"--add-modules", "java.base",
"--output", "image-should-not-have-been-created"
};
final StringWriter jlinkStdout = new StringWriter();
final StringWriter jlinkStderr = new StringWriter();
System.out.println("Launching jlink with args: " + Arrays.toString(args));
final int exitCode = JLINK_TOOL.run(new PrintWriter(jlinkStdout),
new PrintWriter(jlinkStderr), args);
System.out.println(jlinkStdout);
System.err.println(jlinkStderr);
if (exitCode == 0) {
throw new AssertionError("jlink command was expected to fail but completed with" +
" exit code: " + exitCode);
}
// verify the failure was due to the expected error (message)
if (!jlinkStdout.toString().contains("does not match endianness of target platform")) {
throw new AssertionError("jlink process' stderr didn't contain the expected" +
" error message");
}
}
/**
* Launches jlink with "--endian" option whose value matches the target platform's endianness.
* Asserts that the jlink process successfully creates the image.
*/
private static void testCorrectEndian() throws Exception {
// we use a --endian value which matches the current platform
final String endianOptVal = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN
? "little" : "big";
final Path imageOutDir = Path.of("correct-endian-image");
final String[] args = new String[]{
"-v",
"--endian", endianOptVal,
"--add-modules", "java.base",
"--output", imageOutDir.toString()
};
final StringWriter jlinkStdout = new StringWriter();
final StringWriter jlinkStderr = new StringWriter();
System.out.println("Launching jlink with args: " + Arrays.toString(args));
final int exitCode = JLINK_TOOL.run(new PrintWriter(jlinkStdout),
new PrintWriter(jlinkStderr), args);
System.out.println(jlinkStdout);
System.err.println(jlinkStderr);
if (exitCode != 0) {
throw new AssertionError("jlink command was expected to succeed but completed with" +
" exit code: " + exitCode);
}
// trivially verify <image-dir>/bin/java exists
final Path javaBinary = OperatingSystem.isWindows()
? Path.of(imageOutDir.toString(), "bin", "java.exe")
: Path.of(imageOutDir.toString(), "bin", "java");
if (!Files.exists(javaBinary)) {
throw new AssertionError("jlink image generation was expected to create "
+ javaBinary + ", but that file is missing");
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -39,6 +39,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import jdk.tools.jlink.builder.DefaultImageBuilder;
import jdk.tools.jlink.internal.Platform;
import jdk.tools.jlink.internal.ResourcePoolEntryFactory;
import jdk.tools.jlink.internal.ResourcePoolManager;
import jdk.tools.jlink.plugin.PluginException;
@ -61,7 +62,8 @@ public class ResourceDuplicateCheckTest {
input.add(newInMemoryImageFile("/com.foo/bin/myexec",
ResourcePoolEntry.Type.NATIVE_CMD, "mylib"));
Path root = Paths.get(System.getProperty("test.classes"));
DefaultImageBuilder writer = new DefaultImageBuilder(root, Collections.emptyMap());
DefaultImageBuilder writer = new DefaultImageBuilder(root, Collections.emptyMap(),
Platform.runtime());
try {
writer.storeFiles(input.resourcePool());
} catch (PluginException pe) {