Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(136)

Side by Side Diff: build/mac/strip_save_dsym

Issue 9659: Fast release builds (Closed) Base URL: svn://chrome-svn.corp.google.com/chrome/trunk/src/
Patch Set: '' Created 12 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/mac/strip_from_xcode ('k') | build/release.xcconfig » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:executable
+ *
OLDNEW
(Empty)
1 #!/usr/bin/python
2
3 # Copyright (c) 2008 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 # Usage: strip_save_dsym <whatever-arguments-you-would-pass-to-strip>
8 #
9 # strip_save_dsym is a wrapper around the standard strip utility. Given an
10 # input Mach-O file, strip_save_dsym will save a copy of the file in a "fake"
11 # .dSYM bundle for debugging, and then call strip to strip the Mach-O file.
12 # Note that the .dSYM file is a "fake" in that it's not a self-contained
13 # .dSYM bundle, it just contains a copy of the original (unstripped) Mach-O
14 # file, and therefore contains references to object files on the filesystem.
15 # The generated .dSYM bundle is therefore unsuitable for debugging in the
16 # absence of these .o files.
17 #
18 # If a .dSYM already exists and has a newer timestamp than the Mach-O file,
19 # this utility does nothing. That allows strip_save_dsym to be run on a file
20 # that has already been stripped without trashing the .dSYM.
21 #
22 # Rationale: the "right" way to generate dSYM bundles, dsymutil, is incredibly
23 # slow. On the other hand, doing a file copy (which is really all that
24 # dsymutil does) is comparatively fast. Since we usually just want to strip
25 # a release-mode executable but still be able to debug it, and we don't care
26 # so much about generating a hermetic dSYM bundle, we'll prefer the file copy.
27 # If a real dSYM is ever needed, it's still possible to create one by running
28 # dsymutil and pointing it at the original Mach-O file inside the "fake"
29 # bundle, provided that the object files are available.
30
31 import errno
32 import os
33 import re
34 import shutil
35 import subprocess
36 import sys
37 import time
38
39 # Returns a list of architectures contained in a Mach-O file. The file can be
40 # a universal (fat) file, in which case there will be one list element for
41 # each contained architecture, or it can be a thin single-architecture Mach-O
42 # file, in which case the list will contain a single element identifying the
43 # architecture. On error, returns an empty list. Determines the architecture
44 # list by calling file.
45 def macho_archs(macho):
46 file_cmd = subprocess.Popen(["/usr/bin/file", "-b", "--", macho],
47 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
48
49 archs = []
50
51 type_line = file_cmd.stdout.readline()
52 type_match = re.match("^Mach-O executable (.*)$", type_line)
53 if type_match:
54 archs.append(type_match.group(1))
55 return [type_match.group(1)]
56 else:
57 type_match = re.match("^Mach-O universal binary with (.*) architectures$",
58 type_line)
59 if type_match:
60 for i in range(0, int(type_match.group(1))):
61 arch_line = file_cmd.stdout.readline()
62 arch_match = re.match(
63 "^.* \(for architecture (.*)\):\tMach-O executable .*$",
64 arch_line)
65 if arch_match:
66 archs.append(arch_match.group(1))
67
68 if file_cmd.wait() != 0:
69 archs = []
70
71 return archs
72
73 # Returns a dictionary mapping architectures contained in the file as returned
74 # by macho_archs to the LC_UUID load command for that architecture.
75 # Architectures with no LC_UUID load command are omitted from the dictionary.
76 # Determines the UUID value by calling otool.
77 def macho_uuids(macho):
78 archs = macho_archs(macho)
79
80 uuids = {}
81
82 for arch in archs:
83 if arch == "":
84 continue
85
86 otool_cmd = subprocess.Popen(["/usr/bin/otool", "-arch", arch, "-l", "-",
87 macho],
88 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
89 # state 0 is when nothing UUID-related has been seen yet. State 1 is
90 # entered after a load command begins, but it may not be an LC_UUID load
91 # command. States 2, 3, and 4 are intermediate states while reading an
92 # LC_UUID command. State 5 is the terminal state for a successful LC_UUID
93 # read. State 6 is the error state.
94 state = 0
95 uuid = ""
96 for otool_line in otool_cmd.stdout:
97 if state == 0:
98 if re.match("^Load command .*$", otool_line):
99 state = 1
100 elif state == 1:
101 if re.match("^ cmd LC_UUID$", otool_line):
102 state = 2
103 else:
104 state = 0
105 elif state == 2:
106 if re.match("^ cmdsize 24$", otool_line):
107 state = 3
108 else:
109 state = 6
110 elif state == 3:
111 uuid_match = re.match("^ uuid 0x(..) 0x(..) 0x(..) 0x(..) "
112 "0x(..) 0x(..) 0x(..) 0x(..)$",
113 otool_line)
114 if uuid_match:
115 state = 4
116 uuid = uuid_match.group(1) + uuid_match.group(2) + \
117 uuid_match.group(3) + uuid_match.group(4) + "-" + \
118 uuid_match.group(5) + uuid_match.group(6) + "-" + \
119 uuid_match.group(7) + uuid_match.group(8) + "-"
120 else:
121 state = 6
122 elif state == 4:
123 uuid_match = re.match("^ 0x(..) 0x(..) 0x(..) 0x(..) "
124 "0x(..) 0x(..) 0x(..) 0x(..)$",
125 otool_line)
126 if uuid_match:
127 state = 5
128 uuid += uuid_match.group(1) + uuid_match.group(2) + "-" + \
129 uuid_match.group(3) + uuid_match.group(4) + \
130 uuid_match.group(5) + uuid_match.group(6) + \
131 uuid_match.group(7) + uuid_match.group(8)
132 else:
133 state = 6
134
135 if otool_cmd.wait() != 0:
136 state = 6
137
138 if state == 5:
139 uuids[arch] = uuid.upper()
140
141 return uuids
142
143 # Given a path to a Mach-O file and possible information from the environment,
144 # determines the desired path to the .dSYM.
145 def dsym_path(macho):
146 # If building a bundle, the .dSYM should be placed next to the bundle. Use
147 # WRAPPER_NAME to make this determination. If called from xcodebuild,
148 # WRAPPER_NAME will be set to the name of the bundle.
149 dsym = ""
150 if "WRAPPER_NAME" in os.environ:
151 if "BUILT_PRODUCTS_DIR" in os.environ:
152 dsym = os.path.join(os.environ["BUILT_PRODUCTS_DIR"],
153 os.environ["WRAPPER_NAME"])
154 else:
155 dsym = os.environ["WRAPPER_NAME"]
156 else:
157 dsym = macho
158
159 dsym += ".dSYM"
160
161 return dsym
162
163 # Creates a fake .dSYM bundle at dsym for macho, a Mach-O image with the
164 # architectures and UUIDs specified by the uuids map.
165 def make_fake_dsym(macho, dsym):
166 uuids = macho_uuids(macho)
167 if len(uuids) == 0:
168 return False
169
170 dwarf_dir = os.path.join(dsym, "Contents", "Resources", "DWARF")
171 dwarf_file = os.path.join(dwarf_dir, os.path.basename(macho))
172 try:
173 os.makedirs(dwarf_dir)
174 except OSError, (err, error_string):
175 if err != errno.EEXIST:
176 raise
177 shutil.copyfile(macho, dwarf_file)
178
179 # info_template is the same as what dsymutil would have written, with the
180 # addition of the fake_dsym key.
181 info_template = \
182 '''<?xml version="1.0" encoding="UTF-8"?>
183 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple. com/DTDs/PropertyList-1.0.dtd">
184 <plist version="1.0">
185 <dict>
186 <key>CFBundleDevelopmentRegion</key>
187 <string>English</string>
188 <key>CFBundleIdentifier</key>
189 <string>com.apple.xcode.dsym.%(root_name)s</string>
190 <key>CFBundleInfoDictionaryVersion</key>
191 <string>6.0</string>
192 <key>CFBundlePackageType</key>
193 <string>dSYM</string>
194 <key>CFBundleSignature</key>
195 <string>????</string>
196 <key>CFBundleShortVersionString</key>
197 <string>1.0</string>
198 <key>CFBundleVersion</key>
199 <string>1</string>
200 <key>dSYM_UUID</key>
201 <dict>
202 %(uuid_dict)s </dict>
203 <key>fake_dsym</key>
204 <true/>
205 </dict>
206 </plist>
207 '''
208
209 root_name = os.path.basename(dsym)[:-5] # whatever.dSYM without .dSYM
210 uuid_dict = ""
211 for arch in sorted(uuids):
212 uuid_dict += "\t\t\t<key>" + arch + "</key>\n"\
213 "\t\t\t<string>" + uuids[arch] + "</string>\n"
214 info_dict = {
215 "root_name": root_name,
216 "uuid_dict": uuid_dict,
217 }
218 info_contents = info_template % info_dict
219 info_file = os.path.join(dsym, "Info.plist")
220 info_fd = open(info_file, "w")
221 info_fd.write(info_contents)
222 info_fd.close()
223
224 return True
225
226 # For a Mach-O file, determines where the .dSYM bundle should be located. If
227 # the bundle does not exist or has a modification time older than the Mach-O
228 # file, calls make_fake_dsym to create a fake .dSYM bundle there, then strips
229 # the Mach-O file and sets the modification time on the .dSYM bundle and Mach-O
230 # file to be identical.
231 def strip_and_make_fake_dsym(macho):
232 dsym = dsym_path(macho)
233 macho_stat = os.stat(macho)
234 dsym_stat = None
235 try:
236 dsym_stat = os.stat(dsym)
237 except OSError, (err, error_string):
238 if err != errno.ENOENT:
239 raise
240
241 if dsym_stat is None or dsym_stat.st_mtime < macho_stat.st_mtime:
242 # Make a .dSYM bundle
243 if not make_fake_dsym(macho, dsym):
244 return False
245
246 # Strip the Mach-O file
247 remove_dsym = True
248 try:
249 strip_path = ""
250 if "SYSTEM_DEVELOPER_BIN_DIR" in os.environ:
251 strip_path = os.environ["SYSTEM_DEVELOPER_BIN_DIR"]
252 else:
253 strip_path = "/usr/bin"
254 strip_path = os.path.join(strip_path, "strip")
255 strip_cmdline = [strip_path] + sys.argv[1:]
256 # Print the strip invocation so that it's obvious something is happening
257 print " ".join(strip_cmdline)
258 strip_cmd = subprocess.Popen(strip_cmdline)
259 if strip_cmd.wait() == 0:
260 remove_dsym = False
261 finally:
262 if remove_dsym:
263 shutil.rmtree(dsym)
264
265 # Update modification time on the Mach-O file and .dSYM bundle
266 now = time.time()
267 os.utime(macho, (now, now))
268 os.utime(dsym, (now, now))
269
270 return True
271
272 def main(argv=None):
273 if argv is None:
274 argv = sys.argv
275
276 # This only supports operating on one file at a time. Look at the arguments
277 # to strip to figure out what the source to be stripped is. Arguments are
278 # processed in the same way that strip does, although to reduce complexity,
279 # this doesn't do all of the same checking as strip. For example, strip
280 # has no -Z switch and would treat -Z on the command line as an error. For
281 # the purposes this is needed for, that's fine.
282 macho = None
283 process_switches = True
284 ignore_argument = False
285 for arg in argv[1:]:
286 if ignore_argument:
287 ignore_argument = False
288 continue
289 if process_switches:
290 if arg == "-":
291 process_switches = False
292 # strip has these switches accept an argument:
293 if arg in ["-s", "-R", "-d", "-o", "-arch"]:
294 ignore_argument = True
295 if arg[0] == "-":
296 continue
297 if macho is None:
298 macho = arg
299 else:
300 print >> sys.stderr, "Too many things to strip"
301 return 1
302
303 if macho is None:
304 print >> sys.stderr, "Nothing to strip"
305 return 1
306
307 if not strip_and_make_fake_dsym(macho):
308 return 1
309
310 return 0
311
312 if __name__ == '__main__':
313 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « build/mac/strip_from_xcode ('k') | build/release.xcconfig » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698