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