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