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.
15 from gyp.common import GypError
17 # Default debug modes for GYP
20 # List of "official" debug modes, but you can use anything you like.
21 DEBUG_GENERAL = 'general'
22 DEBUG_VARIABLES = 'variables'
23 DEBUG_INCLUDES = 'includes'
26 def DebugOutput(mode, message, *args):
27 if 'all' in gyp.debug or mode in gyp.debug:
28 ctx = ('unknown', 0, 'unknown')
30 f = traceback.extract_stack(limit=2)
37 print '%s:%s:%d:%s %s' % (mode.upper(), os.path.basename(ctx[0]),
38 ctx[1], ctx[2], message)
42 files = os.listdir(os.getcwd())
45 if file.endswith(extension):
46 build_files.append(file)
50 def Load(build_files, format, default_variables={},
51 includes=[], depth='.', params=None, check=False,
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.
64 format, params['flavor'] = format.split('-', 1)
66 default_variables = copy.copy(default_variables)
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
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)
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)
87 generator_name = 'gyp.generator.' + format
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)
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)
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)
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
108 generator_input_info = {
109 'non_configuration_keys':
110 getattr(generator, 'generator_additional_non_configuration_keys', []),
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':
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),
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
132 def NameValueListToDict(name_value_list):
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.
139 for item in name_value_list:
140 tokens = item.split('=', 1)
142 # If we can make it an int, use that, otherwise, use the string.
144 token_value = int(tokens[1])
146 token_value = tokens[1]
147 # Set the variable to the supplied value.
148 result[tokens[0]] = token_value
150 # No value supplied, treat it as a boolean and set it.
151 result[tokens[0]] = True
154 def ShlexEnv(env_name):
155 flags = os.environ.get(env_name, [])
157 flags = shlex.split(flags)
160 def FormatOpt(opt, value):
161 if opt.startswith('--'):
162 return '%s=%s' % (opt, value)
165 def RegenerateAppendFlag(flag, values, predicate, env_name, options):
166 """Regenerate a list of command line flags, for an option of action='append'.
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
176 if options.use_environment and env_name:
177 for flag_value in ShlexEnv(env_name):
178 value = FormatOpt(flag, predicate(flag_value))
183 for flag_value in values:
184 flags.append(FormatOpt(flag, predicate(flag_value)))
187 def 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.)
192 Any path options will be normalized relative to depth.
194 The format flag is not included, as it is assumed the calling generator will
195 set that as appropriate.
198 path = gyp.common.FixIfRelativePath(path, options.depth)
200 return os.path.curdir
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,
218 elif action in ('store', None): # None is a synonym for 'store'.
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)):
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,
232 print >>sys.stderr, ('Warning: regeneration unimplemented for action %r '
233 'flag %r' % (action, opt))
237 class RegeneratableOptionParser(optparse.OptionParser):
239 self.__regeneratable_options = {}
240 optparse.OptionParser.__init__(self)
242 def add_option(self, *args, **kw):
243 """Add an option to the parser.
245 This accepts the same arguments as OptionParser.add_option, plus the
247 regenerate: can be set to False to prevent this option from being included
249 env_name: name of environment variable that additional values for this
251 type: adds type='path', to tell the regenerator that the values of
252 this option need to be made relative to options.depth
254 env_name = kw.pop('env_name', None)
255 if 'dest' in kw and kw.pop('regenerate', True):
258 # The path type is needed for regenerating, for optparse we can just treat
260 type = kw.get('type')
262 kw['type'] = 'string'
264 self.__regeneratable_options[dest] = {
265 'action': kw.get('action'),
267 'env_name': env_name,
271 optparse.OptionParser.add_option(self, *args, **kw)
273 def parse_args(self, *args):
274 values, args = optparse.OptionParser.parse_args(self, *args)
275 values._regeneration_metadata = self.__regeneratable_options
279 my_name = os.path.basename(sys.argv[0])
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 '
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')
338 options, build_files_arg = parser.parse_args(args)
339 build_files = build_files_arg
341 # Set up the configuration directory (defaults to ~/.gyp)
342 if not options.config_dir:
345 if options.use_environment:
346 home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
348 home_dot_gyp = os.path.expanduser(home_dot_gyp)
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)
357 home_dot_gyp = os.path.join(home, '.gyp')
358 if not os.path.exists(home_dot_gyp):
363 home_dot_gyp = os.path.expanduser(options.config_dir)
365 if home_dot_gyp and not os.path.exists(home_dot_gyp):
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', [])
374 generate_formats = re.split('[\s,]', generate_formats)
376 options.formats = generate_formats
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']
384 options.formats = ['make']
386 if not options.generator_output and options.use_environment:
387 g_o = os.environ.get('GYP_GENERATOR_OUTPUT')
389 options.generator_output = g_o
391 options.parallel = not options.no_parallel
393 for mode in options.debug:
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()):
402 if isinstance(value, basestring):
403 DebugOutput(DEBUG_GENERAL, " %s: '%s'", option, value)
405 DebugOutput(DEBUG_GENERAL, " %s: %s", option, value)
408 build_files = FindBuildFiles()
410 raise GypError((usage + '\n\n%s: error: no build_file') %
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)
428 del build_file_dir_components[index]
430 # If the inner loop found something, break without advancing to another
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.')
440 # If toplevel-dir is not set, we assume that depth is the root of our source
442 if not options.toplevel_dir:
443 options.toplevel_dir = options.depth
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 = {}
450 if options.use_environment:
451 defines += ShlexEnv('GYP_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)
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)
470 # Command-line --include files come after the default include.
472 includes.extend(options.includes)
474 # Generator flags should be prefixed with the target generator since they
475 # are global across all generator runs.
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)
485 # Generate all requested formats (use a set in case we got one format request
487 for format in set(options.formats):
488 params = {'options': options,
489 'build_files': build_files,
490 'generator_flags': generator_flags,
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}
498 # Start with the default variables from the command line.
499 [generator, flat_list, targets, data] = Load(
500 build_files, format, cmdline_default_variables, includes, options.depth,
501 params, options.check, options.circular_check)
503 # TODO(mark): Pass |data| for now because the generator needs a list of
504 # build files that came in. In the future, maybe it should just accept
505 # a list, and not the whole data dict.
506 # NOTE: flat_list is the flattened dependency graph specifying the order
507 # that targets may be built. Build systems that operate serially or that
508 # need to have dependencies defined before dependents reference them should
509 # generate targets in the order specified in flat_list.
510 generator.GenerateOutput(flat_list, targets, data, params)
513 valid_configs = targets[flat_list[0]]['configurations'].keys()
514 for conf in options.configs:
515 if conf not in valid_configs:
516 raise GypError('Invalid config specified via --build: %s' % conf)
517 generator.PerformBuild(data, options.configs, params)
525 return gyp_main(args)
527 sys.stderr.write("gyp: %s\n" % e)
530 # NOTE: setuptools generated console_scripts calls function with no arguments
532 return main(sys.argv[1:])
534 if __name__ == '__main__':
535 sys.exit(script_main())