authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-22 17:35:36-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-22 17:35:36-05:00
logcfffb9c5e96eeeae43cd724e2d02ec8c2b7714e0
tree342ef6e97cd6f0a3a65839ab0b5e5326a9ea296d
parent936d0b18b116ea126893d2f165f9be72f8bef845
signature Commit is signed but in an unrecognized format.

improve handling of environment variables on Windows

std.os.getenv and std.os.getenvZ have nice compile errors when not linking libc and using Windows. std.os.getenvW is provided as a Windows-only API that does not require an allocator. It uses the Process Environment Block. std.process.getEnvVarOwned is improved to be a simple wrapper on top of std.os.getenvW. std.process.getEnvMap is improved to use the Process Environment Block rather than calling GetEnvironmentVariableW. std.zig.system.NativePaths uses process.getEnvVarOwned instead of std.os.getenvZ, which works on Windows as well as POSIX.

5 files changed, 98 insertions(+), 71 deletions(-)

lib/std/os.zig+34-8
...@@ -1104,10 +1104,6 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8)...@@ -1104,10 +1104,6 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8)
1104/// Get an environment variable.1104/// Get an environment variable.
1105/// See also `getenvZ`.1105/// See also `getenvZ`.
1106pub fn getenv(key: []const u8) ?[]const u8 {1106pub fn getenv(key: []const u8) ?[]const u8 {
1107 if (builtin.os == .windows) {
1108 // TODO update this to use the ProcessEnvironmentBlock
1109 @compileError("TODO implement std.os.getenv for Windows");
1110 }
1111 if (builtin.link_libc) {1107 if (builtin.link_libc) {
1112 var small_key_buf: [64]u8 = undefined;1108 var small_key_buf: [64]u8 = undefined;
1113 if (key.len < small_key_buf.len) {1109 if (key.len < small_key_buf.len) {
...@@ -1133,6 +1129,9 @@ pub fn getenv(key: []const u8) ?[]const u8 {...@@ -1133,6 +1129,9 @@ pub fn getenv(key: []const u8) ?[]const u8 {
1133 }1129 }
1134 return null;1130 return null;
1135 }1131 }
1132 if (builtin.os == .windows) {
1133 @compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
1134 }
1136 // TODO see https://github.com/ziglang/zig/issues/45241135 // TODO see https://github.com/ziglang/zig/issues/4524
1137 for (environ) |ptr| {1136 for (environ) |ptr| {
1138 var line_i: usize = 0;1137 var line_i: usize = 0;
...@@ -1155,17 +1154,44 @@ pub const getenvC = getenvZ;...@@ -1155,17 +1154,44 @@ pub const getenvC = getenvZ;
1155/// Get an environment variable with a null-terminated name.1154/// Get an environment variable with a null-terminated name.
1156/// See also `getenv`.1155/// See also `getenv`.
1157pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {1156pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {
1158 if (builtin.os == .windows) {
1159 // TODO update this to use the ProcessEnvironmentBlock
1160 @compileError("TODO implement std.os.getenv for Windows");
1161 }
1162 if (builtin.link_libc) {1157 if (builtin.link_libc) {
1163 const value = system.getenv(key) orelse return null;1158 const value = system.getenv(key) orelse return null;
1164 return mem.toSliceConst(u8, value);1159 return mem.toSliceConst(u8, value);
1165 }1160 }
1161 if (builtin.os == .windows) {
1162 @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API.");
1163 }
1166 return getenv(mem.toSliceConst(u8, key));1164 return getenv(mem.toSliceConst(u8, key));
1167}1165}
11681166
1167/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
1168/// See also `getenv`.
1169pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
1170 if (builtin.os != .windows) {
1171 @compileError("std.os.getenvW is a Windows-only API");
1172 }
1173 const key_slice = mem.toSliceConst(u16, key);
1174 const ptr = windows.peb().ProcessParameters.Environment;
1175 var i: usize = 0;
1176 while (ptr[i] != 0) {
1177 const key_start = i;
1178
1179 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
1180 const this_key = ptr[key_start..i];
1181
1182 if (ptr[i] == '=') i += 1;
1183
1184 const value_start = i;
1185 while (ptr[i] != 0) : (i += 1) {}
1186 const this_value = ptr[value_start..i :0];
1187
1188 if (mem.eql(u16, key_slice, this_key)) return this_value;
1189
1190 i += 1; // skip over null byte
1191 }
1192 return null;
1193}
1194
1169pub const GetCwdError = error{1195pub const GetCwdError = error{
1170 NameTooLong,1196 NameTooLong,
1171 CurrentWorkingDirectoryUnlinked,1197 CurrentWorkingDirectoryUnlinked,
lib/std/os/test.zig+8
...@@ -351,3 +351,11 @@ test "mmap" {...@@ -351,3 +351,11 @@ test "mmap" {
351351
352 try fs.cwd().deleteFile(test_out_file);352 try fs.cwd().deleteFile(test_out_file);
353}353}
354
355test "getenv" {
356 if (builtin.os == .windows) {
357 expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
358 } else {
359 expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
360 }
361}
lib/std/os/windows/bits.zig+1-1
...@@ -1187,7 +1187,7 @@ pub const RTL_USER_PROCESS_PARAMETERS = extern struct {...@@ -1187,7 +1187,7 @@ pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
1187 DllPath: UNICODE_STRING,1187 DllPath: UNICODE_STRING,
1188 ImagePathName: UNICODE_STRING,1188 ImagePathName: UNICODE_STRING,
1189 CommandLine: UNICODE_STRING,1189 CommandLine: UNICODE_STRING,
1190 Environment: [*]WCHAR,1190 Environment: [*:0]WCHAR,
1191 dwX: ULONG,1191 dwX: ULONG,
1192 dwY: ULONG,1192 dwY: ULONG,
1193 dwXSize: ULONG,1193 dwXSize: ULONG,
lib/std/process.zig+14-32
...@@ -37,14 +37,11 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {...@@ -37,14 +37,11 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
37 errdefer result.deinit();37 errdefer result.deinit();
3838
39 if (builtin.os == .windows) {39 if (builtin.os == .windows) {
40 // TODO update this to use the ProcessEnvironmentBlock40 const ptr = windows.peb().ProcessParameters.Environment;
41 const ptr = try os.windows.GetEnvironmentStringsW();
42 defer os.windows.FreeEnvironmentStringsW(ptr);41 defer os.windows.FreeEnvironmentStringsW(ptr);
4342
44 var i: usize = 0;43 var i: usize = 0;
45 while (true) {44 while (ptr[i] != 0) {
46 if (ptr[i] == 0) return result;
47
48 const key_start = i;45 const key_start = i;
4946
50 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}47 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
...@@ -64,6 +61,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {...@@ -64,6 +61,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
6461
65 try result.setMove(key, value);62 try result.setMove(key, value);
66 }63 }
64 return result;
67 } else if (builtin.os == .wasi) {65 } else if (builtin.os == .wasi) {
68 var environ_count: usize = undefined;66 var environ_count: usize = undefined;
69 var environ_buf_size: usize = undefined;67 var environ_buf_size: usize = undefined;
...@@ -141,34 +139,18 @@ pub const GetEnvVarOwnedError = error{...@@ -141,34 +139,18 @@ pub const GetEnvVarOwnedError = error{
141/// Caller must free returned memory.139/// Caller must free returned memory.
142pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {140pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
143 if (builtin.os == .windows) {141 if (builtin.os == .windows) {
144 const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);142 const result_w = blk: {
145 defer allocator.free(key_with_null);143 const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
146144 defer allocator.free(key_w);
147 var buf = try allocator.alloc(u16, 256);
148 defer allocator.free(buf);
149
150 while (true) {
151 const windows_buf_len = math.cast(os.windows.DWORD, buf.len) catch return error.OutOfMemory;
152 const result = os.windows.GetEnvironmentVariableW(
153 key_with_null.ptr,
154 buf.ptr,
155 windows_buf_len,
156 ) catch |err| switch (err) {
157 error.Unexpected => return error.EnvironmentVariableNotFound,
158 else => |e| return e,
159 };
160 if (result > buf.len) {
161 buf = try allocator.realloc(buf, result);
162 continue;
163 }
164145
165 return std.unicode.utf16leToUtf8Alloc(allocator, buf[0..result]) catch |err| switch (err) {146 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
166 error.DanglingSurrogateHalf => return error.InvalidUtf8,147 };
167 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,148 return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
168 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,149 error.DanglingSurrogateHalf => return error.InvalidUtf8,
169 else => |e| return e,150 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
170 };151 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
171 }152 else => |e| return e,
153 };
172 } else {154 } else {
173 const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound;155 const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound;
174 return mem.dupe(allocator, u8, result);156 return mem.dupe(allocator, u8, result);
lib/std/zig/system.zig+41-30
...@@ -5,6 +5,8 @@ const ArrayList = std.ArrayList;...@@ -5,6 +5,8 @@ const ArrayList = std.ArrayList;
5const assert = std.debug.assert;5const assert = std.debug.assert;
6const process = std.process;6const process = std.process;
77
8const is_windows = std.Target.current.isWindows();
9
8pub const NativePaths = struct {10pub const NativePaths = struct {
9 include_dirs: ArrayList([:0]u8),11 include_dirs: ArrayList([:0]u8),
10 lib_dirs: ArrayList([:0]u8),12 lib_dirs: ArrayList([:0]u8),
...@@ -21,7 +23,9 @@ pub const NativePaths = struct {...@@ -21,7 +23,9 @@ pub const NativePaths = struct {
21 errdefer self.deinit();23 errdefer self.deinit();
2224
23 var is_nix = false;25 var is_nix = false;
24 if (std.os.getenvZ("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {26 if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
27 defer allocator.free(nix_cflags_compile);
28
25 is_nix = true;29 is_nix = true;
26 var it = mem.tokenize(nix_cflags_compile, " ");30 var it = mem.tokenize(nix_cflags_compile, " ");
27 while (true) {31 while (true) {
...@@ -37,8 +41,14 @@ pub const NativePaths = struct {...@@ -37,8 +41,14 @@ pub const NativePaths = struct {
37 break;41 break;
38 }42 }
39 }43 }
44 } else |err| switch (err) {
45 error.InvalidUtf8 => {},
46 error.EnvironmentVariableNotFound => {},
47 error.OutOfMemory => |e| return e,
40 }48 }
41 if (std.os.getenvZ("NIX_LDFLAGS")) |nix_ldflags| {49 if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
50 defer allocator.free(nix_ldflags);
51
42 is_nix = true;52 is_nix = true;
43 var it = mem.tokenize(nix_ldflags, " ");53 var it = mem.tokenize(nix_ldflags, " ");
44 while (true) {54 while (true) {
...@@ -57,39 +67,40 @@ pub const NativePaths = struct {...@@ -57,39 +67,40 @@ pub const NativePaths = struct {
57 break;67 break;
58 }68 }
59 }69 }
70 } else |err| switch (err) {
71 error.InvalidUtf8 => {},
72 error.EnvironmentVariableNotFound => {},
73 error.OutOfMemory => |e| return e,
60 }74 }
61 if (is_nix) {75 if (is_nix) {
62 return self;76 return self;
63 }77 }
6478
65 switch (std.builtin.os) {79 if (!is_windows) {
66 .windows => {},80 const triple = try std.Target.current.linuxTriple(allocator);
67 else => {81
68 const triple = try std.Target.current.linuxTriple(allocator);82 // TODO: $ ld --verbose | grep SEARCH_DIR
6983 // the output contains some paths that end with lib64, maybe include them too?
70 // TODO: $ ld --verbose | grep SEARCH_DIR84 // TODO: what is the best possible order of things?
71 // the output contains some paths that end with lib64, maybe include them too?85 // TODO: some of these are suspect and should only be added on some systems. audit needed.
72 // TODO: what is the best possible order of things?86
73 // TODO: some of these are suspect and should only be added on some systems. audit needed.87 try self.addIncludeDir("/usr/local/include");
7488 try self.addLibDir("/usr/local/lib");
75 try self.addIncludeDir("/usr/local/include");89 try self.addLibDir("/usr/local/lib64");
76 try self.addLibDir("/usr/local/lib");90
77 try self.addLibDir("/usr/local/lib64");91 try self.addIncludeDirFmt("/usr/include/{}", .{triple});
7892 try self.addLibDirFmt("/usr/lib/{}", .{triple});
79 try self.addIncludeDirFmt("/usr/include/{}", .{triple});93
80 try self.addLibDirFmt("/usr/lib/{}", .{triple});94 try self.addIncludeDir("/usr/include");
8195 try self.addLibDir("/lib");
82 try self.addIncludeDir("/usr/include");96 try self.addLibDir("/lib64");
83 try self.addLibDir("/lib");97 try self.addLibDir("/usr/lib");
84 try self.addLibDir("/lib64");98 try self.addLibDir("/usr/lib64");
85 try self.addLibDir("/usr/lib");99
86 try self.addLibDir("/usr/lib64");100 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
87101 // zlib.h is in /usr/include (added above)
88 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:102 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
89 // zlib.h is in /usr/include (added above)103 try self.addLibDirFmt("/lib/{}", .{triple});
90 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
91 try self.addLibDirFmt("/lib/{}", .{triple});
92 },
93 }104 }
94105
95 return self;106 return self;