| ... | @@ -1871,6 +1871,36 @@ pub const GeneratedFile = struct { | ... | @@ -1871,6 +1871,36 @@ pub const GeneratedFile = struct { |
| 1871 | } | 1871 | } |
| 1872 | }; | 1872 | }; |
| 1873 | | 1873 | |
| | 1874 | // dirnameAllowEmpty is a variant of fs.path.dirname |
| | 1875 | // that allows "" to refer to the root for relative paths. |
| | 1876 | // |
| | 1877 | // For context, dirname("foo") and dirname("") are both null. |
| | 1878 | // However, for relative paths, we want dirname("foo") to be "" |
| | 1879 | // so that we can join it with another path (e.g. build root, cache root, etc.) |
| | 1880 | // |
| | 1881 | // dirname("") should still be null, because we can't go up any further. |
| | 1882 | fn dirnameAllowEmpty(path: []const u8) ?[]const u8 { |
| | 1883 | return fs.path.dirname(path) orelse { |
| | 1884 | if (fs.path.isAbsolute(path) or path.len == 0) return null; |
| | 1885 | |
| | 1886 | return ""; |
| | 1887 | }; |
| | 1888 | } |
| | 1889 | |
| | 1890 | test dirnameAllowEmpty { |
| | 1891 | try std.testing.expectEqualStrings( |
| | 1892 | "foo", |
| | 1893 | dirnameAllowEmpty("foo" ++ fs.path.sep_str ++ "bar") orelse @panic("unexpected null"), |
| | 1894 | ); |
| | 1895 | |
| | 1896 | try std.testing.expectEqualStrings( |
| | 1897 | "", |
| | 1898 | dirnameAllowEmpty("foo") orelse @panic("unexpected null"), |
| | 1899 | ); |
| | 1900 | |
| | 1901 | try std.testing.expect(dirnameAllowEmpty("") == null); |
| | 1902 | } |
| | 1903 | |
| 1874 | /// A reference to an existing or future path. | 1904 | /// A reference to an existing or future path. |
| 1875 | pub const LazyPath = union(enum) { | 1905 | pub const LazyPath = union(enum) { |
| 1876 | /// A source file path relative to build root. | 1906 | /// A source file path relative to build root. |
| ... | @@ -1882,6 +1912,17 @@ pub const LazyPath = union(enum) { | ... | @@ -1882,6 +1912,17 @@ pub const LazyPath = union(enum) { |
| 1882 | /// not available until built by a build step. | 1912 | /// not available until built by a build step. |
| 1883 | generated: *const GeneratedFile, | 1913 | generated: *const GeneratedFile, |
| 1884 | | 1914 | |
| | 1915 | /// One of the parent directories of a file generated by an interface. |
| | 1916 | /// The path is not available until built by a build step. |
| | 1917 | generated_dirname: struct { |
| | 1918 | generated: *const GeneratedFile, |
| | 1919 | |
| | 1920 | /// The number of parent directories to go up. |
| | 1921 | /// 0 means the directory of the generated file, |
| | 1922 | /// 1 means the parent of that directory, and so on. |
| | 1923 | up: usize, |
| | 1924 | }, |
| | 1925 | |
| 1885 | /// An absolute path or a path relative to the current working directory of | 1926 | /// An absolute path or a path relative to the current working directory of |
| 1886 | /// the build runner process. | 1927 | /// the build runner process. |
| 1887 | /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which | 1928 | /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which |
| ... | @@ -1902,12 +1943,72 @@ pub const LazyPath = union(enum) { | ... | @@ -1902,12 +1943,72 @@ pub const LazyPath = union(enum) { |
| 1902 | return LazyPath{ .path = path }; | 1943 | return LazyPath{ .path = path }; |
| 1903 | } | 1944 | } |
| 1904 | | 1945 | |
| | 1946 | /// Returns a lazy path referring to the directory containing this path. |
| | 1947 | /// |
| | 1948 | /// The dirname is not allowed to escape the logical root for underlying path. |
| | 1949 | /// For example, if the path is relative to the build root, |
| | 1950 | /// the dirname is not allowed to traverse outside of the build root. |
| | 1951 | /// Similarly, if the path is a generated file inside zig-cache, |
| | 1952 | /// the dirname is not allowed to traverse outside of zig-cache. |
| | 1953 | pub fn dirname(self: LazyPath) LazyPath { |
| | 1954 | return switch (self) { |
| | 1955 | .generated => |gen| .{ .generated_dirname = .{ .generated = gen, .up = 0 } }, |
| | 1956 | .generated_dirname => |gen| .{ .generated_dirname = .{ .generated = gen.generated, .up = gen.up + 1 } }, |
| | 1957 | .path => |p| .{ |
| | 1958 | .path = dirnameAllowEmpty(p) orelse { |
| | 1959 | dumpBadDirnameHelp(null, null, |
| | 1960 | \\dirname() attempted to traverse outside the build root. |
| | 1961 | \\This is not allowed. |
| | 1962 | \\ |
| | 1963 | , .{}) catch {}; |
| | 1964 | @panic("misconfigured build script"); |
| | 1965 | }, |
| | 1966 | }, |
| | 1967 | .cwd_relative => |p| .{ |
| | 1968 | .cwd_relative = dirnameAllowEmpty(p) orelse { |
| | 1969 | // If we get null, it means one of two things: |
| | 1970 | // - p was absolute, and is now root |
| | 1971 | // - p was relative, and is now "" |
| | 1972 | // In either case, the build script tried to go too far |
| | 1973 | // and we should panic. |
| | 1974 | if (fs.path.isAbsolute(p)) { |
| | 1975 | dumpBadDirnameHelp(null, null, |
| | 1976 | \\dirname() attempted to traverse outside the root. |
| | 1977 | \\No more directories left to go up. |
| | 1978 | \\ |
| | 1979 | , .{}) catch {}; |
| | 1980 | @panic("misconfigured build script"); |
| | 1981 | } else { |
| | 1982 | dumpBadDirnameHelp(null, null, |
| | 1983 | \\dirname() attempted to traverse outside the current working directory. |
| | 1984 | \\This is not allowed. |
| | 1985 | \\ |
| | 1986 | , .{}) catch {}; |
| | 1987 | @panic("misconfigured build script"); |
| | 1988 | } |
| | 1989 | }, |
| | 1990 | }, |
| | 1991 | .dependency => |dep| .{ .dependency = .{ |
| | 1992 | .dependency = dep.dependency, |
| | 1993 | .sub_path = dirnameAllowEmpty(dep.sub_path) orelse { |
| | 1994 | dumpBadDirnameHelp(null, null, |
| | 1995 | \\dirname() attempted to traverse outside the dependency root. |
| | 1996 | \\This is not allowed. |
| | 1997 | \\ |
| | 1998 | , .{}) catch {}; |
| | 1999 | @panic("misconfigured build script"); |
| | 2000 | }, |
| | 2001 | } }, |
| | 2002 | }; |
| | 2003 | } |
| | 2004 | |
| 1905 | /// Returns a string that can be shown to represent the file source. | 2005 | /// Returns a string that can be shown to represent the file source. |
| 1906 | /// Either returns the path or `"generated"`. | 2006 | /// Either returns the path or `"generated"`. |
| 1907 | pub fn getDisplayName(self: LazyPath) []const u8 { | 2007 | pub fn getDisplayName(self: LazyPath) []const u8 { |
| 1908 | return switch (self) { | 2008 | return switch (self) { |
| 1909 | .path, .cwd_relative => self.path, | 2009 | .path, .cwd_relative => self.path, |
| 1910 | .generated => "generated", | 2010 | .generated => "generated", |
| | 2011 | .generated_dirname => "generated", |
| 1911 | .dependency => "dependency", | 2012 | .dependency => "dependency", |
| 1912 | }; | 2013 | }; |
| 1913 | } | 2014 | } |
| ... | @@ -1917,6 +2018,7 @@ pub const LazyPath = union(enum) { | ... | @@ -1917,6 +2018,7 @@ pub const LazyPath = union(enum) { |
| 1917 | switch (self) { | 2018 | switch (self) { |
| 1918 | .path, .cwd_relative, .dependency => {}, | 2019 | .path, .cwd_relative, .dependency => {}, |
| 1919 | .generated => |gen| other_step.dependOn(gen.step), | 2020 | .generated => |gen| other_step.dependOn(gen.step), |
| | 2021 | .generated_dirname => |gen| other_step.dependOn(gen.generated.step), |
| 1920 | } | 2022 | } |
| 1921 | } | 2023 | } |
| 1922 | | 2024 | |
| ... | @@ -1941,6 +2043,39 @@ pub const LazyPath = union(enum) { | ... | @@ -1941,6 +2043,39 @@ pub const LazyPath = union(enum) { |
| 1941 | dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {}; | 2043 | dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {}; |
| 1942 | @panic("misconfigured build script"); | 2044 | @panic("misconfigured build script"); |
| 1943 | }, | 2045 | }, |
| | 2046 | .generated_dirname => |gen| { |
| | 2047 | const cache_root_path = src_builder.cache_root.path orelse |
| | 2048 | (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM")); |
| | 2049 | |
| | 2050 | const gen_step = gen.generated.step; |
| | 2051 | var path = getPath2(LazyPath{ .generated = gen.generated }, src_builder, asking_step); |
| | 2052 | var i: usize = 0; |
| | 2053 | while (i <= gen.up) : (i += 1) { |
| | 2054 | // path is absolute. |
| | 2055 | // dirname will return null only if we're at root. |
| | 2056 | // Typically, we'll stop well before that at the cache root. |
| | 2057 | path = fs.path.dirname(path) orelse { |
| | 2058 | dumpBadDirnameHelp(gen_step, asking_step, |
| | 2059 | \\dirname() reached root. |
| | 2060 | \\No more directories left to go up. |
| | 2061 | \\ |
| | 2062 | , .{}) catch {}; |
| | 2063 | @panic("misconfigured build script"); |
| | 2064 | }; |
| | 2065 | |
| | 2066 | if (mem.eql(u8, path, cache_root_path) and i < gen.up) { |
| | 2067 | // If we hit the cache root and there's still more to go, |
| | 2068 | // the script attempted to go too far. |
| | 2069 | dumpBadDirnameHelp(gen_step, asking_step, |
| | 2070 | \\dirname() attempted to traverse outside the cache root. |
| | 2071 | \\This is not allowed. |
| | 2072 | \\ |
| | 2073 | , .{}) catch {}; |
| | 2074 | @panic("misconfigured build script"); |
| | 2075 | } |
| | 2076 | } |
| | 2077 | return path; |
| | 2078 | }, |
| 1944 | .dependency => |dep| { | 2079 | .dependency => |dep| { |
| 1945 | return dep.dependency.builder.pathJoin(&[_][]const u8{ | 2080 | return dep.dependency.builder.pathJoin(&[_][]const u8{ |
| 1946 | dep.dependency.builder.build_root.path.?, | 2081 | dep.dependency.builder.build_root.path.?, |
| ... | @@ -1956,11 +2091,53 @@ pub const LazyPath = union(enum) { | ... | @@ -1956,11 +2091,53 @@ pub const LazyPath = union(enum) { |
| 1956 | .path => |p| .{ .path = b.dupePath(p) }, | 2091 | .path => |p| .{ .path = b.dupePath(p) }, |
| 1957 | .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) }, | 2092 | .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) }, |
| 1958 | .generated => |gen| .{ .generated = gen }, | 2093 | .generated => |gen| .{ .generated = gen }, |
| | 2094 | .generated_dirname => |gen| .{ |
| | 2095 | .generated_dirname = .{ |
| | 2096 | .generated = gen.generated, |
| | 2097 | .up = gen.up, |
| | 2098 | }, |
| | 2099 | }, |
| 1959 | .dependency => |dep| .{ .dependency = dep }, | 2100 | .dependency => |dep| .{ .dependency = dep }, |
| 1960 | }; | 2101 | }; |
| 1961 | } | 2102 | } |
| 1962 | }; | 2103 | }; |
| 1963 | | 2104 | |
| | 2105 | fn dumpBadDirnameHelp( |
| | 2106 | fail_step: ?*Step, |
| | 2107 | asking_step: ?*Step, |
| | 2108 | comptime msg: []const u8, |
| | 2109 | args: anytype, |
| | 2110 | ) anyerror!void { |
| | 2111 | debug.getStderrMutex().lock(); |
| | 2112 | defer debug.getStderrMutex().unlock(); |
| | 2113 | |
| | 2114 | const stderr = io.getStdErr(); |
| | 2115 | const w = stderr.writer(); |
| | 2116 | try w.print(msg, args); |
| | 2117 | |
| | 2118 | const tty_config = std.io.tty.detectConfig(stderr); |
| | 2119 | |
| | 2120 | if (fail_step) |s| { |
| | 2121 | tty_config.setColor(w, .red) catch {}; |
| | 2122 | try stderr.writeAll(" The step was created by this stack trace:\n"); |
| | 2123 | tty_config.setColor(w, .reset) catch {}; |
| | 2124 | |
| | 2125 | s.dump(stderr); |
| | 2126 | } |
| | 2127 | |
| | 2128 | if (asking_step) |as| { |
| | 2129 | tty_config.setColor(w, .red) catch {}; |
| | 2130 | try stderr.writer().print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); |
| | 2131 | tty_config.setColor(w, .reset) catch {}; |
| | 2132 | |
| | 2133 | as.dump(stderr); |
| | 2134 | } |
| | 2135 | |
| | 2136 | tty_config.setColor(w, .red) catch {}; |
| | 2137 | try stderr.writeAll(" Hope that helps. Proceeding to panic.\n"); |
| | 2138 | tty_config.setColor(w, .reset) catch {}; |
| | 2139 | } |
| | 2140 | |
| 1964 | /// In this function the stderr mutex has already been locked. | 2141 | /// In this function the stderr mutex has already been locked. |
| 1965 | pub fn dumpBadGetPathHelp( | 2142 | pub fn dumpBadGetPathHelp( |
| 1966 | s: *Step, | 2143 | s: *Step, |