From 08b5d558d0080c7ac903a7fd391782ceb2e3f2e5 Mon Sep 17 00:00:00 2001 From: Ilia Motornyi Date: Sat, 8 Feb 2025 00:17:52 +0200 Subject: [PATCH] CPP-34199 Ability to use a PowerShell script as an environment file for toolchains PS5 and PS7 supported GitOrigin-RevId: 49d169c274539eb749fe30c64681598f0c15e252 --- .../src/com/intellij/util/EnvReader.java | 57 +++++++++++++++++-- .../src/com/intellij/util/ReadEnv.java | 20 +++++-- .../intellij/util/EnvironmentUtilTest.java | 45 ++++++++++++++- 3 files changed, 110 insertions(+), 12 deletions(-) diff --git a/platform/platform-util-io/src/com/intellij/util/EnvReader.java b/platform/platform-util-io/src/com/intellij/util/EnvReader.java index c1b9cb88f740..52c115dc8811 100644 --- a/platform/platform-util-io/src/com/intellij/util/EnvReader.java +++ b/platform/platform-util-io/src/com/intellij/util/EnvReader.java @@ -2,6 +2,7 @@ package com.intellij.util; import com.intellij.execution.CommandLineUtil; +import com.intellij.execution.configurations.PathEnvironmentVariableUtil; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.ApiStatus; @@ -14,13 +15,22 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Consumer; @ApiStatus.Internal public class EnvReader extends EnvironmentUtil.ShellEnvReader { + + private static final String READ_ENV_CLASS_NAME = ReadEnv.class.getCanonicalName(); + + private static @Nullable String readEnvClasspath() { + return PathManager.getJarPathForClass(ReadEnv.class); + } + + private static @NotNull String javaExePath() { + return (System.getProperty("java.home") + "/bin/java").replace('/', File.separatorChar); + } + public EnvReader() { } @@ -81,13 +91,12 @@ public class EnvReader extends EnvironmentUtil.ShellEnvReader { callArgs.add("&&"); } - callArgs.add((System.getProperty("java.home") + "/bin/java").replace('/', File.separatorChar)); // NON-NLS + callArgs.add(javaExePath()); // NON-NLS callArgs.add("-cp"); // NON-NLS callArgs.add(PathManager.getJarPathForClass(ReadEnv.class)); callArgs.add(ReadEnv.class.getCanonicalName()); - - callArgs.add(">"); callArgs.add(envDataFile.toString()); + callArgs.add("||"); callArgs.add("exit"); // NON-NLS callArgs.add("/B"); // NON-NLS @@ -102,6 +111,42 @@ public class EnvReader extends EnvironmentUtil.ShellEnvReader { return new Pair<>(entry.getKey(), entry.getValue()); } + @SuppressWarnings("SpellCheckingInspection") + public @NotNull Pair> readPs1OutputAndEnv( + @Nullable Path ps1Path, + @Nullable List<@NotNull String> args, + @NotNull Consumer> scriptEnvironmentProcessor + ) throws IOException { + if (ps1Path != null && !Files.exists(ps1Path)) { + throw new NoSuchFileException(ps1Path.toString()); + } + var envDataFile = Files.createTempFile("intellij-cmd-env-data.", ".tmp"); + final String innerScriptlet; + + if (ps1Path == null) { + innerScriptlet = ""; + } + else { + var argsStr = args == null ? "" : String.join(" ", args); + innerScriptlet = String.format(Locale.ROOT, "& '%s' %s ; if (-not $?) { exit $LASTEXITCODE }; ", ps1Path, argsStr); + } + + final var scriptlet = String.format(Locale.ROOT, "& { %s & '%s' -cp '%s' %s '%s' ; exit $LASTEXITCODE }", + innerScriptlet, javaExePath(), readEnvClasspath(), READ_ENV_CLASS_NAME, envDataFile.toString()); + // Powershell 7+ with a falback + String shellName = PathEnvironmentVariableUtil.findExecutableInWindowsPath("pwsh", "powershell.exe"); + var command = List.of(shellName, "-ExecutionPolicy", "Bypass", "-NonInteractive", "-Command", scriptlet); + Path workingDir = ps1Path != null ? ps1Path.getParent() : null; + var output = + runProcessAndReadOutputAndEnvs(command, workingDir, scriptEnvironmentProcessor, envDataFile); + return new Pair<>(output.getKey(), output.getValue()); + + } + + @NotNull Map readPs1Env(Path ps1Path, List args) throws IOException { + return readPs1OutputAndEnv(ps1Path, args, (it) -> {}).second; + } + private static @NotNull String prepareCallArgs(@NotNull List callArgs) { List preparedCallArgs = CommandLineUtil.toCommandLine(callArgs); String firstArg = preparedCallArgs.remove(0); diff --git a/platform/util-rt/src/com/intellij/util/ReadEnv.java b/platform/util-rt/src/com/intellij/util/ReadEnv.java index 35df788259e1..7a4c3b0e2510 100644 --- a/platform/util-rt/src/com/intellij/util/ReadEnv.java +++ b/platform/util-rt/src/com/intellij/util/ReadEnv.java @@ -2,18 +2,18 @@ package com.intellij.util; import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; -import java.io.BufferedWriter; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Map; @Internal public final class ReadEnv { public static void main(String[] args) throws Exception { - try (Writer out = new BufferedWriter(new OutputStreamWriter(System.out, - StandardCharsets.UTF_8))) { + try (Writer out = new BufferedWriter(createWriter(args))) { for (Map.Entry each : System.getenv().entrySet()) { // On Windows, the environment may include variables that start with '=' (https://stackoverflow.com/questions/30102750). // Such variables break the output parser and are unimportant, hence are filtered out. @@ -26,4 +26,14 @@ public final class ReadEnv { } } } + + @NotNull + private static OutputStreamWriter createWriter(String[] args) throws IOException { + if( args.length > 0) { + return new OutputStreamWriter(Files.newOutputStream(Paths.get(args[0])), StandardCharsets.UTF_8); + } else { + return new OutputStreamWriter(System.out, + StandardCharsets.UTF_8); + } + } } diff --git a/platform/util/testSrc/com/intellij/util/EnvironmentUtilTest.java b/platform/util/testSrc/com/intellij/util/EnvironmentUtilTest.java index cbe0dc187f46..ae67307041d8 100644 --- a/platform/util/testSrc/com/intellij/util/EnvironmentUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/EnvironmentUtilTest.java @@ -2,12 +2,15 @@ package com.intellij.util; import com.intellij.execution.util.EnvVariablesTable; +import com.intellij.openapi.diagnostic.ExceptionWithAttachments; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -66,6 +69,18 @@ public class EnvironmentUtilTest { assertEquals("arg_value", result.get("FOO_TEST_2")); } + @Test(timeout = 30000) + public void loadingPs1Env() throws Exception { + assumeWindows(); + + File file = FileUtil.createTempFile("test file with spaces", ".ps1", true); + FileUtil.writeToFile(file, "$env:FOO_TEST_1=\"123\"\r\n$env:FOO_TEST_2=$($args[0])"); + + Map result = new EnvReader().readPs1Env(file.toPath(), Collections.singletonList("arg_value")); + assertEquals("123", result.get("FOO_TEST_1")); + assertEquals("arg_value", result.get("FOO_TEST_2")); + } + @Test(timeout = 30000) public void loadingBatEnv_ErrorHandling() throws Exception { assumeWindows(); @@ -78,10 +93,38 @@ public class EnvironmentUtilTest { fail("error should be reported"); } catch (Exception e) { - assertTrue(e.getMessage(), e.getMessage().contains("some error")); + String text = collectTextAndAttachment(e); + assertTrue(text, text.contains("some error")); } } + @Test(timeout = 30000) + public void loadingPs1Env_ErrorHandling() throws Exception { + assumeWindows(); + + File file = FileUtil.createTempFile("test_failure", ".ps1", true); + FileUtil.writeToFile(file, "echo \"some failure\"\r\nWrite-Error \"some error\"\r\nexit 100"); + + try { + new EnvReader().readPs1Env(file.toPath(), Collections.emptyList()); + fail("error should be reported"); + } + catch (Exception e) { + String errorText = collectTextAndAttachment(e); + assertTrue(errorText, errorText.contains("some error")); + assertTrue(errorText, errorText.contains("some failure")); + } + } + + private static @NotNull String collectTextAndAttachment(Exception e) { + StringBuilder errorTextBuilder = new StringBuilder(e.getMessage()); + if (e instanceof ExceptionWithAttachments) { + Arrays.stream(((ExceptionWithAttachments)e).getAttachments()).forEach(attachment -> errorTextBuilder.append(attachment.getDisplayText())); + } + String errorText = errorTextBuilder.toString(); + return errorText; + } + @Test public void testPath() { String escaped = File.pathSeparator.equals(";") ? "\\;" : File.pathSeparator;