Improve setEnv logic, fixes #528 (#535)
diff --git a/daemon/pom.xml b/daemon/pom.xml
index 6c650c6..e947d48 100644
--- a/daemon/pom.xml
+++ b/daemon/pom.xml
@@ -121,6 +121,8 @@
<reuseForks>true</reuseForks>
<argLine>
--add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/sun.nio.fs=ALL-UNNAMED
</argLine>
</configuration>
diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
index 90975e8..b55ecc5 100644
--- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
+++ b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
@@ -22,11 +22,7 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintStream;
-import java.lang.reflect.Field;
-import java.nio.charset.Charset;
-import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -39,11 +35,9 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
-import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -100,11 +94,11 @@
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.transfer.TransferListener;
-import org.jline.utils.ExecHelper;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingExtensionRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginArtifactsCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingProjectArtifactsCache;
+import org.mvndaemon.mvnd.cli.EnvHelper;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.common.Os;
import org.mvndaemon.mvnd.execution.BuildResumptionPersistenceException;
@@ -114,7 +108,6 @@
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream;
-import org.mvndaemon.mvnd.nativ.CLibrary;
import org.mvndaemon.mvnd.plugin.CachingPluginVersionResolver;
import org.mvndaemon.mvnd.plugin.CliMavenPluginManager;
import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
@@ -688,130 +681,7 @@
}
private void environment(String workingDir, Map<String, String> clientEnv) {
- Map<String, String> requested = new TreeMap<>(clientEnv);
- Map<String, String> actual = new TreeMap<>(System.getenv());
- requested.put("PWD", Os.current().isCygwin() ? toCygwin(workingDir) : workingDir);
- List<String> diffs = Stream.concat(requested.keySet().stream(), actual.keySet().stream())
- .sorted()
- .distinct()
- .filter(s -> !s.startsWith("JAVA_MAIN_CLASS_"))
- .filter(key -> !Objects.equals(requested.get(key), actual.get(key)))
- .collect(Collectors.toList());
- try {
- for (String key : diffs) {
- String vr = requested.get(key);
- CLibrary.setenv(key, vr);
- }
- setEnv(requested);
- chDir(workingDir);
- } catch (Exception e) {
- slf4jLogger.warn("Environment mismatch ! Could not set the environment (" + e + ")");
- }
- Map<String, String> nactual = new TreeMap<>(System.getenv());
- diffs = Stream.concat(requested.keySet().stream(), actual.keySet().stream())
- .sorted()
- .distinct()
- .filter(s -> !s.startsWith("JAVA_MAIN_CLASS_"))
- .filter(key -> !Objects.equals(requested.get(key), nactual.get(key)))
- .collect(Collectors.toList());
- if (!diffs.isEmpty()) {
- slf4jLogger.warn("A few environment mismatches have been detected between the client and the daemon.");
- diffs.forEach(key -> {
- String vr = requested.get(key);
- String va = nactual.get(key);
- slf4jLogger.warn(" {} -> {} instead of {}", key,
- va != null ? "'" + va + "'" : "<null>", vr != null ? "'" + vr + "'" : "<null>");
- });
- slf4jLogger.warn("If the difference matters to you, stop the running daemons using mvnd --stop and");
- slf4jLogger.warn("start a new daemon from the current environment by issuing any mvnd build command.");
- }
- }
-
- static String toCygwin(String path) {
- if (path.length() >= 3 && ":\\".equals(path.substring(1, 3))) {
- try {
- String p = path.endsWith("\\") ? path.substring(0, path.length() - 1) : path;
- return ExecHelper.exec(false, "cygpath", p).trim();
- } catch (IOException e) {
- String root = path.substring(0, 1);
- String p = path.substring(3);
- return "/cygdrive/" + root.toLowerCase() + "/" + p.replace('\\', '/');
- }
- }
- return path;
- }
-
- private static float javaSpec = 0.0f;
-
- protected static void chDir(String workingDir) throws Exception {
- CLibrary.chdir(workingDir);
- System.setProperty("user.dir", workingDir);
- // change current dir for the java.io.File class
- Class<?> fileClass = Class.forName("java.io.File");
- if (javaSpec <= 0.0f) {
- javaSpec = Float.parseFloat(System.getProperty("java.specification.version"));
- }
- if (javaSpec >= 11.0) {
- Field fsField = fileClass.getDeclaredField("fs");
- fsField.setAccessible(true);
- Object fs = fsField.get(null);
- Field userDirField = fs.getClass().getDeclaredField("userDir");
- userDirField.setAccessible(true);
- userDirField.set(fs, workingDir);
- }
- // change current dir for the java.nio.Path class
- Object fs = FileSystems.getDefault();
- Class<?> fsClass = fs.getClass();
- while (fsClass != Object.class) {
- if ("sun.nio.fs.UnixFileSystem".equals(fsClass.getName())) {
- Field defaultDirectoryField = fsClass.getDeclaredField("defaultDirectory");
- defaultDirectoryField.setAccessible(true);
- String encoding = System.getProperty("sun.jnu.encoding");
- Charset charset = encoding != null ? Charset.forName(encoding) : Charset.defaultCharset();
- defaultDirectoryField.set(fs, workingDir.getBytes(charset));
- } else if ("sun.nio.fs.WindowsFileSystem".equals(fsClass.getName())) {
- Field defaultDirectoryField = fsClass.getDeclaredField("defaultDirectory");
- Field defaultRootField = fsClass.getDeclaredField("defaultRoot");
- defaultDirectoryField.setAccessible(true);
- defaultRootField.setAccessible(true);
- Path wdir = Paths.get(workingDir);
- Path root = wdir.getRoot();
- defaultDirectoryField.set(fs, wdir.toString());
- defaultRootField.set(fs, root.toString());
- }
- fsClass = fsClass.getSuperclass();
- }
- }
-
- @SuppressWarnings("unchecked")
- protected static void setEnv(Map<String, String> newenv) throws Exception {
- try {
- Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
- Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
- theEnvironmentField.setAccessible(true);
- Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
- env.clear();
- env.putAll(newenv);
- Field theCaseInsensitiveEnvironmentField = processEnvironmentClass
- .getDeclaredField("theCaseInsensitiveEnvironment");
- theCaseInsensitiveEnvironmentField.setAccessible(true);
- Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
- cienv.clear();
- cienv.putAll(newenv);
- } catch (NoSuchFieldException e) {
- Class<?>[] classes = Collections.class.getDeclaredClasses();
- Map<String, String> env = System.getenv();
- for (Class<?> cl : classes) {
- if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
- Field field = cl.getDeclaredField("m");
- field.setAccessible(true);
- Object obj = field.get(env);
- Map<String, String> map = (Map<String, String>) obj;
- map.clear();
- map.putAll(newenv);
- }
- }
- }
+ EnvHelper.environment(workingDir, clientEnv);
}
private int execute(CliRequest cliRequest)
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java b/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java
new file mode 100644
index 0000000..9089634
--- /dev/null
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/cli/EnvHelper.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed 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.cli;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.jline.utils.ExecHelper;
+import org.mvndaemon.mvnd.common.JavaVersion;
+import org.mvndaemon.mvnd.common.Os;
+import org.mvndaemon.mvnd.nativ.CLibrary;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EnvHelper {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EnvHelper.class);
+
+ private EnvHelper() {
+ }
+
+ public static void environment(String workingDir, Map<String, String> clientEnv) {
+ environment(workingDir, clientEnv, LOGGER::warn);
+ }
+
+ public static void environment(String workingDir, Map<String, String> clientEnv, Consumer<String> log) {
+ Map<String, String> requested = new TreeMap<>(clientEnv);
+ Map<String, String> actual = new TreeMap<>(System.getenv());
+ requested.put("PWD", Os.current().isCygwin() ? toCygwin(workingDir) : workingDir);
+ List<String> diffs = Stream.concat(requested.keySet().stream(), actual.keySet().stream())
+ .sorted()
+ .distinct()
+ .filter(s -> !s.startsWith("JAVA_MAIN_CLASS_"))
+ .filter(key -> !Objects.equals(requested.get(key), actual.get(key)))
+ .collect(Collectors.toList());
+ try {
+ for (String key : diffs) {
+ String vr = requested.get(key);
+ int rc = CLibrary.setenv(key, vr);
+ if (Os.current() == Os.WINDOWS ^ rc != 0) {
+ log.accept(String.format("Error setting environment value %s = %s", key, vr));
+ }
+ }
+ setEnv(requested);
+ chDir(workingDir);
+ } catch (Exception e) {
+ log.accept("Environment mismatch ! Could not set the environment (" + e + ")");
+ }
+ Map<String, String> nactual = new TreeMap<>(System.getenv());
+ diffs = Stream.concat(requested.keySet().stream(), actual.keySet().stream())
+ .sorted()
+ .distinct()
+ .filter(s -> !s.startsWith("JAVA_MAIN_CLASS_"))
+ .filter(key -> !Objects.equals(requested.get(key), nactual.get(key)))
+ .collect(Collectors.toList());
+ if (!diffs.isEmpty()) {
+ log.accept("A few environment mismatches have been detected between the client and the daemon.");
+ diffs.forEach(key -> {
+ String vr = requested.get(key);
+ String va = nactual.get(key);
+ log.accept(String.format(" %s -> %s instead of %s", key,
+ va != null ? "'" + va + "'" : "<null>", vr != null ? "'" + vr + "'" : "<null>"));
+ });
+ log.accept("If the difference matters to you, stop the running daemons using mvnd --stop and");
+ log.accept("start a new daemon from the current environment by issuing any mvnd build command.");
+ }
+ }
+
+ static String toCygwin(String path) {
+ if (path.length() >= 3 && ":\\".equals(path.substring(1, 3))) {
+ try {
+ String p = path.endsWith("\\") ? path.substring(0, path.length() - 1) : path;
+ return ExecHelper.exec(false, "cygpath", p).trim();
+ } catch (IOException e) {
+ String root = path.substring(0, 1);
+ String p = path.substring(3);
+ return "/cygdrive/" + root.toLowerCase(Locale.ROOT) + "/" + p.replace('\\', '/');
+ }
+ }
+ return path;
+ }
+
+ static void chDir(String workingDir) throws Exception {
+ CLibrary.chdir(workingDir);
+ System.setProperty("user.dir", workingDir);
+ // change current dir for the java.io.File class
+ Class<?> fileClass = Class.forName("java.io.File");
+ if (JavaVersion.getJavaSpec() >= 11.0) {
+ Field fsField = fileClass.getDeclaredField("fs");
+ fsField.setAccessible(true);
+ Object fs = fsField.get(null);
+ Field userDirField = fs.getClass().getDeclaredField("userDir");
+ userDirField.setAccessible(true);
+ userDirField.set(fs, workingDir);
+ }
+ // change current dir for the java.nio.Path class
+ Object fs = FileSystems.getDefault();
+ Class<?> fsClass = fs.getClass();
+ while (fsClass != Object.class) {
+ if ("sun.nio.fs.UnixFileSystem".equals(fsClass.getName())) {
+ Field defaultDirectoryField = fsClass.getDeclaredField("defaultDirectory");
+ defaultDirectoryField.setAccessible(true);
+ String encoding = System.getProperty("sun.jnu.encoding");
+ Charset charset = encoding != null ? Charset.forName(encoding) : Charset.defaultCharset();
+ defaultDirectoryField.set(fs, workingDir.getBytes(charset));
+ } else if ("sun.nio.fs.WindowsFileSystem".equals(fsClass.getName())) {
+ Field defaultDirectoryField = fsClass.getDeclaredField("defaultDirectory");
+ Field defaultRootField = fsClass.getDeclaredField("defaultRoot");
+ defaultDirectoryField.setAccessible(true);
+ defaultRootField.setAccessible(true);
+ Path wdir = Paths.get(workingDir);
+ Path root = wdir.getRoot();
+ defaultDirectoryField.set(fs, wdir.toString());
+ defaultRootField.set(fs, root.toString());
+ }
+ fsClass = fsClass.getSuperclass();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static void setEnv(Map<String, String> newenv) throws Exception {
+ Map<String, String> env = System.getenv();
+ // The map is an unmodifiable view of the environment map, so find a Map field
+ for (Field field : env.getClass().getDeclaredFields()) {
+ if (Map.class.isAssignableFrom(field.getType())) {
+ field.setAccessible(true);
+ Object obj = field.get(env);
+ Map<String, String> map = (Map<String, String>) obj;
+ map.clear();
+ map.putAll(newenv);
+ }
+ }
+ // OpenJDK 8-17 on Windows
+ if (Os.current() == Os.WINDOWS) {
+ Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
+ Field theCaseInsensitiveEnvironmentField = processEnvironmentClass
+ .getDeclaredField("theCaseInsensitiveEnvironment");
+ theCaseInsensitiveEnvironmentField.setAccessible(true);
+ Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
+ cienv.clear();
+ cienv.putAll(newenv);
+ }
+ }
+}
diff --git a/daemon/src/test/java/org/apache/maven/cli/DaemonMavenCliTest.java b/daemon/src/test/java/org/apache/maven/cli/DaemonMavenCliTest.java
deleted file mode 100644
index 31a4432..0000000
--- a/daemon/src/test/java/org/apache/maven/cli/DaemonMavenCliTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed 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.apache.maven.cli;
-
-import java.io.File;
-import java.nio.file.Paths;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class DaemonMavenCliTest {
-
- @Test
- void testChdir() throws Exception {
- File d = new File("target/tstDir").getAbsoluteFile();
- d.mkdirs();
- DaemonMavenCli.chDir(d.toString());
- assertEquals(new File(d, "test").getAbsolutePath(), new File("test").getAbsolutePath());
- assertEquals(d.toPath().resolve("test").toAbsolutePath().toString(),
- Paths.get("test").toAbsolutePath().toString());
- }
-
- @Test
- @Disabled
- void testCygwin() throws Exception {
- assertEquals("/cygdrive/c/work/tmp/", DaemonMavenCli.toCygwin("C:\\work\\tmp\\"));
- }
-
-}
diff --git a/daemon/src/test/java/org/mvndaemon/mvnd/cli/EnvHelperTest.java b/daemon/src/test/java/org/mvndaemon/mvnd/cli/EnvHelperTest.java
new file mode 100644
index 0000000..0698028
--- /dev/null
+++ b/daemon/src/test/java/org/mvndaemon/mvnd/cli/EnvHelperTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed 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.cli;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.mvndaemon.mvnd.common.Os;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class EnvHelperTest {
+
+ @Test
+ void testSetEnv() throws Exception {
+ List<String> log = new ArrayList<>();
+ String id = "Aa" + UUID.randomUUID();
+ assertNull(System.getenv(id));
+ assertNull(System.getenv().get(id));
+ Map<String, String> env = new HashMap<>(System.getenv());
+ env.put(id, id);
+ EnvHelper.environment(System.getProperty("user.dir"), env, log::add);
+ assertEquals(Collections.emptyList(), log);
+ assertEquals(id, System.getenv(id));
+ assertEquals(id, System.getenv().get(id));
+ assertEquals(Os.current() == Os.WINDOWS ? id : null, System.getenv(id.toLowerCase(Locale.ROOT)));
+ assertNull(System.getenv().get(id.toLowerCase(Locale.ROOT)));
+ assertEquals(Os.current() == Os.WINDOWS ? id : null, System.getenv(id.toUpperCase(Locale.ROOT)));
+ assertNull(System.getenv().get(id.toUpperCase(Locale.ROOT)));
+ env.remove(id);
+ EnvHelper.environment(System.getProperty("user.dir"), env, log::add);
+ assertEquals(Collections.emptyList(), log);
+ assertNull(System.getenv(id));
+ assertNull(System.getenv().get(id));
+ }
+
+ @Test
+ void testChdir() throws Exception {
+ File d = new File("target/tstDir").getAbsoluteFile();
+ d.mkdirs();
+ EnvHelper.chDir(d.toString());
+ assertEquals(new File(d, "test").getAbsolutePath(), new File("test").getAbsolutePath());
+ assertEquals(d.toPath().resolve("test").toAbsolutePath().toString(),
+ Paths.get("test").toAbsolutePath().toString());
+ }
+
+ @Test
+ @Disabled
+ void testCygwin() throws Exception {
+ assertEquals("/cygdrive/c/work/tmp/", EnvHelper.toCygwin("C:\\work\\tmp\\"));
+ }
+
+}