1#!/usr/local/bin/python3.8
2
3# Copyright (c) 2012 Google Inc. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7from __future__ import print_function
8
9import copy
10import gyp.input
11import argparse
12import os.path
13import re
14import shlex
15import sys
16import traceback
17from gyp.common import GypError
18
19try:
20    # Python 2
21    string_types = basestring
22except NameError:
23    # Python 3
24    string_types = str
25
26# Default debug modes for GYP
27debug = {}
28
29# List of "official" debug modes, but you can use anything you like.
30DEBUG_GENERAL = 'general'
31DEBUG_VARIABLES = 'variables'
32DEBUG_INCLUDES = 'includes'
33
34
35def DebugOutput(mode, message, *args):
36  if 'all' in gyp.debug or mode in gyp.debug:
37    ctx = ('unknown', 0, 'unknown')
38    try:
39      f = traceback.extract_stack(limit=2)
40      if f:
41        ctx = f[0][:3]
42    except:
43      pass
44    if args:
45      message %= args
46    print('%s:%s:%d:%s %s' % (mode.upper(), os.path.basename(ctx[0]),
47                              ctx[1], ctx[2], message))
48
49def FindBuildFiles():
50  extension = '.gyp'
51  files = os.listdir(os.getcwd())
52  build_files = []
53  for file in files:
54    if file.endswith(extension):
55      build_files.append(file)
56  return build_files
57
58
59def Load(build_files, format, default_variables={},
60         includes=[], depth='.', params=None, check=False,
61         circular_check=True, duplicate_basename_check=True):
62  """
63  Loads one or more specified build files.
64  default_variables and includes will be copied before use.
65  Returns the generator for the specified format and the
66  data returned by loading the specified build files.
67  """
68  if params is None:
69    params = {}
70
71  if '-' in format:
72    format, params['flavor'] = format.split('-', 1)
73
74  default_variables = copy.copy(default_variables)
75
76  # Default variables provided by this program and its modules should be
77  # named WITH_CAPITAL_LETTERS to provide a distinct "best practice" namespace,
78  # avoiding collisions with user and automatic variables.
79  default_variables['GENERATOR'] = format
80  default_variables['GENERATOR_FLAVOR'] = params.get('flavor', '')
81
82  # Format can be a custom python file, or by default the name of a module
83  # within gyp.generator.
84  if format.endswith('.py'):
85    generator_name = os.path.splitext(format)[0]
86    path, generator_name = os.path.split(generator_name)
87
88    # Make sure the path to the custom generator is in sys.path
89    # Don't worry about removing it once we are done.  Keeping the path
90    # to each generator that is used in sys.path is likely harmless and
91    # arguably a good idea.
92    path = os.path.abspath(path)
93    if path not in sys.path:
94      sys.path.insert(0, path)
95  else:
96    generator_name = 'gyp.generator.' + format
97
98  # These parameters are passed in order (as opposed to by key)
99  # because ActivePython cannot handle key parameters to __import__.
100  generator = __import__(generator_name, globals(), locals(), generator_name)
101  for (key, val) in generator.generator_default_variables.items():
102    default_variables.setdefault(key, val)
103
104  # Give the generator the opportunity to set additional variables based on
105  # the params it will receive in the output phase.
106  if getattr(generator, 'CalculateVariables', None):
107    generator.CalculateVariables(default_variables, params)
108
109  # Give the generator the opportunity to set generator_input_info based on
110  # the params it will receive in the output phase.
111  if getattr(generator, 'CalculateGeneratorInputInfo', None):
112    generator.CalculateGeneratorInputInfo(params)
113
114  # Fetch the generator specific info that gets fed to input, we use getattr
115  # so we can default things and the generators only have to provide what
116  # they need.
117  generator_input_info = {
118    'non_configuration_keys':
119        getattr(generator, 'generator_additional_non_configuration_keys', []),
120    'path_sections':
121        getattr(generator, 'generator_additional_path_sections', []),
122    'extra_sources_for_rules':
123        getattr(generator, 'generator_extra_sources_for_rules', []),
124    'generator_supports_multiple_toolsets':
125        getattr(generator, 'generator_supports_multiple_toolsets', False),
126    'generator_wants_static_library_dependencies_adjusted':
127        getattr(generator,
128                'generator_wants_static_library_dependencies_adjusted', True),
129    'generator_wants_sorted_dependencies':
130        getattr(generator, 'generator_wants_sorted_dependencies', False),
131    'generator_filelist_paths':
132        getattr(generator, 'generator_filelist_paths', None),
133  }
134
135  # Process the input specific to this generator.
136  result = gyp.input.Load(build_files, default_variables, includes[:],
137                          depth, generator_input_info, check, circular_check,
138                          duplicate_basename_check,
139                          params['parallel'], params['root_targets'])
140  return [generator] + result
141
142def NameValueListToDict(name_value_list):
143  """
144  Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
145  of the pairs.  If a string is simply NAME, then the value in the dictionary
146  is set to True.  If VALUE can be converted to an integer, it is.
147  """
148  result = { }
149  for item in name_value_list:
150    tokens = item.split('=', 1)
151    if len(tokens) == 2:
152      # If we can make it an int, use that, otherwise, use the string.
153      try:
154        token_value = int(tokens[1])
155      except ValueError:
156        token_value = tokens[1]
157      # Set the variable to the supplied value.
158      result[tokens[0]] = token_value
159    else:
160      # No value supplied, treat it as a boolean and set it.
161      result[tokens[0]] = True
162  return result
163
164def ShlexEnv(env_name):
165  flags = os.environ.get(env_name, [])
166  if flags:
167    flags = shlex.split(flags)
168  return flags
169
170def FormatOpt(opt, value):
171  if opt.startswith('--'):
172    return '%s=%s' % (opt, value)
173  return opt + value
174
175def RegenerateAppendFlag(flag, values, predicate, env_name, options):
176  """Regenerate a list of command line flags, for an option of action='append'.
177
178  The |env_name|, if given, is checked in the environment and used to generate
179  an initial list of options, then the options that were specified on the
180  command line (given in |values|) are appended.  This matches the handling of
181  environment variables and command line flags where command line flags override
182  the environment, while not requiring the environment to be set when the flags
183  are used again.
184  """
185  flags = []
186  if options.use_environment and env_name:
187    for flag_value in ShlexEnv(env_name):
188      value = FormatOpt(flag, predicate(flag_value))
189      if value in flags:
190        flags.remove(value)
191      flags.append(value)
192  if values:
193    for flag_value in values:
194      flags.append(FormatOpt(flag, predicate(flag_value)))
195  return flags
196
197def RegenerateFlags(options):
198  """Given a parsed options object, and taking the environment variables into
199  account, returns a list of flags that should regenerate an equivalent options
200  object (even in the absence of the environment variables.)
201
202  Any path options will be normalized relative to depth.
203
204  The format flag is not included, as it is assumed the calling generator will
205  set that as appropriate.
206  """
207  def FixPath(path):
208    path = gyp.common.FixIfRelativePath(path, options.depth)
209    if not path:
210      return os.path.curdir
211    return path
212
213  def Noop(value):
214    return value
215
216  # We always want to ignore the environment when regenerating, to avoid
217  # duplicate or changed flags in the environment at the time of regeneration.
218  flags = ['--ignore-environment']
219  for name, metadata in options._regeneration_metadata.items():
220    opt = metadata['opt']
221    value = getattr(options, name)
222    value_predicate = metadata['type'] == 'path' and FixPath or Noop
223    action = metadata['action']
224    env_name = metadata['env_name']
225    if action == 'append':
226      flags.extend(RegenerateAppendFlag(opt, value, value_predicate,
227                                        env_name, options))
228    elif action in ('store', None):  # None is a synonym for 'store'.
229      if value:
230        flags.append(FormatOpt(opt, value_predicate(value)))
231      elif options.use_environment and env_name and os.environ.get(env_name):
232        flags.append(FormatOpt(opt, value_predicate(os.environ.get(env_name))))
233    elif action in ('store_true', 'store_false'):
234      if ((action == 'store_true' and value) or
235          (action == 'store_false' and not value)):
236        flags.append(opt)
237      elif options.use_environment and env_name:
238        print('Warning: environment regeneration unimplemented '
239                             'for %s flag %r env_name %r' % (action, opt,
240                                                             env_name), file=sys.stderr)
241    else:
242      print('Warning: regeneration unimplemented for action %r '
243                           'flag %r' % (action, opt), file=sys.stderr)
244
245  return flags
246
247class RegeneratableOptionParser(argparse.ArgumentParser):
248  def __init__(self, usage):
249    self.__regeneratable_options = {}
250    argparse.ArgumentParser.__init__(self, usage=usage)
251
252  def add_argument(self, *args, **kw):
253    """Add an option to the parser.
254
255    This accepts the same arguments as ArgumentParser.add_argument, plus the
256    following:
257      regenerate: can be set to False to prevent this option from being included
258                  in regeneration.
259      env_name: name of environment variable that additional values for this
260                option come from.
261      type: adds type='path', to tell the regenerator that the values of
262            this option need to be made relative to options.depth
263    """
264    env_name = kw.pop('env_name', None)
265    if 'dest' in kw and kw.pop('regenerate', True):
266      dest = kw['dest']
267
268      # The path type is needed for regenerating, for optparse we can just treat
269      # it as a string.
270      type = kw.get('type')
271      if type == 'path':
272        kw['type'] = str
273
274      self.__regeneratable_options[dest] = {
275          'action': kw.get('action'),
276          'type': type,
277          'env_name': env_name,
278          'opt': args[0],
279        }
280
281    argparse.ArgumentParser.add_argument(self, *args, **kw)
282
283  def parse_args(self, *args):
284    values, args = argparse.ArgumentParser.parse_known_args(self, *args)
285    values._regeneration_metadata = self.__regeneratable_options
286    return values, args
287
288def gyp_main(args):
289  my_name = os.path.basename(sys.argv[0])
290  usage = 'usage: %(prog)s [options ...] [build_file ...]'
291
292
293  parser = RegeneratableOptionParser(usage=usage.replace('%s', '%(prog)s'))
294  parser.add_argument('--build', dest='configs', action='append',
295                    help='configuration for build after project generation')
296  parser.add_argument('--check', dest='check', action='store_true',
297                    help='check format of gyp files')
298  parser.add_argument('--config-dir', dest='config_dir', action='store',
299                    env_name='GYP_CONFIG_DIR', default=None,
300                    help='The location for configuration files like '
301                    'include.gypi.')
302  parser.add_argument('-d', '--debug', dest='debug', metavar='DEBUGMODE',
303                    action='append', default=[], help='turn on a debugging '
304                    'mode for debugging GYP.  Supported modes are "variables", '
305                    '"includes" and "general" or "all" for all of them.')
306  parser.add_argument('-D', dest='defines', action='append', metavar='VAR=VAL',
307                    env_name='GYP_DEFINES',
308                    help='sets variable VAR to value VAL')
309  parser.add_argument('--depth', dest='depth', metavar='PATH', type='path',
310                    help='set DEPTH gyp variable to a relative path to PATH')
311  parser.add_argument('-f', '--format', dest='formats', action='append',
312                    env_name='GYP_GENERATORS', regenerate=False,
313                    help='output formats to generate')
314  parser.add_argument('-G', dest='generator_flags', action='append', default=[],
315                    metavar='FLAG=VAL', env_name='GYP_GENERATOR_FLAGS',
316                    help='sets generator flag FLAG to VAL')
317  parser.add_argument('--generator-output', dest='generator_output',
318                    action='store', default=None, metavar='DIR', type='path',
319                    env_name='GYP_GENERATOR_OUTPUT',
320                    help='puts generated build files under DIR')
321  parser.add_argument('--ignore-environment', dest='use_environment',
322                    action='store_false', default=True, regenerate=False,
323                    help='do not read options from environment variables')
324  parser.add_argument('-I', '--include', dest='includes', action='append',
325                    metavar='INCLUDE', type='path',
326                    help='files to include in all loaded .gyp files')
327  # --no-circular-check disables the check for circular relationships between
328  # .gyp files.  These relationships should not exist, but they've only been
329  # observed to be harmful with the Xcode generator.  Chromium's .gyp files
330  # currently have some circular relationships on non-Mac platforms, so this
331  # option allows the strict behavior to be used on Macs and the lenient
332  # behavior to be used elsewhere.
333  # TODO(mark): Remove this option when http://crbug.com/35878 is fixed.
334  parser.add_argument('--no-circular-check', dest='circular_check',
335                    action='store_false', default=True, regenerate=False,
336                    help="don't check for circular relationships between files")
337  # --no-duplicate-basename-check disables the check for duplicate basenames
338  # in a static_library/shared_library project. Visual C++ 2008 generator
339  # doesn't support this configuration. Libtool on Mac also generates warnings
340  # when duplicate basenames are passed into Make generator on Mac.
341  # TODO(yukawa): Remove this option when these legacy generators are
342  # deprecated.
343  parser.add_argument('--no-duplicate-basename-check',
344                    dest='duplicate_basename_check', action='store_false',
345                    default=True, regenerate=False,
346                    help="don't check for duplicate basenames")
347  parser.add_argument('--no-parallel', action='store_true', default=False,
348                    help='Disable multiprocessing')
349  parser.add_argument('-S', '--suffix', dest='suffix', default='',
350                    help='suffix to add to generated files')
351  parser.add_argument('--toplevel-dir', dest='toplevel_dir', action='store',
352                    default=None, metavar='DIR', type='path',
353                    help='directory to use as the root of the source tree')
354  parser.add_argument('-R', '--root-target', dest='root_targets',
355                    action='append', metavar='TARGET',
356                    help='include only TARGET and its deep dependencies')
357
358  options, build_files_arg = parser.parse_args(args)
359  build_files = build_files_arg
360
361  # Set up the configuration directory (defaults to ~/.gyp)
362  if not options.config_dir:
363    home = None
364    home_dot_gyp = None
365    if options.use_environment:
366      home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
367      if home_dot_gyp:
368        home_dot_gyp = os.path.expanduser(home_dot_gyp)
369
370    if not home_dot_gyp:
371      home_vars = ['HOME']
372      if sys.platform in ('cygwin', 'win32'):
373        home_vars.append('USERPROFILE')
374      for home_var in home_vars:
375        home = os.getenv(home_var)
376        if home != None:
377          home_dot_gyp = os.path.join(home, '.gyp')
378          if not os.path.exists(home_dot_gyp):
379            home_dot_gyp = None
380          else:
381            break
382  else:
383    home_dot_gyp = os.path.expanduser(options.config_dir)
384
385  if home_dot_gyp and not os.path.exists(home_dot_gyp):
386    home_dot_gyp = None
387
388  if not options.formats:
389    # If no format was given on the command line, then check the env variable.
390    generate_formats = []
391    if options.use_environment:
392      generate_formats = os.environ.get('GYP_GENERATORS', [])
393    if generate_formats:
394      generate_formats = re.split(r'[\s,]', generate_formats)
395    if generate_formats:
396      options.formats = generate_formats
397    else:
398      # Nothing in the variable, default based on platform.
399      if sys.platform == 'darwin':
400        options.formats = ['xcode']
401      elif sys.platform in ('win32', 'cygwin'):
402        options.formats = ['msvs']
403      else:
404        options.formats = ['make']
405
406  if not options.generator_output and options.use_environment:
407    g_o = os.environ.get('GYP_GENERATOR_OUTPUT')
408    if g_o:
409      options.generator_output = g_o
410
411  options.parallel = not options.no_parallel
412
413  for mode in options.debug:
414    gyp.debug[mode] = 1
415
416  # Do an extra check to avoid work when we're not debugging.
417  if DEBUG_GENERAL in gyp.debug:
418    DebugOutput(DEBUG_GENERAL, 'running with these options:')
419    for option, value in sorted(options.__dict__.items()):
420      if option[0] == '_':
421        continue
422      if isinstance(value, string_types):
423        DebugOutput(DEBUG_GENERAL, "  %s: '%s'", option, value)
424      else:
425        DebugOutput(DEBUG_GENERAL, "  %s: %s", option, value)
426
427  if not build_files:
428    build_files = FindBuildFiles()
429  if not build_files:
430    raise GypError((usage + '\n\n%s: error: no build_file') %
431                   (my_name, my_name))
432
433  # TODO(mark): Chromium-specific hack!
434  # For Chromium, the gyp "depth" variable should always be a relative path
435  # to Chromium's top-level "src" directory.  If no depth variable was set
436  # on the command line, try to find a "src" directory by looking at the
437  # absolute path to each build file's directory.  The first "src" component
438  # found will be treated as though it were the path used for --depth.
439  if not options.depth:
440    for build_file in build_files:
441      build_file_dir = os.path.abspath(os.path.dirname(build_file))
442      build_file_dir_components = build_file_dir.split(os.path.sep)
443      components_len = len(build_file_dir_components)
444      for index in range(components_len - 1, -1, -1):
445        if build_file_dir_components[index] == 'src':
446          options.depth = os.path.sep.join(build_file_dir_components)
447          break
448        del build_file_dir_components[index]
449
450      # If the inner loop found something, break without advancing to another
451      # build file.
452      if options.depth:
453        break
454
455    if not options.depth:
456      raise GypError('Could not automatically locate src directory.  This is'
457                     'a temporary Chromium feature that will be removed.  Use'
458                     '--depth as a workaround.')
459
460  # If toplevel-dir is not set, we assume that depth is the root of our source
461  # tree.
462  if not options.toplevel_dir:
463    options.toplevel_dir = options.depth
464
465  # -D on the command line sets variable defaults - D isn't just for define,
466  # it's for default.  Perhaps there should be a way to force (-F?) a
467  # variable's value so that it can't be overridden by anything else.
468  cmdline_default_variables = {}
469  defines = []
470  if options.use_environment:
471    defines += ShlexEnv('GYP_DEFINES')
472  if options.defines:
473    defines += options.defines
474  cmdline_default_variables = NameValueListToDict(defines)
475  if DEBUG_GENERAL in gyp.debug:
476    DebugOutput(DEBUG_GENERAL,
477                "cmdline_default_variables: %s", cmdline_default_variables)
478
479  # Set up includes.
480  includes = []
481
482  # If ~/.gyp/include.gypi exists, it'll be forcibly included into every
483  # .gyp file that's loaded, before anything else is included.
484  if home_dot_gyp != None:
485    default_include = os.path.join(home_dot_gyp, 'include.gypi')
486    if os.path.exists(default_include):
487      print('Using overrides found in ' + default_include)
488      includes.append(default_include)
489
490  # Command-line --include files come after the default include.
491  if options.includes:
492    includes.extend(options.includes)
493
494  # Generator flags should be prefixed with the target generator since they
495  # are global across all generator runs.
496  gen_flags = []
497  if options.use_environment:
498    gen_flags += ShlexEnv('GYP_GENERATOR_FLAGS')
499  if options.generator_flags:
500    gen_flags += options.generator_flags
501  generator_flags = NameValueListToDict(gen_flags)
502  if DEBUG_GENERAL in gyp.debug.keys():
503    DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags)
504
505  # Generate all requested formats (use a set in case we got one format request
506  # twice)
507  for format in set(options.formats):
508    params = {'options': options,
509              'build_files': build_files,
510              'generator_flags': generator_flags,
511              'cwd': os.getcwd(),
512              'build_files_arg': build_files_arg,
513              'gyp_binary': sys.argv[0],
514              'home_dot_gyp': home_dot_gyp,
515              'parallel': options.parallel,
516              'root_targets': options.root_targets,
517              'target_arch': cmdline_default_variables.get('target_arch', '')}
518
519    # Start with the default variables from the command line.
520    [generator, flat_list, targets, data] = Load(
521        build_files, format, cmdline_default_variables, includes, options.depth,
522        params, options.check, options.circular_check,
523        options.duplicate_basename_check)
524
525    # TODO(mark): Pass |data| for now because the generator needs a list of
526    # build files that came in.  In the future, maybe it should just accept
527    # a list, and not the whole data dict.
528    # NOTE: flat_list is the flattened dependency graph specifying the order
529    # that targets may be built.  Build systems that operate serially or that
530    # need to have dependencies defined before dependents reference them should
531    # generate targets in the order specified in flat_list.
532    generator.GenerateOutput(flat_list, targets, data, params)
533
534    if options.configs:
535      valid_configs = targets[flat_list[0]]['configurations'].keys()
536      for conf in options.configs:
537        if conf not in valid_configs:
538          raise GypError('Invalid config specified via --build: %s' % conf)
539      generator.PerformBuild(data, options.configs, params)
540
541  # Done
542  return 0
543
544
545def main(args):
546  try:
547    return gyp_main(args)
548  except GypError as e:
549    sys.stderr.write("gyp: %s\n" % e)
550    return 1
551
552# NOTE: setuptools generated console_scripts calls function with no arguments
553def script_main():
554  return main(sys.argv[1:])
555
556if __name__ == '__main__':
557  sys.exit(script_main())
558