123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- #!/usr/bin/env python3
- # Copyright (c) 2012 Google Inc. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- from __future__ import annotations
- import copy
- import gyp.input
- import argparse
- import os.path
- import re
- import shlex
- import sys
- import traceback
- from gyp.common import GypError
- # Default debug modes for GYP
- debug = {}
- # List of "official" debug modes, but you can use anything you like.
- DEBUG_GENERAL = "general"
- DEBUG_VARIABLES = "variables"
- DEBUG_INCLUDES = "includes"
- def EscapeForCString(string: bytes | str) -> str:
- if isinstance(string, str):
- string = string.encode(encoding='utf8')
- backslash_or_double_quote = {ord('\\'), ord('"')}
- result = ''
- for char in string:
- if char in backslash_or_double_quote or not 32 <= char < 127:
- result += '\\%03o' % char
- else:
- result += chr(char)
- return result
- def DebugOutput(mode, message, *args):
- if "all" in gyp.debug or mode in gyp.debug:
- ctx = ("unknown", 0, "unknown")
- try:
- f = traceback.extract_stack(limit=2)
- if f:
- ctx = f[0][:3]
- except Exception:
- pass
- if args:
- message %= args
- print(
- "%s:%s:%d:%s %s"
- % (mode.upper(), os.path.basename(ctx[0]), ctx[1], ctx[2], message)
- )
- def FindBuildFiles():
- extension = ".gyp"
- files = os.listdir(os.getcwd())
- build_files = []
- for file in files:
- if file.endswith(extension):
- build_files.append(file)
- return build_files
- def Load(
- build_files,
- format,
- default_variables={},
- includes=[],
- depth=".",
- params=None,
- check=False,
- circular_check=True,
- ):
- """
- Loads one or more specified build files.
- default_variables and includes will be copied before use.
- Returns the generator for the specified format and the
- data returned by loading the specified build files.
- """
- if params is None:
- params = {}
- if "-" in format:
- format, params["flavor"] = format.split("-", 1)
- default_variables = copy.copy(default_variables)
- # Default variables provided by this program and its modules should be
- # named WITH_CAPITAL_LETTERS to provide a distinct "best practice" namespace,
- # avoiding collisions with user and automatic variables.
- default_variables["GENERATOR"] = format
- default_variables["GENERATOR_FLAVOR"] = params.get("flavor", "")
- # Format can be a custom python file, or by default the name of a module
- # within gyp.generator.
- if format.endswith(".py"):
- generator_name = os.path.splitext(format)[0]
- path, generator_name = os.path.split(generator_name)
- # Make sure the path to the custom generator is in sys.path
- # Don't worry about removing it once we are done. Keeping the path
- # to each generator that is used in sys.path is likely harmless and
- # arguably a good idea.
- path = os.path.abspath(path)
- if path not in sys.path:
- sys.path.insert(0, path)
- else:
- generator_name = "gyp.generator." + format
- # These parameters are passed in order (as opposed to by key)
- # because ActivePython cannot handle key parameters to __import__.
- generator = __import__(generator_name, globals(), locals(), generator_name)
- for (key, val) in generator.generator_default_variables.items():
- default_variables.setdefault(key, val)
- output_dir = params["options"].generator_output or params["options"].toplevel_dir
- if default_variables["GENERATOR"] == "ninja":
- product_dir_abs = os.path.join(
- output_dir, "out", default_variables.get("build_type", "default")
- )
- else:
- product_dir_abs = os.path.join(
- output_dir, default_variables["CONFIGURATION_NAME"]
- )
- default_variables.setdefault("PRODUCT_DIR_ABS", product_dir_abs)
- default_variables.setdefault(
- "PRODUCT_DIR_ABS_CSTR", EscapeForCString(product_dir_abs)
- )
- # Give the generator the opportunity to set additional variables based on
- # the params it will receive in the output phase.
- if getattr(generator, "CalculateVariables", None):
- generator.CalculateVariables(default_variables, params)
- # Give the generator the opportunity to set generator_input_info based on
- # the params it will receive in the output phase.
- if getattr(generator, "CalculateGeneratorInputInfo", None):
- generator.CalculateGeneratorInputInfo(params)
- # Fetch the generator specific info that gets fed to input, we use getattr
- # so we can default things and the generators only have to provide what
- # they need.
- generator_input_info = {
- "non_configuration_keys": getattr(
- generator, "generator_additional_non_configuration_keys", []
- ),
- "path_sections": getattr(generator, "generator_additional_path_sections", []),
- "extra_sources_for_rules": getattr(
- generator, "generator_extra_sources_for_rules", []
- ),
- "generator_supports_multiple_toolsets": getattr(
- generator, "generator_supports_multiple_toolsets", False
- ),
- "generator_wants_static_library_dependencies_adjusted": getattr(
- generator, "generator_wants_static_library_dependencies_adjusted", True
- ),
- "generator_wants_sorted_dependencies": getattr(
- generator, "generator_wants_sorted_dependencies", False
- ),
- "generator_filelist_paths": getattr(
- generator, "generator_filelist_paths", None
- ),
- }
- # Process the input specific to this generator.
- result = gyp.input.Load(
- build_files,
- default_variables,
- includes[:],
- depth,
- generator_input_info,
- check,
- circular_check,
- params["parallel"],
- params["root_targets"],
- )
- return [generator] + result
- def NameValueListToDict(name_value_list):
- """
- Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
- of the pairs. If a string is simply NAME, then the value in the dictionary
- is set to True. If VALUE can be converted to an integer, it is.
- """
- result = {}
- for item in name_value_list:
- tokens = item.split("=", 1)
- if len(tokens) == 2:
- # If we can make it an int, use that, otherwise, use the string.
- try:
- token_value = int(tokens[1])
- except ValueError:
- token_value = tokens[1]
- # Set the variable to the supplied value.
- result[tokens[0]] = token_value
- else:
- # No value supplied, treat it as a boolean and set it.
- result[tokens[0]] = True
- return result
- def ShlexEnv(env_name):
- flags = os.environ.get(env_name, [])
- if flags:
- flags = shlex.split(flags)
- return flags
- def FormatOpt(opt, value):
- if opt.startswith("--"):
- return f"{opt}={value}"
- return opt + value
- def RegenerateAppendFlag(flag, values, predicate, env_name, options):
- """Regenerate a list of command line flags, for an option of action='append'.
- The |env_name|, if given, is checked in the environment and used to generate
- an initial list of options, then the options that were specified on the
- command line (given in |values|) are appended. This matches the handling of
- environment variables and command line flags where command line flags override
- the environment, while not requiring the environment to be set when the flags
- are used again.
- """
- flags = []
- if options.use_environment and env_name:
- for flag_value in ShlexEnv(env_name):
- value = FormatOpt(flag, predicate(flag_value))
- if value in flags:
- flags.remove(value)
- flags.append(value)
- if values:
- for flag_value in values:
- flags.append(FormatOpt(flag, predicate(flag_value)))
- return flags
- def RegenerateFlags(options):
- """Given a parsed options object, and taking the environment variables into
- account, returns a list of flags that should regenerate an equivalent options
- object (even in the absence of the environment variables.)
- Any path options will be normalized relative to depth.
- The format flag is not included, as it is assumed the calling generator will
- set that as appropriate.
- """
- def FixPath(path):
- path = gyp.common.FixIfRelativePath(path, options.depth)
- if not path:
- return os.path.curdir
- return path
- def Noop(value):
- return value
- # We always want to ignore the environment when regenerating, to avoid
- # duplicate or changed flags in the environment at the time of regeneration.
- flags = ["--ignore-environment"]
- for name, metadata in options._regeneration_metadata.items():
- opt = metadata["opt"]
- value = getattr(options, name)
- value_predicate = (metadata["type"] == "path" and FixPath) or Noop
- action = metadata["action"]
- env_name = metadata["env_name"]
- if action == "append":
- flags.extend(
- RegenerateAppendFlag(opt, value, value_predicate, env_name, options)
- )
- elif action in ("store", None): # None is a synonym for 'store'.
- if value:
- flags.append(FormatOpt(opt, value_predicate(value)))
- elif options.use_environment and env_name and os.environ.get(env_name):
- flags.append(FormatOpt(opt, value_predicate(os.environ.get(env_name))))
- elif action in ("store_true", "store_false"):
- if (action == "store_true" and value) or (
- action == "store_false" and not value
- ):
- flags.append(opt)
- elif options.use_environment and env_name:
- print(
- "Warning: environment regeneration unimplemented "
- "for %s flag %r env_name %r" % (action, opt, env_name),
- file=sys.stderr,
- )
- else:
- print(
- "Warning: regeneration unimplemented for action %r "
- "flag %r" % (action, opt),
- file=sys.stderr,
- )
- return flags
- class RegeneratableOptionParser(argparse.ArgumentParser):
- def __init__(self, usage):
- self.__regeneratable_options = {}
- argparse.ArgumentParser.__init__(self, usage=usage)
- def add_argument(self, *args, **kw):
- """Add an option to the parser.
- This accepts the same arguments as ArgumentParser.add_argument, plus the
- following:
- regenerate: can be set to False to prevent this option from being included
- in regeneration.
- env_name: name of environment variable that additional values for this
- option come from.
- type: adds type='path', to tell the regenerator that the values of
- this option need to be made relative to options.depth
- """
- env_name = kw.pop("env_name", None)
- if "dest" in kw and kw.pop("regenerate", True):
- dest = kw["dest"]
- # The path type is needed for regenerating, for optparse we can just treat
- # it as a string.
- type = kw.get("type")
- if type == "path":
- kw["type"] = str
- self.__regeneratable_options[dest] = {
- "action": kw.get("action"),
- "type": type,
- "env_name": env_name,
- "opt": args[0],
- }
- argparse.ArgumentParser.add_argument(self, *args, **kw)
- def parse_args(self, *args):
- values, args = argparse.ArgumentParser.parse_known_args(self, *args)
- values._regeneration_metadata = self.__regeneratable_options
- return values, args
- def gyp_main(args):
- my_name = os.path.basename(sys.argv[0])
- usage = "usage: %(prog)s [options ...] [build_file ...]"
- parser = RegeneratableOptionParser(usage=usage.replace("%s", "%(prog)s"))
- parser.add_argument(
- "--build",
- dest="configs",
- action="append",
- help="configuration for build after project generation",
- )
- parser.add_argument(
- "--check", dest="check", action="store_true", help="check format of gyp files"
- )
- parser.add_argument(
- "--config-dir",
- dest="config_dir",
- action="store",
- env_name="GYP_CONFIG_DIR",
- default=None,
- help="The location for configuration files like " "include.gypi.",
- )
- parser.add_argument(
- "-d",
- "--debug",
- dest="debug",
- metavar="DEBUGMODE",
- action="append",
- default=[],
- help="turn on a debugging "
- 'mode for debugging GYP. Supported modes are "variables", '
- '"includes" and "general" or "all" for all of them.',
- )
- parser.add_argument(
- "-D",
- dest="defines",
- action="append",
- metavar="VAR=VAL",
- env_name="GYP_DEFINES",
- help="sets variable VAR to value VAL",
- )
- parser.add_argument(
- "--depth",
- dest="depth",
- metavar="PATH",
- type="path",
- help="set DEPTH gyp variable to a relative path to PATH",
- )
- parser.add_argument(
- "-f",
- "--format",
- dest="formats",
- action="append",
- env_name="GYP_GENERATORS",
- regenerate=False,
- help="output formats to generate",
- )
- parser.add_argument(
- "-G",
- dest="generator_flags",
- action="append",
- default=[],
- metavar="FLAG=VAL",
- env_name="GYP_GENERATOR_FLAGS",
- help="sets generator flag FLAG to VAL",
- )
- parser.add_argument(
- "--generator-output",
- dest="generator_output",
- action="store",
- default=None,
- metavar="DIR",
- type="path",
- env_name="GYP_GENERATOR_OUTPUT",
- help="puts generated build files under DIR",
- )
- parser.add_argument(
- "--ignore-environment",
- dest="use_environment",
- action="store_false",
- default=True,
- regenerate=False,
- help="do not read options from environment variables",
- )
- parser.add_argument(
- "-I",
- "--include",
- dest="includes",
- action="append",
- metavar="INCLUDE",
- type="path",
- help="files to include in all loaded .gyp files",
- )
- # --no-circular-check disables the check for circular relationships between
- # .gyp files. These relationships should not exist, but they've only been
- # observed to be harmful with the Xcode generator. Chromium's .gyp files
- # currently have some circular relationships on non-Mac platforms, so this
- # option allows the strict behavior to be used on Macs and the lenient
- # behavior to be used elsewhere.
- # TODO(mark): Remove this option when http://crbug.com/35878 is fixed.
- parser.add_argument(
- "--no-circular-check",
- dest="circular_check",
- action="store_false",
- default=True,
- regenerate=False,
- help="don't check for circular relationships between files",
- )
- parser.add_argument(
- "--no-parallel",
- action="store_true",
- default=False,
- help="Disable multiprocessing",
- )
- parser.add_argument(
- "-S",
- "--suffix",
- dest="suffix",
- default="",
- help="suffix to add to generated files",
- )
- parser.add_argument(
- "--toplevel-dir",
- dest="toplevel_dir",
- action="store",
- default=None,
- metavar="DIR",
- type="path",
- help="directory to use as the root of the source tree",
- )
- parser.add_argument(
- "-R",
- "--root-target",
- dest="root_targets",
- action="append",
- metavar="TARGET",
- help="include only TARGET and its deep dependencies",
- )
- parser.add_argument(
- "-V",
- "--version",
- dest="version",
- action="store_true",
- help="Show the version and exit.",
- )
- options, build_files_arg = parser.parse_args(args)
- if options.version:
- import pkg_resources
- print(f"v{pkg_resources.get_distribution('gyp-next').version}")
- return 0
- build_files = build_files_arg
- # Set up the configuration directory (defaults to ~/.gyp)
- if not options.config_dir:
- home = None
- home_dot_gyp = None
- if options.use_environment:
- home_dot_gyp = os.environ.get("GYP_CONFIG_DIR", None)
- if home_dot_gyp:
- home_dot_gyp = os.path.expanduser(home_dot_gyp)
- if not home_dot_gyp:
- home_vars = ["HOME"]
- if sys.platform in ("cygwin", "win32"):
- home_vars.append("USERPROFILE")
- for home_var in home_vars:
- home = os.getenv(home_var)
- if home:
- home_dot_gyp = os.path.join(home, ".gyp")
- if not os.path.exists(home_dot_gyp):
- home_dot_gyp = None
- else:
- break
- else:
- home_dot_gyp = os.path.expanduser(options.config_dir)
- if home_dot_gyp and not os.path.exists(home_dot_gyp):
- home_dot_gyp = None
- if not options.formats:
- # If no format was given on the command line, then check the env variable.
- generate_formats = []
- if options.use_environment:
- generate_formats = os.environ.get("GYP_GENERATORS", [])
- if generate_formats:
- generate_formats = re.split(r"[\s,]", generate_formats)
- if generate_formats:
- options.formats = generate_formats
- else:
- # Nothing in the variable, default based on platform.
- if sys.platform == "darwin":
- options.formats = ["xcode"]
- elif sys.platform in ("win32", "cygwin"):
- options.formats = ["msvs"]
- else:
- options.formats = ["make"]
- if not options.generator_output and options.use_environment:
- g_o = os.environ.get("GYP_GENERATOR_OUTPUT")
- if g_o:
- options.generator_output = g_o
- options.parallel = not options.no_parallel
- for mode in options.debug:
- gyp.debug[mode] = 1
- # Do an extra check to avoid work when we're not debugging.
- if DEBUG_GENERAL in gyp.debug:
- DebugOutput(DEBUG_GENERAL, "running with these options:")
- for option, value in sorted(options.__dict__.items()):
- if option[0] == "_":
- continue
- if isinstance(value, str):
- DebugOutput(DEBUG_GENERAL, " %s: '%s'", option, value)
- else:
- DebugOutput(DEBUG_GENERAL, " %s: %s", option, value)
- if not build_files:
- build_files = FindBuildFiles()
- if not build_files:
- raise GypError((usage + "\n\n%s: error: no build_file") % (my_name, my_name))
- # TODO(mark): Chromium-specific hack!
- # For Chromium, the gyp "depth" variable should always be a relative path
- # to Chromium's top-level "src" directory. If no depth variable was set
- # on the command line, try to find a "src" directory by looking at the
- # absolute path to each build file's directory. The first "src" component
- # found will be treated as though it were the path used for --depth.
- if not options.depth:
- for build_file in build_files:
- build_file_dir = os.path.abspath(os.path.dirname(build_file))
- build_file_dir_components = build_file_dir.split(os.path.sep)
- components_len = len(build_file_dir_components)
- for index in range(components_len - 1, -1, -1):
- if build_file_dir_components[index] == "src":
- options.depth = os.path.sep.join(build_file_dir_components)
- break
- del build_file_dir_components[index]
- # If the inner loop found something, break without advancing to another
- # build file.
- if options.depth:
- break
- if not options.depth:
- raise GypError(
- "Could not automatically locate src directory. This is"
- "a temporary Chromium feature that will be removed. Use"
- "--depth as a workaround."
- )
- # If toplevel-dir is not set, we assume that depth is the root of our source
- # tree.
- if not options.toplevel_dir:
- options.toplevel_dir = options.depth
- # -D on the command line sets variable defaults - D isn't just for define,
- # it's for default. Perhaps there should be a way to force (-F?) a
- # variable's value so that it can't be overridden by anything else.
- cmdline_default_variables = {}
- defines = []
- if options.use_environment:
- defines += ShlexEnv("GYP_DEFINES")
- if options.defines:
- defines += options.defines
- cmdline_default_variables = NameValueListToDict(defines)
- if DEBUG_GENERAL in gyp.debug:
- DebugOutput(
- DEBUG_GENERAL, "cmdline_default_variables: %s", cmdline_default_variables
- )
- # Set up includes.
- includes = []
- # If ~/.gyp/include.gypi exists, it'll be forcibly included into every
- # .gyp file that's loaded, before anything else is included.
- if home_dot_gyp:
- default_include = os.path.join(home_dot_gyp, "include.gypi")
- if os.path.exists(default_include):
- print("Using overrides found in " + default_include)
- includes.append(default_include)
- # Command-line --include files come after the default include.
- if options.includes:
- includes.extend(options.includes)
- # Generator flags should be prefixed with the target generator since they
- # are global across all generator runs.
- gen_flags = []
- if options.use_environment:
- gen_flags += ShlexEnv("GYP_GENERATOR_FLAGS")
- if options.generator_flags:
- gen_flags += options.generator_flags
- generator_flags = NameValueListToDict(gen_flags)
- if DEBUG_GENERAL in gyp.debug:
- DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags)
- # Generate all requested formats (use a set in case we got one format request
- # twice)
- for format in set(options.formats):
- params = {
- "options": options,
- "build_files": build_files,
- "generator_flags": generator_flags,
- "cwd": os.getcwd(),
- "build_files_arg": build_files_arg,
- "gyp_binary": sys.argv[0],
- "home_dot_gyp": home_dot_gyp,
- "parallel": options.parallel,
- "root_targets": options.root_targets,
- "target_arch": cmdline_default_variables.get("target_arch", ""),
- }
- # Start with the default variables from the command line.
- [generator, flat_list, targets, data] = Load(
- build_files,
- format,
- cmdline_default_variables,
- includes,
- options.depth,
- params,
- options.check,
- options.circular_check,
- )
- # TODO(mark): Pass |data| for now because the generator needs a list of
- # build files that came in. In the future, maybe it should just accept
- # a list, and not the whole data dict.
- # NOTE: flat_list is the flattened dependency graph specifying the order
- # that targets may be built. Build systems that operate serially or that
- # need to have dependencies defined before dependents reference them should
- # generate targets in the order specified in flat_list.
- generator.GenerateOutput(flat_list, targets, data, params)
- if options.configs:
- valid_configs = targets[flat_list[0]]["configurations"]
- for conf in options.configs:
- if conf not in valid_configs:
- raise GypError("Invalid config specified via --build: %s" % conf)
- generator.PerformBuild(data, options.configs, params)
- # Done
- return 0
- def main(args):
- try:
- return gyp_main(args)
- except GypError as e:
- sys.stderr.write("gyp: %s\n" % e)
- return 1
- # NOTE: setuptools generated console_scripts calls function with no arguments
- def script_main():
- return main(sys.argv[1:])
- if __name__ == "__main__":
- sys.exit(script_main())
|