diff --git a/tools/gyp/buildbot/buildbot_run.py b/tools/gyp/buildbot/buildbot_run.py index a8531258b2..e0fda368ae 100755 --- a/tools/gyp/buildbot/buildbot_run.py +++ b/tools/gyp/buildbot/buildbot_run.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -23,32 +23,6 @@ BUILDBOT_DIR = os.path.dirname(os.path.abspath(__file__)) TRUNK_DIR = os.path.dirname(BUILDBOT_DIR) ROOT_DIR = os.path.dirname(TRUNK_DIR) OUT_DIR = os.path.join(TRUNK_DIR, 'out') -NINJA_PATH = os.path.join(TRUNK_DIR, 'ninja' + EXE_SUFFIX) -NINJA_WORK_DIR = os.path.join(ROOT_DIR, 'ninja_work') - - -def InstallNinja(): - """Install + build ninja. - - Returns: - 0 for success, 1 for failure. - """ - print '@@@BUILD_STEP install ninja@@@' - # Delete old version if any. - try: - shutil.rmtree(NINJA_WORK_DIR, ignore_errors=True) - except: - pass - # Sync new copy from git. - subprocess.check_call( - 'git clone https://github.com/martine/ninja.git ' + NINJA_WORK_DIR, - shell=True) - # Bootstrap. - subprocess.check_call('./bootstrap.sh', cwd=NINJA_WORK_DIR, shell=True) - # Copy out ninja. - shutil.copyfile(os.path.join(NINJA_WORK_DIR, 'ninja' + EXE_SUFFIX), - NINJA_PATH) - os.chmod(NINJA_PATH, 0777) def GypTestFormat(title, format=None, msvs_version=None): @@ -64,17 +38,6 @@ def GypTestFormat(title, format=None, msvs_version=None): if not format: format = title - # Install ninja if needed. - # NOTE: as ninja gets installed each time, regressions to ninja can come - # either from changes to ninja itself, or changes to gyp. - if format == 'ninja': - try: - InstallNinja() - except Exception, e: - print '@@@STEP_FAILURE@@@' - print str(e) - return 1 - print '@@@BUILD_STEP ' + title + '@@@' sys.stdout.flush() env = os.environ.copy() @@ -104,10 +67,6 @@ def GypBuild(): print '@@@BUILD_STEP cleanup@@@' print 'Removing %s...' % OUT_DIR shutil.rmtree(OUT_DIR, ignore_errors=True) - print 'Removing %s...' % NINJA_WORK_DIR - shutil.rmtree(NINJA_WORK_DIR, ignore_errors=True) - print 'Removing %s...' % NINJA_PATH - shutil.rmtree(NINJA_PATH, ignore_errors=True) print 'Done.' retcode = 0 diff --git a/tools/gyp/gyptest.py b/tools/gyp/gyptest.py index e8bf482d99..d9c814f3fa 100755 --- a/tools/gyp/gyptest.py +++ b/tools/gyp/gyptest.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -209,7 +209,7 @@ def main(argv=None): 'freebsd7': ['make'], 'freebsd8': ['make'], 'cygwin': ['msvs'], - 'win32': ['msvs'], + 'win32': ['msvs', 'ninja'], 'linux2': ['make', 'ninja'], 'linux3': ['make', 'ninja'], 'darwin': ['make', 'ninja', 'xcode'], diff --git a/tools/gyp/pylib/gyp/common.py b/tools/gyp/pylib/gyp/common.py index 97594cd5d6..614d207c30 100644 --- a/tools/gyp/pylib/gyp/common.py +++ b/tools/gyp/pylib/gyp/common.py @@ -1,7 +1,9 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from __future__ import with_statement + import errno import filecmp import os.path @@ -347,6 +349,8 @@ def WriteOnDiff(filename): def GetFlavor(params): """Returns |params.flavor| if it's set, the system's default flavor else.""" flavors = { + 'cygwin': 'win', + 'win32': 'win', 'darwin': 'mac', 'sunos5': 'solaris', 'freebsd7': 'freebsd', @@ -356,6 +360,29 @@ def GetFlavor(params): return params.get('flavor', flavor) +def CopyTool(flavor, out_path): + """Finds (mac|sun)_tool.gyp in the gyp directory and copies it + to |out_path|.""" + prefix = { 'solaris': 'sun', 'mac': 'mac' }.get(flavor, None) + if not prefix: + return + + # Slurp input file. + source_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), '%s_tool.py' % prefix) + with open(source_path) as source_file: + source = source_file.readlines() + + # Add header and write it out. + tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix) + with open(tool_path, 'w') as tool_file: + tool_file.write( + ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:])) + + # Make file executable. + os.chmod(tool_path, 0755) + + # From Alex Martelli, # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 # ASPN: Python Cookbook: Remove duplicates from a sequence diff --git a/tools/gyp/pylib/gyp/generator/dump_dependency_json.py b/tools/gyp/pylib/gyp/generator/dump_dependency_json.py index 1e0900a231..c1c2fbdc98 100644 --- a/tools/gyp/pylib/gyp/generator/dump_dependency_json.py +++ b/tools/gyp/pylib/gyp/generator/dump_dependency_json.py @@ -20,15 +20,15 @@ for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', - 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', - 'LINKER_SUPPORTS_ICF']: + 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX']: generator_default_variables[unused] = '' def CalculateVariables(default_variables, params): generator_flags = params.get('generator_flags', {}) - default_variables['OS'] = generator_flags.get( - 'os', gyp.common.GetFlavor(params)) + for key, val in generator_flags.items(): + default_variables.setdefault(key, val) + default_variables.setdefault('OS', gyp.common.GetFlavor(params)) def CalculateGeneratorInputInfo(params): diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index 87ad79a675..e93cb03314 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -1,4 +1,4 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -24,9 +24,9 @@ import gyp import gyp.common import gyp.system_test +import gyp.xcode_emulation import os import re -import shlex import sys generator_default_variables = { @@ -58,9 +58,6 @@ generator_wants_sorted_dependencies = False def CalculateVariables(default_variables, params): """Calculate additional variables for use in the build (called by gyp).""" cc_target = os.environ.get('CC.target', os.environ.get('CC', 'cc')) - default_variables['LINKER_SUPPORTS_ICF'] = \ - gyp.system_test.TestLinkerSupportsICF(cc_command=cc_target) - flavor = gyp.common.GetFlavor(params) if flavor == 'mac': default_variables.setdefault('OS', 'mac') @@ -159,7 +156,7 @@ cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSE LINK_COMMANDS_MAC = """\ quiet_cmd_alink = LIBTOOL-STATIC $@ -cmd_alink = rm -f $@ && libtool -static -o $@ $(filter %.o,$^) +cmd_alink = rm -f $@ && ./gyp-mac-tool filter-libtool libtool -static -o $@ $(filter %.o,$^) quiet_cmd_link = LINK($(TOOLSET)) $@ cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS) @@ -213,6 +210,7 @@ MAKEFLAGS=-r # The source directory tree. srcdir := %(srcdir)s +abs_srcdir := $(abspath $(srcdir)) # The name of the builddir. builddir_name ?= %(builddir)s @@ -260,7 +258,7 @@ CFLAGS.target ?= $(CFLAGS) CXX.target ?= $(CXX) CXXFLAGS.target ?= $(CXXFLAGS) LINK.target ?= $(LINK) -LDFLAGS.target ?= $(LDFLAGS) %(LINK_flags)s +LDFLAGS.target ?= $(LDFLAGS) AR.target ?= $(AR) ARFLAGS.target ?= %(ARFLAGS.target)s @@ -458,7 +456,7 @@ cmd_objcxx = $(CXX.$(TOOLSET)) $(GYP_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $< quiet_cmd_pch_c = CXX($(TOOLSET)) $@ cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_cc = CXX($(TOOLSET)) $@ -cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CCFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< +cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_m = CXX($(TOOLSET)) $@ cmd_pch_m = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCFLAGS) $(DEPFLAGS) -c -o $@ $< quiet_cmd_pch_mm = CXX($(TOOLSET)) $@ @@ -605,7 +603,7 @@ def QuoteIfNecessary(string): def StringToMakefileVariable(string): """Convert a string to a value that is acceptable as a make variable name.""" # TODO: replace other metacharacters that we encounter. - return string.replace(' ', '_') + return re.sub('[ {}$]', '_', string) srcdir_prefix = '' @@ -618,12 +616,8 @@ def Sourceify(path): return srcdir_prefix + path -def QuoteSpaces(s): - return s.replace(' ', r'\ ') - - -def ReplaceQuotedSpaces(s): - return s.replace(r'\ ', SPACE_REPLACEMENT) +def QuoteSpaces(s, quote=r'\ '): + return s.replace(' ', quote) # Map from qualified target to path to output. @@ -635,603 +629,6 @@ target_outputs = {} target_link_deps = {} -class XcodeSettings(object): - """A class that understands the gyp 'xcode_settings' object.""" - - def __init__(self, spec): - self.spec = spec - - # Per-target 'xcode_settings' are pushed down into configs earlier by gyp. - # This means self.xcode_settings[config] always contains all settings - # for that config -- the per-target settings as well. Settings that are - # the same for all configs are implicitly per-target settings. - self.xcode_settings = {} - configs = spec['configurations'] - for configname, config in configs.iteritems(): - self.xcode_settings[configname] = config.get('xcode_settings', {}) - - # This is only non-None temporarily during the execution of some methods. - self.configname = None - - def _Settings(self): - assert self.configname - return self.xcode_settings[self.configname] - - def _Test(self, test_key, cond_key, default): - return self._Settings().get(test_key, default) == cond_key - - def _Appendf(self, lst, test_key, format_str, default=None): - if test_key in self._Settings(): - lst.append(format_str % str(self._Settings()[test_key])) - elif default: - lst.append(format_str % str(default)) - - def _WarnUnimplemented(self, test_key): - if test_key in self._Settings(): - print 'Warning: Ignoring not yet implemented key "%s".' % test_key - - def _IsBundle(self): - return int(self.spec.get('mac_bundle', 0)) != 0 - - def GetFrameworkVersion(self): - """Returns the framework version of the current target. Only valid for - bundles.""" - assert self._IsBundle() - return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A') - - def GetWrapperExtension(self): - """Returns the bundle extension (.app, .framework, .plugin, etc). Only - valid for bundles.""" - assert self._IsBundle() - if self.spec['type'] in ('loadable_module', 'shared_library'): - default_wrapper_extension = { - 'loadable_module': 'bundle', - 'shared_library': 'framework', - }[self.spec['type']] - wrapper_extension = self.GetPerTargetSetting( - 'WRAPPER_EXTENSION', default=default_wrapper_extension) - return '.' + self.spec.get('product_extension', wrapper_extension) - elif self.spec['type'] == 'executable': - return '.app' - else: - assert False, "Don't know extension for '%s', target '%s'" % ( - self.spec['type'], self.spec['target_name']) - - def GetProductName(self): - """Returns PRODUCT_NAME.""" - return self.spec.get('product_name', self.spec['target_name']) - - def GetWrapperName(self): - """Returns the directory name of the bundle represented by this target. - Only valid for bundles.""" - assert self._IsBundle() - return self.GetProductName() + self.GetWrapperExtension() - - def GetBundleContentsFolderPath(self): - """Returns the qualified path to the bundle's contents folder. E.g. - Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles.""" - assert self._IsBundle() - if self.spec['type'] == 'shared_library': - return os.path.join( - self.GetWrapperName(), 'Versions', self.GetFrameworkVersion()) - else: - # loadable_modules have a 'Contents' folder like executables. - return os.path.join(self.GetWrapperName(), 'Contents') - - def GetBundleResourceFolder(self): - """Returns the qualified path to the bundle's resource folder. E.g. - Chromium.app/Contents/Resources. Only valid for bundles.""" - assert self._IsBundle() - return os.path.join(self.GetBundleContentsFolderPath(), 'Resources') - - def GetBundlePlistPath(self): - """Returns the qualified path to the bundle's plist file. E.g. - Chromium.app/Contents/Info.plist. Only valid for bundles.""" - assert self._IsBundle() - if self.spec['type'] in ('executable', 'loadable_module'): - return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist') - else: - return os.path.join(self.GetBundleContentsFolderPath(), - 'Resources', 'Info.plist') - - def GetProductType(self): - """Returns the PRODUCT_TYPE of this target.""" - if self._IsBundle(): - return { - 'executable': 'com.apple.product-type.application', - 'loadable_module': 'com.apple.product-type.bundle', - 'shared_library': 'com.apple.product-type.framework', - }[self.spec['type']] - else: - return { - 'executable': 'com.apple.product-type.tool', - 'loadable_module': 'com.apple.product-type.library.dynamic', - 'shared_library': 'com.apple.product-type.library.dynamic', - 'static_library': 'com.apple.product-type.library.static', - }[self.spec['type']] - - def GetMachOType(self): - """Returns the MACH_O_TYPE of this target.""" - # Weird, but matches Xcode. - if not self._IsBundle() and self.spec['type'] == 'executable': - return '' - return { - 'executable': 'mh_execute', - 'static_library': 'staticlib', - 'shared_library': 'mh_dylib', - 'loadable_module': 'mh_bundle', - }[self.spec['type']] - - def _GetBundleBinaryPath(self): - """Returns the name of the bundle binary of by this target. - E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles.""" - assert self._IsBundle() - if self.spec['type'] in ('shared_library'): - path = self.GetBundleContentsFolderPath() - elif self.spec['type'] in ('executable', 'loadable_module'): - path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS') - return os.path.join(path, self.spec.get('product_name', - self.spec['target_name'])) - - def _GetStandaloneExecutableSuffix(self): - if 'product_extension' in self.spec: - return '.' + self.spec['product_extension'] - return { - 'executable': '', - 'static_library': '.a', - 'shared_library': '.dylib', - 'loadable_module': '.so', - }[self.spec['type']] - - def _GetStandaloneExecutablePrefix(self): - return self.spec.get('product_prefix', { - 'executable': '', - 'static_library': 'lib', - 'shared_library': 'lib', - # Non-bundled loadable_modules are called foo.so for some reason - # (that is, .so and no prefix) with the xcode build -- match that. - 'loadable_module': '', - }[self.spec['type']]) - - def _GetStandaloneBinaryPath(self): - """Returns the name of the non-bundle binary represented by this target. - E.g. hello_world. Only valid for non-bundles.""" - assert not self._IsBundle() - assert self.spec['type'] in ( - 'executable', 'shared_library', 'static_library', 'loadable_module') - target = self.spec['target_name'] - if self.spec['type'] == 'static_library': - if target[:3] == 'lib': - target = target[3:] - elif self.spec['type'] in ('loadable_module', 'shared_library'): - if target[:3] == 'lib': - target = target[3:] - - target_prefix = self._GetStandaloneExecutablePrefix() - target = self.spec.get('product_name', target) - target_ext = self._GetStandaloneExecutableSuffix() - return target_prefix + target + target_ext - - def GetExecutablePath(self): - """Returns the directory name of the bundle represented by this target. E.g. - Chromium.app/Contents/MacOS/Chromium.""" - if self._IsBundle(): - return self._GetBundleBinaryPath() - else: - return self._GetStandaloneBinaryPath() - - def _SdkPath(self): - sdk_root = 'macosx10.5' - if 'SDKROOT' in self._Settings(): - sdk_root = self._Settings()['SDKROOT'] - if sdk_root.startswith('macosx'): - sdk_root = 'MacOSX' + sdk_root[len('macosx'):] - return '/Developer/SDKs/%s.sdk' % sdk_root - - def GetCflags(self, configname): - """Returns flags that need to be added to .c, .cc, .m, and .mm - compilations.""" - # This functions (and the similar ones below) do not offer complete - # emulation of all xcode_settings keys. They're implemented on demand. - - self.configname = configname - cflags = [] - - sdk_root = self._SdkPath() - if 'SDKROOT' in self._Settings(): - cflags.append('-isysroot %s' % sdk_root) - - if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'): - cflags.append('-fasm-blocks') - - if 'GCC_DYNAMIC_NO_PIC' in self._Settings(): - if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES': - cflags.append('-mdynamic-no-pic') - else: - pass - # TODO: In this case, it depends on the target. xcode passes - # mdynamic-no-pic by default for executable and possibly static lib - # according to mento - - if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'): - cflags.append('-mpascal-strings') - - self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s') - - if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'): - dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') - if dbg_format == 'dwarf': - cflags.append('-gdwarf-2') - elif dbg_format == 'stabs': - raise NotImplementedError('stabs debug format is not supported yet.') - elif dbg_format == 'dwarf-with-dsym': - cflags.append('-gdwarf-2') - else: - raise NotImplementedError('Unknown debug format %s' % dbg_format) - - if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'): - cflags.append('-fvisibility=hidden') - - if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'): - cflags.append('-Werror') - - if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'): - cflags.append('-Wnewline-eof') - - self._Appendf(cflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') - - # TODO: - self._WarnUnimplemented('ARCHS') - if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'): - self._WarnUnimplemented('COPY_PHASE_STRIP') - self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS') - self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS') - self._WarnUnimplemented('GCC_ENABLE_OBJC_GC') - - # TODO: This is exported correctly, but assigning to it is not supported. - self._WarnUnimplemented('MACH_O_TYPE') - self._WarnUnimplemented('PRODUCT_TYPE') - - # TODO: Do not hardcode arch. Supporting fat binaries will be annoying. - cflags.append('-arch i386') - - cflags += self._Settings().get('OTHER_CFLAGS', []) - cflags += self._Settings().get('WARNING_CFLAGS', []) - - config = self.spec['configurations'][self.configname] - framework_dirs = config.get('mac_framework_dirs', []) - for directory in framework_dirs: - cflags.append('-F ' + directory.replace('$(SDKROOT)', sdk_root)) - - self.configname = None - return cflags - - def GetCflagsC(self, configname): - """Returns flags that need to be added to .c, and .m compilations.""" - self.configname = configname - cflags_c = [] - self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s') - self.configname = None - return cflags_c - - def GetCflagsCC(self, configname): - """Returns flags that need to be added to .cc, and .mm compilations.""" - self.configname = configname - cflags_cc = [] - if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'): - cflags_cc.append('-fno-rtti') - if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'): - cflags_cc.append('-fno-exceptions') - if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'): - cflags_cc.append('-fvisibility-inlines-hidden') - if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'): - cflags_cc.append('-fno-threadsafe-statics') - self.configname = None - return cflags_cc - - def GetCflagsObjC(self, configname): - """Returns flags that need to be added to .m compilations.""" - self.configname = configname - self.configname = None - return [] - - def GetCflagsObjCC(self, configname): - """Returns flags that need to be added to .mm compilations.""" - self.configname = configname - cflags_objcc = [] - if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'): - cflags_objcc.append('-fobjc-call-cxx-cdtors') - self.configname = None - return cflags_objcc - - def GetLdflags(self, target, configname): - """Returns flags that need to be passed to the linker.""" - self.configname = configname - ldflags = [] - - # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS - # contains two entries that depend on this. Explicitly absolutify for these - # two cases. - def AbsolutifyPrefix(flag, prefix): - if flag.startswith(prefix): - flag = prefix + target.Absolutify(flag[len(prefix):]) - return flag - for ldflag in self._Settings().get('OTHER_LDFLAGS', []): - # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS, - # TODO(thakis): Update ffmpeg.gyp): - ldflag = AbsolutifyPrefix(ldflag, '-L') - # Required for the nacl plugin: - ldflag = AbsolutifyPrefix(ldflag, '-Wl,-exported_symbols_list ') - ldflags.append(ldflag) - - if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'): - ldflags.append('-Wl,-dead_strip') - - if self._Test('PREBINDING', 'YES', default='NO'): - ldflags.append('-Wl,-prebind') - - self._Appendf( - ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s') - self._Appendf( - ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s') - self._Appendf( - ldflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') - if 'SDKROOT' in self._Settings(): - ldflags.append('-isysroot ' + self._SdkPath()) - - for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): - ldflags.append('-L' + library_path) - - if 'ORDER_FILE' in self._Settings(): - ldflags.append('-Wl,-order_file ' + - '-Wl,' + target.Absolutify(self._Settings()['ORDER_FILE'])) - - # TODO: Do not hardcode arch. Supporting fat binaries will be annoying. - ldflags.append('-arch i386') - - # Xcode adds the product directory by default. - ldflags.append('-L' + generator_default_variables['PRODUCT_DIR']) - - install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME') - install_base = self.GetPerTargetSetting('DYLIB_INSTALL_NAME_BASE') - default_install_name = \ - '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)' - if not install_name and install_base: - install_name = default_install_name - - if install_name: - # Hardcode support for the variables used in chromium for now, to unblock - # people using the make build. - if '$' in install_name: - assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/' - '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), ( - 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported yet' - ' in target \'%s\' (got \'%s\')' % - (self.spec['target_name'], install_name)) - # I'm not quite sure what :standardizepath does. Just call normpath(), - # but don't let @executable_path/../foo collapse to foo. - if '/' in install_base: - prefix, rest = '', install_base - if install_base.startswith('@'): - prefix, rest = install_base.split('/', 1) - rest = os.path.normpath(rest) # :standardizepath - install_base = os.path.join(prefix, rest) - - install_name = install_name.replace( - '$(DYLIB_INSTALL_NAME_BASE:standardizepath)', install_base) - if self._IsBundle(): - # These are only valid for bundles, hence the |if|. - install_name = install_name.replace( - '$(WRAPPER_NAME)', self.GetWrapperName()) - install_name = install_name.replace( - '$(PRODUCT_NAME)', self.GetProductName()) - else: - assert '$(WRAPPER_NAME)' not in install_name - assert '$(PRODUCT_NAME)' not in install_name - - install_name = install_name.replace( - '$(EXECUTABLE_PATH)', self.GetExecutablePath()) - - install_name = QuoteSpaces(install_name) - ldflags.append('-install_name ' + install_name) - - self.configname = None - return ldflags - - def GetPerTargetSettings(self): - """Gets a list of all the per-target settings. This will only fetch keys - whose values are the same across all configurations.""" - first_pass = True - result = {} - for configname in sorted(self.xcode_settings.keys()): - if first_pass: - result = dict(self.xcode_settings[configname]) - first_pass = False - else: - for key, value in self.xcode_settings[configname].iteritems(): - if key not in result: - continue - elif result[key] != value: - del result[key] - return result - - def GetPerTargetSetting(self, setting, default=None): - """Tries to get xcode_settings.setting from spec. Assumes that the setting - has the same value in all configurations and throws otherwise.""" - first_pass = True - result = None - for configname in sorted(self.xcode_settings.keys()): - if first_pass: - result = self.xcode_settings[configname].get(setting, None) - first_pass = False - else: - assert result == self.xcode_settings[configname].get(setting, None), ( - "Expected per-target setting for '%s', got per-config setting " - "(target %s)" % (setting, spec['target_name'])) - if result is None: - return default - return result - - def _GetStripPostbuilds(self, configname, output_binary): - """Returns a list of shell commands that contain the shell commands - neccessary to strip this target's binary. These should be run as postbuilds - before the actual postbuilds run.""" - self.configname = configname - - result = [] - if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and - self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')): - - default_strip_style = 'debugging' - if self._IsBundle(): - default_strip_style = 'non-global' - elif self.spec['type'] == 'executable': - default_strip_style = 'all' - - strip_style = self._Settings().get('STRIP_STYLE', default_strip_style) - strip_flags = { - 'all': '', - 'non-global': '-x', - 'debugging': '-S', - }[strip_style] - - explicit_strip_flags = self._Settings().get('STRIPFLAGS', '') - if explicit_strip_flags: - strip_flags += ' ' + explicit_strip_flags - - result.append('echo STRIP\\(%s\\)' % self.spec['target_name']) - result.append('strip %s %s' % (strip_flags, output_binary)) - - self.configname = None - return result - - def _GetDebugPostbuilds(self, configname, output, output_binary): - """Returns a list of shell commands that contain the shell commands - neccessary to massage this target's debug information. These should be run - as postbuilds before the actual postbuilds run.""" - self.configname = configname - - # For static libraries, no dSYMs are created. - result = [] - if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and - self._Test( - 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and - self.spec['type'] != 'static_library'): - result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name']) - result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM')) - - self.configname = None - return result - - def GetTargetPostbuilds(self, configname, output, output_binary): - """Returns a list of shell commands that contain the shell commands - to run as postbuilds for this target, before the actual postbuilds.""" - # dSYMs need to build before stripping happens. - return (self._GetDebugPostbuilds(configname, output, output_binary) + - self._GetStripPostbuilds(configname, output_binary)) - - -class MacPrefixHeader(object): - """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. If - GCC_PREFIX_HEADER isn't present (in most gyp targets on mac, and always on - non-mac systems), all methods of this class are no-ops.""" - - def __init__(self, path_provider): - # This doesn't support per-configuration prefix headers. Good enough - # for now. - self.header = None - self.compile_headers = False - if path_provider.flavor == 'mac': - self.header = path_provider.xcode_settings.GetPerTargetSetting( - 'GCC_PREFIX_HEADER') - self.compile_headers = path_provider.xcode_settings.GetPerTargetSetting( - 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO' - self.compiled_headers = {} - if self.header: - self.header = path_provider.Absolutify(self.header) - if self.compile_headers: - for lang in ['c', 'cc', 'm', 'mm']: - self.compiled_headers[lang] = path_provider.Pchify(self.header, lang) - - def _Gch(self, lang): - """Returns the actual file name of the prefix header for language |lang|.""" - assert self.compile_headers - return self.compiled_headers[lang] + '.gch' - - def WriteObjDependencies(self, compilable, objs, writer): - """Writes dependencies from the object files in |objs| to the corresponding - precompiled header file. |compilable[i]| has to be the source file belonging - to |objs[i]|.""" - if not self.header or not self.compile_headers: - return - - writer.WriteLn('# Dependencies from obj files to their precompiled headers') - for source, obj in zip(compilable, objs): - ext = os.path.splitext(source)[1] - lang = { - '.c': 'c', - '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc', - '.m': 'm', - '.mm': 'mm', - }.get(ext, None) - if lang: - writer.WriteLn('%s: %s' % (obj, self._Gch(lang))) - writer.WriteLn('# End precompiled header dependencies') - - def GetInclude(self, lang): - """Gets the cflags to include the prefix header for language |lang|.""" - if self.compile_headers and lang in self.compiled_headers: - return '-include %s ' % self.compiled_headers[lang] - elif self.header: - return '-include %s ' % self.header - else: - return '' - - def WritePchTargets(self, writer): - """Writes make rules to compile the prefix headers.""" - if not self.header or not self.compile_headers: - return - - writer.WriteLn(self._Gch('c') + ": GYP_PCH_CFLAGS := " - "-x c-header " - "$(DEFS_$(BUILDTYPE)) " - "$(INCS_$(BUILDTYPE)) " - "$(CFLAGS_$(BUILDTYPE)) " - "$(CFLAGS_C_$(BUILDTYPE))") - - writer.WriteLn(self._Gch('cc') + ": GYP_PCH_CCFLAGS := " - "-x c++-header " - "$(DEFS_$(BUILDTYPE)) " - "$(INCS_$(BUILDTYPE)) " - "$(CFLAGS_$(BUILDTYPE)) " - "$(CFLAGS_CC_$(BUILDTYPE))") - - writer.WriteLn(self._Gch('m') + ": GYP_PCH_OBJCFLAGS := " - "-x objective-c-header " - "$(DEFS_$(BUILDTYPE)) " - "$(INCS_$(BUILDTYPE)) " - "$(CFLAGS_$(BUILDTYPE)) " - "$(CFLAGS_C_$(BUILDTYPE)) " - "$(CFLAGS_OBJC_$(BUILDTYPE))") - - writer.WriteLn(self._Gch('mm') + ": GYP_PCH_OBJCXXFLAGS := " - "-x objective-c++-header " - "$(DEFS_$(BUILDTYPE)) " - "$(INCS_$(BUILDTYPE)) " - "$(CFLAGS_$(BUILDTYPE)) " - "$(CFLAGS_CC_$(BUILDTYPE)) " - "$(CFLAGS_OBJCC_$(BUILDTYPE))") - - for lang in self.compiled_headers: - writer.WriteLn('%s: %s FORCE_DO_CMD' % (self._Gch(lang), self.header)) - writer.WriteLn('\t@$(call do_cmd,pch_%s,1)' % lang) - writer.WriteLn('') - assert ' ' not in self._Gch(lang), ( - "Spaces in gch filenames not supported (%s)" % self._Gch(lang)) - writer.WriteLn('all_deps += %s' % self._Gch(lang)) - writer.WriteLn('') - - class MakefileWriter: """MakefileWriter packages up the writing of one target-specific foobar.mk. @@ -1294,27 +691,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.type = spec['type'] self.toolset = spec['toolset'] - if self.type == 'settings': - # TODO: 'settings' is not actually part of gyp; it was - # accidentally introduced somehow into just the Linux build files. - # Remove this (or make it an error) once all the users are fixed. - print ("WARNING: %s uses invalid type 'settings'. " % self.target + - "Please fix the source gyp file to use type 'none'.") - print "See http://code.google.com/p/chromium/issues/detail?id=96629 ." - self.type = 'none' - - # Bundles are directories with a certain subdirectory structure, instead of - # just a single file. Bundle rules do not produce a binary but also package - # resources into that directory. - self.is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and - self.flavor == 'mac') - if self.is_mac_bundle: - assert self.type != 'none', ( - 'mac_bundle targets cannot have type none (target "%s")' % - self.target) - + self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) if self.flavor == 'mac': - self.xcode_settings = XcodeSettings(spec) + self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + else: + self.xcode_settings = None deps, link_deps = self.ComputeDeps(spec) @@ -1333,9 +714,6 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD else: self.output = self.output_binary = self.ComputeOutput(spec) - self.output = QuoteSpaces(self.output) - self.output_binary = QuoteSpaces(self.output_binary) - self._INSTALLABLE_TARGETS = ('executable', 'loadable_module', 'shared_library') if self.type in self._INSTALLABLE_TARGETS: @@ -1351,7 +729,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # Actions must come first, since they can generate more OBJs for use below. if 'actions' in spec: self.WriteActions(spec['actions'], extra_sources, extra_outputs, - extra_mac_bundle_resources, part_of_all, spec) + extra_mac_bundle_resources, part_of_all) # Rules must be early like actions. if 'rules' in spec: @@ -1359,25 +737,23 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD extra_mac_bundle_resources, part_of_all) if 'copies' in spec: - self.WriteCopies(spec['copies'], extra_outputs, part_of_all, spec) + self.WriteCopies(spec['copies'], extra_outputs, part_of_all) # Bundle resources. if self.is_mac_bundle: all_mac_bundle_resources = ( spec.get('mac_bundle_resources', []) + extra_mac_bundle_resources) - if all_mac_bundle_resources: - self.WriteMacBundleResources( - all_mac_bundle_resources, mac_bundle_deps, spec) - info_plist = self.xcode_settings.GetPerTargetSetting('INFOPLIST_FILE') - if info_plist: - self.WriteMacInfoPlist(info_plist, mac_bundle_deps, spec) + self.WriteMacBundleResources(all_mac_bundle_resources, mac_bundle_deps) + self.WriteMacInfoPlist(mac_bundle_deps) # Sources. all_sources = spec.get('sources', []) + extra_sources if all_sources: self.WriteSources( configs, deps, all_sources, extra_outputs, - extra_link_deps, part_of_all, MacPrefixHeader(self)) + extra_link_deps, part_of_all, + gyp.xcode_emulation.MacPrefixHeader( + self.xcode_settings, self.Absolutify, self.Pchify)) sources = filter(Compilable, all_sources) if sources: self.WriteLn(SHARED_HEADER_SUFFIX_RULES_COMMENT1) @@ -1444,7 +820,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD def WriteActions(self, actions, extra_sources, extra_outputs, - extra_mac_bundle_resources, part_of_all, spec): + extra_mac_bundle_resources, part_of_all): """Write Makefile code for any 'actions' from the gyp input. extra_sources: a list that will be filled in with newly generated source @@ -1510,10 +886,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # it's superfluous for the "extra outputs", and this avoids accidentally # writing duplicate dummy rules for those outputs. # Same for environment. - self.WriteMakeRule(outputs[:1], ['obj := $(abs_obj)']) - # Needs to be before builddir is redefined in the next line! - self.WriteXcodeEnv(outputs[0], spec, target_relative_path=True) - self.WriteMakeRule(outputs[:1], ['builddir := $(abs_builddir)']) + self.WriteLn("%s: obj := $(abs_obj)" % QuoteSpaces(outputs[0])) + self.WriteLn("%s: builddir := $(abs_builddir)" % QuoteSpaces(outputs[0])) + self.WriteXcodeEnv(outputs[0], self.GetXcodeEnv()) for input in inputs: assert ' ' not in input, ( @@ -1522,6 +897,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD assert ' ' not in output, ( "Spaces in action output filenames not supported (%s)" % output) + # See the comment in WriteCopies about expanding env vars. + env = self.GetXcodeEnv() + outputs = [gyp.xcode_emulation.ExpandEnvVars(o, env) for o in outputs] + inputs = [gyp.xcode_emulation.ExpandEnvVars(i, env) for i in inputs] + self.WriteDoCmd(outputs, map(Sourceify, map(self.Absolutify, inputs)), part_of_all=part_of_all, command=name) @@ -1592,8 +972,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # Only write the 'obj' and 'builddir' rules for the "primary" output # (:1); it's superfluous for the "extra outputs", and this avoids # accidentally writing duplicate dummy rules for those outputs. - self.WriteMakeRule(outputs[:1], ['obj := $(abs_obj)']) - self.WriteMakeRule(outputs[:1], ['builddir := $(abs_builddir)']) + self.WriteLn('%s: obj := $(abs_obj)' % outputs[0]) + self.WriteLn('%s: builddir := $(abs_builddir)' % outputs[0]) self.WriteMakeRule(outputs, inputs + ['FORCE_DO_CMD'], actions) for output in outputs: assert ' ' not in output, ( @@ -1652,7 +1032,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn('') - def WriteCopies(self, copies, extra_outputs, part_of_all, spec): + def WriteCopies(self, copies, extra_outputs, part_of_all): """Write Makefile code for any 'copies' from the gyp input. extra_outputs: a list that will be filled in with any outputs of this action @@ -1670,8 +1050,6 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD filename = os.path.split(path)[1] output = Sourceify(self.Absolutify(os.path.join(copy['destination'], filename))) - path = QuoteSpaces(path) - output = QuoteSpaces(output) # If the output path has variables in it, which happens in practice for # 'copies', writing the environment as target-local doesn't work, @@ -1682,65 +1060,39 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # As a workaround, manually expand variables at gyp time. Since 'copies' # can't run scripts, there's no need to write the env then. # WriteDoCmd() will escape spaces for .d files. - import gyp.generator.xcode as xcode_generator - env = self.GetXcodeEnv(spec) - output = xcode_generator.ExpandXcodeVariables(output, env) - path = xcode_generator.ExpandXcodeVariables(path, env) + env = self.GetXcodeEnv() + output = gyp.xcode_emulation.ExpandEnvVars(output, env) + path = gyp.xcode_emulation.ExpandEnvVars(path, env) self.WriteDoCmd([output], [path], 'copy', part_of_all) outputs.append(output) - self.WriteLn('%s = %s' % (variable, ' '.join(outputs))) + self.WriteLn('%s = %s' % (variable, ' '.join(map(QuoteSpaces, outputs)))) extra_outputs.append('$(%s)' % variable) self.WriteLn() - def WriteMacBundleResources(self, resources, bundle_deps, spec): + def WriteMacBundleResources(self, resources, bundle_deps): """Writes Makefile code for 'mac_bundle_resources'.""" self.WriteLn('### Generated for mac_bundle_resources') - variable = self.target + '_mac_bundle_resources' - path = generator_default_variables['PRODUCT_DIR'] - dest = os.path.join(path, self.xcode_settings.GetBundleResourceFolder()) - dest = QuoteSpaces(dest) - for res in resources: - output = dest - - assert ' ' not in res, ( - "Spaces in resource filenames not supported (%s)" % res) - - # Split into (path,file). - path = Sourceify(self.Absolutify(res)) - path_parts = os.path.split(path) - - # Now split the path into (prefix,maybe.lproj). - lproj_parts = os.path.split(path_parts[0]) - # If the resource lives in a .lproj bundle, add that to the destination. - if lproj_parts[1].endswith('.lproj'): - output = os.path.join(output, lproj_parts[1]) - - output = Sourceify(self.Absolutify(os.path.join(output, path_parts[1]))) - # Compiled XIB files are referred to by .nib. - if output.endswith('.xib'): - output = output[0:-3] + 'nib' - - self.WriteDoCmd([output], [path], 'mac_tool,,,copy-bundle-resource', + + for output, res in gyp.xcode_emulation.GetMacBundleResources( + generator_default_variables['PRODUCT_DIR'], self.xcode_settings, + map(Sourceify, map(self.Absolutify, resources))): + self.WriteDoCmd([output], [res], 'mac_tool,,,copy-bundle-resource', part_of_all=True) bundle_deps.append(output) - def WriteMacInfoPlist(self, info_plist, bundle_deps, spec): + def WriteMacInfoPlist(self, bundle_deps): """Write Makefile code for bundle Info.plist files.""" - assert ' ' not in info_plist, ( - "Spaces in resource filenames not supported (%s)" % info_plist) - info_plist = self.Absolutify(info_plist) - settings = self.xcode_settings - - # If explicilty set to preprocess the plist, invoke the C preprocessor and - # specify any defines as -D flags. - if settings.GetPerTargetSetting('INFOPLIST_PREPROCESS', 'NO') == 'YES': - # Create an intermediate file based on the path. + info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist( + generator_default_variables['PRODUCT_DIR'], self.xcode_settings, + self.Absolutify) + if not info_plist: + return + if defines: + # Create an intermediate file to store preprocessed results. intermediate_plist = ('$(obj).$(TOOLSET)/$(TARGET)/' + os.path.basename(info_plist)) - defines = shlex.split(settings.GetPerTargetSetting( - 'INFOPLIST_PREPROCESSOR_DEFINITIONS', '')) self.WriteList(defines, intermediate_plist + ': INFOPLIST_DEFINES', '-D', quoter=EscapeCppDefine) self.WriteMakeRule([intermediate_plist], [info_plist], @@ -1749,16 +1101,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # preprocessor do not affect the XML parser in mac_tool. '@plutil -convert xml1 $@ $@']) info_plist = intermediate_plist - - path = generator_default_variables['PRODUCT_DIR'] - dest_plist = os.path.join(path, settings.GetBundlePlistPath()) - dest_plist = QuoteSpaces(dest_plist) - extra_settings = settings.GetPerTargetSettings() - # plists can contain envvars and substitute them into the file.. - self.WriteXcodeEnv(dest_plist, spec, additional_settings=extra_settings) - self.WriteDoCmd([dest_plist], [info_plist], 'mac_tool,,,copy-info-plist', + # plists can contain envvars and substitute them into the file. + self.WriteXcodeEnv(out, self.GetXcodeEnv(additional_settings=extra_env)) + self.WriteDoCmd([out], [info_plist], 'mac_tool,,,copy-info-plist', part_of_all=True) - bundle_deps.append(dest_plist) + bundle_deps.append(out) def WriteSources(self, configs, deps, sources, @@ -1837,7 +1184,12 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD 'before any of us.', order_only = True) - precompiled_header.WriteObjDependencies(compilable, objs, self) + pchdeps = precompiled_header.GetObjDependencies(compilable, objs ) + if pchdeps: + self.WriteLn('# Dependencies from obj files to their precompiled headers') + for source, obj, gch in pchdeps: + self.WriteLn('%s: %s' % (obj, gch)) + self.WriteLn('# End precompiled header dependencies') if objs: extra_link_deps.append('$(OBJS)') @@ -1848,32 +1200,32 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn("$(OBJS): GYP_CFLAGS := " "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s" % precompiled_header.GetInclude('c') + + "%s " % precompiled_header.GetInclude('c') + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_C_$(BUILDTYPE))") self.WriteLn("$(OBJS): GYP_CXXFLAGS := " "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s" % precompiled_header.GetInclude('cc') + + "%s " % precompiled_header.GetInclude('cc') + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_CC_$(BUILDTYPE))") if self.flavor == 'mac': self.WriteLn("$(OBJS): GYP_OBJCFLAGS := " "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s" % precompiled_header.GetInclude('m') + + "%s " % precompiled_header.GetInclude('m') + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_C_$(BUILDTYPE)) " "$(CFLAGS_OBJC_$(BUILDTYPE))") self.WriteLn("$(OBJS): GYP_OBJCXXFLAGS := " "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s" % precompiled_header.GetInclude('mm') + + "%s " % precompiled_header.GetInclude('mm') + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_CC_$(BUILDTYPE)) " "$(CFLAGS_OBJCC_$(BUILDTYPE))") - precompiled_header.WritePchTargets(self) + self.WritePchTargets(precompiled_header.GetGchBuildCommands()) # If there are any object files in our input file list, link them into our # output. @@ -1881,6 +1233,38 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteLn() + def WritePchTargets(self, pch_commands): + """Writes make rules to compile prefix headers.""" + if not pch_commands: + return + + for gch, lang_flag, lang, input in pch_commands: + extra_flags = { + 'c': '$(CFLAGS_C_$(BUILDTYPE))', + 'cc': '$(CFLAGS_CC_$(BUILDTYPE))', + 'm': '$(CFLAGS_C_$(BUILDTYPE)) $(CFLAGS_OBJC_$(BUILDTYPE))', + 'mm': '$(CFLAGS_CC_$(BUILDTYPE)) $(CFLAGS_OBJCC_$(BUILDTYPE))', + }[lang] + var_name = { + 'c': 'GYP_PCH_CFLAGS', + 'cc': 'GYP_PCH_CXXFLAGS', + 'm': 'GYP_PCH_OBJCFLAGS', + 'mm': 'GYP_PCH_OBJCXXFLAGS', + }[lang] + self.WriteLn("%s: %s := %s " % (gch, var_name, lang_flag) + + "$(DEFS_$(BUILDTYPE)) " + "$(INCS_$(BUILDTYPE)) " + "$(CFLAGS_$(BUILDTYPE)) " + + extra_flags) + + self.WriteLn('%s: %s FORCE_DO_CMD' % (gch, input)) + self.WriteLn('\t@$(call do_cmd,pch_%s,1)' % lang) + self.WriteLn('') + assert ' ' not in gch, ( + "Spaces in gch filenames not supported (%s)" % gch) + self.WriteLn('all_deps += %s' % gch) + self.WriteLn('') + def ComputeOutputBasename(self, spec): """Return the 'output basename' of a gyp spec. @@ -2003,43 +1387,21 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD order_only = True, multiple_output_trick = False) - if self.flavor == 'mac': - # Write an envvar for postbuilds. - # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack. - # TODO(thakis): It would be nice to have some general mechanism instead. - # This variable may be referenced by TARGET_POSTBUILDS_$(BUILDTYPE), - # so we must output its definition first, since we declare variables - # using ":=". - # TODO(thakis): Write this only for targets that actually have - # postbuilds. - strip_save_file = self.xcode_settings.GetPerTargetSetting( - 'CHROMIUM_STRIP_SAVE_FILE') - if strip_save_file: - strip_save_file = self.Absolutify(strip_save_file) - else: - # Explicitly clear this out, else a postbuild might pick up an export - # from an earlier target. - strip_save_file = '' - self.WriteXcodeEnv( - self.output, spec, - additional_settings={'CHROMIUM_STRIP_SAVE_FILE': strip_save_file}) - - has_target_postbuilds = False + target_postbuilds = {} if self.type != 'none': for configname in sorted(configs.keys()): config = configs[configname] if self.flavor == 'mac': - ldflags = self.xcode_settings.GetLdflags(self, configname) + ldflags = self.xcode_settings.GetLdflags(configname, + generator_default_variables['PRODUCT_DIR'], self.Absolutify) # TARGET_POSTBUILDS_$(BUILDTYPE) is added to postbuilds later on. - target_postbuilds = self.xcode_settings.GetTargetPostbuilds( - configname, self.output, self.output_binary) - if target_postbuilds: - has_target_postbuilds = True - self.WriteLn('%s: TARGET_POSTBUILDS_%s := %s' % - (self.output, - configname, - gyp.common.EncodePOSIXShellList(target_postbuilds))) + target_postbuild = self.xcode_settings.GetTargetPostbuilds( + configname, + QuoteSpaces(self.output), + QuoteSpaces(self.output_binary)) + if target_postbuild: + target_postbuilds[configname] = target_postbuild else: ldflags = config.get('ldflags', []) # Compute an rpath for this output if needed. @@ -2054,44 +1416,40 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD if libraries: # Remove duplicate entries libraries = gyp.common.uniquer(libraries) - # On Mac, framework libraries need to be passed as '-framework Cocoa'. if self.flavor == 'mac': - libraries = [ - '-framework ' + os.path.splitext(os.path.basename(library))[0] - if library.endswith('.framework') else library - for library in libraries] + libraries = self.xcode_settings.AdjustLibraries(libraries) self.WriteList(libraries, 'LIBS') - self.WriteLn( - '%s: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))' % self.output_binary) - self.WriteLn('%s: LIBS := $(LIBS)' % self.output_binary) + self.WriteLn('%s: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))' % + QuoteSpaces(self.output_binary)) + self.WriteLn('%s: LIBS := $(LIBS)' % QuoteSpaces(self.output_binary)) + # Postbuild actions. Like actions, but implicitly depend on the target's + # output. postbuilds = [] if self.flavor == 'mac': - if has_target_postbuilds: + if target_postbuilds: postbuilds.append('$(TARGET_POSTBUILDS_$(BUILDTYPE))') - # Postbuild actions. Like actions, but implicitly depend on the target's - # output. - for postbuild in spec.get('postbuilds', []): - postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( - self.target, postbuild['postbuild_name'])) - shell_list = postbuild['action'] - # The first element is the command. If it's a relative path, it's - # a script in the source tree relative to the gyp file and needs to be - # absolutified. Else, it's in the PATH (e.g. install_name_tool, ln). - if os.path.sep in shell_list[0]: - shell_list[0] = self.Absolutify(shell_list[0]) - - # "script.sh" -> "./script.sh" - if not os.path.sep in shell_list[0]: - shell_list[0] = os.path.join('.', shell_list[0]) - postbuilds.append(gyp.common.EncodePOSIXShellList(shell_list)) + postbuilds.extend( + gyp.xcode_emulation.GetSpecPostbuildCommands(spec, self.Absolutify)) if postbuilds: + # Envvars may be referenced by TARGET_POSTBUILDS_$(BUILDTYPE), + # so we must output its definition first, since we declare variables + # using ":=". + self.WriteXcodeEnv(self.output, self.GetXcodePostbuildEnv()) + + for configname in target_postbuilds: + self.WriteLn('%s: TARGET_POSTBUILDS_%s := %s' % + (QuoteSpaces(self.output), + configname, + gyp.common.EncodePOSIXShellList(target_postbuilds[configname]))) + for i in xrange(len(postbuilds)): if not postbuilds[i].startswith('$'): postbuilds[i] = EscapeShellArgument(postbuilds[i]) - self.WriteLn('%s: builddir := $(abs_builddir)' % self.output) - self.WriteLn('%s: POSTBUILDS := %s' % (self.output, ' '.join(postbuilds))) + self.WriteLn('%s: builddir := $(abs_builddir)' % QuoteSpaces(self.output)) + self.WriteLn('%s: POSTBUILDS := %s' % ( + QuoteSpaces(self.output), ' '.join(postbuilds))) # A bundle directory depends on its dependencies such as bundle resources # and bundle binary. When all dependencies have been built, the bundle @@ -2103,8 +1461,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # Bundle dependencies. Note that the code below adds actions to this # target, so if you move these two lines, move the lines below as well. - self.WriteList(bundle_deps, 'BUNDLE_DEPS') - self.WriteLn('%s: $(BUNDLE_DEPS)' % self.output) + self.WriteList(map(QuoteSpaces, bundle_deps), 'BUNDLE_DEPS') + self.WriteLn('%s: $(BUNDLE_DEPS)' % QuoteSpaces(self.output)) # After the framework is built, package it. Needs to happen before # postbuilds, since postbuilds depend on this. @@ -2126,7 +1484,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # its dependencies usually. To prevent this rule from executing # on every build (expensive, especially with postbuilds), expliclity # update the time on the framework directory. - self.WriteLn('\t@touch -c %s' % self.output) + self.WriteLn('\t@touch -c %s' % QuoteSpaces(self.output)) if postbuilds: assert not self.is_mac_bundle, ('Postbuilds for bundles should be done ' @@ -2135,8 +1493,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD 'custom product_dir') if self.type == 'executable': - self.WriteLn( - '%s: LD_INPUTS := %s' % (self.output_binary, ' '.join(link_deps))) + self.WriteLn('%s: LD_INPUTS := %s' % ( + QuoteSpaces(self.output_binary), + ' '.join(map(QuoteSpaces, link_deps)))) if self.toolset == 'host' and self.flavor == 'android': self.WriteDoCmd([self.output_binary], link_deps, 'link_host', part_of_all, postbuilds=postbuilds) @@ -2151,8 +1510,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.WriteDoCmd([self.output_binary], link_deps, 'alink', part_of_all, postbuilds=postbuilds) elif self.type == 'shared_library': - self.WriteLn( - '%s: LD_INPUTS := %s' % (self.output_binary, ' '.join(link_deps))) + self.WriteLn('%s: LD_INPUTS := %s' % ( + QuoteSpaces(self.output_binary), + ' '.join(map(QuoteSpaces, link_deps)))) self.WriteDoCmd([self.output_binary], link_deps, 'solink', part_of_all, postbuilds=postbuilds) elif self.type == 'loadable_module': @@ -2252,7 +1612,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD # all_deps is only used for deps file reading, and for deps files we replace # spaces with ? because escaping doesn't work with make's $(sort) and # other functions. - outputs = [ReplaceQuotedSpaces(o) for o in outputs] + outputs = [QuoteSpaces(o, SPACE_REPLACEMENT) for o in outputs] self.WriteLn('all_deps += %s' % ' '.join(outputs)) self._num_outputs += len(outputs) @@ -2275,6 +1635,9 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD multiple_output_trick: if true (the default), perform tricks such as dummy rules to avoid problems with multiple outputs. """ + outputs = map(QuoteSpaces, outputs) + inputs = map(QuoteSpaces, inputs) + if comment: self.WriteLn('# ' + comment) if phony: @@ -2405,154 +1768,37 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD self.fp.write(text + '\n') - def GetXcodeEnv(self, spec, target_relative_path=False): - """Return the environment variables that Xcode would set. See - http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153 - for a full list.""" - if self.flavor != 'mac': return {} - - built_products_dir = generator_default_variables['PRODUCT_DIR'] - def StripProductDir(s): - assert s.startswith(built_products_dir), s - return s[len(built_products_dir) + 1:] - - product_name = spec.get('product_name', self.output) - - if self._InstallImmediately(): - if product_name.startswith(built_products_dir): - product_name = StripProductDir(product_name) - - srcroot = self.path - if target_relative_path: - built_products_dir = os.path.relpath(built_products_dir, srcroot) - srcroot = '.' - # These are filled in on a as-needed basis. - env = { - 'BUILT_PRODUCTS_DIR' : built_products_dir, - 'CONFIGURATION' : '$(BUILDTYPE)', - 'PRODUCT_NAME' : product_name, - # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME - 'FULL_PRODUCT_NAME' : product_name, - 'SRCROOT' : srcroot, - 'SOURCE_ROOT': '$(SRCROOT)', - # This is not true for static libraries, but currently the env is only - # written for bundles: - 'TARGET_BUILD_DIR' : built_products_dir, - 'TEMP_DIR' : '$(TMPDIR)', - } - if self.type in ('executable', 'shared_library', 'loadable_module'): - env['EXECUTABLE_NAME'] = os.path.basename(self.output_binary) - if self.type in ( - 'executable', 'static_library', 'shared_library', 'loadable_module'): - env['EXECUTABLE_PATH'] = self.xcode_settings.GetExecutablePath() - mach_o_type = self.xcode_settings.GetMachOType() - if mach_o_type: - env['MACH_O_TYPE'] = mach_o_type - env['PRODUCT_TYPE'] = self.xcode_settings.GetProductType() - if self.is_mac_bundle: - env['CONTENTS_FOLDER_PATH'] = \ - self.xcode_settings.GetBundleContentsFolderPath() - env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \ - self.xcode_settings.GetBundleResourceFolder() - env['INFOPLIST_PATH'] = self.xcode_settings.GetBundlePlistPath() - env['WRAPPER_NAME'] = self.xcode_settings.GetWrapperName() - - # TODO(thakis): Remove this. - env['EXECUTABLE_PATH'] = QuoteSpaces(env['EXECUTABLE_PATH']) - env['CONTENTS_FOLDER_PATH'] = QuoteSpaces(env['CONTENTS_FOLDER_PATH']) - env['INFOPLIST_PATH'] = QuoteSpaces(env['INFOPLIST_PATH']) - env['WRAPPER_NAME'] = QuoteSpaces(env['WRAPPER_NAME']) - - return env - - - def WriteXcodeEnv(self, - target, - spec, - target_relative_path=False, - additional_settings={}): - env = additional_settings - env.update(self.GetXcodeEnv(spec, target_relative_path)) - - # Keys whose values will not have $(builddir) replaced with $(abs_builddir). - # These have special substitution rules in some cases; see above in - # GetXcodeEnv() for the full rationale. - keys_to_not_absolutify = ('PRODUCT_NAME', 'FULL_PRODUCT_NAME') - - # First sort the list of keys, removing any non-string values. - # Values that are not strings but are, for example, lists or tuples such - # as LDFLAGS or CFLAGS, should not be written out because they are - # not needed and it's undefined how multi-valued keys should be written. - key_list = env.keys() - key_list.sort() - key_list = [k for k in key_list if isinstance(env[k], str)] - - # Since environment variables can refer to other variables, the evaluation - # order is important. Below is the logic to compute the dependency graph - # and sort it. - regex = re.compile(r'\$\(([a-zA-Z0-9\-_]+)\)') - - # Phase 1: Create a set of edges of (DEPENDEE, DEPENDER) where in the graph, - # DEPENDEE -> DEPENDER. Also create sets of dependers and dependees. - edges = set() - dependees = set() - dependers = set() - for k in key_list: - matches = regex.findall(env[k]) - if not len(matches): - continue - - dependers.add(k) - for dependee in matches: - if dependee in env: - edges.add((dependee, k)) - dependees.add(dependee) - - # Phase 2: Create a list of graph nodes with no incoming edges. - sorted_nodes = [] - edgeless_nodes = dependees - dependers - - # Phase 3: Perform Kahn topological sort. - while len(edgeless_nodes): - # Find a node with no incoming edges, add it to the sorted list, and - # remove it from the list of nodes that aren't part of the graph. - node = edgeless_nodes.pop() - sorted_nodes.append(node) - key_list.remove(node) - - # Find all the edges between |node| and other nodes. - edges_to_node = [e for e in edges if e[0] == node] - for edge in edges_to_node: - edges.remove(edge) - # If the node connected to |node| by |edge| has no other incoming edges, - # add it to |edgeless_nodes|. - if not len([e for e in edges if e[1] == edge[1]]): - edgeless_nodes.add(edge[1]) - - # Any remaining edges indicate a cycle. - if len(edges): - raise Exception('Xcode environment variables are cyclically dependent: ' + - str(edges)) - - # Append the "nodes" not in the graph to those that were just sorted. - sorted_nodes.extend(key_list) - - # Perform some transformations that are required to mimic Xcode behavior. - for k in sorted_nodes: + def GetXcodeEnv(self, additional_settings=None): + return gyp.xcode_emulation.GetXcodeEnv( + self.xcode_settings, "$(abs_builddir)", + os.path.join("$(abs_srcdir)", self.path), "$(BUILDTYPE)", + additional_settings) + + + def GetXcodePostbuildEnv(self): + # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack. + # TODO(thakis): It would be nice to have some general mechanism instead. + strip_save_file = self.xcode_settings.GetPerTargetSetting( + 'CHROMIUM_STRIP_SAVE_FILE') + if strip_save_file: + strip_save_file = self.Absolutify(strip_save_file) + else: + # Explicitly clear this out, else a postbuild might pick up an export + # from an earlier target. + strip_save_file = '' + return self.GetXcodeEnv( + additional_settings={'CHROMIUM_STRIP_SAVE_FILE': strip_save_file}) + + + def WriteXcodeEnv(self, target, env): + for k in gyp.xcode_emulation.TopologicallySortedEnvVarKeys(env): # For # foo := a\ b # the escaped space does the right thing. For # export foo := a\ b # it does not -- the backslash is written to the env as literal character. - # Hence, unescape all spaces here. - v = env[k].replace(r'\ ', ' ') - - # Xcode works purely with absolute paths. When writing env variables to - # mimic its usage, replace $(builddir) with $(abs_builddir). - if k not in keys_to_not_absolutify: - v = v.replace('$(builddir)', '$(abs_builddir)') - - self.WriteLn('%s: export %s := %s' % (target, k, v)) + # So don't escape spaces in |env[k]|. + self.WriteLn('%s: export %s := %s' % (QuoteSpaces(target), k, env[k])) def Objectify(self, path): @@ -2565,6 +1811,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD def Pchify(self, path, lang): """Convert a prefix header path to its output directory form.""" + path = self.Absolutify(path) if '$(' in path: path = path.replace('$(obj)/', '$(obj).%s/$(TARGET)/pch-%s' % (self.toolset, lang)) @@ -2663,49 +1910,8 @@ def RunSystemTests(flavor): cc_command=cc_host): arflags_host = 'crsT' - link_flags = '' - if gyp.system_test.TestLinkerSupportsThreads(cc_command=cc_target): - # N.B. we don't test for cross-compilation; as currently written, we - # don't even use flock when linking in the cross-compile setup! - # TODO(evan): refactor cross-compilation such that this code can - # be reused. - link_flags = '-Wl,--threads -Wl,--thread-count=4' - - # TODO(evan): cache this output. (But then we'll need to add extra - # flags to gyp to flush the cache, yuk! It's fast enough for now to - # just run it every time.) - return { 'ARFLAGS.target': arflags_target, - 'ARFLAGS.host': arflags_host, - 'LINK_flags': link_flags } - - -def CopyTool(flavor, out_path): - """Finds (mac|sun)_tool.gyp in the gyp directory and copies it - to |out_path|.""" - prefix = { 'solaris': 'sun', 'mac': 'mac' }.get(flavor, None) - if not prefix: - return - - tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix) - if os.path.exists(tool_path): - os.remove(tool_path) - - # Slurp input file. - source_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), '..', '%s_tool.py' % prefix) - source_file = open(source_path) - source = source_file.readlines() - source_file.close() - - # Add header and write it out. - tool_file = open(tool_path, 'w') - tool_file.write( - ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:])) - tool_file.close() - - # Make file executable. - os.chmod(tool_path, 0755) + 'ARFLAGS.host': arflags_host } def GenerateOutput(target_list, target_dicts, data, params): @@ -2824,9 +2030,9 @@ def GenerateOutput(target_list, target_dicts, data, params): root_makefile.write('TOOLSET := %s\n' % toolset) WriteRootHeaderSuffixRules(root_makefile) - # Put mac_tool next to the root Makefile. + # Put build-time support tools next to the root Makefile. dest_path = os.path.dirname(makefile_path) - CopyTool(flavor, dest_path) + gyp.common.CopyTool(flavor, dest_path) # Find the list of targets that derive from the gyp file(s) being built. needed_targets = set() @@ -2869,17 +2075,8 @@ def GenerateOutput(target_list, target_dicts, data, params): spec = target_dicts[qualified_target] configs = spec['configurations'] - # The xcode generator special-cases global xcode_settings and does something - # that amounts to merging in the global xcode_settings into each local - # xcode_settings dict. if flavor == 'mac': - global_xcode_settings = data[build_file].get('xcode_settings', {}) - for configname in configs.keys(): - config = configs[configname] - if 'xcode_settings' in config: - new_settings = global_xcode_settings.copy() - new_settings.update(config['xcode_settings']) - config['xcode_settings'] = new_settings + gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec) writer = MakefileWriter(generator_flags, flavor) writer.Write(qualified_target, base_path, output_file, spec, configs, diff --git a/tools/gyp/pylib/gyp/generator/msvs.py b/tools/gyp/pylib/gyp/generator/msvs.py index e9ead80d6a..a4c6efd1a4 100644 --- a/tools/gyp/pylib/gyp/generator/msvs.py +++ b/tools/gyp/pylib/gyp/generator/msvs.py @@ -1,4 +1,4 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -162,7 +162,8 @@ def _FixPaths(paths): return [_FixPath(i) for i in paths] -def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None): +def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None, + list_excluded=True): """Converts a list split source file paths into a vcproj folder hierarchy. Arguments: @@ -197,14 +198,15 @@ def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None): folders[s[0]] = [] folders[s[0]].append(s[1:]) # Add a folder for excluded files. - if excluded_result: + if excluded_result and list_excluded: excluded_folder = MSVSProject.Filter('_excluded_files', contents=excluded_result) result.append(excluded_folder) # Populate all the folders. for f in folders: contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f], - excluded=excluded) + excluded=excluded, + list_excluded=list_excluded) contents = MSVSProject.Filter(f, contents=contents) result.append(contents) @@ -831,13 +833,14 @@ def _GetGuidOfProject(proj_path, spec): return guid -def _GenerateProject(project, options, version): +def _GenerateProject(project, options, version, generator_flags): """Generates a vcproj file. Arguments: project: the MSVSProject object. options: global generator options. version: the MSVSVersion object. + generator_flags: dict of generator-specific flags. """ default_config = _GetDefaultConfiguration(project.spec) @@ -846,18 +849,19 @@ def _GenerateProject(project, options, version): return if version.UsesVcxproj(): - _GenerateMSBuildProject(project, options, version) + _GenerateMSBuildProject(project, options, version, generator_flags) else: - _GenerateMSVSProject(project, options, version) + _GenerateMSVSProject(project, options, version, generator_flags) -def _GenerateMSVSProject(project, options, version): +def _GenerateMSVSProject(project, options, version, generator_flags): """Generates a .vcproj file. It may create .rules and .user files too. Arguments: project: The project object we will generate the file for. options: Global options passed to the generator. version: The VisualStudioVersion object. + generator_flags: dict of generator-specific flags. """ spec = project.spec vcproj_dir = os.path.dirname(project.path) @@ -886,9 +890,10 @@ def _GenerateMSVSProject(project, options, version): _GenerateRulesForMSVS(p, project_dir, options, spec, sources, excluded_sources, actions_to_add) + list_excluded = generator_flags.get('msvs_list_excluded_files', True) sources, excluded_sources, excluded_idl = ( _AdjustSourcesAndConvertToFilterHierarchy( - spec, options, project_dir, sources, excluded_sources)) + spec, options, project_dir, sources, excluded_sources, list_excluded)) # Add in files. _VerifySourcesExist(sources, project_dir) @@ -904,7 +909,8 @@ def _GenerateMSVSProject(project, options, version): # Don't excluded sources with actions attached, or they won't run. excluded_sources = _FilterActionsFromExcluded( excluded_sources, actions_to_add) - _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl) + _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, + list_excluded) _AddAccumulatedActionsToMSVS(p, spec, actions_to_add) # Write it out. @@ -963,7 +969,6 @@ def _GetMSVSConfigurationType(spec, build_file): 'loadable_module': '2', # .dll 'static_library': '4', # .lib 'none': '10', # Utility type - 'dummy_executable': '1', # .exe }[spec['type']] except KeyError: if spec.get('type'): @@ -1120,7 +1125,6 @@ def _GetOutputFilePathAndTool(spec): # VisualStudio 2010, we will have to change the value of $(OutDir) # to contain the \lib suffix, rather than doing it as below. 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)\\lib\\', '.lib'), - 'dummy_executable': ('VCLinkerTool', 'Link', '$(IntDir)\\', '.junk'), } output_file_props = output_file_map.get(spec['type']) if output_file_props and int(spec.get('msvs_auto_output_file', 1)): @@ -1287,7 +1291,7 @@ def _PrepareListOfSources(spec, gyp_file): def _AdjustSourcesAndConvertToFilterHierarchy( - spec, options, gyp_dir, sources, excluded_sources): + spec, options, gyp_dir, sources, excluded_sources, list_excluded): """Adjusts the list of sources and excluded sources. Also converts the sets to lists. @@ -1321,13 +1325,8 @@ def _AdjustSourcesAndConvertToFilterHierarchy( # Convert to folders and the right slashes. sources = [i.split('\\') for i in sources] - sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded) - # Add in dummy file for type none. - if spec['type'] == 'dummy_executable': - # Pull in a dummy main so it can link successfully. - dummy_relpath = gyp.common.RelativePath( - options.depth + '\\tools\\gyp\\gyp_dummy.c', gyp_dir) - sources.append(dummy_relpath) + sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded, + list_excluded=list_excluded) return sources, excluded_sources, excluded_idl @@ -1358,12 +1357,19 @@ def _GetPrecompileRelatedFiles(spec): return precompiled_related -def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl): +def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, + list_excluded): exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl) for file_name, excluded_configs in exclusions.iteritems(): - for config_name, config in excluded_configs: - p.AddFileConfig(file_name, _ConfigFullName(config_name, config), - {'ExcludedFromBuild': 'true'}) + if (not list_excluded and + len(excluded_configs) == len(spec['configurations'])): + # If we're not listing excluded files, then they won't appear in the + # project, so don't try to configure them to be excluded. + pass + else: + for config_name, config in excluded_configs: + p.AddFileConfig(file_name, _ConfigFullName(config_name, config), + {'ExcludedFromBuild': 'true'}) def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl): @@ -1751,6 +1757,8 @@ def GenerateOutput(target_list, target_dicts, data, params): # GeneratorCalculatedVariables. msvs_version = params['msvs_version'] + generator_flags = params.get('generator_flags', {}) + # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT. (target_list, target_dicts) = _ShardTargets(target_list, target_dicts) @@ -1769,7 +1777,7 @@ def GenerateOutput(target_list, target_dicts, data, params): # Generate each project. for project in project_objects.values(): fixpath_prefix = project.fixpath_prefix - _GenerateProject(project, options, msvs_version) + _GenerateProject(project, options, msvs_version, generator_flags) fixpath_prefix = None for build_file in data: @@ -1873,7 +1881,10 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): A pair of (group this file should be part of, the label of element) """ _, ext = os.path.splitext(source) - if ext in ['.cc', '.cpp', '.c', '.cxx']: + if ext in extension_to_rule_name: + group = 'rule' + element = extension_to_rule_name[ext] + elif ext in ['.cc', '.cpp', '.c', '.cxx']: group = 'compile' element = 'ClCompile' elif ext in ['.h', '.hxx']: @@ -1885,9 +1896,6 @@ def _MapFileToMsBuildSourceType(source, extension_to_rule_name): elif ext == '.idl': group = 'midl' element = 'Midl' - elif ext in extension_to_rule_name: - group = 'rule' - element = extension_to_rule_name[ext] else: group = 'none' element = 'None' @@ -2663,14 +2671,14 @@ def _VerifySourcesExist(sources, root_dir): def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, - actions_spec, sources_handled_by_action): + actions_spec, sources_handled_by_action, list_excluded): groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule'] grouped_sources = {} for g in groups: grouped_sources[g] = [] _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action) + extension_to_rule_name, sources_handled_by_action, list_excluded) sources = [] for g in groups: if grouped_sources[g]: @@ -2681,12 +2689,14 @@ def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name, def _AddSources2(spec, sources, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action): + extension_to_rule_name, sources_handled_by_action, + list_excluded): extensions_excluded_from_precompile = [] for source in sources: if isinstance(source, MSVSProject.Filter): _AddSources2(spec, source.contents, exclusions, grouped_sources, - extension_to_rule_name, sources_handled_by_action) + extension_to_rule_name, sources_handled_by_action, + list_excluded) else: if not source in sources_handled_by_action: detail = [] @@ -2741,17 +2751,22 @@ def _GetMSBuildProjectReferences(project): guid = dependency.guid project_dir = os.path.split(project.path)[0] relative_path = gyp.common.RelativePath(dependency.path, project_dir) - group.append( - ['ProjectReference', - {'Include': relative_path}, - ['Project', guid], - ['ReferenceOutputAssembly', 'false'] - ]) + project_ref = ['ProjectReference', + {'Include': relative_path}, + ['Project', guid], + ['ReferenceOutputAssembly', 'false'] + ] + for config in dependency.spec.get('configurations', {}).itervalues(): + # If it's disabled in any config, turn it off in the reference. + if config.get('msvs_2010_disable_uldi_when_referenced', 0): + project_ref.append(['UseLibraryDependencyInputs', 'false']) + break + group.append(project_ref) references.append(group) return references -def _GenerateMSBuildProject(project, options, version): +def _GenerateMSBuildProject(project, options, version, generator_flags): spec = project.spec configurations = spec['configurations'] project_dir, project_file_name = os.path.split(project.path) @@ -2769,6 +2784,7 @@ def _GenerateMSBuildProject(project, options, version): props_files_of_rules = set() targets_files_of_rules = set() extension_to_rule_name = {} + list_excluded = generator_flags.get('msvs_list_excluded_files', True) _GenerateRulesForMSBuild(project_dir, options, spec, sources, excluded_sources, props_files_of_rules, targets_files_of_rules, @@ -2776,7 +2792,8 @@ def _GenerateMSBuildProject(project, options, version): sources, excluded_sources, excluded_idl = ( _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir, sources, - excluded_sources)) + excluded_sources, + list_excluded)) _AddActions(actions_to_add, spec, project.build_file) _AddCopies(actions_to_add, spec) @@ -2826,7 +2843,7 @@ def _GenerateMSBuildProject(project, options, version): content += _GetMSBuildToolSettingsSections(spec, configurations) content += _GetMSBuildSources( spec, sources, exclusions, extension_to_rule_name, actions_spec, - sources_handled_by_action) + sources_handled_by_action, list_excluded) content += _GetMSBuildProjectReferences(project) content += import_cpp_targets_section content += _GetMSBuildExtensionTargets(targets_files_of_rules) diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index 193f295a55..8d6c6f52d1 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -1,12 +1,14 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import copy import gyp import gyp.common import gyp.system_test +import gyp.xcode_emulation import os.path -import pprint +import re import subprocess import sys @@ -15,7 +17,7 @@ import gyp.ninja_syntax as ninja_syntax generator_default_variables = { 'EXECUTABLE_PREFIX': '', 'EXECUTABLE_SUFFIX': '', - 'STATIC_LIB_PREFIX': '', + 'STATIC_LIB_PREFIX': 'lib', 'STATIC_LIB_SUFFIX': '.a', 'SHARED_LIB_PREFIX': 'lib', @@ -30,8 +32,6 @@ generator_default_variables = { 'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen', 'PRODUCT_DIR': '$!PRODUCT_DIR', - 'SHARED_LIB_DIR': '$!PRODUCT_DIR/lib', - 'LIB_DIR': '', # Special variables that may be used by gyp 'rule' targets. # We generate definitions for these variables on the fly when processing a @@ -57,6 +57,12 @@ def StripPrefix(arg, prefix): def QuoteShellArgument(arg): + """Quote a string such that it will be interpreted as a single argument + by the shell.""" + # Rather than attempting to enumerate the bad shell characters, just + # whitelist common OK ones and quote anything else. + if re.match(r'^[a-zA-Z0-9_=-]+$', arg): + return arg # No quoting necessary. return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'" @@ -71,8 +77,68 @@ def InvertRelativePath(path): return path # Only need to handle relative paths into subdirectories for now. assert '..' not in path, path - depth = len(path.split('/')) - return '/'.join(['..'] * depth) + depth = len(path.split(os.path.sep)) + return os.path.sep.join(['..'] * depth) + + +class Target: + """Target represents the paths used within a single gyp target. + + Conceptually, building a single target A is a series of steps: + + 1) actions/rules/copies generates source/resources/etc. + 2) compiles generates .o files + 3) link generates a binary (library/executable) + 4) bundle merges the above in a mac bundle + + (Any of these steps can be optional.) + + From a build ordering perspective, a dependent target B could just + depend on the last output of this series of steps. + + But some dependent commands sometimes need to reach inside the box. + For example, when linking B it needs to get the path to the static + library generated by A. + + This object stores those paths. To keep things simple, member + variables only store concrete paths to single files, while methods + compute derived values like "the last output of the target". + """ + def __init__(self, type): + # Gyp type ("static_library", etc.) of this target. + self.type = type + # File representing whether any input dependencies necessary for + # dependent actions have completed. + self.preaction_stamp = None + # File representing whether any input dependencies necessary for + # dependent compiles have completed. + self.precompile_stamp = None + # File representing the completion of actions/rules/copies, if any. + self.actions_stamp = None + # Path to the output of the link step, if any. + self.binary = None + # Path to the file representing the completion of building the bundle, + # if any. + self.bundle = None + + def Linkable(self): + """Return true if this is a target that can be linked against.""" + return self.type in ('static_library', 'shared_library') + + def PreActionInput(self): + """Return the path, if any, that should be used as a dependency of + any dependent action step.""" + return self.FinalOutput() or self.preaction_stamp + + def PreCompileInput(self): + """Return the path, if any, that should be used as a dependency of + any dependent compile step.""" + return self.actions_stamp or self.precompile_stamp + + def FinalOutput(self): + """Return the last output of the target, which depends on all prior + steps.""" + return self.bundle or self.binary or self.actions_stamp # A small discourse on paths as used within the Ninja build: @@ -100,11 +166,13 @@ def InvertRelativePath(path): # to the input file name as well as the output target name. class NinjaWriter: - def __init__(self, target_outputs, base_dir, build_dir, output_file, flavor): + def __init__(self, target_outputs, base_dir, build_dir, output_file, flavor, + abs_build_dir=None): """ base_dir: path from source root to directory containing this gyp file, by gyp semantics, all input paths are relative to this build_dir: path from source root to build output + abs_build_dir: absolute path to the build directory """ self.target_outputs = target_outputs @@ -112,6 +180,8 @@ class NinjaWriter: self.build_dir = build_dir self.ninja = ninja_syntax.Writer(output_file) self.flavor = flavor + self.abs_build_dir = abs_build_dir + self.obj_ext = '.obj' if flavor == 'win' else '.o' # Relative path from build output dir to base dir. self.build_to_base = os.path.join(InvertRelativePath(build_dir), base_dir) @@ -132,6 +202,7 @@ class NinjaWriter: path = path.replace(PRODUCT_DIR, product_dir) else: path = path.replace(PRODUCT_DIR + '/', '') + path = path.replace(PRODUCT_DIR + '\\', '') path = path.replace(PRODUCT_DIR, '.') INTERMEDIATE_DIR = '$!INTERMEDIATE_DIR' @@ -142,7 +213,7 @@ class NinjaWriter: path = path.replace(INTERMEDIATE_DIR, os.path.join(product_dir or '', int_dir)) - return path + return os.path.normpath(path) def ExpandRuleVariables(self, path, root, dirname, source, ext, name): path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root) @@ -153,10 +224,13 @@ class NinjaWriter: path = path.replace(generator_default_variables['RULE_INPUT_NAME'], name) return path - def GypPathToNinja(self, path): - """Translate a gyp path to a ninja path. + def GypPathToNinja(self, path, env=None): + """Translate a gyp path to a ninja path, optionally expanding environment + variable references in |path| with |env|. See the above discourse on path conversions.""" + if env: + path = gyp.xcode_emulation.ExpandEnvVars(path, env) if path.startswith('$!'): return self.ExpandSpecial(path) assert '$' not in path, path @@ -196,34 +270,38 @@ class NinjaWriter: path_basename)) def WriteCollapsedDependencies(self, name, targets): - """Given a list of targets, return a dependency list for a single - file representing the result of building all the targets. + """Given a list of targets, return a path for a single file + representing the result of building all the targets or None. Uses a stamp file if necessary.""" + assert targets == filter(None, targets), targets + if len(targets) == 0: + return None if len(targets) > 1: stamp = self.GypPathToUniqueOutput(name + '.stamp') targets = self.ninja.build(stamp, 'stamp', targets) self.ninja.newline() - return targets + return targets[0] - def WriteSpec(self, spec, config): + def WriteSpec(self, spec, config_name): """The main entry point for NinjaWriter: write the build rules for a spec. - Returns the path to the build output, or None, and a list of targets for - dependencies of its compile steps.""" + Returns a Target object, which represents the output paths for this spec. + Returns None if there are no outputs (e.g. a settings-only 'none' type + target).""" + self.config_name = config_name self.name = spec['target_name'] self.toolset = spec['toolset'] + config = spec['configurations'][config_name] + self.target = Target(spec['type']) - if spec['type'] == 'settings': - # TODO: 'settings' is not actually part of gyp; it was - # accidentally introduced somehow into just the Linux build files. - # Remove this (or make it an error) once all the users are fixed. - print ("WARNING: %s uses invalid type 'settings'. " % self.name + - "Please fix the source gyp file to use type 'none'.") - print "See http://code.google.com/p/chromium/issues/detail?id=96629 ." - spec['type'] = 'none' + self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) + if self.flavor == 'mac': + self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) + else: + self.xcode_settings = None # Compute predepends for all rules. # actions_depends is the dependencies this target depends on before running @@ -232,66 +310,99 @@ class NinjaWriter: # any of its compile steps. actions_depends = [] compile_depends = [] + # TODO(evan): it is rather confusing which things are lists and which + # are strings. Fix these. if 'dependencies' in spec: for dep in spec['dependencies']: if dep in self.target_outputs: - input, precompile_input, linkable = self.target_outputs[dep] - actions_depends.append(input) - compile_depends.extend(precompile_input) + target = self.target_outputs[dep] + actions_depends.append(target.PreActionInput()) + compile_depends.append(target.PreCompileInput()) + actions_depends = filter(None, actions_depends) + compile_depends = filter(None, compile_depends) actions_depends = self.WriteCollapsedDependencies('actions_depends', actions_depends) + compile_depends = self.WriteCollapsedDependencies('compile_depends', + compile_depends) + self.target.preaction_stamp = actions_depends + self.target.precompile_stamp = compile_depends # Write out actions, rules, and copies. These must happen before we # compile any sources, so compute a list of predependencies for sources # while we do it. extra_sources = [] - sources_depends = self.WriteActionsRulesCopies(spec, extra_sources, - actions_depends) + mac_bundle_depends = [] + self.target.actions_stamp = self.WriteActionsRulesCopies( + spec, extra_sources, actions_depends, mac_bundle_depends) # If we have actions/rules/copies, we depend directly on those, but # otherwise we depend on dependent target's actions/rules/copies etc. # We never need to explicitly depend on previous target's link steps, # because no compile ever depends on them. - compile_depends = self.WriteCollapsedDependencies('compile_depends', - sources_depends or compile_depends) + compile_depends_stamp = (self.target.actions_stamp or compile_depends) # Write out the compilation steps, if any. link_deps = [] sources = spec.get('sources', []) + extra_sources if sources: - link_deps = self.WriteSources(config, sources, compile_depends) + link_deps = self.WriteSources( + config_name, config, sources, compile_depends_stamp, + gyp.xcode_emulation.MacPrefixHeader( + self.xcode_settings, self.GypPathToNinja, + lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang))) # Some actions/rules output 'sources' that are already object files. - link_deps += [self.GypPathToNinja(f) for f in sources if f.endswith('.o')] + link_deps += [self.GypPathToNinja(f) + for f in sources if f.endswith(self.obj_ext)] - # The final output of our target depends on the last output of the - # above steps. + # Write out a link step, if needed. output = None - final_deps = link_deps or sources_depends or actions_depends - if final_deps: - output = self.WriteTarget(spec, config, final_deps, - order_only=actions_depends) - if self.name != output and self.toolset == 'target': - # Write a short name to build this target. This benefits both the - # "build chrome" case as well as the gyp tests, which expect to be - # able to run actions and build libraries by their short name. - self.ninja.build(self.name, 'phony', output) - return output, compile_depends - - def WriteActionsRulesCopies(self, spec, extra_sources, prebuild): - """Write out the Actions, Rules, and Copies steps. Return any outputs - of these steps (or a stamp file if there are lots of outputs).""" + if link_deps or self.target.actions_stamp or actions_depends: + output = self.WriteTarget(spec, config_name, config, link_deps, + self.target.actions_stamp or actions_depends) + if self.is_mac_bundle: + mac_bundle_depends.append(output) + + # Bundle all of the above together, if needed. + if self.is_mac_bundle: + output = self.WriteMacBundle(spec, mac_bundle_depends) + + if not output: + return None + + if self.name != output and self.toolset == 'target': + # Write a short name to build this target. This benefits both the + # "build chrome" case as well as the gyp tests, which expect to be + # able to run actions and build libraries by their short name. + self.ninja.build(self.name, 'phony', output) + + assert self.target.FinalOutput(), output + return self.target + + def WriteActionsRulesCopies(self, spec, extra_sources, prebuild, + mac_bundle_depends): + """Write out the Actions, Rules, and Copies steps. Return a path + representing the outputs of these steps.""" outputs = [] + extra_mac_bundle_resources = [] if 'actions' in spec: - outputs += self.WriteActions(spec['actions'], extra_sources, prebuild) + outputs += self.WriteActions(spec['actions'], extra_sources, prebuild, + extra_mac_bundle_resources) if 'rules' in spec: - outputs += self.WriteRules(spec['rules'], extra_sources, prebuild) + outputs += self.WriteRules(spec['rules'], extra_sources, prebuild, + extra_mac_bundle_resources) if 'copies' in spec: outputs += self.WriteCopies(spec['copies'], prebuild) - outputs = self.WriteCollapsedDependencies('actions_rules_copies', outputs) + stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) - return outputs + if self.is_mac_bundle: + mac_bundle_resources = spec.get('mac_bundle_resources', []) + \ + extra_mac_bundle_resources + self.WriteMacBundleResources(mac_bundle_resources, mac_bundle_depends) + self.WriteMacInfoPlist(mac_bundle_depends) + + return stamp def GenerateDescription(self, verb, message, fallback): """Generate and return a description of a build step. @@ -307,20 +418,26 @@ class NinjaWriter: else: return '%s %s: %s' % (verb, self.name, fallback) - def WriteActions(self, actions, extra_sources, prebuild): + def WriteActions(self, actions, extra_sources, prebuild, + extra_mac_bundle_resources): + # Actions cd into the base directory. + env = self.GetXcodeEnv() all_outputs = [] for action in actions: # First write out a rule for the action. - name = action['action_name'] + name = re.sub(r'[ {}$]', '_', action['action_name']) description = self.GenerateDescription('ACTION', action.get('message', None), name) - rule_name = self.WriteNewNinjaRule(name, action['action'], description) + rule_name = self.WriteNewNinjaRule(name, action['action'], description, + env=env) - inputs = [self.GypPathToNinja(i) for i in action['inputs']] + inputs = [self.GypPathToNinja(i, env) for i in action['inputs']] if int(action.get('process_outputs_as_sources', False)): extra_sources += action['outputs'] - outputs = [self.GypPathToNinja(o) for o in action['outputs']] + if int(action.get('process_outputs_as_mac_bundle_resources', False)): + extra_mac_bundle_resources += action['outputs'] + outputs = [self.GypPathToNinja(o, env) for o in action['outputs']] # Then write out an edge using the rule. self.ninja.build(outputs, rule_name, inputs, @@ -331,7 +448,8 @@ class NinjaWriter: return all_outputs - def WriteRules(self, rules, extra_sources, prebuild): + def WriteRules(self, rules, extra_sources, prebuild, + extra_mac_bundle_resources): all_outputs = [] for rule in rules: # First write out a rule for the rule action. @@ -369,6 +487,8 @@ class NinjaWriter: if int(rule.get('process_outputs_as_sources', False)): extra_sources += outputs + if int(rule.get('process_outputs_as_mac_bundle_resources', False)): + extra_mac_bundle_resources += outputs extra_bindings = [] for var in needed_variables: @@ -402,31 +522,67 @@ class NinjaWriter: def WriteCopies(self, copies, prebuild): outputs = [] + env = self.GetXcodeEnv() for copy in copies: for path in copy['files']: # Normalize the path so trailing slashes don't confuse us. path = os.path.normpath(path) basename = os.path.split(path)[1] - src = self.GypPathToNinja(path) - dst = self.GypPathToNinja(os.path.join(copy['destination'], basename)) - outputs += self.ninja.build(dst, 'copy', src, - order_only=prebuild) + src = self.GypPathToNinja(path, env) + dst = self.GypPathToNinja(os.path.join(copy['destination'], basename), + env) + outputs += self.ninja.build(dst, 'copy', src, order_only=prebuild) return outputs - def WriteSources(self, config, sources, predepends): + def WriteMacBundleResources(self, resources, bundle_depends): + """Writes ninja edges for 'mac_bundle_resources'.""" + for output, res in gyp.xcode_emulation.GetMacBundleResources( + self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), + self.xcode_settings, map(self.GypPathToNinja, resources)): + self.ninja.build(output, 'mac_tool', res, + variables=[('mactool_cmd', 'copy-bundle-resource')]) + bundle_depends.append(output) + + def WriteMacInfoPlist(self, bundle_depends): + """Write build rules for bundle Info.plist files.""" + info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist( + self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), + self.xcode_settings, self.GypPathToNinja) + if not info_plist: + return + if defines: + # Create an intermediate file to store preprocessed results. + intermediate_plist = self.GypPathToUniqueOutput( + os.path.basename(info_plist)) + defines = ' '.join( + [QuoteShellArgument(ninja_syntax.escape('-D' + d)) for d in defines]) + info_plist = self.ninja.build(intermediate_plist, 'infoplist', info_plist, + variables=[('defines',defines)]) + + env = self.GetXcodeEnv(additional_settings=extra_env) + env = self.ComputeExportEnvString(env) + + self.ninja.build(out, 'mac_tool', info_plist, + variables=[('mactool_cmd', 'copy-info-plist'), + ('env', env)]) + bundle_depends.append(out) + + def WriteSources(self, config_name, config, sources, predepends, + precompiled_header): """Write build rules to compile all of |sources|.""" if self.toolset == 'host': self.ninja.variable('cc', '$cc_host') self.ninja.variable('cxx', '$cxx_host') if self.flavor == 'mac': - # TODO(jeremya/thakis): Extract these from XcodeSettings instead. - cflags = [] - cflags_c = [] - cflags_cc = [] - cflags_objc = [] - cflags_objcc = [] + cflags = self.xcode_settings.GetCflags(config_name) + cflags_c = self.xcode_settings.GetCflagsC(config_name) + cflags_cc = self.xcode_settings.GetCflagsCC(config_name) + cflags_objc = ['$cflags_c'] + \ + self.xcode_settings.GetCflagsObjC(config_name) + cflags_objcc = ['$cflags_cc'] + \ + self.xcode_settings.GetCflagsObjCC(config_name) else: cflags = config.get('cflags', []) cflags_c = config.get('cflags_c', []) @@ -438,14 +594,26 @@ class NinjaWriter: self.WriteVariableList('includes', ['-I' + self.GypPathToNinja(i) for i in config.get('include_dirs', [])]) + + pch_commands = precompiled_header.GetGchBuildCommands() + if self.flavor == 'mac': + self.WriteVariableList('cflags_pch_c', + [precompiled_header.GetInclude('c')]) + self.WriteVariableList('cflags_pch_cc', + [precompiled_header.GetInclude('cc')]) + self.WriteVariableList('cflags_pch_objc', + [precompiled_header.GetInclude('m')]) + self.WriteVariableList('cflags_pch_objcc', + [precompiled_header.GetInclude('mm')]) + self.WriteVariableList('cflags', map(self.ExpandSpecial, cflags)) self.WriteVariableList('cflags_c', map(self.ExpandSpecial, cflags_c)) self.WriteVariableList('cflags_cc', map(self.ExpandSpecial, cflags_cc)) if self.flavor == 'mac': self.WriteVariableList('cflags_objc', map(self.ExpandSpecial, - cflags_objc)) + cflags_objc)) self.WriteVariableList('cflags_objcc', map(self.ExpandSpecial, - cflags_objcc)) + cflags_objcc)) self.ninja.newline() outputs = [] for source in sources: @@ -463,106 +631,232 @@ class NinjaWriter: # TODO: should we assert here on unexpected extensions? continue input = self.GypPathToNinja(source) - output = self.GypPathToUniqueOutput(filename + '.o') + output = self.GypPathToUniqueOutput(filename + self.obj_ext) + implicit = precompiled_header.GetObjDependencies([input], [output]) self.ninja.build(output, command, input, + implicit=[gch for _, _, gch in implicit], order_only=predepends) outputs.append(output) + + self.WritePchTargets(pch_commands) + self.ninja.newline() return outputs - def WriteTarget(self, spec, config, final_deps, order_only): - if spec['type'] == 'none': - # This target doesn't have any explicit final output, but is instead - # used for its effects before the final output (e.g. copies steps). - # Reuse the existing output if it's easy. - if len(final_deps) == 1: - return final_deps[0] - # Otherwise, fall through to writing out a stamp file. + def WritePchTargets(self, pch_commands): + """Writes ninja rules to compile prefix headers.""" + if not pch_commands: + return + + for gch, lang_flag, lang, input in pch_commands: + var_name = { + 'c': 'cflags_pch_c', + 'cc': 'cflags_pch_cc', + 'm': 'cflags_pch_objc', + 'mm': 'cflags_pch_objcc', + }[lang] + + cmd = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', }.get(lang) + self.ninja.build(gch, cmd, input, variables=[(var_name, lang_flag)]) - output = self.ComputeOutput(spec) - output_uses_linker = spec['type'] in ('executable', 'loadable_module', - 'shared_library') + def WriteLink(self, spec, config_name, config, link_deps): + """Write out a link step. Returns the path to the output.""" + + command = { + 'executable': 'link', + 'loadable_module': 'solink_module', + 'shared_library': 'solink', + }[spec['type']] implicit_deps = set() + if 'dependencies' in spec: # Two kinds of dependencies: # - Linkable dependencies (like a .a or a .so): add them to the link line. # - Non-linkable dependencies (like a rule that generates a file # and writes a stamp file): add them to implicit_deps - if output_uses_linker: - extra_deps = set() - for dep in spec['dependencies']: - input, _, linkable = self.target_outputs.get(dep, (None, [], False)) - if not input: - continue - if linkable: - extra_deps.add(input) - else: - # TODO: Chrome-specific HACK. Chrome runs this lastchange rule on - # every build, but we don't want to rebuild when it runs. - if 'lastchange' not in input: - implicit_deps.add(input) - final_deps.extend(list(extra_deps)) - command_map = { - 'executable': 'link', - 'static_library': 'alink', - 'loadable_module': 'solink_module', - 'shared_library': 'solink', - 'none': 'stamp', - } - command = command_map[spec['type']] - - if output_uses_linker: - if self.flavor == 'mac': - # TODO(jeremya/thakis): Get this from XcodeSettings. - ldflags = [] - else: - ldflags = config.get('ldflags', []) - self.WriteVariableList('ldflags', - gyp.common.uniquer(map(self.ExpandSpecial, - ldflags))) - self.WriteVariableList('libs', - gyp.common.uniquer(map(self.ExpandSpecial, - spec.get('libraries', [])))) + extra_link_deps = set() + for dep in spec['dependencies']: + target = self.target_outputs.get(dep) + if not target: + continue + linkable = target.Linkable() + if linkable: + extra_link_deps.add(target.binary) + + final_output = target.FinalOutput() + if not linkable or final_output != target.binary: + implicit_deps.add(final_output) + + link_deps.extend(list(extra_link_deps)) extra_bindings = [] + if self.is_mac_bundle: + output = self.ComputeMacBundleBinaryOutput() + else: + output = self.ComputeOutput(spec) + extra_bindings.append(('postbuilds', + self.GetPostbuildCommand(spec, output, output))) + + if self.flavor == 'mac': + ldflags = self.xcode_settings.GetLdflags(config_name, + self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), + self.GypPathToNinja) + else: + ldflags = config.get('ldflags', []) + self.WriteVariableList('ldflags', + gyp.common.uniquer(map(self.ExpandSpecial, + ldflags))) + + libraries = gyp.common.uniquer(map(self.ExpandSpecial, + spec.get('libraries', []))) + if self.flavor == 'mac': + libraries = self.xcode_settings.AdjustLibraries(libraries) + self.WriteVariableList('libs', libraries) + if command in ('solink', 'solink_module'): extra_bindings.append(('soname', os.path.split(output)[1])) - self.ninja.build(output, command, final_deps, + self.ninja.build(output, command, link_deps, implicit=list(implicit_deps), - order_only=order_only, variables=extra_bindings) + return output + def WriteTarget(self, spec, config_name, config, link_deps, compile_deps): + if spec['type'] == 'none': + # TODO(evan): don't call this function for 'none' target types, as + # it doesn't do anything, and we fake out a 'binary' with a stamp file. + self.target.binary = compile_deps + elif spec['type'] == 'static_library': + self.target.binary = self.ComputeOutput(spec) + self.ninja.build(self.target.binary, 'alink', link_deps, + order_only=compile_deps, + variables=[('postbuilds', self.GetPostbuildCommand( + spec, self.target.binary, self.target.binary))]) + else: + self.target.binary = self.WriteLink(spec, config_name, config, link_deps) + return self.target.binary + + def WriteMacBundle(self, spec, mac_bundle_depends): + assert self.is_mac_bundle + package_framework = spec['type'] in ('shared_library', 'loadable_module') + output = self.ComputeMacBundleOutput() + postbuild = self.GetPostbuildCommand(spec, output, self.target.binary, + is_command_start=not package_framework) + variables = [] + if postbuild: + variables.append(('postbuilds', postbuild)) + if package_framework: + variables.append(('version', self.xcode_settings.GetFrameworkVersion())) + self.ninja.build(output, 'package_framework', mac_bundle_depends, + variables=variables) + else: + self.ninja.build(output, 'stamp', mac_bundle_depends, + variables=variables) + self.target.bundle = output return output - def ComputeOutputFileName(self, spec): + def GetXcodeEnv(self, additional_settings=None): + """Returns the variables Xcode would set for build steps.""" + assert self.abs_build_dir + abs_build_dir = self.abs_build_dir + return gyp.xcode_emulation.GetXcodeEnv( + self.xcode_settings, abs_build_dir, + os.path.join(abs_build_dir, self.build_to_base), self.config_name, + additional_settings) + + def GetXcodePostbuildEnv(self): + """Returns the variables Xcode would set for postbuild steps.""" + postbuild_settings = {} + # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack. + # TODO(thakis): It would be nice to have some general mechanism instead. + strip_save_file = self.xcode_settings.GetPerTargetSetting( + 'CHROMIUM_STRIP_SAVE_FILE') + if strip_save_file: + postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = self.GypPathToNinja( + strip_save_file) + return self.GetXcodeEnv(additional_settings=postbuild_settings) + + def GetPostbuildCommand(self, spec, output, output_binary, + is_command_start=False): + """Returns a shell command that runs all the postbuilds, and removes + |output| if any of them fails. If |is_command_start| is False, then the + returned string will start with ' && '.""" + if not self.xcode_settings or spec['type'] == 'none' or not output: + return '' + output = QuoteShellArgument(output) + target_postbuilds = self.xcode_settings.GetTargetPostbuilds( + self.config_name, output, QuoteShellArgument(output_binary), quiet=True) + postbuilds = gyp.xcode_emulation.GetSpecPostbuildCommands( + spec, self.GypPathToNinja, quiet=True) + postbuilds = target_postbuilds + postbuilds + if not postbuilds: + return '' + env = self.ComputeExportEnvString(self.GetXcodePostbuildEnv()) + commands = env + ' F=0; ' + \ + ' '.join([ninja_syntax.escape(command) + ' || F=$$?;' + for command in postbuilds]) + command_string = env + commands + ' ((exit $$F) || rm -rf %s) ' % output + \ + '&& exit $$F)' + if is_command_start: + return '(' + command_string + ' && ' + else: + return '$ && (' + command_string + + def ComputeExportEnvString(self, env): + """Given an environment, returns a string looking like + 'export FOO=foo; export BAR="${FOO} bar;' + that exports |env| to the shell.""" + export_str = [] + for k in gyp.xcode_emulation.TopologicallySortedEnvVarKeys(env): + export_str.append('export %s=%s;' % + (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(env[k])))) + return ' '.join(export_str) + + def ComputeMacBundleOutput(self): + """Return the 'output' (full output path) to a bundle output directory.""" + assert self.is_mac_bundle + path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']) + return os.path.join(path, self.xcode_settings.GetWrapperName()) + + def ComputeMacBundleBinaryOutput(self): + """Return the 'output' (full output path) to the binary in a bundle.""" + assert self.is_mac_bundle + path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']) + return os.path.join(path, self.xcode_settings.GetExecutablePath()) + + def ComputeOutputFileName(self, spec, type=None): """Compute the filename of the final output for the current target.""" + if not 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': 'lib', - 'shared_library': 'lib', + '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(spec['type'], '')) + prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, '')) # Compute filename extension: the product extension, or a default # for the product type. DEFAULT_EXTENSION = { - 'static_library': 'a', - 'loadable_module': 'so', - 'shared_library': 'so', + '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'], } - # TODO(thakis/jeremya): Remove once the mac path name computation is done - # by XcodeSettings. - if self.flavor == 'mac': - DEFAULT_EXTENSION['shared_library'] = 'dylib' - extension = spec.get('product_extension', - DEFAULT_EXTENSION.get(spec['type'], '')) + 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. @@ -574,37 +868,44 @@ class NinjaWriter: # Snip out an extra 'lib' from libs if appropriate. target = StripPrefix(target, 'lib') - if spec['type'] in ('static_library', 'loadable_module', 'shared_library', + if type in ('static_library', 'loadable_module', 'shared_library', 'executable'): return '%s%s%s' % (prefix, target, extension) - elif spec['type'] == 'none': + elif type == 'none': return '%s.stamp' % target else: - raise 'Unhandled output type', spec['type'] + raise 'Unhandled output type', type - def ComputeOutput(self, spec): + def ComputeOutput(self, spec, type=None): """Compute the path for the final output of the spec.""" + assert not self.is_mac_bundle or type - filename = self.ComputeOutputFileName(spec) + if not type: + type = spec['type'] + + if self.flavor == 'mac' and type in ( + 'static_library', 'executable', 'shared_library', 'loadable_module'): + filename = self.xcode_settings.GetExecutablePath() + else: + filename = self.ComputeOutputFileName(spec, type) if 'product_dir' in spec: path = os.path.join(spec['product_dir'], filename) return self.ExpandSpecial(path) - # Executables and loadable modules go into the output root, - # libraries go into shared library dir, and everything else - # goes into the normal place. - if spec['type'] in ('executable', 'loadable_module'): + # Some products go into the output root, libraries go into shared library + # dir, and everything else goes into the normal place. + type_in_output_root = ['executable', 'loadable_module'] + if self.flavor == 'mac' and self.toolset == 'target': + type_in_output_root += ['shared_library', 'static_library'] + + if type in type_in_output_root: return filename - elif spec['type'] == 'shared_library': + elif type == 'shared_library': libdir = 'lib' if self.toolset != 'target': - libdir = 'lib/%s' % self.toolset + libdir = os.path.join('lib', '%s' % self.toolset) return os.path.join(libdir, filename) - # TODO(thakis/jeremya): Remove once the mac path name computation is done - # by XcodeSettings. - elif spec['type'] == 'static_library' and self.flavor == 'mac': - return filename else: return self.GypPathToUniqueOutput(filename, qualified=False) @@ -613,7 +914,7 @@ class NinjaWriter: values = [] self.ninja.variable(var, ' '.join(values)) - def WriteNewNinjaRule(self, name, args, description): + def WriteNewNinjaRule(self, name, args, description, env={}): """Write out a new ninja "rule" statement for a given command. Returns the name of the new rule.""" @@ -632,11 +933,29 @@ class NinjaWriter: # gyp dictates that commands are run from the base directory. # cd into the directory before running, and adjust paths in # the arguments to point to the proper locations. - cd = 'cd %s; ' % self.build_to_base + if self.flavor == 'win': + cd = 'cmd /s /c "cd %s && ' % self.build_to_base + else: + cd = 'cd %s; ' % self.build_to_base args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args] - - command = cd + gyp.common.EncodePOSIXShellList(args) - self.ninja.rule(rule_name, command, description) + env = self.ComputeExportEnvString(env) + if self.flavor == 'win': + # TODO(scottmg): Really don't want encourage cygwin, but I'm not sure + # how much sh is depended upon. For now, double quote args to make most + # things work. + command = args[0] + ' "' + '" "'.join(args[1:]) + '""' + else: + command = gyp.common.EncodePOSIXShellList(args) + if env: + # If an environment is passed in, variables in the command should be + # read from it, instead of from ninja's internal variables. + command = ninja_syntax.escape(command) + + command = cd + env + command + # GYP rules/actions express being no-ops by not touching their outputs. + # Avoid executing downstream dependencies in this case by specifying + # restat=1 to ninja. + self.ninja.rule(rule_name, command, description, restat=True) self.ninja.newline() return rule_name @@ -645,15 +964,14 @@ class NinjaWriter: def CalculateVariables(default_variables, params): """Calculate additional variables for use in the build (called by gyp).""" cc_target = os.environ.get('CC.target', os.environ.get('CC', 'cc')) - default_variables['LINKER_SUPPORTS_ICF'] = \ - gyp.system_test.TestLinkerSupportsICF(cc_command=cc_target) - flavor = gyp.common.GetFlavor(params) if flavor == 'mac': default_variables.setdefault('OS', 'mac') default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib') - - # TODO(jeremya/thakis): Set SHARED_LIB_DIR / LIB_DIR. + 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. @@ -667,12 +985,22 @@ def CalculateVariables(default_variables, params): global generator_extra_sources_for_rules generator_extra_sources_for_rules = getattr(xcode_generator, 'generator_extra_sources_for_rules', []) + elif flavor == 'win': + default_variables['OS'] = 'win' + default_variables['EXECUTABLE_SUFFIX'] = '.exe' + default_variables['STATIC_LIB_PREFIX'] = '' + default_variables['STATIC_LIB_SUFFIX'] = '.lib' + default_variables['SHARED_LIB_PREFIX'] = '' + default_variables['SHARED_LIB_SUFFIX'] = '.dll' 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', '') def OpenOutput(path): @@ -684,55 +1012,85 @@ def OpenOutput(path): return open(path, 'w') -def GenerateOutput(target_list, target_dicts, data, params): +def GenerateOutputForConfig(target_list, target_dicts, data, params, + config_name): options = params['options'] flavor = gyp.common.GetFlavor(params) generator_flags = params.get('generator_flags', {}) - if options.generator_output: - raise NotImplementedError, "--generator_output not implemented for ninja" - - config_name = generator_flags.get('config', None) - if config_name is None: - # Guess which config we want to use: pick the first one from the - # first target. - config_name = target_dicts[target_list[0]]['default_configuration'] - - # builddir: relative path from source root to our output files. + # build_dir: relative path from source root to our output files. # e.g. "out/Debug" - builddir = os.path.join(generator_flags.get('output_dir', 'out'), config_name) + build_dir = os.path.join(generator_flags.get('output_dir', 'out'), + config_name) master_ninja = ninja_syntax.Writer( - OpenOutput(os.path.join(options.toplevel_dir, builddir, 'build.ninja')), + OpenOutput(os.path.join(options.toplevel_dir, build_dir, 'build.ninja')), width=120) - # TODO: compute cc/cxx/ld/etc. by command-line arguments and system tests. - master_ninja.variable('cc', os.environ.get('CC', 'gcc')) - master_ninja.variable('cxx', os.environ.get('CXX', 'g++')) - # TODO(bradnelson): remove NOGOLD when this is resolved: - # http://code.google.com/p/chromium/issues/detail?id=108251 - if flavor != 'mac' and not os.environ.get('NOGOLD'): - master_ninja.variable('ld', '$cxx -Wl,--threads -Wl,--thread-count=4') + # Put build-time support tools in out/{config_name}. + gyp.common.CopyTool(flavor, os.path.join(options.toplevel_dir, build_dir)) + + # Grab make settings for CC/CXX. + if flavor == 'win': + cc = cxx = 'cl' else: - # TODO(jeremya/thakis): flock - master_ninja.variable('ld', '$cxx') + cc, cxx = 'gcc', 'g++' + build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) + make_global_settings = data[build_file].get('make_global_settings', []) + build_to_root = InvertRelativePath(build_dir) + for key, value in make_global_settings: + if key == 'CC': cc = os.path.join(build_to_root, value) + if key == 'CXX': cxx = os.path.join(build_to_root, value) + + flock = 'flock' + if flavor == 'mac': + flock = './gyp-mac-tool flock' + master_ninja.variable('cc', os.environ.get('CC', cc)) + master_ninja.variable('cxx', os.environ.get('CXX', cxx)) + if flavor == 'win': + master_ninja.variable('ld', 'link') + else: + master_ninja.variable('ld', flock + ' linker.lock $cxx') master_ninja.variable('cc_host', '$cc') master_ninja.variable('cxx_host', '$cxx') + if flavor == 'mac': + master_ninja.variable('mac_tool', os.path.join('.', 'gyp-mac-tool')) master_ninja.newline() - master_ninja.rule( - 'cc', - description='CC $out', - command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c ' - '-c $in -o $out'), - depfile='$out.d') - master_ninja.rule( - 'cxx', - description='CXX $out', - command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc ' - '-c $in -o $out'), - depfile='$out.d') - if flavor != 'mac': + if flavor != 'win': + master_ninja.rule( + 'cc', + description='CC $out', + command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c ' + '$cflags_pch_c -c $in -o $out'), + depfile='$out.d') + master_ninja.rule( + 'cxx', + description='CXX $out', + command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc ' + '$cflags_pch_cc -c $in -o $out'), + depfile='$out.d') + else: + # TODO(scottmg): Requires deplist branch of ninja for now (for + # /showIncludes handling). + master_ninja.rule( + 'cc', + description='CC $out', + command=('cmd /c $cc /nologo /showIncludes ' + '$defines $includes $cflags $cflags_c ' + '$cflags_pch_c /c $in /Fo$out ' + '| ninja-deplist-helper -f cl -o $out.dl'), + deplist='$out.dl') + master_ninja.rule( + 'cxx', + description='CXX $out', + command=('cmd /c $cxx /nologo /showIncludes ' + '$defines $includes $cflags $cflags_cc ' + '$cflags_pch_cc /c $in /Fo$out ' + '| ninja-deplist-helper -f cl -o $out.dl'), + deplist='$out.dl') + + if flavor != 'mac' and flavor != 'win': master_ninja.rule( 'alink', description='AR $out', @@ -752,48 +1110,88 @@ def GenerateOutput(target_list, target_dicts, data, params): description='LINK $out', command=('$ld $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib ' '-Wl,--start-group $in -Wl,--end-group $libs')) + elif flavor == 'win': + master_ninja.rule( + 'alink', + description='AR $out', + command='lib /nologo /OUT:$out $in') + master_ninja.rule( + 'solink', + description='SOLINK $out', + command=('$ld /nologo /DLL $ldflags /OUT:$out $in $libs')) + master_ninja.rule( + 'solink_module', + description='SOLINK(module) $out', + command=('$ld /nologo /DLL $ldflags /OUT:$out $in $libs')) + master_ninja.rule( + 'link', + description='LINK $out', + command=('$ld /nologo $ldflags /OUT:$out $in $libs')) else: master_ninja.rule( 'objc', description='OBJC $out', - command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c ' - '$cflags_objc -c $in -o $out'), + command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc ' + '$cflags_pch_objc -c $in -o $out'), depfile='$out.d') master_ninja.rule( 'objcxx', description='OBJCXX $out', - command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc ' - '$cflags_objcc -c $in -o $out'), + command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc ' + '$cflags_pch_objcc -c $in -o $out'), depfile='$out.d') master_ninja.rule( 'alink', - description='LIBTOOL-STATIC $out', - command='rm -f $out && libtool -static -o $out $in') + description='LIBTOOL-STATIC $out, POSTBUILDS', + command='rm -f $out && ' + './gyp-mac-tool filter-libtool libtool -static -o $out $in' + '$postbuilds') # TODO(thakis): The solink_module rule is likely wrong. Xcode seems to pass # -bundle -single_module here (for osmesa.so). master_ninja.rule( 'solink', - description='SOLINK $out', + description='SOLINK $out, POSTBUILDS', command=('$ld -shared $ldflags -o $out ' - '$in $libs')) + '$in $libs$postbuilds')) master_ninja.rule( 'solink_module', - description='SOLINK(module) $out', + description='SOLINK(module) $out, POSTBUILDS', command=('$ld -shared $ldflags -o $out ' - '$in $libs')) + '$in $libs$postbuilds')) master_ninja.rule( 'link', - description='LINK $out', + description='LINK $out, POSTBUILDS', command=('$ld $ldflags -o $out ' - '$in $libs')) + '$in $libs$postbuilds')) + master_ninja.rule( + 'infoplist', + description='INFOPLIST $out', + command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && ' + 'plutil -convert xml1 $out $out')) + master_ninja.rule( + 'mac_tool', + description='MACTOOL $mactool_cmd $in', + command='$env $mac_tool $mactool_cmd $in $out') + master_ninja.rule( + 'package_framework', + description='PACKAGE FRAMEWORK $out, POSTBUILDS', + command='$mac_tool package-framework $out $version$postbuilds ' + '&& touch $out') master_ninja.rule( 'stamp', description='STAMP $out', - command='touch $out') - master_ninja.rule( - 'copy', - description='COPY $in $out', - command='ln -f $in $out 2>/dev/null || (rm -rf $out && cp -af $in $out)') + command='${postbuilds}touch $out') + if flavor == 'win': + # TODO(scottmg): Copy fallback? + master_ninja.rule( + 'copy', + description='COPY $in $out', + command='cmd /c mklink /h $out $in >nul || mklink /h /j $out $in >nul') + else: + master_ninja.rule( + 'copy', + description='COPY $in $out', + command='ln -f $in $out 2>/dev/null || (rm -rf $out && cp -af $in $out)') master_ninja.newline() all_targets = set() @@ -802,38 +1200,57 @@ def GenerateOutput(target_list, target_dicts, data, params): all_targets.add(target) all_outputs = set() + # target_outputs is a map from qualified target name to a Target object. target_outputs = {} for qualified_target in target_list: # qualified_target is like: third_party/icu/icu.gyp:icui18n#target build_file, name, toolset = \ gyp.common.ParseQualifiedTarget(qualified_target) - # TODO: what is options.depth and how is it different than - # options.toplevel_dir? - build_file = gyp.common.RelativePath(build_file, options.depth) + this_make_global_settings = data[build_file].get('make_global_settings', []) + assert make_global_settings == this_make_global_settings, ( + "make_global_settings needs to be the same for all targets.") + + spec = target_dicts[qualified_target] + if flavor == 'mac': + gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec) + + build_file = gyp.common.RelativePath(build_file, options.toplevel_dir) base_path = os.path.dirname(build_file) obj = 'obj' if toolset != 'target': obj += '.' + toolset output_file = os.path.join(obj, base_path, name + '.ninja') - spec = target_dicts[qualified_target] - config = spec['configurations'][config_name] - writer = NinjaWriter(target_outputs, base_path, builddir, + abs_build_dir=os.path.abspath(os.path.join(options.toplevel_dir, build_dir)) + writer = NinjaWriter(target_outputs, base_path, build_dir, OpenOutput(os.path.join(options.toplevel_dir, - builddir, + build_dir, output_file)), - flavor) + flavor, abs_build_dir=abs_build_dir) master_ninja.subninja(output_file) - output, compile_depends = writer.WriteSpec(spec, config) - if output: - linkable = spec['type'] in ('static_library', 'shared_library') - target_outputs[qualified_target] = (output, compile_depends, linkable) - + target = writer.WriteSpec(spec, config_name) + if target: + target_outputs[qualified_target] = target if qualified_target in all_targets: - all_outputs.add(output) + all_outputs.add(target.FinalOutput()) if all_outputs: master_ninja.build('all', 'phony', list(all_outputs)) + + +def GenerateOutput(target_list, target_dicts, data, params): + if params['options'].generator_output: + raise NotImplementedError, "--generator_output not implemented for ninja" + + user_config = params.get('generator_flags', {}).get('config', None) + if user_config: + GenerateOutputForConfig(target_list, target_dicts, data, params, + user_config) + else: + config_names = target_dicts[target_list[0]]['configurations'].keys() + for config_name in config_names: + GenerateOutputForConfig(target_list, target_dicts, data, params, + config_name) diff --git a/tools/gyp/pylib/gyp/generator/ninja_test.py b/tools/gyp/pylib/gyp/generator/ninja_test.py new file mode 100644 index 0000000000..af2e6d32fc --- /dev/null +++ b/tools/gyp/pylib/gyp/generator/ninja_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Unit tests for the ninja.py file. """ + +import gyp.generator.ninja as ninja +import unittest +import StringIO +import TestCommon + + +class TestPrefixesAndSuffixes(unittest.TestCase): + def test_BinaryNamesWindows(self): + writer = ninja.NinjaWriter('wee', '.', '.', 'ninja.build', 'win') + spec = { 'target_name': 'wee' } + self.assertTrue(writer.ComputeOutputFileName(spec, 'executable'). + endswith('.exe')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). + endswith('.dll')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). + endswith('.lib')) + + def test_BinaryNamesLinux(self): + writer = ninja.NinjaWriter('wee', '.', '.', 'ninja.build', 'linux') + spec = { + 'target_name': 'wee' + } + self.assertTrue('.' not in writer.ComputeOutputFileName(spec, 'executable')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). + startswith('lib')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). + startswith('lib')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'shared_library'). + endswith('.so')) + self.assertTrue(writer.ComputeOutputFileName(spec, 'static_library'). + endswith('.a')) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/gyp/pylib/gyp/input.py b/tools/gyp/pylib/gyp/input.py index 19a16288cd..74a96b6d8b 100644 --- a/tools/gyp/pylib/gyp/input.py +++ b/tools/gyp/pylib/gyp/input.py @@ -2065,6 +2065,25 @@ def ProcessListFiltersInList(name, the_list): ProcessListFiltersInList(name, item) +def ValidateTargetType(target, target_dict): + """Ensures the 'type' field on the target is one of the known types. + + Arguments: + target: string, name of target. + target_dict: dict, target spec. + + Raises an exception on error. + """ + VALID_TARGET_TYPES = ('executable', 'loadable_module', + 'static_library', 'shared_library', + 'none') + target_type = target_dict.get('type', None) + if target_type not in VALID_TARGET_TYPES: + raise Exception("Target %s has an invalid target type '%s'. " + "Must be one of %s." % + (target, target_type, '/'.join(VALID_TARGET_TYPES))) + + def ValidateRulesInTarget(target, target_dict, extra_sources_for_rules): """Ensures that the rules sections in target_dict are valid and consistent, and determines which sources they apply to. @@ -2349,6 +2368,7 @@ def Load(build_files, variables, includes, depth, generator_input_info, check, for target in flat_list: target_dict = targets[target] build_file = gyp.common.BuildFile(target) + ValidateTargetType(target, target_dict) ValidateRulesInTarget(target, target_dict, extra_sources_for_rules) ValidateRunAsInTarget(target, target_dict, build_file) ValidateActionsInTarget(target, target_dict, build_file) diff --git a/tools/gyp/pylib/gyp/mac_tool.py b/tools/gyp/pylib/gyp/mac_tool.py index 64707762d3..42dd6c9923 100755 --- a/tools/gyp/pylib/gyp/mac_tool.py +++ b/tools/gyp/pylib/gyp/mac_tool.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,9 +8,10 @@ These functions are executed via gyp-mac-tool when using the Makefile generator. """ -import os import fcntl +import os import plistlib +import re import shutil import string import subprocess @@ -19,7 +20,9 @@ import sys def main(args): executor = MacTool() - executor.Dispatch(args) + exit_code = executor.Dispatch(args) + if exit_code is not None: + sys.exit(exit_code) class MacTool(object): @@ -32,18 +35,70 @@ class MacTool(object): raise Exception("Not enough arguments") method = "Exec%s" % self._CommandifyName(args[0]) - getattr(self, method)(*args[1:]) + return getattr(self, method)(*args[1:]) def _CommandifyName(self, name_string): """Transforms a tool name like copy-info-plist to CopyInfoPlist""" return name_string.title().replace('-', '') - def ExecFlock(self, lockfile, *cmd_list): - """Emulates the most basic behavior of Linux's flock(1).""" - # Rely on exception handling to report errors. - fd = os.open(lockfile, os.O_RDONLY|os.O_NOCTTY|os.O_CREAT, 0o666) - fcntl.flock(fd, fcntl.LOCK_EX) - return subprocess.call(cmd_list) + def ExecCopyBundleResource(self, source, dest): + """Copies a resource file to the bundle/Resources directory, performing any + necessary compilation on each resource.""" + extension = os.path.splitext(source)[1].lower() + if os.path.isdir(source): + # Copy tree. + if os.path.exists(dest): + shutil.rmtree(dest) + shutil.copytree(source, dest) + elif extension == '.xib': + self._CopyXIBFile(source, dest) + elif extension == '.strings': + self._CopyStringsFile(source, dest) + # TODO: Given that files with arbitrary extensions can be copied to the + # bundle, we will want to get rid of this whitelist eventually. + elif extension in [ + '.icns', '.manifest', '.pak', '.pdf', '.png', '.sb', '.sh', + '.ttf', '.sdef']: + shutil.copyfile(source, dest) + else: + raise NotImplementedError( + "Don't know how to copy bundle resources of type %s while copying " + "%s to %s)" % (extension, source, dest)) + + def _CopyXIBFile(self, source, dest): + """Compiles a XIB file with ibtool into a binary plist in the bundle.""" + args = ['/Developer/usr/bin/ibtool', '--errors', '--warnings', + '--notices', '--output-format', 'human-readable-text', '--compile', + dest, source] + subprocess.call(args) + + def _CopyStringsFile(self, source, dest): + """Copies a .strings file using iconv to reconvert the input into UTF-16.""" + input_code = self._DetectInputEncoding(source) or "UTF-8" + fp = open(dest, 'w') + args = ['/usr/bin/iconv', '--from-code', input_code, '--to-code', + 'UTF-16', source] + subprocess.call(args, stdout=fp) + fp.close() + + def _DetectInputEncoding(self, file_name): + """Reads the first few bytes from file_name and tries to guess the text + encoding. Returns None as a guess if it can't detect it.""" + fp = open(file_name, 'rb') + try: + header = fp.read(3) + except e: + fp.close() + return None + fp.close() + if header.startswith("\xFE\xFF"): + return "UTF-16BE" + elif header.startswith("\xFF\xFE"): + return "UTF-16LE" + elif header.startswith("\xEF\xBB\xBF"): + return "UTF-8" + else: + return None def ExecCopyInfoPlist(self, source, dest): """Copies the |source| Info.plist to the destination directory |dest|.""" @@ -92,6 +147,22 @@ class MacTool(object): fp.write('%s%s' % (package_type, signature_code)) fp.close() + def ExecFlock(self, lockfile, *cmd_list): + """Emulates the most basic behavior of Linux's flock(1).""" + # Rely on exception handling to report errors. + fd = os.open(lockfile, os.O_RDONLY|os.O_NOCTTY|os.O_CREAT, 0o666) + fcntl.flock(fd, fcntl.LOCK_EX) + return subprocess.call(cmd_list) + + def ExecFilterLibtool(self, *cmd_list): + """Calls libtool and filters out 'libtool: file: foo.o has no symbols'.""" + libtool_re = re.compile(r'^libtool: file: .* has no symbols$') + libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE) + for line in libtoolout.stderr: + if not libtool_re.match(line): + sys.stderr.write(line) + return libtoolout.returncode + def ExecPackageFramework(self, framework, version): """Takes a path to Something.framework and the Current version of that and sets up all the symlinks.""" @@ -128,65 +199,6 @@ class MacTool(object): os.remove(link) os.symlink(dest, link) - def ExecCopyBundleResource(self, source, dest): - """Copies a resource file to the bundle/Resources directory, performing any - necessary compilation on each resource.""" - extension = os.path.splitext(source)[1].lower() - if os.path.isdir(source): - # Copy tree. - if os.path.exists(dest): - shutil.rmtree(dest) - shutil.copytree(source, dest) - elif extension == '.xib': - self._CopyXIBFile(source, dest) - elif extension == '.strings': - self._CopyStringsFile(source, dest) - # TODO: Given that files with arbitrary extensions can be copied to the - # bundle, we will want to get rid of this whitelist eventually. - elif extension in [ - '.icns', '.manifest', '.pak', '.pdf', '.png', '.sb', '.sh', - '.ttf', '.sdef']: - shutil.copyfile(source, dest) - else: - raise NotImplementedError( - "Don't know how to copy bundle resources of type %s while copying " - "%s to %s)" % (extension, source, dest)) - - def _CopyXIBFile(self, source, dest): - """Compiles a XIB file with ibtool into a binary plist in the bundle.""" - args = ['/Developer/usr/bin/ibtool', '--errors', '--warnings', - '--notices', '--output-format', 'human-readable-text', '--compile', - dest, source] - subprocess.call(args) - - def _CopyStringsFile(self, source, dest): - """Copies a .strings file using iconv to reconvert the input into UTF-16.""" - input_code = self._DetectInputEncoding(source) or "UTF-8" - fp = open(dest, 'w') - args = ['/usr/bin/iconv', '--from-code', input_code, '--to-code', - 'UTF-16', source] - subprocess.call(args, stdout=fp) - fp.close() - - def _DetectInputEncoding(self, file_name): - """Reads the first few bytes from file_name and tries to guess the text - encoding. Returns None as a guess if it can't detect it.""" - fp = open(file_name, 'rb') - try: - header = fp.read(3) - except e: - fp.close() - return None - fp.close() - if header.startswith("\xFE\xFF"): - return "UTF-16BE" - elif header.startswith("\xFF\xFE"): - return "UTF-16LE" - elif header.startswith("\xEF\xBB\xBF"): - return "UTF-8" - else: - return None - if __name__ == '__main__': sys.exit(main(sys.argv[1:])) diff --git a/tools/gyp/pylib/gyp/ninja_syntax.py b/tools/gyp/pylib/gyp/ninja_syntax.py index 35cda05707..27e1908761 100644 --- a/tools/gyp/pylib/gyp/ninja_syntax.py +++ b/tools/gyp/pylib/gyp/ninja_syntax.py @@ -31,19 +31,23 @@ class Writer(object): if value is None: return if isinstance(value, list): - value = ' '.join(value) + value = ' '.join(filter(None, value)) # Filter out empty strings. self._line('%s = %s' % (key, value), indent) def rule(self, name, command, description=None, depfile=None, - generator=False): + generator=False, restat=False, deplist=None): self._line('rule %s' % name) self.variable('command', command, indent=1) if description: self.variable('description', description, indent=1) if depfile: self.variable('depfile', depfile, indent=1) + if deplist: + self.variable('deplist', deplist, indent=1) if generator: self.variable('generator', '1', indent=1) + if restat: + self.variable('restat', '1', indent=1) def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): @@ -80,49 +84,49 @@ class Writer(object): def default(self, paths): self._line('default %s' % ' '.join(self._as_list(paths))) + def _count_dollars_before_index(self, s, i): + """Returns the number of '$' characters right in front of s[i].""" + dollar_count = 0 + dollar_index = i - 1 + while dollar_index > 0 and s[dollar_index] == '$': + dollar_count += 1 + dollar_index -= 1 + return dollar_count + def _line(self, text, indent=0): """Write 'text' word-wrapped at self.width characters.""" leading_space = ' ' * indent while len(text) > self.width: # The text is too wide; wrap if possible. - self.output.write(leading_space) - + # Find the rightmost space that would obey our width constraint and + # that's not an escaped space. available_space = self.width - len(leading_space) - len(' $') + space = available_space + while True: + space = text.rfind(' ', 0, space) + if space < 0 or \ + self._count_dollars_before_index(text, space) % 2 == 0: + break - # Write as much as we can into this line. - done = False - written_stuff = False - while available_space > 0: - space = re.search('((\$\$)+([^$]|^)|[^$]|^) ', text) - if space: - space_idx = space.start() + 1 - else: - # No spaces left. - done = True + if space < 0: + # No such space; just use the first unescaped space we can find. + space = available_space - 1 + while True: + space = text.find(' ', space + 1) + if space < 0 or \ + self._count_dollars_before_index(text, space) % 2 == 0: break + if space < 0: + # Give up on breaking. + break - if space_idx > available_space: - # We're out of space. - if written_stuff: - # See if we can fit it on the next line. - break - # If we haven't written anything yet on this line, don't - # try to wrap. - self.output.write(text[0:space_idx] + ' ') - written_stuff = True - text = text[space_idx+1:] - available_space -= space_idx+1 - - self.output.write('$\n') + self.output.write(leading_space + text[0:space] + ' $\n') + text = text[space+1:] # Subsequent lines are continuations, so indent them. leading_space = ' ' * (indent+2) - if done: - # No more spaces, so bail. - break - self.output.write(leading_space + text + '\n') def _as_list(self, input): diff --git a/tools/gyp/pylib/gyp/system_test.py b/tools/gyp/pylib/gyp/system_test.py index 4245f86e11..51c71e36be 100755 --- a/tools/gyp/pylib/gyp/system_test.py +++ b/tools/gyp/pylib/gyp/system_test.py @@ -51,20 +51,6 @@ def TestArSupportsT(ar_command='ar', cc_command='cc'): env={'ar': ar_command, 'cc': cc_command}) -def TestLinkerSupportsThreads(cc_command='cc'): - """Test whether the linker supports the --threads flag.""" - return TestCommands(['%(cc)s -Wl,--threads test.c'], - files={'test.c': 'int main(){}'}, - env={'cc': cc_command}) - - -def TestLinkerSupportsICF(cc_command='cc'): - """Test whether the linker supports identical code folding.""" - return TestCommands(['%(cc)s -Wl,--icf=safe test.c'], - files={'test.c': 'int main(){}'}, - env={'cc': cc_command}) - - def main(): # Run the various test functions and print the results. def RunTest(description, function, **kwargs): @@ -75,7 +61,6 @@ def main(): print 'fail' RunTest("ar 'T' flag", TestArSupportsT) RunTest("ar 'T' flag with ccache", TestArSupportsT, cc_command='ccache cc') - RunTest("ld --threads", TestLinkerSupportsThreads) return 0 diff --git a/tools/gyp/pylib/gyp/xcode_emulation.py b/tools/gyp/pylib/gyp/xcode_emulation.py new file mode 100644 index 0000000000..4d8440b69f --- /dev/null +++ b/tools/gyp/pylib/gyp/xcode_emulation.py @@ -0,0 +1,972 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This module contains classes that help to emulate xcodebuild behavior on top of +other build systems, such as make and ninja. +""" + +import gyp.common +import os.path +import re +import shlex + +class XcodeSettings(object): + """A class that understands the gyp 'xcode_settings' object.""" + + def __init__(self, spec): + self.spec = spec + + # Per-target 'xcode_settings' are pushed down into configs earlier by gyp. + # This means self.xcode_settings[config] always contains all settings + # for that config -- the per-target settings as well. Settings that are + # the same for all configs are implicitly per-target settings. + self.xcode_settings = {} + configs = spec['configurations'] + for configname, config in configs.iteritems(): + self.xcode_settings[configname] = config.get('xcode_settings', {}) + + # This is only non-None temporarily during the execution of some methods. + self.configname = None + + # Used by _AdjustLibrary to match .a and .dylib entries in libraries. + self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$') + + def _Settings(self): + assert self.configname + return self.xcode_settings[self.configname] + + def _Test(self, test_key, cond_key, default): + return self._Settings().get(test_key, default) == cond_key + + def _Appendf(self, lst, test_key, format_str, default=None): + if test_key in self._Settings(): + lst.append(format_str % str(self._Settings()[test_key])) + elif default: + lst.append(format_str % str(default)) + + def _WarnUnimplemented(self, test_key): + if test_key in self._Settings(): + print 'Warning: Ignoring not yet implemented key "%s".' % test_key + + def _IsBundle(self): + return int(self.spec.get('mac_bundle', 0)) != 0 + + def GetFrameworkVersion(self): + """Returns the framework version of the current target. Only valid for + bundles.""" + assert self._IsBundle() + return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A') + + def GetWrapperExtension(self): + """Returns the bundle extension (.app, .framework, .plugin, etc). Only + valid for bundles.""" + assert self._IsBundle() + if self.spec['type'] in ('loadable_module', 'shared_library'): + default_wrapper_extension = { + 'loadable_module': 'bundle', + 'shared_library': 'framework', + }[self.spec['type']] + wrapper_extension = self.GetPerTargetSetting( + 'WRAPPER_EXTENSION', default=default_wrapper_extension) + return '.' + self.spec.get('product_extension', wrapper_extension) + elif self.spec['type'] == 'executable': + return '.app' + else: + assert False, "Don't know extension for '%s', target '%s'" % ( + self.spec['type'], self.spec['target_name']) + + def GetProductName(self): + """Returns PRODUCT_NAME.""" + return self.spec.get('product_name', self.spec['target_name']) + + def GetFullProductName(self): + """Returns FULL_PRODUCT_NAME.""" + if self._IsBundle(): + return self.GetWrapperName() + else: + return self._GetStandaloneBinaryPath() + + def GetWrapperName(self): + """Returns the directory name of the bundle represented by this target. + Only valid for bundles.""" + assert self._IsBundle() + return self.GetProductName() + self.GetWrapperExtension() + + def GetBundleContentsFolderPath(self): + """Returns the qualified path to the bundle's contents folder. E.g. + Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles.""" + assert self._IsBundle() + if self.spec['type'] == 'shared_library': + return os.path.join( + self.GetWrapperName(), 'Versions', self.GetFrameworkVersion()) + else: + # loadable_modules have a 'Contents' folder like executables. + return os.path.join(self.GetWrapperName(), 'Contents') + + def GetBundleResourceFolder(self): + """Returns the qualified path to the bundle's resource folder. E.g. + Chromium.app/Contents/Resources. Only valid for bundles.""" + assert self._IsBundle() + return os.path.join(self.GetBundleContentsFolderPath(), 'Resources') + + def GetBundlePlistPath(self): + """Returns the qualified path to the bundle's plist file. E.g. + Chromium.app/Contents/Info.plist. Only valid for bundles.""" + assert self._IsBundle() + if self.spec['type'] in ('executable', 'loadable_module'): + return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist') + else: + return os.path.join(self.GetBundleContentsFolderPath(), + 'Resources', 'Info.plist') + + def GetProductType(self): + """Returns the PRODUCT_TYPE of this target.""" + if self._IsBundle(): + return { + 'executable': 'com.apple.product-type.application', + 'loadable_module': 'com.apple.product-type.bundle', + 'shared_library': 'com.apple.product-type.framework', + }[self.spec['type']] + else: + return { + 'executable': 'com.apple.product-type.tool', + 'loadable_module': 'com.apple.product-type.library.dynamic', + 'shared_library': 'com.apple.product-type.library.dynamic', + 'static_library': 'com.apple.product-type.library.static', + }[self.spec['type']] + + def GetMachOType(self): + """Returns the MACH_O_TYPE of this target.""" + # Weird, but matches Xcode. + if not self._IsBundle() and self.spec['type'] == 'executable': + return '' + return { + 'executable': 'mh_execute', + 'static_library': 'staticlib', + 'shared_library': 'mh_dylib', + 'loadable_module': 'mh_bundle', + }[self.spec['type']] + + def _GetBundleBinaryPath(self): + """Returns the name of the bundle binary of by this target. + E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles.""" + assert self._IsBundle() + if self.spec['type'] in ('shared_library'): + path = self.GetBundleContentsFolderPath() + elif self.spec['type'] in ('executable', 'loadable_module'): + path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS') + return os.path.join(path, self.GetExecutableName()) + + def _GetStandaloneExecutableSuffix(self): + if 'product_extension' in self.spec: + return '.' + self.spec['product_extension'] + return { + 'executable': '', + 'static_library': '.a', + 'shared_library': '.dylib', + 'loadable_module': '.so', + }[self.spec['type']] + + def _GetStandaloneExecutablePrefix(self): + return self.spec.get('product_prefix', { + 'executable': '', + 'static_library': 'lib', + 'shared_library': 'lib', + # Non-bundled loadable_modules are called foo.so for some reason + # (that is, .so and no prefix) with the xcode build -- match that. + 'loadable_module': '', + }[self.spec['type']]) + + def _GetStandaloneBinaryPath(self): + """Returns the name of the non-bundle binary represented by this target. + E.g. hello_world. Only valid for non-bundles.""" + assert not self._IsBundle() + assert self.spec['type'] in ( + 'executable', 'shared_library', 'static_library', 'loadable_module'), ( + 'Unexpected type %s' % self.spec['type']) + target = self.spec['target_name'] + if self.spec['type'] == 'static_library': + if target[:3] == 'lib': + target = target[3:] + elif self.spec['type'] in ('loadable_module', 'shared_library'): + if target[:3] == 'lib': + target = target[3:] + + target_prefix = self._GetStandaloneExecutablePrefix() + target = self.spec.get('product_name', target) + target_ext = self._GetStandaloneExecutableSuffix() + return target_prefix + target + target_ext + + def GetExecutableName(self): + """Returns the executable name of the bundle represented by this target. + E.g. Chromium.""" + if self._IsBundle(): + return self.spec.get('product_name', self.spec['target_name']) + else: + return self._GetStandaloneBinaryPath() + + def GetExecutablePath(self): + """Returns the directory name of the bundle represented by this target. E.g. + Chromium.app/Contents/MacOS/Chromium.""" + if self._IsBundle(): + return self._GetBundleBinaryPath() + else: + return self._GetStandaloneBinaryPath() + + def _SdkPath(self): + sdk_root = self.GetPerTargetSetting('SDKROOT', default='macosx10.5') + if sdk_root.startswith('macosx'): + sdk_root = 'MacOSX' + sdk_root[len('macosx'):] + return '/Developer/SDKs/%s.sdk' % sdk_root + + def GetCflags(self, configname): + """Returns flags that need to be added to .c, .cc, .m, and .mm + compilations.""" + # This functions (and the similar ones below) do not offer complete + # emulation of all xcode_settings keys. They're implemented on demand. + + self.configname = configname + cflags = [] + + sdk_root = self._SdkPath() + if 'SDKROOT' in self._Settings(): + cflags.append('-isysroot %s' % sdk_root) + + if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'): + cflags.append('-funsigned-char') + + if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'): + cflags.append('-fasm-blocks') + + if 'GCC_DYNAMIC_NO_PIC' in self._Settings(): + if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES': + cflags.append('-mdynamic-no-pic') + else: + pass + # TODO: In this case, it depends on the target. xcode passes + # mdynamic-no-pic by default for executable and possibly static lib + # according to mento + + if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'): + cflags.append('-mpascal-strings') + + self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s') + + if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'): + dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf') + if dbg_format == 'dwarf': + cflags.append('-gdwarf-2') + elif dbg_format == 'stabs': + raise NotImplementedError('stabs debug format is not supported yet.') + elif dbg_format == 'dwarf-with-dsym': + cflags.append('-gdwarf-2') + else: + raise NotImplementedError('Unknown debug format %s' % dbg_format) + + if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'): + cflags.append('-fvisibility=hidden') + + if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'): + cflags.append('-Werror') + + if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'): + cflags.append('-Wnewline-eof') + + self._Appendf(cflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') + + # TODO: + if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'): + self._WarnUnimplemented('COPY_PHASE_STRIP') + self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS') + self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS') + self._WarnUnimplemented('GCC_ENABLE_OBJC_GC') + + # TODO: This is exported correctly, but assigning to it is not supported. + self._WarnUnimplemented('MACH_O_TYPE') + self._WarnUnimplemented('PRODUCT_TYPE') + + archs = self._Settings().get('ARCHS', ['i386']) + if len(archs) != 1: + # TODO: Supporting fat binaries will be annoying. + self._WarnUnimplemented('ARCHS') + archs = ['i386'] + cflags.append('-arch ' + archs[0]) + + if archs[0] in ('i386', 'x86_64'): + if self._Test('GCC_ENABLE_SSE3_EXTENSIONS', 'YES', default='NO'): + cflags.append('-msse3') + if self._Test('GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS', 'YES', + default='NO'): + cflags.append('-mssse3') # Note 3rd 's'. + if self._Test('GCC_ENABLE_SSE41_EXTENSIONS', 'YES', default='NO'): + cflags.append('-msse4.1') + if self._Test('GCC_ENABLE_SSE42_EXTENSIONS', 'YES', default='NO'): + cflags.append('-msse4.2') + + cflags += self._Settings().get('OTHER_CFLAGS', []) + cflags += self._Settings().get('WARNING_CFLAGS', []) + + config = self.spec['configurations'][self.configname] + framework_dirs = config.get('mac_framework_dirs', []) + for directory in framework_dirs: + cflags.append('-F ' + directory.replace('$(SDKROOT)', sdk_root)) + + self.configname = None + return cflags + + def GetCflagsC(self, configname): + """Returns flags that need to be added to .c, and .m compilations.""" + self.configname = configname + cflags_c = [] + self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s') + self.configname = None + return cflags_c + + def GetCflagsCC(self, configname): + """Returns flags that need to be added to .cc, and .mm compilations.""" + self.configname = configname + cflags_cc = [] + if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'): + cflags_cc.append('-fno-rtti') + if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'): + cflags_cc.append('-fno-exceptions') + if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'): + cflags_cc.append('-fvisibility-inlines-hidden') + if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'): + cflags_cc.append('-fno-threadsafe-statics') + self.configname = None + return cflags_cc + + def GetCflagsObjC(self, configname): + """Returns flags that need to be added to .m compilations.""" + self.configname = configname + self.configname = None + return [] + + def GetCflagsObjCC(self, configname): + """Returns flags that need to be added to .mm compilations.""" + self.configname = configname + cflags_objcc = [] + if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'): + cflags_objcc.append('-fobjc-call-cxx-cdtors') + self.configname = None + return cflags_objcc + + def GetLdflags(self, configname, product_dir, gyp_to_build_path): + """Returns flags that need to be passed to the linker. + + Args: + configname: The name of the configuration to get ld flags for. + product_dir: The directory where products such static and dynamic + libraries are placed. This is added to the library search path. + gyp_to_build_path: A function that converts paths relative to the + current gyp file to paths relative to the build direcotry. + """ + self.configname = configname + ldflags = [] + + # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS + # contains two entries that depend on this. Explicitly absolutify for these + # two cases. + def MapGypPathWithPrefix(flag, prefix): + if flag.startswith(prefix): + flag = prefix + gyp_to_build_path(flag[len(prefix):]) + return flag + for ldflag in self._Settings().get('OTHER_LDFLAGS', []): + # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS, + # TODO(thakis): Update ffmpeg.gyp): + ldflag = MapGypPathWithPrefix(ldflag, '-L') + # Required for the nacl plugin: + ldflag = MapGypPathWithPrefix(ldflag, '-Wl,-exported_symbols_list ') + ldflags.append(ldflag) + + if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'): + ldflags.append('-Wl,-dead_strip') + + if self._Test('PREBINDING', 'YES', default='NO'): + ldflags.append('-Wl,-prebind') + + self._Appendf( + ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s') + self._Appendf( + ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s') + self._Appendf( + ldflags, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') + if 'SDKROOT' in self._Settings(): + ldflags.append('-isysroot ' + self._SdkPath()) + + for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []): + ldflags.append('-L' + gyp_to_build_path(library_path)) + + if 'ORDER_FILE' in self._Settings(): + ldflags.append('-Wl,-order_file ' + + '-Wl,' + gyp_to_build_path( + self._Settings()['ORDER_FILE'])) + + archs = self._Settings().get('ARCHS', ['i386']) + if len(archs) != 1: + # TODO: Supporting fat binaries will be annoying. + self._WarnUnimplemented('ARCHS') + archs = ['i386'] + ldflags.append('-arch ' + archs[0]) + + # Xcode adds the product directory by default. + ldflags.append('-L' + product_dir) + + install_name = self.GetPerTargetSetting('LD_DYLIB_INSTALL_NAME') + install_base = self.GetPerTargetSetting('DYLIB_INSTALL_NAME_BASE') + default_install_name = \ + '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)' + if not install_name and install_base: + install_name = default_install_name + + if install_name: + # Hardcode support for the variables used in chromium for now, to unblock + # people using the make build. + if '$' in install_name: + assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/' + '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), ( + 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported yet' + ' in target \'%s\' (got \'%s\')' % + (self.spec['target_name'], install_name)) + # I'm not quite sure what :standardizepath does. Just call normpath(), + # but don't let @executable_path/../foo collapse to foo. + if '/' in install_base: + prefix, rest = '', install_base + if install_base.startswith('@'): + prefix, rest = install_base.split('/', 1) + rest = os.path.normpath(rest) # :standardizepath + install_base = os.path.join(prefix, rest) + + install_name = install_name.replace( + '$(DYLIB_INSTALL_NAME_BASE:standardizepath)', install_base) + if self._IsBundle(): + # These are only valid for bundles, hence the |if|. + install_name = install_name.replace( + '$(WRAPPER_NAME)', self.GetWrapperName()) + install_name = install_name.replace( + '$(PRODUCT_NAME)', self.GetProductName()) + else: + assert '$(WRAPPER_NAME)' not in install_name + assert '$(PRODUCT_NAME)' not in install_name + + install_name = install_name.replace( + '$(EXECUTABLE_PATH)', self.GetExecutablePath()) + + install_name = install_name.replace(' ', r'\ ') + ldflags.append('-install_name ' + install_name) + + self.configname = None + return ldflags + + def GetPerTargetSettings(self): + """Gets a list of all the per-target settings. This will only fetch keys + whose values are the same across all configurations.""" + first_pass = True + result = {} + for configname in sorted(self.xcode_settings.keys()): + if first_pass: + result = dict(self.xcode_settings[configname]) + first_pass = False + else: + for key, value in self.xcode_settings[configname].iteritems(): + if key not in result: + continue + elif result[key] != value: + del result[key] + return result + + def GetPerTargetSetting(self, setting, default=None): + """Tries to get xcode_settings.setting from spec. Assumes that the setting + has the same value in all configurations and throws otherwise.""" + first_pass = True + result = None + for configname in sorted(self.xcode_settings.keys()): + if first_pass: + result = self.xcode_settings[configname].get(setting, None) + first_pass = False + else: + assert result == self.xcode_settings[configname].get(setting, None), ( + "Expected per-target setting for '%s', got per-config setting " + "(target %s)" % (setting, spec['target_name'])) + if result is None: + return default + return result + + def _GetStripPostbuilds(self, configname, output_binary, quiet): + """Returns a list of shell commands that contain the shell commands + neccessary to strip this target's binary. These should be run as postbuilds + before the actual postbuilds run.""" + self.configname = configname + + result = [] + if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and + self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')): + + default_strip_style = 'debugging' + if self._IsBundle(): + default_strip_style = 'non-global' + elif self.spec['type'] == 'executable': + default_strip_style = 'all' + + strip_style = self._Settings().get('STRIP_STYLE', default_strip_style) + strip_flags = { + 'all': '', + 'non-global': '-x', + 'debugging': '-S', + }[strip_style] + + explicit_strip_flags = self._Settings().get('STRIPFLAGS', '') + if explicit_strip_flags: + strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags) + + if not quiet: + result.append('echo STRIP\\(%s\\)' % self.spec['target_name']) + result.append('strip %s %s' % (strip_flags, output_binary)) + + self.configname = None + return result + + def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet): + """Returns a list of shell commands that contain the shell commands + neccessary to massage this target's debug information. These should be run + as postbuilds before the actual postbuilds run.""" + self.configname = configname + + # For static libraries, no dSYMs are created. + result = [] + if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and + self._Test( + 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and + self.spec['type'] != 'static_library'): + if not quiet: + result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name']) + result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM')) + + self.configname = None + return result + + def GetTargetPostbuilds(self, configname, output, output_binary, quiet=False): + """Returns a list of shell commands that contain the shell commands + to run as postbuilds for this target, before the actual postbuilds.""" + # dSYMs need to build before stripping happens. + return ( + self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) + + self._GetStripPostbuilds(configname, output_binary, quiet)) + + def _AdjustLibrary(self, library): + if library.endswith('.framework'): + l = '-framework ' + os.path.splitext(os.path.basename(library))[0] + else: + m = self.library_re.match(library) + if m: + l = '-l' + m.group(1) + else: + l = library + return l.replace('$(SDKROOT)', self._SdkPath()) + + def AdjustLibraries(self, libraries): + """Transforms entries like 'Cocoa.framework' in libraries into entries like + '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc. + """ + libraries = [ self._AdjustLibrary(library) for library in libraries] + return libraries + + +class MacPrefixHeader(object): + """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. + + This feature consists of several pieces: + * If GCC_PREFIX_HEADER is present, all compilations in that project get an + additional |-include path_to_prefix_header| cflag. + * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is + instead compiled, and all other compilations in the project get an + additional |-include path_to_compiled_header| instead. + + Compiled prefix headers have the extension gch. There is one gch file for + every language used in the project (c, cc, m, mm), since gch files for + different languages aren't compatible. + + gch files themselves are built with the target's normal cflags, but they + obviously don't get the |-include| flag. Instead, they need a -x flag that + describes their language. + + All o files in the target need to depend on the gch file, to make sure + it's built before any o file is built. + + This class helps with some of these tasks, but it needs help from the build + system for writing dependencies to the gch files, for writing build commands + for the gch files, and for figuring out the location of the gch files. + """ + def __init__(self, xcode_settings, + gyp_path_to_build_path, gyp_path_to_build_output): + """If xcode_settings is None, all methods on this class are no-ops. + + Args: + gyp_path_to_build_path: A function that takes a gyp-relative path, + and returns a path relative to the build directory. + gyp_path_to_build_output: A function that takes a gyp-relative path and + a language code ('c', 'cc', 'm', or 'mm'), and that returns a path + to where the output of precompiling that path for that language + should be placed (without the trailing '.gch'). + """ + # This doesn't support per-configuration prefix headers. Good enough + # for now. + self.header = None + self.compile_headers = False + if xcode_settings: + self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER') + self.compile_headers = xcode_settings.GetPerTargetSetting( + 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO' + self.compiled_headers = {} + if self.header: + if self.compile_headers: + for lang in ['c', 'cc', 'm', 'mm']: + self.compiled_headers[lang] = gyp_path_to_build_output( + self.header, lang) + self.header = gyp_path_to_build_path(self.header) + + def GetInclude(self, lang): + """Gets the cflags to include the prefix header for language |lang|.""" + if self.compile_headers and lang in self.compiled_headers: + return '-include %s' % self.compiled_headers[lang] + elif self.header: + return '-include %s' % self.header + else: + return '' + + def _Gch(self, lang): + """Returns the actual file name of the prefix header for language |lang|.""" + assert self.compile_headers + return self.compiled_headers[lang] + '.gch' + + def GetObjDependencies(self, sources, objs): + """Given a list of source files and the corresponding object files, returns + a list of (source, object, gch) tuples, where |gch| is the build-directory + relative path to the gch file each object file depends on. |compilable[i]| + has to be the source file belonging to |objs[i]|.""" + if not self.header or not self.compile_headers: + return [] + + result = [] + for source, obj in zip(sources, objs): + ext = os.path.splitext(source)[1] + lang = { + '.c': 'c', + '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc', + '.m': 'm', + '.mm': 'mm', + }.get(ext, None) + if lang: + result.append((source, obj, self._Gch(lang))) + return result + + def GetGchBuildCommands(self): + """Returns [(path_to_gch, language_flag, language, header)]. + |path_to_gch| and |header| are relative to the build directory. + """ + if not self.header or not self.compile_headers: + return [] + return [ + (self._Gch('c'), '-x c-header', 'c', self.header), + (self._Gch('cc'), '-x c++-header', 'cc', self.header), + (self._Gch('m'), '-x objective-c-header', 'm', self.header), + (self._Gch('mm'), '-x objective-c++-header', 'mm', self.header), + ] + + +def MergeGlobalXcodeSettingsToSpec(global_dict, spec): + """Merges the global xcode_settings dictionary into each configuration of the + target represented by spec. For keys that are both in the global and the local + xcode_settings dict, the local key gets precendence. + """ + # The xcode generator special-cases global xcode_settings and does something + # that amounts to merging in the global xcode_settings into each local + # xcode_settings dict. + global_xcode_settings = global_dict.get('xcode_settings', {}) + for config in spec['configurations'].values(): + if 'xcode_settings' in config: + new_settings = global_xcode_settings.copy() + new_settings.update(config['xcode_settings']) + config['xcode_settings'] = new_settings + + +def IsMacBundle(flavor, spec): + """Returns if |spec| should be treated as a bundle. + + Bundles are directories with a certain subdirectory structure, instead of + just a single file. Bundle rules do not produce a binary but also package + resources into that directory.""" + is_mac_bundle = (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac') + if is_mac_bundle: + assert spec['type'] != 'none', ( + 'mac_bundle targets cannot have type none (target "%s")' % + spec['target_name']) + return is_mac_bundle + + +def GetMacBundleResources(product_dir, xcode_settings, resources): + """Yields (output, resource) pairs for every resource in |resources|. + Only call this for mac bundle targets. + + Args: + product_dir: Path to the directory containing the output bundle, + relative to the build directory. + xcode_settings: The XcodeSettings of the current target. + resources: A list of bundle resources, relative to the build directory. + """ + dest = os.path.join(product_dir, + xcode_settings.GetBundleResourceFolder()) + for res in resources: + output = dest + + # The make generator doesn't support it, so forbid it everywhere + # to keep the generators more interchangable. + assert ' ' not in res, ( + "Spaces in resource filenames not supported (%s)" % res) + + # Split into (path,file). + res_parts = os.path.split(res) + + # Now split the path into (prefix,maybe.lproj). + lproj_parts = os.path.split(res_parts[0]) + # If the resource lives in a .lproj bundle, add that to the destination. + if lproj_parts[1].endswith('.lproj'): + output = os.path.join(output, lproj_parts[1]) + + output = os.path.join(output, res_parts[1]) + # Compiled XIB files are referred to by .nib. + if output.endswith('.xib'): + output = output[0:-3] + 'nib' + + yield output, res + + +def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path): + """Returns (info_plist, dest_plist, defines, extra_env), where: + * |info_plist| is the sourc plist path, relative to the + build directory, + * |dest_plist| is the destination plist path, relative to the + build directory, + * |defines| is a list of preprocessor defines (empty if the plist + shouldn't be preprocessed, + * |extra_env| is a dict of env variables that should be exported when + invoking |mac_tool copy-info-plist|. + + Only call this for mac bundle targets. + + Args: + product_dir: Path to the directory containing the output bundle, + relative to the build directory. + xcode_settings: The XcodeSettings of the current target. + gyp_to_build_path: A function that converts paths relative to the + current gyp file to paths relative to the build direcotry. + """ + info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE') + if not info_plist: + return None, None, [], {} + + # The make generator doesn't support it, so forbid it everywhere + # to keep the generators more interchangable. + assert ' ' not in info_plist, ( + "Spaces in Info.plist filenames not supported (%s)" % info_plist) + + info_plist = gyp_path_to_build_path(info_plist) + + # If explicitly set to preprocess the plist, invoke the C preprocessor and + # specify any defines as -D flags. + if xcode_settings.GetPerTargetSetting( + 'INFOPLIST_PREPROCESS', default='NO') == 'YES': + # Create an intermediate file based on the path. + defines = shlex.split(xcode_settings.GetPerTargetSetting( + 'INFOPLIST_PREPROCESSOR_DEFINITIONS', default='')) + else: + defines = [] + + dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath()) + extra_env = xcode_settings.GetPerTargetSettings() + + return info_plist, dest_plist, defines, extra_env + + +def GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration, + additional_settings=None): + """Return the environment variables that Xcode would set. See + http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153 + for a full list. + + Args: + xcode_settings: An XcodeSettings object. If this is None, this function + returns an empty dict. + built_products_dir: Absolute path to the built products dir. + srcroot: Absolute path to the source root. + configuration: The build configuration name. + additional_settings: An optional dict with more values to add to the + result. + """ + if not xcode_settings: return {} + + # This function is considered a friend of XcodeSettings, so let it reach into + # its implementation details. + spec = xcode_settings.spec + + # These are filled in on a as-needed basis. + env = { + 'BUILT_PRODUCTS_DIR' : built_products_dir, + 'CONFIGURATION' : configuration, + 'PRODUCT_NAME' : xcode_settings.GetProductName(), + # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME + 'SRCROOT' : srcroot, + 'SOURCE_ROOT': '${SRCROOT}', + # This is not true for static libraries, but currently the env is only + # written for bundles: + 'TARGET_BUILD_DIR' : built_products_dir, + 'TEMP_DIR' : '${TMPDIR}', + } + if spec['type'] in ( + 'executable', 'static_library', 'shared_library', 'loadable_module'): + env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName() + env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath() + env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName() + mach_o_type = xcode_settings.GetMachOType() + if mach_o_type: + env['MACH_O_TYPE'] = mach_o_type + env['PRODUCT_TYPE'] = xcode_settings.GetProductType() + if xcode_settings._IsBundle(): + env['CONTENTS_FOLDER_PATH'] = \ + xcode_settings.GetBundleContentsFolderPath() + env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \ + xcode_settings.GetBundleResourceFolder() + env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath() + env['WRAPPER_NAME'] = xcode_settings.GetWrapperName() + + if not additional_settings: + additional_settings = {} + else: + # Flatten lists to strings. + for k in additional_settings: + if not isinstance(additional_settings[k], str): + additional_settings[k] = ' '.join(additional_settings[k]) + additional_settings.update(env) + + for k in additional_settings: + additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k]) + + return additional_settings + + +def _NormalizeEnvVarReferences(str): + """Takes a string containing variable references in the form ${FOO}, $(FOO), + or $FOO, and returns a string with all variable references in the form ${FOO}. + """ + # $FOO -> ${FOO} + str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str) + + # $(FOO) -> ${FOO} + matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str) + for match in matches: + to_replace, variable = match + assert '$(' not in match, '$($(FOO)) variables not supported: ' + match + str = str.replace(to_replace, '${' + variable + '}') + + return str + + +def ExpandEnvVars(string, expansions): + """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the + expansions dict. If the variable expands to something that references + another variable, this variable is expanded as well if it's in env -- + until no variables present in env are left.""" + for k in reversed(TopologicallySortedEnvVarKeys(expansions)): + string = string.replace('${' + k + '}', expansions[k]) + string = string.replace('$(' + k + ')', expansions[k]) + string = string.replace('$' + k, expansions[k]) + return string + + +def TopologicallySortedEnvVarKeys(env): + """Takes a dict |env| whose values are strings that can refer to other keys, + for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of + env such that key2 is after key1 in L if env[key2] refers to env[key1]. + + Throws an Exception in case of dependency cycles. + """ + # Since environment variables can refer to other variables, the evaluation + # order is important. Below is the logic to compute the dependency graph + # and sort it. + regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}') + + # First sort the list of keys. + key_list = sorted(env.keys()) + + # Phase 1: Create a set of edges of (DEPENDEE, DEPENDER) where in the graph, + # DEPENDEE -> DEPENDER. Also create sets of dependers and dependees. + edges = set() + dependees = set() + dependers = set() + for k in key_list: + matches = regex.findall(env[k]) + if not len(matches): + continue + + depends_on_other_var = False + for dependee in matches: + assert '${' not in dependee, 'Nested variables not supported: ' + dependee + if dependee in env: + edges.add((dependee, k)) + dependees.add(dependee) + depends_on_other_var = True + if depends_on_other_var: + dependers.add(k) + + # Phase 2: Create a list of graph nodes with no incoming edges. + sorted_nodes = [] + edgeless_nodes = dependees - dependers + + # Phase 3: Perform Kahn topological sort. + while len(edgeless_nodes): + # Find a node with no incoming edges, add it to the sorted list, and + # remove it from the list of nodes that aren't part of the graph. + node = edgeless_nodes.pop() + sorted_nodes.append(node) + key_list.remove(node) + + # Find all the edges between |node| and other nodes. + edges_to_node = [e for e in edges if e[0] == node] + for edge in edges_to_node: + edges.remove(edge) + # If the node connected to |node| by |edge| has no other incoming edges, + # add it to |edgeless_nodes|. + if not len([e for e in edges if e[1] == edge[1]]): + edgeless_nodes.add(edge[1]) + + # Any remaining edges indicate a cycle. + if len(edges): + raise Exception('Xcode environment variables are cyclically dependent: ' + + str(edges)) + + # Append the "nodes" not in the graph to those that were just sorted. + sorted_nodes.extend(key_list) + + return sorted_nodes + +def GetSpecPostbuildCommands(spec, gyp_path_to_build_path, quiet=False): + """Returns the list of postbuilds explicitly defined on |spec|, in a form + executable by a shell.""" + postbuilds = [] + for postbuild in spec.get('postbuilds', []): + if not quiet: + postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( + spec['target_name'], postbuild['postbuild_name'])) + shell_list = postbuild['action'][:] + # The first element is the command. If it's a relative path, it's + # a script in the source tree relative to the gyp file and needs to be + # absolutified. Else, it's in the PATH (e.g. install_name_tool, ln). + if os.path.sep in shell_list[0]: + shell_list[0] = gyp_path_to_build_path(shell_list[0]) + + # "script.sh" -> "./script.sh" + if not os.path.sep in shell_list[0]: + shell_list[0] = os.path.join('.', shell_list[0]) + postbuilds.append(gyp.common.EncodePOSIXShellList(shell_list)) + + return postbuilds diff --git a/tools/gyp/test/home_dot_gyp/gyptest-home-includes-regyp.py b/tools/gyp/test/home_dot_gyp/gyptest-home-includes-regyp.py index 974db7fee5..59573414ae 100755 --- a/tools/gyp/test/home_dot_gyp/gyptest-home-includes-regyp.py +++ b/tools/gyp/test/home_dot_gyp/gyptest-home-includes-regyp.py @@ -5,7 +5,7 @@ # found in the LICENSE file. """ -Verifies inclusion of $HOME/.gyp/includes.gypi works properly with relocation +Verifies inclusion of $HOME/.gyp/include.gypi works properly with relocation and with regeneration. """ @@ -21,7 +21,7 @@ os.environ['HOME'] = os.path.abspath('home') test.run_gyp('all.gyp', chdir='src') # After relocating, we should still be able to build (build file shouldn't -# contain relative reference to ~/.gyp/includes.gypi) +# contain relative reference to ~/.gyp/include.gypi) test.relocate('src', 'relocate/src') test.build('all.gyp', test.ALL, chdir='relocate/src') @@ -30,7 +30,7 @@ test.run_built_executable('printfoo', chdir='relocate/src', stdout='FOO is fromhome\n') -# Building should notice any changes to ~/.gyp/includes.gypi and regyp. +# Building should notice any changes to ~/.gyp/include.gypi and regyp. test.sleep() test.write('home/.gyp/include.gypi', test.read('home2/.gyp/include.gypi')) diff --git a/tools/gyp/test/home_dot_gyp/gyptest-home-includes.py b/tools/gyp/test/home_dot_gyp/gyptest-home-includes.py index 3131e2d248..8ad52556be 100755 --- a/tools/gyp/test/home_dot_gyp/gyptest-home-includes.py +++ b/tools/gyp/test/home_dot_gyp/gyptest-home-includes.py @@ -5,7 +5,7 @@ # found in the LICENSE file. """ -Verifies inclusion of $HOME/.gyp/includes.gypi works. +Verifies inclusion of $HOME/.gyp/include.gypi works. """ import os @@ -18,7 +18,7 @@ os.environ['HOME'] = os.path.abspath('home') test.run_gyp('all.gyp', chdir='src') # After relocating, we should still be able to build (build file shouldn't -# contain relative reference to ~/.gyp/includes.gypi) +# contain relative reference to ~/.gyp/include.gypi) test.relocate('src', 'relocate/src') test.build('all.gyp', test.ALL, chdir='relocate/src') diff --git a/tools/gyp/test/lib/TestGyp.py b/tools/gyp/test/lib/TestGyp.py index 2b4b0db967..7fbb56bfac 100644 --- a/tools/gyp/test/lib/TestGyp.py +++ b/tools/gyp/test/lib/TestGyp.py @@ -1,4 +1,4 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -222,7 +222,11 @@ class TestGypBase(TestCommon.TestCommon): Runs gyp against the specified gyp_file with the specified args. """ # TODO: --depth=. works around Chromium-specific tree climbing. - args = ('--depth=.', '--format='+self.format, gyp_file) + args + depth = '.' + if 'depth' in kw: + depth = kw['depth'] + del kw['depth'] + args = ('--depth='+depth, '--format='+self.format, gyp_file) + args return self.run(program=self.gyp, arguments=args, **kw) def run(self, *args, **kw): @@ -415,24 +419,10 @@ class TestGypNinja(TestGypBase): ALL = 'all' DEFAULT = 'all' - # The default library prefix is computed from TestCommon.lib_prefix, - # but ninja uses no prefix for static libraries. - lib_ = '' - def run_gyp(self, gyp_file, *args, **kw): - # We must pass the desired configuration as a parameter. - if self.configuration: - args = list(args) + ['-Gconfig=' + self.configuration] - # Stash the gyp configuration we used to run gyp, so we can - # know whether we need to rerun it later. - self.last_gyp_configuration = self.configuration TestGypBase.run_gyp(self, gyp_file, *args, **kw) def build(self, gyp_file, target=None, **kw): - if self.last_gyp_configuration != self.configuration: - # Rerun gyp if necessary. - self.run_gyp(gyp_file) - arguments = kw.get('arguments', [])[:] # Add a -C output/path to the command line. @@ -450,9 +440,9 @@ class TestGypNinja(TestGypBase): # Enclosing the name in a list avoids prepending the original dir. program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)] if sys.platform == 'darwin': - libdir = os.path.join('out', 'Default', 'lib') + libdir = os.path.join('out', 'Default') if self.configuration: - libdir = os.path.join('out', self.configuration, 'lib') + libdir = os.path.join('out', self.configuration) os.environ['DYLD_LIBRARY_PATH'] = libdir return self.run(program=program, *args, **kw) @@ -467,7 +457,8 @@ class TestGypNinja(TestGypBase): if sys.platform != 'darwin': result.append('obj') elif type == self.SHARED_LIB: - result.append('lib') + if sys.platform != 'darwin': + result.append('lib') subdir = kw.get('subdir') if subdir: result.append(subdir) diff --git a/tools/gyp/test/mac/action-envvars/action/action.gyp b/tools/gyp/test/mac/action-envvars/action/action.gyp new file mode 100644 index 0000000000..d9d65745ca --- /dev/null +++ b/tools/gyp/test/mac/action-envvars/action/action.gyp @@ -0,0 +1,34 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'action', + 'type': 'none', + 'actions': [ + { + 'inputs': [ ], + 'outputs': [ + '<(PRODUCT_DIR)/result', + '<(SHARED_INTERMEDIATE_DIR)/tempfile', + ], + 'action_name': 'Test action', + 'action': ['./action.sh', '<(SHARED_INTERMEDIATE_DIR)/tempfile' ], + }, + { + 'inputs': [ + '<(SHARED_INTERMEDIATE_DIR)/tempfile', + ], + 'outputs': [ + '<(PRODUCT_DIR)/other_result', + ], + 'action_name': 'Other test action', + 'action': ['cp', '<(SHARED_INTERMEDIATE_DIR)/tempfile', + '<(PRODUCT_DIR)/other_result' ], + }, + ], + }, + ], +} + diff --git a/tools/gyp/test/mac/action-envvars/action/action.sh b/tools/gyp/test/mac/action-envvars/action/action.sh new file mode 100755 index 0000000000..48d5f6bf86 --- /dev/null +++ b/tools/gyp/test/mac/action-envvars/action/action.sh @@ -0,0 +1,8 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +echo 'Test output' > "${BUILT_PRODUCTS_DIR}/result" +echo 'Other output' > "$1" diff --git a/tools/gyp/test/mac/archs/my_file.cc b/tools/gyp/test/mac/archs/my_file.cc new file mode 100644 index 0000000000..94216a74df --- /dev/null +++ b/tools/gyp/test/mac/archs/my_file.cc @@ -0,0 +1,4 @@ +/* Copyright (c) 2012 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ +int x = 1; diff --git a/tools/gyp/test/mac/archs/my_main_file.cc b/tools/gyp/test/mac/archs/my_main_file.cc new file mode 100644 index 0000000000..f1fa06f276 --- /dev/null +++ b/tools/gyp/test/mac/archs/my_main_file.cc @@ -0,0 +1,9 @@ +/* Copyright (c) 2012 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ +#include +extern int x; +int main() { + printf("hello, world %d\n", x); +} + diff --git a/tools/gyp/test/mac/archs/test-archs-x86_64.gyp b/tools/gyp/test/mac/archs/test-archs-x86_64.gyp new file mode 100644 index 0000000000..d11a896273 --- /dev/null +++ b/tools/gyp/test/mac/archs/test-archs-x86_64.gyp @@ -0,0 +1,27 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'lib', + 'product_name': 'Test64', + 'type': 'static_library', + 'sources': [ 'my_file.cc' ], + 'xcode_settings': { + 'ARCHS': [ 'x86_64' ], + }, + }, + { + 'target_name': 'exe', + 'product_name': 'Test64', + 'type': 'executable', + 'dependencies': [ 'lib' ], + 'sources': [ 'my_main_file.cc' ], + 'xcode_settings': { + 'ARCHS': [ 'x86_64' ], + }, + }, + ] +} diff --git a/tools/gyp/test/mac/archs/test-no-archs.gyp b/tools/gyp/test/mac/archs/test-no-archs.gyp new file mode 100644 index 0000000000..8f3b6b47cc --- /dev/null +++ b/tools/gyp/test/mac/archs/test-no-archs.gyp @@ -0,0 +1,21 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'lib', + 'product_name': 'Test', + 'type': 'static_library', + 'sources': [ 'my_file.cc' ], + }, + { + 'target_name': 'exe', + 'product_name': 'Test', + 'type': 'executable', + 'dependencies': [ 'lib' ], + 'sources': [ 'my_main_file.cc' ], + }, + ] +} diff --git a/tools/gyp/test/mac/copy-dylib/empty.c b/tools/gyp/test/mac/copy-dylib/empty.c new file mode 100644 index 0000000000..237c8ce181 --- /dev/null +++ b/tools/gyp/test/mac/copy-dylib/empty.c @@ -0,0 +1 @@ +int main() {} diff --git a/tools/gyp/test/mac/copy-dylib/test.gyp b/tools/gyp/test/mac/copy-dylib/test.gyp new file mode 100644 index 0000000000..4210c51463 --- /dev/null +++ b/tools/gyp/test/mac/copy-dylib/test.gyp @@ -0,0 +1,31 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'my_dylib', + 'type': 'shared_library', + 'sources': [ 'empty.c', ], + }, + { + 'target_name': 'test_app', + 'product_name': 'Test App', + 'type': 'executable', + 'mac_bundle': 1, + 'dependencies': [ 'my_dylib', ], + 'sources': [ + 'empty.c', + ], + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)/Test App.app/Contents/Resources', + 'files': [ + '<(PRODUCT_DIR)/libmy_dylib.dylib', + ], + }, + ], + }, + ], +} + diff --git a/tools/gyp/test/mac/depend-on-bundle/English.lproj/InfoPlist.strings b/tools/gyp/test/mac/depend-on-bundle/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000..b92732c79e --- /dev/null +++ b/tools/gyp/test/mac/depend-on-bundle/English.lproj/InfoPlist.strings @@ -0,0 +1 @@ +/* Localized versions of Info.plist keys */ diff --git a/tools/gyp/test/mac/depend-on-bundle/Info.plist b/tools/gyp/test/mac/depend-on-bundle/Info.plist new file mode 100644 index 0000000000..5e05a5190c --- /dev/null +++ b/tools/gyp/test/mac/depend-on-bundle/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/tools/gyp/test/mac/depend-on-bundle/bundle.c b/tools/gyp/test/mac/depend-on-bundle/bundle.c new file mode 100644 index 0000000000..d64ff8ca23 --- /dev/null +++ b/tools/gyp/test/mac/depend-on-bundle/bundle.c @@ -0,0 +1 @@ +int f() { return 42; } diff --git a/tools/gyp/test/mac/depend-on-bundle/executable.c b/tools/gyp/test/mac/depend-on-bundle/executable.c new file mode 100644 index 0000000000..931bce637e --- /dev/null +++ b/tools/gyp/test/mac/depend-on-bundle/executable.c @@ -0,0 +1,4 @@ +int f(); +int main() { + return f(); +} diff --git a/tools/gyp/test/mac/depend-on-bundle/test.gyp b/tools/gyp/test/mac/depend-on-bundle/test.gyp new file mode 100644 index 0000000000..e00b105415 --- /dev/null +++ b/tools/gyp/test/mac/depend-on-bundle/test.gyp @@ -0,0 +1,28 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'my_bundle', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ 'bundle.c' ], + 'mac_bundle_resources': [ + 'English.lproj/InfoPlist.strings', + ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'Info.plist', + } + }, + { + 'target_name': 'dependent_on_bundle', + 'type': 'executable', + 'sources': [ 'executable.c' ], + 'dependencies': [ + 'my_bundle', + ], + }, + ], +} + diff --git a/tools/gyp/test/mac/global-settings/src/dir1/dir1.gyp b/tools/gyp/test/mac/global-settings/src/dir1/dir1.gyp new file mode 100644 index 0000000000..153e34ddd6 --- /dev/null +++ b/tools/gyp/test/mac/global-settings/src/dir1/dir1.gyp @@ -0,0 +1,11 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'dir1_target', + 'type': 'none', + }, + ], +} diff --git a/tools/gyp/test/mac/global-settings/src/dir2/dir2.gyp b/tools/gyp/test/mac/global-settings/src/dir2/dir2.gyp new file mode 100644 index 0000000000..cda46c839b --- /dev/null +++ b/tools/gyp/test/mac/global-settings/src/dir2/dir2.gyp @@ -0,0 +1,22 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'dir2_target', + 'type': 'none', + 'dependencies': [ + '../dir1/dir1.gyp:dir1_target', + ], + 'actions': [ + { + 'inputs': [ ], + 'outputs': [ '<(PRODUCT_DIR)/file.txt' ], + 'action_name': 'Test action', + 'action': ['cp', 'file.txt', '${BUILT_PRODUCTS_DIR}/file.txt' ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/global-settings/src/dir2/file.txt b/tools/gyp/test/mac/global-settings/src/dir2/file.txt new file mode 100644 index 0000000000..58da2d8e9a --- /dev/null +++ b/tools/gyp/test/mac/global-settings/src/dir2/file.txt @@ -0,0 +1 @@ +File. diff --git a/tools/gyp/test/mac/gyptest-action-envvars.py b/tools/gyp/test/mac/gyptest-action-envvars.py new file mode 100644 index 0000000000..b4f37c43a2 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-action-envvars.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that env vars work with actions, with relative directory paths. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + CHDIR = 'action-envvars' + test.run_gyp('action/action.gyp', chdir=CHDIR) + test.build('action/action.gyp', 'action', chdir=CHDIR, SYMROOT='../build') + + result_file = test.built_file_path('result', chdir=CHDIR) + test.must_exist(result_file) + test.must_contain(result_file, 'Test output') + + other_result_file = test.built_file_path('other_result', chdir=CHDIR) + test.must_exist(other_result_file) + test.must_contain(other_result_file, 'Other output') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-app.py b/tools/gyp/test/mac/gyptest-app.py index 5e772f8144..bce48f8938 100755 --- a/tools/gyp/test/mac/gyptest-app.py +++ b/tools/gyp/test/mac/gyptest-app.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='app-bundle') diff --git a/tools/gyp/test/mac/gyptest-archs.py b/tools/gyp/test/mac/gyptest-archs.py new file mode 100644 index 0000000000..781e9ef169 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-archs.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Tests things related to ARCHS. +""" + +import TestGyp + +import subprocess +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + def CheckFileType(file, expected): + proc = subprocess.Popen(['file', '-b', file], stdout=subprocess.PIPE) + o = proc.communicate()[0].strip() + assert not proc.returncode + if o != expected: + print 'File: Expected %s, got %s' % (expected, o) + test.fail_test() + + test.run_gyp('test-no-archs.gyp', chdir='archs') + test.build('test-no-archs.gyp', test.ALL, chdir='archs') + result_file = test.built_file_path('Test', chdir='archs') + test.must_exist(result_file) + CheckFileType(result_file, 'Mach-O executable i386') + + test.run_gyp('test-archs-x86_64.gyp', chdir='archs') + test.build('test-archs-x86_64.gyp', test.ALL, chdir='archs') + result_file = test.built_file_path('Test64', chdir='archs') + test.must_exist(result_file) + CheckFileType(result_file, 'Mach-O 64-bit executable x86_64') diff --git a/tools/gyp/test/mac/gyptest-copies.py b/tools/gyp/test/mac/gyptest-copies.py index 091b1dec5b..c88065eade 100755 --- a/tools/gyp/test/mac/gyptest-copies.py +++ b/tools/gyp/test/mac/gyptest-copies.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -15,7 +15,7 @@ import sys import time if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('framework.gyp', chdir='framework') diff --git a/tools/gyp/test/mac/gyptest-copy-dylib.py b/tools/gyp/test/mac/gyptest-copy-dylib.py new file mode 100644 index 0000000000..253623d1c6 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-copy-dylib.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that dylibs can be copied into app bundles. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + test.run_gyp('test.gyp', chdir='copy-dylib') + + test.build('test.gyp', 'test_app', chdir='copy-dylib') + + test.built_file_must_exist( + 'Test App.app/Contents/Resources/libmy_dylib.dylib', chdir='copy-dylib') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-debuginfo.py b/tools/gyp/test/mac/gyptest-debuginfo.py index 1ec2d1da10..a0e9438e2a 100755 --- a/tools/gyp/test/mac/gyptest-debuginfo.py +++ b/tools/gyp/test/mac/gyptest-debuginfo.py @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='debuginfo') diff --git a/tools/gyp/test/mac/gyptest-depend-on-bundle.py b/tools/gyp/test/mac/gyptest-depend-on-bundle.py new file mode 100644 index 0000000000..5cccb03227 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-depend-on-bundle.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that a dependency on a bundle causes the whole bundle to be built. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + test.run_gyp('test.gyp', chdir='depend-on-bundle') + + test.build('test.gyp', 'dependent_on_bundle', chdir='depend-on-bundle') + + # Binary itself. + test.built_file_must_exist('dependent_on_bundle', chdir='depend-on-bundle') + + # Bundle dependency. + test.built_file_must_exist( + 'my_bundle.framework/Versions/A/my_bundle', + chdir='depend-on-bundle') + test.built_file_must_exist( # package_framework + 'my_bundle.framework/my_bundle', + chdir='depend-on-bundle') + test.built_file_must_exist( # plist + 'my_bundle.framework/Versions/A/Resources/Info.plist', + chdir='depend-on-bundle') + test.built_file_must_exist( + 'my_bundle.framework/Versions/A/Resources/English.lproj/' # Resources + 'InfoPlist.strings', + chdir='depend-on-bundle') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-framework.py b/tools/gyp/test/mac/gyptest-framework.py index 6c19e40b33..e4342d8420 100755 --- a/tools/gyp/test/mac/gyptest-framework.py +++ b/tools/gyp/test/mac/gyptest-framework.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('framework.gyp', chdir='framework') diff --git a/tools/gyp/test/mac/gyptest-global-settings.py b/tools/gyp/test/mac/gyptest-global-settings.py new file mode 100644 index 0000000000..648d32cded --- /dev/null +++ b/tools/gyp/test/mac/gyptest-global-settings.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that the global xcode_settings processing doesn't throw. +Regression test for http://crbug.com/109163 +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + test.run_gyp('src/dir2/dir2.gyp', chdir='global-settings', depth='src') + # run_gyp shouldn't throw. + + # Check that BUILT_PRODUCTS_DIR was set correctly, too. + test.build('dir2/dir2.gyp', 'dir2_target', chdir='global-settings/src', + SYMROOT='../build') + test.built_file_must_exist('file.txt', chdir='global-settings/src') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-infoplist-process.py b/tools/gyp/test/mac/gyptest-infoplist-process.py index 9a2b140d31..20874a398a 100755 --- a/tools/gyp/test/mac/gyptest-infoplist-process.py +++ b/tools/gyp/test/mac/gyptest-infoplist-process.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) CHDIR = 'infoplist-process' INFO_PLIST_PATH = 'Test.app/Contents/Info.plist' diff --git a/tools/gyp/test/mac/gyptest-libraries.py b/tools/gyp/test/mac/gyptest-libraries.py new file mode 100755 index 0000000000..46814d65af --- /dev/null +++ b/tools/gyp/test/mac/gyptest-libraries.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies libraries (in link_settings) are properly found. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + test.run_gyp('subdir/test.gyp', chdir='libraries') + + test.build('subdir/test.gyp', test.ALL, chdir='libraries') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-loadable-module.py b/tools/gyp/test/mac/gyptest-loadable-module.py index 39e87f0605..e5e022c9fb 100755 --- a/tools/gyp/test/mac/gyptest-loadable-module.py +++ b/tools/gyp/test/mac/gyptest-loadable-module.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,7 +14,7 @@ import os import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='loadable-module') test.build('test.gyp', test.ALL, chdir='loadable-module') diff --git a/tools/gyp/test/mac/gyptest-non-strs-flattened-to-env.py b/tools/gyp/test/mac/gyptest-non-strs-flattened-to-env.py new file mode 100644 index 0000000000..504dcd589b --- /dev/null +++ b/tools/gyp/test/mac/gyptest-non-strs-flattened-to-env.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that list xcode_settings are flattened before being exported to the +environment. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + CHDIR = 'non-strs-flattened-to-env' + INFO_PLIST_PATH = 'Test.app/Contents/Info.plist' + + test.run_gyp('test.gyp', chdir=CHDIR) + test.build('test.gyp', test.ALL, chdir=CHDIR) + info_plist = test.built_file_path(INFO_PLIST_PATH, chdir=CHDIR) + test.must_exist(info_plist) + test.must_contain(info_plist, '''\ +\tMy Variable +\tsome expansion''') + test.must_contain(info_plist, '''\ +\tCFlags +\t-fstack-protector-all -fno-strict-aliasing -DS="A Space"''') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-postbuild-copy-bundle.py b/tools/gyp/test/mac/gyptest-postbuild-copy-bundle.py new file mode 100644 index 0000000000..0f63ad5241 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-postbuild-copy-bundle.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that a postbuild copying a dependend framework into an app bundle is +rerun if the resources in the framework change. +""" + +import TestGyp + +import os.path +import sys + +if sys.platform == 'darwin': + # TODO(thakis): Make this pass with the make generator, http://crbug.com/95529 + test = TestGyp.TestGyp(formats=['ninja', 'xcode']) + + CHDIR = 'postbuild-copy-bundle' + test.run_gyp('test.gyp', chdir=CHDIR) + + app_bundle_dir = test.built_file_path('Test app.app', chdir=CHDIR) + bundled_framework_dir = os.path.join( + app_bundle_dir, 'Contents', 'My Framework.framework', 'Resources') + final_plist_path = os.path.join(bundled_framework_dir, 'Info.plist') + final_resource_path = os.path.join(bundled_framework_dir, 'resource_file.sb') + + # Check that the dependency was built and copied into the app bundle: + test.build('test.gyp', 'test_app', chdir=CHDIR) + test.must_exist(final_resource_path) + test.must_match(final_resource_path, + 'This is included in the framework bundle.\n') + + test.must_exist(final_plist_path) + test.must_contain(final_plist_path, '''\ +\tRandomKey +\tRandomValue''') + + # Touch the dependency's bundle resource, and check that the modification + # makes it all the way into the app bundle: + test.sleep() + test.write('postbuild-copy-bundle/resource_file.sb', 'New text\n') + test.build('test.gyp', 'test_app', chdir=CHDIR) + + test.must_exist(final_resource_path) + test.must_match(final_resource_path, 'New text\n') + + # Check the same for the plist file. + test.sleep() + contents = test.read('postbuild-copy-bundle/Framework-Info.plist') + contents = contents.replace('RandomValue', 'NewRandomValue') + test.write('postbuild-copy-bundle/Framework-Info.plist', contents) + test.build('test.gyp', 'test_app', chdir=CHDIR) + + test.must_exist(final_plist_path) + test.must_contain(final_plist_path, '''\ +\tRandomKey +\tNewRandomValue''') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-postbuild-defaults.py b/tools/gyp/test/mac/gyptest-postbuild-defaults.py new file mode 100644 index 0000000000..0560904c29 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-postbuild-defaults.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that a postbuild invoking |defaults| works. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + CHDIR = 'postbuild-defaults' + test.run_gyp('test.gyp', chdir=CHDIR) + test.build('test.gyp', test.ALL, chdir=CHDIR) + + result_file = test.built_file_path('result', chdir=CHDIR) + test.must_exist(result_file) + test.must_contain(result_file, '''\ +Test +${PRODUCT_NAME} +''') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-postbuild-fail.py b/tools/gyp/test/mac/gyptest-postbuild-fail.py index 28b1a8061f..dba0d6f042 100755 --- a/tools/gyp/test/mac/gyptest-postbuild-fail.py +++ b/tools/gyp/test/mac/gyptest-postbuild-fail.py @@ -14,13 +14,15 @@ import sys if sys.platform == 'darwin': # set |match| to ignore build stderr output. - test = TestGyp.TestGyp(formats=['make', 'xcode'], match = lambda a, b: True) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode'], + match = lambda a, b: True) test.run_gyp('test.gyp', chdir='postbuild-fail') build_error_code = { 'xcode': 1, 'make': 2, + 'ninja': 1, }[test.format] diff --git a/tools/gyp/test/mac/gyptest-postbuild-multiple-configurations.py b/tools/gyp/test/mac/gyptest-postbuild-multiple-configurations.py new file mode 100644 index 0000000000..84694f36cc --- /dev/null +++ b/tools/gyp/test/mac/gyptest-postbuild-multiple-configurations.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that a postbuild work in projects with multiple configurations. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + CHDIR = 'postbuild-multiple-configurations' + test.run_gyp('test.gyp', chdir=CHDIR) + + for configuration in ['Debug', 'Release']: + test.set_configuration(configuration) + test.build('test.gyp', test.ALL, chdir=CHDIR) + test.built_file_must_exist('postbuild-file', chdir=CHDIR) + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-postbuild-static-library.gyp b/tools/gyp/test/mac/gyptest-postbuild-static-library.gyp new file mode 100644 index 0000000000..8f9a6ebcb0 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-postbuild-static-library.gyp @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that a postbuilds on static libraries work, and that sourceless +libraries don't cause failures at gyp time. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['make', 'xcode']) + + CHDIR = 'postbuild-static-library' + test.run_gyp('test.gyp', chdir=CHDIR) + test.build('test.gyp', 'my_lib', chdir=CHDIR) + # Building my_sourceless_lib doesn't work with make. gyp should probably + # forbid sourceless static libraries, since they're pretty pointless. + # But they shouldn't cause gyp time exceptions. + + test.built_file_must_exist('postbuild-file', chdir=CHDIR) + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-postbuild.py b/tools/gyp/test/mac/gyptest-postbuild.py index b69a0bdfea..d1dcba7158 100755 --- a/tools/gyp/test/mac/gyptest-postbuild.py +++ b/tools/gyp/test/mac/gyptest-postbuild.py @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='postbuilds') diff --git a/tools/gyp/test/mac/gyptest-prefixheader.py b/tools/gyp/test/mac/gyptest-prefixheader.py index 04b00f6b13..768551f9b0 100755 --- a/tools/gyp/test/mac/gyptest-prefixheader.py +++ b/tools/gyp/test/mac/gyptest-prefixheader.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='prefixheader') test.build('test.gyp', test.ALL, chdir='prefixheader') test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-rebuild.py b/tools/gyp/test/mac/gyptest-rebuild.py index f88dcb94b2..c130bd32af 100755 --- a/tools/gyp/test/mac/gyptest-rebuild.py +++ b/tools/gyp/test/mac/gyptest-rebuild.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,16 +14,29 @@ import os import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) - test.run_gyp('test.gyp', chdir='app-bundle') + CHDIR = 'rebuild' + test.run_gyp('test.gyp', chdir=CHDIR) - test.build('test.gyp', test.ALL, chdir='app-bundle') + test.build('test.gyp', 'test_app', chdir=CHDIR) # Touch a source file, rebuild, and check that the app target is up-to-date. - os.utime('app-bundle/TestApp/main.m', None) - test.build('test.gyp', test.ALL, chdir='app-bundle') - - test.up_to_date('test.gyp', test.ALL, chdir='app-bundle') + test.touch('rebuild/main.c') + test.build('test.gyp', 'test_app', chdir=CHDIR) + + test.up_to_date('test.gyp', 'test_app', chdir=CHDIR) + + # Xcode runs postbuilds on every build, so targets with postbuilds are + # never marked as up_to_date. + if test.format != 'xcode': + # Same for a framework bundle. + test.build('test.gyp', 'test_framework_postbuilds', chdir=CHDIR) + test.up_to_date('test.gyp', 'test_framework_postbuilds', chdir=CHDIR) + + # Test that an app bundle with a postbuild that touches the app binary needs + # to be built only once. + test.build('test.gyp', 'test_app_postbuilds', chdir=CHDIR) + test.up_to_date('test.gyp', 'test_app_postbuilds', chdir=CHDIR) test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-sourceless-module.gyp b/tools/gyp/test/mac/gyptest-sourceless-module.gyp new file mode 100644 index 0000000000..c3ea73a171 --- /dev/null +++ b/tools/gyp/test/mac/gyptest-sourceless-module.gyp @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that bundles that have no 'sources' (pure resource containers) work. +""" + +import TestGyp + +import sys + +if sys.platform == 'darwin': + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + + test.run_gyp('test.gyp', chdir='sourceless-module') + + # Just needs to build without errors. + test.build('test.gyp', 'empty_bundle', chdir='sourceless-module') + test.built_file_must_not_exist( + 'empty_bundle.bundle', chdir='sourceless-module') + + # Needs to build, and contain a resource. + test.build('test.gyp', 'resource_bundle', chdir='sourceless-module') + + test.built_file_must_exist( + 'resource_bundle.bundle/Contents/Resources/foo.manifest', + chdir='sourceless-module') + test.built_file_must_not_exist( + 'resource_bundle.bundle/Contents/MacOS/resource_bundle', + chdir='sourceless-module') + + # Needs to build and cause the bundle to be built. + test.build( + 'test.gyp', 'dependent_on_resource_bundle', chdir='sourceless-module') + + test.built_file_must_exist( + 'resource_bundle.bundle/Contents/Resources/foo.manifest', + chdir='sourceless-module') + test.built_file_must_not_exist( + 'resource_bundle.bundle/Contents/MacOS/resource_bundle', + chdir='sourceless-module') + + test.pass_test() diff --git a/tools/gyp/test/mac/gyptest-strip.py b/tools/gyp/test/mac/gyptest-strip.py index d031d4b3d8..7d4abc6004 100755 --- a/tools/gyp/test/mac/gyptest-strip.py +++ b/tools/gyp/test/mac/gyptest-strip.py @@ -17,7 +17,7 @@ import sys import time if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='strip') diff --git a/tools/gyp/test/mac/gyptest-type-envvars.py b/tools/gyp/test/mac/gyptest-type-envvars.py index 1ef677319f..61596bae23 100755 --- a/tools/gyp/test/mac/gyptest-type-envvars.py +++ b/tools/gyp/test/mac/gyptest-type-envvars.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,7 +13,7 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) test.run_gyp('test.gyp', chdir='type_envvars') diff --git a/tools/gyp/test/mac/gyptest-xcode-env-order.py b/tools/gyp/test/mac/gyptest-xcode-env-order.py index 6459373247..d1c8542876 100755 --- a/tools/gyp/test/mac/gyptest-xcode-env-order.py +++ b/tools/gyp/test/mac/gyptest-xcode-env-order.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,18 +13,65 @@ import TestGyp import sys if sys.platform == 'darwin': - test = TestGyp.TestGyp(formats=['make', 'xcode']) + test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) CHDIR = 'xcode-env-order' INFO_PLIST_PATH = 'Test.app/Contents/Info.plist' test.run_gyp('test.gyp', chdir=CHDIR) test.build('test.gyp', test.ALL, chdir=CHDIR) + + # Env vars in 'copies' filenames. + test.built_file_must_exist('Test-copy-brace/main.c', chdir=CHDIR) + test.built_file_must_exist('Test-copy-paren/main.c', chdir=CHDIR) + test.built_file_must_exist('Test-copy-bare/main.c', chdir=CHDIR) + + # Env vars in 'actions' filenames and inline actions + test.built_file_must_exist('action-copy-brace.txt', chdir=CHDIR) + test.built_file_must_exist('action-copy-paren.txt', chdir=CHDIR) + test.built_file_must_exist('action-copy-bare.txt', chdir=CHDIR) + + # Env vars in Info.plist. info_plist = test.built_file_path(INFO_PLIST_PATH, chdir=CHDIR) test.must_exist(info_plist) - test.must_contain(info_plist, '>/Source/Project/Test') - test.must_contain(info_plist, '>DEP:/Source/Project/Test') - test.must_contain(info_plist, - '>com.apple.product-type.application:DEP:/Source/Project/Test') + + test.must_contain(info_plist, '''\ +\tBraceProcessedKey1 +\tD:/Source/Project/Test''') + test.must_contain(info_plist, '''\ +\tBraceProcessedKey2 +\t/Source/Project/Test''') + test.must_contain(info_plist, '''\ +\tBraceProcessedKey3 +\tcom.apple.product-type.application:D:/Source/Project/Test''') + + test.must_contain(info_plist, '''\ +\tParenProcessedKey1 +\tD:/Source/Project/Test''') + test.must_contain(info_plist, '''\ +\tParenProcessedKey2 +\t/Source/Project/Test''') + test.must_contain(info_plist, '''\ +\tParenProcessedKey3 +\tcom.apple.product-type.application:D:/Source/Project/Test''') + + test.must_contain(info_plist, '''\ +\tBareProcessedKey1 +\tD:/Source/Project/Test''') + test.must_contain(info_plist, '''\ +\tBareProcessedKey2 +\t/Source/Project/Test''') + # NOTE: For bare variables, $PRODUCT_TYPE is not replaced! It _is_ replaced + # if it's not right at the start of the string (e.g. ':$PRODUCT_TYPE'), so + # this looks like an Xcode bug. This bug isn't emulated (yet?), so check this + # only for Xcode. + if test.format == 'xcode': + test.must_contain(info_plist, '''\ +\tBareProcessedKey3 +\t$PRODUCT_TYPE:D:/Source/Project/Test''') + + test.must_contain(info_plist, '''\ +\tMixedProcessedKey +\t/Source/Project:Test:mh_execute''') test.pass_test() diff --git a/tools/gyp/test/mac/libraries/subdir/README.txt b/tools/gyp/test/mac/libraries/subdir/README.txt new file mode 100644 index 0000000000..4031ded85f --- /dev/null +++ b/tools/gyp/test/mac/libraries/subdir/README.txt @@ -0,0 +1 @@ +Make things live in a subdirectory, to make sure that DEPTH works correctly. diff --git a/tools/gyp/test/mac/libraries/subdir/hello.cc b/tools/gyp/test/mac/libraries/subdir/hello.cc new file mode 100644 index 0000000000..a43554c8ca --- /dev/null +++ b/tools/gyp/test/mac/libraries/subdir/hello.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +int main() { + std::cout << "Hello, world!" << std::endl; + return 0; +} diff --git a/tools/gyp/test/mac/libraries/subdir/mylib.c b/tools/gyp/test/mac/libraries/subdir/mylib.c new file mode 100644 index 0000000000..e771991e83 --- /dev/null +++ b/tools/gyp/test/mac/libraries/subdir/mylib.c @@ -0,0 +1,7 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +int my_foo(int x) { + return x + 1; +} diff --git a/tools/gyp/test/mac/libraries/subdir/test.gyp b/tools/gyp/test/mac/libraries/subdir/test.gyp new file mode 100644 index 0000000000..80a0269772 --- /dev/null +++ b/tools/gyp/test/mac/libraries/subdir/test.gyp @@ -0,0 +1,66 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'libraries-test', + 'type': 'executable', + 'sources': [ + 'hello.cc', + ], + 'link_settings': { + 'libraries': [ + 'libcrypto.dylib', + 'libfl.a', + ], + }, + }, + { + # This creates a static library and puts it in a nonstandard location for + # libraries-search-path-test. + 'target_name': 'mylib', + 'type': 'static_library', + 'sources': [ + 'mylib.c', + ], + 'postbuilds': [ + { + 'postbuild_name': 'Make a secret location', + 'action': [ + 'mkdir', + '-p', + '${SRCROOT}/../secret_location', + ], + }, + { + 'postbuild_name': 'Copy to secret location, with secret name', + 'action': [ + 'cp', + '${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}', + '${SRCROOT}/../secret_location/libmysecretlib.a', + ], + }, + ], + }, + { + 'target_name': 'libraries-search-path-test', + 'type': 'executable', + 'dependencies': [ 'mylib' ], + 'sources': [ + 'hello.cc', + ], + 'xcode_settings': { + 'LIBRARY_SEARCH_PATHS': [ + '<(DEPTH)/secret_location', + ], + }, + 'link_settings': { + 'libraries': [ + 'libmysecretlib.a', + ], + }, + }, + ], +} diff --git a/tools/gyp/test/mac/non-strs-flattened-to-env/Info.plist b/tools/gyp/test/mac/non-strs-flattened-to-env/Info.plist new file mode 100644 index 0000000000..11fc4b660d --- /dev/null +++ b/tools/gyp/test/mac/non-strs-flattened-to-env/Info.plist @@ -0,0 +1,15 @@ + + + + + + CFBundlePackageType + APPL + CFBundleSignature + ???? + My Variable + ${MY_VAR} + CFlags + ${OTHER_CFLAGS} + + diff --git a/tools/gyp/test/mac/non-strs-flattened-to-env/main.c b/tools/gyp/test/mac/non-strs-flattened-to-env/main.c new file mode 100644 index 0000000000..1711567ef5 --- /dev/null +++ b/tools/gyp/test/mac/non-strs-flattened-to-env/main.c @@ -0,0 +1,7 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +int main() { + return 0; +} diff --git a/tools/gyp/test/mac/non-strs-flattened-to-env/test.gyp b/tools/gyp/test/mac/non-strs-flattened-to-env/test.gyp new file mode 100644 index 0000000000..58814b73f6 --- /dev/null +++ b/tools/gyp/test/mac/non-strs-flattened-to-env/test.gyp @@ -0,0 +1,24 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'test_app', + 'product_name': 'Test', + 'type': 'executable', + 'mac_bundle': 1, + 'sources': [ 'main.c', ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'Info.plist', + 'MY_VAR': 'some expansion', + 'OTHER_CFLAGS': [ + # Just some (more than one) random flags. + '-fstack-protector-all', + '-fno-strict-aliasing', + '-DS="A Space"', # Would normally be in 'defines' + ], + }, + }, + ], +} diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/Framework-Info.plist b/tools/gyp/test/mac/postbuild-copy-bundle/Framework-Info.plist new file mode 100644 index 0000000000..ec36829c08 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/Framework-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + RandomKey + RandomValue + + diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/TestApp-Info.plist b/tools/gyp/test/mac/postbuild-copy-bundle/TestApp-Info.plist new file mode 100644 index 0000000000..98fd515200 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/TestApp-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.google.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/empty.c b/tools/gyp/test/mac/postbuild-copy-bundle/empty.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/main.c b/tools/gyp/test/mac/postbuild-copy-bundle/main.c new file mode 100644 index 0000000000..21c1963526 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/main.c @@ -0,0 +1,4 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +int main() {} diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/postbuild-copy-framework.sh b/tools/gyp/test/mac/postbuild-copy-bundle/postbuild-copy-framework.sh new file mode 100755 index 0000000000..930fec6612 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/postbuild-copy-framework.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +rsync -acC --delete "$1" "$2" diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/resource_file.sb b/tools/gyp/test/mac/postbuild-copy-bundle/resource_file.sb new file mode 100644 index 0000000000..42057fa235 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/resource_file.sb @@ -0,0 +1 @@ +This is included in the framework bundle. diff --git a/tools/gyp/test/mac/postbuild-copy-bundle/test.gyp b/tools/gyp/test/mac/postbuild-copy-bundle/test.gyp new file mode 100644 index 0000000000..547737ce58 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-copy-bundle/test.gyp @@ -0,0 +1,43 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'test_bundle', + 'product_name': 'My Framework', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ 'empty.c', ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'Framework-Info.plist', + }, + 'mac_bundle_resources': [ + 'resource_file.sb', + ], + }, + { + 'target_name': 'test_app', + 'product_name': 'Test App', + 'type': 'executable', + 'mac_bundle': 1, + 'dependencies': [ + 'test_bundle', + ], + 'sources': [ 'main.c', ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'TestApp-Info.plist', + }, + 'postbuilds': [ + { + 'postbuild_name': 'Copy dependent framework into app', + 'action': [ + './postbuild-copy-framework.sh', + '${BUILT_PRODUCTS_DIR}/My Framework.framework', + '${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/', + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/postbuild-defaults/Info.plist b/tools/gyp/test/mac/postbuild-defaults/Info.plist new file mode 100644 index 0000000000..d3f54d76cd --- /dev/null +++ b/tools/gyp/test/mac/postbuild-defaults/Info.plist @@ -0,0 +1,13 @@ + + + + + + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleName + ${PRODUCT_NAME} + + diff --git a/tools/gyp/test/mac/postbuild-defaults/main.c b/tools/gyp/test/mac/postbuild-defaults/main.c new file mode 100644 index 0000000000..1711567ef5 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-defaults/main.c @@ -0,0 +1,7 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +int main() { + return 0; +} diff --git a/tools/gyp/test/mac/postbuild-defaults/postbuild-defaults.sh b/tools/gyp/test/mac/postbuild-defaults/postbuild-defaults.sh new file mode 100755 index 0000000000..56af2a8329 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-defaults/postbuild-defaults.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +# This is the built Info.plist in the output directory. +PLIST="${BUILT_PRODUCTS_DIR}"/Test.app/Contents/Info # No trailing .plist +echo $(defaults read "${PLIST}" "CFBundleName") > "${BUILT_PRODUCTS_DIR}/result" + +# This is the source Info.plist next to this script file. +PLIST="${SRCROOT}"/Info # No trailing .plist +echo $(defaults read "${PLIST}" "CFBundleName") \ + >> "${BUILT_PRODUCTS_DIR}/result" diff --git a/tools/gyp/test/mac/postbuild-defaults/test.gyp b/tools/gyp/test/mac/postbuild-defaults/test.gyp new file mode 100644 index 0000000000..be0a075efc --- /dev/null +++ b/tools/gyp/test/mac/postbuild-defaults/test.gyp @@ -0,0 +1,26 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'test_app', + 'product_name': 'Test', + 'type': 'executable', + 'mac_bundle': 1, + 'sources': [ 'main.c', ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'Info.plist', + }, + 'postbuilds': [ + { + 'postbuild_name': 'Postbuild that calls defaults', + 'action': [ + './postbuild-defaults.sh', + '${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}', + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/postbuild-fail/postbuild-fail.sh b/tools/gyp/test/mac/postbuild-fail/postbuild-fail.sh index d4113d059e..dc1a60d987 100755 --- a/tools/gyp/test/mac/postbuild-fail/postbuild-fail.sh +++ b/tools/gyp/test/mac/postbuild-fail/postbuild-fail.sh @@ -1,4 +1,4 @@ -#!/usr/bash +#!/usr/bin/bash # Copyright (c) 2011 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/tools/gyp/test/mac/postbuild-multiple-configurations/main.c b/tools/gyp/test/mac/postbuild-multiple-configurations/main.c new file mode 100644 index 0000000000..21c1963526 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-multiple-configurations/main.c @@ -0,0 +1,4 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +int main() {} diff --git a/tools/gyp/test/mac/postbuild-multiple-configurations/postbuild-touch-file.sh b/tools/gyp/test/mac/postbuild-multiple-configurations/postbuild-touch-file.sh new file mode 100755 index 0000000000..b6170cf7a7 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-multiple-configurations/postbuild-touch-file.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +touch "${BUILT_PRODUCTS_DIR}/postbuild-file" diff --git a/tools/gyp/test/mac/postbuild-multiple-configurations/test.gyp b/tools/gyp/test/mac/postbuild-multiple-configurations/test.gyp new file mode 100644 index 0000000000..c350b20d68 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-multiple-configurations/test.gyp @@ -0,0 +1,26 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'target_defaults': { + 'configurations': { + 'Debug': {}, + 'Release': {}, + }, + }, + 'targets': [ + { + 'target_name': 'random_target', + 'type': 'executable', + 'sources': [ 'main.c', ], + 'postbuilds': [ + { + 'postbuild_name': 'Touch a file.', + 'action': [ + './postbuild-touch-file.sh', + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/postbuild-static-library/empty.c b/tools/gyp/test/mac/postbuild-static-library/empty.c new file mode 100644 index 0000000000..9554336c0c --- /dev/null +++ b/tools/gyp/test/mac/postbuild-static-library/empty.c @@ -0,0 +1,4 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +void f() {} diff --git a/tools/gyp/test/mac/postbuild-static-library/postbuild-touch-file.sh b/tools/gyp/test/mac/postbuild-static-library/postbuild-touch-file.sh new file mode 100755 index 0000000000..37de4de4f6 --- /dev/null +++ b/tools/gyp/test/mac/postbuild-static-library/postbuild-touch-file.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +touch "${BUILT_PRODUCTS_DIR}/$1" diff --git a/tools/gyp/test/mac/postbuild-static-library/test.gyp b/tools/gyp/test/mac/postbuild-static-library/test.gyp new file mode 100644 index 0000000000..9ef55a0afa --- /dev/null +++ b/tools/gyp/test/mac/postbuild-static-library/test.gyp @@ -0,0 +1,34 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'my_lib', + 'type': 'static_library', + 'sources': [ 'empty.c', ], + 'postbuilds': [ + { + 'postbuild_name': 'Postbuild that touches a file', + 'action': [ + './postbuild-touch-file.sh', 'postbuild-file' + ], + }, + ], + }, + + { + 'target_name': 'my_sourceless_lib', + 'type': 'static_library', + 'dependencies': [ 'my_lib' ], + 'postbuilds': [ + { + 'postbuild_name': 'Postbuild that touches a file', + 'action': [ + './postbuild-touch-file.sh', 'postbuild-file-sourceless' + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/prefixheader/file.cc b/tools/gyp/test/mac/prefixheader/file.cc new file mode 100644 index 0000000000..d0b39d1f6d --- /dev/null +++ b/tools/gyp/test/mac/prefixheader/file.cc @@ -0,0 +1 @@ +MyInt f() { return 0; } diff --git a/tools/gyp/test/mac/prefixheader/file.m b/tools/gyp/test/mac/prefixheader/file.m new file mode 100644 index 0000000000..d0b39d1f6d --- /dev/null +++ b/tools/gyp/test/mac/prefixheader/file.m @@ -0,0 +1 @@ +MyInt f() { return 0; } diff --git a/tools/gyp/test/mac/prefixheader/file.mm b/tools/gyp/test/mac/prefixheader/file.mm new file mode 100644 index 0000000000..d0b39d1f6d --- /dev/null +++ b/tools/gyp/test/mac/prefixheader/file.mm @@ -0,0 +1 @@ +MyInt f() { return 0; } diff --git a/tools/gyp/test/mac/prefixheader/test.gyp b/tools/gyp/test/mac/prefixheader/test.gyp index ce318dfc71..7e6b1af807 100644 --- a/tools/gyp/test/mac/prefixheader/test.gyp +++ b/tools/gyp/test/mac/prefixheader/test.gyp @@ -4,7 +4,7 @@ { 'targets': [ { - 'target_name': 'prefix_header', + 'target_name': 'prefix_header_c', 'type': 'static_library', 'sources': [ 'file.c', ], 'xcode_settings': { @@ -12,7 +12,7 @@ }, }, { - 'target_name': 'precompiled_prefix_header', + 'target_name': 'precompiled_prefix_header_c', 'type': 'shared_library', 'mac_bundle': 1, 'sources': [ 'file.c', ], @@ -21,5 +21,62 @@ 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', }, }, + + { + 'target_name': 'prefix_header_cc', + 'type': 'static_library', + 'sources': [ 'file.cc', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + }, + }, + { + 'target_name': 'precompiled_prefix_header_cc', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ 'file.cc', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + }, + + { + 'target_name': 'prefix_header_m', + 'type': 'static_library', + 'sources': [ 'file.m', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + }, + }, + { + 'target_name': 'precompiled_prefix_header_m', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ 'file.m', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + }, + + { + 'target_name': 'prefix_header_mm', + 'type': 'static_library', + 'sources': [ 'file.mm', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + }, + }, + { + 'target_name': 'precompiled_prefix_header_mm', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ 'file.mm', ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'header.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + }, ], } diff --git a/tools/gyp/test/mac/rebuild/TestApp-Info.plist b/tools/gyp/test/mac/rebuild/TestApp-Info.plist new file mode 100644 index 0000000000..98fd515200 --- /dev/null +++ b/tools/gyp/test/mac/rebuild/TestApp-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.google.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/tools/gyp/test/mac/rebuild/delay-touch.sh b/tools/gyp/test/mac/rebuild/delay-touch.sh new file mode 100755 index 0000000000..7caf105b6e --- /dev/null +++ b/tools/gyp/test/mac/rebuild/delay-touch.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +sleep 1 # mtime resolution is 1 sec on unix. +touch "$1" diff --git a/tools/gyp/test/mac/rebuild/empty.c b/tools/gyp/test/mac/rebuild/empty.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/gyp/test/mac/rebuild/main.c b/tools/gyp/test/mac/rebuild/main.c new file mode 100644 index 0000000000..237c8ce181 --- /dev/null +++ b/tools/gyp/test/mac/rebuild/main.c @@ -0,0 +1 @@ +int main() {} diff --git a/tools/gyp/test/mac/rebuild/test.gyp b/tools/gyp/test/mac/rebuild/test.gyp new file mode 100644 index 0000000000..15b4e4ef2f --- /dev/null +++ b/tools/gyp/test/mac/rebuild/test.gyp @@ -0,0 +1,56 @@ +# Copyright (c) 2011 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'test_app', + 'product_name': 'Test App', + 'type': 'executable', + 'mac_bundle': 1, + 'sources': [ + 'main.c', + ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'TestApp-Info.plist', + }, + }, + { + 'target_name': 'test_app_postbuilds', + 'product_name': 'Test App 2', + 'type': 'executable', + 'mac_bundle': 1, + 'sources': [ + 'main.c', + ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'TestApp-Info.plist', + }, + 'postbuilds': [ + { + 'postbuild_name': 'Postbuild that touches the app binary', + 'action': [ + './delay-touch.sh', '${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}', + ], + }, + ], + }, + { + 'target_name': 'test_framework_postbuilds', + 'product_name': 'Test Framework', + 'type': 'shared_library', + 'mac_bundle': 1, + 'sources': [ + 'empty.c', + ], + 'postbuilds': [ + { + 'postbuild_name': 'Postbuild that touches the framework binary', + 'action': [ + './delay-touch.sh', '${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}', + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/mac/sourceless-module/empty.c b/tools/gyp/test/mac/sourceless-module/empty.c new file mode 100644 index 0000000000..237c8ce181 --- /dev/null +++ b/tools/gyp/test/mac/sourceless-module/empty.c @@ -0,0 +1 @@ +int main() {} diff --git a/tools/gyp/test/mac/sourceless-module/test.gyp b/tools/gyp/test/mac/sourceless-module/test.gyp new file mode 100644 index 0000000000..49dc2af9c6 --- /dev/null +++ b/tools/gyp/test/mac/sourceless-module/test.gyp @@ -0,0 +1,39 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'empty_bundle', + 'type': 'loadable_module', + 'mac_bundle': 1, + }, + { + 'target_name': 'resource_bundle', + 'type': 'loadable_module', + 'mac_bundle': 1, + 'actions': [ + { + 'action_name': 'Add Resource', + 'inputs': [], + 'outputs': [ + '<(INTERMEDIATE_DIR)/app_manifest/foo.manifest', + ], + 'action': [ + 'touch', '<(INTERMEDIATE_DIR)/app_manifest/foo.manifest', + ], + 'process_outputs_as_mac_bundle_resources': 1, + }, + ], + }, + { + 'target_name': 'dependent_on_resource_bundle', + 'type': 'executable', + 'sources': [ 'empty.c' ], + 'dependencies': [ + 'resource_bundle', + ], + }, + ], +} + diff --git a/tools/gyp/test/mac/type_envvars/test.gyp b/tools/gyp/test/mac/type_envvars/test.gyp index 4827957f67..465670056b 100644 --- a/tools/gyp/test/mac/type_envvars/test.gyp +++ b/tools/gyp/test/mac/type_envvars/test.gyp @@ -40,6 +40,7 @@ }, ], }, + # Types 'static_library' and 'none' can't exist as bundles. { 'target_name': 'nonbundle_executable', @@ -85,5 +86,15 @@ }, ], }, + { + 'target_name': 'nonbundle_none', + 'type': 'none', + 'postbuilds': [ + { + 'postbuild_name': 'envtest', + 'action': [ './test_nonbundle_none.sh', ], + }, + ], + }, ], } diff --git a/tools/gyp/test/mac/type_envvars/test_bundle_executable.sh b/tools/gyp/test/mac/type_envvars/test_bundle_executable.sh index 4d3a084f07..1cc6e5668f 100755 --- a/tools/gyp/test/mac/type_envvars/test_bundle_executable.sh +++ b/tools/gyp/test/mac/type_envvars/test_bundle_executable.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,9 @@ set -e test $MACH_O_TYPE = mh_execute test $PRODUCT_TYPE = com.apple.product-type.application +test "${PRODUCT_NAME}" = "My App" +test "${FULL_PRODUCT_NAME}" = "My App.app" + +test "${EXECUTABLE_NAME}" = "My App" +test "${EXECUTABLE_PATH}" = "My App.app/Contents/MacOS/My App" +test "${WRAPPER_NAME}" = "My App.app" diff --git a/tools/gyp/test/mac/type_envvars/test_bundle_loadable_module.sh b/tools/gyp/test/mac/type_envvars/test_bundle_loadable_module.sh index c74c8e435e..86fc5c99d8 100755 --- a/tools/gyp/test/mac/type_envvars/test_bundle_loadable_module.sh +++ b/tools/gyp/test/mac/type_envvars/test_bundle_loadable_module.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,10 @@ set -e test $MACH_O_TYPE = mh_bundle test $PRODUCT_TYPE = com.apple.product-type.bundle +test $PRODUCT_NAME = bundle_loadable_module +test $FULL_PRODUCT_NAME = bundle_loadable_module.bundle + +test $EXECUTABLE_NAME = bundle_loadable_module +test $EXECUTABLE_PATH = \ + "bundle_loadable_module.bundle/Contents/MacOS/bundle_loadable_module" +test $WRAPPER_NAME = bundle_loadable_module.bundle diff --git a/tools/gyp/test/mac/type_envvars/test_bundle_shared_library.sh b/tools/gyp/test/mac/type_envvars/test_bundle_shared_library.sh index 08ad4fb7f6..5585af4221 100755 --- a/tools/gyp/test/mac/type_envvars/test_bundle_shared_library.sh +++ b/tools/gyp/test/mac/type_envvars/test_bundle_shared_library.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,10 @@ set -e test $MACH_O_TYPE = mh_dylib test $PRODUCT_TYPE = com.apple.product-type.framework +test $PRODUCT_NAME = bundle_shared_library +test $FULL_PRODUCT_NAME = bundle_shared_library.framework + +test $EXECUTABLE_NAME = bundle_shared_library +test $EXECUTABLE_PATH = \ + "bundle_shared_library.framework/Versions/A/bundle_shared_library" +test $WRAPPER_NAME = bundle_shared_library.framework diff --git a/tools/gyp/test/mac/type_envvars/test_nonbundle_executable.sh b/tools/gyp/test/mac/type_envvars/test_nonbundle_executable.sh index 02d373de86..e026a1e261 100755 --- a/tools/gyp/test/mac/type_envvars/test_nonbundle_executable.sh +++ b/tools/gyp/test/mac/type_envvars/test_nonbundle_executable.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,3 +8,9 @@ set -e # Check for "not set", not just "empty": [[ ! $MACH_O_TYPE && ${MACH_O_TYPE-_} ]] test $PRODUCT_TYPE = com.apple.product-type.tool +test $PRODUCT_NAME = nonbundle_executable +test $FULL_PRODUCT_NAME = nonbundle_executable + +test $EXECUTABLE_NAME = nonbundle_executable +test $EXECUTABLE_PATH = nonbundle_executable +[[ ! $WRAPPER_NAME && ${WRAPPER_NAME-_} ]] diff --git a/tools/gyp/test/mac/type_envvars/test_nonbundle_loadable_module.sh b/tools/gyp/test/mac/type_envvars/test_nonbundle_loadable_module.sh index aa67df3531..317881eda1 100755 --- a/tools/gyp/test/mac/type_envvars/test_nonbundle_loadable_module.sh +++ b/tools/gyp/test/mac/type_envvars/test_nonbundle_loadable_module.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,9 @@ set -e test $MACH_O_TYPE = mh_bundle test $PRODUCT_TYPE = com.apple.product-type.library.dynamic +test $PRODUCT_NAME = nonbundle_loadable_module +test $FULL_PRODUCT_NAME = nonbundle_loadable_module.so + +test $EXECUTABLE_NAME = nonbundle_loadable_module.so +test $EXECUTABLE_PATH = nonbundle_loadable_module.so +[[ ! $WRAPPER_NAME && ${WRAPPER_NAME-_} ]] diff --git a/tools/gyp/test/mac/type_envvars/test_nonbundle_none.sh b/tools/gyp/test/mac/type_envvars/test_nonbundle_none.sh new file mode 100755 index 0000000000..11c40bc638 --- /dev/null +++ b/tools/gyp/test/mac/type_envvars/test_nonbundle_none.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +# Check for "not set", not just "empty": +[[ ! $MACH_O_TYPE && ${MACH_O_TYPE-_} ]] +[[ ! $PRODUCT_TYPE && ${PRODUCT_TYPE-_} ]] +test $PRODUCT_NAME = nonbundle_none +[[ ! $FULL_PRODUCT_NAME && ${FULL_PRODUCT_NAME-_} ]] + +[[ ! $EXECUTABLE_NAME && ${EXECUTABLE_NAME-_} ]] +[[ ! $EXECUTABLE_PATH && ${EXECUTABLE_PATH-_} ]] +[[ ! $WRAPPER_NAME && ${WRAPPER_NAME-_} ]] diff --git a/tools/gyp/test/mac/type_envvars/test_nonbundle_shared_library.sh b/tools/gyp/test/mac/type_envvars/test_nonbundle_shared_library.sh index 937a50c4aa..3b9fb72dd3 100755 --- a/tools/gyp/test/mac/type_envvars/test_nonbundle_shared_library.sh +++ b/tools/gyp/test/mac/type_envvars/test_nonbundle_shared_library.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,9 @@ set -e test $MACH_O_TYPE = mh_dylib test $PRODUCT_TYPE = com.apple.product-type.library.dynamic +test $PRODUCT_NAME = nonbundle_shared_library +test $FULL_PRODUCT_NAME = libnonbundle_shared_library.dylib + +test $EXECUTABLE_NAME = libnonbundle_shared_library.dylib +test $EXECUTABLE_PATH = libnonbundle_shared_library.dylib +[[ ! $WRAPPER_NAME && ${WRAPPER_NAME-_} ]] diff --git a/tools/gyp/test/mac/type_envvars/test_nonbundle_static_library.sh b/tools/gyp/test/mac/type_envvars/test_nonbundle_static_library.sh index 892f0d9f11..77cee2c20a 100755 --- a/tools/gyp/test/mac/type_envvars/test_nonbundle_static_library.sh +++ b/tools/gyp/test/mac/type_envvars/test_nonbundle_static_library.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -7,3 +7,9 @@ set -e test $MACH_O_TYPE = staticlib test $PRODUCT_TYPE = com.apple.product-type.library.static +test $PRODUCT_NAME = nonbundle_static_library +test $FULL_PRODUCT_NAME = libnonbundle_static_library.a + +test $EXECUTABLE_NAME = libnonbundle_static_library.a +test $EXECUTABLE_PATH = libnonbundle_static_library.a +[[ ! $WRAPPER_NAME && ${WRAPPER_NAME-_} ]] diff --git a/tools/gyp/test/mac/xcode-env-order/Info.plist b/tools/gyp/test/mac/xcode-env-order/Info.plist index 55db30dba6..e11f21e52d 100644 --- a/tools/gyp/test/mac/xcode-env-order/Info.plist +++ b/tools/gyp/test/mac/xcode-env-order/Info.plist @@ -28,11 +28,29 @@ MainMenu NSPrincipalClass NSApplication - ProcessedKey1 - ${DEPENDENT_KEY1} - ProcessedKey2 - ${DEPENDENT_KEY2} - ProcessedKey3 - ${DEPENDENT_KEY3} + + BraceProcessedKey1 + ${BRACE_DEPENDENT_KEY1} + BraceProcessedKey2 + ${BRACE_DEPENDENT_KEY2} + BraceProcessedKey3 + ${BRACE_DEPENDENT_KEY3} + + ParenProcessedKey1 + ${PAREN_DEPENDENT_KEY1} + ParenProcessedKey2 + ${PAREN_DEPENDENT_KEY2} + ParenProcessedKey3 + ${PAREN_DEPENDENT_KEY3} + + BareProcessedKey1 + ${BARE_DEPENDENT_KEY1} + BareProcessedKey2 + ${BARE_DEPENDENT_KEY2} + BareProcessedKey3 + ${BARE_DEPENDENT_KEY3} + + MixedProcessedKey + ${MIXED_DEPENDENT_KEY} diff --git a/tools/gyp/test/mac/xcode-env-order/test.gyp b/tools/gyp/test/mac/xcode-env-order/test.gyp index d9191aab5e..8433faea3c 100644 --- a/tools/gyp/test/mac/xcode-env-order/test.gyp +++ b/tools/gyp/test/mac/xcode-env-order/test.gyp @@ -1,4 +1,4 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. { @@ -11,12 +11,72 @@ 'sources': [ 'main.c', ], + # Env vars in copies. + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)/${PRODUCT_NAME}-copy-brace', + 'files': [ 'main.c', ], # ${SOURCE_ROOT} doesn't work with xcode + }, + { + 'destination': '<(PRODUCT_DIR)/$(PRODUCT_NAME)-copy-paren', + 'files': [ '$(SOURCE_ROOT)/main.c', ], + }, + { + 'destination': '<(PRODUCT_DIR)/$PRODUCT_NAME-copy-bare', + 'files': [ 'main.c', ], # $SOURCE_ROOT doesn't work with xcode + }, + ], + # Env vars in actions. + 'actions': [ + { + 'action_name': 'Action copy braces ${PRODUCT_NAME}', + 'description': 'Action copy braces ${PRODUCT_NAME}', + 'inputs': [ '${SOURCE_ROOT}/main.c' ], + # Referencing ${PRODUCT_NAME} in action outputs doesn't work with + # the Xcode generator (PRODUCT_NAME expands to "Test Support"). + 'outputs': [ '<(PRODUCT_DIR)/action-copy-brace.txt' ], + 'action': [ 'cp', '${SOURCE_ROOT}/main.c', + '<(PRODUCT_DIR)/action-copy-brace.txt' ], + }, + { + 'action_name': 'Action copy parens ${PRODUCT_NAME}', + 'description': 'Action copy parens ${PRODUCT_NAME}', + 'inputs': [ '${SOURCE_ROOT}/main.c' ], + # Referencing ${PRODUCT_NAME} in action outputs doesn't work with + # the Xcode generator (PRODUCT_NAME expands to "Test Support"). + 'outputs': [ '<(PRODUCT_DIR)/action-copy-paren.txt' ], + 'action': [ 'cp', '${SOURCE_ROOT}/main.c', + '<(PRODUCT_DIR)/action-copy-paren.txt' ], + }, + { + 'action_name': 'Action copy bare ${PRODUCT_NAME}', + 'description': 'Action copy bare ${PRODUCT_NAME}', + 'inputs': [ '${SOURCE_ROOT}/main.c' ], + # Referencing ${PRODUCT_NAME} in action outputs doesn't work with + # the Xcode generator (PRODUCT_NAME expands to "Test Support"). + 'outputs': [ '<(PRODUCT_DIR)/action-copy-bare.txt' ], + 'action': [ 'cp', '${SOURCE_ROOT}/main.c', + '<(PRODUCT_DIR)/action-copy-bare.txt' ], + }, + ], + # Env vars in copies. 'xcode_settings': { 'INFOPLIST_FILE': 'Info.plist', 'STRING_KEY': '/Source/Project', - 'DEPENDENT_KEY2': '$(STRING_KEY)/$(PRODUCT_NAME)', - 'DEPENDENT_KEY1': 'DEP:$(DEPENDENT_KEY2)', - 'DEPENDENT_KEY3': '$(PRODUCT_TYPE):$(DEPENDENT_KEY1)', + + 'BRACE_DEPENDENT_KEY2': '${STRING_KEY}/${PRODUCT_NAME}', + 'BRACE_DEPENDENT_KEY1': 'D:${BRACE_DEPENDENT_KEY2}', + 'BRACE_DEPENDENT_KEY3': '${PRODUCT_TYPE}:${BRACE_DEPENDENT_KEY1}', + + 'PAREN_DEPENDENT_KEY2': '$(STRING_KEY)/$(PRODUCT_NAME)', + 'PAREN_DEPENDENT_KEY1': 'D:$(PAREN_DEPENDENT_KEY2)', + 'PAREN_DEPENDENT_KEY3': '$(PRODUCT_TYPE):$(PAREN_DEPENDENT_KEY1)', + + 'BARE_DEPENDENT_KEY2': '$STRING_KEY/$PRODUCT_NAME', + 'BARE_DEPENDENT_KEY1': 'D:$BARE_DEPENDENT_KEY2', + 'BARE_DEPENDENT_KEY3': '$PRODUCT_TYPE:$BARE_DEPENDENT_KEY1', + + 'MIXED_DEPENDENT_KEY': '${STRING_KEY}:$(PRODUCT_NAME):$MACH_O_TYPE', }, }, ], diff --git a/tools/gyp/test/module/src/module.gyp b/tools/gyp/test/module/src/module.gyp index bb43c30230..cc567ef7b5 100644 --- a/tools/gyp/test/module/src/module.gyp +++ b/tools/gyp/test/module/src/module.gyp @@ -15,7 +15,7 @@ 'defines': ['PLATFORM_LINUX'], # Support 64-bit shared libs (also works fine for 32-bit). 'cflags': ['-fPIC'], - 'ldflags': ['-ldl'], + 'libraries': ['-ldl'], }], ], }, diff --git a/tools/gyp/test/msvs/list_excluded/gyptest-all.py b/tools/gyp/test/msvs/list_excluded/gyptest-all.py new file mode 100644 index 0000000000..5a370f6b47 --- /dev/null +++ b/tools/gyp/test/msvs/list_excluded/gyptest-all.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that msvs_list_excluded_files=0 doesn't list files that would +normally be in _excluded_files, and that if that flag is not set, then they +are still listed. +""" + +import os +import TestGyp + +test = TestGyp.TestGyp(formats=['msvs'], workdir='workarea_all') + + +# with the flag set to 0 +try: + os.environ['GYP_GENERATOR_FLAGS'] = 'msvs_list_excluded_files=0' + test.run_gyp('hello_exclude.gyp') +finally: + del os.environ['GYP_GENERATOR_FLAGS'] +if test.uses_msbuild: + test.must_not_contain('hello.vcxproj', 'hello_mac') +else: + test.must_not_contain('hello.vcproj', 'hello_mac') + + +# with the flag not set +test.run_gyp('hello_exclude.gyp') +if test.uses_msbuild: + test.must_contain('hello.vcxproj', 'hello_mac') +else: + test.must_contain('hello.vcproj', 'hello_mac') + + +# with the flag explicitly set to 1 +try: + os.environ['GYP_GENERATOR_FLAGS'] = 'msvs_list_excluded_files=1' + test.run_gyp('hello_exclude.gyp') +finally: + del os.environ['GYP_GENERATOR_FLAGS'] +if test.uses_msbuild: + test.must_contain('hello.vcxproj', 'hello_mac') +else: + test.must_contain('hello.vcproj', 'hello_mac') + + +test.pass_test() diff --git a/tools/gyp/test/msvs/list_excluded/hello.cpp b/tools/gyp/test/msvs/list_excluded/hello.cpp new file mode 100644 index 0000000000..69acc38bd8 --- /dev/null +++ b/tools/gyp/test/msvs/list_excluded/hello.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +int main(int argc, char *argv[]) { + printf("Hello, world!\n"); + return 0; +} diff --git a/tools/gyp/test/msvs/list_excluded/hello_exclude.gyp b/tools/gyp/test/msvs/list_excluded/hello_exclude.gyp new file mode 100644 index 0000000000..aa160f2367 --- /dev/null +++ b/tools/gyp/test/msvs/list_excluded/hello_exclude.gyp @@ -0,0 +1,19 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'hello', + 'type': 'executable', + 'sources': [ + 'hello.cpp', + 'hello_mac.cpp', + ], + 'conditions': [ + ['OS!="mac"', {'sources!': ['hello_mac.cpp']}], + ] + }, + ], +} diff --git a/tools/gyp/test/msvs/list_excluded/hello_mac.cpp b/tools/gyp/test/msvs/list_excluded/hello_mac.cpp new file mode 100644 index 0000000000..b9f6242c4b --- /dev/null +++ b/tools/gyp/test/msvs/list_excluded/hello_mac.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2012 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +int hello2() { + printf("Hello, two!\n"); + return 0; +} diff --git a/tools/gyp/test/msvs/uldi2010/gyptest-all.py b/tools/gyp/test/msvs/uldi2010/gyptest-all.py new file mode 100644 index 0000000000..cc248fbd63 --- /dev/null +++ b/tools/gyp/test/msvs/uldi2010/gyptest-all.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that uldi can be disabled on a per-project-reference basis in vs2010. +""" + +import TestGyp + +test = TestGyp.TestGyp(formats=['msvs'], workdir='workarea_all') + +test.run_gyp('hello.gyp') + +if test.uses_msbuild: + test.must_contain('hello.vcxproj', 'false') + +test.pass_test() diff --git a/tools/gyp/test/msvs/uldi2010/hello.c b/tools/gyp/test/msvs/uldi2010/hello.c new file mode 100644 index 0000000000..2769093694 --- /dev/null +++ b/tools/gyp/test/msvs/uldi2010/hello.c @@ -0,0 +1,13 @@ +/* Copyright (c) 2012 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#include + +extern int hello2(); + +int main(int argc, char *argv[]) { + printf("Hello, world!\n"); + hello2(); + return 0; +} diff --git a/tools/gyp/test/msvs/uldi2010/hello.gyp b/tools/gyp/test/msvs/uldi2010/hello.gyp new file mode 100644 index 0000000000..a2bf2badb1 --- /dev/null +++ b/tools/gyp/test/msvs/uldi2010/hello.gyp @@ -0,0 +1,26 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'hello', + 'type': 'executable', + 'sources': [ + 'hello.c', + ], + 'dependencies': [ + 'hellolib', + ] + }, + { + 'target_name': 'hellolib', + 'type': 'static_library', + 'sources': [ + 'hello2.c', + ], + 'msvs_2010_disable_uldi_when_referenced': 1, + }, + ], +} diff --git a/tools/gyp/test/msvs/uldi2010/hello2.c b/tools/gyp/test/msvs/uldi2010/hello2.c new file mode 100644 index 0000000000..e2f23238d1 --- /dev/null +++ b/tools/gyp/test/msvs/uldi2010/hello2.c @@ -0,0 +1,10 @@ +/* Copyright (c) 2012 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#include + +int hello2() { + printf("Hello, two!\n"); + return 0; +} diff --git a/tools/gyp/test/ninja/chained-dependency/chained-dependency.gyp b/tools/gyp/test/ninja/chained-dependency/chained-dependency.gyp new file mode 100644 index 0000000000..a08f893d8c --- /dev/null +++ b/tools/gyp/test/ninja/chained-dependency/chained-dependency.gyp @@ -0,0 +1,52 @@ +# Copyright (c) 2010 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + # This first target generates a header. + { + 'target_name': 'generate_header', + 'type': 'none', + 'actions': [ + { + 'action_name': 'generate header', + 'inputs': [], + 'outputs': ['<(SHARED_INTERMEDIATE_DIR)/generated/header.h'], + 'action': [ + 'python', '-c', 'open(<(_outputs), "w")' + ] + }, + ], + 'all_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)', + ], + }, + }, + + # This intermediate target does nothing other than pull in a + # dependency on the above generated target. + { + 'target_name': 'chain', + 'type': 'none', + 'dependencies': [ + 'generate_header', + ], + }, + + # This final target is: + # - a static library (so gyp doesn't transitively pull in dependencies); + # - that relies on the generated file two dependencies away. + { + 'target_name': 'chained', + 'type': 'static_library', + 'dependencies': [ + 'chain', + ], + 'sources': [ + 'chained.c', + ], + }, + ], +} diff --git a/tools/gyp/test/ninja/chained-dependency/chained.c b/tools/gyp/test/ninja/chained-dependency/chained.c new file mode 100644 index 0000000000..e4eb5e861f --- /dev/null +++ b/tools/gyp/test/ninja/chained-dependency/chained.c @@ -0,0 +1,5 @@ +#include "generated/header.h" + +int main(int argc, char** argv) { + return 0; +} diff --git a/tools/gyp/test/ninja/chained-dependency/gyptest-chained-dependency.py b/tools/gyp/test/ninja/chained-dependency/gyptest-chained-dependency.py new file mode 100755 index 0000000000..5494c79e09 --- /dev/null +++ b/tools/gyp/test/ninja/chained-dependency/gyptest-chained-dependency.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verifies that files generated by two-steps-removed actions are built before +dependent compile steps. +""" + +import os +import TestGyp + +# This test is Ninja-specific in that: +# - the bug only showed nondeterministically in parallel builds; +# - it relies on a ninja-specific output file path. + +test = TestGyp.TestGyp(formats=['ninja']) +test.run_gyp('chained-dependency.gyp') +test.build('chained-dependency.gyp', 'obj/chained.chained.o') +# The test passes if the .o file builds successfully. +test.pass_test() diff --git a/tools/gyp/test/restat/gyptest-restat.py b/tools/gyp/test/restat/gyptest-restat.py new file mode 100644 index 0000000000..87379044dd --- /dev/null +++ b/tools/gyp/test/restat/gyptest-restat.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Verify that dependent rules are executed iff a dependency action modifies its +outputs. +""" + +import TestGyp +import os + +test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode']) + +test.run_gyp('restat.gyp', chdir='src') + +chdir = 'relocate/src' +test.relocate('src', chdir) + +# Building 'dependent' the first time generates 'side_effect', but building it +# the second time doesn't, because 'create_intermediate' doesn't update its +# output. +test.build('restat.gyp', 'dependent', chdir=chdir) +test.built_file_must_exist('side_effect', chdir=chdir) +os.remove(test.built_file_path('side_effect', chdir=chdir)) +test.build('restat.gyp', 'dependent', chdir=chdir) +test.built_file_must_not_exist('side_effect', chdir=chdir) + +test.pass_test() diff --git a/tools/gyp/test/restat/src/create_intermediate.py b/tools/gyp/test/restat/src/create_intermediate.py new file mode 100644 index 0000000000..a4d7450371 --- /dev/null +++ b/tools/gyp/test/restat/src/create_intermediate.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import sys + +""" +Create argv[1] iff it doesn't already exist. +""" + +outfile = sys.argv[1] +if os.path.exists(outfile): + sys.exit() +open(outfile, "wb").close() diff --git a/tools/gyp/test/restat/src/restat.gyp b/tools/gyp/test/restat/src/restat.gyp new file mode 100644 index 0000000000..09b3d71fa8 --- /dev/null +++ b/tools/gyp/test/restat/src/restat.gyp @@ -0,0 +1,48 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'create_intermediate', + 'type': 'none', + 'actions': [ + { + 'action_name': 'create_intermediate', + 'inputs': [ + 'create_intermediate.py', + ], + 'outputs': [ + '<(PRODUCT_DIR)/intermediate', + 'ALWAYS.run.ALWAYS', + ], + 'action': [ + 'python', 'create_intermediate.py', '<(PRODUCT_DIR)/intermediate', + ], + }, + ], + }, + { + 'target_name': 'dependent', + 'type': 'none', + 'dependencies': [ + 'create_intermediate', + ], + 'actions': [ + { + 'action_name': 'dependent', + 'inputs': [ + '<(PRODUCT_DIR)/intermediate', + ], + 'outputs': [ + '<(PRODUCT_DIR)/dependent' + ], + 'action': [ + 'touch', '<(PRODUCT_DIR)/dependent', '<(PRODUCT_DIR)/side_effect', + ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/same-gyp-name/gyptest-all.py b/tools/gyp/test/same-gyp-name/gyptest-all.py index 76456885ed..98732d52fb 100755 --- a/tools/gyp/test/same-gyp-name/gyptest-all.py +++ b/tools/gyp/test/same-gyp-name/gyptest-all.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 Google Inc. All rights reserved. +# Copyright (c) 2011 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,7 +12,7 @@ import TestGyp # This causes a problem on XCode (duplicate ID). # See http://code.google.com/p/gyp/issues/detail?id=114 -test = TestGyp.TestGyp(formats=['msvs', 'scons', 'make']) +test = TestGyp.TestGyp(formats=['msvs', 'scons', 'make', 'ninja']) test.run_gyp('all.gyp', chdir='src') diff --git a/tools/gyp/test/same-gyp-name/gyptest-default.py b/tools/gyp/test/same-gyp-name/gyptest-default.py index c1031f86bd..b8fb63d15b 100755 --- a/tools/gyp/test/same-gyp-name/gyptest-default.py +++ b/tools/gyp/test/same-gyp-name/gyptest-default.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 Google Inc. All rights reserved. +# Copyright (c) 2011 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,7 +12,7 @@ import TestGyp # This causes a problem on XCode (duplicate ID). # See http://code.google.com/p/gyp/issues/detail?id=114 -test = TestGyp.TestGyp(formats=['msvs', 'scons', 'make']) +test = TestGyp.TestGyp(formats=['msvs', 'scons', 'make', 'ninja']) test.run_gyp('all.gyp', chdir='src') diff --git a/tools/gyp/test/settings/gyptest-settings.py b/tools/gyp/test/settings/gyptest-settings.py deleted file mode 100755 index 0ed81edbf2..0000000000 --- a/tools/gyp/test/settings/gyptest-settings.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2011 Google Inc. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -""" -Smoke-tests 'settings' blocks. -""" - -import TestGyp - -# 'settings' is only supported for make and scons (and will be removed there as -# well eventually). -test = TestGyp.TestGyp(formats=['make', 'scons']) -test.run_gyp('settings.gyp') -test.build('test.gyp', test.ALL) -test.pass_test() diff --git a/tools/gyp/test/settings/settings.gyp b/tools/gyp/test/settings/settings.gyp deleted file mode 100644 index 5bde13ef27..0000000000 --- a/tools/gyp/test/settings/settings.gyp +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2011 Google Inc. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'settings_target', - 'type': 'settings', - # In real life, this would set 'cflags' etc and other targets - # would depend on it. - }, - { - # This is needed so scons will actually generate a SConstruct - # (which it doesn't do for settings targets alone). - 'target_name': 'junk1', - 'type': 'none', - }, - ], -} diff --git a/tools/gyp/test/small/gyptest-small.py b/tools/gyp/test/small/gyptest-small.py index a484cb3667..8d22ccafc9 100755 --- a/tools/gyp/test/small/gyptest-small.py +++ b/tools/gyp/test/small/gyptest-small.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2011 Google Inc. All rights reserved. +# Copyright (c) 2012 Google Inc. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -27,6 +27,7 @@ files_to_test = [ 'pylib/gyp/MSVSSettings_test.py', 'pylib/gyp/easy_xml_test.py', 'pylib/gyp/generator/msvs_test.py', + 'pylib/gyp/generator/ninja_test.py', ] # Collect all the suites from the above files. diff --git a/tools/gyp/test/toplevel-dir/gyptest-toplevel-dir.py b/tools/gyp/test/toplevel-dir/gyptest-toplevel-dir.py index 61986cdaa5..4daa6b2c11 100755 --- a/tools/gyp/test/toplevel-dir/gyptest-toplevel-dir.py +++ b/tools/gyp/test/toplevel-dir/gyptest-toplevel-dir.py @@ -13,7 +13,7 @@ and using the subdirectory's solution or project file as the entry point. import TestGyp import errno -test = TestGyp.TestGyp(formats=['make']) +test = TestGyp.TestGyp(formats=['ninja', 'make']) # We want our Makefile to be one dir up from main.gyp. test.run_gyp('main.gyp', '--toplevel-dir=..', chdir='src/sub1') diff --git a/tools/gyp/test/variables/commands/commands.gyp b/tools/gyp/test/variables/commands/commands.gyp index 113e4a279f..985f8bd49d 100644 --- a/tools/gyp/test/variables/commands/commands.gyp +++ b/tools/gyp/test/variables/commands/commands.gyp @@ -58,6 +58,8 @@ 'var16': '<(not_int_5)', 'var17': '<(negative_int)', 'var18': '<(zero_int)', + 'var19': ['