Refactor GN executable.

Changes:
* Refactored FindGnInPath to FindInPath
* Added function FindGnTool like we have for all other tools.
* Improved error messages
* Switched subprocess to execv for better handling of signals.

Bug: chromium:471053914
Change-Id: I58ca4d153974dd50a64c6651917a9fb96a6a6964
This commit is contained in:
Matt Stark
2025-12-22 15:34:13 +11:00
parent 967382fdcd
commit 3256e818da
2 changed files with 54 additions and 48 deletions

View File

@@ -122,6 +122,8 @@ def _GetBuildtoolsPathInternal(cwd, override):
# buildtools may be in the gclient root.
gclient_root = FindGclientRoot(os.getcwd())
if not gclient_root:
return None
buildtools_path = os.path.join(gclient_root, 'buildtools')
if os.path.exists(buildtools_path):
return buildtools_path
@@ -185,3 +187,17 @@ def GetGClientPrimarySolutionURL(gclient_root_dir_path):
if solutions:
return solutions[0].get('url')
return None
def FindInPath(name: str):
"""Shutil.which replacement that skips depot_tools in PATH."""
env_path = os.getenv('PATH')
if not env_path:
return
for bin_dir in env_path.split(os.pathsep):
if bin_dir.rstrip(os.sep).endswith('depot_tools'):
# skip depot_tools to avoid calling gn.py infinitely.
continue
path = os.path.join(bin_dir, name + GetExeSuffix())
if os.path.isfile(path):
return path

86
gn.py
View File

@@ -33,18 +33,42 @@ def PruneVirtualEnv():
])
def findGnInPath():
env_path = os.getenv('PATH')
if not env_path:
return
for bin_dir in env_path.split(os.pathsep):
if bin_dir.rstrip(os.sep).endswith('depot_tools'):
# skip depot_tools to avoid calling gn.py infinitely.
continue
gn_path = os.path.join(bin_dir, 'gn' + gclient_paths.GetExeSuffix())
if os.path.isfile(gn_path):
def FindGnTool():
# Try in primary solution location first, with the gn binary having been
# downloaded by cipd in the projects DEPS.
primary_solution_path = gclient_paths.GetPrimarySolutionPath()
if primary_solution_path:
gn_path = os.path.join(primary_solution_path, 'third_party', 'gn',
'gn' + gclient_paths.GetExeSuffix())
if os.path.exists(gn_path):
return gn_path
# Otherwise try the old .sha1 and download_from_google_storage locations
# inside of buildtools.
bin_path = gclient_paths.GetBuildtoolsPlatformBinaryPath()
if not bin_path:
gn_path = gclient_paths.FindInPath('gn')
if gn_path:
return gn_path
print('gn.py: Unable to find gn in your $PATH')
print('Hint: `which -a gn` should output two entries')
return None
# TODO(b/328065301): Once chromium/src CL has landed to migrate
# buildtools/<platform>/gn to buildtools/<platform>/gn/gn, only return
# gn/gn path.
old_gn_path = os.path.join(bin_path, 'gn' + gclient_paths.GetExeSuffix())
new_gn_path = os.path.join(bin_path, 'gn',
'gn' + gclient_paths.GetExeSuffix())
paths = [new_gn_path, old_gn_path]
for path in paths:
if os.path.isfile(path):
return path
print('gn.py: Could not find gn executable at: %s' % paths, file=sys.stderr)
print(
"Either GN isn't installed on your system, or you're not running in " +
"a checkout with a preinstalled gn binary.",
file=sys.stderr)
def main(args):
# Prune all evidence of VPython/VirtualEnv out of the environment. This
@@ -55,45 +79,11 @@ def main(args):
# .vpython3 file (or lack thereof), but instead reference the default python
# from the PATH.
PruneVirtualEnv()
# Try in primary solution location first, with the gn binary having been
# downloaded by cipd in the projects DEPS.
primary_solution_path = gclient_paths.GetPrimarySolutionPath()
if primary_solution_path:
gn_path = os.path.join(primary_solution_path, 'third_party', 'gn',
'gn' + gclient_paths.GetExeSuffix())
if os.path.exists(gn_path):
return subprocess.call([gn_path] + args[1:])
# Otherwise try the old .sha1 and download_from_google_storage locations
# inside of buildtools.
bin_path = gclient_paths.GetBuildtoolsPlatformBinaryPath()
if not bin_path:
gn_path = findGnInPath()
if gn_path:
return subprocess.call([gn_path] + args[1:])
print(
'gn.py: Could not find checkout in any parent of the current '
'path.\nThis must be run inside a checkout.',
file=sys.stderr)
return 1
# TODO(b/328065301): Once chromium/src CL has landed to migrate
# buildtools/<platform>/gn to buildtools/<platform>/gn/gn, only return
# gn/gn path.
old_gn_path = os.path.join(bin_path, 'gn' + gclient_paths.GetExeSuffix())
new_gn_path = os.path.join(bin_path, 'gn',
'gn' + gclient_paths.GetExeSuffix())
paths = [new_gn_path, old_gn_path]
for path in paths:
if os.path.isfile(path):
return subprocess.call([path] + args[1:])
print('gn.py: Could not find gn executable at: %s' % paths, file=sys.stderr)
gn = FindGnTool()
if gn:
os.execv(gn, [gn] + args[1:])
return 2
if __name__ == '__main__':
try:
sys.exit(main(sys.argv))
except KeyboardInterrupt:
sys.stderr.write('interrupted\n')
sys.exit(1)
sys.exit(main(sys.argv))