Skip to content

Instantly share code, notes, and snippets.

@uucidl
Last active July 18, 2016 08:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uucidl/3bf3795242aefe6846a4 to your computer and use it in GitHub Desktop.
Save uucidl/3bf3795242aefe6846a4 to your computer and use it in GitHub Desktop.
(WIP) FASTbuild generator for gyp
From 2c2a5e747a601ec655e5a94d935cca09599580d1 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Mon, 9 May 2016 12:51:23 +0200
Subject: [PATCH 01/19] Import fastbuild generator
---
pylib/gyp/generator/fastbuild.py | 1095 ++++++++++++++++++++++++++++++++++++++
1 file changed, 1095 insertions(+)
create mode 100644 pylib/gyp/generator/fastbuild.py
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
new file mode 100644
index 0000000..8cdf52c
--- /dev/null
+++ b/pylib/gyp/generator/fastbuild.py
@@ -0,0 +1,1095 @@
+##
+# NOTE(nicolas):
+#
+# This module generates `.bff` files for consumption with the FASTbuild
+# URL(http://fastbuild.org) opensource build system developed by Franta Fulin.
+#
+# TODO(nicolas): $(IntDir) MSVS macro to be supported. (Or not?)
+
+import copy
+import gyp
+import gyp.common
+import gyp.msvs_emulation
+import gyp.xcode_emulation
+import os
+
+from collections import defaultdict
+from itertools import product
+from pprint import pformat
+
+# Utilities
+
+
+def enum(*sequential, **named):
+ enums = dict(zip(sequential, range(len(sequential))), **named)
+ return type('Enum', (), enums)
+
+
+def debug_assert(x):
+ if fastbuild_debug:
+ if not x:
+ import pdb
+ pdb.set_trace()
+ assert(x)
+
+# TODO(nil): shouldn't we also search in the runtime path of the toolset?
+# for instance we have ml64.exe that needs to be found but that cannot be guaranteed
+# when launching the `gyp` command
+def shell_which(filename, additional_paths=None):
+ if additional_paths is None:
+ additional_paths = []
+
+ if os.access(filename, os.F_OK | os.X_OK):
+ return filename
+
+ path_dirs = [y.strip('"') for y in os.environ["PATH"].split(os.pathsep)] + additional_paths
+ extensions = [""] + os.environ.get("PATHEXT", "").split(os.pathsep)
+
+ for path_dir, ext in product(path_dirs, extensions):
+ filepath = os.path.join(path_dir, filename + ext)
+ if os.access(filepath, os.F_OK | os.X_OK):
+ return filepath
+ debug_assert(False)
+ raise ValueError('Could not find executable %s' % filename)
+
+# Configuration & Constants
+
+fastbuild_debug = True
+fastbuild_verbose = False
+
+CompilerType = enum(
+ C='C',
+ CXX='CXX',
+ ObjC='ObjC',
+ ObjCXX='ObjCXX',
+ NONE='NONE'
+)
+
+compiler_type_extension_index = {
+ '.c': CompilerType.C,
+ '.cc': CompilerType.CXX,
+ '.cpp': CompilerType.CXX,
+ '.cxx': CompilerType.CXX,
+ '.m': CompilerType.ObjC,
+ '.mm': CompilerType.ObjCXX,
+ '.h': CompilerType.NONE,
+ '.hpp': CompilerType.NONE,
+}
+
+# GENERATOR API Implementation:
+
+# Here are some entry points left undefined:
+#
+# CalculateGeneratorInputInfo(params)
+# generator_additional_non_configuration_keys
+# generator_additional_path_sections
+# generator_extra_sources_for_rules
+# generator_supports_multiple_toolsets
+# generator_wants_static_library_dependencies_adjusted
+# generator_filelist_paths
+
+
+def CalculateVariables(default_variables, params):
+ # fastbuild supports windows, mac and linux
+ flavor = gyp.common.GetFlavor(params)
+ default_variables.setdefault('OS', flavor)
+
+# TODO(nicolas): clean this list up
+generator_default_variables = {
+ 'CONFIGURATION_NAME': '$ConfigName$',
+ 'EXECUTABLE_PREFIX': '',
+ 'EXECUTABLE_SUFFIX': '',
+ 'INTERMEDIATE_DIR': '$IntermediateDir$',
+ 'PRODUCT_DIR': '$ProductDir$',
+ 'SHARED_INTERMEDIATE_DIR': '$SharedIntermediateDir$',
+ 'SHARED_LIB_PREFIX': 'lib',
+ 'STATIC_LIB_PREFIX': 'lib',
+ 'STATIC_LIB_SUFFIX': '.a',
+ # TODO(nicolas): review this when implementing rules
+ 'RULE_INPUT_PATH': '%1',
+ # This gets expanded by Python.
+ 'RULE_INPUT_DIRNAME': '$RuleInputDirname$',
+ 'RULE_INPUT_ROOT': '$RuleInputRoot$',
+ 'RULE_INPUT_NAME': '$RuleInputName$',
+ 'RULE_INPUT_EXT': '$RuleInputExt$',
+}
+generator_wants_sorted_dependencies = True
+
+# main entry point
+
+
+def GenerateOutput(target_list, target_dicts, data, params):
+ def get_bff_path(build_file, params):
+ (build_file_root, _ignored_ext) = os.path.splitext(build_file)
+ options = params['options']
+ bff_basename = build_file_root + options.suffix + '.bff'
+ if options.generator_output:
+ return os.path.join(options.generator_output, bff_basename)
+ else:
+ return bff_basename
+
+ def get_config_names(target_list, target_dicts):
+ first_target = target_list[0]
+ return target_dicts[first_target]['configurations'].keys()
+
+ flavor = gyp.common.GetFlavor(params)
+ for build_file in params['build_files']:
+ bff_path = get_bff_path(build_file, params)
+
+ # TODO(nicolas): profiling
+ print 'Generating FASTbuild file:'
+ bff = MakeBFFCreator()
+ PushHeadingComment(bff, """FASTBuild build file, generated by gyp
+For flavor: %s
+ """ % flavor)
+ PushHardcodedPrologue(bff)
+ toolsets_with_bff_config = set()
+ config_names = get_config_names(target_list, target_dicts)
+ # TODO(nicolas): remove, this is needed for now because I don't know how to
+ # avoid duplicating actions which lead to the same output across
+ # configs
+ config_names = [y for y in config_names if y == 'Debug']
+
+ generator_flags = params.get('generator_flags', {})
+ toolset = MakeToolset(flavor, generator_flags)
+
+ for qualified_target in target_list:
+ target_build_file, target_name, target_toolset = \
+ gyp.common.ParseQualifiedTarget(qualified_target)
+
+ if target_toolset not in toolsets_with_bff_config:
+ PushToolsetConfig(bff, target_toolset, config_names)
+ toolsets_with_bff_config.add(target_toolset)
+
+ target_spec = target_dicts[qualified_target]
+ PushHeadingComment(bff, 'target: %s (type: %s)' %
+ (target_name, target_spec['type']))
+ if fastbuild_verbose:
+ PushComment(bff, "here is the target spec:\n%s" %
+ pformat(target_spec))
+ if flavor == 'mac':
+ gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(
+ data[build_file],
+ target_spec
+ )
+ inputs_base_dir = os.path.dirname(target_build_file)
+ platform = MakePlatform(flavor, generator_flags, toolset, target_spec)
+ PushTargetSpecs(
+ bff,
+ platform,
+ inputs_base_dir,
+ qualified_target,
+ target_spec,
+ target_dicts,
+ config_names,
+ )
+ BFFFinalize(bff)
+ BFFWriteToFile(bff, bff_path)
+
+
+def GetBFFConfigVariableName(toolset, config_name):
+ return '.%s%sConfig' % (config_name.capitalize(), toolset.capitalize())
+
+
+def PushToolsetConfig(bff, toolset, config_names):
+ products_base_dir = os.path.join('output', toolset)
+ toolset_config_definition = BFFStruct(
+ [
+ BFFAssignment('.Toolset', '%s' % toolset),
+ BFFAssignment('.SharedProductDir', '%s' % products_base_dir),
+ BFFAssignment(
+ '.SharedIntermediateDir',
+ '$SharedProductDir$/gen'
+ ),
+ # Are these ProductDir/IntermediateDir legal when outside of any
+ # configuration? TODO(nicolas): or change gyp, or change rules to
+ # be generated per config
+ BFFAssignment('.ProductDir', '$SharedProductDir$'),
+ BFFAssignment(
+ '.IntermediateDir',
+ '$SharedIntermediateDir$'
+ ),
+ BFFUsing('.%sLinker' % toolset.capitalize()),
+ BFFUsing('.%sLibrarian' % toolset.capitalize()),
+ ]
+ )
+ PushAssignment(bff, GetBFFConfigVariableName(
+ toolset, ''), toolset_config_definition)
+
+ for config_name in config_names:
+ PushConfig(
+ bff,
+ GetBFFConfigVariableName(toolset, config_name),
+ config_name,
+ toolset
+ )
+
+
+def PushConfig(bff, bff_config_name, config_name, toolset):
+ PushHeadingComment(bff, "%s Configuration" % config_name)
+ config_definition = BFFStruct(
+ [
+ BFFUsing(GetBFFConfigVariableName(toolset, '')),
+ BFFAssignment('.ConfigName', '%s' % config_name),
+ BFFAssignment('.ProductDir', '$SharedProductDir$/%s' %
+ config_name),
+ BFFAssignment('.IntermediateDir', '$ProductDir$/gen'),
+ ]
+ )
+ PushAssignment(bff,
+ bff_config_name,
+ config_definition
+ )
+
+
+def MakeBFFCreator():
+ return BFFCreator()
+
+
+CommandType = enum(
+ 'Assignment',
+ 'Concatenation',
+ 'FunctionBegin',
+ 'FunctionEnd',
+ 'Raw',
+ 'IndentedRaw',
+ 'Struct',
+ 'Using',
+)
+
+
+def BFFRaw(s):
+ return CommandType.Raw, s
+
+
+def BFFIndentedRaw(s):
+ return CommandType.IndentedRaw, s
+
+
+def BFFUsing(variable_name):
+ return CommandType.Using, locals()
+
+
+def BFFFunctionBegin(function_name, function_arguments):
+ return CommandType.FunctionBegin, locals()
+
+
+def BFFFunctionEnd():
+ return CommandType.FunctionEnd, locals()
+
+
+def BFFAssignment(var_name, expr):
+ return CommandType.Assignment, locals()
+
+
+def BFFConcatenation(var_name, expr):
+ return CommandType.Concatenation, locals()
+
+
+def PushCommand(bff, command):
+ bff.commands.append(command)
+
+
+def PushHardcodedPrologue(bff):
+ PushCommand(bff, BFFRaw(HARDCODED_PROLOGUE))
+
+
+def PushHeadingComment(bff, message):
+ if len(bff.commands) > 0:
+ PushCommand(bff, BFFRaw('\n'))
+ lines = message.splitlines()
+ PushCommand(bff, BFFRaw(';; # %s\n' % lines[0]))
+ for line in lines[1:]:
+ PushCommand(bff, BFFRaw(';; %s\n' % line))
+
+
+def PushComment(bff, message):
+ for line in message.splitlines():
+ PushCommand(bff, BFFIndentedRaw(';; %s\n' % line))
+
+
+def BFFBlock(bff, begin_command):
+ class ContextManager:
+
+ def __enter__(self):
+ PushCommand(bff, begin_command)
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ PushCommand(bff, BFFFunctionEnd())
+ return ContextManager()
+
+
+def BFFStruct(commands):
+ return CommandType.Struct, locals()
+
+
+def MakeToolset(flavor, generator_flags):
+ class Toolset:
+ def __init__(self):
+ self.path_from_arch = {}
+
+ toolset = Toolset()
+ if flavor == 'win':
+ def OpenOutput(__ignored_path, __ignored_mode):
+ class NullFile:
+ def write(self,_):
+ pass
+ def close(self):
+ pass
+ return NullFile()
+
+ shared_system_includes = []
+ toplevel_build = ''
+ cl_paths = gyp.msvs_emulation.GenerateEnvironmentFiles(
+ toplevel_build, generator_flags, shared_system_includes, OpenOutput)
+ for arch, path in sorted(cl_paths.iteritems()):
+ toolset.path_from_arch[arch] = os.path.dirname(path)
+
+ return toolset
+
+def MakePlatform(flavor, generator_flags, toolset, target_spec):
+ if flavor == 'mac':
+ class MacPlatform:
+
+ def __init__(self):
+ self.xcode_settings = gyp.xcode_emulation.XcodeSettings(
+ target_spec)
+
+ def get_dll_extension(self):
+ return '.dylib'
+
+ def get_path(self, config_name):
+ return None
+
+ def linker_options_for_dll(self):
+ return '-shared'
+
+ def push_specific_cflags(self, options, config_name):
+ options += self.xcode_settings.GetCflags(config_name)
+
+ def push_include_dir(self, options, include_dir):
+ options.append('-I')
+ options.append('"%s"' % include_dir)
+
+ def push_define(self, options, flag, value=None):
+ options.append('-D')
+ if value is not None:
+ options.append('%s="%s"' % (flag, value))
+ else:
+ options.append('%s' % flag)
+
+ def ldflags(self, libraries):
+ return self.xcode_settings.AdjustLibraries(libraries)
+
+ def ldflags_for_library_dirs(self, library_dirs):
+ flags = []
+ for library_dir in library_dirs:
+ flags += ['-L"%s"' % library_dir]
+ flags += ['-F"%s"' % library_dir]
+ return flags
+
+ return MacPlatform()
+ elif flavor == 'win':
+ class WinPlatform:
+ def __init__(self):
+ self.msvs_settings = gyp.msvs_emulation.MsvsSettings(target_spec, generator_flags)
+
+ def get_dll_extension(self):
+ return '.dll'
+
+ def get_path(self, config_name):
+ arch = self.msvs_settings.GetArch(config_name)
+ return toolset.path_from_arch[arch]
+
+ def push_specific_cflags(self, options, config_name):
+ options += self.msvs_settings.GetCflags(config_name)
+
+ def push_define(self, options, flag, value=None):
+ if value is not None:
+ options.append('/D%s="%s"' % (flag, value))
+ else:
+ options.append('/D%s' % flag)
+
+ def push_include_dir(self, options, include_dir):
+ options.append('/I"%s"' % include_dir)
+
+ def linker_options_for_dll(self):
+ return '/DLL'
+
+ def ldflags(self, libraries):
+ return self.msvs_settings.AdjustLibraries(libraries)
+
+ def ldflags_for_library_dirs(self, library_dirs):
+ flags = []
+ for library_dir in library_dirs:
+ flags += ['/LIBPATH:"%s"' % library_dir]
+ return flags
+
+
+
+
+
+ return WinPlatform()
+ else:
+ raise ValueError('Unimplemented flavor %s' % flavor)
+
+
+def MakeCompilerOptions(platform):
+ return []
+
+
+def CopyCompilerOptions(compiler_options):
+ result = copy.copy(compiler_options)
+ return result
+
+
+def CompilerOptionsPushDefine(platform, compiler_options, flag, value=None):
+ platform.push_define(compiler_options, flag, value)
+
+
+def CompilerOptionsPushIncludeDir(platform, compiler_options, include_dir):
+ platform.push_include_dir(compiler_options, include_dir)
+
+
+def CompilerOptionsHasCommandLine(compiler_options):
+ return len(compiler_options) > 0
+
+
+def CompilerOptionsConfigure(platform, compiler_options, config_name):
+ # TODO(nicolas): take language into account
+ platform.push_specific_cflags(compiler_options, config_name)
+
+
+def CompilerOptionsGetCommandLine(compiler_options):
+ return ' '.join(compiler_options)
+
+
+def PlatformLinkerOptionsForDLL(platform):
+ return platform.linker_options_for_dll()
+
+def PlatformLDFlagsForLibraryDirs(platform, library_dirs):
+ return platform.ldflags_for_library_dirs(library_dirs)
+
+
+def PlatformLDFlagsForLibraries(platform, libraries):
+ return platform.ldflags(libraries)
+
+def PlatformGetToolsetPaths(platform, config_name):
+ return [platform.get_path(config_name)]
+
+def PlatformGetDLLExtension(platform):
+ return platform.get_dll_extension()
+
+# TODO(nil): evaluate the gyp variables correctly.
+# see usage of GypPathToNinja in ninja.py for reference (and discussion around it)
+# return bff targets produced to caller (useful to create aliases)
+
+
+def PushTargetSpecs(
+ bff,
+ platform,
+ inputs_base_dir,
+ qualified_target,
+ target_spec,
+ target_dicts,
+ config_names
+):
+ target_build_file, target_name, target_toolset = \
+ gyp.common.ParseQualifiedTarget(qualified_target)
+
+ dependencies = target_spec.get('dependencies', [])
+ if not inputs_base_dir:
+ inputs_base_dir = '.'
+
+ linkable_types = set(['static_library', 'shared_library'])
+ all_bff_target_names = []
+ linkable_dependencies = []
+ other_dependencies = []
+ for d_qualified_target in dependencies:
+ if target_dicts[d_qualified_target]['type'] in linkable_types:
+ linkable_dependencies.append(d_qualified_target)
+ else:
+ other_dependencies.append(d_qualified_target)
+
+ for config_name in config_names:
+ def bff_target_from_gyp_target(qualified_target, config_name):
+ return '%s-%s' % (qualified_target, config_name)
+
+ ##
+ # NOTE(nicolas@uucidl.com):
+ # We are appending the "config" after the target name. We make
+ # this initial choice which means we have all configs in the same bff at the
+ # expense of making it slightly more complicated.
+ #
+ # The advantage of the many configs in one bff is ease of building Debug and Release with
+ # the same project file.
+ bff_target_name = bff_target_from_gyp_target(
+ qualified_target, config_name)
+ bff_linkable_dependencies = []
+ for d_qualified_target in linkable_dependencies:
+ bff_linkable_dependencies.append(
+ bff_target_from_gyp_target(
+ d_qualified_target,
+ config_name
+ )
+ )
+
+ bff_other_dependencies = []
+ for d_qualified_target in other_dependencies:
+ d_bff_target_name = '%s' % bff_target_from_gyp_target(
+ d_qualified_target,
+ config_name
+ )
+ if not BFFIsEmptyTarget(bff, d_bff_target_name):
+ bff_other_dependencies.append(d_bff_target_name)
+
+ if PushTargetForConfig(
+ bff,
+ platform,
+ inputs_base_dir,
+ target_spec,
+ config_name,
+ bff_target_name,
+ bff_linkable_dependencies,
+ bff_other_dependencies):
+ # Easier name for the target
+ bff_alias_name = '%s-%s' % (target_name, config_name)
+ PushAlias(bff, bff_alias_name, [bff_target_name])
+ all_bff_target_names.append(bff_target_name)
+ else:
+ # A target w/o a corresponding FB correspondant is a empty target
+ # That we'd like to remember
+ BFFRegisterEmptyTarget(bff, bff_target_name)
+
+ if not all_bff_target_names:
+ PushComment(bff, "Nothing to generate for %s" % target_name)
+ else:
+ PushAlias(bff, target_name, all_bff_target_names)
+
+
+def PushTargetForConfig(
+ bff,
+ platform,
+ inputs_base_dir,
+ target_spec,
+ config_name,
+ bff_target_name,
+ bff_linkable_dependencies,
+ bff_other_dependencies):
+ target_toolset = target_spec['toolset']
+ target_name = target_spec['target_name']
+
+ def gyp_path_to_os_path(gyp_path):
+ fragments = gyp_path.split('/')
+ return os.path.abspath(
+ os.path.join(
+ inputs_base_dir,
+ os.path.join(*fragments)
+ )
+ )
+
+ def using_config():
+ PushCommand(
+ bff,
+ BFFUsing(GetBFFConfigVariableName(target_toolset, config_name))
+ )
+
+ def using_compiler(toolset, config_name, compiler_type):
+ PushCommand(
+ bff,
+ BFFUsing(
+ '.%s%s%sCompiler' %
+ (toolset.capitalize(),
+ config_name.capitalize(),
+ compiler_type)))
+
+ bff_action_target_names = []
+ # copies targets are shared across configs:
+ for copy_index, copy in enumerate(target_spec.get('copies', [])):
+ copy_name = '%s-Copy-%s' % (bff_target_name, copy_index)
+ with BFFBlock(bff, BFFFunctionBegin('Copy', [copy_name])):
+ using_config()
+ PushAssignment(bff,
+ '.Source',
+ # TODO(nicolas): gyp_path_to_os_path ->
+ # os_path_from_gyp_path
+ map(gyp_path_to_os_path, copy['files'])
+ )
+ destination = gyp_path_to_os_path(copy['destination'])
+ destination = os.path.join(destination, '')
+ PushAssignment(bff, '.Dest', destination)
+ bff_action_target_names.append(copy_name)
+
+ def push_single_output_action(action_spec, prepended_inputs=None):
+ if prepended_inputs is None:
+ prepended_inputs = []
+ if len(action_spec['outputs']) > 1:
+ raise ValueError(
+ "More than 1 output (action_name: %s) not supported" %
+ action_spec['action_name'])
+ action = action_spec['action']
+ # TODO(nicolas): FASTbuild wants the actual executable location
+ # TODO(nicolas): look in toolset path too
+ executable = shell_which(action[0], PlatformGetToolsetPaths(platform, config_name))
+ PushAssignment(bff, '.ExecExecutable', executable)
+ # TODO(nicolas): hardcoded platform quoting
+ quoted_arguments = ['"%s"' % x for x in action[1:]]
+ PushAssignment(
+ bff, '.ExecArguments', ' '.join(quoted_arguments))
+ PushAssignment(
+ bff,
+ '.ExecInput',
+ prepended_inputs +
+ action_spec.get(
+ 'inputs',
+ []))
+ output = gyp_path_to_os_path(action_spec['outputs'][0])
+ PushAssignment(bff, '.ExecOutput', output)
+
+ def compiler_type_for_source(source):
+ _ignored_name, ext = os.path.splitext(source)
+ if ext in compiler_type_extension_index:
+ compiler_type = compiler_type_extension_index[ext]
+ return compiler_type
+
+ def is_compilable(source):
+ compiler_type = compiler_type_for_source(source)
+ return compiler_type and compiler_type != CompilerType.NONE
+
+ def apply_multiple_output_hack(action_spec):
+ # TODO(nicolas): TAG(hack) workaround for multiple generated files
+ if len(action_spec['outputs']) > 1:
+ # TODO(nicolas): also of interests are the linkable products, like
+ # dylibs
+ compilable_sources = [
+ y for y in action_spec['outputs'] if is_compilable(y)]
+ unique_output = None
+ if len(compilable_sources) == 1:
+ unique_output = compilable_sources[0]
+ else:
+ unique_output = action_spec['outputs'][0]
+
+ if unique_output:
+ PushComment(
+ bff,
+ 'TAG(hack) had to ignore extra outputs %s because Exec does not support multiple outputs, see URL(https://github.com/fastbuild/fastbuild/issues/95)' %
+ action_spec['outputs'][
+ 1:])
+ action_spec['outputs'] = [unique_output]
+ print 'Applied workaround for %s (Exec missing multiple outputs)' % action_name
+
+ source_generators = defaultdict(list)
+ for action_spec in target_spec.get('actions', []):
+ action_name = action_spec['action_name']
+ bff_action_name = '%s-%s' % (bff_target_name, action_name)
+ PushComment(bff, 'Action %s' % action_name)
+
+ apply_multiple_output_hack(action_spec)
+
+ with BFFBlock(bff, BFFFunctionBegin('Exec', [bff_action_name])):
+ using_config()
+ push_single_output_action(action_spec)
+
+ output_path = action_spec['outputs'][0]
+ compiler_type = compiler_type_for_source(output_path)
+ if compiler_type:
+ source_generators[compiler_type].append(bff_action_name)
+ else:
+ bff_action_target_names.append(bff_action_name)
+
+ # rules produce actions:
+ for rule in target_spec.get('rules', []):
+ rule_name = rule['rule_name']
+ PushComment(bff, 'Rule %s' % rule_name)
+ rule_inputs = rule.get('inputs', [])
+ rule_extension = '.%s' % rule['extension']
+ action_index = 0
+ for gyp_source in target_spec.get('sources', []):
+ os_path = gyp_path_to_os_path(gyp_source)
+ name, ext = os.path.splitext(os_path)
+ if ext == rule_extension:
+ PushComment(
+ bff, 'Action for %s' % gyp_source)
+ bff_action_name = '%s-%s-%s' % (bff_target_name,
+ rule_name, action_index)
+ action_index += 1
+ action = rule.get('action', [])
+ with BFFBlock(bff, BFFFunctionBegin('Exec', [bff_action_name])):
+ using_config()
+ PushAssignment(
+ bff, '.RuleInputDirname', os.path.dirname(name))
+ PushAssignment(
+ bff, '.RuleInputName', os.path.basename(os_path))
+ PushAssignment(
+ bff, '.RuleInputRoot', os.path.basename(name))
+ PushAssignment(bff, '.RuleInputExt', ext)
+ push_single_output_action(rule, [os_path])
+
+ rule_output = rule['outputs'][0]
+ if rule.get('process_outputs_as_sources', False):
+ compiler_type = compiler_type_for_source(rule_output)
+ if compiler_type:
+ source_generators[compiler_type].append(
+ bff_action_name)
+ else:
+ PushComment(bff, 'Unknown generated file: %s' %
+ rule_output)
+ else:
+ # TODO(nicolas): actually these actions are meant to be
+ # pre-requisite to us, and should not be fed into the
+ # alias that stands for our products. Otherwise we trigger
+ # the execution of the wrong actions at the wrong time
+ # when triggering the main target alias
+ bff_action_target_names.append(bff_action_name)
+
+ sources_by_compiler_type = defaultdict(list)
+ for gyp_source in target_spec.get('sources', []):
+ compiler_type = compiler_type_for_source(gyp_source)
+ if compiler_type:
+ sources_by_compiler_type[compiler_type].append(
+ gyp_path_to_os_path(gyp_source)
+ )
+ else:
+ PushComment(bff, 'Unknown compiler type for file: %s' % gyp_source)
+
+ def push_compiler_sources(
+ os_sources,
+ compiler_type,
+ target_spec,
+ config_name,
+ dependencies):
+ using_compiler(target_toolset, config_name, compiler_type)
+
+ PushAssignment(bff, '.CompilerInputFiles', os_sources)
+ compiler_options = MakeCompilerOptions(platform)
+ CompilerOptionsConfigure(platform, compiler_options, config_name)
+ if CompilerOptionsHasCommandLine(compiler_options):
+ # TODO(nicolas): I wonder if we could de-duplicate those
+ # into structs, for more readability
+ PushComment(bff, 'Config-specific command line')
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ '.CompilerOptions',
+ ' ' + CompilerOptionsGetCommandLine(compiler_options),
+ )
+ )
+
+ config = target_spec.get('configurations').get(config_name, {})
+ compiler_options = MakeCompilerOptions(platform)
+ for define in config.get('defines', []):
+ CompilerOptionsPushDefine(
+ platform,
+ compiler_options,
+ define
+ )
+ for include_dir in config.get('include_dirs', []):
+ CompilerOptionsPushIncludeDir(
+ platform,
+ compiler_options,
+ include_dir if os.path.isabs(
+ include_dir) else gyp_path_to_os_path(include_dir)
+ )
+ if CompilerOptionsHasCommandLine(compiler_options):
+ PushComment(
+ bff,
+ 'Target-specific command line'
+ )
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ '.CompilerOptions',
+ ' ' + CompilerOptionsGetCommandLine(compiler_options)
+ )
+ )
+ PushAssignment(
+ bff,
+ '.CompilerOutputPath',
+ '$ProductDir$/obj/%s' %
+ target_name)
+
+ config = target_spec.get('configurations').get(config_name, {})
+
+ # Generate compilation targets:
+ bff_all_object_list_names = []
+ all_compilation_sources = set(
+ sources_by_compiler_type.keys() +
+ source_generators.keys())
+ if fastbuild_verbose:
+ PushComment(bff, 'Sources by compiler\n%s' %
+ pformat(all_compilation_sources))
+
+ for compiler_type in all_compilation_sources:
+ if compiler_type == CompilerType.NONE:
+ continue
+ sources = (
+ sources_by_compiler_type.get(compiler_type, []) +
+ source_generators.get(compiler_type, [])
+ )
+ bff_object_list_name = "%s-Objects-%s" % (
+ bff_target_name, compiler_type)
+ with BFFBlock(
+ bff,
+ BFFFunctionBegin('ObjectList', [bff_object_list_name])
+ ):
+ using_config()
+ push_compiler_sources(
+ sources,
+ compiler_type,
+ target_spec, config_name,
+ bff_other_dependencies)
+ bff_all_object_list_names.append(bff_object_list_name)
+
+ # Generate link targets:
+ bff_product_target_name = None
+ bff_executable_libraries = []
+ if (
+ target_spec['type'] == 'executable' or
+ target_spec['type'] == 'shared_library'
+ ):
+ # TODO(nicolas): simplify
+ linker_target_name = '%s-Exe' % bff_target_name
+ # TODO(nicolas): TAG(platform) this depends on the target platform
+ linker_product_suffix = dict(
+ executable='.exe',
+ shared_library=PlatformGetDLLExtension(platform)
+ )[target_spec['type']]
+ linker_product = "%s%s" % (
+ target_spec['product_name'], linker_product_suffix)
+ linker_function = dict(
+ executable='Executable',
+ shared_library='DLL',
+ )[target_spec['type']]
+ with BFFBlock(bff, BFFFunctionBegin(linker_function, [linker_target_name])):
+ using_config()
+ PushAssignment(
+ bff, '.LinkerOutput', '$ProductDir$/%s' % linker_product
+ )
+ if linker_function == 'DLL':
+ # NOTE(nil): TAG(platform) the right flags are
+ # necessary. DLL nodes in fbuild don't actually do
+ # anything special with linker flags, but forgetting
+ # them will lead FBuild to consider the node an
+ # executable rather than a DLL, and it will then
+ # complain during dependency checking.
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ '.LinkerOptions',
+ ' %s' % PlatformLinkerOptionsForDLL(platform)
+ )
+ )
+
+ for flag in PlatformLDFlagsForLibraryDirs(
+ platform, config.get('library_dirs', [])):
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ '.LinkerOptions',
+ ' %s' % flag
+ )
+ )
+ for flag in PlatformLDFlagsForLibraries(
+ platform, target_spec.get('libraries', [])):
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ '.LinkerOptions',
+ ' %s' % flag
+ )
+ )
+
+ PushAssignment(
+ bff,
+ '.Libraries',
+ bff_linkable_dependencies +
+ bff_executable_libraries +
+ bff_all_object_list_names
+ )
+ bff_product_target_name = linker_target_name
+ elif target_spec['type'] == 'static_library':
+ library_name = '%s-Lib' % bff_target_name
+ with BFFBlock(bff, BFFFunctionBegin('Library', [library_name])):
+ using_config()
+ using_compiler(target_toolset, config_name, CompilerType.CXX)
+
+ # TODO(nil): change, this shouldnt be hardcoded
+ PushAssignment(
+ bff,
+ '.LibrarianOutput',
+ '$ProductDir$/Lib%s.lib' %
+ target_spec['product_name'])
+ PushAssignment(bff,
+ '.LibrarianAdditionalInputs',
+ bff_all_object_list_names)
+ PushAssignment(bff,
+ '.CompilerOutputPath',
+ '$ProductDir$/lib/%s' % target_name)
+
+ bff_product_target_name = library_name
+ elif target_spec['type'] == 'none':
+ # Nothing compilable there
+ pass
+ else:
+ PushComment(
+ bff, "don't know how to generate target %s" % bff_target_name)
+
+ subtarget_names = []
+ subtarget_names += bff_action_target_names
+ if bff_product_target_name:
+ subtarget_names += [bff_product_target_name]
+
+ if subtarget_names:
+ BFFRegisterTarget(bff, bff_target_name)
+ PushAlias(bff, bff_target_name,
+ subtarget_names + bff_other_dependencies)
+ return True
+ else:
+ return False
+
+
+def BFFRegisterTarget(bff, fastbuild_target_name):
+ debug_assert(fastbuild_target_name not in
+ bff.all_fb_targets + bff.all_empty_targets)
+ bff.all_fb_targets.append(fastbuild_target_name)
+
+
+def BFFRegisterEmptyTarget(bff, fastbuild_target_name):
+ debug_assert(fastbuild_target_name not in
+ bff.all_fb_targets + bff.all_empty_targets)
+ bff.all_empty_targets.append(fastbuild_target_name)
+
+
+def BFFIsEmptyTarget(bff, fastbuild_target_name):
+ return fastbuild_target_name in bff.all_empty_targets
+
+
+def PushAssignment(bff, key, value):
+ PushCommand(bff, BFFAssignment(key, value))
+
+
+def PushAlias(bff, alias_name, targets):
+ with BFFBlock(bff, BFFFunctionBegin('Alias', [alias_name])):
+ PushCommand(bff,
+ BFFAssignment(
+ '.Targets',
+ targets
+ )
+ )
+
+
+def BFFFinalize(bff):
+ PushHeadingComment(bff, 'This is the global, default target')
+ PushAlias(bff, 'All', bff.all_fb_targets)
+
+
+def BFFWriteToFile(bff, bff_path):
+ print "Writing to file %s" % bff_path
+ bff_dir = os.path.dirname(bff_path)
+ if len(bff_dir) and not os.path.exists(bff_dir):
+ os.makedirs(bff_dir)
+
+ class Indentation:
+
+ def __init__(self):
+ self.current = 0
+ self.increment = 4
+
+ def inc(self):
+ self.current += self.increment
+
+ def dec(self):
+ self.current -= self.increment
+
+ def write(self, dest):
+ dest.write(' ' * self.current)
+
+ indent = Indentation()
+ current_block = None
+
+ def write_string_list(dest, list_value):
+ if len(list_value) == 0:
+ return
+ # TODO(nil): wrap when too long
+ dest.write("'%s'" % list_value[0])
+ for e in list_value[1:]:
+ dest.write(', ')
+ dest.write("'%s'" % e)
+
+ def write_struct(bff_file, struct_data):
+ bff_file.write('\n')
+ indent.write(bff_file)
+ bff_file.write('[\n')
+ indent.inc()
+ for command_type, command_data in struct_data['commands']:
+ if command_type == CommandType.Raw:
+ bff_file.write(command_data)
+ elif command_type == CommandType.IndentedRaw:
+ indent.write(bff_file)
+ bff_file.write(command_data)
+ elif command_type == CommandType.Assignment:
+ write_variable_op(bff_file, command_data, '=')
+ elif command_type == CommandType.Using:
+ indent.write(bff_file)
+ bff_file.write('Using(%s)\n' % command_data['variable_name'])
+ else:
+ assert false, "invalid command type %s" % command_type
+ indent.dec()
+ indent.write(bff_file)
+ bff_file.write(']')
+
+ def write_variable_op(bff_file, command_data, op):
+ indent.write(bff_file)
+ bff_file.write(command_data['var_name'])
+ bff_file.write(' %s ' % op)
+ if isinstance(command_data['expr'], list):
+ list_value = command_data['expr']
+ bff_file.write('{')
+ write_string_list(bff_file, list_value)
+ bff_file.write('}')
+ elif command_data['expr'][0] == CommandType.Struct:
+ write_struct(bff_file, command_data['expr'][1])
+ else:
+ bff_file.write("'%s'" % command_data['expr'])
+ bff_file.write('\n')
+
+ with open(bff_path, 'w') as bff_file:
+ for command_type, command_data in bff.commands:
+ if command_type == CommandType.Raw:
+ bff_file.write(command_data)
+ elif command_type == CommandType.IndentedRaw:
+ indent.write(bff_file)
+ bff_file.write(command_data)
+ elif command_type == CommandType.FunctionBegin:
+ current_block = command_data['function_name']
+ indent.write(bff_file)
+ bff_file.write("%s(" % command_data['function_name'])
+ write_string_list(bff_file, command_data['function_arguments'])
+ bff_file.write(")\n")
+ bff_file.write("{\n")
+ indent.inc()
+ elif command_type == CommandType.FunctionEnd:
+ indent.dec()
+ indent.write(bff_file)
+ bff_file.write('}\n')
+ elif command_type == CommandType.Assignment:
+ write_variable_op(bff_file, command_data, '=')
+ elif command_type == CommandType.Concatenation:
+ write_variable_op(bff_file, command_data, '+')
+ elif command_type == CommandType.Using:
+ indent.write(bff_file)
+ bff_file.write('Using(%s)\n' % command_data['variable_name'])
+ else:
+ assert false, "unknown command type %s" % command_type
+
+
+class BFFCreator:
+
+ def __init__(self):
+ self.commands = []
+ self.all_fb_targets = []
+ self.all_empty_targets = []
+
+HARDCODED_PROLOGUE = r"""
+#include "local.bff"
+"""
--
2.7.0.windows.1
From 3dba691888124a267d2e7351beb8c6c73c036c00 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Mon, 20 Jun 2016 14:36:56 +0200
Subject: [PATCH 02/19] Escape defines as shell arguments should
Since we were not escaping double-quotes, you could not define a string
using a 'define' directive.
---
pylib/gyp/generator/fastbuild.py | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 8cdf52c..974b580 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -12,6 +12,7 @@ import gyp.common
import gyp.msvs_emulation
import gyp.xcode_emulation
import os
+import re
from collections import defaultdict
from itertools import product
@@ -371,12 +372,9 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
options.append('-I')
options.append('"%s"' % include_dir)
- def push_define(self, options, flag, value=None):
+ def push_define(self, options, flag):
options.append('-D')
- if value is not None:
- options.append('%s="%s"' % (flag, value))
- else:
- options.append('%s' % flag)
+ options.append('%s' % flag)
def ldflags(self, libraries):
return self.xcode_settings.AdjustLibraries(libraries)
@@ -390,6 +388,12 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
return MacPlatform()
elif flavor == 'win':
+ def QuoteShellArgument(arg):
+ # NOTE(nil): adapted from ninja.py
+ if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
+ return arg # No quoting necessary.
+ return gyp.msvs_emulation.QuoteForRspFile(arg)
+
class WinPlatform:
def __init__(self):
self.msvs_settings = gyp.msvs_emulation.MsvsSettings(target_spec, generator_flags)
@@ -404,14 +408,14 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def push_specific_cflags(self, options, config_name):
options += self.msvs_settings.GetCflags(config_name)
- def push_define(self, options, flag, value=None):
- if value is not None:
- options.append('/D%s="%s"' % (flag, value))
- else:
- options.append('/D%s' % flag)
+ def push_define(self, options, flag):
+ argument = '/D%s' % flag
+ # NOTE(nil): cl.exe interprets # as =
+ argument.replace('#', '\\%03o' % ord('#'))
+ options.append(QuoteShellArgument(argument))
def push_include_dir(self, options, include_dir):
- options.append('/I"%s"' % include_dir)
+ options.append(QuoteShellArgument('/I"%s"' % include_dir))
def linker_options_for_dll(self):
return '/DLL'
@@ -425,10 +429,6 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
flags += ['/LIBPATH:"%s"' % library_dir]
return flags
-
-
-
-
return WinPlatform()
else:
raise ValueError('Unimplemented flavor %s' % flavor)
@@ -443,8 +443,8 @@ def CopyCompilerOptions(compiler_options):
return result
-def CompilerOptionsPushDefine(platform, compiler_options, flag, value=None):
- platform.push_define(compiler_options, flag, value)
+def CompilerOptionsPushDefine(platform, compiler_options, flag):
+ platform.push_define(compiler_options, flag)
def CompilerOptionsPushIncludeDir(platform, compiler_options, include_dir):
--
2.7.0.windows.1
From 34098c204bedb15a50046e0c63602ca92e84f997 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Mon, 20 Jun 2016 16:50:18 +0200
Subject: [PATCH 03/19] Correct linking
- support loadable_module
- append linker flags as generated by the MSVS emulation module
---
pylib/gyp/generator/fastbuild.py | 137 ++++++++++++++++++++++++++++++---------
1 file changed, 108 insertions(+), 29 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 974b580..c64f81d 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -5,6 +5,7 @@
# URL(http://fastbuild.org) opensource build system developed by Franta Fulin.
#
# TODO(nicolas): $(IntDir) MSVS macro to be supported. (Or not?)
+# TODO(nicolas): autopep8, add double spaces after top level functions
import copy
import gyp
@@ -350,6 +351,11 @@ def MakeToolset(flavor, generator_flags):
def MakePlatform(flavor, generator_flags, toolset, target_spec):
if flavor == 'mac':
+ def QuoteShellArgument(arg):
+ # NOTE(nil): adapted from ninja.py
+ if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
+ return arg # No quoting necessary.
+ return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'"
class MacPlatform:
def __init__(self):
@@ -366,7 +372,9 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
return '-shared'
def push_specific_cflags(self, options, config_name):
- options += self.xcode_settings.GetCflags(config_name)
+ options.extend(self.xcode_settings.GetCflags(config_name))
+
+ # TODO(nicolas) mac version of push_specific_ldflags
def push_include_dir(self, options, include_dir):
options.append('-I')
@@ -376,6 +384,9 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
options.append('-D')
options.append('%s' % flag)
+ def quote_argument(self, argument):
+ return QuoteShellArgument(argument)
+
def ldflags(self, libraries):
return self.xcode_settings.AdjustLibraries(libraries)
@@ -406,7 +417,7 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
return toolset.path_from_arch[arch]
def push_specific_cflags(self, options, config_name):
- options += self.msvs_settings.GetCflags(config_name)
+ options.extend(self.msvs_settings.GetCflags(config_name))
def push_define(self, options, flag):
argument = '/D%s' % flag
@@ -417,9 +428,37 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def push_include_dir(self, options, include_dir):
options.append(QuoteShellArgument('/I"%s"' % include_dir))
+ def quote_argument(self, argument):
+ return QuoteShellArgument(argument)
+
def linker_options_for_dll(self):
return '/DLL'
+ def push_specific_ldflags(self, options, inputs_base_dir, config_name, target_type, target_product_name):
+ # TODO(nicolas): remove those hardcoded sentinels and replace with correct
+ # stuff (I need to figure out what there are for)
+ def gyp_to_build_path(x):
+ return PlatformGetPathFromGypPath(self, inputs_base_dir, x)
+ def expand_special(path, product_dir=None):
+ return "EXPAND_SPECIAL(%s, %s)" % (path, product_dir)
+ manifest_base_name = "MANIFEST_BASE_NAME"
+ output_name = "$ProductDir$/%s" % target_product_name
+ is_executable = target_type == 'executable'
+ build_dir = "BUILD_DIR"
+
+ ldflags,\
+ intermediate_manifest,\
+ manifest_files = self.msvs_settings.GetLdflags(
+ config_name,
+ gyp_to_build_path,
+ expand_special,
+ manifest_base_name,
+ output_name,
+ is_executable,
+ build_dir
+ )
+ options.extend(ldflags)
+
def ldflags(self, libraries):
return self.msvs_settings.AdjustLibraries(libraries)
@@ -464,13 +503,32 @@ def CompilerOptionsGetCommandLine(compiler_options):
return ' '.join(compiler_options)
-def PlatformLinkerOptionsForDLL(platform):
- return platform.linker_options_for_dll()
+def MakeLinkerOptions(platform):
+ return []
+
+def LinkerOptionsConfigure(platform, linker_options, inputs_base_dir, config_name, target_type, target_product_name):
+ if target_type != 'executable':
+ # NOTE(nil): TAG(platform) FASTbuild figures out the type of a
+ # node by the presence of certain linker flags. Therefore the
+ # right flags are necessary. Forgetting it shows up as a
+ # FASTbuild complaint during dependency checking.
+ linker_options.append(platform.linker_options_for_dll())
+ platform.push_specific_ldflags(linker_options, inputs_base_dir, config_name, target_type, target_product_name)
+
+
+def LinkerOptionsHasCommandLine(linker_options):
+ return len(linker_options) > 0
+
+
+def LinkerOptionsGetCommandLine(linker_options):
+ return ' '.join(linker_options)
+
+# TODO(nicolas): what about moving that to linker options function
def PlatformLDFlagsForLibraryDirs(platform, library_dirs):
return platform.ldflags_for_library_dirs(library_dirs)
-
+# TODO(nicolas): what about moving that to linker options function
def PlatformLDFlagsForLibraries(platform, libraries):
return platform.ldflags(libraries)
@@ -480,6 +538,18 @@ def PlatformGetToolsetPaths(platform, config_name):
def PlatformGetDLLExtension(platform):
return platform.get_dll_extension()
+def PlatformQuoteArgument(platform, argument):
+ return platform.quote_argument(argument)
+
+def PlatformGetPathFromGypPath(platform, inputs_base_dir, gyp_path):
+ fragments = gyp_path.split('/')
+ return os.path.abspath(
+ os.path.join(
+ inputs_base_dir,
+ os.path.join(*fragments)
+ )
+ )
+
# TODO(nil): evaluate the gyp variables correctly.
# see usage of GypPathToNinja in ninja.py for reference (and discussion around it)
# return bff targets produced to caller (useful to create aliases)
@@ -580,13 +650,7 @@ def PushTargetForConfig(
target_name = target_spec['target_name']
def gyp_path_to_os_path(gyp_path):
- fragments = gyp_path.split('/')
- return os.path.abspath(
- os.path.join(
- inputs_base_dir,
- os.path.join(*fragments)
- )
- )
+ return PlatformGetPathFromGypPath(platform, inputs_base_dir, gyp_path)
def using_config():
PushCommand(
@@ -632,7 +696,10 @@ def PushTargetForConfig(
# TODO(nicolas): look in toolset path too
executable = shell_which(action[0], PlatformGetToolsetPaths(platform, config_name))
PushAssignment(bff, '.ExecExecutable', executable)
- # TODO(nicolas): hardcoded platform quoting
+ # TODO(nicolas): correct this hardcoded platform quoting
+ # NOTE(nicolas): it's tricky. I tried using PlatformQuoteArgument however
+ # this would transform the %1 that fasbtuild uses. Maybe we should protect it somehow,
+ # quote the arguments, and replace it back to %1 as a last pass.
quoted_arguments = ['"%s"' % x for x in action[1:]]
PushAssignment(
bff, '.ExecArguments', ' '.join(quoted_arguments))
@@ -741,6 +808,7 @@ def PushTargetForConfig(
# the execution of the wrong actions at the wrong time
# when triggering the main target alias
bff_action_target_names.append(bff_action_name)
+ PushComment(bff, 'End Rule %s' % rule_name)
sources_by_compiler_type = defaultdict(list)
for gyp_source in target_spec.get('sources', []):
@@ -843,40 +911,48 @@ def PushTargetForConfig(
# Generate link targets:
bff_product_target_name = None
bff_executable_libraries = []
+ target_type = target_spec['type']
if (
- target_spec['type'] == 'executable' or
- target_spec['type'] == 'shared_library'
+ target_type == 'executable' or
+ target_type == 'shared_library' or
+ target_type == 'loadable_module'
):
# TODO(nicolas): simplify
linker_target_name = '%s-Exe' % bff_target_name
# TODO(nicolas): TAG(platform) this depends on the target platform
linker_product_suffix = dict(
executable='.exe',
- shared_library=PlatformGetDLLExtension(platform)
- )[target_spec['type']]
+ shared_library=PlatformGetDLLExtension(platform),
+ loadable_module=PlatformGetDLLExtension(platform),
+ )[target_type]
linker_product = "%s%s" % (
target_spec['product_name'], linker_product_suffix)
linker_function = dict(
executable='Executable',
shared_library='DLL',
- )[target_spec['type']]
+ loadable_module='DLL',
+ )[target_type]
with BFFBlock(bff, BFFFunctionBegin(linker_function, [linker_target_name])):
using_config()
+
PushAssignment(
bff, '.LinkerOutput', '$ProductDir$/%s' % linker_product
)
- if linker_function == 'DLL':
- # NOTE(nil): TAG(platform) the right flags are
- # necessary. DLL nodes in fbuild don't actually do
- # anything special with linker flags, but forgetting
- # them will lead FBuild to consider the node an
- # executable rather than a DLL, and it will then
- # complain during dependency checking.
+ linker_options = MakeLinkerOptions(platform)
+ LinkerOptionsConfigure(
+ platform,
+ linker_options,
+ inputs_base_dir,
+ config_name,
+ target_type,
+ target_spec['product_name']
+ )
+ if LinkerOptionsHasCommandLine(linker_options):
PushCommand(
bff,
BFFConcatenation(
'.LinkerOptions',
- ' %s' % PlatformLinkerOptionsForDLL(platform)
+ ' ' + LinkerOptionsGetCommandLine(linker_options)
)
)
@@ -907,7 +983,7 @@ def PushTargetForConfig(
bff_all_object_list_names
)
bff_product_target_name = linker_target_name
- elif target_spec['type'] == 'static_library':
+ elif target_type == 'static_library':
library_name = '%s-Lib' % bff_target_name
with BFFBlock(bff, BFFFunctionBegin('Library', [library_name])):
using_config()
@@ -927,12 +1003,15 @@ def PushTargetForConfig(
'$ProductDir$/lib/%s' % target_name)
bff_product_target_name = library_name
- elif target_spec['type'] == 'none':
+ elif target_type == 'none':
# Nothing compilable there
pass
else:
PushComment(
- bff, "don't know how to generate target %s" % bff_target_name)
+ bff, "don't know how to generate target %s (type: %s)" % (
+ bff_target_name, target_type
+ )
+ )
subtarget_names = []
subtarget_names += bff_action_target_names
--
2.7.0.windows.1
From 83ce56b175885544d99973b7a620112f609403d9 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Mon, 20 Jun 2016 17:59:40 +0200
Subject: [PATCH 04/19] TODOs and one fix
---
pylib/gyp/generator/fastbuild.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index c64f81d..a6e1595 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -6,6 +6,18 @@
#
# TODO(nicolas): $(IntDir) MSVS macro to be supported. (Or not?)
# TODO(nicolas): autopep8, add double spaces after top level functions
+#
+# TODO(nicolas): the way the generator creates alias nodes injects
+# some unwanted dependencies onto the other dependents:
+#
+# If executable A depends on a dynamic library B. Should the node
+# alias for A also bring B in, this sounds dubious. It is even more so
+# when the dependency (say C) is not even linkable, such as a
+# generator dependency.
+#
+# Try removing the Aliases entirely. Would the generated bff file work
+# at all without them. And if not, why not?
+#
import copy
import gyp
@@ -356,6 +368,7 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
return arg # No quoting necessary.
return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'"
+
class MacPlatform:
def __init__(self):
@@ -571,7 +584,7 @@ def PushTargetSpecs(
if not inputs_base_dir:
inputs_base_dir = '.'
- linkable_types = set(['static_library', 'shared_library'])
+ linkable_types = set(['static_library', 'shared_library', 'loadable_module'])
all_bff_target_names = []
linkable_dependencies = []
other_dependencies = []
@@ -810,6 +823,7 @@ def PushTargetForConfig(
bff_action_target_names.append(bff_action_name)
PushComment(bff, 'End Rule %s' % rule_name)
+
sources_by_compiler_type = defaultdict(list)
for gyp_source in target_spec.get('sources', []):
compiler_type = compiler_type_for_source(gyp_source)
--
2.7.0.windows.1
From 6f9c26546ce6c298189f7568d70b0824ea2f6831 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Mon, 11 Jul 2016 11:42:40 +0200
Subject: [PATCH 05/19] Prepend shell interpreter when command is composite
---
pylib/gyp/generator/fastbuild.py | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index a6e1595..4fe1397 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -441,6 +441,12 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def push_include_dir(self, options, include_dir):
options.append(QuoteShellArgument('/I"%s"' % include_dir))
+ def shell_executable(self):
+ return "cmd.exe"
+
+ def shell_cli_arguments(self, command_string):
+ return ["/C", '"%s"' % command_string]
+
def quote_argument(self, argument):
return QuoteShellArgument(argument)
@@ -545,6 +551,12 @@ def PlatformLDFlagsForLibraryDirs(platform, library_dirs):
def PlatformLDFlagsForLibraries(platform, libraries):
return platform.ldflags(libraries)
+def PlatformGetShellInterpreter(platform):
+ return platform.shell_executable()
+
+def PlatformShellInterpreterCommandLineArgumentsFor(platform, command):
+ return platform.shell_cli_arguments(command)
+
def PlatformGetToolsetPaths(platform, config_name):
return [platform.get_path(config_name)]
@@ -707,13 +719,19 @@ def PushTargetForConfig(
action = action_spec['action']
# TODO(nicolas): FASTbuild wants the actual executable location
# TODO(nicolas): look in toolset path too
- executable = shell_which(action[0], PlatformGetToolsetPaths(platform, config_name))
+ if len(action) == 1:
+ executable = PlatformGetShellInterpreter(platform)
+ quoted_arguments = PlatformShellInterpreterCommandLineArgumentsFor(platform, action[0])
+ else:
+ executable = action[0]
+ # TODO(nicolas): correct this hardcoded platform quoting
+ # NOTE(nicolas): it's tricky. I tried using PlatformQuoteArgument however
+ # this would transform the %1 that fasbtuild uses. Maybe we should protect it somehow,
+ # quote the arguments, and replace it back to %1 as a last pass.
+ quoted_arguments = ['"%s"' % x for x in action[1:]]
+
+ executable = shell_which(executable, PlatformGetToolsetPaths(platform, config_name))
PushAssignment(bff, '.ExecExecutable', executable)
- # TODO(nicolas): correct this hardcoded platform quoting
- # NOTE(nicolas): it's tricky. I tried using PlatformQuoteArgument however
- # this would transform the %1 that fasbtuild uses. Maybe we should protect it somehow,
- # quote the arguments, and replace it back to %1 as a last pass.
- quoted_arguments = ['"%s"' % x for x in action[1:]]
PushAssignment(
bff, '.ExecArguments', ' '.join(quoted_arguments))
PushAssignment(
--
2.7.0.windows.1
From 291d2e58d2d2e4948b6e64db5999dea1d83f3534 Mon Sep 17 00:00:00 2001
From: nil <nil@ableton.com>
Date: Tue, 12 Jul 2016 13:19:45 +0200
Subject: [PATCH 06/19] Expand MSVS macros + fix quoting of arguments
---
pylib/gyp/generator/fastbuild.py | 43 ++++++++++++++++++++++++++++++++++------
1 file changed, 37 insertions(+), 6 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 4fe1397..2de57ce 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -4,7 +4,9 @@
# This module generates `.bff` files for consumption with the FASTbuild
# URL(http://fastbuild.org) opensource build system developed by Franta Fulin.
#
-# TODO(nicolas): $(IntDir) MSVS macro to be supported. (Or not?)
+# TODO(nicolas): Where should PlatformExpandMacros actually be called?
+# Every gyp input variable?
+#
# TODO(nicolas): autopep8, add double spaces after top level functions
#
# TODO(nicolas): the way the generator creates alias nodes injects
@@ -422,6 +424,9 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def __init__(self):
self.msvs_settings = gyp.msvs_emulation.MsvsSettings(target_spec, generator_flags)
+ def expand_macros(self, argument, config_name):
+ return self.msvs_settings.ConvertVSMacros(argument, config=config_name)
+
def get_dll_extension(self):
return '.dll'
@@ -439,7 +444,8 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
options.append(QuoteShellArgument(argument))
def push_include_dir(self, options, include_dir):
- options.append(QuoteShellArgument('/I"%s"' % include_dir))
+ options.append('/I')
+ options.append(QuoteShellArgument(include_dir))
def shell_executable(self):
return "cmd.exe"
@@ -516,7 +522,11 @@ def CompilerOptionsHasCommandLine(compiler_options):
def CompilerOptionsConfigure(platform, compiler_options, config_name):
# TODO(nicolas): take language into account
platform.push_specific_cflags(compiler_options, config_name)
-
+ new_compiler_options = [
+ PlatformExpandMacros(platform, x, config_name)
+ for x in compiler_options
+ ]
+ compiler_options[:] = new_compiler_options
def CompilerOptionsGetCommandLine(compiler_options):
return ' '.join(compiler_options)
@@ -566,6 +576,17 @@ def PlatformGetDLLExtension(platform):
def PlatformQuoteArgument(platform, argument):
return platform.quote_argument(argument)
+def PlatformExpandMacros(platform, argument, config_name):
+ new_argument = platform.expand_macros(argument, config_name)
+ # ExpandSpecial in ninja.py
+ INTERMEDIATE_DIR = '$!INTERMEDIATE_DIR'
+ if INTERMEDIATE_DIR in new_argument:
+ new_argument = new_argument.replace(INTERMEDIATE_DIR, generator_default_variables['INTERMEDIATE_DIR'])
+ PRODUCT_DIR = '$!PRODUCT_DIR'
+ if PRODUCT_DIR in new_argument:
+ new_argument = new_argument.replace(PRODUCT_DIR, generator_default_variables['PRODUCT_DIR'])
+ return new_argument
+
def PlatformGetPathFromGypPath(platform, inputs_base_dir, gyp_path):
fragments = gyp_path.split('/')
return os.path.abspath(
@@ -675,6 +696,11 @@ def PushTargetForConfig(
target_name = target_spec['target_name']
def gyp_path_to_os_path(gyp_path):
+ gyp_path = PlatformExpandMacros(
+ platform,
+ gyp_path,
+ config_name
+ )
return PlatformGetPathFromGypPath(platform, inputs_base_dir, gyp_path)
def using_config():
@@ -721,14 +747,17 @@ def PushTargetForConfig(
# TODO(nicolas): look in toolset path too
if len(action) == 1:
executable = PlatformGetShellInterpreter(platform)
- quoted_arguments = PlatformShellInterpreterCommandLineArgumentsFor(platform, action[0])
+ quoted_arguments = PlatformShellInterpreterCommandLineArgumentsFor(
+ platform,
+ PlatformExpandMacros(platform, action[0], config_name)
+ )
else:
executable = action[0]
# TODO(nicolas): correct this hardcoded platform quoting
# NOTE(nicolas): it's tricky. I tried using PlatformQuoteArgument however
# this would transform the %1 that fasbtuild uses. Maybe we should protect it somehow,
# quote the arguments, and replace it back to %1 as a last pass.
- quoted_arguments = ['"%s"' % x for x in action[1:]]
+ quoted_arguments = ['"%s"' % PlatformExpandMacros(platform, x, config_name) for x in action[1:]]
executable = shell_which(executable, PlatformGetToolsetPaths(platform, config_name))
PushAssignment(bff, '.ExecExecutable', executable)
@@ -741,7 +770,9 @@ def PushTargetForConfig(
action_spec.get(
'inputs',
[]))
- output = gyp_path_to_os_path(action_spec['outputs'][0])
+ output = gyp_path_to_os_path(
+ action_spec['outputs'][0],
+ )
PushAssignment(bff, '.ExecOutput', output)
def compiler_type_for_source(source):
--
2.7.0.windows.1
From ebf07a0a36835ee6e70db7a0fb680a02806ebb34 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Wed, 13 Jul 2016 14:15:22 +0200
Subject: [PATCH 07/19] Factorize some ninja code for use in fastbuild
generator
---
pylib/gyp/generator/common.py | 82 +++++++++++++++++++++++++++++++++++++++++++
pylib/gyp/generator/ninja.py | 76 +++------------------------------------
2 files changed, 86 insertions(+), 72 deletions(-)
create mode 100644 pylib/gyp/generator/common.py
diff --git a/pylib/gyp/generator/common.py b/pylib/gyp/generator/common.py
new file mode 100644
index 0000000..ba908cb
--- /dev/null
+++ b/pylib/gyp/generator/common.py
@@ -0,0 +1,82 @@
+import copy
+import gyp
+import gyp.common
+
+def SetDefaultVariables(default_variables, params):
+ global generator_additional_non_configuration_keys
+ global generator_additional_path_sections
+ flavor = gyp.common.GetFlavor(params)
+ if flavor == 'mac':
+ default_variables.setdefault('OS', 'mac')
+ default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
+ default_variables.setdefault('SHARED_LIB_DIR',
+ default_variables['PRODUCT_DIR'])
+ default_variables.setdefault('LIB_DIR',
+ default_variables['PRODUCT_DIR'])
+ elif flavor == 'win':
+ exts = gyp.MSVSUtil.TARGET_TYPE_EXT
+ default_variables.setdefault('OS', 'win')
+ default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable']
+ default_variables['STATIC_LIB_PREFIX'] = ''
+ default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library']
+ default_variables['SHARED_LIB_PREFIX'] = ''
+ default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library']
+ # Copy additional generator configuration data from VS, which is shared
+ # by the Windows Ninja generator.
+ import gyp.generator.msvs as msvs_generator
+ gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
+ else:
+ operating_system = flavor
+ if flavor == 'android':
+ operating_system = 'linux' # Keep this legacy behavior for now.
+ default_variables.setdefault('OS', operating_system)
+ default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
+ default_variables.setdefault('SHARED_LIB_DIR',
+ os.path.join(default_variables['PRODUCT_DIR'], 'lib'))
+ default_variables.setdefault('LIB_DIR',
+ os.path.join(default_variables['PRODUCT_DIR'], 'obj'))
+
+def ComputeOutputFileName(spec, type, default_variables):
+ """Compute the filename of the final output for the current target."""
+
+ # Compute filename prefix: the product prefix, or a default for
+ # the product type.
+ DEFAULT_PREFIX = {
+ 'loadable_module': default_variables['SHARED_LIB_PREFIX'],
+ 'shared_library': default_variables['SHARED_LIB_PREFIX'],
+ 'static_library': default_variables['STATIC_LIB_PREFIX'],
+ 'executable': default_variables['EXECUTABLE_PREFIX'],
+ }
+ prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
+
+ # Compute filename extension: the product extension, or a default
+ # for the product type.
+ DEFAULT_EXTENSION = {
+ 'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
+ 'shared_library': default_variables['SHARED_LIB_SUFFIX'],
+ 'static_library': default_variables['STATIC_LIB_SUFFIX'],
+ 'executable': default_variables['EXECUTABLE_SUFFIX'],
+ }
+ extension = spec.get('product_extension')
+ if extension:
+ extension = '.' + extension
+ else:
+ extension = DEFAULT_EXTENSION.get(type, '')
+
+ if 'product_name' in spec:
+ # If we were given an explicit name, use that.
+ target = spec['product_name']
+ else:
+ # Otherwise, derive a name from the target name.
+ target = spec['target_name']
+ if prefix == 'lib':
+ # Snip out an extra 'lib' from libs if appropriate.
+ target = StripPrefix(target, 'lib')
+
+ if type in ('static_library', 'loadable_module', 'shared_library',
+ 'executable'):
+ return '%s%s%s' % (prefix, target, extension)
+ elif type == 'none':
+ return '%s.stamp' % target
+ else:
+ raise Exception('Unhandled output type %s' % type)
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index 9cfc706..de213e1 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -14,6 +14,7 @@ import subprocess
import sys
import gyp
import gyp.common
+import gyp.generator.common as gcommon
from gyp.common import OrderedSet
import gyp.msvs_emulation
import gyp.MSVSUtil as MSVSUtil
@@ -1475,54 +1476,11 @@ class NinjaWriter(object):
os.path.join(path, self.xcode_settings.GetWrapperName()))
def ComputeOutputFileName(self, spec, type=None):
- """Compute the filename of the final output for the current target."""
if not type:
- type = spec['type']
-
+ type = spec['type']
default_variables = copy.copy(generator_default_variables)
CalculateVariables(default_variables, {'flavor': self.flavor})
-
- # Compute filename prefix: the product prefix, or a default for
- # the product type.
- DEFAULT_PREFIX = {
- 'loadable_module': default_variables['SHARED_LIB_PREFIX'],
- 'shared_library': default_variables['SHARED_LIB_PREFIX'],
- 'static_library': default_variables['STATIC_LIB_PREFIX'],
- 'executable': default_variables['EXECUTABLE_PREFIX'],
- }
- prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
-
- # Compute filename extension: the product extension, or a default
- # for the product type.
- DEFAULT_EXTENSION = {
- 'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
- 'shared_library': default_variables['SHARED_LIB_SUFFIX'],
- 'static_library': default_variables['STATIC_LIB_SUFFIX'],
- 'executable': default_variables['EXECUTABLE_SUFFIX'],
- }
- extension = spec.get('product_extension')
- if extension:
- extension = '.' + extension
- else:
- extension = DEFAULT_EXTENSION.get(type, '')
-
- if 'product_name' in spec:
- # If we were given an explicit name, use that.
- target = spec['product_name']
- else:
- # Otherwise, derive a name from the target name.
- target = spec['target_name']
- if prefix == 'lib':
- # Snip out an extra 'lib' from libs if appropriate.
- target = StripPrefix(target, 'lib')
-
- if type in ('static_library', 'loadable_module', 'shared_library',
- 'executable'):
- return '%s%s%s' % (prefix, target, extension)
- elif type == 'none':
- return '%s.stamp' % target
- else:
- raise Exception('Unhandled output type %s' % type)
+ return gcommon.ComputeOutputFileName(spec, type, default_variables)
def ComputeOutput(self, spec, arch=None):
"""Compute the path for the final output of the spec."""
@@ -1648,13 +1606,6 @@ def CalculateVariables(default_variables, params):
global generator_additional_path_sections
flavor = gyp.common.GetFlavor(params)
if flavor == 'mac':
- default_variables.setdefault('OS', 'mac')
- default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
- default_variables.setdefault('SHARED_LIB_DIR',
- generator_default_variables['PRODUCT_DIR'])
- default_variables.setdefault('LIB_DIR',
- generator_default_variables['PRODUCT_DIR'])
-
# Copy additional generator configuration data from Xcode, which is shared
# by the Mac Ninja generator.
import gyp.generator.xcode as xcode_generator
@@ -1666,14 +1617,6 @@ def CalculateVariables(default_variables, params):
generator_extra_sources_for_rules = getattr(xcode_generator,
'generator_extra_sources_for_rules', [])
elif flavor == 'win':
- exts = gyp.MSVSUtil.TARGET_TYPE_EXT
- default_variables.setdefault('OS', 'win')
- default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable']
- default_variables['STATIC_LIB_PREFIX'] = ''
- default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library']
- default_variables['SHARED_LIB_PREFIX'] = ''
- default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library']
-
# Copy additional generator configuration data from VS, which is shared
# by the Windows Ninja generator.
import gyp.generator.msvs as msvs_generator
@@ -1681,18 +1624,7 @@ def CalculateVariables(default_variables, params):
'generator_additional_non_configuration_keys', [])
generator_additional_path_sections = getattr(msvs_generator,
'generator_additional_path_sections', [])
-
- gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
- else:
- operating_system = flavor
- if flavor == 'android':
- operating_system = 'linux' # Keep this legacy behavior for now.
- default_variables.setdefault('OS', operating_system)
- default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
- default_variables.setdefault('SHARED_LIB_DIR',
- os.path.join('$!PRODUCT_DIR', 'lib'))
- default_variables.setdefault('LIB_DIR',
- os.path.join('$!PRODUCT_DIR', 'obj'))
+ gcommon.SetDefaultVariables(default_variables, params);
def ComputeOutputDir(params):
"""Returns the path from the toplevel_dir to the build output directory."""
--
2.7.0.windows.1
From 01eef231489203c8fa40a011e3b0a4eca0a6488b Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Wed, 13 Jul 2016 14:15:38 +0200
Subject: [PATCH 08/19] Handle empty target list
---
pylib/gyp/generator/fastbuild.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 2de57ce..dd99bb8 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -145,6 +145,8 @@ def GenerateOutput(target_list, target_dicts, data, params):
return bff_basename
def get_config_names(target_list, target_dicts):
+ if not target_list:
+ return []
first_target = target_list[0]
return target_dicts[first_target]['configurations'].keys()
--
2.7.0.windows.1
From 314d1d17bdd58f4e8eaea42b217218e338321b2c Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Wed, 13 Jul 2016 14:15:50 +0200
Subject: [PATCH 09/19] Pick an arbitrary config name for now
---
pylib/gyp/generator/fastbuild.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index dd99bb8..fa2cd31 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -166,7 +166,7 @@ For flavor: %s
# TODO(nicolas): remove, this is needed for now because I don't know how to
# avoid duplicating actions which lead to the same output across
# configs
- config_names = [y for y in config_names if y == 'Debug']
+ config_names = config_names[0:1] if config_names else []
generator_flags = params.get('generator_flags', {})
toolset = MakeToolset(flavor, generator_flags)
--
2.7.0.windows.1
From 554934112743a17ec247724fc4658c77b9f337c5 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Wed, 13 Jul 2016 14:16:21 +0200
Subject: [PATCH 10/19] Compute accurate product filename
We reuse the ninja logic
---
pylib/gyp/generator/fastbuild.py | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index fa2cd31..17bd531 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -28,6 +28,7 @@ import gyp.msvs_emulation
import gyp.xcode_emulation
import os
import re
+import gyp.generator.common as gcommon
from collections import defaultdict
from itertools import product
@@ -109,6 +110,7 @@ def CalculateVariables(default_variables, params):
# fastbuild supports windows, mac and linux
flavor = gyp.common.GetFlavor(params)
default_variables.setdefault('OS', flavor)
+ gcommon.SetDefaultVariables(default_variables, {'flavor': flavor})
# TODO(nicolas): clean this list up
generator_default_variables = {
@@ -192,8 +194,12 @@ For flavor: %s
)
inputs_base_dir = os.path.dirname(target_build_file)
platform = MakePlatform(flavor, generator_flags, toolset, target_spec)
+ default_variables = copy.copy(generator_default_variables)
+ gcommon.SetDefaultVariables(default_variables, {'flavor': flavor})
+ target_output_filename = gcommon.ComputeOutputFileName(target_spec, target_spec['type'], default_variables)
PushTargetSpecs(
bff,
+ target_output_filename,
platform,
inputs_base_dir,
qualified_target,
@@ -461,7 +467,7 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def linker_options_for_dll(self):
return '/DLL'
- def push_specific_ldflags(self, options, inputs_base_dir, config_name, target_type, target_product_name):
+ def push_specific_ldflags(self, options, inputs_base_dir, config_name, target_type, target_output_filename):
# TODO(nicolas): remove those hardcoded sentinels and replace with correct
# stuff (I need to figure out what there are for)
def gyp_to_build_path(x):
@@ -469,7 +475,7 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
def expand_special(path, product_dir=None):
return "EXPAND_SPECIAL(%s, %s)" % (path, product_dir)
manifest_base_name = "MANIFEST_BASE_NAME"
- output_name = "$ProductDir$/%s" % target_product_name
+ output_name = "$ProductDir$/%s" % target_output_filename
is_executable = target_type == 'executable'
build_dir = "BUILD_DIR"
@@ -537,14 +543,14 @@ def CompilerOptionsGetCommandLine(compiler_options):
def MakeLinkerOptions(platform):
return []
-def LinkerOptionsConfigure(platform, linker_options, inputs_base_dir, config_name, target_type, target_product_name):
+def LinkerOptionsConfigure(platform, linker_options, inputs_base_dir, config_name, target_type, target_output_filename):
if target_type != 'executable':
# NOTE(nil): TAG(platform) FASTbuild figures out the type of a
# node by the presence of certain linker flags. Therefore the
# right flags are necessary. Forgetting it shows up as a
# FASTbuild complaint during dependency checking.
linker_options.append(platform.linker_options_for_dll())
- platform.push_specific_ldflags(linker_options, inputs_base_dir, config_name, target_type, target_product_name)
+ platform.push_specific_ldflags(linker_options, inputs_base_dir, config_name, target_type, target_output_filename)
def LinkerOptionsHasCommandLine(linker_options):
@@ -605,6 +611,7 @@ def PlatformGetPathFromGypPath(platform, inputs_base_dir, gyp_path):
def PushTargetSpecs(
bff,
+ output_filename,
platform,
inputs_base_dir,
qualified_target,
@@ -663,6 +670,7 @@ def PushTargetSpecs(
if PushTargetForConfig(
bff,
+ output_filename,
platform,
inputs_base_dir,
target_spec,
@@ -687,6 +695,7 @@ def PushTargetSpecs(
def PushTargetForConfig(
bff,
+ output_filename,
platform,
inputs_base_dir,
target_spec,
@@ -990,8 +999,8 @@ def PushTargetForConfig(
shared_library=PlatformGetDLLExtension(platform),
loadable_module=PlatformGetDLLExtension(platform),
)[target_type]
- linker_product = "%s%s" % (
- target_spec['product_name'], linker_product_suffix)
+ assert output_filename.endswith(linker_product_suffix)
+ linker_product = output_filename
linker_function = dict(
executable='Executable',
shared_library='DLL',
@@ -1010,7 +1019,7 @@ def PushTargetForConfig(
inputs_base_dir,
config_name,
target_type,
- target_spec['product_name']
+ linker_product
)
if LinkerOptionsHasCommandLine(linker_options):
PushCommand(
@@ -1053,13 +1062,11 @@ def PushTargetForConfig(
with BFFBlock(bff, BFFFunctionBegin('Library', [library_name])):
using_config()
using_compiler(target_toolset, config_name, CompilerType.CXX)
-
- # TODO(nil): change, this shouldnt be hardcoded
PushAssignment(
bff,
'.LibrarianOutput',
- '$ProductDir$/Lib%s.lib' %
- target_spec['product_name'])
+ '$ProductDir$/%s' % output_filename
+ )
PushAssignment(bff,
'.LibrarianAdditionalInputs',
bff_all_object_list_names)
--
2.7.0.windows.1
From 069592f6d475d8f213d8b74f5c8584c6e65b60cf Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Thu, 14 Jul 2016 09:13:15 +0200
Subject: [PATCH 11/19] Find clearer name
---
pylib/gyp/generator/common.py | 4 +---
pylib/gyp/generator/fastbuild.py | 4 ++--
pylib/gyp/generator/ninja.py | 2 +-
3 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/pylib/gyp/generator/common.py b/pylib/gyp/generator/common.py
index ba908cb..92aa8b9 100644
--- a/pylib/gyp/generator/common.py
+++ b/pylib/gyp/generator/common.py
@@ -2,9 +2,7 @@ import copy
import gyp
import gyp.common
-def SetDefaultVariables(default_variables, params):
- global generator_additional_non_configuration_keys
- global generator_additional_path_sections
+def SetPlatformDefaultVariables(default_variables, params):
flavor = gyp.common.GetFlavor(params)
if flavor == 'mac':
default_variables.setdefault('OS', 'mac')
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 17bd531..793424b 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -110,7 +110,7 @@ def CalculateVariables(default_variables, params):
# fastbuild supports windows, mac and linux
flavor = gyp.common.GetFlavor(params)
default_variables.setdefault('OS', flavor)
- gcommon.SetDefaultVariables(default_variables, {'flavor': flavor})
+ gcommon.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
# TODO(nicolas): clean this list up
generator_default_variables = {
@@ -195,7 +195,7 @@ For flavor: %s
inputs_base_dir = os.path.dirname(target_build_file)
platform = MakePlatform(flavor, generator_flags, toolset, target_spec)
default_variables = copy.copy(generator_default_variables)
- gcommon.SetDefaultVariables(default_variables, {'flavor': flavor})
+ gcommon.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
target_output_filename = gcommon.ComputeOutputFileName(target_spec, target_spec['type'], default_variables)
PushTargetSpecs(
bff,
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index de213e1..425b238 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -1624,7 +1624,7 @@ def CalculateVariables(default_variables, params):
'generator_additional_non_configuration_keys', [])
generator_additional_path_sections = getattr(msvs_generator,
'generator_additional_path_sections', [])
- gcommon.SetDefaultVariables(default_variables, params);
+ gcommon.SetPlatformDefaultVariables(default_variables, params);
def ComputeOutputDir(params):
"""Returns the path from the toplevel_dir to the build output directory."""
--
2.7.0.windows.1
From e47375e454fead3b9392b939baf4971f1ea6fc70 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Thu, 14 Jul 2016 09:17:50 +0200
Subject: [PATCH 12/19] Move utils to generator_utils
Keep library code away from the generator list
---
pylib/gyp/generator/common.py | 80 ----------------------------------------
pylib/gyp/generator/fastbuild.py | 8 ++--
pylib/gyp/generator/ninja.py | 6 +--
pylib/gyp/generator_utils.py | 80 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 87 insertions(+), 87 deletions(-)
delete mode 100644 pylib/gyp/generator/common.py
create mode 100644 pylib/gyp/generator_utils.py
diff --git a/pylib/gyp/generator/common.py b/pylib/gyp/generator/common.py
deleted file mode 100644
index 92aa8b9..0000000
--- a/pylib/gyp/generator/common.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import copy
-import gyp
-import gyp.common
-
-def SetPlatformDefaultVariables(default_variables, params):
- flavor = gyp.common.GetFlavor(params)
- if flavor == 'mac':
- default_variables.setdefault('OS', 'mac')
- default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
- default_variables.setdefault('SHARED_LIB_DIR',
- default_variables['PRODUCT_DIR'])
- default_variables.setdefault('LIB_DIR',
- default_variables['PRODUCT_DIR'])
- elif flavor == 'win':
- exts = gyp.MSVSUtil.TARGET_TYPE_EXT
- default_variables.setdefault('OS', 'win')
- default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable']
- default_variables['STATIC_LIB_PREFIX'] = ''
- default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library']
- default_variables['SHARED_LIB_PREFIX'] = ''
- default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library']
- # Copy additional generator configuration data from VS, which is shared
- # by the Windows Ninja generator.
- import gyp.generator.msvs as msvs_generator
- gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
- else:
- operating_system = flavor
- if flavor == 'android':
- operating_system = 'linux' # Keep this legacy behavior for now.
- default_variables.setdefault('OS', operating_system)
- default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
- default_variables.setdefault('SHARED_LIB_DIR',
- os.path.join(default_variables['PRODUCT_DIR'], 'lib'))
- default_variables.setdefault('LIB_DIR',
- os.path.join(default_variables['PRODUCT_DIR'], 'obj'))
-
-def ComputeOutputFileName(spec, type, default_variables):
- """Compute the filename of the final output for the current target."""
-
- # Compute filename prefix: the product prefix, or a default for
- # the product type.
- DEFAULT_PREFIX = {
- 'loadable_module': default_variables['SHARED_LIB_PREFIX'],
- 'shared_library': default_variables['SHARED_LIB_PREFIX'],
- 'static_library': default_variables['STATIC_LIB_PREFIX'],
- 'executable': default_variables['EXECUTABLE_PREFIX'],
- }
- prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
-
- # Compute filename extension: the product extension, or a default
- # for the product type.
- DEFAULT_EXTENSION = {
- 'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
- 'shared_library': default_variables['SHARED_LIB_SUFFIX'],
- 'static_library': default_variables['STATIC_LIB_SUFFIX'],
- 'executable': default_variables['EXECUTABLE_SUFFIX'],
- }
- extension = spec.get('product_extension')
- if extension:
- extension = '.' + extension
- else:
- extension = DEFAULT_EXTENSION.get(type, '')
-
- if 'product_name' in spec:
- # If we were given an explicit name, use that.
- target = spec['product_name']
- else:
- # Otherwise, derive a name from the target name.
- target = spec['target_name']
- if prefix == 'lib':
- # Snip out an extra 'lib' from libs if appropriate.
- target = StripPrefix(target, 'lib')
-
- if type in ('static_library', 'loadable_module', 'shared_library',
- 'executable'):
- return '%s%s%s' % (prefix, target, extension)
- elif type == 'none':
- return '%s.stamp' % target
- else:
- raise Exception('Unhandled output type %s' % type)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 793424b..a6b2ffb 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -28,7 +28,7 @@ import gyp.msvs_emulation
import gyp.xcode_emulation
import os
import re
-import gyp.generator.common as gcommon
+import gyp.generator_utils as gutils
from collections import defaultdict
from itertools import product
@@ -110,7 +110,7 @@ def CalculateVariables(default_variables, params):
# fastbuild supports windows, mac and linux
flavor = gyp.common.GetFlavor(params)
default_variables.setdefault('OS', flavor)
- gcommon.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
+ gutils.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
# TODO(nicolas): clean this list up
generator_default_variables = {
@@ -195,8 +195,8 @@ For flavor: %s
inputs_base_dir = os.path.dirname(target_build_file)
platform = MakePlatform(flavor, generator_flags, toolset, target_spec)
default_variables = copy.copy(generator_default_variables)
- gcommon.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
- target_output_filename = gcommon.ComputeOutputFileName(target_spec, target_spec['type'], default_variables)
+ gutils.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
+ target_output_filename = gutils.ComputeOutputFileName(target_spec, target_spec['type'], default_variables)
PushTargetSpecs(
bff,
target_output_filename,
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index 425b238..15a75ba 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -14,7 +14,7 @@ import subprocess
import sys
import gyp
import gyp.common
-import gyp.generator.common as gcommon
+import gyp.generator_utils as gutils
from gyp.common import OrderedSet
import gyp.msvs_emulation
import gyp.MSVSUtil as MSVSUtil
@@ -1480,7 +1480,7 @@ class NinjaWriter(object):
type = spec['type']
default_variables = copy.copy(generator_default_variables)
CalculateVariables(default_variables, {'flavor': self.flavor})
- return gcommon.ComputeOutputFileName(spec, type, default_variables)
+ return gutils.ComputeOutputFileName(spec, type, default_variables)
def ComputeOutput(self, spec, arch=None):
"""Compute the path for the final output of the spec."""
@@ -1624,7 +1624,7 @@ def CalculateVariables(default_variables, params):
'generator_additional_non_configuration_keys', [])
generator_additional_path_sections = getattr(msvs_generator,
'generator_additional_path_sections', [])
- gcommon.SetPlatformDefaultVariables(default_variables, params);
+ gutils.SetPlatformDefaultVariables(default_variables, params);
def ComputeOutputDir(params):
"""Returns the path from the toplevel_dir to the build output directory."""
diff --git a/pylib/gyp/generator_utils.py b/pylib/gyp/generator_utils.py
new file mode 100644
index 0000000..92aa8b9
--- /dev/null
+++ b/pylib/gyp/generator_utils.py
@@ -0,0 +1,80 @@
+import copy
+import gyp
+import gyp.common
+
+def SetPlatformDefaultVariables(default_variables, params):
+ flavor = gyp.common.GetFlavor(params)
+ if flavor == 'mac':
+ default_variables.setdefault('OS', 'mac')
+ default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
+ default_variables.setdefault('SHARED_LIB_DIR',
+ default_variables['PRODUCT_DIR'])
+ default_variables.setdefault('LIB_DIR',
+ default_variables['PRODUCT_DIR'])
+ elif flavor == 'win':
+ exts = gyp.MSVSUtil.TARGET_TYPE_EXT
+ default_variables.setdefault('OS', 'win')
+ default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable']
+ default_variables['STATIC_LIB_PREFIX'] = ''
+ default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library']
+ default_variables['SHARED_LIB_PREFIX'] = ''
+ default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library']
+ # Copy additional generator configuration data from VS, which is shared
+ # by the Windows Ninja generator.
+ import gyp.generator.msvs as msvs_generator
+ gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
+ else:
+ operating_system = flavor
+ if flavor == 'android':
+ operating_system = 'linux' # Keep this legacy behavior for now.
+ default_variables.setdefault('OS', operating_system)
+ default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
+ default_variables.setdefault('SHARED_LIB_DIR',
+ os.path.join(default_variables['PRODUCT_DIR'], 'lib'))
+ default_variables.setdefault('LIB_DIR',
+ os.path.join(default_variables['PRODUCT_DIR'], 'obj'))
+
+def ComputeOutputFileName(spec, type, default_variables):
+ """Compute the filename of the final output for the current target."""
+
+ # Compute filename prefix: the product prefix, or a default for
+ # the product type.
+ DEFAULT_PREFIX = {
+ 'loadable_module': default_variables['SHARED_LIB_PREFIX'],
+ 'shared_library': default_variables['SHARED_LIB_PREFIX'],
+ 'static_library': default_variables['STATIC_LIB_PREFIX'],
+ 'executable': default_variables['EXECUTABLE_PREFIX'],
+ }
+ prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
+
+ # Compute filename extension: the product extension, or a default
+ # for the product type.
+ DEFAULT_EXTENSION = {
+ 'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
+ 'shared_library': default_variables['SHARED_LIB_SUFFIX'],
+ 'static_library': default_variables['STATIC_LIB_SUFFIX'],
+ 'executable': default_variables['EXECUTABLE_SUFFIX'],
+ }
+ extension = spec.get('product_extension')
+ if extension:
+ extension = '.' + extension
+ else:
+ extension = DEFAULT_EXTENSION.get(type, '')
+
+ if 'product_name' in spec:
+ # If we were given an explicit name, use that.
+ target = spec['product_name']
+ else:
+ # Otherwise, derive a name from the target name.
+ target = spec['target_name']
+ if prefix == 'lib':
+ # Snip out an extra 'lib' from libs if appropriate.
+ target = StripPrefix(target, 'lib')
+
+ if type in ('static_library', 'loadable_module', 'shared_library',
+ 'executable'):
+ return '%s%s%s' % (prefix, target, extension)
+ elif type == 'none':
+ return '%s.stamp' % target
+ else:
+ raise Exception('Unhandled output type %s' % type)
--
2.7.0.windows.1
From 4b76adf9c3d40a97f02f4aa4a4a04cf217749b18 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Thu, 14 Jul 2016 10:04:09 +0200
Subject: [PATCH 13/19] Pass output directory for manifest generation
---
pylib/gyp/generator/fastbuild.py | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index a6b2ffb..793ed5d 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -174,12 +174,12 @@ For flavor: %s
toolset = MakeToolset(flavor, generator_flags)
for qualified_target in target_list:
- target_build_file, target_name, target_toolset = \
+ target_build_file, target_name, target_toolset_name = \
gyp.common.ParseQualifiedTarget(qualified_target)
- if target_toolset not in toolsets_with_bff_config:
- PushToolsetConfig(bff, target_toolset, config_names)
- toolsets_with_bff_config.add(target_toolset)
+ if target_toolset_name not in toolsets_with_bff_config:
+ PushToolsetConfig(bff, target_toolset_name, config_names)
+ toolsets_with_bff_config.add(target_toolset_name)
target_spec = target_dicts[qualified_target]
PushHeadingComment(bff, 'target: %s (type: %s)' %
@@ -193,7 +193,9 @@ For flavor: %s
target_spec
)
inputs_base_dir = os.path.dirname(target_build_file)
- platform = MakePlatform(flavor, generator_flags, toolset, target_spec)
+ platform = MakePlatform(
+ flavor, generator_flags, toolset, params['options'].toplevel_dir, target_spec
+ )
default_variables = copy.copy(generator_default_variables)
gutils.SetPlatformDefaultVariables(default_variables, {'flavor': flavor})
target_output_filename = gutils.ComputeOutputFileName(target_spec, target_spec['type'], default_variables)
@@ -205,7 +207,7 @@ For flavor: %s
qualified_target,
target_spec,
target_dicts,
- config_names,
+ config_names
)
BFFFinalize(bff)
BFFWriteToFile(bff, bff_path)
@@ -371,7 +373,8 @@ def MakeToolset(flavor, generator_flags):
return toolset
-def MakePlatform(flavor, generator_flags, toolset, target_spec):
+def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
+ outputs_base_dir = os.path.join('output', target_spec['toolset'])
if flavor == 'mac':
def QuoteShellArgument(arg):
# NOTE(nil): adapted from ninja.py
@@ -477,7 +480,6 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
manifest_base_name = "MANIFEST_BASE_NAME"
output_name = "$ProductDir$/%s" % target_output_filename
is_executable = target_type == 'executable'
- build_dir = "BUILD_DIR"
ldflags,\
intermediate_manifest,\
@@ -488,7 +490,7 @@ def MakePlatform(flavor, generator_flags, toolset, target_spec):
manifest_base_name,
output_name,
is_executable,
- build_dir
+ outputs_base_dir
)
options.extend(ldflags)
--
2.7.0.windows.1
From ca276fa5429f3b800c9f82ca34dbad583b0dc572 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Fri, 15 Jul 2016 09:35:27 +0200
Subject: [PATCH 14/19] Quote all tools command line options (fixes PDB w/
spaces)
---
pylib/gyp/generator/fastbuild.py | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 793ed5d..d251326 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -452,11 +452,11 @@ def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
argument = '/D%s' % flag
# NOTE(nil): cl.exe interprets # as =
argument.replace('#', '\\%03o' % ord('#'))
- options.append(QuoteShellArgument(argument))
+ options.append(argument)
def push_include_dir(self, options, include_dir):
options.append('/I')
- options.append(QuoteShellArgument(include_dir))
+ options.append(include_dir)
def shell_executable(self):
return "cmd.exe"
@@ -500,7 +500,7 @@ def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
def ldflags_for_library_dirs(self, library_dirs):
flags = []
for library_dir in library_dirs:
- flags += ['/LIBPATH:"%s"' % library_dir]
+ flags += ['/LIBPATH:%s' % library_dir]
return flags
return WinPlatform()
@@ -538,8 +538,12 @@ def CompilerOptionsConfigure(platform, compiler_options, config_name):
]
compiler_options[:] = new_compiler_options
-def CompilerOptionsGetCommandLine(compiler_options):
- return ' '.join(compiler_options)
+def PlatformGetCommandLine(platform, options):
+ return ' '.join([PlatformQuoteArgument(platform, x) for x in options])
+
+
+def CompilerOptionsGetCommandLine(platform, compiler_options):
+ return PlatformGetCommandLine(platform, compiler_options)
def MakeLinkerOptions(platform):
@@ -559,8 +563,8 @@ def LinkerOptionsHasCommandLine(linker_options):
return len(linker_options) > 0
-def LinkerOptionsGetCommandLine(linker_options):
- return ' '.join(linker_options)
+def LinkerOptionsGetCommandLine(platform, linker_options):
+ return PlatformGetCommandLine(platform, linker_options)
# TODO(nicolas): what about moving that to linker options function
@@ -915,7 +919,7 @@ def PushTargetForConfig(
bff,
BFFConcatenation(
'.CompilerOptions',
- ' ' + CompilerOptionsGetCommandLine(compiler_options),
+ ' ' + CompilerOptionsGetCommandLine(platform, compiler_options),
)
)
@@ -943,7 +947,7 @@ def PushTargetForConfig(
bff,
BFFConcatenation(
'.CompilerOptions',
- ' ' + CompilerOptionsGetCommandLine(compiler_options)
+ ' ' + CompilerOptionsGetCommandLine(platform, compiler_options)
)
)
PushAssignment(
@@ -1028,7 +1032,7 @@ def PushTargetForConfig(
bff,
BFFConcatenation(
'.LinkerOptions',
- ' ' + LinkerOptionsGetCommandLine(linker_options)
+ ' ' + LinkerOptionsGetCommandLine(platform, linker_options)
)
)
--
2.7.0.windows.1
From a1ee5549cb4511bc4c38412da019b65624299d85 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Fri, 15 Jul 2016 09:35:49 +0200
Subject: [PATCH 15/19] Format all paths in the native platform syntax
---
pylib/gyp/generator/fastbuild.py | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index d251326..ecfbecc 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -225,7 +225,7 @@ def PushToolsetConfig(bff, toolset, config_names):
BFFAssignment('.SharedProductDir', '%s' % products_base_dir),
BFFAssignment(
'.SharedIntermediateDir',
- '$SharedProductDir$/gen'
+ os.path.join('$SharedProductDir$', 'gen')
),
# Are these ProductDir/IntermediateDir legal when outside of any
# configuration? TODO(nicolas): or change gyp, or change rules to
@@ -257,9 +257,8 @@ def PushConfig(bff, bff_config_name, config_name, toolset):
[
BFFUsing(GetBFFConfigVariableName(toolset, '')),
BFFAssignment('.ConfigName', '%s' % config_name),
- BFFAssignment('.ProductDir', '$SharedProductDir$/%s' %
- config_name),
- BFFAssignment('.IntermediateDir', '$ProductDir$/gen'),
+ BFFAssignment('.ProductDir', os.path.join('$SharedProductDir$', config_name)),
+ BFFAssignment('.IntermediateDir', os.path.join('$ProductDir$', 'gen')),
]
)
PushAssignment(bff,
@@ -953,8 +952,8 @@ def PushTargetForConfig(
PushAssignment(
bff,
'.CompilerOutputPath',
- '$ProductDir$/obj/%s' %
- target_name)
+ os.path.join('$ProductDir$', 'obj', target_name)
+ )
config = target_spec.get('configurations').get(config_name, {})
@@ -1016,7 +1015,7 @@ def PushTargetForConfig(
using_config()
PushAssignment(
- bff, '.LinkerOutput', '$ProductDir$/%s' % linker_product
+ bff, '.LinkerOutput', os.path.join('$ProductDir$', linker_product)
)
linker_options = MakeLinkerOptions(platform)
LinkerOptionsConfigure(
@@ -1071,14 +1070,14 @@ def PushTargetForConfig(
PushAssignment(
bff,
'.LibrarianOutput',
- '$ProductDir$/%s' % output_filename
+ os.path.join('$ProductDir$', output_filename)
)
PushAssignment(bff,
'.LibrarianAdditionalInputs',
bff_all_object_list_names)
PushAssignment(bff,
'.CompilerOutputPath',
- '$ProductDir$/lib/%s' % target_name)
+ os.path.join('$ProductDir$', 'lib', target_name))
bff_product_target_name = library_name
elif target_type == 'none':
--
2.7.0.windows.1
From 21c2ff6d4053d07c23c611e18287ec41ae98ee76 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Fri, 15 Jul 2016 10:24:19 +0200
Subject: [PATCH 16/19] Unify some aspects of command line generation
---
pylib/gyp/generator/fastbuild.py | 72 +++++++++++++++-------------------------
1 file changed, 27 insertions(+), 45 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index ecfbecc..1ce8c10 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -524,10 +524,6 @@ def CompilerOptionsPushIncludeDir(platform, compiler_options, include_dir):
platform.push_include_dir(compiler_options, include_dir)
-def CompilerOptionsHasCommandLine(compiler_options):
- return len(compiler_options) > 0
-
-
def CompilerOptionsConfigure(platform, compiler_options, config_name):
# TODO(nicolas): take language into account
platform.push_specific_cflags(compiler_options, config_name)
@@ -537,13 +533,13 @@ def CompilerOptionsConfigure(platform, compiler_options, config_name):
]
compiler_options[:] = new_compiler_options
-def PlatformGetCommandLine(platform, options):
- return ' '.join([PlatformQuoteArgument(platform, x) for x in options])
+def PlatformHasCommandLine(options):
+ return len(options) > 0
-def CompilerOptionsGetCommandLine(platform, compiler_options):
- return PlatformGetCommandLine(platform, compiler_options)
+def PlatformGetCommandLine(platform, options):
+ return ' '.join([PlatformQuoteArgument(platform, x) for x in options])
def MakeLinkerOptions(platform):
return []
@@ -562,10 +558,6 @@ def LinkerOptionsHasCommandLine(linker_options):
return len(linker_options) > 0
-def LinkerOptionsGetCommandLine(platform, linker_options):
- return PlatformGetCommandLine(platform, linker_options)
-
-
# TODO(nicolas): what about moving that to linker options function
def PlatformLDFlagsForLibraryDirs(platform, library_dirs):
return platform.ldflags_for_library_dirs(library_dirs)
@@ -698,6 +690,20 @@ def PushTargetSpecs(
PushAlias(bff, target_name, all_bff_target_names)
+def PushCommandLineConcatenation(bff, variable_name, platform, command_line_options, comment=None):
+ # TODO(nicolas): see if we can support this in BFFWriteToFile w/ a wrap-mode
+ # for making it more readable.
+ if PlatformHasCommandLine(command_line_options):
+ if comment is not None:
+ PushComment(bff, comment)
+ PushCommand(
+ bff,
+ BFFConcatenation(
+ variable_name,
+ ' ' + PlatformGetCommandLine(platform, command_line_options)
+ )
+ )
+
def PushTargetForConfig(
bff,
output_filename,
@@ -910,18 +916,12 @@ def PushTargetForConfig(
PushAssignment(bff, '.CompilerInputFiles', os_sources)
compiler_options = MakeCompilerOptions(platform)
CompilerOptionsConfigure(platform, compiler_options, config_name)
- if CompilerOptionsHasCommandLine(compiler_options):
- # TODO(nicolas): I wonder if we could de-duplicate those
- # into structs, for more readability
- PushComment(bff, 'Config-specific command line')
- PushCommand(
- bff,
- BFFConcatenation(
- '.CompilerOptions',
- ' ' + CompilerOptionsGetCommandLine(platform, compiler_options),
- )
- )
-
+ # TODO(nicolas): I wonder if we could de-duplicate those
+ # into structs, for more readability
+ PushCommandLineConcatenation(
+ bff, '.CompilerOptions', platform, compiler_options,
+ comment='Config-specific command line'
+ )
config = target_spec.get('configurations').get(config_name, {})
compiler_options = MakeCompilerOptions(platform)
for define in config.get('defines', []):
@@ -937,18 +937,8 @@ def PushTargetForConfig(
include_dir if os.path.isabs(
include_dir) else gyp_path_to_os_path(include_dir)
)
- if CompilerOptionsHasCommandLine(compiler_options):
- PushComment(
- bff,
- 'Target-specific command line'
- )
- PushCommand(
- bff,
- BFFConcatenation(
- '.CompilerOptions',
- ' ' + CompilerOptionsGetCommandLine(platform, compiler_options)
- )
- )
+ PushCommandLineConcatenation(bff, '.CompilerOptions', platform, compiler_options,
+ comment='Target-specific command line')
PushAssignment(
bff,
'.CompilerOutputPath',
@@ -1026,15 +1016,7 @@ def PushTargetForConfig(
target_type,
linker_product
)
- if LinkerOptionsHasCommandLine(linker_options):
- PushCommand(
- bff,
- BFFConcatenation(
- '.LinkerOptions',
- ' ' + LinkerOptionsGetCommandLine(platform, linker_options)
- )
- )
-
+ PushCommandLineConcatenation(bff, '.LinkerOptions', platform, linker_options)
for flag in PlatformLDFlagsForLibraryDirs(
platform, config.get('library_dirs', [])):
PushCommand(
--
2.7.0.windows.1
From 0f2eb412cd8c6bef39ab5b1d95415df5d1ded9bc Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Fri, 15 Jul 2016 10:24:35 +0200
Subject: [PATCH 17/19] Start studying what's happening with manifest files
---
pylib/gyp/generator/fastbuild.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index 1ce8c10..cd5080a 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -4,6 +4,11 @@
# This module generates `.bff` files for consumption with the FASTbuild
# URL(http://fastbuild.org) opensource build system developed by Franta Fulin.
#
+# TODO(nicolas): a `generated.manifest` file gets generated by the msvs emulation,
+# yet it does not appear in our `.bff` file. What's up with that? Furthermore the
+# bff file we generate tends to produce an `intermediate.manifest` file in the
+# current working directory of the build.
+#
# TODO(nicolas): Where should PlatformExpandMacros actually be called?
# Every gyp input variable?
#
@@ -20,6 +25,8 @@
# Try removing the Aliases entirely. Would the generated bff file work
# at all without them. And if not, why not?
#
+# i.e. Use PreBuildDepedencies and actual build products instead!
+#
import copy
import gyp
@@ -475,8 +482,8 @@ def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
def gyp_to_build_path(x):
return PlatformGetPathFromGypPath(self, inputs_base_dir, x)
def expand_special(path, product_dir=None):
- return "EXPAND_SPECIAL(%s, %s)" % (path, product_dir)
- manifest_base_name = "MANIFEST_BASE_NAME"
+ return "SENTINEL.EXPAND_SPECIAL(%s, %s)" % (path, product_dir)
+ manifest_base_name = "SENTINEL." + target_output_filename
output_name = "$ProductDir$/%s" % target_output_filename
is_executable = target_type == 'executable'
--
2.7.0.windows.1
From feb98d31d66d9886c3c263c6864e996babc73887 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Fri, 15 Jul 2016 10:45:02 +0200
Subject: [PATCH 18/19] Add TODO
---
pylib/gyp/generator/fastbuild.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index cd5080a..e5f07cf 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -7,7 +7,7 @@
# TODO(nicolas): a `generated.manifest` file gets generated by the msvs emulation,
# yet it does not appear in our `.bff` file. What's up with that? Furthermore the
# bff file we generate tends to produce an `intermediate.manifest` file in the
-# current working directory of the build.
+# current working directory of the build.
#
# TODO(nicolas): Where should PlatformExpandMacros actually be called?
# Every gyp input variable?
@@ -499,6 +499,8 @@ def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
outputs_base_dir
)
options.extend(ldflags)
+ # TODO(nicolas): use the manifest_files listed here to generate
+ # the necessary commands.
def ldflags(self, libraries):
return self.msvs_settings.AdjustLibraries(libraries)
--
2.7.0.windows.1
From d69b3bba0ba2bd67892c7896046a37a4d41f6822 Mon Sep 17 00:00:00 2001
From: nicolas <nicolas@uucidl.com>
Date: Mon, 18 Jul 2016 10:10:49 +0200
Subject: [PATCH 19/19] Study a bit more the manifest situation
---
pylib/gyp/generator/fastbuild.py | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/pylib/gyp/generator/fastbuild.py b/pylib/gyp/generator/fastbuild.py
index e5f07cf..883a5c1 100644
--- a/pylib/gyp/generator/fastbuild.py
+++ b/pylib/gyp/generator/fastbuild.py
@@ -7,7 +7,7 @@
# TODO(nicolas): a `generated.manifest` file gets generated by the msvs emulation,
# yet it does not appear in our `.bff` file. What's up with that? Furthermore the
# bff file we generate tends to produce an `intermediate.manifest` file in the
-# current working directory of the build.
+# current working directory of the build.
#
# TODO(nicolas): Where should PlatformExpandMacros actually be called?
# Every gyp input variable?
@@ -27,6 +27,14 @@
#
# i.e. Use PreBuildDepedencies and actual build products instead!
#
+# TODO(nicolas): converting explicit file rules into wildcards would
+# help reducing the amount of `.bff` file changes which in turn helps
+# FASTbuild not re-building everything needlessly over and over again.
+# see URL(https://github.com/fastbuild/fastbuild/issues/108#issuecomment-233198894)
+#
+# TODO(nicolas): check and mark all ignored values returned by emulation backends
+#
+# TODO(nicolas): should we use gyp-win-tool rather than the compiler and linker directly?
import copy
import gyp
@@ -58,7 +66,8 @@ def debug_assert(x):
# TODO(nil): shouldn't we also search in the runtime path of the toolset?
# for instance we have ml64.exe that needs to be found but that cannot be guaranteed
-# when launching the `gyp` command
+# when launching the `gyp` command.
+# On the other hand ml64.exe should really not be inside an Exec node and instead in an `ObjectList` w/ Compiler `ml64.exe`
def shell_which(filename, additional_paths=None):
if additional_paths is None:
additional_paths = []
@@ -501,6 +510,18 @@ def MakePlatform(flavor, generator_flags, toolset, toplevel_dir, target_spec):
options.extend(ldflags)
# TODO(nicolas): use the manifest_files listed here to generate
# the necessary commands.
+ # URL(https://msdn.microsoft.com/en-us/library/aa375649(v=vs.85).aspx) MT.exe documentation
+ print "ignored intermediate manifest: %s, manifest files: %s " % (intermediate_manifest, manifest_files)
+ print "This should roughly do:"
+ print "mt -nologo -manifest %(manifests)s -out:%(out)s.manifest"
+ print "turn manifest into rc file (manually)"
+ # TODO(nicolas): the commands above will need some Exec(...) nodes and
+ # to add them to the dependencies for the executable being built.
+ # We will need to support .PreBuildDependencies btw for that.
+ # TODO(nicolas) Put .rc file into ObjectList w/ Compiler RC.exe
+ # this will require something like: .ResourceCompiler = '$WindowsSDKBasePath$\bin\x86\RC.exe' in our config
+ # rc %(out)s.manifest.rc
+ # and link to the resulting .res
def ldflags(self, libraries):
return self.msvs_settings.AdjustLibraries(libraries)
@@ -843,6 +864,14 @@ def PushTargetForConfig(
action_name = action_spec['action_name']
bff_action_name = '%s-%s' % (bff_target_name, action_name)
PushComment(bff, 'Action %s' % action_name)
+ objects = []
+ for output in action_spec['outputs']:
+ # TODO(nicolas): TAG(win32) TAG(platform) this should not be hardcoded here
+ if output.endswith(".obj"):
+ objects.append(output)
+ if objects:
+ PushComment(bff, 'Action contains object files, should it not be an ObjectList instead?')
+ PushComment(bff, 'Objects: %s' % objects)
apply_multiple_output_hack(action_spec)
--
2.7.0.windows.1
Settings
{
.CachePath = '/tmp/FBCache'
}
Compiler('clangcxx-x64')
{
.Executable = '/usr/bin/clang++'
}
Compiler('clangcc-x64')
{
.Executable = '/usr/bin/clang'
}
.ClangCXXConfig_OSX =
[
.Compiler = 'clangcxx-x64'
.CompilerOptions = '-c "%1" -o "%2"'
.CompilerOptions + ' -std=c++0x -stdlib=libc++'
;; .CompilerOptions + ' -mmacosx-version-min=10.7'
]
.TargetLinker =
[
.Linker = '/usr/bin/clang++'
.LinkerOptions = '"%1" -o "%2"'
]
.TargetLibrarian =
[
.Librarian = '/usr/bin/ar'
.LibrarianOptions = 'rcs "%2" "%1"'
]
.TargetCXXCompiler = .ClangCXXConfig_OSX
.TargetCCompiler =
[
.Compiler = 'clangcc-x64'
.CompilerOptions = '-c "%1" -o "%2"'
]
.TargetObjCCompiler =
[
.Compiler = 'clangcc-x64'
.CompilerOptions = '-c "%1" -o "%2"'
]
.TargetObjCXXCompiler =
[
.Compiler = 'clangcxx-x64'
.CompilerOptions = '-c "%1" -o "%2"'
]
.TargetDebugCXXCompiler =
[
Using(.TargetCXXCompiler)
;; .CompilerOptions + ' -g -O1'
]
.TargetReleaseCXXCompiler =
[
Using(.TargetCXXCompiler)
;; .CompilerOptions + ' -O2'
]
.TargetDebugCCompiler = .TargetCCompiler
.TargetReleaseCCompiler = .TargetCCompiler
.TargetDebugObjCCompiler = .TargetObjCCompiler
.TargetReleaseObjCCompiler = .TargetObjCCompiler
.TargetDebugObjCXXCompiler = .TargetObjCXXCompiler
.TargetReleaseObjCXXCompiler = .TargetObjCXXCompiler
// One of the few references for a big project is the build file for FASTbuild itself,
// See URL( https://github.com/fastbuild/fastbuild/blob/master/Code/fbuild.bff )
//
// Example:
// RUN(FASTBUILD_CACHE_PATH=m:/dev/FASTBUILDCache fbuild -config ../live.bff -cacheread -cachewrite)
;;
.QtDir = 'c:/GuiEnv_32/Qt5.5.0/msvc2013_32/'
.WindowsKitDir = 'C:\Program Files (x86)\Windows Kits\8.1'
#define VS140
#if VS120
#import VS120COMNTOOLS
.VSBasePath = '$VS120COMNTOOLS$\..\..'
Compiler('Compiler-msvc-x86')
{
.CompilerOptions = '/nologo'
.CompilerOptions + ' /I"$VSBasePath$"\VC\INCLUDE'
.CompilerOptions + ' /I"$VSBasePath$"\VC\ATLMFC\INCLUDE'
.CompilerOptions + ' /I"$WindowsKitDir$"\include\shared'
.CompilerOptions + ' /I"$WindowsKitDir$"\include\um'
.Root = '$VSBasePath$\VC\Bin\'
.Executable = '$Root$\cl.exe'
.ExtraFiles = {
'$Root$\c1.dll'
'$Root$\c1ast.dll',
'$Root$\c1xx.dll',
'$Root$\c1xxast.dll',
'$Root$\c2.dll',
'$Root$\msobj120.dll'
'$Root$\mspdb120.dll'
'$Root$\mspdbsrv.exe'
'$Root$\mspdbcore.dll'
'$Root$\mspft120.dll'
'$Root$\1033\clui.dll'
'$VSBasePath$\VC\redist\x86\Microsoft.VC120.CRT\msvcp120.dll'
'$VSBasePath$\VC\redist\x86\Microsoft.VC120.CRT\msvcr120.dll'
'$VSBasePath$\VC\redist\x86\Microsoft.VC120.CRT\vccorlib120.dll'
}
}
#endif
#if VS140
#import VS140COMNTOOLS
.VSBasePath = '$VS140COMNTOOLS$\..\..'
Compiler('Compiler-msvc-x64')
{
.CompilerOptions = '/nologo'
+ ' /WIN64'
.CompilerOptions + ' /I"$VSBasePath$"\VC\INCLUDE'
.CompilerOptions + ' /I"$VSBasePath$"\VC\ATLMFC\INCLUDE'
.CompilerOptions + ' /I"$WindowsKitDir$"\include\shared'
.CompilerOptions + ' /I"$WindowsKitDir$"\include\um'
.Root = '$VSBasePath$\VC\Bin\'
.Executable = '$Root$\amd64\cl.exe'
.ExtraFiles = {
;; TODO(nil): to be defined
}
}
#endif
;;.Compiler = 'Compiler-msvc-x64'
.CompilerOptions = '"%1" /Fo"%2" /c /Z7 /EHsc /nologo'
.StrictCompilerOptions = [
.CompilerOptions + ' /W4 /WX'
.CompilerOptions + ' /wd4512 /wd4505'
]
.TargetLinker = [
.Linker = '$VSBasePath$\VC\Bin\link.exe'
.LinkerOptions = ' /WX /NOLOGO /INCREMENTAL:NO /OUT:"%2" "%1" /DEBUG'
+ ' /MACHINE:x64'
+ ' /IGNORE:4001' ; don't complain about linking libs only
]
.TargetLibrarian = [
.Librarian = '$VSBasePath$\VC\Bin\lib.exe'
.LibrarianOptions = '/OUT:"%2" "%1"'
]
.TargetDefault_x64CXXCompiler = [
;; don't bother with this right now
.Compiler = 'Compiler-msvc-x64'
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment