| 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 |