Skip to content

Commit e204a6e

Browse files
authored
Merge pull request #18988 from castholm/lazy-build-zig
std.Build: add `lazyImport` (`@import` for lazy dependencies)
2 parents 278db0a + b381fb8 commit e204a6e

File tree

5 files changed

+86
-10
lines changed

5 files changed

+86
-10
lines changed

lib/compiler/build_runner.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const root = @import("@build");
21
const std = @import("std");
32
const builtin = @import("builtin");
43
const assert = std.debug.assert;
@@ -10,6 +9,7 @@ const ArrayList = std.ArrayList;
109
const File = std.fs.File;
1110
const Step = std.Build.Step;
1211

12+
pub const root = @import("@build");
1313
pub const dependencies = @import("@dependencies");
1414

1515
pub fn main() !void {

lib/std/Build.zig

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ named_writefiles: std.StringArrayHashMap(*Step.WriteFile),
9393
/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child
9494
/// `Build`s.
9595
initialized_deps: *InitializedDepMap,
96+
/// The hash of this instance's package. `""` means that this is the root package.
97+
pkg_hash: []const u8,
9698
/// A mapping from dependency names to package hashes.
9799
available_deps: AvailableDeps,
98100

@@ -305,6 +307,7 @@ pub fn create(
305307
.modules = std.StringArrayHashMap(*Module).init(arena),
306308
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(arena),
307309
.initialized_deps = initialized_deps,
310+
.pkg_hash = "",
308311
.available_deps = available_deps,
309312
.release_mode = .off,
310313
};
@@ -318,10 +321,11 @@ fn createChild(
318321
parent: *Build,
319322
dep_name: []const u8,
320323
build_root: Cache.Directory,
324+
pkg_hash: []const u8,
321325
pkg_deps: AvailableDeps,
322326
user_input_options: UserInputOptionsMap,
323327
) !*Build {
324-
const child = try createChildOnly(parent, dep_name, build_root, pkg_deps, user_input_options);
328+
const child = try createChildOnly(parent, dep_name, build_root, pkg_hash, pkg_deps, user_input_options);
325329
try determineAndApplyInstallPrefix(child);
326330
return child;
327331
}
@@ -330,6 +334,7 @@ fn createChildOnly(
330334
parent: *Build,
331335
dep_name: []const u8,
332336
build_root: Cache.Directory,
337+
pkg_hash: []const u8,
333338
pkg_deps: AvailableDeps,
334339
user_input_options: UserInputOptionsMap,
335340
) !*Build {
@@ -397,6 +402,7 @@ fn createChildOnly(
397402
.modules = std.StringArrayHashMap(*Module).init(allocator),
398403
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
399404
.initialized_deps = parent.initialized_deps,
405+
.pkg_hash = pkg_hash,
400406
.available_deps = pkg_deps,
401407
.release_mode = parent.release_mode,
402408
};
@@ -1831,6 +1837,26 @@ fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {
18311837
std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });
18321838
}
18331839

1840+
inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 {
1841+
const build_runner = @import("root");
1842+
const deps = build_runner.dependencies;
1843+
1844+
const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).Struct.decls) |decl| {
1845+
const pkg_hash = decl.name;
1846+
const pkg = @field(deps.packages, pkg_hash);
1847+
if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps };
1848+
} else .{ "", deps.root_deps };
1849+
if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
1850+
std.debug.panic("'{}' is not the struct that corresponds to '{s}'", .{ asking_build_zig, b.pathFromRoot("build.zig") });
1851+
}
1852+
comptime for (b_pkg_deps) |dep| {
1853+
if (std.mem.eql(u8, dep[0], dep_name)) return dep[1];
1854+
};
1855+
1856+
const full_path = b.pathFromRoot("build.zig.zon");
1857+
std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ dep_name, full_path });
1858+
}
1859+
18341860
fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
18351861
b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM");
18361862
}
@@ -1861,7 +1887,7 @@ pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency {
18611887
markNeededLazyDep(b, pkg_hash);
18621888
return null;
18631889
}
1864-
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, args);
1890+
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args);
18651891
}
18661892
}
18671893

@@ -1879,13 +1905,48 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
18791905
if (@hasDecl(pkg, "available")) {
18801906
std.debug.panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name });
18811907
}
1882-
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, args);
1908+
return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args);
18831909
}
18841910
}
18851911

18861912
unreachable; // Bad @dependencies source
18871913
}
18881914

