| ... | ... | @@ -99,6 +99,8 @@ pub const Graph = struct { |
| 99 | 99 | wip_configuration: Configuration.Wip, |
| 100 | 100 | |
| 101 | 101 | cache_poison: CachePoison = .pure, |
| 102 | /// Observing this data causes cache poisoning. See `CachePoison`. |
| 103 | search_prefixes: std.ArrayList([]const u8) = .empty, |
| 102 | 104 | |
| 103 | 105 | /// If the cache is poisoned means that the **configure logic** had side |
| 104 | 106 | /// effects, or otherwise did something that could not be tracked by the |
| ... | ... | @@ -1706,9 +1708,6 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { |
| 1706 | 1708 | /// Creates an anonymous `Step` that searches for an executable on the host that |
| 1707 | 1709 | /// has more than one possible name. |
| 1708 | 1710 | /// |
| 1709 | | /// Names are searched in order, observing search prefixes first and then PATH |
| 1710 | | /// environment variable. |
| 1711 | | /// |
| 1712 | 1711 | /// Returns the `LazyPath` of the found executable. The search only takes place |
| 1713 | 1712 | /// if the `LazyPath` will be used by a depending `Step`. |
| 1714 | 1713 | /// |
| ... | ... | @@ -1719,6 +1718,9 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { |
| 1719 | 1718 | /// globally installed and will therefore be possibly found in one of the |
| 1720 | 1719 | /// search prefix paths. |
| 1721 | 1720 | /// |
| 1721 | /// Names are searched in order, observing search prefixes first and then PATH |
| 1722 | /// environment variable. |
| 1723 | /// |
| 1722 | 1724 | /// Windows file name extensions are searched automatically, respecting the |
| 1723 | 1725 | /// PATHEXT environment variable, so they need not be included in this list. |
| 1724 | 1726 | /// However, even on Windows, the names will be checked without appending |
| ... | ... | @@ -1730,24 +1732,98 @@ pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath { |
| 1730 | 1732 | return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } }; |
| 1731 | 1733 | } |
| 1732 | 1734 | |
| 1735 | pub const FindProgramOptions = Step.FindProgram.Options; |
| 1736 | |
| 1733 | 1737 | /// Immediately (in the configure phase), searches for an executable on the host |
| 1734 | 1738 | /// that has more than one possible name. |
| 1735 | 1739 | /// |
| 1740 | /// Calling this function poisons the configuration cache, so it is only |
| 1741 | /// appropriate when the existence of the program or its output needs to be |
| 1742 | /// observed by configuration logic. For more information, see |
| 1743 | /// `Graph.CachePoison` documentation. |
| 1744 | /// |
| 1736 | 1745 | /// Names are searched in order, observing search prefixes first and then PATH |
| 1737 | 1746 | /// environment variable. |
| 1738 | 1747 | /// |
| 1739 | | /// Calling this function poisons the configuration cache. For more |
| 1740 | | /// information, see `Graph.CachePoison` documentation. |
| 1748 | /// Windows file name extensions are searched automatically, respecting the |
| 1749 | /// PATHEXT environment variable, so they need not be included in this list. |
| 1750 | /// However, even on Windows, the names will be checked without appending |
| 1751 | /// extensions first, so that can be used as a priority system. |
| 1741 | 1752 | /// |
| 1742 | 1753 | /// See also: |
| 1743 | 1754 | /// * `findProgramLazy` |
| 1744 | | pub fn findProgram(b: *Build, names: []const []const u8) ?[]const u8 { |
| 1755 | pub fn findProgram(b: *Build, options: FindProgramOptions) ?[]const u8 { |
| 1745 | 1756 | const graph = b.graph; |
| 1746 | | const wc = &graph.wip_configuration; |
| 1747 | | const string_list = wc.addStringList(names) catch @panic("OOM"); |
| 1748 | | _ = string_list; |
| 1757 | |
| 1758 | // Because it observes search prefixes and contents of directories in PATH. |
| 1749 | 1759 | graph.poisonCache(); |
| 1750 | | @panic("TODO"); |
| 1760 | |
| 1761 | for (options.names) |name| { |
| 1762 | if (Io.Dir.path.isAbsolute(name)) { |
| 1763 | if (tryFindProgram(b, name)) |found| return found; |
| 1764 | } |
| 1765 | for (graph.search_prefixes.items) |search_prefix| { |
| 1766 | const full_path = b.pathJoin(&.{ search_prefix, "bin", name }); |
| 1767 | if (tryFindProgram(b, full_path)) |found| return found; |
| 1768 | } |
| 1769 | } |
| 1770 | |
| 1771 | if (b.graph.environ_map.get("PATH")) |PATH| { |
| 1772 | for (options.names) |name| { |
| 1773 | var it = mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); |
| 1774 | while (it.next()) |p| { |
| 1775 | const full_path = b.pathJoin(&.{ p, name }); |
| 1776 | if (tryFindProgram(b, full_path)) |found| return found; |
| 1777 | } |
| 1778 | } |
| 1779 | } |
| 1780 | |
| 1781 | return null; |
| 1782 | } |
| 1783 | |
| 1784 | fn supportedWindowsProgramExtension(ext: []const u8) bool { |
| 1785 | inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| { |
| 1786 | if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true; |
| 1787 | } |
| 1788 | return false; |
| 1789 | } |
| 1790 | |
| 1791 | fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1792 | const graph = b.graph; |
| 1793 | const io = graph.io; |
| 1794 | const arena = graph.arena; |
| 1795 | |
| 1796 | if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { |
| 1797 | return full_path; |
| 1798 | } else |err| switch (err) { |
| 1799 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { |
| 1800 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, full_path }); |
| 1801 | }, |
| 1802 | else => |e| return panic("failed accessing {s}: {t}", .{ full_path, e }), |
| 1803 | } |
| 1804 | |
| 1805 | if (builtin.os.tag == .windows) { |
| 1806 | if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| { |
| 1807 | var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); |
| 1808 | |
| 1809 | while (it.next()) |ext| { |
| 1810 | if (!supportedWindowsProgramExtension(ext)) continue; |
| 1811 | |
| 1812 | const extended_path = try mem.concat(arena, &.{ full_path, ext }); |
| 1813 | |
| 1814 | if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { |
| 1815 | return extended_path; |
| 1816 | } else |err| switch (err) { |
| 1817 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { |
| 1818 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, extended_path }); |
| 1819 | }, |
| 1820 | else => |e| return panic("failed accessing {s}: {t}", .{ extended_path, e }), |
| 1821 | } |
| 1822 | } |
| 1823 | } |
| 1824 | } |
| 1825 | |
| 1826 | return null; |
| 1751 | 1827 | } |
| 1752 | 1828 | |
| 1753 | 1829 | /// Deprecated; use `runFallible`. |