Skip to content

Commit 7f38429

Browse files
committed
Add support for local copy of dependency trees
This change is still in progress, currently by providing the argument `--cache-dependencies-locally`, you can have grabbed dependencies stored in a local `zig-deps` folder which theoretically should be viable for checking in to your VCS. However - there are a few unsolved problems: - dependenices which are no longer depended on should probably be removed. - dependencies being stored flatly in the zig-deps folder by hash makes it non-obvious which dependency is which (from a user POV). Additionally, if you updated dependencies the hash (and by extension folder name) will change - this then makes tracking logical changes to dependencies in your version control system trickier. I'm not entirely sure how to best solve these problems - one could be to have some kind of naming-resolution scheme, alongside a local lock file mapping names to hashes? This could be combined with a build option to force hashes to be recomputed to validate the contents of these folders have not changed (but having it as an option as it would be slower).
1 parent 64ef45e commit 7f38429

File tree

5 files changed

+179
-42
lines changed

5 files changed

+179
-42
lines changed

lib/compiler/build_runner.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ pub fn main() !void {
213213
builder.verbose_link = true;
214214
} else if (mem.eql(u8, arg, "--verbose-air")) {
215215
builder.verbose_air = true;
216+
} else if (mem.eql(u8, arg, "--cache-dependencies-locally")) {
217+
builder.cache_dependencies_locally = true;
216218
} else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
217219
builder.verbose_llvm_ir = "-";
218220
} else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
@@ -1184,6 +1186,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
11841186
\\ --build-file [file] Override path to build.zig
11851187
\\ --cache-dir [path] Override path to local Zig cache directory
11861188
\\ --global-cache-dir [path] Override path to global Zig cache directory
1189+
\\ --cache-dependencies-locally Cache dependencies locally in zig-deps
11871190
\\ --zig-lib-dir [arg] Override path to Zig lib directory
11881191
\\ --build-runner [file] Override path to build runner
11891192
\\ --seed [integer] For shuffling dependency traversal order (default: random)

lib/std/Build.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ verbose: bool,
3333
verbose_link: bool,
3434
verbose_cc: bool,
3535
verbose_air: bool,
36+
cache_dependencies_locally: bool,
3637
verbose_llvm_ir: ?[]const u8,
3738
verbose_llvm_bc: ?[]const u8,
3839
verbose_cimport: bool,
@@ -254,6 +255,7 @@ pub fn create(
254255
.graph = graph,
255256
.build_root = build_root,
256257
.cache_root = cache_root,
258+
.cache_dependencies_locally = false,
257259
.verbose = false,
258260
.verbose_link = false,
259261
.verbose_cc = false,
@@ -380,6 +382,7 @@ fn createChildOnly(
380382
.installed_files = ArrayList(InstalledFile).init(allocator),
381383
.build_root = build_root,
382384
.cache_root = parent.cache_root,
385+
.cache_dependencies_locally = parent.cache_dependencies_locally,
383386
.zig_lib_dir = parent.zig_lib_dir,
384387
.debug_log_scopes = parent.debug_log_scopes,
385388
.debug_compile_errors = parent.debug_compile_errors,

lib/std/Build/Step/Compile.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1380,7 +1380,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
13801380
if (b.debug_compile_errors) {
13811381
try zig_args.append("--debug-compile-errors");
13821382
}
1383-
1383+
if (b.cache_dependencies_locally) try zig_args.append("--cache-dependencies-locally");
13841384
if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
13851385
if (b.verbose_air) try zig_args.append("--verbose-air");
13861386
if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path}));

src/Package/Fetch.zig

