From 053119083c2c93cb1fc4129bc647c03301f4010d Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Mon, 23 Jan 2023 11:28:32 +0100 Subject: [PATCH 1/2] zig env: remove the json output The current json output is not very convenient to use from the shell or a Zig program. An example using the shell: `zig env | jq -r .lib_dir`. Remove the json output, and instead use the POSIX shell syntax: name="value" Additionally, when args is not empty, assume each argument is the environment variable name and print the associated value. Unrecognized environment variables are ignored. The new output format has been copied from `go env`, with the difference that `go env` uses OS specific syntax for windows and plan9. Define the environment variables in a single place, in order to avoid possible bugs. --- src/print_env.zig | 52 ++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/print_env.zig b/src/print_env.zig index cf4720c5f2..fcfc3f3aeb 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -1,11 +1,16 @@ const std = @import("std"); +const mem = std.mem; const build_options = @import("build_options"); const introspect = @import("introspect.zig"); const Allocator = std.mem.Allocator; const fatal = @import("main.zig").fatal; +const Env = struct { + name: []const u8, + value: []const u8, +}; + pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writer) !void { - _ = args; const self_exe_path = try introspect.findZigExePath(gpa); defer gpa.free(self_exe_path); @@ -25,32 +30,33 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ const triple = try info.target.zigTriple(gpa); defer gpa.free(triple); + const envars: []Env = &[_]Env{ + .{ .name = "zig_exe", .value = self_exe_path }, + .{ .name = "lib_dir", .value = zig_lib_directory.path.? }, + .{ .name = "std_dir", .value = zig_std_dir }, + .{ .name = "global_cache_dir", .value = global_cache_dir }, + .{ .name = "version", .value = build_options.version }, + .{ .name = "target", .value = triple }, + }; + var bw = std.io.bufferedWriter(stdout); const w = bw.writer(); - var jws = std.json.writeStream(w, .{ .whitespace = .indent_1 }); + if (args.len > 0) { + for (args) |name| { + for (envars) |env| { + if (mem.eql(u8, name, env.name)) { + try w.print("{s}\n", .{env.value}); + } + } + } + try bw.flush(); - try jws.beginObject(); + return; + } - try jws.objectField("zig_exe"); - try jws.write(self_exe_path); - - try jws.objectField("lib_dir"); - try jws.write(zig_lib_directory.path.?); - - try jws.objectField("std_dir"); - try jws.write(zig_std_dir); - - try jws.objectField("global_cache_dir"); - try jws.write(global_cache_dir); - - try jws.objectField("version"); - try jws.write(build_options.version); - - try jws.objectField("target"); - try jws.write(triple); - - try jws.endObject(); - try w.writeByte('\n'); + for (envars) |env| { + try w.print("{[name]s}=\"{[value]s}\"\n", env); + } try bw.flush(); } From 09dea957ed74f87b7f5f88bc2c760f3f93d0165a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 17 Oct 2023 21:27:09 -0700 Subject: [PATCH 2/2] rework zig env Introduce introspect.EnvVar which tracks all the environment variables that are observed by the compiler, so that we can print them with `zig env`. The `zig env` command now prints both the resolved values as well as all the possibly observed environment variables. --- src/introspect.zig | 56 +++++++++++++++++++++++------ src/libc_installation.zig | 3 +- src/main.zig | 64 ++++++++++++++------------------- src/print_env.zig | 68 +++++++++++++++--------------------- src/resinator/compile.zig | 5 +-- src/resinator/preprocess.zig | 3 +- 6 files changed, 108 insertions(+), 91 deletions(-) diff --git a/src/introspect.zig b/src/introspect.zig index 2eeae956ec..2cd19b798f 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -4,6 +4,7 @@ const mem = std.mem; const os = std.os; const fs = std.fs; const Compilation = @import("Compilation.zig"); +const build_options = @import("build_options"); /// Returns the sub_path that worked, or `null` if none did. /// The path of the returned Directory is relative to `base`. @@ -80,23 +81,17 @@ pub fn findZigLibDirFromSelfExe( /// Caller owns returned memory. pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { - if (builtin.os.tag == .wasi) { + if (builtin.os.tag == .wasi) @compileError("on WASI the global cache dir must be resolved with preopens"); - } - if (std.process.getEnvVarOwned(allocator, "ZIG_GLOBAL_CACHE_DIR")) |value| { - if (value.len > 0) { - return value; - } else { - allocator.free(value); - } - } else |_| {} + + if (try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(allocator)) |value| return value; const appname = "zig"; if (builtin.os.tag != .windows) { - if (std.os.getenv("XDG_CACHE_HOME")) |cache_root| { + if (EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { return fs.path.join(allocator, &[_][]const u8{ cache_root, appname }); - } else if (std.os.getenv("HOME")) |home| { + } else if (EnvVar.HOME.getPosix()) |home| { return fs.path.join(allocator, &[_][]const u8{ home, ".cache", appname }); } } @@ -146,3 +141,42 @@ pub fn resolvePath( pub fn isUpDir(p: []const u8) bool { return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == fs.path.sep); } + +/// Collects all the environment variables that Zig could possibly inspect, so +/// that we can do reflection on this and print them with `zig env`. +pub const EnvVar = enum { + ZIG_GLOBAL_CACHE_DIR, + ZIG_LOCAL_CACHE_DIR, + ZIG_LIB_DIR, + ZIG_LIBC, + ZIG_BUILD_RUNNER, + ZIG_VERBOSE_LINK, + ZIG_VERBOSE_CC, + ZIG_BTRFS_WORKAROUND, + CC, + NO_COLOR, + XDG_CACHE_HOME, + HOME, + /// https://github.com/ziglang/zig/issues/17585 + INCLUDE, + + pub fn isSet(comptime ev: EnvVar) bool { + return std.process.hasEnvVarConstant(@tagName(ev)); + } + + pub fn get(ev: EnvVar, arena: mem.Allocator) !?[]u8 { + // Env vars aren't used in the bootstrap stage. + if (build_options.only_c) return null; + + if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { + return value; + } else |err| switch (err) { + error.EnvironmentVariableNotFound => return null, + else => |e| return e, + } + } + + pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { + return std.os.getenvZ(@tagName(ev)); + } +}; diff --git a/src/libc_installation.zig b/src/libc_installation.zig index c93377c321..1a64e0f8bc 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -11,6 +11,7 @@ const is_haiku = builtin.target.os.tag == .haiku; const log = std.log.scoped(.libc_installation); const ZigWindowsSDK = @import("windows_sdk.zig").ZigWindowsSDK; +const EnvVar = @import("introspect.zig").EnvVar; /// See the render function implementation for documentation of the fields. pub const LibCInstallation = struct { @@ -694,7 +695,7 @@ fn appendCcExe(args: *std.ArrayList([]const u8), skip_cc_env_var: bool) !void { args.appendAssumeCapacity(default_cc_exe); return; } - const cc_env_var = std.os.getenvZ("CC") orelse { + const cc_env_var = EnvVar.CC.getPosix() orelse { args.appendAssumeCapacity(default_cc_exe); return; }; diff --git a/src/main.zig b/src/main.zig index 6dd4fb0725..2b8aa37462 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,6 +18,7 @@ const link = @import("link.zig"); const Package = @import("Package.zig"); const build_options = @import("build_options"); const introspect = @import("introspect.zig"); +const EnvVar = introspect.EnvVar; const LibCInstallation = @import("libc_installation.zig").LibCInstallation; const wasi_libc = @import("wasi_libc.zig"); const BuildId = std.Build.CompileStep.BuildId; @@ -231,14 +232,14 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi fatal("expected command argument", .{}); } - if (std.process.can_execv and std.os.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { + if (process.can_execv and std.os.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion // via child process execution due to the CC environment variable pointing to Zig. // Here we ignore the CC environment variable and exec `cc` as a child process. // However it's possible Zig is installed as *that* C compiler as well, which is // why we have this additional environment variable here to check. - var env_map = try std.process.getEnvMap(arena); + var env_map = try process.getEnvMap(arena); const inf_loop_env_key = "ZIG_IS_TRYING_TO_NOT_CALL_ITSELF"; if (env_map.get(inf_loop_env_key) != null) { @@ -254,11 +255,11 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi // CC environment variable. We detect and support this scenario here because of // the ZIG_IS_DETECTING_LIBC_PATHS environment variable. if (mem.eql(u8, args[1], "cc")) { - return std.process.execve(arena, args[1..], &env_map); + return process.execve(arena, args[1..], &env_map); } else { const modified_args = try arena.dupe([]const u8, args); modified_args[0] = "cc"; - return std.process.execve(arena, modified_args, &env_map); + return process.execve(arena, modified_args, &env_map); } } @@ -686,19 +687,6 @@ const Emit = union(enum) { } }; -fn optionalStringEnvVar(arena: Allocator, name: []const u8) !?[]const u8 { - // Env vars aren't used in the bootstrap stage. - if (build_options.only_c) { - return null; - } - if (std.process.getEnvVarOwned(arena, name)) |value| { - return value; - } else |err| switch (err) { - error.EnvironmentVariableNotFound => return null, - else => |e| return e, - } -} - const ArgMode = union(enum) { build: std.builtin.OutputMode, cc, @@ -797,8 +785,10 @@ fn buildOutputType( var no_builtin = false; var listen: Listen = .none; var debug_compile_errors = false; - var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); - var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC"); + var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and + EnvVar.ZIG_VERBOSE_LINK.isSet(); + var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and + EnvVar.ZIG_VERBOSE_CC.isSet(); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -892,15 +882,15 @@ fn buildOutputType( var each_lib_rpath: ?bool = null; var build_id: ?BuildId = null; var sysroot: ?[]const u8 = null; - var libc_paths_file: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIBC"); + var libc_paths_file: ?[]const u8 = try EnvVar.ZIG_LIBC.get(arena); var machine_code_model: std.builtin.CodeModel = .default; var runtime_args_start: ?usize = null; var test_filter: ?[]const u8 = null; var test_name_prefix: ?[]const u8 = null; var test_runner_path: ?[]const u8 = null; - var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); - var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); - var override_lib_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIB_DIR"); + var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena); + var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); + var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); var main_mod_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; @@ -960,7 +950,7 @@ fn buildOutputType( // if it exists, default the color setting to .off // explicit --color arguments will still override this setting. // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/162 - color = if (builtin.os.tag == .wasi or std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto; + color = if (builtin.os.tag == .wasi or EnvVar.NO_COLOR.isSet()) .off else .auto; switch (arg_mode) { .build, .translate_c, .zig_test, .run => { @@ -4059,18 +4049,18 @@ fn runOrTest( if (runtime_args_start) |i| { try argv.appendSlice(all_args[i..]); } - var env_map = try std.process.getEnvMap(arena); + var env_map = try process.getEnvMap(arena); try env_map.put("ZIG_EXE", self_exe_path); // We do not execve for tests because if the test fails we want to print // the error message and invocation below. - if (std.process.can_execv and arg_mode == .run) { + if (process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. - const err = std.process.execve(gpa, argv.items, &env_map); + const err = process.execve(gpa, argv.items, &env_map); try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd }); - } else if (std.process.can_spawn) { + } else if (process.can_spawn) { var child = std.ChildProcess.init(argv.items, gpa); child.env_map = &env_map; child.stdin_behavior = .Inherit; @@ -4489,7 +4479,7 @@ fn cmdRc(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]}); } - if (std.process.can_spawn) { + if (process.can_spawn) { var result = std.ChildProcess.exec(.{ .allocator = gpa, .argv = argv.items, @@ -4922,7 +4912,7 @@ pub const usage_build = pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { const work_around_btrfs_bug = builtin.os.tag == .linux and - std.process.hasEnvVarConstant("ZIG_BTRFS_WORKAROUND"); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); var color: Color = .auto; // We want to release all the locks before executing the child process, so we make a nice @@ -4931,10 +4921,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const self_exe_path = try introspect.findZigExePath(arena); var build_file: ?[]const u8 = null; - var override_lib_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIB_DIR"); - var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); - var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); - var override_build_runner: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_BUILD_RUNNER"); + var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); + var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); + var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena); + var override_build_runner: ?[]const u8 = try EnvVar.ZIG_BUILD_RUNNER.get(arena); var child_argv = std.ArrayList([]const u8).init(arena); var reference_trace: ?u32 = null; var debug_compile_errors = false; @@ -5292,7 +5282,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi break :argv child_argv.items; }; - if (std.process.can_spawn) { + if (process.can_spawn) { var child = std.ChildProcess.init(child_argv, gpa); child.stdin_behavior = .Inherit; child.stdout_behavior = .Inherit; @@ -7003,9 +6993,9 @@ fn cmdFetch( ) !void { const color: Color = .auto; const work_around_btrfs_bug = builtin.os.tag == .linux and - std.process.hasEnvVarConstant("ZIG_BTRFS_WORKAROUND"); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); var opt_path_or_url: ?[]const u8 = null; - var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); + var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); var debug_hash: bool = false; { diff --git a/src/print_env.zig b/src/print_env.zig index fcfc3f3aeb..ca64889848 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -1,62 +1,52 @@ const std = @import("std"); -const mem = std.mem; const build_options = @import("build_options"); const introspect = @import("introspect.zig"); const Allocator = std.mem.Allocator; const fatal = @import("main.zig").fatal; -const Env = struct { - name: []const u8, - value: []const u8, -}; +pub fn cmdEnv(arena: Allocator, args: []const []const u8, stdout: std.fs.File.Writer) !void { + _ = args; + const self_exe_path = try introspect.findZigExePath(arena); -pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writer) !void { - const self_exe_path = try introspect.findZigExePath(gpa); - defer gpa.free(self_exe_path); - - var zig_lib_directory = introspect.findZigLibDirFromSelfExe(gpa, self_exe_path) catch |err| { + var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); }; - defer gpa.free(zig_lib_directory.path.?); defer zig_lib_directory.handle.close(); - const zig_std_dir = try std.fs.path.join(gpa, &[_][]const u8{ zig_lib_directory.path.?, "std" }); - defer gpa.free(zig_std_dir); + const zig_std_dir = try std.fs.path.join(arena, &[_][]const u8{ zig_lib_directory.path.?, "std" }); - const global_cache_dir = try introspect.resolveGlobalCacheDir(gpa); - defer gpa.free(global_cache_dir); + const global_cache_dir = try introspect.resolveGlobalCacheDir(arena); const info = try std.zig.system.NativeTargetInfo.detect(.{}); - const triple = try info.target.zigTriple(gpa); - defer gpa.free(triple); - - const envars: []Env = &[_]Env{ - .{ .name = "zig_exe", .value = self_exe_path }, - .{ .name = "lib_dir", .value = zig_lib_directory.path.? }, - .{ .name = "std_dir", .value = zig_std_dir }, - .{ .name = "global_cache_dir", .value = global_cache_dir }, - .{ .name = "version", .value = build_options.version }, - .{ .name = "target", .value = triple }, - }; + const triple = try info.target.zigTriple(arena); var bw = std.io.bufferedWriter(stdout); const w = bw.writer(); - if (args.len > 0) { - for (args) |name| { - for (envars) |env| { - if (mem.eql(u8, name, env.name)) { - try w.print("{s}\n", .{env.value}); - } - } + try w.print( + \\zig_exe={s} + \\lib_dir={s} + \\std_dir={s} + \\global_cache_dir={s} + \\version={s} + \\target={s} + \\ + , .{ + self_exe_path, + zig_lib_directory.path.?, + zig_std_dir, + global_cache_dir, + build_options.version, + triple, + }); + + inline for (@typeInfo(introspect.EnvVar).Enum.fields) |field| { + if (try @field(introspect.EnvVar, field.name).get(arena)) |value| { + try w.print("{s}={s}\n", .{ field.name, value }); + } else { + try w.print("{s}\n", .{field.name}); } - try bw.flush(); - - return; } - for (envars) |env| { - try w.print("{[name]s}=\"{[value]s}\"\n", env); - } try bw.flush(); } diff --git a/src/resinator/compile.zig b/src/resinator/compile.zig index d0c4172d2f..fb4a8a2432 100644 --- a/src/resinator/compile.zig +++ b/src/resinator/compile.zig @@ -28,6 +28,7 @@ const windows1252 = @import("windows1252.zig"); const lang = @import("lang.zig"); const code_pages = @import("code_pages.zig"); const errors = @import("errors.zig"); +const introspect = @import("../introspect.zig"); pub const CompileOptions = struct { cwd: std.fs.Dir, @@ -91,7 +92,7 @@ pub fn compile(allocator: Allocator, source: []const u8, writer: anytype, option // `catch unreachable` since `options.cwd` is expected to be a valid dir handle, so opening // a new handle to it should be fine as well. // TODO: Maybe catch and return an error instead - const cwd_dir = options.cwd.openDir(".", .{}) catch unreachable; + const cwd_dir = options.cwd.openDir(".", .{}) catch @panic("unable to open dir"); try search_dirs.append(.{ .dir = cwd_dir, .path = null }); for (options.extra_include_paths) |extra_include_path| { var dir = openSearchPathDir(options.cwd, extra_include_path) catch { @@ -110,7 +111,7 @@ pub fn compile(allocator: Allocator, source: []const u8, writer: anytype, option try search_dirs.append(.{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) }); } if (!options.ignore_include_env_var) { - const INCLUDE = std.process.getEnvVarOwned(allocator, "INCLUDE") catch ""; + const INCLUDE = (introspect.EnvVar.INCLUDE.get(allocator) catch @panic("OOM")) orelse ""; defer allocator.free(INCLUDE); // TODO: Should this be platform-specific? How does windres/llvm-rc handle this (if at all)? diff --git a/src/resinator/preprocess.zig b/src/resinator/preprocess.zig index e831c8147c..9a214f1fad 100644 --- a/src/resinator/preprocess.zig +++ b/src/resinator/preprocess.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const cli = @import("cli.zig"); +const introspect = @import("../introspect.zig"); pub const IncludeArgs = struct { clang_target: ?[]const u8 = null, @@ -67,7 +68,7 @@ pub fn appendClangArgs(arena: Allocator, argv: *std.ArrayList([]const u8), optio } if (!options.ignore_include_env_var) { - const INCLUDE = std.process.getEnvVarOwned(arena, "INCLUDE") catch ""; + const INCLUDE = (introspect.EnvVar.INCLUDE.get(arena) catch @panic("OOM")) orelse ""; // TODO: Should this be platform-specific? How does windres/llvm-rc handle this (if at all)? var it = std.mem.tokenize(u8, INCLUDE, ";");