╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ link.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1786 /// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running 1787 /// by then, we expose this function which can be called directly. 1788 pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void { 1789 const zcu = pt.zcu; 1790 const comp = zcu.comp; 1791 const diags = &comp.link_diags; 1792 if (zcu.llvm_object) |llvm_object| { 1793 llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { 1794 error.OutOfMemory => diags.setAllocFailure(), 1795 }; 1796 } else if (comp.bin_file) |lf| { 1797 lf.updateNav(pt, nav_index) catch |err| switch (err) { 1798 error.Canceled => comp.io.recancel(), 1799 error.AlreadyReported => return, 1800 error.OutOfMemory => diags.setAllocFailure(), 1801 }; 1802 } 1803 } 1804 pub fn updateErrorData(pt: Zcu.PerThread) void { 1805 const comp = pt.zcu.comp; 1806 if (comp.bin_file) |lf| lf.updateErrorData(pt) catch |err| switch (err) { 1807 error.OutOfMemory => comp.link_diags.setAllocFailure(), 1808 error.Canceled => comp.io.recancel(), 1809 error.AlreadyReported => {}, 1810 }; 1811 } 1812 1813 /// Provided by the CLI, processed into `LinkInput` instances at the start of 1814 /// the compilation pipeline. 1815 pub const UnresolvedInput = union(enum) { 1816 /// A library name that could potentially be dynamic or static depending on 1817 /// query parameters, resolved according to library directories. 1818 /// This could potentially resolve to a GNU ld script, resulting in more 1819 /// library dependencies. 1820 name_query: NameQuery, 1821 /// When a file path is provided, query info is still needed because the 1822 /// path may point to a .so file which may actually be a GNU ld script that 1823 /// references library names which need to be resolved. 1824 path_query: PathQuery, 1825 /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? 1826 /// Who knows! Fuck around and find out. 1827 ambiguous_name: NameQuery, 1828 /// Put exactly this string in the dynamic section, no rpath. 1829 dso_exact: Input.DsoExact, 1830 1831 pub const NameQuery = struct { 1832 name: []const u8, 1833 query: Query, 1834 }; 1835 1836 pub const PathQuery = struct { 1837 path: Path, 1838 query: Query, 1839 }; 1840 1841 pub const Query = struct { 1842 needed: bool = false, 1843 weak: bool = false, 1844 reexport: bool = false, 1845 must_link: bool = false, 1846 hidden: bool = false, 1847 allow_so_scripts: bool = false, 1848 preferred_mode: std.lang.LinkMode, 1849 search_strategy: SearchStrategy, 1850 1851 fn fallbackMode(q: Query) std.lang.LinkMode { 1852 assert(q.search_strategy != .no_fallback); 1853 return switch (q.preferred_mode) { 1854 .dynamic => .static, 1855 .static => .dynamic, 1856 }; 1857 } 1858 }; 1859 1860 pub const SearchStrategy = enum { 1861 paths_first, 1862 mode_first, 1863 no_fallback, 1864 }; 1865 }; 1866 1867 pub const Input = union(enum) { 1868 object: Object, 1869 archive: Object, 1870 res: Res, 1871 /// May not be a GNU ld script. Those are resolved when converting from 1872 /// `UnresolvedInput` to `Input` values. 1873 dso: Dso, 1874 dso_exact: DsoExact, 1875 1876 pub const Object = struct { 1877 path: Path, 1878 file: Io.File, 1879 must_link: bool, 1880 hidden: bool, 1881 }; 1882 1883 pub const Res = struct { 1884 path: Path, 1885 file: Io.File, 1886 }; 1887 1888 pub const Dso = struct { 1889 path: Path, 1890 file: Io.File, 1891 needed: bool, 1892 weak: bool, 1893 reexport: bool, 1894 }; 1895 1896 pub const DsoExact = struct { 1897 /// Includes the ":" prefix. This is intended to be put into the DSO 1898 /// section verbatim with no corresponding rpaths. 1899 name: []const u8, 1900 }; 1901 1902 /// Returns `null` in the case of `dso_exact`. 1903 pub fn path(input: Input) ?Path { 1904 return switch (input) { 1905 .object, .archive => |obj| obj.path, 1906 inline .res, .dso => |x| x.path, 1907 .dso_exact => null, 1908 }; 1909 } 1910 1911 /// Returns `null` in the case of `dso_exact`. 1912 pub fn pathAndFile(input: Input) ?struct { Path, Io.File } { 1913 return switch (input) { 1914 .object, .archive => |obj| .{ obj.path, obj.file }, 1915 inline .res, .dso => |x| .{ x.path, x.file }, 1916 .dso_exact => null, 1917 }; 1918 } 1919 1920 pub fn taskName(input: Input) []const u8 { 1921 return switch (input) { 1922 .object, .archive => |obj| obj.path.basename(), 1923 inline .res, .dso => |x| x.path.basename(), 1924 .dso_exact => "dso_exact", 1925 }; 1926 } 1927 }; 1928 1929 pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { 1930 for (link_inputs) |link_input| { 1931 man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input)); 1932 switch (link_input) { 1933 .object, .archive => |obj| { 1934 _ = try man.addInputPath(obj.path, .{ 1935 .handle = .{ .file = obj.file }, 1936 .request_handle = true, 1937 }); 1938 man.hash.add(obj.must_link); 1939 man.hash.add(obj.hidden); 1940 }, 1941 .res => |res| { 1942 _ = try man.addInputPath(res.path, .{ 1943 .handle = .{ .file = res.file }, 1944 .request_handle = true, 1945 }); 1946 }, 1947 .dso => |dso| { 1948 _ = try man.addInputPath(dso.path, .{ 1949 .handle = .{ .file = dso.file }, 1950 .request_handle = true, 1951 }); 1952 man.hash.add(dso.needed); 1953 man.hash.add(dso.weak); 1954 man.hash.add(dso.reexport); 1955 }, 1956 .dso_exact => |dso_exact| { 1957 man.hash.addBytes(dso_exact.name); 1958 }, 1959 } 1960 } 1961 } 1962 1963 pub fn resolveInputs( 1964 gpa: Allocator, 1965 arena: Allocator, 1966 io: Io, 1967 target: *const std.Target, 1968 /// This function mutates this array but does not take ownership. 1969 /// Allocated with `gpa`. 1970 unresolved_inputs: *std.ArrayList(UnresolvedInput), 1971 /// Allocated with `gpa`. 1972 resolved_inputs: *std.ArrayList(Input), 1973 lib_directories: []const Cache.Directory, 1974 color: std.zig.Color, 1975 ) Allocator.Error!void { 1976 var checked_paths: std.ArrayList(u8) = .empty; 1977 defer checked_paths.deinit(gpa); 1978 1979 var ld_script_bytes: std.ArrayList(u8) = .empty; 1980 defer ld_script_bytes.deinit(gpa); 1981 1982 var archive_dedup: ArchiveDedupMap = .empty; 1983 defer archive_dedup.deinit(gpa); 1984 1985 var failed_libs: std.ArrayList(struct { 1986 name: []const u8, 1987 strategy: UnresolvedInput.SearchStrategy, 1988 checked_paths: []const u8, 1989 preferred_mode: std.lang.LinkMode, 1990 }) = .empty; 1991 1992 // Convert external system libs into a stack so that items can be 1993 // pushed to it. 1994 // 1995 // This is necessary because shared objects might turn out to be 1996 // "linker scripts" that in fact resolve to one or more other 1997 // external system libs, including parameters such as "needed". 1998 // 1999 // Unfortunately, such files need to be detected immediately, so 2000 // that this library search logic can be applied to them. 2001 mem.reverse(UnresolvedInput, unresolved_inputs.items); 2002 2003 syslib: while (unresolved_inputs.pop()) |unresolved_input| { 2004 switch (unresolved_input) { 2005 .name_query => |name_query| { 2006 const query = name_query.query; 2007 2008 // Checked in the first pass above while looking for libc libraries. 2009 assert(!fs.path.isAbsolute(name_query.name)); 2010 2011 checked_paths.clearRetainingCapacity(); 2012 2013 switch (query.search_strategy) { 2014 .mode_first, .no_fallback => { 2015 // check for preferred mode 2016 for (lib_directories) |lib_directory| switch (try resolveLibInput( 2017 gpa, 2018 arena, 2019 io, 2020 unresolved_inputs, 2021 resolved_inputs, 2022 &checked_paths, 2023 &ld_script_bytes, 2024 &archive_dedup, 2025 lib_directory, 2026 name_query, 2027 target, 2028 query.preferred_mode, 2029 color, 2030 )) { 2031 .ok => continue :syslib, 2032 .no_match => {}, 2033 }; 2034 // check for fallback mode 2035 if (query.search_strategy == .no_fallback) { 2036 try failed_libs.append(arena, .{ 2037 .name = name_query.name, 2038 .strategy = query.search_strategy, 2039 .checked_paths = try arena.dupe(u8, checked_paths.items), 2040 .preferred_mode = query.preferred_mode, 2041 }); 2042 continue :syslib; 2043 } 2044 for (lib_directories) |lib_directory| switch (try resolveLibInput( 2045 gpa, 2046 arena, 2047 io, 2048 unresolved_inputs, 2049 resolved_inputs, 2050 &checked_paths, 2051 &ld_script_bytes, 2052 &archive_dedup, 2053 lib_directory, 2054 name_query, 2055 target, 2056 query.fallbackMode(), 2057 color, 2058 )) { 2059 .ok => continue :syslib, 2060 .no_match => {}, 2061 }; 2062 try failed_libs.append(arena, .{ 2063 .name = name_query.name, 2064 .strategy = query.search_strategy, 2065 .checked_paths = try arena.dupe(u8, checked_paths.items), 2066 .preferred_mode = query.preferred_mode, 2067 }); 2068 continue :syslib; 2069 }, 2070 .paths_first => { 2071 for (lib_directories) |lib_directory| { 2072 // check for preferred mode 2073 switch (try resolveLibInput( 2074 gpa, 2075 arena, 2076 io, 2077 unresolved_inputs, 2078 resolved_inputs, 2079 &checked_paths, 2080 &ld_script_bytes, 2081 &archive_dedup, 2082 lib_directory, 2083 name_query, 2084 target, 2085 query.preferred_mode, 2086 color, 2087 )) { 2088 .ok => continue :syslib, 2089 .no_match => {}, 2090 } 2091 2092 // check for fallback mode 2093 switch (try resolveLibInput( 2094 gpa, 2095 arena, 2096 io, 2097 unresolved_inputs, 2098 resolved_inputs, 2099 &checked_paths, 2100 &ld_script_bytes, 2101 &archive_dedup, 2102 lib_directory, 2103 name_query, 2104 target, 2105 query.fallbackMode(), 2106 color, 2107 )) { 2108 .ok => continue :syslib, 2109 .no_match => {}, 2110 } 2111 } 2112 try failed_libs.append(arena, .{ 2113 .name = name_query.name, 2114 .strategy = query.search_strategy, 2115 .checked_paths = try arena.dupe(u8, checked_paths.items), 2116 .preferred_mode = query.preferred_mode, 2117 }); 2118 continue :syslib; 2119 }, 2120 } 2121 }, 2122 .ambiguous_name => |an| { 2123 // First check the path relative to the current working directory. 2124 // If the file is a library and is not found there, check the library search paths as well. 2125 // This is consistent with the behavior of GNU ld. 2126 if (try resolvePathInput( 2127 gpa, 2128 arena, 2129 io, 2130 unresolved_inputs, 2131 resolved_inputs, 2132 &ld_script_bytes, 2133 &archive_dedup, 2134 target, 2135 .{ 2136 .path = Path.initCwd(an.name), 2137 .query = an.query, 2138 }, 2139 color, 2140 )) |lib_result| { 2141 switch (lib_result) { 2142 .ok => continue :syslib, 2143 .no_match => { 2144 for (lib_directories) |lib_directory| { 2145 switch ((try resolvePathInput( 2146 gpa, 2147 arena, 2148 io, 2149 unresolved_inputs, 2150 resolved_inputs, 2151 &ld_script_bytes, 2152 &archive_dedup, 2153 target, 2154 .{ 2155 .path = .{ 2156 .root_dir = lib_directory, 2157 .sub_path = an.name, 2158 }, 2159 .query = an.query, 2160 }, 2161 color, 2162 )).?) { 2163 .ok => continue :syslib, 2164 .no_match => {}, 2165 } 2166 } 2167 fatal("{s}: file listed in linker script not found", .{an.name}); 2168 }, 2169 } 2170 } 2171 continue; 2172 }, 2173 .path_query => |pq| { 2174 if (try resolvePathInput( 2175 gpa, 2176 arena, 2177 io, 2178 unresolved_inputs, 2179 resolved_inputs, 2180 &ld_script_bytes, 2181 &archive_dedup, 2182 target, 2183 pq, 2184 color, 2185 )) |lib_result| { 2186 switch (lib_result) { 2187 .ok => {}, 2188 .no_match => fatal("{f}: file not found", .{pq.path}), 2189 } 2190 } 2191 continue; 2192 }, 2193 .dso_exact => |dso_exact| { 2194 try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact }); 2195 continue; 2196 }, 2197 } 2198 comptime unreachable; 2199 } 2200 2201 if (failed_libs.items.len > 0) { 2202 for (failed_libs.items) |f| { 2203 const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; 2204 std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{ 2205 @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths, 2206 }); 2207 } 2208 std.process.exit(1); 2209 } 2210 } 2211 2212 const ResolveLibInputResult = enum { ok, no_match }; 2213 const fatal = std.process.fatal; 2214 2215 fn resolveLibInput( 2216 gpa: Allocator, 2217 arena: Allocator, 2218 io: Io, 2219 /// Allocated via `gpa`. 2220 unresolved_inputs: *std.ArrayList(UnresolvedInput), 2221 /// Allocated via `gpa`. 2222 resolved_inputs: *std.ArrayList(Input), 2223 /// Allocated via `gpa`. 2224 checked_paths: *std.ArrayList(u8), 2225 /// Allocated via `gpa`. 2226 ld_script_bytes: *std.ArrayList(u8), 2227 /// Allocated via `gpa`. 2228 archive_dedup: *ArchiveDedupMap, 2229 lib_directory: Directory, 2230 name_query: UnresolvedInput.NameQuery, 2231 target: *const std.Target, 2232 link_mode: std.lang.LinkMode, 2233 color: std.zig.Color, 2234 ) Allocator.Error!ResolveLibInputResult { 2235 try resolved_inputs.ensureUnusedCapacity(gpa, 1); 2236 try archive_dedup.ensureUnusedCapacity(gpa, 1); 2237 2238 const lib_name = name_query.name; 2239 2240 if (target.os.tag.isDarwin() and link_mode == .dynamic) tbd: { 2241 // Prefer .tbd over .dylib. 2242 const test_path: Path = .{ 2243 .root_dir = lib_directory, 2244 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}), 2245 }; 2246 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2247 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2248 error.FileNotFound => break :tbd, 2249 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }), 2250 }; 2251 errdefer file.close(io); 2252 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); 2253 } 2254 2255 { 2256 const test_path: Path = .{ 2257 .root_dir = lib_directory, 2258 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ 2259 target.libPrefix(), lib_name, 2260 switch (link_mode) { 2261 .static => target.staticLibSuffix(), 2262 .dynamic => target.dynamicLibSuffix(), 2263 }, 2264 }), 2265 }; 2266 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2267 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ 2268 .path = test_path, 2269 .query = name_query.query, 2270 }, link_mode, color)) { 2271 .no_match => {}, 2272 .ok => return .ok, 2273 } 2274 } 2275 2276 // In the case of Darwin, the main check will be .dylib, so here we 2277 // additionally check for .so files. 2278 if (target.os.tag.isDarwin() and link_mode == .dynamic) so: { 2279 const test_path: Path = .{ 2280 .root_dir = lib_directory, 2281 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), 2282 }; 2283 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2284 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2285 error.FileNotFound => break :so, 2286 else => |e| fatal("unable to search for so library '{f}': {s}", .{ 2287 test_path, @errorName(e), 2288 }), 2289 }; 2290 errdefer file.close(io); 2291 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); 2292 } 2293 2294 // In the case of MinGW, the main check will be .lib but we also need to 2295 // look for `libfoo.a`. 2296 if (target.isMinGW() and link_mode == .static) mingw: { 2297 const test_path: Path = .{ 2298 .root_dir = lib_directory, 2299 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), 2300 }; 2301 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2302 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2303 error.FileNotFound => break :mingw, 2304 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }), 2305 }; 2306 errdefer file.close(io); 2307 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); 2308 } 2309 2310 // In the case of OpenBSD, dynamic libraries are always versioned, without 2311 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned 2312 // shared library, and this code is intended to match that upstream behavior. 2313 if (target.isOpenBSDLibC() and link_mode == .dynamic) versioned: { 2314 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name}); 2315 2316 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) { 2317 error.NotDir, error.FileNotFound => break :versioned, 2318 else => |e| fatal("unable to search for shared library '{s}.*': {s}", .{ prefix, @errorName(e) }), 2319 }; 2320 defer dir.close(io); 2321 2322 var best_match_major: u32 = 0; 2323 var best_match_minor: u32 = 0; 2324 var best_match: ?[]const u8 = null; 2325 2326 var iter = dir.iterate(); 2327 while (iter.next(io) catch |err| { 2328 fatal("unable to scan library directory '{s}'", .{@errorName(err)}); 2329 }) |entry| { 2330 if (entry.kind != .file) continue; 2331 if (!std.mem.startsWith(u8, entry.name, prefix)) continue; 2332 2333 const rest = entry.name[prefix.len..]; 2334 var sit = std.mem.splitScalar(u8, rest, '.'); 2335 const major_str = sit.next() orelse continue; 2336 const minor_str = sit.next() orelse continue; 2337 if (sit.next() != null) continue; 2338 const major = std.fmt.parseInt(u32, major_str, 10) catch continue; 2339 const minor = std.fmt.parseInt(u32, minor_str, 10) catch continue; 2340 2341 if (major > best_match_major or (major == best_match_major and minor >= best_match_minor)) { 2342 best_match_major = major; 2343 best_match_minor = minor; 2344 best_match = try arena.dupe(u8, entry.name); 2345 } 2346 } 2347 2348 if (best_match) |found| { 2349 const test_path: Path = .{ 2350 .root_dir = lib_directory, 2351 .sub_path = found, 2352 }; 2353 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2354 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ 2355 .path = test_path, 2356 .query = name_query.query, 2357 }, link_mode, color)) { 2358 .no_match => {}, 2359 .ok => return .ok, 2360 } 2361 } 2362 } 2363 2364 return .no_match; 2365 } 2366 2367 /// Deduplicates static archive link inputs based on their path. This is done for efficiency, so 2368 /// that linker implementations do not need to open and scan the archive just to determine that they 2369 /// need not extract any objects. At the time of writing, it also helps avoid "multiple definitions 2370 /// of symbol" errors in incomplete linker implementations. 2371 /// 2372 /// Key is index into `resolved_inputs` of an `Input.archive`. 2373 /// 2374 /// Accessed through `ArchiveDedupAdapter`. 2375 /// 2376 const ArchiveDedupMap = std.array_hash_map.Custom(u32, void, void, true); 2377 /// Adapter for accessing `ArchiveDedupMap` with an effective key type of `Path`. 2378 const ArchiveDedupAdapter = struct { 2379 resolved_inputs: []const Input, 2380 pub fn hash(ctx: ArchiveDedupAdapter, path: Path) u32 { 2381 _ = ctx; 2382 return Path.TableAdapter.hash(.{}, path); 2383 } 2384 pub fn eql(ctx: ArchiveDedupAdapter, a_path: Path, b_input_index: u32, _: usize) bool { 2385 const b_path = ctx.resolved_inputs[b_input_index].archive.path; 2386 return a_path.eql(b_path); 2387 } 2388 }; 2389 2390 fn finishResolveLibInput( 2391 io: Io, 2392 resolved_inputs: *std.ArrayList(Input), 2393 archive_dedup: *ArchiveDedupMap, 2394 path: Path, 2395 file: Io.File, 2396 link_mode: std.lang.LinkMode, 2397 query: UnresolvedInput.Query, 2398 ) ResolveLibInputResult { 2399 switch (link_mode) { 2400 .static => { 2401 const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; 2402 const gop = archive_dedup.getOrPutAssumeCapacityAdapted(path, ctx); 2403 if (gop.found_existing) { 2404 // Ignore duplicate archive input 2405 file.close(io); 2406 return .ok; 2407 } 2408 gop.key_ptr.* = @intCast(resolved_inputs.items.len); 2409 resolved_inputs.appendAssumeCapacity(.{ .archive = .{ 2410 .path = path, 2411 .file = file, 2412 .must_link = query.must_link, 2413 .hidden = query.hidden, 2414 } }); 2415 }, 2416 .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ 2417 .path = path, 2418 .file = file, 2419 .needed = query.needed, 2420 .weak = query.weak, 2421 .reexport = query.reexport, 2422 } }), 2423 } 2424 return .ok; 2425 } 2426 2427 fn resolvePathInput( 2428 gpa: Allocator, 2429 arena: Allocator, 2430 io: Io, 2431 /// Allocated with `gpa`. 2432 unresolved_inputs: *std.ArrayList(UnresolvedInput), 2433 /// Allocated with `gpa`. 2434 resolved_inputs: *std.ArrayList(Input), 2435 /// Allocated via `gpa`. 2436 ld_script_bytes: *std.ArrayList(u8), 2437 /// Allocated via `gpa`. 2438 archive_dedup: *ArchiveDedupMap, 2439 target: *const std.Target, 2440 pq: UnresolvedInput.PathQuery, 2441 color: std.zig.Color, 2442 ) Allocator.Error!?ResolveLibInputResult { 2443 switch (Compilation.classifyFileExt(pq.path.sub_path)) { 2444 .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .static, color), 2445 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color), 2446 .object => { 2447 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| 2448 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); 2449 errdefer file.close(io); 2450 try resolved_inputs.append(gpa, .{ .object = .{ 2451 .path = pq.path, 2452 .file = file, 2453 .must_link = pq.query.must_link, 2454 .hidden = pq.query.hidden, 2455 } }); 2456 return null; 2457 }, 2458 .res => { 2459 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| 2460 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) }); 2461 errdefer file.close(io); 2462 try resolved_inputs.append(gpa, .{ .res = .{ 2463 .path = pq.path, 2464 .file = file, 2465 } }); 2466 return null; 2467 }, 2468 else => fatal("{f}: unrecognized file extension", .{pq.path}), 2469 } 2470 } 2471 2472 fn resolvePathInputLib( 2473 gpa: Allocator, 2474 arena: Allocator, 2475 io: Io, 2476 /// Allocated with `gpa`. 2477 unresolved_inputs: *std.ArrayList(UnresolvedInput), 2478 /// Allocated with `gpa`. 2479 resolved_inputs: *std.ArrayList(Input), 2480 /// Allocated via `gpa`. 2481 ld_script_bytes: *std.ArrayList(u8), 2482 /// Allocated via `gpa`. 2483 archive_dedup: *ArchiveDedupMap, 2484 target: *const std.Target, 2485 pq: UnresolvedInput.PathQuery, 2486 link_mode: std.lang.LinkMode, 2487 color: std.zig.Color, 2488 ) Allocator.Error!ResolveLibInputResult { 2489 try resolved_inputs.ensureUnusedCapacity(gpa, 1); 2490 try archive_dedup.ensureUnusedCapacity(gpa, 1); 2491 2492 const test_path: Path = pq.path; 2493 // In the case of shared libraries, they might actually be "linker scripts" 2494 // that contain references to other libraries. 2495 if (pq.query.allow_so_scripts and target.ofmt == .elf and switch (Compilation.classifyFileExt(test_path.sub_path)) { 2496 .static_library, .shared_library => true, 2497 else => false, 2498 }) { 2499 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2500 error.FileNotFound => return .no_match, 2501 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{ 2502 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, 2503 }), 2504 }; 2505 errdefer file.close(io); 2506 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); 2507 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| 2508 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err }); 2509 const buf = ld_script_bytes.items[0..n]; 2510 if (mem.startsWith(u8, buf, std.elf.MAGIC) or 2511 mem.startsWith(u8, buf, std.elf.ARMAG) or 2512 mem.startsWith(u8, buf, std.elf.ARMAG_THIN)) 2513 { 2514 // Appears to be an ELF or archive file. 2515 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); 2516 } 2517 const stat = file.stat(io) catch |err| 2518 fatal("failed to stat {f}: {t}", .{ test_path, err }); 2519 const size = std.math.cast(u32, stat.size) orelse 2520 fatal("{f}: linker script too big", .{test_path}); 2521 try ld_script_bytes.resize(gpa, size); 2522 const buf2 = ld_script_bytes.items[n..]; 2523 const n2 = file.readPositionalAll(io, buf2, n) catch |err| 2524 fatal("failed to read {f}: {t}", .{ test_path, err }); 2525 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path}); 2526 2527 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent. 2528 var threaded: Io.Threaded = .init_single_threaded; 2529 defer threaded.deinit(); 2530 var diags: Diags = .init(gpa, threaded.io()); 2531 defer diags.deinit(); 2532 2533 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items); 2534 if (diags.hasErrors()) { 2535 var wip_errors: std.zig.ErrorBundle.Wip = undefined; 2536 try wip_errors.init(gpa); 2537 defer wip_errors.deinit(); 2538 2539 try diags.addMessagesToBundle(&wip_errors, null); 2540 2541 var error_bundle = try wip_errors.toOwnedBundle(""); 2542 defer error_bundle.deinit(gpa); 2543 2544 error_bundle.renderToStderr(io, .{}, color) catch {}; 2545 std.process.exit(1); 2546 } 2547 2548 var ld_script = ld_script_result catch |err| 2549 fatal("{f}: failed to parse linker script: {t}", .{ test_path, err }); 2550 defer ld_script.deinit(gpa); 2551 2552 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len); 2553 for (ld_script.args) |arg| { 2554 const query: UnresolvedInput.Query = .{ 2555 .needed = arg.needed or pq.query.needed, 2556 .weak = pq.query.weak, 2557 .reexport = pq.query.reexport, 2558 .preferred_mode = pq.query.preferred_mode, 2559 .search_strategy = pq.query.search_strategy, 2560 .allow_so_scripts = pq.query.allow_so_scripts, 2561 }; 2562 if (mem.startsWith(u8, arg.path, "-l")) { 2563 unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{ 2564 .name = try arena.dupe(u8, arg.path["-l".len..]), 2565 .query = query, 2566 } }); 2567 } else { 2568 unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{ 2569 .name = try arena.dupe(u8, arg.path), 2570 .query = query, 2571 } }); 2572 } 2573 } 2574 file.close(io); 2575 return .ok; 2576 } 2577 2578 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2579 error.FileNotFound => return .no_match, 2580 else => |e| fatal("unable to search for {s} library {f}: {s}", .{ 2581 @tagName(link_mode), test_path, @errorName(e), 2582 }), 2583 }; 2584 errdefer file.close(io); 2585 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); 2586 } 2587 2588 pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { 2589 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); 2590 errdefer file.close(io); 2591 return .{ 2592 .path = path, 2593 .file = file, 2594 .must_link = must_link, 2595 .hidden = hidden, 2596 }; 2597 } 2598 2599 pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { 2600 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); 2601 errdefer file.close(io); 2602 return .{ 2603 .path = path, 2604 .file = file, 2605 .needed = needed, 2606 .weak = weak, 2607 .reexport = reexport, 2608 }; 2609 } 2610 2611 pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{AlreadyReported}!Input { 2612 return .{ .object = openObject(io, path, false, false) catch |err| { 2613 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2614 } }; 2615 } 2616 2617 pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{AlreadyReported}!Input { 2618 return .{ .archive = openObject(io, path, must_link, hidden) catch |err| { 2619 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2620 } }; 2621 } 2622 2623 pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{AlreadyReported}!Input { 2624 return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| { 2625 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2626 } }; 2627 } 2628 2629 /// Returns true if and only if there is at least one input of type object, 2630 /// archive, or Windows resource file. 2631 pub fn anyObjectInputs(inputs: []const Input) bool { 2632 return countObjectInputs(inputs) != 0; 2633 } 2634 2635 /// Returns the number of inputs of type object, archive, or Windows resource file. 2636 pub fn countObjectInputs(inputs: []const Input) usize { 2637 var count: usize = 0; 2638 for (inputs) |input| switch (input) { 2639 .dso, .dso_exact => continue, 2640 .res, .object, .archive => count += 1, 2641 }; 2642 return count; 2643 } 2644 2645 /// Returns the first input of type object or archive. 2646 pub fn firstObjectInput(inputs: []const Input) ?Input.Object { 2647 for (inputs) |input| switch (input) { 2648 .object, .archive => |obj| return obj, 2649 .res, .dso, .dso_exact => continue, 2650 }; 2651 return null; 2652 } 2653