build/gen.py: Fix regen command arguments generation.
The 'regen' rule embedded in the build.ninja generated by the
script was missing critical arguments like --use-lto, which caused
incremental rebuilds to alter the build configuration, leading
to surprises (especially when measuring performance!).
This fixes the issue by ensuring that any command-line argument
is properly re-created in the regen's command, with proper
shell quoting.
+ Upgrade from optparse to argparse Python module.
+ Remove obsolete imports
Bug: None
Change-Id: I8e3a995c0a9c6c46ceeb7878cd1db04674ef02cd
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12640
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: David Turner <digit@google.com>
diff --git a/build/gen.py b/build/gen.py
index 772d090..ae9c40d 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -5,15 +5,16 @@
"""Generates build.ninja that will build GN."""
-import contextlib
-import errno
-import optparse
+import argparse
import os
import platform
import re
+import shlex
import subprocess
import sys
-import tempfile
+
+# IMPORTANT: This script is also executed as python2 on
+# GN's CI builders.
try: # py3
from shlex import quote as shell_quote
@@ -97,33 +98,82 @@
def is_zos(self):
return self._platform == 'zos'
+class ArgumentsList:
+ """Helper class to accumulate ArgumentParser argument definitions
+ and be able to regenerate a corresponding command-line to be
+ written in the generated Ninja file for the 'regen' rule.
+ """
+ def __init__(self):
+ self._arguments = []
+
+ def add(self, *args, **kwargs):
+ """Add an argument definition, use as argparse.ArgumentParser.add_argument()."""
+ self._arguments.append((args, kwargs))
+
+ def add_to_parser(self, parser):
+ """Add all known arguments to parser."""
+ for args, kwargs in self._arguments:
+ parser.add_argument(*args, **kwargs)
+
+ def gen_command_line_args(self, parser_arguments):
+ """Generate a gen.py argument list to be embedded in a Ninja file."""
+ result = []
+ for args, kwargs in self._arguments:
+ if len(args) == 2:
+ long_option = args[1]
+ else:
+ long_option = args[0]
+ dest = kwargs.get('dest', None)
+ if dest is None:
+ assert long_option.startswith('--')
+ dest = long_option[2:].replace('-', '_')
+
+ if getattr(parser_arguments, dest, None) is None:
+ # This was not set on the command-line so skip it.
+ continue
+
+ action = kwargs.get('action', None)
+ if action == 'store_true':
+ if getattr(parser_arguments, dest):
+ result.append(long_option)
+ elif action == 'store' or action is None:
+ result.append('%s=%s' % (long_option, getattr(parser_arguments, dest)))
+ elif action == 'append':
+ for item in getattr(parser_arguments, dest):
+ result.append('%s=%s' % (long_option, item))
+ else:
+ assert action is None, "Unsupported action " + action
+ return ' '.join(shell_quote(item) for item in result)
+
def main(argv):
- parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
- parser.add_option('-d', '--debug', action='store_true',
+ parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
+ args_list = ArgumentsList()
+
+ args_list.add('-d', '--debug', action='store_true',
help='Do a debug build. Defaults to release build.')
- parser.add_option('--platform',
+ args_list.add('--platform',
help='target platform (' +
'/'.join(Platform.known_platforms()) + ')',
choices=Platform.known_platforms())
- parser.add_option('--host',
+ args_list.add('--host',
help='host platform (' +
'/'.join(Platform.known_platforms()) + ')',
choices=Platform.known_platforms())
- parser.add_option('--use-lto', action='store_true',
+ args_list.add('--use-lto', action='store_true',
help='Enable the use of LTO')
- parser.add_option('--use-icf', action='store_true',
+ args_list.add('--use-icf', action='store_true',
help='Enable the use of Identical Code Folding')
- parser.add_option('--no-last-commit-position', action='store_true',
+ args_list.add('--no-last-commit-position', action='store_true',
help='Do not generate last_commit_position.h.')
- parser.add_option('--out-path',
+ args_list.add('--out-path',
help='The path to generate the build files in.')
- parser.add_option('--no-strip', action='store_true',
+ args_list.add('--no-strip', action='store_true',
help='Don\'t strip release build. Useful for profiling.')
- parser.add_option('--no-static-libstdc++', action='store_true',
+ args_list.add('--no-static-libstdc++', action='store_true',
default=False, dest='no_static_libstdcpp',
help='Don\'t link libstdc++ statically')
- parser.add_option('--link-lib',
+ args_list.add('--link-lib',
action='append',
metavar='LINK_LIB',
default=[],
@@ -134,7 +184,7 @@
'used multiple times. Useful to link custom malloc ' +
'or cpu profiling libraries.'))
if sys.platform == 'zos':
- parser.add_option('--zoslib-dir',
+ args_list.add('--zoslib-dir',
action='store',
default='../third_party/zoslib',
dest='zoslib_dir',
@@ -143,10 +193,8 @@
'add -I<ZOSLIB_DIR>/install/include to the compile ' +
'commands. See README.md for details.'))
- options, args = parser.parse_args(argv)
-
- if args:
- parser.error('Unrecognized command line arguments: %s.' % ', '.join(args))
+ args_list.add_to_parser(parser)
+ options = parser.parse_args(argv)
platform = Platform(options.platform)
if options.host:
@@ -160,7 +208,7 @@
if not options.no_last_commit_position:
GenerateLastCommitPosition(host,
os.path.join(out_dir, 'last_commit_position.h'))
- WriteGNNinja(os.path.join(out_dir, 'build.ninja'), platform, host, options)
+ WriteGNNinja(os.path.join(out_dir, 'build.ninja'), platform, host, options, args_list)
return 0
@@ -198,11 +246,11 @@
def WriteGenericNinja(path, static_libraries, executables,
cxx, ar, ld, platform, host, options,
- cflags=[], ldflags=[], libflags=[],
- include_dirs=[], solibs=[]):
- args = ' -d' if options.debug else ''
- for link_lib in options.link_libs:
- args += ' --link-lib=' + shell_quote(link_lib)
+ args_list, cflags=[], ldflags=[],
+ libflags=[], include_dirs=[], solibs=[]):
+ args = args_list.gen_command_line_args(options)
+ if args:
+ args = " " + args
ninja_header_lines = [
'cxx = ' + cxx,
@@ -308,7 +356,7 @@
os.path.relpath(template_filename, os.path.dirname(path)) + '\n')
-def WriteGNNinja(path, platform, host, options):
+def WriteGNNinja(path, platform, host, options, args_list):
if platform.is_msvc():
cxx = os.environ.get('CXX', 'cl.exe')
ld = os.environ.get('LD', 'link.exe')
@@ -830,9 +878,8 @@
executables['gn_unittests']['libs'].extend(static_libraries.keys())
WriteGenericNinja(path, static_libraries, executables, cxx, ar, ld,
- platform, host, options, cflags, ldflags,
- libflags, include_dirs, libs)
-
+ platform, host, options, args_list,
+ cflags, ldflags, libflags, include_dirs, libs)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))