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