From b00e7978a86d9ea8165dc9fe7d672bff59e54f2b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 18 May 2023 07:37:14 +0200 Subject: [PATCH] Fix broken detection of mvnd home on alpine linux (fixes #849) (#848) * Fix for musl dynamic loader * Add caching for mvnd.home and java.home * Add IT for alpine linux --- .../mvnd/client/DaemonParameters.java | 37 +++++++++- integration-tests/pom.xml | 12 ++++ .../mvnd/it/AlpineLinuxNativeIT.java | 70 +++++++++++++++++++ .../mvndaemon/mvnd/it/AlpineLinuxTest.java | 32 +++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxNativeIT.java create mode 100644 integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxTest.java diff --git a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonParameters.java b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonParameters.java index e09deaf0..716ee4a7 100644 --- a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonParameters.java +++ b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonParameters.java @@ -111,6 +111,7 @@ public class DaemonParameters { .orLocalProperty(provider, userPropertiesPath()) .orEnvironmentVariable() .orFail() + .cache(provider) .asPath() .toAbsolutePath() .normalize(); @@ -119,7 +120,25 @@ public class DaemonParameters { private String mvndHomeFromExecutable() { Optional cmd = ProcessHandle.current().info().command(); if (Environment.isNative() && cmd.isPresent()) { - final Path mvndH = Paths.get(cmd.get()).getParent().getParent(); + String cmdStr = cmd.get(); + // When running on alpine, musl uses a dynamic loader + // which is used as the main exec and the full path to the + // executable is the argument following "--" + if (cmdStr.startsWith("/lib/ld-musl-")) { + Optional args = ProcessHandle.current().info().arguments(); + if (args.isPresent()) { + boolean nextIsArg0 = false; + for (String arg : args.get()) { + if (nextIsArg0) { + cmdStr = arg; + break; + } else { + nextIsArg0 = "--".equals(arg); + } + } + } + } + Path mvndH = Paths.get(cmdStr).getParent().getParent(); if (mvndH != null) { Path mvndDaemon = Paths.get("mvnd-daemon-" + BuildProperties.getInstance().getVersion() + ".jar"); @@ -143,6 +162,7 @@ public class DaemonParameters { .or(new ValueSource( description -> description.append("java command"), DaemonParameters::javaHomeFromPath)) .orFail() + .cache(provider) .asPath(); try { return result.toRealPath(); @@ -698,5 +718,20 @@ public class DaemonParameters { throw couldNotgetValue(); } } + + public EnvValue cache(Function provider) { + return new EnvValue(this, envKey, new ValueSource(sb -> sb, () -> null)) { + @Override + String get() { + Properties props = provider.apply(Paths.get(DaemonParameters.class.getName() + "#cache")); + String value = props.getProperty(envKey.getProperty()); + if (value == null) { + value = super.get(); + props.setProperty(envKey.getProperty(), value); + } + return value; + } + }; + } } } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 8fc84bf1..bcf2094f 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -94,6 +94,18 @@ commons-compress test + + org.testcontainers + testcontainers + 1.18.1 + test + + + org.testcontainers + junit-jupiter + 1.18.1 + test + diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxNativeIT.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxNativeIT.java new file mode 100644 index 00000000..27164887 --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxNativeIT.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.mvndaemon.mvnd.it; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Testcontainers(disabledWithoutDocker = true) +public class AlpineLinuxNativeIT { + + private ImageFromDockerfile image; + private String mvndHome; + + @BeforeAll + void createImage() { + image = new ImageFromDockerfile() + .withDockerfileFromBuilder(builder -> builder.from("bellsoft/liberica-openjdk-alpine-musl:latest") + .run("apk add gcompat") + .build()); + mvndHome = System.getProperty("mvnd.home"); + if (mvndHome == null) { + throw new IllegalStateException("System property mnvd.home is undefined"); + } + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void testAlpineJvm() throws Exception { + String logs; + try (GenericContainer alpine = new GenericContainer<>(image) + .withFileSystemBind(mvndHome, "/mvnd") + .withCommand(isNative() ? "/mvnd/bin/mvnd" : "/mvnd/bin/mvnd.sh", "-v")) { + alpine.start(); + while (alpine.isRunning()) { + Thread.sleep(100); + } + logs = alpine.getLogs(); + } + assertTrue(logs.contains("Apache Maven Daemon"), logs); + } + + protected boolean isNative() { + return true; + } +} diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxTest.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxTest.java new file mode 100644 index 00000000..bfda1793 --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/AlpineLinuxTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.mvndaemon.mvnd.it; + +import org.junit.jupiter.api.TestInstance; +import org.testcontainers.junit.jupiter.Testcontainers; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Testcontainers(disabledWithoutDocker = true) +public class AlpineLinuxTest extends AlpineLinuxNativeIT { + + @Override + protected boolean isNative() { + return false; + } +}