OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Downloads, builds (with instrumentation) and installs shared libraries.""" | 6 """Downloads, builds (with instrumentation) and installs shared libraries.""" |
7 | 7 |
8 import argparse | 8 import argparse |
9 import os | 9 import os |
10 import platform | 10 import platform |
| 11 import shlex |
11 import shutil | 12 import shutil |
12 import subprocess | 13 import subprocess |
13 import sys | 14 import sys |
14 | 15 |
15 # Build parameters for different sanitizers. | |
16 # We use XORIGIN as RPATH and after building library replace it to $ORIGIN | |
17 # The reason: this flag goes through configure script and makefiles | |
18 # differently for different libraries. So the dollar sign '$' should be | |
19 # differently escaped. Instead of having problems with that it just | |
20 # uses XORIGIN to build library and after that replaces it to $ORIGIN | |
21 # directly in .so file. | |
22 SUPPORTED_SANITIZERS = { | |
23 'asan': { | |
24 'compiler_flags': '-O2 -fsanitize=address -gline-tables-only -fPIC -w ' | |
25 '-U_FORITFY_SOURCE', | |
26 'linker_flags': '-fsanitize=address -Wl,-z,origin -Wl,-R,XORIGIN/.' | |
27 }, | |
28 'msan': { | |
29 'compiler_flags': '-O2 -fsanitize=memory ' | |
30 '-fsanitize-memory-track-origins ' | |
31 '-gline-tables-only -fPIC -w -U_FORTIFY_SOURCE', | |
32 'linker_flags': '-fsanitize=memory -Wl,-z,origin -Wl,-R,XORIGIN/.' | |
33 }, | |
34 'tsan': { | |
35 'compiler_flags': '-O2 -fsanitize=thread -gline-tables-only -fPIC -w ' | |
36 '-U_FORTIFY_SOURCE', | |
37 'linker_flags': '-fsanitize=thread -Wl,-z,origin -Wl,-R,XORIGIN/.' | |
38 }, | |
39 } | |
40 | |
41 | |
42 class ScopedChangeDirectory(object): | 16 class ScopedChangeDirectory(object): |
43 """Changes current working directory and restores it back automatically.""" | 17 """Changes current working directory and restores it back automatically.""" |
44 | 18 |
45 def __init__(self, path): | 19 def __init__(self, path): |
46 self.path = path | 20 self.path = path |
47 self.old_path = '' | 21 self.old_path = '' |
48 | 22 |
49 def __enter__(self): | 23 def __enter__(self): |
50 self.old_path = os.getcwd() | 24 self.old_path = os.getcwd() |
51 os.chdir(self.path) | 25 os.chdir(self.path) |
52 return self | 26 return self |
53 | 27 |
54 def __exit__(self, exc_type, exc_value, traceback): | 28 def __exit__(self, exc_type, exc_value, traceback): |
55 os.chdir(self.old_path) | 29 os.chdir(self.old_path) |
56 | 30 |
57 | 31 |
58 def get_script_absolute_path(): | 32 def get_script_absolute_path(): |
59 return os.path.dirname(os.path.abspath(__file__)) | 33 return os.path.dirname(os.path.abspath(__file__)) |
60 | 34 |
61 | 35 |
62 def get_library_build_dependencies(library): | 36 def get_package_build_dependencies(package): |
63 command = 'apt-get -s build-dep %s | grep Inst | cut -d " " -f 2' % library | 37 command = 'apt-get -s build-dep %s | grep Inst | cut -d " " -f 2' % package |
64 command_result = subprocess.Popen(command, stdout=subprocess.PIPE, | 38 command_result = subprocess.Popen(command, stdout=subprocess.PIPE, |
65 shell=True) | 39 shell=True) |
66 if command_result.wait(): | 40 if command_result.wait(): |
67 raise Exception('Failed to determine build dependencies for %s' % library) | 41 raise Exception('Failed to determine build dependencies for %s' % package) |
68 build_dependencies = [l.strip() for l in command_result.stdout] | 42 build_dependencies = [l.strip() for l in command_result.stdout] |
69 return build_dependencies | 43 return build_dependencies |
70 | 44 |
71 | 45 |
72 def check_library_build_dependencies(library): | 46 def check_package_build_dependencies(package): |
73 build_dependencies = get_library_build_dependencies(library) | 47 build_dependencies = get_package_build_dependencies(package) |
74 if len(build_dependencies): | 48 if len(build_dependencies): |
75 print >> sys.stderr, 'Please, install build-dependencies for %s' % library | 49 print >> sys.stderr, 'Please, install build-dependencies for %s' % package |
76 print >> sys.stderr, 'One-liner for APT:' | 50 print >> sys.stderr, 'One-liner for APT:' |
77 print >> sys.stderr, 'sudo apt-get -y --no-remove build-dep %s' % library | 51 print >> sys.stderr, 'sudo apt-get -y --no-remove build-dep %s' % package |
78 sys.exit(1) | 52 sys.exit(1) |
79 | 53 |
80 | 54 |
81 def shell_call(command, verbose=False, environment=None): | 55 def shell_call(command, verbose=False, environment=None): |
82 """ Wrapper on subprocess.Popen | 56 """ Wrapper on subprocess.Popen |
83 | 57 |
84 Calls command with specific environment and verbosity using | 58 Calls command with specific environment and verbosity using |
85 subprocess.Popen | 59 subprocess.Popen |
86 | 60 |
87 Args: | 61 Args: |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
251 elif parsed_arguments.build_method == 'custom_libpci3': | 225 elif parsed_arguments.build_method == 'custom_libpci3': |
252 libpci3_make_install(parsed_arguments, environment, install_prefix) | 226 libpci3_make_install(parsed_arguments, environment, install_prefix) |
253 elif parsed_arguments.build_method == 'custom_libappindicator1': | 227 elif parsed_arguments.build_method == 'custom_libappindicator1': |
254 environment['CSC'] = '/usr/bin/mono-csc' | 228 environment['CSC'] = '/usr/bin/mono-csc' |
255 destdir_configure_make_install( | 229 destdir_configure_make_install( |
256 parsed_arguments, environment, install_prefix) | 230 parsed_arguments, environment, install_prefix) |
257 else: | 231 else: |
258 raise Exception('Unrecognized build method: %s' % | 232 raise Exception('Unrecognized build method: %s' % |
259 parsed_arguments.build_method) | 233 parsed_arguments.build_method) |
260 | 234 |
| 235 def unescape_flags(s): |
| 236 # GYP escapes the build flags as if they are going to be inserted directly |
| 237 # into the command line. Since we pass them via CFLAGS/LDFLAGS, we must drop |
| 238 # the double quotes accordingly. |
| 239 return ' '.join(shlex.split(s)) |
261 | 240 |
262 def download_build_install(parsed_arguments): | 241 def download_build_install(parsed_arguments): |
263 sanitizer_params = SUPPORTED_SANITIZERS[parsed_arguments.sanitizer_type] | |
264 | |
265 environment = os.environ.copy() | 242 environment = os.environ.copy() |
266 # Usage of environment variables CC and CXX prefers usage flags --c-compiler | 243 # The CC/CXX environment variables take precedence over the command line |
267 # and --cxx-compiler | 244 # flags. |
268 if 'CC' not in environment and parsed_arguments.cc: | 245 if 'CC' not in environment and parsed_arguments.cc: |
269 environment['CC'] = parsed_arguments.cc | 246 environment['CC'] = parsed_arguments.cc |
270 if 'CXX' not in environment and parsed_arguments.cxx: | 247 if 'CXX' not in environment and parsed_arguments.cxx: |
271 environment['CXX'] = parsed_arguments.cxx | 248 environment['CXX'] = parsed_arguments.cxx |
272 | 249 |
273 product_directory = os.path.normpath('%s/%s' % ( | 250 product_directory = os.path.normpath('%s/%s' % ( |
274 get_script_absolute_path(), | 251 get_script_absolute_path(), |
275 parsed_arguments.product_directory)) | 252 parsed_arguments.product_directory)) |
276 | 253 |
277 compiler_flags = sanitizer_params['compiler_flags'] | 254 cflags = unescape_flags(parsed_arguments.cflags) |
278 if parsed_arguments.sanitizer_blacklist: | 255 if parsed_arguments.sanitizer_blacklist: |
279 compiler_flags += ' -fsanitize-blacklist=%s/%s' % ( | 256 cflags += ' -fsanitize-blacklist=%s/%s' % ( |
280 product_directory, | 257 product_directory, |
281 parsed_arguments.sanitizer_blacklist) | 258 parsed_arguments.sanitizer_blacklist) |
282 environment['CFLAGS'] = '%s %s' % (compiler_flags, | 259 environment['CFLAGS'] = cflags |
283 parsed_arguments.extra_cflags) | 260 environment['CXXFLAGS'] = cflags |
284 environment['CXXFLAGS'] = '%s %s' % ( | |
285 compiler_flags, | |
286 parsed_arguments.extra_cxxflags) | |
287 | 261 |
288 install_prefix = '%s/instrumented_libraries/%s' % ( | 262 install_prefix = '%s/instrumented_libraries/%s' % ( |
289 product_directory, | 263 product_directory, |
290 parsed_arguments.sanitizer_type) | 264 parsed_arguments.sanitizer_type) |
291 | 265 |
| 266 ldflags = unescape_flags(parsed_arguments.ldflags) |
292 # Make sure the linker searches the instrumented libraries dir for | 267 # Make sure the linker searches the instrumented libraries dir for |
293 # library dependencies. | 268 # library dependencies. |
294 environment['LDFLAGS'] = '%s -L%s/lib %s' % ( | 269 environment['LDFLAGS'] = '%s -L%s/lib' % (ldflags, install_prefix) |
295 sanitizer_params['linker_flags'], | |
296 install_prefix, parsed_arguments.extra_ldflags) | |
297 | 270 |
298 library_directory = '%s/%s' % (parsed_arguments.intermediate_directory, | 271 package_directory = '%s/%s' % (parsed_arguments.intermediate_directory, |
299 parsed_arguments.library) | 272 parsed_arguments.package) |
300 | 273 |
301 # A failed build might have left a dirty source tree behind. | 274 # A failed build might have left a dirty source tree behind. |
302 if os.path.exists(library_directory): | 275 if os.path.exists(package_directory): |
303 shell_call('rm -rf %s' % library_directory, parsed_arguments.verbose) | 276 shell_call('rm -rf %s' % package_directory, parsed_arguments.verbose) |
304 os.makedirs(library_directory) | 277 os.makedirs(package_directory) |
305 | 278 |
306 with ScopedChangeDirectory(library_directory) as cd_library: | 279 with ScopedChangeDirectory(package_directory) as cd_package: |
307 shell_call('apt-get source %s' % parsed_arguments.library, | 280 shell_call('apt-get source %s' % parsed_arguments.package, |
308 parsed_arguments.verbose) | 281 parsed_arguments.verbose) |
309 # There should be exactly one subdirectory after downloading a package. | 282 # There should be exactly one subdirectory after downloading a package. |
310 subdirectories = [d for d in os.listdir('.') if os.path.isdir(d)] | 283 subdirectories = [d for d in os.listdir('.') if os.path.isdir(d)] |
311 if len(subdirectories) != 1: | 284 if len(subdirectories) != 1: |
312 raise (Exception('There was not one directory after downloading ' | 285 raise (Exception('There was not one directory after downloading ' |
313 'a package %s' % parsed_arguments.library)) | 286 'a package %s' % parsed_arguments.package)) |
314 with ScopedChangeDirectory(subdirectories[0]): | 287 with ScopedChangeDirectory(subdirectories[0]): |
315 # Here we are in the package directory. | 288 # Here we are in the package directory. |
316 if parsed_arguments.run_before_build: | 289 if parsed_arguments.run_before_build: |
317 shell_call( | 290 shell_call( |
318 '%s/%s' % | 291 '%s/%s' % |
319 (os.path.relpath(cd_library.old_path), | 292 (os.path.relpath(cd_package.old_path), |
320 parsed_arguments.run_before_build), | 293 parsed_arguments.run_before_build), |
321 parsed_arguments.verbose) | 294 parsed_arguments.verbose) |
322 try: | 295 try: |
323 build_and_install(parsed_arguments, environment, install_prefix) | 296 build_and_install(parsed_arguments, environment, install_prefix) |
324 except Exception as exception: | 297 except Exception as exception: |
325 print exception | 298 print exception |
326 print 'Failed to build library %s.' % parsed_arguments.library | 299 print 'Failed to build package %s.' % parsed_arguments.package |
327 print ('Probably, some of its dependencies are not installed: %s' % | 300 print ('Probably, some of its dependencies are not installed: %s' % |
328 ' '.join(get_library_build_dependencies(parsed_arguments.library)
)) | 301 ' '.join(get_package_build_dependencies(parsed_arguments.package)
)) |
329 sys.exit(1) | 302 sys.exit(1) |
330 | 303 |
331 # Touch a txt file to indicate library is installed. | 304 # Touch a txt file to indicate package is installed. |
332 open('%s/%s.txt' % (install_prefix, parsed_arguments.library), 'w').close() | 305 open('%s/%s.txt' % (install_prefix, parsed_arguments.package), 'w').close() |
333 | 306 |
334 # Remove downloaded package and generated temporary build files. | 307 # Remove downloaded package and generated temporary build files. |
335 # Failed builds intentionally skip this step, in order to aid in tracking down | 308 # Failed builds intentionally skip this step, in order to aid in tracking down |
336 # build failures. | 309 # build failures. |
337 shell_call('rm -rf %s' % library_directory, parsed_arguments.verbose) | 310 shell_call('rm -rf %s' % package_directory, parsed_arguments.verbose) |
338 | |
339 | 311 |
340 def main(): | 312 def main(): |
341 argument_parser = argparse.ArgumentParser( | 313 argument_parser = argparse.ArgumentParser( |
342 description='Download, build and install instrumented library') | 314 description='Download, build and install instrumented package') |
343 | 315 |
344 argument_parser.add_argument('-j', '--jobs', type=int, default=1) | 316 argument_parser.add_argument('-j', '--jobs', type=int, default=1) |
345 argument_parser.add_argument('-l', '--library', required=True) | 317 argument_parser.add_argument('-p', '--package', required=True) |
346 argument_parser.add_argument( | 318 argument_parser.add_argument( |
347 '-i', '--product-directory', default='.', | 319 '-i', '--product-directory', default='.', |
348 help='Relative path to the directory with chrome binaries') | 320 help='Relative path to the directory with chrome binaries') |
349 argument_parser.add_argument( | 321 argument_parser.add_argument( |
350 '-m', '--intermediate-directory', default='.', | 322 '-m', '--intermediate-directory', default='.', |
351 help='Relative path to the directory for temporary build files') | 323 help='Relative path to the directory for temporary build files') |
352 argument_parser.add_argument('--extra-configure-flags', default='') | 324 argument_parser.add_argument('--extra-configure-flags', default='') |
353 argument_parser.add_argument('--extra-cflags', default='') | 325 argument_parser.add_argument('--cflags', default='') |
354 argument_parser.add_argument('--extra-cxxflags', default='') | 326 argument_parser.add_argument('--ldflags', default='') |
355 argument_parser.add_argument('--extra-ldflags', default='') | |
356 argument_parser.add_argument('-s', '--sanitizer-type', required=True, | 327 argument_parser.add_argument('-s', '--sanitizer-type', required=True, |
357 choices=SUPPORTED_SANITIZERS.keys()) | 328 choices=['asan', 'msan', 'tsan']) |
358 argument_parser.add_argument('-v', '--verbose', action='store_true') | 329 argument_parser.add_argument('-v', '--verbose', action='store_true') |
359 argument_parser.add_argument('--check-build-deps', action='store_true') | 330 argument_parser.add_argument('--check-build-deps', action='store_true') |
360 argument_parser.add_argument('--cc') | 331 argument_parser.add_argument('--cc') |
361 argument_parser.add_argument('--cxx') | 332 argument_parser.add_argument('--cxx') |
362 # This should be a shell script to run before building specific libraries | 333 # This should be a shell script to run before building specific libraries |
363 # e.g. extracting archives with sources, patching makefiles, etc. | 334 # e.g. extracting archives with sources, patching makefiles, etc. |
364 argument_parser.add_argument('--run-before-build', default='') | 335 argument_parser.add_argument('--run-before-build', default='') |
365 argument_parser.add_argument('--build-method', default='destdir') | 336 argument_parser.add_argument('--build-method', default='destdir') |
366 argument_parser.add_argument('--sanitizer-blacklist', default='') | 337 argument_parser.add_argument('--sanitizer-blacklist', default='') |
367 | 338 |
368 # Ignore all empty arguments because in several cases gyp passes them to the | 339 # Ignore all empty arguments because in several cases gyp passes them to the |
369 # script, but ArgumentParser treats them as positional arguments instead of | 340 # script, but ArgumentParser treats them as positional arguments instead of |
370 # ignoring (and doesn't have such options). | 341 # ignoring (and doesn't have such options). |
371 parsed_arguments = argument_parser.parse_args( | 342 parsed_arguments = argument_parser.parse_args( |
372 [arg for arg in sys.argv[1:] if len(arg) != 0]) | 343 [arg for arg in sys.argv[1:] if len(arg) != 0]) |
373 # Ensure current working directory is this script directory. | 344 # Ensure current working directory is this script directory. |
374 os.chdir(get_script_absolute_path()) | 345 os.chdir(get_script_absolute_path()) |
375 # Ensure all build dependencies are installed. | 346 # Ensure all build dependencies are installed. |
376 if parsed_arguments.check_build_deps: | 347 if parsed_arguments.check_build_deps: |
377 check_library_build_dependencies(parsed_arguments.library) | 348 check_package_build_dependencies(parsed_arguments.package) |
378 | 349 |
379 download_build_install(parsed_arguments) | 350 download_build_install(parsed_arguments) |
380 | 351 |
381 | 352 |
382 if __name__ == '__main__': | 353 if __name__ == '__main__': |
383 main() | 354 main() |
OLD | NEW |