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
>

Reply via email to