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 'generator_wants_absolute_build_file_paths':
110 getattr(generator, 'generator_wants_absolute_build_file_paths', False),
111 'generator_handles_variants':
112 getattr(generator, 'generator_handles_variants', False),
113 'non_configuration_keys':
114 getattr(generator, 'generator_additional_non_configuration_keys', []),
116 getattr(generator, 'generator_additional_path_sections', []),
117 'extra_sources_for_rules':
118 getattr(generator, 'generator_extra_sources_for_rules', []),
119 'generator_supports_multiple_toolsets':
120 getattr(generator, 'generator_supports_multiple_toolsets', False),
121 'generator_wants_static_library_dependencies_adjusted':
123 'generator_wants_static_library_dependencies_adjusted', True),
124 'generator_wants_sorted_dependencies':
125 getattr(generator, 'generator_wants_sorted_dependencies', False),
128 # Process the input specific to this generator.
129 result = gyp.input.Load(build_files, default_variables, includes[:],
130 depth, generator_input_info, check, circular_check,
132 return [generator] + result
134 def NameValueListToDict(name_value_list):
136 Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
137 of the pairs. If a string is simply NAME, then the value in the dictionary
138 is set to True. If VALUE can be converted to an integer, it is.
141 for item in name_value_list:
142 tokens = item.split('=', 1)
144 # If we can make it an int, use that, otherwise, use the string.
146 token_value = int(tokens[1])
148 token_value = tokens[1]
149 # Set the variable to the supplied value.
150 result[tokens[0]] = token_value
152 # No value supplied, treat it as a boolean and set it.
153 result[tokens[0]] = True
156 def ShlexEnv(env_name):
157 flags = os.environ.get(env_name, [])
159 flags = shlex.split(flags)
162 def FormatOpt(opt, value):
163 if opt.startswith('--'):
164 return '%s=%s' % (opt, value)
167 def RegenerateAppendFlag(flag, values, predicate, env_name, options):
168 """Regenerate a list of command line flags, for an option of action='append'.
170 The |env_name|, if given, is checked in the environment and used to generate
171 an initial list of options, then the options that were specified on the
172 command line (given in |values|) are appended. This matches the handling of
173 environment variables and command line flags where command line flags override
174 the environment, while not requiring the environment to be set when the flags
178 if options.use_environment and env_name:
179 for flag_value in ShlexEnv(env_name):
180 value = FormatOpt(flag, predicate(flag_value))
185 for flag_value in values:
186 flags.append(FormatOpt(flag, predicate(flag_value)))
189 def RegenerateFlags(options):
190 """Given a parsed options object, and taking the environment variables into
191 account, returns a list of flags that should regenerate an equivalent options
192 object (even in the absence of the environment variables.)
194 Any path options will be normalized relative to depth.
196 The format flag is not included, as it is assumed the calling generator will
197 set that as appropriate.
200 path = gyp.common.FixIfRelativePath(path, options.depth)
202 return os.path.curdir
208 # We always want to ignore the environment when regenerating, to avoid
209 # duplicate or changed flags in the environment at the time of regeneration.
210 flags = ['--ignore-environment']
211 for name, metadata in options._regeneration_metadata.iteritems():
212 opt = metadata['opt']
213 value = getattr(options, name)
214 value_predicate = metadata['type'] == 'path' and FixPath or Noop
215 action = metadata['action']
216 env_name = metadata['env_name']
217 if action == 'append':
218 flags.extend(RegenerateAppendFlag(opt, value, value_predicate,
220 elif action in ('store', None): # None is a synonym for 'store'.
222 flags.append(FormatOpt(opt, value_predicate(value)))
223 elif options.use_environment and env_name and os.environ.get(env_name):
224 flags.append(FormatOpt(opt, value_predicate(os.environ.get(env_name))))
225 elif action in ('store_true', 'store_false'):
226 if ((action == 'store_true' and value) or
227 (action == 'store_false' and not value)):
229 elif options.use_environment and env_name:
230 print >>sys.stderr, ('Warning: environment regeneration unimplemented '
231 'for %s flag %r env_name %r' % (action, opt,
234 print >>sys.stderr, ('Warning: regeneration unimplemented for action %r '
235 'flag %r' % (action, opt))
239 class RegeneratableOptionParser(optparse.OptionParser):
241 self.__regeneratable_options = {}
242 optparse.OptionParser.__init__(self)
244 def add_option(self, *args, **kw):
245 """Add an option to the parser.
247 This accepts the same arguments as OptionParser.add_option, plus the
249 regenerate: can be set to False to prevent this option from being included
251 env_name: name of environment variable that additional values for this
253 type: adds type='path', to tell the regenerator that the values of
254 this option need to be made relative to options.depth
256 env_name = kw.pop('env_name', None)
257 if 'dest' in kw and kw.pop('regenerate', True):
260 # The path type is needed for regenerating, for optparse we can just treat
262 type = kw.get('type')
264 kw['type'] = 'string'
266 self.__regeneratable_options[dest] = {
267 'action': kw.get('action'),
269 'env_name': env_name,
273 optparse.OptionParser.add_option(self, *args, **kw)
275 def parse_args(self, *args):
276 values, args = optparse.OptionParser.parse_args(self, *args)
277 values._regeneration_metadata = self.__regeneratable_options
281 my_name = os.path.basename(sys.argv[0])
283 parser = RegeneratableOptionParser()
284 usage = 'usage: %s [options ...] [build_file ...]'
285 parser.set_usage(usage.replace('%s', '%prog'))
286 parser.add_option('-D', dest='defines', action='append', metavar='VAR=VAL',
287 env_name='GYP_DEFINES',
288 help='sets variable VAR to value VAL')
289 parser.add_option('-f', '--format', dest='formats', action='append',
290 env_name='GYP_GENERATORS', regenerate=False,
291 help='output formats to generate')
292 parser.add_option('--msvs-version', dest='msvs_version',
294 help='Deprecated; use -G msvs_version=MSVS_VERSION instead')
295 parser.add_option('-I', '--include', dest='includes', action='append',
296 metavar='INCLUDE', type='path',
297 help='files to include in all loaded .gyp files')
298 parser.add_option('--depth', dest='depth', metavar='PATH', type='path',
299 help='set DEPTH gyp variable to a relative path to PATH')
300 parser.add_option('-d', '--debug', dest='debug', metavar='DEBUGMODE',
301 action='append', default=[], help='turn on a debugging '
302 'mode for debugging GYP. Supported modes are "variables", '
303 '"includes" and "general" or "all" for all of them.')
304 parser.add_option('-S', '--suffix', dest='suffix', default='',
305 help='suffix to add to generated files')
306 parser.add_option('-G', dest='generator_flags', action='append', default=[],
307 metavar='FLAG=VAL', env_name='GYP_GENERATOR_FLAGS',
308 help='sets generator flag FLAG to VAL')
309 parser.add_option('--generator-output', dest='generator_output',
310 action='store', default=None, metavar='DIR', type='path',
311 env_name='GYP_GENERATOR_OUTPUT',
312 help='puts generated build files under DIR')
313 parser.add_option('--ignore-environment', dest='use_environment',
314 action='store_false', default=True, regenerate=False,
315 help='do not read options from environment variables')
316 parser.add_option('--check', dest='check', action='store_true',
317 help='check format of gyp files')
318 parser.add_option('--parallel', action='store_true',
319 env_name='GYP_PARALLEL',
320 help='Use multiprocessing for speed (experimental)')
321 parser.add_option('--toplevel-dir', dest='toplevel_dir', action='store',
322 default=None, metavar='DIR', type='path',
323 help='directory to use as the root of the source tree')
324 parser.add_option('--build', dest='configs', action='append',
325 help='configuration for build after project generation')
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")
337 # We read a few things from ~/.gyp, so set up a var for that.
339 if sys.platform in ('cygwin', 'win32'):
340 home_vars.append('USERPROFILE')
343 for home_var in home_vars:
344 home = os.getenv(home_var)
346 home_dot_gyp = os.path.join(home, '.gyp')
347 if not os.path.exists(home_dot_gyp):
352 # TODO(thomasvl): add support for ~/.gyp/defaults
354 options, build_files_arg = parser.parse_args(args)
355 build_files = build_files_arg
357 if not options.formats:
358 # If no format was given on the command line, then check the env variable.
359 generate_formats = []
360 if options.use_environment:
361 generate_formats = os.environ.get('GYP_GENERATORS', [])
363 generate_formats = re.split('[\s,]', generate_formats)
365 options.formats = generate_formats
367 # Nothing in the variable, default based on platform.
368 if sys.platform == 'darwin':
369 options.formats = ['xcode']
370 elif sys.platform in ('win32', 'cygwin'):
371 options.formats = ['msvs']
373 options.formats = ['make']
375 if not options.generator_output and options.use_environment:
376 g_o = os.environ.get('GYP_GENERATOR_OUTPUT')
378 options.generator_output = g_o
380 if not options.parallel and options.use_environment:
381 p = os.environ.get('GYP_PARALLEL')
382 options.parallel = bool(p and p != '0')
384 for mode in options.debug:
387 # Do an extra check to avoid work when we're not debugging.
388 if DEBUG_GENERAL in gyp.debug:
389 DebugOutput(DEBUG_GENERAL, 'running with these options:')
390 for option, value in sorted(options.__dict__.items()):
393 if isinstance(value, basestring):
394 DebugOutput(DEBUG_GENERAL, " %s: '%s'", option, value)
396 DebugOutput(DEBUG_GENERAL, " %s: %s", option, value)
399 build_files = FindBuildFiles()
401 raise GypError((usage + '\n\n%s: error: no build_file') %
404 # TODO(mark): Chromium-specific hack!
405 # For Chromium, the gyp "depth" variable should always be a relative path
406 # to Chromium's top-level "src" directory. If no depth variable was set
407 # on the command line, try to find a "src" directory by looking at the
408 # absolute path to each build file's directory. The first "src" component
409 # found will be treated as though it were the path used for --depth.
410 if not options.depth:
411 for build_file in build_files:
412 build_file_dir = os.path.abspath(os.path.dirname(build_file))
413 build_file_dir_components = build_file_dir.split(os.path.sep)
414 components_len = len(build_file_dir_components)
415 for index in xrange(components_len - 1, -1, -1):
416 if build_file_dir_components[index] == 'src':
417 options.depth = os.path.sep.join(build_file_dir_components)
419 del build_file_dir_components[index]
421 # If the inner loop found something, break without advancing to another
426 if not options.depth:
427 raise GypError('Could not automatically locate src directory. This is'
428 'a temporary Chromium feature that will be removed. Use'
429 '--depth as a workaround.')
431 # If toplevel-dir is not set, we assume that depth is the root of our source
433 if not options.toplevel_dir:
434 options.toplevel_dir = options.depth
436 # -D on the command line sets variable defaults - D isn't just for define,
437 # it's for default. Perhaps there should be a way to force (-F?) a
438 # variable's value so that it can't be overridden by anything else.
439 cmdline_default_variables = {}
441 if options.use_environment:
442 defines += ShlexEnv('GYP_DEFINES')
444 defines += options.defines
445 cmdline_default_variables = NameValueListToDict(defines)
446 if DEBUG_GENERAL in gyp.debug:
447 DebugOutput(DEBUG_GENERAL,
448 "cmdline_default_variables: %s", cmdline_default_variables)
453 # If ~/.gyp/include.gypi exists, it'll be forcibly included into every
454 # .gyp file that's loaded, before anything else is included.
455 if home_dot_gyp != None:
456 default_include = os.path.join(home_dot_gyp, 'include.gypi')
457 if os.path.exists(default_include):
458 print 'Using overrides found in ' + default_include
459 includes.append(default_include)
461 # Command-line --include files come after the default include.
463 includes.extend(options.includes)
465 # Generator flags should be prefixed with the target generator since they
466 # are global across all generator runs.
468 if options.use_environment:
469 gen_flags += ShlexEnv('GYP_GENERATOR_FLAGS')
470 if options.generator_flags:
471 gen_flags += options.generator_flags
472 generator_flags = NameValueListToDict(gen_flags)
473 if DEBUG_GENERAL in gyp.debug.keys():
474 DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags)
476 # TODO: Remove this and the option after we've gotten folks to move to the
478 if options.msvs_version:
479 print >>sys.stderr, \
480 'DEPRECATED: Use generator flag (-G msvs_version=' + \
481 options.msvs_version + ') instead of --msvs-version=' + \
483 generator_flags['msvs_version'] = options.msvs_version
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}
497 # Start with the default variables from the command line.
498 [generator, flat_list, targets, data] = Load(build_files, format,
499 cmdline_default_variables,
500 includes, options.depth,
501 params, options.check,
502 options.circular_check)
504 # TODO(mark): Pass |data| for now because the generator needs a list of
505 # build files that came in. In the future, maybe it should just accept
506 # a list, and not the whole data dict.
507 # NOTE: flat_list is the flattened dependency graph specifying the order
508 # that targets may be built. Build systems that operate serially or that
509 # need to have dependencies defined before dependents reference them should
510 # generate targets in the order specified in flat_list.
511 generator.GenerateOutput(flat_list, targets, data, params)
514 valid_configs = targets[flat_list[0]]['configurations'].keys()
515 for conf in options.configs:
516 if conf not in valid_configs:
517 raise GypError('Invalid config specified via --build: %s' % conf)
518 generator.PerformBuild(data, options.configs, params)
526 return gyp_main(args)
528 sys.stderr.write("gyp: %s\n" % e)
531 if __name__ == '__main__':
532 sys.exit(main(sys.argv[1:]))