1915+
/// In a build.zig file, this function is to `@import` what `lazyDependency` is to `dependency`.
1916+
/// If the dependency is lazy and has not yet been fetched, it instructs the parent process to fetch
1917+
/// that dependency after the build script has finished running, then returns `null`.
1918+
/// If the dependency is lazy but has already been fetched, or if it is eager, it returns
1919+
/// the build.zig struct of that dependency, just like a regular `@import`.
1920+
pub inline fn lazyImport(
1921+
b: *Build,
1922+
/// The build.zig struct of the package importing the dependency.
1923+
/// When calling this function from the `build` function of a build.zig file's, you normally
1924+
/// pass `@This()`.
1925+
comptime asking_build_zig: type,
1926+
comptime dep_name: []const u8,
1927+
) ?type {
1928+
const build_runner = @import("root");
1929+
const deps = build_runner.dependencies;
1930+
const pkg_hash = findImportPkgHashOrFatal(b, asking_build_zig, dep_name);
1931+
1932+
inline for (@typeInfo(deps.packages).Struct.decls) |decl| {
1933+
if (comptime mem.eql(u8, decl.name, pkg_hash)) {
1934+
const pkg = @field(deps.packages, decl.name);
1935+
const available = !@hasDecl(pkg, "available") or pkg.available;
1936+
if (!available) {
1937+
markNeededLazyDep(b, pkg_hash);
1938+
return null;
1939+
}
1940+
return if (@hasDecl(pkg, "build_zig"))
1941+
pkg.build_zig
1942+
else
1943+
@compileError("dependency '" ++ dep_name ++ "' does not have a build.zig");
1944+
}
1945+
}
1946+
1947+
comptime unreachable; // Bad @dependencies source
1948+
}
1949+
18891950
pub fn anonymousDependency(
18901951
b: *Build,
18911952
/// The path to the directory containing the dependency's build.zig file,
@@ -1902,7 +1963,7 @@ pub fn anonymousDependency(
19021963
'/', '\\' => byte.* = '.',
19031964
else => continue,
19041965
};
1905-
return dependencyInner(b, name, build_root, build_zig, &.{}, args);
1966+
return dependencyInner(b, name, build_root, build_zig, "anonymous", &.{}, args);
19061967
}
19071968

19081969
fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
@@ -1957,6 +2018,7 @@ pub fn dependencyInner(
19572018
name: []const u8,
19582019
build_root_string: []const u8,
19592020
comptime build_zig: ?type,
2021+
pkg_hash: []const u8,
19602022
pkg_deps: AvailableDeps,
19612023
args: anytype,
19622024
) *Dependency {
@@ -1977,7 +2039,7 @@ pub fn dependencyInner(
19772039
},
19782040
};
19792041

1980-
const sub_builder = b.createChild(name, build_root, pkg_deps, user_input_options) catch @panic("unhandled error");
2042+
const sub_builder = b.createChild(name, build_root, pkg_hash, pkg_deps, user_input_options) catch @panic("unhandled error");
19812043
if (build_zig) |bz| {
19822044
sub_builder.runBuild(bz) catch @panic("unhandled error");
19832045

lib/std/math/nextafter.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ test "int" {
144144
}
145145

146146
test "float" {
147-
@setEvalBranchQuota(2000);
147+
@setEvalBranchQuota(3000);
148148

149149
// normal -> normal
150150
try expect(nextAfter(f16, 0x1.234p0, 2.0) == 0x1.238p0);

src/Sema.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7531,10 +7531,12 @@ fn analyzeCall(
75317531

75327532
var is_generic_call = func_ty_info.is_generic;
75337533
var is_comptime_call = block.is_comptime or modifier == .compile_time;
7534+
var is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline;
75347535
var comptime_reason: ?*const Block.ComptimeReason = null;
7535-
if (!is_comptime_call) {
7536+
if (!is_inline_call and !is_comptime_call) {
75367537
if (sema.typeRequiresComptime(Type.fromInterned(func_ty_info.return_type))) |ct| {
75377538
is_comptime_call = ct;
7539+
is_inline_call = ct;
75387540
if (ct) {
75397541
comptime_reason = &.{ .comptime_ret_ty = .{
75407542
.block = block,
@@ -7548,8 +7550,6 @@ fn analyzeCall(
75487550
else => |e| return e,
75497551
}
75507552
}
7551-
var is_inline_call = is_comptime_call or modifier == .always_inline or
7552-
func_ty_info.cc == .Inline;
75537553

75547554
if (sema.func_is_naked and !is_inline_call and !is_comptime_call) {
75557555
const msg = msg: {

test/behavior/fn.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,17 @@ test "comptime parameters don't have to be marked comptime if only called at com
604604
};
605605
comptime std.debug.assert(S.foo(5, 6) == 11);
606606
}
607+
608+
test "inline function with comptime-known comptime-only return type called at runtime" {
609+
const S = struct {
610+
inline fn foo(x: *i32, y: *const i32) type {
611+
x.* = y.*;
612+
return f32;
613+
}
614+
};
615+
var a: i32 = 0;
616+
const b: i32 = 111;
617+
const T = S.foo(&a, &b);
618+
try expectEqual(111, a);
619+
try expectEqual(f32, T);
620+
}

0 commit comments

Comments
 (0)