authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-07 15:43:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-04-07 15:43:54-07:00
loge204a6edb8866093a087ef2325a5d6ea1824487c
tree8d4b1db9110c55bb0684791d7f2b33a8a19eb247
parent278db0ad4543d7a768c63c5482b8873b58690920
parentb381fb805cfe382a6786f2f1ae670d440d4184bf
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18988 from castholm/lazy-build-zig

std.Build: add `lazyImport` (`@import` for lazy dependencies)

5 files changed, 86 insertions(+), 10 deletions(-)

lib/compiler/build_runner.zig+1-1
...@@ -1,4 +1,3 @@...@@ -1,4 +1,3 @@
1const root = @import("@build");
2const std = @import("std");1const std = @import("std");
3const builtin = @import("builtin");2const builtin = @import("builtin");
4const assert = std.debug.assert;3const assert = std.debug.assert;
...@@ -10,6 +9,7 @@ const ArrayList = std.ArrayList;...@@ -10,6 +9,7 @@ const ArrayList = std.ArrayList;
10const File = std.fs.File;9const File = std.fs.File;
11const Step = std.Build.Step;10const Step = std.Build.Step;
1211
12pub const root = @import("@build");
13pub const dependencies = @import("@dependencies");13pub const dependencies = @import("@dependencies");
1414
15pub fn main() !void {15pub fn main() !void {
lib/std/Build.zig+67-5
...@@ -93,6 +93,8 @@ named_writefiles: std.StringArrayHashMap(*Step.WriteFile),...@@ -93,6 +93,8 @@ named_writefiles: std.StringArrayHashMap(*Step.WriteFile),
93/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child93/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child
94/// `Build`s.94/// `Build`s.
95initialized_deps: *InitializedDepMap,95initialized_deps: *InitializedDepMap,
96/// The hash of this instance's package. `""` means that this is the root package.
97pkg_hash: []const u8,
96/// A mapping from dependency names to package hashes.98/// A mapping from dependency names to package hashes.
97available_deps: AvailableDeps,99available_deps: AvailableDeps,
98100
...@@ -305,6 +307,7 @@ pub fn create(...@@ -305,6 +307,7 @@ pub fn create(
305 .modules = std.StringArrayHashMap(*Module).init(arena),307 .modules = std.StringArrayHashMap(*Module).init(arena),
306 .named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(arena),308 .named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(arena),
307 .initialized_deps = initialized_deps,309 .initialized_deps = initialized_deps,
310 .pkg_hash = "",
308 .available_deps = available_deps,311 .available_deps = available_deps,
309 .release_mode = .off,312 .release_mode = .off,
310 };313 };
...@@ -318,10 +321,11 @@ fn createChild(...@@ -318,10 +321,11 @@ fn createChild(
318 parent: *Build,321 parent: *Build,
319 dep_name: []const u8,322 dep_name: []const u8,
320 build_root: Cache.Directory,323 build_root: Cache.Directory,
324 pkg_hash: []const u8,
321 pkg_deps: AvailableDeps,325 pkg_deps: AvailableDeps,
322 user_input_options: UserInputOptionsMap,326 user_input_options: UserInputOptionsMap,
323) !*Build {327) !*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);
325 try determineAndApplyInstallPrefix(child);329 try determineAndApplyInstallPrefix(child);
326 return child;330 return child;
327}331}
...@@ -330,6 +334,7 @@ fn createChildOnly(...@@ -330,6 +334,7 @@ fn createChildOnly(
330 parent: *Build,334 parent: *Build,
331 dep_name: []const u8,335 dep_name: []const u8,
332 build_root: Cache.Directory,336 build_root: Cache.Directory,
337 pkg_hash: []const u8,
333 pkg_deps: AvailableDeps,338 pkg_deps: AvailableDeps,
334 user_input_options: UserInputOptionsMap,339 user_input_options: UserInputOptionsMap,
335) !*Build {340) !*Build {
...@@ -397,6 +402,7 @@ fn createChildOnly(...@@ -397,6 +402,7 @@ fn createChildOnly(
397 .modules = std.StringArrayHashMap(*Module).init(allocator),402 .modules = std.StringArrayHashMap(*Module).init(allocator),
398 .named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),403 .named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
399 .initialized_deps = parent.initialized_deps,404 .initialized_deps = parent.initialized_deps,
405 .pkg_hash = pkg_hash,
400 .available_deps = pkg_deps,406 .available_deps = pkg_deps,
401 .release_mode = parent.release_mode,407 .release_mode = parent.release_mode,
402 };408 };
...@@ -1831,6 +1837,26 @@ fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {...@@ -1831,6 +1837,26 @@ fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {
1831 std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });1837 std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });
1832}1838}
18331839
1840inline 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
1834fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {1860fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
1835 b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM");1861 b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM");
1836}1862}
...@@ -1861,7 +1887,7 @@ pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency {...@@ -1861,7 +1887,7 @@ pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency {
1861 markNeededLazyDep(b, pkg_hash);1887 markNeededLazyDep(b, pkg_hash);
1862 return null;1888 return null;
1863 }1889 }
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);
1865 }1891 }
1866 }1892 }
18671893
...@@ -1879,13 +1905,48 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {...@@ -1879,13 +1905,48 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
1879 if (@hasDecl(pkg, "available")) {1905 if (@hasDecl(pkg, "available")) {
1880 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 });1906 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 });
1881 }1907 }
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);
1883 }1909 }
1884 }1910 }
18851911
1886 unreachable; // Bad @dependencies source1912 unreachable; // Bad @dependencies source
1887}1913}
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`.
1920pub 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
1889pub fn anonymousDependency(1950pub fn anonymousDependency(
1890 b: *Build,1951 b: *Build,
1891 /// The path to the directory containing the dependency's build.zig file,1952 /// The path to the directory containing the dependency's build.zig file,
...@@ -1902,7 +1963,7 @@ pub fn anonymousDependency(...@@ -1902,7 +1963,7 @@ pub fn anonymousDependency(
1902 '/', '\\' => byte.* = '.',1963 '/', '\\' => byte.* = '.',
1903 else => continue,1964 else => continue,
1904 };1965 };
1905 return dependencyInner(b, name, build_root, build_zig, &.{}, args);1966 return dependencyInner(b, name, build_root, build_zig, "anonymous", &.{}, args);
1906}1967}
19071968
1908fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {1969fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
...@@ -1957,6 +2018,7 @@ pub fn dependencyInner(...@@ -1957,6 +2018,7 @@ pub fn dependencyInner(
1957 name: []const u8,2018 name: []const u8,
1958 build_root_string: []const u8,2019 build_root_string: []const u8,
1959 comptime build_zig: ?type,2020 comptime build_zig: ?type,
2021 pkg_hash: []const u8,
1960 pkg_deps: AvailableDeps,2022 pkg_deps: AvailableDeps,
1961 args: anytype,2023 args: anytype,
1962) *Dependency {2024) *Dependency {
...@@ -1977,7 +2039,7 @@ pub fn dependencyInner(...@@ -1977,7 +2039,7 @@ pub fn dependencyInner(
1977 },2039 },
1978 };2040 };
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");
1981 if (build_zig) |bz| {2043 if (build_zig) |bz| {
1982 sub_builder.runBuild(bz) catch @panic("unhandled error");2044 sub_builder.runBuild(bz) catch @panic("unhandled error");
19832045
lib/std/math/nextafter.zig+1-1
...@@ -144,7 +144,7 @@ test "int" {...@@ -144,7 +144,7 @@ test "int" {
144}144}
145145
146test "float" {146test "float" {
147 @setEvalBranchQuota(2000);147 @setEvalBranchQuota(3000);
148148
149 // normal -> normal149 // normal -> normal
150 try expect(nextAfter(f16, 0x1.234p0, 2.0) == 0x1.238p0);150 try expect(nextAfter(f16, 0x1.234p0, 2.0) == 0x1.238p0);
src/Sema.zig+3-3
...@@ -7531,10 +7531,12 @@ fn analyzeCall(...@@ -7531,10 +7531,12 @@ fn analyzeCall(
75317531
7532 var is_generic_call = func_ty_info.is_generic;7532 var is_generic_call = func_ty_info.is_generic;
7533 var is_comptime_call = block.is_comptime or modifier == .compile_time;7533 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;
7534 var comptime_reason: ?*const Block.ComptimeReason = null;7535 var comptime_reason: ?*const Block.ComptimeReason = null;
7535 if (!is_comptime_call) {7536 if (!is_inline_call and !is_comptime_call) {
7536 if (sema.typeRequiresComptime(Type.fromInterned(func_ty_info.return_type))) |ct| {7537 if (sema.typeRequiresComptime(Type.fromInterned(func_ty_info.return_type))) |ct| {
7537 is_comptime_call = ct;7538 is_comptime_call = ct;
7539 is_inline_call = ct;
7538 if (ct) {7540 if (ct) {
7539 comptime_reason = &.{ .comptime_ret_ty = .{7541 comptime_reason = &.{ .comptime_ret_ty = .{
7540 .block = block,7542 .block = block,
...@@ -7548,8 +7550,6 @@ fn analyzeCall(...@@ -7548,8 +7550,6 @@ fn analyzeCall(
7548 else => |e| return e,7550 else => |e| return e,
7549 }7551 }
7550 }7552 }
7551 var is_inline_call = is_comptime_call or modifier == .always_inline or
7552 func_ty_info.cc == .Inline;
75537553
7554 if (sema.func_is_naked and !is_inline_call and !is_comptime_call) {7554 if (sema.func_is_naked and !is_inline_call and !is_comptime_call) {
7555 const msg = msg: {7555 const msg = msg: {
test/behavior/fn.zig+14
...@@ -604,3 +604,17 @@ test "comptime parameters don't have to be marked comptime if only called at com...@@ -604,3 +604,17 @@ test "comptime parameters don't have to be marked comptime if only called at com
604 };604 };
605 comptime std.debug.assert(S.foo(5, 6) == 11);605 comptime std.debug.assert(S.foo(5, 6) == 11);
606}606}
607
608test "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}