Skip to content

Commit 0f64bed

Browse files
authored
Merge System::Process.make_env_block into System::Env (win32) (#16605)
Environment variable keys are case-insensitive on Windows. This is currently emulated by using a `Hash` with upcased keys to key/value pairs. This patch simplifies this with case-insensitive string comparisons and consolidates the two methods into a single one in the `System::Env` module. The `System::Env.make_env_block` method is now close to the `System::Env.make_envp` method for UNIX targets.
1 parent 3265ca7 commit 0f64bed

File tree

2 files changed

+31
-29
lines changed

2 files changed

+31
-29
lines changed

src/crystal/system/win32/env.cr

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,40 @@ module Crystal::System::Env
8181
end
8282
end
8383

84-
# Used internally to create an input for `CreateProcess` `lpEnvironment`.
85-
def self.make_env_block(env : Enumerable({String, String}))
84+
# Used internally to create an input for `CreateProcessW` `lpEnvironment`.
85+
def self.make_env_block(env, clear_env)
86+
# If neither clearing nor adding anything, use the default behavior of
87+
# inheriting everything.
88+
return Pointer(UInt16).null if !env && !clear_env
89+
8690
# NOTE: the entire string contains embedded null bytes so we can't use
8791
# `System.to_wstr` here
8892
String.build do |io|
89-
env.each do |(key, value)|
90-
check_valid_key(key)
91-
io << key.check_no_null_byte("key") << '=' << value.check_no_null_byte("value") << '\0'
93+
unless clear_env
94+
each do |key, value|
95+
# skip override
96+
next if env.try(&.any? { |k, _| k.compare(key, case_insensitive: true) == 0 })
97+
98+
io << key.check_no_null_byte("key")
99+
io << '='
100+
io << value.check_no_null_byte("value")
101+
io << '\0'
102+
end
92103
end
104+
105+
env.try(&.each do |key, value|
106+
check_valid_key(key)
107+
108+
# skip deletion
109+
next if value.nil?
110+
111+
io << key.check_no_null_byte("key")
112+
io << '='
113+
io << value.check_no_null_byte("value")
114+
io << '\0'
115+
end)
116+
117+
# terminate the block
93118
io << '\0'
94119
end.to_utf16.to_unsafe
95120
end

src/crystal/system/win32/process.cr

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ struct Crystal::System::Process
302302

303303
if LibC.CreateProcessW(
304304
nil, System.to_wstr(prepared_args), nil, nil, true, LibC::CREATE_SUSPENDED | LibC::CREATE_UNICODE_ENVIRONMENT,
305-
make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
305+
Env.make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
306306
pointerof(startup_info), pointerof(process_info)
307307
) == 0
308308
error = WinError.value
@@ -457,29 +457,6 @@ struct Crystal::System::Process
457457
def self.chroot(path)
458458
raise NotImplementedError.new("Process.chroot")
459459
end
460-
461-
protected def self.make_env_block(env, clear_env : Bool) : UInt16*
462-
# If neither clearing nor adding anything, use the default behavior of inheriting everything.
463-
return Pointer(UInt16).null if !env && !clear_env
464-
465-
# Emulate case-insensitive behavior using a Hash like {"KEY" => {"kEy", "value"}, ...}
466-
final_env = {} of String => {String, String}
467-
unless clear_env
468-
Crystal::System::Env.each do |key, val|
469-
final_env[key.upcase] = {key, val}
470-
end
471-
end
472-
env.try &.each do |(key, val)|
473-
if val
474-
# Note: in the case of overriding, the last "case-spelling" of the key wins.
475-
final_env[key.upcase] = {key, val}
476-
else
477-
final_env.delete key.upcase
478-
end
479-
end
480-
# The "values" we're passing are actually key-value pairs.
481-
Crystal::System::Env.make_env_block(final_env.each_value)
482-
end
483460
end
484461

485462
private def close_handle(handle)

0 commit comments

Comments
 (0)