master
1const std = @import("std");
2const builtin = @import("builtin");
3const mem = std.mem;
4const Allocator = mem.Allocator;
5const os = std.os;
6const fs = std.fs;
7const Cache = std.Build.Cache;
8const Compilation = @import("Compilation.zig");
9const Package = @import("Package.zig");
10const build_options = @import("build_options");
11
12/// Returns the sub_path that worked, or `null` if none did.
13/// The path of the returned Directory is relative to `base`.
14/// The handle of the returned Directory is open.
15fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory {
16 const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig";
17
18 zig_dir: {
19 // Try lib/zig/std/std.zig
20 const lib_zig = "lib" ++ fs.path.sep_str ++ "zig";
21 var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir;
22 const file = test_zig_dir.openFile(test_index_file, .{}) catch {
23 test_zig_dir.close();
24 break :zig_dir;
25 };
26 file.close();
27 return .{ .handle = test_zig_dir, .path = lib_zig };
28 }
29
30 // Try lib/std/std.zig
31 var test_zig_dir = base_dir.openDir("lib", .{}) catch return null;
32 const file = test_zig_dir.openFile(test_index_file, .{}) catch {
33 test_zig_dir.close();
34 return null;
35 };
36 file.close();
37 return .{ .handle = test_zig_dir, .path = "lib" };
38}
39
40/// Both the directory handle and the path are newly allocated resources which the caller now owns.
41pub fn findZigLibDir(gpa: Allocator) !Cache.Directory {
42 const cwd_path = try getResolvedCwd(gpa);
43 defer gpa.free(cwd_path);
44 const self_exe_path = try fs.selfExePathAlloc(gpa);
45 defer gpa.free(self_exe_path);
46
47 return findZigLibDirFromSelfExe(gpa, cwd_path, self_exe_path);
48}
49
50/// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This
51/// means the path has no repeated separators, no "." or ".." components, and no trailing separator.
52/// On WASI, "" is returned instead of ".".
53pub fn getResolvedCwd(gpa: Allocator) error{
54 OutOfMemory,
55 CurrentWorkingDirectoryUnlinked,
56 Unexpected,
57}![]u8 {
58 if (builtin.target.os.tag == .wasi) {
59 if (std.debug.runtime_safety) {
60 const cwd = try std.process.getCwdAlloc(gpa);
61 defer gpa.free(cwd);
62 std.debug.assert(mem.eql(u8, cwd, "."));
63 }
64 return "";
65 }
66 const cwd = try std.process.getCwdAlloc(gpa);
67 defer gpa.free(cwd);
68 const resolved = try fs.path.resolve(gpa, &.{cwd});
69 std.debug.assert(fs.path.isAbsolute(resolved));
70 return resolved;
71}
72
73/// Both the directory handle and the path are newly allocated resources which the caller now owns.
74pub fn findZigLibDirFromSelfExe(
75 allocator: Allocator,
76 /// The return value of `getResolvedCwd`.
77 /// Passed as an argument to avoid pointlessly repeating the call.
78 cwd_path: []const u8,
79 self_exe_path: []const u8,
80) error{ OutOfMemory, FileNotFound }!Cache.Directory {
81 const cwd = fs.cwd();
82 var cur_path: []const u8 = self_exe_path;
83 while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
84 var base_dir = cwd.openDir(dirname, .{}) catch continue;
85 defer base_dir.close();
86
87 const sub_directory = testZigInstallPrefix(base_dir) orelse continue;
88 const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? });
89 defer allocator.free(p);
90
91 const resolved = try resolvePath(allocator, cwd_path, &.{p});
92 return .{
93 .handle = sub_directory.handle,
94 .path = if (resolved.len == 0) null else resolved,
95 };
96 }
97 return error.FileNotFound;
98}
99
100/// Caller owns returned memory.
101pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 {
102 if (builtin.os.tag == .wasi)
103 @compileError("on WASI the global cache dir must be resolved with preopens");
104
105 if (try std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(allocator)) |value| return value;
106
107 const appname = "zig";
108
109 if (builtin.os.tag != .windows) {
110 if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| {
111 if (cache_root.len > 0) {
112 return fs.path.join(allocator, &.{ cache_root, appname });
113 }
114 }
115 if (std.zig.EnvVar.HOME.getPosix()) |home| {
116 return fs.path.join(allocator, &.{ home, ".cache", appname });
117 }
118 }
119
120 return fs.getAppDataDir(allocator, appname);
121}
122
123/// Similar to `fs.path.resolve`, but converts to a cwd-relative path, or, if that would
124/// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd
125/// returns the empty string ("") instead of ".".
126pub fn resolvePath(
127 gpa: Allocator,
128 /// The return value of `getResolvedCwd`.
129 /// Passed as an argument to avoid pointlessly repeating the call.
130 cwd_resolved: []const u8,
131 paths: []const []const u8,
132) Allocator.Error![]u8 {
133 if (builtin.target.os.tag == .wasi) {
134 std.debug.assert(mem.eql(u8, cwd_resolved, ""));
135 const res = try fs.path.resolve(gpa, paths);
136 if (mem.eql(u8, res, ".")) {
137 gpa.free(res);
138 return "";
139 }
140 return res;
141 }
142
143 // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`.
144 for (paths) |p| {
145 if (fs.path.isAbsolute(p)) break; // absolute path
146 if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir
147 } else {
148 // no absolute path, no "..".
149 const res = try fs.path.resolve(gpa, paths);
150 if (mem.eql(u8, res, ".")) {
151 gpa.free(res);
152 return "";
153 }
154 std.debug.assert(!fs.path.isAbsolute(res));
155 std.debug.assert(!isUpDir(res));
156 return res;
157 }
158
159 // The fast path failed; resolve the whole thing.
160 // Optimization: `paths` often has just one element.
161 const path_resolved = switch (paths.len) {
162 0 => unreachable,
163 1 => try fs.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),
164 else => r: {
165 const all_paths = try gpa.alloc([]const u8, paths.len + 1);
166 defer gpa.free(all_paths);
167 all_paths[0] = cwd_resolved;
168 @memcpy(all_paths[1..], paths);
169 break :r try fs.path.resolve(gpa, all_paths);
170 },
171 };
172 errdefer gpa.free(path_resolved);
173
174 std.debug.assert(fs.path.isAbsolute(path_resolved));
175 std.debug.assert(fs.path.isAbsolute(cwd_resolved));
176
177 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd
178 if (path_resolved.len == cwd_resolved.len) {
179 // equal to cwd
180 gpa.free(path_resolved);
181 return "";
182 }
183 if (path_resolved[cwd_resolved.len] != std.fs.path.sep) return path_resolved; // not in cwd (last component differs)
184
185 // in cwd; extract sub path
186 const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]);
187 gpa.free(path_resolved);
188 return sub_path;
189}
190
191/// TODO move this to std.fs.path
192pub fn isUpDir(p: []const u8) bool {
193 return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == fs.path.sep);
194}
195
196pub const default_local_zig_cache_basename = ".zig-cache";
197
198/// Searches upwards from `cwd` for a directory containing a `build.zig` file.
199/// If such a directory is found, returns the path to it joined to the `.zig_cache` name.
200/// Otherwise, returns `null`, indicating no suitable local cache location.
201pub fn resolveSuitableLocalCacheDir(arena: Allocator, cwd: []const u8) Allocator.Error!?[]u8 {
202 var cur_dir = cwd;
203 while (true) {
204 const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename });
205 if (fs.cwd().access(joined, .{})) |_| {
206 return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });
207 } else |err| switch (err) {
208 error.FileNotFound => {
209 cur_dir = fs.path.dirname(cur_dir) orelse return null;
210 continue;
211 },
212 else => return null,
213 }
214 }
215}