feat: IO.Process.SpawnArgs.inheritEnv (#6081)

This PR adds an `inheritEnv` field to `IO.Process.SpawnArgs`. If
`false`, the spawned process does not inherit its parent's environment.

For example, Lake will make use of this to ensure that build processes
do not use environment variables that Lake is not properly tracking with
its traces.
This commit is contained in:
Mac Malone 2025-04-15 20:25:32 -04:00 committed by GitHub
parent 46769b64c9
commit b51115dac5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 32 deletions

View file

@ -1355,6 +1355,8 @@ structure SpawnArgs extends StdioConfig where
and `some` sets the variable to the new value, adding it if necessary. Variables are processed from left to right.
-/
env : Array (String × Option String) := #[]
/-- Inherit environment variables from the spawning process. -/
inheritEnv : Bool := true
/--
Starts the child process in a new session and process group using `setsid`. Currently a no-op on
non-POSIX platforms.

View file

@ -172,7 +172,7 @@ static void setup_stdio(SECURITY_ATTRIBUTES * saAttr, HANDLE * theirs, object **
// This code is adapted from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const & args, stdio stdin_mode, stdio stdout_mode,
stdio stderr_mode, option_ref<string_ref> const & cwd, array_ref<pair_ref<string_ref, option_ref<string_ref>>> const & env,
bool _do_setsid) {
bool inherit_env, bool _do_setsid) {
HANDLE child_stdin = GetStdHandle(STD_INPUT_HANDLE);
HANDLE child_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE child_stderr = GetStdHandle(STD_ERROR_HANDLE);
@ -229,38 +229,45 @@ static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const &
std::unique_ptr<char[]> new_env(nullptr);
if (env.size()) {
auto * esp = GetEnvironmentStrings();
std::unordered_map<std::string, std::string> new_env_vars; // C++17 gives us no-copy std::string_view for this, much better!
for (auto & entry : env) {
new_env_vars[entry.fst().data()] = entry.snd() ? entry.snd().get()->data() : std::string{};
}
if (env.size() || !inherit_env) {
static constexpr auto env_buf_size = 0x7fff; // according to MS docs 0x7fff is the max total size of env block
new_env = std::make_unique<char[]>(env_buf_size);
// First copy old evars not in new evars.
char *new_envp = new_env.get(), *key_begin = esp;
while (*key_begin) {
char *key_end = strchr(key_begin, '=');
char *entry_end = key_end + strlen(key_end);
if (!new_env_vars.count({key_begin, key_end})) {
new_envp = std::copy(key_begin, entry_end + 1, new_envp);
}
key_begin = entry_end + 1;
}
// Then copy new evars if nonempty
for(const auto & ev : new_env_vars) {
if (ev.second.empty()) continue;
// Check if the destination buffer has enough room.
if (new_envp + ev.first.length() + 1 + ev.second.length() + 1 > new_env.get() + env_buf_size - 1) break;
new_envp = std::copy(ev.first.cbegin(), ev.first.cend(), new_envp);
*new_envp++ = '=';
new_envp = std::copy(ev.second.cbegin(), ev.second.cend(), new_envp);
*new_envp++ = '\0';
}
*new_envp = '\0';
char *new_envp = new_env.get();
FreeEnvironmentStrings(esp);
if (env.size()) {
std::unordered_map<std::string, std::string> new_env_vars; // C++17 gives us no-copy std::string_view for this, much better!
for (auto & entry : env) {
new_env_vars[entry.fst().data()] = entry.snd() ? entry.snd().get()->data() : std::string{};
}
// First copy old evars not in new evars.
if (inherit_env) {
auto *esp = GetEnvironmentStrings();
char *key_begin = esp;
while (*key_begin) {
char *key_end = strchr(key_begin, '=');
char *entry_end = key_end + strlen(key_end);
if (!new_env_vars.count({key_begin, key_end})) {
new_envp = std::copy(key_begin, entry_end + 1, new_envp);
}
key_begin = entry_end + 1;
}
FreeEnvironmentStrings(esp);
}
// Then copy new evars if nonempty
for(const auto & ev : new_env_vars) {
if (ev.second.empty()) continue;
// Check if the destination buffer has enough room.
if (new_envp + ev.first.length() + 1 + ev.second.length() + 1 > new_env.get() + env_buf_size - 1) break;
new_envp = std::copy(ev.first.cbegin(), ev.first.cend(), new_envp);
*new_envp++ = '=';
new_envp = std::copy(ev.second.cbegin(), ev.second.cend(), new_envp);
*new_envp++ = '\0';
}
}
*new_envp = '\0';
}
// Create the child process.
@ -423,9 +430,13 @@ static optional<pipe> setup_stdio(stdio cfg) {
lean_unreachable();
}
#ifdef __APPLE__
extern "C" char **environ;
#endif
static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const & args, stdio stdin_mode, stdio stdout_mode,
stdio stderr_mode, option_ref<string_ref> const & cwd, array_ref<pair_ref<string_ref, option_ref<string_ref>>> const & env,
bool do_setsid) {
bool inherit_env, bool do_setsid) {
/* Setup stdio based on process configuration. */
auto stdin_pipe = setup_stdio(stdin_mode);
auto stdout_pipe = setup_stdio(stdout_mode);
@ -434,6 +445,13 @@ static obj_res spawn(string_ref const & proc_name, array_ref<string_ref> const &
int pid = fork();
if (pid == 0) {
if (!inherit_env) {
#ifdef __APPLE__
environ = NULL;
#else
clearenv();
#endif
}
for (auto & entry : env) {
if (entry.snd()) {
setenv(entry.fst().data(), entry.snd().get()->data(), true);
@ -549,7 +567,8 @@ extern "C" LEAN_EXPORT obj_res lean_io_process_spawn(obj_arg args_, obj_arg) {
stderr_mode,
cnstr_get_ref_t<option_ref<string_ref>>(args, 3),
cnstr_get_ref_t<array_ref<pair_ref<string_ref, option_ref<string_ref>>>>(args, 4),
cnstr_get_uint8(args.raw(), 5 * sizeof(object *)));
cnstr_get_uint8(args.raw(), 5 * sizeof(object *)),
cnstr_get_uint8(args.raw(), 5 * sizeof(object *) + 1));
} catch (int err) {
return lean_io_result_mk_error(decode_io_error(err, nullptr));
} catch (std::system_error const & err) {