diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-07 23:55:47 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-07 23:55:47 +0000 |
commit | a6879a7486c9a3c9b619f9dded1d535e734c7fcd (patch) | |
tree | 9c19a3eef8eb73b3a4307969122dcf9a20bfe6ef /build | |
parent | 83c6bda785fa8fffa47bf2af47513d9bd399e7ca (diff) | |
download | chromium_src-a6879a7486c9a3c9b619f9dded1d535e734c7fcd.zip chromium_src-a6879a7486c9a3c9b619f9dded1d535e734c7fcd.tar.gz chromium_src-a6879a7486c9a3c9b619f9dded1d535e734c7fcd.tar.bz2 |
Dramatically improve the link time in release mode. Today's MC Hammer Xcode
Voodoo lesson: don't use the slow dsymutil utility; instead, make a "fake"
.dSYM that contains the original unstripped Mach-O file.
Review URL: http://codereview.chromium.org/9659
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'build')
-rw-r--r-- | build/googleurl.xcodeproj/project.pbxproj | 15 | ||||
-rwxr-xr-x | build/mac/strip_from_xcode | 42 | ||||
-rwxr-xr-x | build/mac/strip_save_dsym | 313 | ||||
-rw-r--r-- | build/release.xcconfig | 7 | ||||
-rw-r--r-- | build/staticlib.xcconfig | 2 | ||||
-rw-r--r-- | build/v8.xcodeproj/project.pbxproj | 32 |
6 files changed, 406 insertions, 5 deletions
diff --git a/build/googleurl.xcodeproj/project.pbxproj b/build/googleurl.xcodeproj/project.pbxproj index 1591557..0ad4846 100644 --- a/build/googleurl.xcodeproj/project.pbxproj +++ b/build/googleurl.xcodeproj/project.pbxproj @@ -420,6 +420,7 @@ buildPhases = ( 7BA018DF0E5A2AFF00044150 /* Sources */, 7BA018E00E5A2AFF00044150 /* Frameworks */, + 4D3D54390EC3A46900650CA0 /* Strip If Needed */, ); buildRules = ( ); @@ -551,6 +552,20 @@ /* End PBXReferenceProxy section */ /* Begin PBXShellScriptBuildPhase section */ + 4D3D54390EC3A46900650CA0 /* Strip If Needed */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Strip If Needed"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "exec \"${XCODEPROJ_DEPTH}/build/mac/strip_from_xcode\"\n"; + }; 7B8500180E5A2F6400730B43 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/build/mac/strip_from_xcode b/build/mac/strip_from_xcode new file mode 100755 index 0000000..7ef0e28 --- /dev/null +++ b/build/mac/strip_from_xcode @@ -0,0 +1,42 @@ +#!/bin/sh + +# Copyright (c) 2008 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This is a handy wrapper script that figures out how to call the strip +# utility (strip_save_dsym in this case), if it even needs to be called at all, +# and then does it. This script should be called by a post-link phase in +# targets that might generate Mach-O executables, dynamic libraries, or +# loadable bundles. +# +# An example "Strip If Needed" build phase placed after "Link Binary With +# Libraries" would do: +# exec "${XCODEPROJ_DEPTH}/build/mac/strip_from_xcode" + +if [ "${CONFIGURATION}" != "Release" ] ; then + # Only strip in release mode. + exit 0 +fi + +# MACH_O_TYPE is not set for a command-line tool, so check PRODUCT_TYPE too. +# Weird. +if [ "${MACH_O_TYPE}" = "mh_execute" ] || \ + [ "${PRODUCT_TYPE}" = "com.apple.product-type.tool" ] ; then + # Strip everything + STRIPFLAGS= +elif [ "${MACH_O_TYPE}" = "mh_dylib" ] || \ + "${MACH_O_TYPE}" = "mh_bundle" ]; then + # Strip debugging symbols and local symbols + STRIPFLAGS="-S -x" +elif [ "${MACH_O_TYPE}" = "staticlib" ] ; then + # Don't strip static libraries. + exit 0 +else + # Warn, but don't treat this as an error. + echo $0: warning: unrecognized MACH_O_TYPE ${MACH_O_TYPE} + exit 0 +fi + +exec "$(dirname ${0})/strip_save_dsym" ${STRIPFLAGS} \ + "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" diff --git a/build/mac/strip_save_dsym b/build/mac/strip_save_dsym new file mode 100755 index 0000000..a1d3ecd --- /dev/null +++ b/build/mac/strip_save_dsym @@ -0,0 +1,313 @@ +#!/usr/bin/python + +# Copyright (c) 2008 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Usage: strip_save_dsym <whatever-arguments-you-would-pass-to-strip> +# +# strip_save_dsym is a wrapper around the standard strip utility. Given an +# input Mach-O file, strip_save_dsym will save a copy of the file in a "fake" +# .dSYM bundle for debugging, and then call strip to strip the Mach-O file. +# Note that the .dSYM file is a "fake" in that it's not a self-contained +# .dSYM bundle, it just contains a copy of the original (unstripped) Mach-O +# file, and therefore contains references to object files on the filesystem. +# The generated .dSYM bundle is therefore unsuitable for debugging in the +# absence of these .o files. +# +# If a .dSYM already exists and has a newer timestamp than the Mach-O file, +# this utility does nothing. That allows strip_save_dsym to be run on a file +# that has already been stripped without trashing the .dSYM. +# +# Rationale: the "right" way to generate dSYM bundles, dsymutil, is incredibly +# slow. On the other hand, doing a file copy (which is really all that +# dsymutil does) is comparatively fast. Since we usually just want to strip +# a release-mode executable but still be able to debug it, and we don't care +# so much about generating a hermetic dSYM bundle, we'll prefer the file copy. +# If a real dSYM is ever needed, it's still possible to create one by running +# dsymutil and pointing it at the original Mach-O file inside the "fake" +# bundle, provided that the object files are available. + +import errno +import os +import re +import shutil +import subprocess +import sys +import time + +# Returns a list of architectures contained in a Mach-O file. The file can be +# a universal (fat) file, in which case there will be one list element for +# each contained architecture, or it can be a thin single-architecture Mach-O +# file, in which case the list will contain a single element identifying the +# architecture. On error, returns an empty list. Determines the architecture +# list by calling file. +def macho_archs(macho): + file_cmd = subprocess.Popen(["/usr/bin/file", "-b", "--", macho], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + archs = [] + + type_line = file_cmd.stdout.readline() + type_match = re.match("^Mach-O executable (.*)$", type_line) + if type_match: + archs.append(type_match.group(1)) + return [type_match.group(1)] + else: + type_match = re.match("^Mach-O universal binary with (.*) architectures$", + type_line) + if type_match: + for i in range(0, int(type_match.group(1))): + arch_line = file_cmd.stdout.readline() + arch_match = re.match( + "^.* \(for architecture (.*)\):\tMach-O executable .*$", + arch_line) + if arch_match: + archs.append(arch_match.group(1)) + + if file_cmd.wait() != 0: + archs = [] + + return archs + +# Returns a dictionary mapping architectures contained in the file as returned +# by macho_archs to the LC_UUID load command for that architecture. +# Architectures with no LC_UUID load command are omitted from the dictionary. +# Determines the UUID value by calling otool. +def macho_uuids(macho): + archs = macho_archs(macho) + + uuids = {} + + for arch in archs: + if arch == "": + continue + + otool_cmd = subprocess.Popen(["/usr/bin/otool", "-arch", arch, "-l", "-", + macho], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # state 0 is when nothing UUID-related has been seen yet. State 1 is + # entered after a load command begins, but it may not be an LC_UUID load + # command. States 2, 3, and 4 are intermediate states while reading an + # LC_UUID command. State 5 is the terminal state for a successful LC_UUID + # read. State 6 is the error state. + state = 0 + uuid = "" + for otool_line in otool_cmd.stdout: + if state == 0: + if re.match("^Load command .*$", otool_line): + state = 1 + elif state == 1: + if re.match("^ cmd LC_UUID$", otool_line): + state = 2 + else: + state = 0 + elif state == 2: + if re.match("^ cmdsize 24$", otool_line): + state = 3 + else: + state = 6 + elif state == 3: + uuid_match = re.match("^ uuid 0x(..) 0x(..) 0x(..) 0x(..) " + "0x(..) 0x(..) 0x(..) 0x(..)$", + otool_line) + if uuid_match: + state = 4 + uuid = uuid_match.group(1) + uuid_match.group(2) + \ + uuid_match.group(3) + uuid_match.group(4) + "-" + \ + uuid_match.group(5) + uuid_match.group(6) + "-" + \ + uuid_match.group(7) + uuid_match.group(8) + "-" + else: + state = 6 + elif state == 4: + uuid_match = re.match("^ 0x(..) 0x(..) 0x(..) 0x(..) " + "0x(..) 0x(..) 0x(..) 0x(..)$", + otool_line) + if uuid_match: + state = 5 + uuid += uuid_match.group(1) + uuid_match.group(2) + "-" + \ + uuid_match.group(3) + uuid_match.group(4) + \ + uuid_match.group(5) + uuid_match.group(6) + \ + uuid_match.group(7) + uuid_match.group(8) + else: + state = 6 + + if otool_cmd.wait() != 0: + state = 6 + + if state == 5: + uuids[arch] = uuid.upper() + + return uuids + +# Given a path to a Mach-O file and possible information from the environment, +# determines the desired path to the .dSYM. +def dsym_path(macho): + # If building a bundle, the .dSYM should be placed next to the bundle. Use + # WRAPPER_NAME to make this determination. If called from xcodebuild, + # WRAPPER_NAME will be set to the name of the bundle. + dsym = "" + if "WRAPPER_NAME" in os.environ: + if "BUILT_PRODUCTS_DIR" in os.environ: + dsym = os.path.join(os.environ["BUILT_PRODUCTS_DIR"], + os.environ["WRAPPER_NAME"]) + else: + dsym = os.environ["WRAPPER_NAME"] + else: + dsym = macho + + dsym += ".dSYM" + + return dsym + +# Creates a fake .dSYM bundle at dsym for macho, a Mach-O image with the +# architectures and UUIDs specified by the uuids map. +def make_fake_dsym(macho, dsym): + uuids = macho_uuids(macho) + if len(uuids) == 0: + return False + + dwarf_dir = os.path.join(dsym, "Contents", "Resources", "DWARF") + dwarf_file = os.path.join(dwarf_dir, os.path.basename(macho)) + try: + os.makedirs(dwarf_dir) + except OSError, (err, error_string): + if err != errno.EEXIST: + raise + shutil.copyfile(macho, dwarf_file) + + # info_template is the same as what dsymutil would have written, with the + # addition of the fake_dsym key. + info_template = \ +'''<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.xcode.dsym.%(root_name)s</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>dSYM</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>dSYM_UUID</key> + <dict> +%(uuid_dict)s </dict> + <key>fake_dsym</key> + <true/> + </dict> +</plist> +''' + + root_name = os.path.basename(dsym)[:-5] # whatever.dSYM without .dSYM + uuid_dict = "" + for arch in sorted(uuids): + uuid_dict += "\t\t\t<key>" + arch + "</key>\n"\ + "\t\t\t<string>" + uuids[arch] + "</string>\n" + info_dict = { + "root_name": root_name, + "uuid_dict": uuid_dict, + } + info_contents = info_template % info_dict + info_file = os.path.join(dsym, "Info.plist") + info_fd = open(info_file, "w") + info_fd.write(info_contents) + info_fd.close() + + return True + +# For a Mach-O file, determines where the .dSYM bundle should be located. If +# the bundle does not exist or has a modification time older than the Mach-O +# file, calls make_fake_dsym to create a fake .dSYM bundle there, then strips +# the Mach-O file and sets the modification time on the .dSYM bundle and Mach-O +# file to be identical. +def strip_and_make_fake_dsym(macho): + dsym = dsym_path(macho) + macho_stat = os.stat(macho) + dsym_stat = None + try: + dsym_stat = os.stat(dsym) + except OSError, (err, error_string): + if err != errno.ENOENT: + raise + + if dsym_stat is None or dsym_stat.st_mtime < macho_stat.st_mtime: + # Make a .dSYM bundle + if not make_fake_dsym(macho, dsym): + return False + + # Strip the Mach-O file + remove_dsym = True + try: + strip_path = "" + if "SYSTEM_DEVELOPER_BIN_DIR" in os.environ: + strip_path = os.environ["SYSTEM_DEVELOPER_BIN_DIR"] + else: + strip_path = "/usr/bin" + strip_path = os.path.join(strip_path, "strip") + strip_cmdline = [strip_path] + sys.argv[1:] + # Print the strip invocation so that it's obvious something is happening + print " ".join(strip_cmdline) + strip_cmd = subprocess.Popen(strip_cmdline) + if strip_cmd.wait() == 0: + remove_dsym = False + finally: + if remove_dsym: + shutil.rmtree(dsym) + + # Update modification time on the Mach-O file and .dSYM bundle + now = time.time() + os.utime(macho, (now, now)) + os.utime(dsym, (now, now)) + + return True + +def main(argv=None): + if argv is None: + argv = sys.argv + + # This only supports operating on one file at a time. Look at the arguments + # to strip to figure out what the source to be stripped is. Arguments are + # processed in the same way that strip does, although to reduce complexity, + # this doesn't do all of the same checking as strip. For example, strip + # has no -Z switch and would treat -Z on the command line as an error. For + # the purposes this is needed for, that's fine. + macho = None + process_switches = True + ignore_argument = False + for arg in argv[1:]: + if ignore_argument: + ignore_argument = False + continue + if process_switches: + if arg == "-": + process_switches = False + # strip has these switches accept an argument: + if arg in ["-s", "-R", "-d", "-o", "-arch"]: + ignore_argument = True + if arg[0] == "-": + continue + if macho is None: + macho = arg + else: + print >> sys.stderr, "Too many things to strip" + return 1 + + if macho is None: + print >> sys.stderr, "Nothing to strip" + return 1 + + if not strip_and_make_fake_dsym(macho): + return 1 + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/build/release.xcconfig b/build/release.xcconfig index 3c44cfc..0feda85 100644 --- a/build/release.xcconfig +++ b/build/release.xcconfig @@ -4,8 +4,9 @@ #include "common.xcconfig" +// Stripping is done in release mode, but is handled externally to Xcode. +// See mac/strip_from_xcode for details. + DEAD_CODE_STRIPPING = YES -DEBUG_INFORMATION_FORMAT = dwarf-with-dsym -DEPLOYMENT_POSTPROCESSING = YES +DEBUG_INFORMATION_FORMAT = dwarf GCC_PREPROCESSOR_DEFINITIONS = $(GCC_PREPROCESSOR_DEFINITIONS) NDEBUG -STRIP_STYLE = all diff --git a/build/staticlib.xcconfig b/build/staticlib.xcconfig index 5423219..72d9b22 100644 --- a/build/staticlib.xcconfig +++ b/build/staticlib.xcconfig @@ -2,6 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -DEPLOYMENT_POSTPROCESSING = NO -STRIP_STYLE = debugging CODECOVERAGE_OTHER_LDFLAGS = diff --git a/build/v8.xcodeproj/project.pbxproj b/build/v8.xcodeproj/project.pbxproj index c75e097..9105974 100644 --- a/build/v8.xcodeproj/project.pbxproj +++ b/build/v8.xcodeproj/project.pbxproj @@ -854,6 +854,7 @@ buildPhases = ( 897F76770E71B4CC007ACF34 /* Sources */, 897F76780E71B4CC007ACF34 /* Frameworks */, + 4D3D54950EC3A50100650CA0 /* Strip If Needed */, ); buildRules = ( ); @@ -905,6 +906,7 @@ buildPhases = ( 89F23C8D0E78D5B6006B2466 /* Sources */, 89F23C8F0E78D5B6006B2466 /* Frameworks */, + 4D3D54970EC3A50A00650CA0 /* Strip If Needed */, ); buildRules = ( ); @@ -941,6 +943,36 @@ /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ + 4D3D54950EC3A50100650CA0 /* Strip If Needed */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Strip If Needed"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "exec \"${XCODEPROJ_DEPTH}/build/mac/strip_from_xcode\"\n"; + showEnvVarsInLog = 0; + }; + 4D3D54970EC3A50A00650CA0 /* Strip If Needed */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Strip If Needed"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "exec \"${XCODEPROJ_DEPTH}/build/mac/strip_from_xcode\"\n"; + showEnvVarsInLog = 0; + }; 89EA6FB50E71AA1F00F59E1B /* Pre-Build */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; |