CPP-34199 Ability to use a PowerShell script as an environment file for toolchains

PS5 and PS7 supported

GitOrigin-RevId: 49d169c274539eb749fe30c64681598f0c15e252
This commit is contained in:
Ilia Motornyi
2025-02-08 00:17:52 +02:00
committed by intellij-monorepo-bot
parent 145edae022
commit 08b5d558d0
3 changed files with 110 additions and 12 deletions

View File

@@ -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<String, Map<String, String>> readPs1OutputAndEnv(
@Nullable Path ps1Path,
@Nullable List<@NotNull String> args,
@NotNull Consumer<? super @NotNull Map<String, String>> 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<String, String> readPs1Env(Path ps1Path, List<String> args) throws IOException {
return readPs1OutputAndEnv(ps1Path, args, (it) -> {}).second;
}
private static @NotNull String prepareCallArgs(@NotNull List<String> callArgs) {
List<String> preparedCallArgs = CommandLineUtil.toCommandLine(callArgs);
String firstArg = preparedCallArgs.remove(0);

View File

@@ -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<String, String> 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);
}
}
}

View File

@@ -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<String, String> 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;