Lines changed: 143 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
//! Represents one independent job whose responsibility is to:
22
//!
3-
//! 1. Check the global zig package cache to see if the hash already exists.
4-
//! If so, load, parse, and validate the build.zig.zon file therein, and
5-
//! goto step 8. Likewise if the location is a relative path, treat this
6-
//! the same as a cache hit. Otherwise, proceed.
3+
//! 1. if local zig packages is enabled:
4+
//! a. Check the local zig package directory to see if the hash already exists.
5+
//! If so, load, parse, and validate the build.zig.zon file therein, and
6+
//! goto step 8. Likewise if the location is a relative path, treat this
7+
//! the same as a cache hit. Otherwise, proceed.
8+
//! b. Check the global zig package cache to see if the hash already exists.
9+
//! If so, load, parse, and validate the build.zig.zon file therein, copy
10+
//! it to the local zig package directory and goto step 8. Otherwise, proceed.
11+
//! if local zig packages is disabled:
12+
//! Check the global zig package cache to see if the hash already exists.
13+
//! If so, load, parse, and validate the build.zig.zon file therein, and
14+
//! goto step 8. Likewise if the location is a relative path, treat this
15+
//! the same as a cache hit. Otherwise, proceed.
716
//! 2. Fetch and unpack a URL into a temporary directory.
817
//! 3. Load, parse, and validate the build.zig.zon file therein. It is allowed
918
//! for the file to be missing, in which case this fetched package is considered
@@ -18,7 +27,8 @@
1827
//! directory. If the hash already exists, delete the temporary directory and
1928
//! leave the zig package cache directory untouched as it may be in use by the
2029
//! system. This is done even if the hash is invalid, in case the package with
21-
//! the different hash is used in the future.
30+
//! the different hash is used in the future. If local packages is enabled, copy
31+
//! the package locally.
2232
//! 7. Validate the computed hash against the expected hash. If invalid,
2333
//! this job is done.
2434
//! 8. Spawn a new fetch job for each dependency in the manifest file. Use
@@ -97,6 +107,7 @@ pub const JobQueue = struct {
97107
thread_pool: *ThreadPool,
98108
wait_group: WaitGroup = .{},
99109
global_cache: Cache.Directory,
110+
local_package_cache_root: ?Cache.Directory,
100111
/// If true then, no fetching occurs, and:
101112
/// * The `global_cache` directory is assumed to be the direct parent
102113
/// directory of on-disk packages rather than having the "p/" directory
@@ -299,7 +310,7 @@ pub fn run(f: *Fetch) RunError!void {
299310
const arena = f.arena.allocator();
300311
const gpa = f.arena.child_allocator;
301312
const cache_root = f.job_queue.global_cache;
302-
313+
const local_package_cache = f.job_queue.local_package_cache_root;
303314
try eb.init(gpa);
304315

305316
// Check the global zig package cache to see if the hash already exists. If
@@ -372,41 +383,102 @@ pub fn run(f: *Fetch) RunError!void {
372383
const prefixed_pkg_sub_path = "p" ++ s ++ expected_hash;
373384
const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0;
374385
const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..];
375-
if (cache_root.handle.access(pkg_sub_path, .{})) |_| {
376-
assert(f.lazy_status != .unavailable);
377-
f.package_root = .{
378-
.root_dir = cache_root,
379-
.sub_path = try arena.dupe(u8, pkg_sub_path),
380-
};
381-
try loadManifest(f, f.package_root);
382-
try checkBuildFileExistence(f);
383-
if (!f.job_queue.recursive) return;
384-
return queueJobsForDeps(f);
385-
} else |err| switch (err) {
386-
error.FileNotFound => {
387-
switch (f.lazy_status) {
388-
.eager => {},
389-
.available => if (!f.job_queue.unlazy_set.contains(expected_hash)) {
390-
f.lazy_status = .unavailable;
391-
return;
392-
},
393-
.unavailable => unreachable,
386+
var check_cache_root = local_package_cache == null;
387+
if (local_package_cache) |lpc| {
388+
if (lpc.handle.access(&expected_hash, .{})) {
389+
f.package_root = .{
390+
.root_dir = lpc,
391+
.sub_path = try arena.dupe(u8, &expected_hash),
392+
};
393+
try loadManifest(f, f.package_root);
394+
try checkBuildFileExistence(f);
395+
if (!f.job_queue.recursive) return;
396+
return queueJobsForDeps(f);
397+
} else |err| switch (err) {
398+
error.FileNotFound => {
399+
check_cache_root = true;
400+
},
401+
else => |e| {
402+
try eb.addRootErrorMessage(.{
403+
.msg = try eb.printString("unable to open local package cache directory '{}{s}': {s}", .{
404+
lpc, &expected_hash, @errorName(e),
405+
}),
406+
});
407+
return error.FetchFailed;
408+
},
409+
}
410+
}
411+
if (check_cache_root) {
412+
if (cache_root.handle.access(pkg_sub_path, .{})) |_| {
413+
assert(f.lazy_status != .unavailable);
414+
if (local_package_cache) |lpc| {
415+
var dst_handle = lpc.handle.makeOpenPath(&expected_hash, .{}) catch |err| {
416+
try eb.addRootErrorMessage(.{
417+
.msg = try eb.printString("unable to open local package cache directory '{}{s}': {s}", .{
418+
lpc, &expected_hash, @errorName(err),
419+
}),
420+
});
421+
return error.FetchFailed;
422+
};
423+
defer dst_handle.close();
424+
var src_handle = cache_root.handle.openDir(pkg_sub_path, .{}) catch |err| {
425+
try eb.addRootErrorMessage(.{
426+
.msg = try eb.printString("unable to open global package cache directory '{}{s}': {s}", .{
427+
cache_root, pkg_sub_path, @errorName(err),
428+
}),
429+
});
430+
return error.FetchFailed;
431+
};
432+
defer src_handle.close();
433+
f.recursiveDirectoryCopy(src_handle, dst_handle) catch |err| {
434+
const src = try cache_root.join(arena, &.{pkg_sub_path});
435+
const dest = try lpc.join(arena, &.{&expected_hash});
436+
try eb.addRootErrorMessage(.{ .msg = try eb.printString(
437+
"sunable to copy into local package directory '{s}' from package cache directory '{s}': {s}",
438+
.{ dest, src, @errorName(err) },
439+
) });
440+
return error.FetchFailed;
441+
};
442+
f.package_root = .{
443+
.root_dir = lpc,
444+
.sub_path = try arena.dupe(u8, &expected_hash),
445+
};
446+
} else {
447+
f.package_root = .{
448+
.root_dir = cache_root,
449+
.sub_path = try arena.dupe(u8, pkg_sub_path),
450+
};
394451
}
395-
if (f.job_queue.read_only) return f.fail(
396-
f.name_tok,
397-
try eb.printString("package not found at '{}{s}'", .{
398-
cache_root, pkg_sub_path,
399-
}),
400-
);
401-
},
402-
else => |e| {
403-
try eb.addRootErrorMessage(.{
404-
.msg = try eb.printString("unable to open global package cache directory '{}{s}': {s}", .{
405-
cache_root, pkg_sub_path, @errorName(e),
406-
}),
407-
});
408-
return error.FetchFailed;
409-
},
452+
try loadManifest(f, f.package_root);
453+
try checkBuildFileExistence(f);
454+
if (!f.job_queue.recursive) return;
455+
return queueJobsForDeps(f);
456+
} else |err| switch (err) {
457+
error.FileNotFound => {
458+
switch (f.lazy_status) {
459+
.eager => {},
460+
.available => if (!f.job_queue.unlazy_set.contains(expected_hash)) {
461+
f.lazy_status = .unavailable;
462+
return;
463+
},
464+
.unavailable => unreachable,
465+
}
466+
if (f.job_queue.read_only) return f.fail(
467+
f.name_tok,
468+
try eb.printString("package not found at '{}{s}'", .{
469+
cache_root, pkg_sub_path,
470+
}),
471+
);
472+
},
473+
else => |e| {
474+
try eb.addRootErrorMessage(.{
475+
.msg = try eb.printString("unable to open global package cache directory '{}{s}': {s}", .{
476+
cache_root, pkg_sub_path, @errorName(e),
477+
}),
478+
});
479+
return error.FetchFailed;
480+
},
481+
}
410482
}
411483
} else if (f.job_queue.read_only) {
412484
try eb.addRootErrorMessage(.{
@@ -444,6 +516,7 @@ fn runResource(
444516
const eb = &f.error_bundle;
445517
const s = fs.path.sep_str;
446518
const cache_root = f.job_queue.global_cache;
519+
const local_package_cache = f.job_queue.local_package_cache_root;
447520
const rand_int = std.crypto.random.int(u64);
448521
const tmp_dir_sub_path = "tmp" ++ s ++ Manifest.hex64(rand_int);
449522

@@ -526,6 +599,36 @@ fn runResource(
526599
) });
527600
return error.FetchFailed;
528601
};
602+
if (local_package_cache) |lpc| {
603+
var dst_handle = lpc.handle.makeOpenPath(&Manifest.hexDigest(f.actual_hash), .{}) catch |err| {
604+
try eb.addRootErrorMessage(.{
605+
.msg = try eb.printString("unable to open local package cache directory '{}{s}': {s}", .{
606+
lpc, &Manifest.hexDigest(f.actual_hash), @errorName(err),
607+
}),
608+
});
609+
return error.FetchFailed;
610+
};
611+
defer dst_handle.close();
612+
var src_handle = cache_root.handle.openDir(f.package_root.sub_path, .{}) catch |err| {
613+
try eb.addRootErrorMessage(.{
614+
.msg = try eb.printString("unable to open global package cache directory '{}{s}': {s}", .{
615+
cache_root, f.package_root.sub_path, @errorName(err),
616+
}),
617+
});
618+
return error.FetchFailed;
619+
};
620+
defer src_handle.close();
621+
f.recursiveDirectoryCopy(src_handle, dst_handle) catch |err| {
622+
const src = try cache_root.join(arena, &.{f.package_root.sub_path});
623+
const dest = try lpc.join(arena, &.{&Manifest.hexDigest(f.actual_hash)});
624+
try eb.addRootErrorMessage(.{ .msg = try eb.printString(
625+
"unable to copy into local package directory '{s}' from package cache directory '{s}': {s}",
626+
.{ dest, src, @errorName(err) },
627+
) });
628+
return error.FetchFailed;
629+
};
630+
}
631+
529632
// Remove temporary directory root if not already renamed to global cache.
530633
if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) {
531634
cache_root.handle.deleteDir(tmp_dir_sub_path) catch {};

src/main.zig

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4632,6 +4632,7 @@ const usage_build =
46324632
\\ --build-file [file] Override path to build.zig
46334633
\\ --cache-dir [path] Override path to local Zig cache directory
46344634
\\ --global-cache-dir [path] Override path to global Zig cache directory
4635+
\\ --cache-dependencies-locally Cache dependencies locally in zig-deps
46354636
\\ --zig-lib-dir [arg] Override path to Zig lib directory
46364637
\\ --build-runner [file] Override path to build runner
46374638
\\ --prominent-compile-errors Buffer compile errors and display at end
@@ -4647,6 +4648,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46474648
var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
46484649
var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena);
46494650
var override_build_runner: ?[]const u8 = try EnvVar.ZIG_BUILD_RUNNER.get(arena);
4651+
var cache_dependencies_locally = false;
46504652
var child_argv = std.ArrayList([]const u8).init(arena);
46514653
var reference_trace: ?u32 = null;
46524654
var debug_compile_errors = false;
@@ -4735,6 +4737,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47354737
i += 1;
47364738
override_global_cache_dir = args[i];
47374739
continue;
4740+
} else if (mem.eql(u8, arg, "--cache-dependencies-locally")) {
4741+
cache_dependencies_locally = true;
4742+
continue;
47384743
} else if (mem.eql(u8, arg, "-freference-trace")) {
47394744
reference_trace = 256;
47404745
} else if (mem.eql(u8, arg, "--fetch")) {
@@ -4860,6 +4865,15 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48604865
};
48614866
defer global_cache_directory.handle.close();
48624867

4868+
var local_package_cache_root: ?Compilation.Directory = null;
4869+
if (cache_dependencies_locally) {
4870+
local_package_cache_root = .{
4871+
.handle = try fs.cwd().makeOpenPath("zig-deps", .{}),
4872+
.path = "zig-deps",
4873+
};
4874+
}
4875+
defer if (local_package_cache_root) |*lpc| lpc.handle.close();
4876+
48634877
child_argv.items[argv_index_global_cache_dir] = global_cache_directory.path orelse cwd_path;
48644878

48654879
var local_cache_directory: Compilation.Directory = l: {
@@ -4976,6 +4990,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
49764990
.http_client = &http_client,
49774991
.thread_pool = &thread_pool,
49784992
.global_cache = global_cache_directory,
4993+
.local_package_cache_root = local_package_cache_root,
49794994
.read_only = false,
49804995
.recursive = true,
49814996
.debug_hash = false,
@@ -6800,7 +6815,7 @@ const usage_fetch =
68006815
\\ --save=[name] Add the fetched package to build.zig.zon as name
68016816
\\ --save-exact Add the fetched package to build.zig.zon, storing the URL verbatim
68026817
\\ --save-exact=[name] Add the fetched package to build.zig.zon as name, storing the URL verbatim
6803-
\\
6818+
\\ --cache-dependencies-locally Cache dependencies locally in zig-deps
68046819
;
68056820

68066821
fn cmdFetch(
@@ -6813,6 +6828,7 @@ fn cmdFetch(
68136828
EnvVar.ZIG_BTRFS_WORKAROUND.isSet();
68146829
var opt_path_or_url: ?[]const u8 = null;
68156830
var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
6831+
var cache_dependencies_locally = false;
68166832
var debug_hash: bool = false;
68176833
var save: union(enum) {
68186834
no,
@@ -6837,6 +6853,8 @@ fn cmdFetch(
68376853
debug_hash = true;
68386854
} else if (mem.eql(u8, arg, "--save")) {
68396855
save = .{ .yes = null };
6856+
} else if (mem.eql(u8, arg, "--cache-dependencies-locally")) {
6857+
cache_dependencies_locally = true;
68406858
} else if (mem.startsWith(u8, arg, "--save=")) {
68416859
save = .{ .yes = arg["--save=".len..] };
68426860
} else if (mem.eql(u8, arg, "--save-exact")) {
@@ -6879,10 +6897,20 @@ fn cmdFetch(
68796897
};
68806898
defer global_cache_directory.handle.close();
68816899

6900+
var local_package_cache_root: ?Compilation.Directory = null;
6901+
if (cache_dependencies_locally) {
6902+
local_package_cache_root = .{
6903+
.handle = try fs.cwd().makeOpenPath("zig-deps", .{}),
6904+
.path = "zig-deps",
6905+
};
6906+
}
6907+
defer if (local_package_cache_root) |*lpc| lpc.handle.close();
6908+
68826909
var job_queue: Package.Fetch.JobQueue = .{
68836910
.http_client = &http_client,
68846911
.thread_pool = &thread_pool,
68856912
.global_cache = global_cache_directory,
6913+
.local_package_cache_root = local_package_cache_root,
68866914
.recursive = false,
68876915
.read_only = false,
68886916
.debug_hash = debug_hash,

0 commit comments

Comments
 (0)