| ... | ... | @@ -55,6 +55,20 @@ pub const Builder = struct { |
| 55 | 55 | override_std_dir: ?[]const u8, |
| 56 | 56 | override_lib_dir: ?[]const u8, |
| 57 | 57 | |
| 58 | pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, |
| 59 | |
| 60 | const PkgConfigError = error{ |
| 61 | PkgConfigCrashed, |
| 62 | PkgConfigFailed, |
| 63 | PkgConfigNotInstalled, |
| 64 | PkgConfigInvalidOutput, |
| 65 | }; |
| 66 | |
| 67 | pub const PkgConfigPkg = struct { |
| 68 | name: []const u8, |
| 69 | desc: []const u8, |
| 70 | }; |
| 71 | |
| 58 | 72 | pub const CStd = enum { |
| 59 | 73 | C89, |
| 60 | 74 | C99, |
| ... | ... | @@ -833,20 +847,21 @@ pub const Builder = struct { |
| 833 | 847 | return error.FileNotFound; |
| 834 | 848 | } |
| 835 | 849 | |
| 836 | | pub fn exec(self: *Builder, argv: []const []const u8) ![]u8 { |
| 850 | pub fn execAllowFail( |
| 851 | self: *Builder, |
| 852 | argv: []const []const u8, |
| 853 | out_code: *u8, |
| 854 | stderr_behavior: std.ChildProcess.StdIo, |
| 855 | ) ![]u8 { |
| 837 | 856 | assert(argv.len != 0); |
| 838 | 857 | |
| 839 | | if (self.verbose) { |
| 840 | | printCmd(null, argv); |
| 841 | | } |
| 842 | | |
| 843 | 858 | const max_output_size = 100 * 1024; |
| 844 | 859 | const child = try std.ChildProcess.init(argv, self.allocator); |
| 845 | 860 | defer child.deinit(); |
| 846 | 861 | |
| 847 | 862 | child.stdin_behavior = .Ignore; |
| 848 | 863 | child.stdout_behavior = .Pipe; |
| 849 | | child.stderr_behavior = .Inherit; |
| 864 | child.stderr_behavior = stderr_behavior; |
| 850 | 865 | |
| 851 | 866 | try child.spawn(); |
| 852 | 867 | |
| ... | ... | @@ -856,24 +871,48 @@ pub const Builder = struct { |
| 856 | 871 | var stdout_file_in_stream = child.stdout.?.inStream(); |
| 857 | 872 | try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); |
| 858 | 873 | |
| 859 | | const term = child.wait() catch |err| panic("unable to spawn {}: {}", argv[0], err); |
| 874 | const term = try child.wait(); |
| 860 | 875 | switch (term) { |
| 861 | 876 | .Exited => |code| { |
| 862 | 877 | if (code != 0) { |
| 863 | | warn("The following command exited with error code {}:\n", code); |
| 864 | | printCmd(null, argv); |
| 865 | | std.os.exit(@truncate(u8, code)); |
| 878 | out_code.* = @truncate(u8, code); |
| 879 | return error.ExitCodeFailure; |
| 866 | 880 | } |
| 867 | 881 | return stdout.toOwnedSlice(); |
| 868 | 882 | }, |
| 869 | 883 | .Signal, .Stopped, .Unknown => |code| { |
| 884 | out_code.* = @truncate(u8, code); |
| 885 | return error.ProcessTerminated; |
| 886 | }, |
| 887 | } |
| 888 | } |
| 889 | |
| 890 | pub fn exec(self: *Builder, argv: []const []const u8) ![]u8 { |
| 891 | assert(argv.len != 0); |
| 892 | |
| 893 | if (self.verbose) { |
| 894 | printCmd(null, argv); |
| 895 | } |
| 896 | |
| 897 | var code: u8 = undefined; |
| 898 | return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) { |
| 899 | error.FileNotFound => { |
| 900 | warn("Unable to spawn the following command: file not found\n"); |
| 901 | printCmd(null, argv); |
| 902 | std.os.exit(@truncate(u8, code)); |
| 903 | }, |
| 904 | error.ExitCodeFailure => { |
| 905 | warn("The following command exited with error code {}:\n", code); |
| 906 | printCmd(null, argv); |
| 907 | std.os.exit(@truncate(u8, code)); |
| 908 | }, |
| 909 | error.ProcessTerminated => { |
| 870 | 910 | warn("The following command terminated unexpectedly:\n"); |
| 871 | 911 | printCmd(null, argv); |
| 872 | 912 | std.os.exit(@truncate(u8, code)); |
| 873 | 913 | }, |
| 874 | | } |
| 875 | | |
| 876 | | return stdout.toOwnedSlice(); |
| 914 | else => |e| return e, |
| 915 | }; |
| 877 | 916 | } |
| 878 | 917 | |
| 879 | 918 | pub fn addSearchPrefix(self: *Builder, search_prefix: []const u8) void { |
| ... | ... | @@ -891,13 +930,45 @@ pub const Builder = struct { |
| 891 | 930 | [_][]const u8{ base_dir, dest_rel_path }, |
| 892 | 931 | ) catch unreachable; |
| 893 | 932 | } |
| 933 | |
| 934 | fn execPkgConfigList(self: *Builder, out_code: *u8) ![]const PkgConfigPkg { |
| 935 | const stdout = try self.execAllowFail([_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); |
| 936 | var list = ArrayList(PkgConfigPkg).init(self.allocator); |
| 937 | var line_it = mem.tokenize(stdout, "\r\n"); |
| 938 | while (line_it.next()) |line| { |
| 939 | if (mem.trim(u8, line, " \t").len == 0) continue; |
| 940 | var tok_it = mem.tokenize(line, " \t"); |
| 941 | try list.append(PkgConfigPkg{ |
| 942 | .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, |
| 943 | .desc = tok_it.rest(), |
| 944 | }); |
| 945 | } |
| 946 | return list.toSliceConst(); |
| 947 | } |
| 948 | |
| 949 | fn getPkgConfigList(self: *Builder) ![]const PkgConfigPkg { |
| 950 | if (self.pkg_config_pkg_list) |res| { |
| 951 | return res; |
| 952 | } |
| 953 | var code: u8 = undefined; |
| 954 | if (self.execPkgConfigList(&code)) |list| { |
| 955 | self.pkg_config_pkg_list = list; |
| 956 | return list; |
| 957 | } else |err| { |
| 958 | const result = switch (err) { |
| 959 | error.ProcessTerminated => error.PkgConfigCrashed, |
| 960 | error.ExitCodeFailure => error.PkgConfigFailed, |
| 961 | error.FileNotFound => error.PkgConfigNotInstalled, |
| 962 | error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, |
| 963 | else => return err, |
| 964 | }; |
| 965 | self.pkg_config_pkg_list = result; |
| 966 | return result; |
| 967 | } |
| 968 | } |
| 894 | 969 | }; |
| 895 | 970 | |
| 896 | 971 | test "builder.findProgram compiles" { |
| 897 | | //allocator: *Allocator, |
| 898 | | //zig_exe: []const u8, |
| 899 | | //build_root: []const u8, |
| 900 | | //cache_root: []const u8, |
| 901 | 972 | const builder = try Builder.create(std.heap.direct_allocator, "zig", "zig-cache", "zig-cache"); |
| 902 | 973 | _ = builder.findProgram([_][]const u8{}, [_][]const u8{}) catch null; |
| 903 | 974 | } |
| ... | ... | @@ -1388,6 +1459,7 @@ pub const LibExeObjStep = struct { |
| 1388 | 1459 | |
| 1389 | 1460 | link_objects: ArrayList(LinkObject), |
| 1390 | 1461 | include_dirs: ArrayList(IncludeDir), |
| 1462 | c_macros: ArrayList([]const u8), |
| 1391 | 1463 | output_dir: ?[]const u8, |
| 1392 | 1464 | need_system_paths: bool, |
| 1393 | 1465 | is_linking_libc: bool = false, |
| ... | ... | @@ -1491,6 +1563,7 @@ pub const LibExeObjStep = struct { |
| 1491 | 1563 | .packages = ArrayList(Pkg).init(builder.allocator), |
| 1492 | 1564 | .include_dirs = ArrayList(IncludeDir).init(builder.allocator), |
| 1493 | 1565 | .link_objects = ArrayList(LinkObject).init(builder.allocator), |
| 1566 | .c_macros = ArrayList([]const u8).init(builder.allocator), |
| 1494 | 1567 | .lib_paths = ArrayList([]const u8).init(builder.allocator), |
| 1495 | 1568 | .framework_dirs = ArrayList([]const u8).init(builder.allocator), |
| 1496 | 1569 | .object_src = undefined, |
| ... | ... | @@ -1617,6 +1690,9 @@ pub const LibExeObjStep = struct { |
| 1617 | 1690 | |
| 1618 | 1691 | /// Returns whether the library, executable, or object depends on a particular system library. |
| 1619 | 1692 | pub fn dependsOnSystemLibrary(self: LibExeObjStep, name: []const u8) bool { |
| 1693 | if (isLibCLibrary(name)) { |
| 1694 | return self.is_linking_libc; |
| 1695 | } |
| 1620 | 1696 | for (self.link_objects.toSliceConst()) |link_object| { |
| 1621 | 1697 | switch (link_object) { |
| 1622 | 1698 | LinkObject.SystemLib => |n| if (mem.eql(u8, n, name)) return true, |
| ... | ... | @@ -1641,13 +1717,135 @@ pub const LibExeObjStep = struct { |
| 1641 | 1717 | return self.isDynamicLibrary() or self.kind == .Exe; |
| 1642 | 1718 | } |
| 1643 | 1719 | |
| 1644 | | pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { |
| 1720 | pub fn linkLibC(self: *LibExeObjStep) void { |
| 1721 | if (!self.is_linking_libc) { |
| 1722 | self.is_linking_libc = true; |
| 1723 | self.link_objects.append(LinkObject{ .SystemLib = "c" }) catch unreachable; |
| 1724 | } |
| 1725 | } |
| 1726 | |
| 1727 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. |
| 1728 | pub fn defineCMacro(self: *LibExeObjStep, name_and_value: []const u8) void { |
| 1729 | self.c_macros.append(self.builder.dupe(name_and_value)) catch unreachable; |
| 1730 | } |
| 1731 | |
| 1732 | /// This one has no integration with anything, it just puts -lname on the command line. |
| 1733 | /// Prefer to use `linkSystemLibrary` instead. |
| 1734 | pub fn linkSystemLibraryName(self: *LibExeObjStep, name: []const u8) void { |
| 1645 | 1735 | self.link_objects.append(LinkObject{ .SystemLib = self.builder.dupe(name) }) catch unreachable; |
| 1736 | self.need_system_paths = true; |
| 1737 | } |
| 1738 | |
| 1739 | /// This links against a system library, exclusively using pkg-config to find the library. |
| 1740 | /// Prefer to use `linkSystemLibrary` instead. |
| 1741 | pub fn linkSystemLibraryPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) !void { |
| 1742 | const pkg_name = match: { |
| 1743 | // First we have to map the library name to pkg config name. Unfortunately, |
| 1744 | // there are several examples where this is not straightforward: |
| 1745 | // -lSDL2 -> pkg-config sdl2 |
| 1746 | // -lgdk-3 -> pkg-config gdk-3.0 |
| 1747 | // -latk-1.0 -> pkg-config atk |
| 1748 | const pkgs = try self.builder.getPkgConfigList(); |
| 1749 | |
| 1750 | // Exact match means instant winner. |
| 1751 | for (pkgs) |pkg| { |
| 1752 | if (mem.eql(u8, pkg.name, lib_name)) { |
| 1753 | break :match pkg.name; |
| 1754 | } |
| 1755 | } |
| 1756 | |
| 1757 | // Next we'll try ignoring case. |
| 1758 | for (pkgs) |pkg| { |
| 1759 | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { |
| 1760 | break :match pkg.name; |
| 1761 | } |
| 1762 | } |
| 1763 | |
| 1764 | // Now try appending ".0". |
| 1765 | for (pkgs) |pkg| { |
| 1766 | if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { |
| 1767 | if (pos != 0) continue; |
| 1768 | if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { |
| 1769 | break :match pkg.name; |
| 1770 | } |
| 1771 | } |
| 1772 | } |
| 1773 | |
| 1774 | // Trimming "-1.0". |
| 1775 | if (mem.endsWith(u8, lib_name, "-1.0")) { |
| 1776 | const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; |
| 1777 | for (pkgs) |pkg| { |
| 1778 | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { |
| 1779 | break :match pkg.name; |
| 1780 | } |
| 1781 | } |
| 1782 | } |
| 1783 | |
| 1784 | return error.PackageNotFound; |
| 1785 | }; |
| 1786 | |
| 1787 | var code: u8 = undefined; |
| 1788 | const stdout = if (self.builder.execAllowFail([_][]const u8{ |
| 1789 | "pkg-config", |
| 1790 | pkg_name, |
| 1791 | "--cflags", |
| 1792 | "--libs", |
| 1793 | }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { |
| 1794 | error.ProcessTerminated => return error.PkgConfigCrashed, |
| 1795 | error.ExitCodeFailure => return error.PkgConfigFailed, |
| 1796 | error.FileNotFound => return error.PkgConfigNotInstalled, |
| 1797 | else => return err, |
| 1798 | }; |
| 1799 | var it = mem.tokenize(stdout, " \r\n\t"); |
| 1800 | while (it.next()) |tok| { |
| 1801 | if (mem.eql(u8, tok, "-I")) { |
| 1802 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; |
| 1803 | self.addIncludeDir(dir); |
| 1804 | } else if (mem.startsWith(u8, tok, "-I")) { |
| 1805 | self.addIncludeDir(tok["-I".len..]); |
| 1806 | } else if (mem.eql(u8, tok, "-L")) { |
| 1807 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; |
| 1808 | self.addLibPath(dir); |
| 1809 | } else if (mem.startsWith(u8, tok, "-L")) { |
| 1810 | self.addLibPath(tok["-L".len..]); |
| 1811 | } else if (mem.eql(u8, tok, "-l")) { |
| 1812 | const lib = it.next() orelse return error.PkgConfigInvalidOutput; |
| 1813 | self.linkSystemLibraryName(lib); |
| 1814 | } else if (mem.startsWith(u8, tok, "-l")) { |
| 1815 | self.linkSystemLibraryName(tok["-l".len..]); |
| 1816 | } else if (mem.eql(u8, tok, "-D")) { |
| 1817 | const macro = it.next() orelse return error.PkgConfigInvalidOutput; |
| 1818 | self.defineCMacro(macro); |
| 1819 | } else if (mem.startsWith(u8, tok, "-D")) { |
| 1820 | self.defineCMacro(tok["-D".len..]); |
| 1821 | } else if (mem.eql(u8, tok, "-pthread")) { |
| 1822 | self.linkLibC(); |
| 1823 | } else if (self.builder.verbose) { |
| 1824 | warn("Ignoring pkg-config flag '{}'\n", tok); |
| 1825 | } |
| 1826 | } |
| 1827 | } |
| 1828 | |
| 1829 | pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { |
| 1646 | 1830 | if (isLibCLibrary(name)) { |
| 1647 | | self.is_linking_libc = true; |
| 1648 | | } else { |
| 1649 | | self.need_system_paths = true; |
| 1831 | self.linkLibC(); |
| 1832 | return; |
| 1833 | } |
| 1834 | if (self.linkSystemLibraryPkgConfigOnly(name)) |_| { |
| 1835 | // pkg-config worked, so nothing further needed to do. |
| 1836 | return; |
| 1837 | } else |err| switch (err) { |
| 1838 | error.PkgConfigInvalidOutput, |
| 1839 | error.PkgConfigCrashed, |
| 1840 | error.PkgConfigFailed, |
| 1841 | error.PkgConfigNotInstalled, |
| 1842 | error.PackageNotFound, |
| 1843 | => {}, |
| 1844 | |
| 1845 | else => unreachable, |
| 1650 | 1846 | } |
| 1847 | |
| 1848 | self.linkSystemLibraryName(name); |
| 1651 | 1849 | } |
| 1652 | 1850 | |
| 1653 | 1851 | pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void { |
| ... | ... | @@ -2072,6 +2270,11 @@ pub const LibExeObjStep = struct { |
| 2072 | 2270 | } |
| 2073 | 2271 | } |
| 2074 | 2272 | |
| 2273 | for (self.c_macros.toSliceConst()) |c_macro| { |
| 2274 | try zig_args.append("-D"); |
| 2275 | try zig_args.append(c_macro); |
| 2276 | } |
| 2277 | |
| 2075 | 2278 | if (self.target.isDarwin()) { |
| 2076 | 2279 | for (self.framework_dirs.toSliceConst()) |dir| { |
| 2077 | 2280 | try zig_args.append("-F"); |