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