On Mon, Aug 10, 2026 at 12:04 AM John Ericson <[email protected]> wrote:
>
> Resolving a program was split between two places. find_a_program
> searched the compiler's own directories; where it came up empty, execute
> left the bare name in argv[0] and passed PEX_SEARCH, handing the rest of
> the job to execvp.
>
> Do that last step in find_a_program instead, walking PATH a directory at
> a time, and stop passing PEX_SEARCH.
>
> This is the first caller of for_each_env_path that wants to stop
> before the end, so the helper gains the same protocol for_each_path
> already uses: the callback returns null to carry on, or the answer to
> stop there. The existing callers visit every directory and simply
> return null.
>
> This is not new ground for GCC: collect2 and gcc-ar have always folded
> PATH into their own prefix lists and searched it directly, rather than
> delegating to execvp.
>
> Reimplementing a search the C library had been doing for us means we no
> longer inherit its behaviour, and the full consequences of that are not
> knowable from here: how execvp treats PATH differs between C libraries,
> and the driver is built against whichever one the host happens to
> provide.
>
> What that buys is predictability. The search is now GCC's own, so it is
> the same search on every host, rather than whatever the local libc
> implements. It is also the same search the driver already performs for
> its own directories, instead of one set of rules there and another once
> PATH is reached.
>
> It also makes -print-prog-name give a useful answer. Until now, for a
> program the driver could only reach through PATH, it printed the name
> back unchanged -- 'ld' in, 'ld' out -- because the driver genuinely did
> not know, having delegated the lookup to execvp. A build system asking
> the compiler where its linker is therefore learned nothing, and had to
> go looking itself, possibly settling on a different file than the one
> GCC would run. Now it prints the file GCC will actually execute.
>
> This also leaves resolution as one job in one function, which the next
> commit needs: the search can then be taught about machine-prefixed names
> in a single place.
>
> One final note: an alternative implementation strategy would be to
> simply add PATH to exec_prefixes. The for_each_path helper would then
> process it along with the other exec prefixes, and PATH would only be
> parsed up front. Both of these sound nice. I did not do it because it
> would introduce more user-visible behaviour changes: that list is
> exported to subprocesses as COMPILER_PATH, and printed by
> -print-search-dirs, so PATH would leak into both. for_each_path also
> probes each prefix with the machine and multilib suffixes, whereas PATH
> means precisely the directories named.
>
> gcc/ChangeLog:
>
> * gcc.cc (find_a_program): Search PATH once the exec prefixes are
> exhausted.
> (execute): Do not pass PEX_SEARCH.
> (for_each_env_path): Return the callback's value, stopping at
> the first non-null one.
> (process_command): Adjust callbacks accordingly.
> * doc/invoke.texi (Developer Options): Document that
> -print-prog-name searches PATH.
>
> Signed-off-by: John Ericson <[email protected]>
> ---
> gcc/doc/invoke.texi | 2 ++
> gcc/gcc.cc | 60 ++++++++++++++++++++++++++++++++++++---------
> 2 files changed, 51 insertions(+), 11 deletions(-)
>
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 7c65c924271..43469bf3d5e 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -21230,6 +21230,8 @@ relative to some @file{lib} subdirectory.
> @itemx --print-prog-name=@var{program}
> @itemx --print-prog-name @var{program}
> Like @option{-print-file-name}, but searches for a program such as
> @command{cpp}.
> +The compiler's own directories are searched first, then @env{PATH}. If no
> +such program is found, @var{program} is printed back unchanged.
>
> @opindex print-libgcc-file-name
> @item -print-libgcc-file-name
> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
> index 08a192ade76..c494ddd0665 100644
> --- a/gcc/gcc.cc
> +++ b/gcc/gcc.cc
> @@ -2990,14 +2990,19 @@ for_each_path (const struct path_prefix *paths,
> An empty element denotes the current directory. Each directory is passed
> with a trailing directory separator, as add_prefix expects. The buffer
> handed to CALLBACK is reused between iterations, so CALLBACK must copy
> - anything it wants to keep -- add_prefix does. */
> + anything it wants to keep -- add_prefix does.
> +
> + As with for_each_path, CALLBACK returns null to carry on to the next
> + directory, and anything else to stop there. That value is returned. */
>
> template<typename fun>
> -static void
> +static auto *
> for_each_env_path (const char *value, fun callback)
> {
> + decltype (callback (nullptr)) ret = nullptr;
I am not a fan of this at all. Is there a better way? Like requiring
the callback function returning `const char*` or `char*`?
> +
> if (!value)
> - return;
> + return ret;
>
> char *nstore = (char *) alloca (strlen (value) + 3);
>
> @@ -3020,12 +3025,16 @@ for_each_env_path (const char *value, fun callback)
> nstore[endp - startp] = 0;
> }
>
> - callback (nstore);
> + ret = callback (nstore);
> + if (ret)
> + break;
>
> if (*endp == 0)
> break;
> startp = endp + 1;
> }
> +
> + return ret;
> }
>
> /* Add or change the value of an environment variable, outputting the
> @@ -3186,9 +3195,9 @@ find_a_program (const char *name)
> /* Callback appends the file name to the directory path. If the
> resulting file exists in the right mode, return the full pathname
> to the file. */
> - return for_each_path (&exec_prefixes, false,
> - prefix_len + name_len + suffix_len,
> - [=](char *path, bool machine_specific) -> char*
> + char *ret = for_each_path (&exec_prefixes, false,
> + prefix_len + name_len + suffix_len,
> + [=](char *path, bool machine_specific) -> char*
I Know this was not done before but can we have an explicit list of captures?
> {
> size_t path_len = strlen (path);
>
> @@ -3227,6 +3236,34 @@ find_a_program (const char *name)
>
> return search(path_len);
> });
> +
> + if (ret)
> + return ret;
> +
> + /* Nothing among our own directories, so fall back to PATH.
> +
> + Searching it here rather than via execvp means we know which file
> + we picked, so -print-prog-name can report it. */
> +
> + return for_each_env_path (env.get ("PATH"), [&] (const char *dir) -> char*
List out the captures so it is more obvious why you need to capture by
reference here.
> + {
> + /* Some systems have a suffix for executable files. As above, try
> + appending that first. */
> + if (suffix_len)
> + {
> + char *candidate = concat (dir, name, suffix, NULL);
> + if (access_check (candidate, X_OK) == 0)
> + return candidate;
> + free (candidate);
> + }
> +
> + char *candidate = concat (dir, name, NULL);
> + if (access_check (candidate, X_OK) == 0)
> + return candidate;
> +
> + free (candidate);
> + return NULL;
> + });
> }
>
> /* Ranking of prefixes in the sort list. -B prefixes are put before
> @@ -3548,8 +3585,7 @@ execute (void)
> const char *string = commands[i].argv[0];
>
> errmsg = pex_run (pex,
> - ((i + 1 == n_commands ? PEX_LAST : 0)
> - | (string == commands[i].prog ? PEX_SEARCH : 0)),
> + (i + 1 == n_commands ? PEX_LAST : 0),
> string, const_cast<char **> (commands[i].argv),
> NULL, NULL, &err);
> if (errmsg != NULL)
> @@ -5007,18 +5043,20 @@ process_command (unsigned int decoded_options_count,
> /* COMPILER_PATH and LIBRARY_PATH have values
> that are lists of directory names with colons. */
>
> - for_each_env_path (env.get ("COMPILER_PATH"), [] (const char *dir)
> + for_each_env_path (env.get ("COMPILER_PATH"), [] (const char *dir) -> void*
> {
> add_prefix (&exec_prefixes, dir, 0, PREFIX_PRIORITY_LAST, 0, 0);
> add_prefix (&include_prefixes, dir, 0, PREFIX_PRIORITY_LAST, 0, 0);
> + return NULL;
> });
>
> if (*cross_compile == '0')
> {
> - auto add_startfile_prefix = [] (const char *dir)
> + auto add_startfile_prefix = [] (const char *dir) -> void*
> {
> add_prefix (&startfile_prefixes, dir, NULL,
> PREFIX_PRIORITY_LAST, 0, 1);
> + return NULL;
> };
>
> for_each_env_path (env.get (LIBRARY_PATH_ENV), add_startfile_prefix);
> --
> 2.54.0
>