diff --git a/build/OWNERS b/build/OWNERS
new file mode 100644
index 00000000000..17d067cff36
--- /dev/null
+++ b/build/OWNERS
@@ -0,0 +1,5 @@
+cjhopman@chromium.org
+dpranke@chromium.org
+jochen@chromium.org
+scottmg@chromium.org
+thakis@chromium.org
diff --git a/build/PRESUBMIT.py b/build/PRESUBMIT.py
new file mode 100644
index 00000000000..fca962f1caa
--- /dev/null
+++ b/build/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2015 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.
+
+
+def _RunTests(input_api, output_api):
+ return (input_api.canned_checks.RunUnitTestsInDirectory(
+ input_api, output_api, '.', whitelist=[r'.+_test.py$']))
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _RunTests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _RunTests(input_api, output_api)
diff --git a/build/README.chromium b/build/README.chromium
new file mode 100644
index 00000000000..012df35c7aa
--- /dev/null
+++ b/build/README.chromium
@@ -0,0 +1,15 @@
+List of property sheets to be included by projects:
+ common.vsprops
+ Not used anymore. No-op. Kept for compatibility with current projects.
+
+ debug.vsprops
+ Enables debug settings. Must be included directly in Debug configuration. Includes internal\essential.vsprops.
+
+ external_code.vsprops
+ Contains settings made to simplify usage of external (non-Google) code. It relaxes the warning levels. Should be included after debug.vsprops or release.vsprops to override their settings.
+
+ output_dll_copy.rules
+ Run to enable automatic copy of DLL when they are as an input file in a vcproj project.
+
+ release.vsprops
+ Enables release settings. Must be included directly in Release configuration. Includes internal\essential.vsprops. Also includes "internal\release_impl$(CHROME_BUILD_TYPE).vsprops". So the behavior is dependant on the CHROME_BUILD_TYPE environment variable.
diff --git a/build/README.dart b/build/README.dart
new file mode 100644
index 00000000000..ac57fbe6222
--- /dev/null
+++ b/build/README.dart
@@ -0,0 +1,7 @@
+This directory was taken from a snapshot of flutter/engine/src/build/.
+
+The snapshot was taken with a recursive copy `cp -R` of the directory from
+the flutter repository.
+
+The contents is used to support the GN build system.
+
diff --git a/build/all.gyp b/build/all.gyp
new file mode 100644
index 00000000000..b36fae6a575
--- /dev/null
+++ b/build/all.gyp
@@ -0,0 +1,1442 @@
+# Copyright (c) 2012 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.
+
+{
+ 'variables': {
+ # A hook that can be overridden in other repositories to add additional
+ # compilation targets to 'All'.
+ 'app_targets%': [],
+ # For Android-specific targets.
+ 'android_app_targets%': [],
+ },
+ 'targets': [
+ {
+ 'target_name': 'All',
+ 'type': 'none',
+ 'xcode_create_dependents_test_runner': 1,
+ 'dependencies': [
+ '<@(app_targets)',
+ 'some.gyp:*',
+ '../base/base.gyp:*',
+ '../components/components.gyp:*',
+ '../components/components_tests.gyp:*',
+ '../content/content.gyp:*',
+ '../crypto/crypto.gyp:*',
+ '../net/net.gyp:*',
+ '../sdch/sdch.gyp:*',
+ '../sql/sql.gyp:*',
+ '../testing/gmock.gyp:*',
+ '../testing/gtest.gyp:*',
+ '../third_party/icu/icu.gyp:*',
+ '../third_party/libxml/libxml.gyp:*',
+ '../third_party/sqlite/sqlite.gyp:*',
+ '../third_party/zlib/zlib.gyp:*',
+ '../ui/accessibility/accessibility.gyp:*',
+ '../ui/base/ui_base.gyp:*',
+ '../ui/display/display.gyp:display_unittests',
+ '../ui/snapshot/snapshot.gyp:*',
+ '../url/url.gyp:*',
+ ],
+ 'conditions': [
+ ['OS!="ios" and OS!="mac"', {
+ 'dependencies': [
+ '../ui/touch_selection/ui_touch_selection.gyp:*',
+ ],
+ }],
+ ['OS=="ios"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:browser',
+ '../chrome/chrome.gyp:browser_ui',
+ '../ios/ios.gyp:*',
+ # NOTE: This list of targets is present because
+ # mojo_base.gyp:mojo_base cannot be built on iOS, as
+ # javascript-related targets cause v8 to be built.
+ '../mojo/mojo_base.gyp:mojo_common_lib',
+ '../mojo/mojo_base.gyp:mojo_common_unittests',
+ '../google_apis/google_apis.gyp:google_apis_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../third_party/mojo/mojo_edk.gyp:mojo_system_impl',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_bindings_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_environment_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_system_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_utility_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_system_unittests',
+ '../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
+ '../third_party/mojo/mojo_public.gyp:mojo_public_test_utils',
+ '../third_party/mojo/mojo_public.gyp:mojo_system',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ ],
+ }],
+ ['OS=="android"', {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:content_shell_apk',
+ '<@(android_app_targets)',
+ 'android_builder_tests',
+ '../tools/telemetry/telemetry.gyp:*#host',
+ # TODO(nyquist) This should instead by a target for sync when all of
+ # the sync-related code for Android has been upstreamed.
+ # See http://crbug.com/159203
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_javalib',
+ ],
+ 'conditions': [
+ ['chromecast==0', {
+ 'dependencies': [
+ '../android_webview/android_webview.gyp:android_webview_apk',
+ '../android_webview/android_webview.gyp:system_webview_apk',
+ '../android_webview/android_webview_shell.gyp:android_webview_shell_apk',
+ '../chrome/android/chrome_apk.gyp:chrome_public_apk',
+ '../chrome/chrome.gyp:chrome_shell_apk',
+ '../chrome/chrome.gyp:chrome_sync_shell_apk',
+ '../remoting/remoting.gyp:remoting_apk',
+ ],
+ }],
+ # TODO: Enable packed relocations for x64. See: b/20532404
+ ['target_arch != "x64"', {
+ 'dependencies': [
+ '../third_party/android_platform/relocation_packer.gyp:android_relocation_packer_unittests#host',
+ ],
+ }],
+ ],
+ }, {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:*',
+ # TODO: This should build on Android and the target should move to the list above.
+ '../sync/sync.gyp:*',
+ ],
+ }],
+ ['OS!="ios" and OS!="android" and chromecast==0', {
+ 'dependencies': [
+ '../third_party/re2/re2.gyp:re2',
+ '../chrome/chrome.gyp:*',
+ '../chrome/tools/profile_reset/jtl_compiler.gyp:*',
+ '../cc/blink/cc_blink_tests.gyp:*',
+ '../cc/cc_tests.gyp:*',
+ '../device/usb/usb.gyp:*',
+ '../extensions/extensions.gyp:*',
+ '../extensions/extensions_tests.gyp:*',
+ '../gin/gin.gyp:*',
+ '../gpu/gpu.gyp:*',
+ '../gpu/tools/tools.gyp:*',
+ '../ipc/ipc.gyp:*',
+ '../ipc/mojo/ipc_mojo.gyp:*',
+ '../jingle/jingle.gyp:*',
+ '../media/cast/cast.gyp:*',
+ '../media/media.gyp:*',
+ '../media/midi/midi.gyp:*',
+ '../mojo/mojo.gyp:*',
+ '../mojo/mojo_base.gyp:*',
+ '../ppapi/ppapi.gyp:*',
+ '../ppapi/ppapi_internal.gyp:*',
+ '../ppapi/tools/ppapi_tools.gyp:*',
+ '../printing/printing.gyp:*',
+ '../skia/skia.gyp:*',
+ '../sync/tools/sync_tools.gyp:*',
+ '../third_party/WebKit/public/all.gyp:*',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:*',
+ '../third_party/codesighs/codesighs.gyp:*',
+ '../third_party/ffmpeg/ffmpeg.gyp:*',
+ '../third_party/iccjpeg/iccjpeg.gyp:*',
+ '../third_party/libpng/libpng.gyp:*',
+ '../third_party/libusb/libusb.gyp:*',
+ '../third_party/libwebp/libwebp.gyp:*',
+ '../third_party/libxslt/libxslt.gyp:*',
+ '../third_party/lzma_sdk/lzma_sdk.gyp:*',
+ '../third_party/mesa/mesa.gyp:*',
+ '../third_party/modp_b64/modp_b64.gyp:*',
+ '../third_party/npapi/npapi.gyp:*',
+ '../third_party/ots/ots.gyp:*',
+ '../third_party/pdfium/samples/samples.gyp:*',
+ '../third_party/qcms/qcms.gyp:*',
+ '../tools/gn/gn.gyp:*',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:*',
+ '../tools/telemetry/telemetry.gyp:*',
+ '../v8/tools/gyp/v8.gyp:*',
+ '<(libjpeg_gyp_path):*',
+ ],
+ }],
+ ['OS!="ios"', {
+ 'dependencies': [
+ '../device/bluetooth/bluetooth.gyp:*',
+ '../device/device_tests.gyp:*',
+ '../gpu/skia_runner/skia_runner.gyp:*',
+ ],
+ }],
+ ['use_openssl==0 and (OS=="mac" or OS=="ios" or OS=="win")', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:*',
+ ],
+ }],
+ ['OS=="win" or OS=="ios" or OS=="linux"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:*',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'dependencies': [
+ '../sandbox/sandbox.gyp:*',
+ '../third_party/crashpad/crashpad/crashpad.gyp:*',
+ '../third_party/ocmock/ocmock.gyp:*',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../courgette/courgette.gyp:*',
+ '../sandbox/sandbox.gyp:*',
+ ],
+ 'conditions': [
+ ['branding=="Chrome"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:linux_packages_<(channel)',
+ ],
+ }],
+ ['enable_ipc_fuzzer==1', {
+ 'dependencies': [
+ '../tools/ipc_fuzzer/ipc_fuzzer.gyp:*',
+ ],
+ }],
+ ['use_dbus==1', {
+ 'dependencies': [
+ '../dbus/dbus.gyp:*',
+ ],
+ }],
+ ],
+ }],
+ ['chromecast==1', {
+ 'dependencies': [
+ '../chromecast/chromecast.gyp:*',
+ ],
+ }],
+ ['use_x11==1', {
+ 'dependencies': [
+ '../tools/xdisplaycheck/xdisplaycheck.gyp:*',
+ ],
+ }],
+ ['OS=="win"', {
+ 'conditions': [
+ ['win_use_allocator_shim==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:*',
+ ],
+ }],
+ ],
+ 'dependencies': [
+ '../chrome/tools/crash_service/caps/caps.gyp:*',
+ '../chrome_elf/chrome_elf.gyp:*',
+ '../cloud_print/cloud_print.gyp:*',
+ '../courgette/courgette.gyp:*',
+ '../rlz/rlz.gyp:*',
+ '../sandbox/sandbox.gyp:*',
+ '<(angle_path)/src/angle.gyp:*',
+ '../third_party/bspatch/bspatch.gyp:*',
+ '../tools/win/static_initializers/static_initializers.gyp:*',
+ ],
+ }, {
+ 'dependencies': [
+ '../third_party/libevent/libevent.gyp:*',
+ ],
+ }],
+ ['toolkit_views==1', {
+ 'dependencies': [
+ '../ui/views/controls/webview/webview.gyp:*',
+ '../ui/views/views.gyp:*',
+ ],
+ }],
+ ['use_aura==1', {
+ 'dependencies': [
+ '../ui/aura/aura.gyp:*',
+ '../ui/aura_extra/aura_extra.gyp:*',
+ ],
+ }],
+ ['use_ash==1', {
+ 'dependencies': [
+ '../ash/ash.gyp:*',
+ ],
+ }],
+ ['remoting==1', {
+ 'dependencies': [
+ '../remoting/remoting_all.gyp:remoting_all',
+ ],
+ }],
+ ['use_openssl==0', {
+ 'dependencies': [
+ '../net/third_party/nss/ssl.gyp:*',
+ ],
+ }],
+ ['use_openssl==1', {
+ 'dependencies': [
+ '../third_party/boringssl/boringssl.gyp:*',
+ '../third_party/boringssl/boringssl_tests.gyp:*',
+ ],
+ }],
+ ['enable_app_list==1', {
+ 'dependencies': [
+ '../ui/app_list/app_list.gyp:*',
+ ],
+ }],
+ ['OS!="android" and OS!="ios"', {
+ 'dependencies': [
+ '../google_apis/gcm/gcm.gyp:*',
+ ],
+ }],
+ ['(chromeos==1 or OS=="linux" or OS=="win" or OS=="mac") and chromecast==0', {
+ 'dependencies': [
+ '../extensions/shell/app_shell.gyp:*',
+ ],
+ }],
+ ['envoy==1', {
+ 'dependencies': [
+ '../envoy/envoy.gyp:*',
+ ],
+ }],
+ ],
+ }, # target_name: All
+ {
+ 'target_name': 'All_syzygy',
+ 'type': 'none',
+ 'conditions': [
+ ['OS=="win" and fastbuild==0 and target_arch=="ia32" and '
+ '(syzyasan==1 or syzygy_optimize==1)', {
+ 'dependencies': [
+ '../chrome/installer/mini_installer_syzygy.gyp:*',
+ ],
+ }],
+ ],
+ }, # target_name: All_syzygy
+ {
+ # Note: Android uses android_builder_tests below.
+ # TODO: Consider merging that with this target.
+ 'target_name': 'chromium_builder_tests',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../net/net.gyp:net_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/display/display.gyp:display_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ 'conditions': [
+ ['OS!="ios"', {
+ 'dependencies': [
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ ],
+ }],
+ ['OS!="ios" and OS!="mac"', {
+ 'dependencies': [
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ ],
+ }],
+ ['OS!="ios" and OS!="android"', {
+ 'dependencies': [
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_shell',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../gin/gin.gyp:gin_unittests',
+ '../google_apis/google_apis.gyp:google_apis_unittests',
+ '../gpu/gles2_conform_support/gles2_conform_support.gyp:gles2_conform_support',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/cast/cast.gyp:cast_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../mojo/mojo.gyp:mojo',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../third_party/WebKit/public/all.gyp:all_blink',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../tools/telemetry/telemetry.gyp:*',
+ ],
+ }],
+ ['OS!="ios" and OS!="android" and chromecast==0', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:chromedriver_tests',
+ '../chrome/chrome.gyp:chromedriver_unittests',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../extensions/extensions_tests.gyp:extensions_browsertests',
+ '../extensions/extensions_tests.gyp:extensions_unittests',
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ '../chrome/chrome.gyp:installer_util_unittests',
+ '../chrome/chrome.gyp:setup_unittests',
+ # ../chrome/test/mini_installer requires mini_installer.
+ '../chrome/installer/mini_installer.gyp:mini_installer',
+ '../chrome_elf/chrome_elf.gyp:chrome_elf_unittests',
+ '../content/content_shell_and_tests.gyp:copy_test_netscape_plugin',
+ '../courgette/courgette.gyp:courgette_unittests',
+ '../sandbox/sandbox.gyp:sbox_integration_tests',
+ '../sandbox/sandbox.gyp:sbox_unittests',
+ '../sandbox/sandbox.gyp:sbox_validation_tests',
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ ],
+ 'conditions': [
+ # remoting_host_installation uses lots of non-trivial GYP that tend
+ # to break because of differences between ninja and msbuild. Make
+ # sure this target is built by the builders on the main waterfall.
+ # See http://crbug.com/180600.
+ ['wix_exists == "True" and sas_dll_exists == "True"', {
+ 'dependencies': [
+ '../remoting/remoting.gyp:remoting_host_installation',
+ ],
+ }],
+ ['syzyasan==1', {
+ 'variables': {
+ # Disable incremental linking for all modules.
+ # 0: inherit, 1: disabled, 2: enabled.
+ 'msvs_debug_link_incremental': '1',
+ 'msvs_large_module_debug_link_mode': '1',
+ # Disable RTC. Syzygy explicitly doesn't support RTC
+ # instrumented binaries for now.
+ 'win_debug_RuntimeChecks': '0',
+ },
+ 'defines': [
+ # Disable iterator debugging (huge speed boost).
+ '_HAS_ITERATOR_DEBUGGING=0',
+ ],
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ # Enable profile information (necessary for SyzyAsan
+ # instrumentation). This is incompatible with incremental
+ # linking.
+ 'Profile': 'true',
+ },
+ }
+ }],
+ ],
+ }],
+ ['chromeos==1', {
+ 'dependencies': [
+ '../ui/chromeos/ui_chromeos.gyp:ui_chromeos_unittests',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../sandbox/sandbox.gyp:sandbox_linux_unittests',
+ ],
+ }],
+ ['OS=="linux" and use_dbus==1', {
+ 'dependencies': [
+ '../dbus/dbus.gyp:dbus_unittests',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'dependencies': [
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ '../ui/message_center/message_center.gyp:*',
+ ],
+ }],
+ ['test_isolation_mode != "noop"', {
+ 'dependencies': [
+ 'chromium_swarm_tests',
+ ],
+ }],
+ ['OS!="android"', {
+ 'dependencies': [
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ ],
+ }],
+ ['enable_basic_printing==1 or enable_print_preview==1', {
+ 'dependencies': [
+ '../printing/printing.gyp:printing_unittests',
+ ],
+ }],
+ ['use_aura==1', {
+ 'dependencies': [
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ '../ui/aura/aura.gyp:aura_unittests',
+ '../ui/compositor/compositor.gyp:compositor_unittests',
+ ],
+ }],
+ ['use_aura==1 and chromecast==0', {
+ 'dependencies': [
+ '../ui/keyboard/keyboard.gyp:keyboard_unittests',
+ '../ui/views/views.gyp:views_unittests',
+ ],
+ }],
+ ['use_aura==1 or toolkit_views==1', {
+ 'dependencies': [
+ '../ui/events/events.gyp:events_unittests',
+ ],
+ }],
+ ['use_ash==1', {
+ 'dependencies': [
+ '../ash/ash.gyp:ash_unittests',
+ ],
+ }],
+ ['disable_nacl==0', {
+ 'dependencies': [
+ '../components/nacl.gyp:nacl_loader_unittests',
+ ],
+ }],
+ ['disable_nacl==0 and disable_nacl_untrusted==0 and enable_nacl_nonsfi_test==1', {
+ 'dependencies': [
+ '../components/nacl.gyp:nacl_helper_nonsfi_unittests',
+ ],
+ }],
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'dependencies': [
+ '../mojo/mojo_nacl_untrusted.gyp:libmojo',
+ '../mojo/mojo_nacl.gyp:monacl_codegen',
+ '../mojo/mojo_nacl.gyp:monacl_sel',
+ '../mojo/mojo_nacl.gyp:monacl_shell',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_builder_tests
+ ],
+ 'conditions': [
+ # TODO(GYP): make gn_migration.gypi work unconditionally.
+ ['OS=="mac" or OS=="win" or (OS=="linux" and target_arch=="x64" and chromecast==0)', {
+ 'includes': [
+ 'gn_migration.gypi',
+ ],
+ }],
+ ['OS!="ios"', {
+ 'targets': [
+ {
+ 'target_name': 'blink_tests',
+ 'type': 'none',
+ 'dependencies': [
+ '../third_party/WebKit/public/all.gyp:all_blink',
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:content_shell_apk',
+ '../breakpad/breakpad.gyp:dump_syms#host',
+ '../breakpad/breakpad.gyp:minidump_stackwalk#host',
+ ],
+ }, { # OS!="android"
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:content_shell',
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../components/test_runner/test_runner.gyp:layout_test_helper',
+ '../content/content_shell_and_tests.gyp:content_shell_crash_service',
+ ],
+ }],
+ ['OS!="win" and OS!="android"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'dependencies': [
+ '../components/test_runner/test_runner.gyp:layout_test_helper',
+ '../breakpad/breakpad.gyp:dump_syms#host',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:dump_syms#host',
+ ],
+ }],
+ ],
+ }, # target_name: blink_tests
+ ],
+ }], # OS!=ios
+ ['OS!="ios" and OS!="android" and chromecast==0', {
+ 'targets': [
+ {
+ 'target_name': 'chromium_builder_nacl_win_integration',
+ 'type': 'none',
+ 'dependencies': [
+ 'chromium_builder_tests',
+ ],
+ }, # target_name: chromium_builder_nacl_win_integration
+ {
+ 'target_name': 'chromium_builder_perf',
+ 'type': 'none',
+ 'dependencies': [
+ '../cc/cc_tests.gyp:cc_perftests',
+ '../chrome/chrome.gyp:chrome',
+ '../chrome/chrome.gyp:load_library_perf_tests',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../chrome/chrome.gyp:sync_performance_tests',
+ '../content/content_shell_and_tests.gyp:content_shell',
+ '../gpu/gpu.gyp:gpu_perftests',
+ '../media/media.gyp:media_perftests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:*',
+ '../tools/telemetry/telemetry.gyp:*',
+ ],
+ 'conditions': [
+ ['OS!="ios" and OS!="win"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:linux_symbols'
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ '../gpu/gpu.gyp:angle_perftests',
+ ],
+ }],
+ ['OS=="win" and target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_builder_perf
+ {
+ 'target_name': 'chromium_gpu_builder',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_gl_tests',
+ '../gpu/gles2_conform_support/gles2_conform_test.gyp:gles2_conform_test',
+ '../gpu/khronos_glcts_support/khronos_glcts_test.gyp:khronos_glcts_test',
+ '../gpu/gpu.gyp:gl_tests',
+ '../gpu/gpu.gyp:angle_unittests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../tools/telemetry/telemetry.gyp:*',
+ ],
+ 'conditions': [
+ ['OS!="ios" and OS!="win"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:linux_symbols'
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ ],
+ }],
+ ['OS=="win" and target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_gpu_builder
+ {
+ 'target_name': 'chromium_gpu_debug_builder',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_gl_tests',
+ '../gpu/gles2_conform_support/gles2_conform_test.gyp:gles2_conform_test',
+ '../gpu/khronos_glcts_support/khronos_glcts_test.gyp:khronos_glcts_test',
+ '../gpu/gpu.gyp:gl_tests',
+ '../gpu/gpu.gyp:angle_unittests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../tools/telemetry/telemetry.gyp:*',
+ ],
+ 'conditions': [
+ ['OS!="ios" and OS!="win"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:linux_symbols'
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ ],
+ }],
+ ['OS=="win" and target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_gpu_debug_builder
+ {
+ # This target contains everything we need to run tests on the special
+ # device-equipped WebRTC bots. We have device-requiring tests in
+ # browser_tests and content_browsertests.
+ 'target_name': 'chromium_builder_webrtc',
+ 'type': 'none',
+ 'dependencies': [
+ 'chromium_builder_perf',
+ '../chrome/chrome.gyp:browser_tests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../third_party/webrtc/tools/tools.gyp:frame_analyzer',
+ '../third_party/webrtc/tools/tools.gyp:rgba_to_i420_converter',
+ ],
+ 'conditions': [
+ ['remoting==1', {
+ 'dependencies': [
+ '../remoting/remoting.gyp:*',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_builder_webrtc
+ {
+ 'target_name': 'chromium_builder_chromedriver',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chromedriver',
+ '../chrome/chrome.gyp:chromedriver_tests',
+ '../chrome/chrome.gyp:chromedriver_unittests',
+ ],
+ }, # target_name: chromium_builder_chromedriver
+ {
+ 'target_name': 'chromium_builder_asan',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome',
+
+ # We refer to content_shell directly rather than blink_tests
+ # because we don't want the _unittests binaries.
+ '../content/content_shell_and_tests.gyp:content_shell',
+ ],
+ 'conditions': [
+ ['OS!="win"', {
+ 'dependencies': [
+ '../net/net.gyp:hpack_fuzz_wrapper',
+ '../net/net.gyp:dns_fuzz_stub',
+ '../skia/skia.gyp:filter_fuzz_stub',
+ ],
+ }],
+ ['enable_ipc_fuzzer==1 and component!="shared_library" and '
+ '(OS=="linux" or OS=="win")', {
+ 'dependencies': [
+ '../tools/ipc_fuzzer/ipc_fuzzer.gyp:*',
+ ],
+ }],
+ ['chromeos==0', {
+ 'dependencies': [
+ '../v8/src/d8.gyp:d8#host',
+ '../third_party/pdfium/samples/samples.gyp:pdfium_test',
+ ],
+ }],
+ ['internal_filter_fuzzer==1', {
+ 'dependencies': [
+ '../skia/tools/clusterfuzz-data/fuzzers/filter_fuzzer/filter_fuzzer.gyp:filter_fuzzer',
+ ],
+ }], # internal_filter_fuzzer
+ ['clang==1', {
+ 'dependencies': [
+ 'sanitizers/sanitizers.gyp:llvm-symbolizer',
+ ],
+ }],
+ ['OS=="win" and fastbuild==0 and target_arch=="ia32" and syzyasan==1', {
+ 'dependencies': [
+ '../chrome/chrome_syzygy.gyp:chrome_dll_syzygy',
+ '../content/content_shell_and_tests.gyp:content_shell_syzyasan',
+ ],
+ 'conditions': [
+ ['chrome_multiple_dll==1', {
+ 'dependencies': [
+ '../chrome/chrome_syzygy.gyp:chrome_child_dll_syzygy',
+ ],
+ }],
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_nacl_sdk',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome_nacl_win64',
+ ]
+ }],
+ ],
+ }, #target_name: chromium_builder_nacl_sdk
+ ], # targets
+ }], #OS!=ios and OS!=android
+ ['OS=="android"', {
+ 'targets': [
+ {
+ # The current list of tests for android. This is temporary
+ # until the full set supported. If adding a new test here,
+ # please also add it to build/android/pylib/gtest/gtest_config.py,
+ # else the test is not run.
+ #
+ # WARNING:
+ # Do not add targets here without communicating the implications
+ # on tryserver triggers and load. Discuss with
+ # chrome-infrastructure-team please.
+ 'target_name': 'android_builder_tests',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/android/jni_generator/jni_generator.gyp:jni_generator_tests',
+ '../base/base.gyp:base_unittests',
+ '../breakpad/breakpad.gyp:breakpad_unittests_deps',
+ # Also compile the tools needed to deal with minidumps, they are
+ # needed to run minidump tests upstream.
+ '../breakpad/breakpad.gyp:dump_syms#host',
+ '../breakpad/breakpad.gyp:symupload#host',
+ '../breakpad/breakpad.gyp:minidump_dump#host',
+ '../breakpad/breakpad.gyp:minidump_stackwalk#host',
+ '../build/android/pylib/device/commands/commands.gyp:chromium_commands',
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_perftests_apk',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_gl_tests',
+ '../content/content_shell_and_tests.gyp:content_junit_tests',
+ '../content/content_shell_and_tests.gyp:chromium_linker_test_apk',
+ '../content/content_shell_and_tests.gyp:content_shell_test_apk',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../gpu/gpu.gyp:gl_tests',
+ '../gpu/gpu.gyp:gpu_perftests_apk',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../media/media.gyp:media_perftests_apk',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests_apk',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../net/net.gyp:net_unittests',
+ '../sandbox/sandbox.gyp:sandbox_linux_unittests_deps',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../testing/android/junit/junit_test.gyp:junit_unit_tests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/WebKit/public/all.gyp:*',
+ '../tools/android/android_tools.gyp:android_tools',
+ '../tools/android/android_tools.gyp:memconsumer',
+ '../tools/android/findbugs_plugin/findbugs_plugin.gyp:findbugs_plugin_test',
+ '../ui/android/ui_android.gyp:ui_android_unittests',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/events/events.gyp:events_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ # Unit test bundles packaged as an apk.
+ '../base/base.gyp:base_unittests_apk',
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests_apk',
+ '../cc/cc_tests.gyp:cc_unittests_apk',
+ '../components/components_tests.gyp:components_browsertests_apk',
+ '../components/components_tests.gyp:components_unittests_apk',
+ '../content/content_shell_and_tests.gyp:content_browsertests_apk',
+ '../content/content_shell_and_tests.gyp:content_gl_tests_apk',
+ '../content/content_shell_and_tests.gyp:content_unittests_apk',
+ '../content/content_shell_and_tests.gyp:video_decode_accelerator_unittest_apk',
+ '../gpu/gpu.gyp:gl_tests_apk',
+ '../gpu/gpu.gyp:gpu_unittests_apk',
+ '../ipc/ipc.gyp:ipc_tests_apk',
+ '../media/media.gyp:media_unittests_apk',
+ '../media/midi/midi.gyp:midi_unittests_apk',
+ '../net/net.gyp:net_unittests_apk',
+ '../sandbox/sandbox.gyp:sandbox_linux_jni_unittests_apk',
+ '../skia/skia_tests.gyp:skia_unittests_apk',
+ '../sql/sql.gyp:sql_unittests_apk',
+ '../sync/sync.gyp:sync_unit_tests_apk',
+ '../tools/android/heap_profiler/heap_profiler.gyp:heap_profiler_unittests_apk',
+ '../ui/android/ui_android.gyp:ui_android_unittests_apk',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests_apk',
+ '../ui/events/events.gyp:events_unittests_apk',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests_apk',
+ '../ui/gl/gl_tests.gyp:gl_unittests_apk',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests_apk',
+ ],
+ 'conditions': [
+ ['chromecast==0', {
+ 'dependencies': [
+ '../android_webview/android_webview.gyp:android_webview_unittests',
+ '../chrome/chrome.gyp:unit_tests',
+ # Unit test bundles packaged as an apk.
+ '../android_webview/android_webview.gyp:android_webview_test_apk',
+ '../android_webview/android_webview.gyp:android_webview_unittests_apk',
+ '../chrome/android/chrome_apk.gyp:chrome_public_test_apk',
+ '../chrome/chrome.gyp:chrome_junit_tests',
+ '../chrome/chrome.gyp:chrome_shell_test_apk',
+ '../chrome/chrome.gyp:chrome_sync_shell_test_apk',
+ '../chrome/chrome.gyp:chrome_shell_uiautomator_tests',
+ '../chrome/chrome.gyp:chromedriver_webview_shell_apk',
+ '../chrome/chrome.gyp:unit_tests_apk',
+ '../third_party/custom_tabs_client/src/custom_tabs_client.gyp:custom_tabs_client_example_apk',
+ ],
+ }],
+ ],
+ },
+ {
+ # WebRTC Chromium tests to run on Android.
+ 'target_name': 'android_builder_chromium_webrtc',
+ 'type': 'none',
+ 'dependencies': [
+ '../build/android/pylib/device/commands/commands.gyp:chromium_commands',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../tools/android/android_tools.gyp:android_tools',
+ '../tools/android/android_tools.gyp:memconsumer',
+ '../content/content_shell_and_tests.gyp:content_browsertests_apk',
+ ],
+ }, # target_name: android_builder_chromium_webrtc
+ ], # targets
+ }], # OS="android"
+ ['OS=="mac"', {
+ 'targets': [
+ {
+ # Target to build everything plus the dmg. We don't put the dmg
+ # in the All target because developers really don't need it.
+ 'target_name': 'all_and_dmg',
+ 'type': 'none',
+ 'dependencies': [
+ 'All',
+ '../chrome/chrome.gyp:build_app_dmg',
+ ],
+ },
+ # These targets are here so the build bots can use them to build
+ # subsets of a full tree for faster cycle times.
+ {
+ 'target_name': 'chromium_builder_dbg',
+ 'type': 'none',
+ 'dependencies': [
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../rlz/rlz.gyp:*',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:*',
+ '../tools/telemetry/telemetry.gyp:*',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_rel',
+ 'type': 'none',
+ 'dependencies': [
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:*',
+ '../tools/telemetry/telemetry.gyp:*',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_dbg_tsan_mac',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_unittests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../net/net.gyp:net_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_dbg_valgrind_mac',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_unittests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../net/net.gyp:net_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ ], # targets
+ }], # OS="mac"
+ ['OS=="win"', {
+ 'targets': [
+ # These targets are here so the build bots can use them to build
+ # subsets of a full tree for faster cycle times.
+ {
+ 'target_name': 'chromium_builder',
+ 'type': 'none',
+ 'dependencies': [
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:crash_service',
+ '../chrome/chrome.gyp:gcapi_test',
+ '../chrome/chrome.gyp:installer_util_unittests',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../chrome/chrome.gyp:setup_unittests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../content/content_shell_and_tests.gyp:copy_test_netscape_plugin',
+ # ../chrome/test/mini_installer requires mini_installer.
+ '../chrome/installer/mini_installer.gyp:mini_installer',
+ '../courgette/courgette.gyp:courgette_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:*',
+ '../tools/telemetry/telemetry.gyp:*',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/events/events.gyp:events_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ '../ui/views/views.gyp:views_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ 'conditions': [
+ ['target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_dbg_tsan_win',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_unittests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../net/net.gyp:net_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_lkgr_drmemory_win',
+ 'type': 'none',
+ 'dependencies': [
+ '../components/test_runner/test_runner.gyp:layout_test_helper',
+ '../content/content_shell_and_tests.gyp:content_shell',
+ '../content/content_shell_and_tests.gyp:content_shell_crash_service',
+ ],
+ },
+ {
+ 'target_name': 'chromium_builder_dbg_drmemory_win',
+ 'type': 'none',
+ 'dependencies': [
+ '../ash/ash.gyp:ash_shell_unittests',
+ '../ash/ash.gyp:ash_unittests',
+ '../base/base.gyp:base_unittests',
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:chrome_app_unittests',
+ '../chrome/chrome.gyp:chromedriver_unittests',
+ '../chrome/chrome.gyp:installer_util_unittests',
+ '../chrome/chrome.gyp:setup_unittests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../chrome_elf/chrome_elf.gyp:chrome_elf_unittests',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../components/test_runner/test_runner.gyp:layout_test_helper',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_shell',
+ '../content/content_shell_and_tests.gyp:content_shell_crash_service',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../courgette/courgette.gyp:courgette_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../extensions/extensions_tests.gyp:extensions_browsertests',
+ '../extensions/extensions_tests.gyp:extensions_unittests',
+ '../gin/gin.gyp:gin_shell',
+ '../gin/gin.gyp:gin_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../google_apis/google_apis.gyp:google_apis_unittests',
+ '../gpu/gpu.gyp:angle_unittests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/cast/cast.gyp:cast_unittests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../mojo/mojo.gyp:mojo',
+ '../net/net.gyp:net_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_heap_unittests',
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_platform_unittests',
+ '../ui/accessibility/accessibility.gyp:accessibility_unittests',
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ '../ui/aura/aura.gyp:aura_unittests',
+ '../ui/compositor/compositor.gyp:compositor_unittests',
+ '../ui/display/display.gyp:display_unittests',
+ '../ui/events/events.gyp:events_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../ui/keyboard/keyboard.gyp:keyboard_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ ], # targets
+ 'conditions': [
+ ['branding=="Chrome"', {
+ 'targets': [
+ {
+ 'target_name': 'chrome_official_builder_no_unittests',
+ 'type': 'none',
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ '../chrome/chrome.gyp:gcapi_dll',
+ '../chrome/chrome.gyp:pack_policy_templates',
+ '../chrome/installer/mini_installer.gyp:mini_installer',
+ '../cloud_print/cloud_print.gyp:cloud_print',
+ '../courgette/courgette.gyp:courgette',
+ '../courgette/courgette.gyp:courgette64',
+ '../remoting/remoting.gyp:remoting_webapp',
+ '../third_party/widevine/cdm/widevine_cdm.gyp:widevinecdmadapter',
+ ],
+ 'conditions': [
+ ['target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ['component != "shared_library" and wix_exists == "True" and \
+ sas_dll_exists == "True"', {
+ 'dependencies': [
+ '../remoting/remoting.gyp:remoting_host_installation',
+ ],
+ }], # component != "shared_library"
+ ]
+ }, {
+ 'target_name': 'chrome_official_builder',
+ 'type': 'none',
+ 'dependencies': [
+ 'chrome_official_builder_no_unittests',
+ '../base/base.gyp:base_unittests',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../net/net.gyp:net_unittests',
+ '../printing/printing.gyp:printing_unittests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ '../ui/views/views.gyp:views_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ },
+ ], # targets
+ }], # branding=="Chrome"
+ ], # conditions
+ }], # OS="win"
+ ['chromeos==1', {
+ 'targets': [
+ {
+ 'target_name': 'chromiumos_preflight',
+ 'type': 'none',
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ '../chrome/chrome.gyp:chrome',
+ '../chrome/chrome.gyp:chromedriver',
+ '../content/content_shell_and_tests.gyp:video_decode_accelerator_unittest',
+ '../content/content_shell_and_tests.gyp:video_encode_accelerator_unittest',
+ '../media/media.gyp:media_unittests',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_video_decode',
+ '../sandbox/sandbox.gyp:chrome_sandbox',
+ '../sandbox/sandbox.gyp:sandbox_linux_unittests',
+ '../third_party/mesa/mesa.gyp:osmesa',
+ '../tools/telemetry/telemetry.gyp:bitmaptools#host',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:clear_system_cache',
+ ],
+ 'conditions': [
+ ['disable_nacl==0', {
+ 'dependencies': [
+ '../components/nacl.gyp:nacl_helper',
+ '../native_client/src/trusted/service_runtime/linux/nacl_bootstrap.gyp:nacl_helper_bootstrap',
+ ],
+ }],
+ ],
+ },
+ ], # targets
+ }], # "chromeos==1"
+ ['use_aura==1', {
+ 'targets': [
+ {
+ 'target_name': 'aura_builder',
+ 'type': 'none',
+ 'dependencies': [
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../remoting/remoting.gyp:remoting_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../ui/app_list/app_list.gyp:*',
+ '../ui/aura/aura.gyp:*',
+ '../ui/aura_extra/aura_extra.gyp:*',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/compositor/compositor.gyp:*',
+ '../ui/display/display.gyp:display_unittests',
+ '../ui/events/events.gyp:*',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../ui/keyboard/keyboard.gyp:*',
+ '../ui/snapshot/snapshot.gyp:snapshot_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ '../ui/wm/wm.gyp:*',
+ 'blink_tests',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service',
+ ],
+ }],
+ ['OS=="win" and target_arch=="ia32"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:crash_service_win64',
+ ],
+ }],
+ ['use_ash==1', {
+ 'dependencies': [
+ '../ash/ash.gyp:ash_shell',
+ '../ash/ash.gyp:ash_unittests',
+ ],
+ }],
+ ['OS=="linux"', {
+ # Tests that currently only work on Linux.
+ 'dependencies': [
+ '../base/base.gyp:base_unittests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:sync_unit_tests',
+ ],
+ }],
+ ['chromeos==1', {
+ 'dependencies': [
+ '../chromeos/chromeos.gyp:chromeos_unittests',
+ '../ui/chromeos/ui_chromeos.gyp:ui_chromeos_unittests',
+ ],
+ }],
+ ['use_ozone==1', {
+ 'dependencies': [
+ '../ui/ozone/ozone.gyp:*',
+ '../ui/ozone/demo/ozone_demos.gyp:*',
+ ],
+ }],
+ ['chromecast==0', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:chrome',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../ui/message_center/message_center.gyp:*',
+ '../ui/views/examples/examples.gyp:views_examples_with_content_exe',
+ '../ui/views/views.gyp:views',
+ '../ui/views/views.gyp:views_unittests',
+ ],
+ }],
+ ],
+ },
+ ], # targets
+ }], # "use_aura==1"
+ ['test_isolation_mode != "noop"', {
+ 'targets': [
+ {
+ 'target_name': 'chromium_swarm_tests',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_unittests_run',
+ '../content/content_shell_and_tests.gyp:content_browsertests_run',
+ '../content/content_shell_and_tests.gyp:content_unittests_run',
+ '../net/net.gyp:net_unittests_run',
+ ],
+ 'conditions': [
+ ['chromecast==0', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:browser_tests_run',
+ '../chrome/chrome.gyp:interactive_ui_tests_run',
+ '../chrome/chrome.gyp:sync_integration_tests_run',
+ '../chrome/chrome.gyp:unit_tests_run',
+ ],
+ }],
+ ],
+ }, # target_name: chromium_swarm_tests
+ ],
+ }],
+ ['archive_chromoting_tests==1', {
+ 'targets': [
+ {
+ 'target_name': 'chromoting_swarm_tests',
+ 'type': 'none',
+ 'dependencies': [
+ '../testing/chromoting/integration_tests.gyp:*',
+ ],
+ }, # target_name: chromoting_swarm_tests
+ ]
+ }],
+ ['OS=="mac" and toolkit_views==1', {
+ 'targets': [
+ {
+ 'target_name': 'macviews_builder',
+ 'type': 'none',
+ 'dependencies': [
+ '../ui/views/examples/examples.gyp:views_examples_with_content_exe',
+ '../ui/views/views.gyp:views',
+ '../ui/views/views.gyp:views_unittests',
+ ],
+ }, # target_name: macviews_builder
+ ], # targets
+ }], # os=='mac' and toolkit_views==1
+ ], # conditions
+}
diff --git a/build/android/AndroidManifest.xml b/build/android/AndroidManifest.xml
new file mode 100644
index 00000000000..f27872eee47
--- /dev/null
+++ b/build/android/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
new file mode 100644
index 00000000000..d90ad70abe6
--- /dev/null
+++ b/build/android/BUILD.gn
@@ -0,0 +1,56 @@
+# Copyright 2014 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.
+
+import("//build/config/android/rules.gni")
+
+sun_tools_jar_path = "$root_gen_dir/sun_tools_jar/tools.jar"
+
+action("find_sun_tools_jar") {
+ script = "//build/android/gyp/find_sun_tools_jar.py"
+ depfile = "$target_gen_dir/$target_name.d"
+ outputs = [
+ depfile,
+ sun_tools_jar_path,
+ ]
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--output",
+ rebase_path(sun_tools_jar_path, root_build_dir),
+ ]
+}
+
+java_prebuilt("sun_tools_java") {
+ jar_path = sun_tools_jar_path
+ jar_dep = ":find_sun_tools_jar"
+}
+
+action("cpplib_stripped") {
+ _strip_bin = "${android_tool_prefix}strip"
+ _soname = "libc++_shared.so"
+ _input_so = "${android_libcpp_root}/libs/${android_app_abi}/${_soname}"
+ _output_so = "${root_out_dir}/lib.stripped/${_soname}"
+
+ script = "//build/gn_run_binary.py"
+ inputs = [
+ _strip_bin,
+ ]
+ sources = [
+ _input_so,
+ ]
+ outputs = [
+ _output_so,
+ ]
+
+ _rebased_strip_bin = rebase_path(_strip_bin, root_out_dir)
+ _rebased_input_so = rebase_path(_input_so, root_out_dir)
+ _rebased_output_so = rebase_path(_output_so, root_out_dir)
+ args = [
+ _rebased_strip_bin,
+ "--strip-unneeded",
+ "-o",
+ _rebased_output_so,
+ _rebased_input_so,
+ ]
+}
diff --git a/build/android/CheckInstallApk-debug.apk b/build/android/CheckInstallApk-debug.apk
new file mode 100644
index 00000000000..3dc31910a53
Binary files /dev/null and b/build/android/CheckInstallApk-debug.apk differ
diff --git a/build/android/OWNERS b/build/android/OWNERS
new file mode 100644
index 00000000000..9a5d2701f1d
--- /dev/null
+++ b/build/android/OWNERS
@@ -0,0 +1,3 @@
+jbudorick@chromium.org
+klundberg@chromium.org
+pasko@chromium.org
diff --git a/build/android/PRESUBMIT.py b/build/android/PRESUBMIT.py
new file mode 100644
index 00000000000..6e0a3de8846
--- /dev/null
+++ b/build/android/PRESUBMIT.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2013 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.
+
+"""Presubmit script for android buildbot.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ output = []
+
+ def J(*dirs):
+ """Returns a path relative to presubmit directory."""
+ return input_api.os_path.join(input_api.PresubmitLocalPath(), *dirs)
+
+ output.extend(input_api.canned_checks.RunPylint(
+ input_api,
+ output_api,
+ black_list=[r'pylib/symbols/.*\.py$', r'gyp/.*\.py$', r'gn/.*\.py'],
+ extra_paths_list=[
+ J(), J('..', '..', 'third_party', 'android_testrunner'),
+ J('buildbot')]))
+ output.extend(input_api.canned_checks.RunPylint(
+ input_api,
+ output_api,
+ white_list=[r'gyp/.*\.py$', r'gn/.*\.py'],
+ extra_paths_list=[J('gyp'), J('gn')]))
+
+ # Disabled due to http://crbug.com/410936
+ #output.extend(input_api.canned_checks.RunUnitTestsInDirectory(
+ #input_api, output_api, J('buildbot', 'tests')))
+
+ pylib_test_env = dict(input_api.environ)
+ pylib_test_env.update({
+ 'PYTHONPATH': input_api.PresubmitLocalPath(),
+ 'PYTHONDONTWRITEBYTECODE': '1',
+ })
+ output.extend(input_api.canned_checks.RunUnitTests(
+ input_api,
+ output_api,
+ unit_tests=[
+ J('pylib', 'base', 'test_dispatcher_unittest.py'),
+ J('pylib', 'device', 'battery_utils_test.py'),
+ J('pylib', 'device', 'device_utils_test.py'),
+ J('pylib', 'device', 'logcat_monitor_test.py'),
+ J('pylib', 'gtest', 'gtest_test_instance_test.py'),
+ J('pylib', 'instrumentation',
+ 'instrumentation_test_instance_test.py'),
+ J('pylib', 'results', 'json_results_test.py'),
+ J('pylib', 'utils', 'md5sum_test.py'),
+ ],
+ env=pylib_test_env))
+ return output
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
diff --git a/build/android/adb_android_webview_command_line b/build/android/adb_android_webview_command_line
new file mode 100755
index 00000000000..791e27069e2
--- /dev/null
+++ b/build/android/adb_android_webview_command_line
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (c) 2013 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.
+
+# If no flags are given, prints the current content shell flags.
+#
+# Otherwise, the given flags are used to REPLACE (not modify) the content shell
+# flags. For example:
+# adb_android_webview_command_line --enable-webgl
+#
+# To remove all content shell flags, pass an empty string for the flags:
+# adb_android_webview_command_line ""
+
+. $(dirname $0)/adb_command_line_functions.sh
+CMD_LINE_FILE=/data/local/tmp/android-webview-command-line
+REQUIRES_SU=0
+set_command_line "$@"
+
diff --git a/build/android/adb_chrome_public_command_line b/build/android/adb_chrome_public_command_line
new file mode 100755
index 00000000000..9bf91c6ace7
--- /dev/null
+++ b/build/android/adb_chrome_public_command_line
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# If no flags are given, prints the current Chrome flags.
+#
+# Otherwise, the given flags are used to REPLACE (not modify) the Chrome
+# flags. For example:
+# adb_chrome_public_command_line --enable-webgl
+#
+# To remove all Chrome flags, pass an empty string for the flags:
+# adb_chrome_public_command_line ""
+
+. $(dirname $0)/adb_command_line_functions.sh
+CMD_LINE_FILE=/data/local/chrome-command-line
+REQUIRES_SU=1
+set_command_line "$@"
diff --git a/build/android/adb_chrome_shell_command_line b/build/android/adb_chrome_shell_command_line
new file mode 100755
index 00000000000..750f9060365
--- /dev/null
+++ b/build/android/adb_chrome_shell_command_line
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2014 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.
+
+# If no flags are given, prints the current chrome shell flags.
+#
+# Otherwise, the given flags are used to REPLACE (not modify) the chrome shell
+# flags. For example:
+# adb_chrome_shell_command_line --enable-webgl
+#
+# To remove all chrome shell flags, pass an empty string for the flags:
+# adb_chrome_shell_command_line ""
+
+. $(dirname $0)/adb_command_line_functions.sh
+CMD_LINE_FILE=/data/local/tmp/chrome-shell-command-line
+REQUIRES_SU=0
+set_command_line "$@"
+
diff --git a/build/android/adb_command_line_functions.sh b/build/android/adb_command_line_functions.sh
new file mode 100755
index 00000000000..7ea98b09be6
--- /dev/null
+++ b/build/android/adb_command_line_functions.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# Variables must be set before calling:
+# CMD_LINE_FILE - Path on device to flags file.
+# REQUIRES_SU - Set to 1 if path requires root.
+function set_command_line() {
+ SU_CMD=""
+ if [[ "$REQUIRES_SU" = 1 ]]; then
+ # Older androids accept "su -c", while newer use "su uid".
+ SDK_LEVEL=$(adb shell getprop ro.build.version.sdk | tr -d '\r')
+ # E.g. if no device connected.
+ if [[ -z "$SDK_LEVEL" ]]; then
+ exit 1
+ fi
+ SU_CMD="su -c"
+ if (( $SDK_LEVEL >= 21 )); then
+ SU_CMD="su 0"
+ fi
+ fi
+
+ if [ $# -eq 0 ] ; then
+ # If nothing specified, print the command line (stripping off "chrome ")
+ adb shell "cat $CMD_LINE_FILE 2>/dev/null" | cut -d ' ' -s -f2-
+ elif [ $# -eq 1 ] && [ "$1" = '' ] ; then
+ # If given an empty string, delete the command line.
+ set -x
+ adb shell $SU_CMD rm $CMD_LINE_FILE >/dev/null
+ else
+ # Else set it.
+ set -x
+ adb shell "echo 'chrome $*' | $SU_CMD dd of=$CMD_LINE_FILE"
+ # Prevent other apps from modifying flags (this can create security issues).
+ adb shell $SU_CMD chmod 0664 $CMD_LINE_FILE
+ fi
+}
+
diff --git a/build/android/adb_content_shell_command_line b/build/android/adb_content_shell_command_line
new file mode 100755
index 00000000000..2ac7ece75c7
--- /dev/null
+++ b/build/android/adb_content_shell_command_line
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+
+# If no flags are given, prints the current content shell flags.
+#
+# Otherwise, the given flags are used to REPLACE (not modify) the content shell
+# flags. For example:
+# adb_content_shell_command_line --enable-webgl
+#
+# To remove all content shell flags, pass an empty string for the flags:
+# adb_content_shell_command_line ""
+
+. $(dirname $0)/adb_command_line_functions.sh
+CMD_LINE_FILE=/data/local/tmp/content-shell-command-line
+REQUIRES_SU=0
+set_command_line "$@"
+
diff --git a/build/android/adb_device_functions.sh b/build/android/adb_device_functions.sh
new file mode 100755
index 00000000000..66cc32fc4e3
--- /dev/null
+++ b/build/android/adb_device_functions.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+#
+# A collection of functions useful for maintaining android devices
+
+
+# Run an adb command on all connected device in parallel.
+# Usage: adb_all command line to eval. Quoting is optional.
+#
+# Examples:
+# adb_all install Chrome.apk
+# adb_all 'shell cat /path/to/file'
+#
+adb_all() {
+ if [[ $# == 0 ]]; then
+ echo "Usage: adb_all . Quoting is optional."
+ echo "Example: adb_all install Chrome.apk"
+ return 1
+ fi
+ local DEVICES=$(adb_get_devices -b)
+ local NUM_DEVICES=$(echo $DEVICES | wc -w)
+ if (( $NUM_DEVICES > 1 )); then
+ echo "Looping over $NUM_DEVICES devices"
+ fi
+ _adb_multi "$DEVICES" "$*"
+}
+
+
+# Run a command on each connected device. Quoting the command is suggested but
+# not required. The script setups up variable DEVICE to correspond to the
+# current serial number. Intended for complex one_liners that don't work in
+# adb_all
+# Usage: adb_device_loop 'command line to eval'
+adb_device_loop() {
+ if [[ $# == 0 ]]; then
+ echo "Intended for more complex one-liners that cannot be done with" \
+ "adb_all."
+ echo 'Usage: adb_device_loop "echo $DEVICE: $(adb root &&' \
+ 'adb shell cat /data/local.prop)"'
+ return 1
+ fi
+ local DEVICES=$(adb_get_devices)
+ if [[ -z $DEVICES ]]; then
+ return
+ fi
+ # Do not change DEVICE variable name - part of api
+ for DEVICE in $DEVICES; do
+ DEV_TYPE=$(adb -s $DEVICE shell getprop ro.product.device | sed 's/\r//')
+ echo "Running on $DEVICE ($DEV_TYPE)"
+ ANDROID_SERIAL=$DEVICE eval "$*"
+ done
+}
+
+# Erases data from any devices visible on adb. To preserve a device,
+# disconnect it or:
+# 1) Reboot it into fastboot with 'adb reboot bootloader'
+# 2) Run wipe_all_devices to wipe remaining devices
+# 3) Restore device it with 'fastboot reboot'
+#
+# Usage: wipe_all_devices [-f]
+#
+wipe_all_devices() {
+ if [[ -z $(which adb) || -z $(which fastboot) ]]; then
+ echo "aborting: adb and fastboot not in path"
+ return 1
+ elif ! $(groups | grep -q 'plugdev'); then
+ echo "If fastboot fails, run: 'sudo adduser $(whoami) plugdev'"
+ fi
+
+ local DEVICES=$(adb_get_devices -b)
+
+ if [[ $1 != '-f' ]]; then
+ echo "This will ERASE ALL DATA from $(echo $DEVICES | wc -w) device."
+ read -p "Hit enter to continue"
+ fi
+
+ _adb_multi "$DEVICES" "reboot bootloader"
+ # Subshell to isolate job list
+ (
+ for DEVICE in $DEVICES; do
+ fastboot_erase $DEVICE &
+ done
+ wait
+ )
+
+ # Reboot devices together
+ for DEVICE in $DEVICES; do
+ fastboot -s $DEVICE reboot
+ done
+}
+
+# Wipe a device in fastboot.
+# Usage fastboot_erase [serial]
+fastboot_erase() {
+ if [[ -n $1 ]]; then
+ echo "Wiping $1"
+ local SERIAL="-s $1"
+ else
+ if [ -z $(fastboot devices) ]; then
+ echo "No devices in fastboot, aborting."
+ echo "Check out wipe_all_devices to see if sufficient"
+ echo "You can put a device in fastboot using adb reboot bootloader"
+ return 1
+ fi
+ local SERIAL=""
+ fi
+ fastboot $SERIAL erase cache
+ fastboot $SERIAL erase userdata
+}
+
+# Get list of devices connected via adb
+# Args: -b block until adb detects a device
+adb_get_devices() {
+ local DEVICES="$(adb devices | grep 'device$')"
+ if [[ -z $DEVICES && $1 == '-b' ]]; then
+ echo '- waiting for device -' >&2
+ local DEVICES="$(adb wait-for-device devices | grep 'device$')"
+ fi
+ echo "$DEVICES" | awk -vORS=' ' '{print $1}' | sed 's/ $/\n/'
+}
+
+###################################################
+## HELPER FUNCTIONS
+###################################################
+
+# Run an adb command in parallel over a device list
+_adb_multi() {
+ local DEVICES=$1
+ local ADB_ARGS=$2
+ (
+ for DEVICE in $DEVICES; do
+ adb -s $DEVICE $ADB_ARGS &
+ done
+ wait
+ )
+}
diff --git a/build/android/adb_gdb b/build/android/adb_gdb
new file mode 100755
index 00000000000..65ec7b20b87
--- /dev/null
+++ b/build/android/adb_gdb
@@ -0,0 +1,1047 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+#
+
+# A generic script used to attach to a running Chromium process and
+# debug it. Most users should not use this directly, but one of the
+# wrapper scripts like adb_gdb_content_shell
+#
+# Use --help to print full usage instructions.
+#
+
+PROGNAME=$(basename "$0")
+PROGDIR=$(dirname "$0")
+
+# Location of Chromium-top-level sources.
+CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null)
+
+# Location of Chromium out/ directory.
+if [ -z "$CHROMIUM_OUT_DIR" ]; then
+ CHROMIUM_OUT_DIR=out
+fi
+
+TMPDIR=
+GDBSERVER_PIDFILE=
+TARGET_GDBSERVER=
+COMMAND_PREFIX=
+
+clean_exit () {
+ if [ "$TMPDIR" ]; then
+ GDBSERVER_PID=$(cat $GDBSERVER_PIDFILE 2>/dev/null)
+ if [ "$GDBSERVER_PID" ]; then
+ log "Killing background gdbserver process: $GDBSERVER_PID"
+ kill -9 $GDBSERVER_PID >/dev/null 2>&1
+ fi
+ if [ "$TARGET_GDBSERVER" ]; then
+ log "Removing target gdbserver binary: $TARGET_GDBSERVER."
+ "$ADB" shell "$COMMAND_PREFIX" rm "$TARGET_GDBSERVER" >/dev/null 2>&1
+ fi
+ log "Cleaning up: $TMPDIR"
+ rm -rf "$TMPDIR"
+ fi
+ trap "" EXIT
+ exit $1
+}
+
+# Ensure clean exit on Ctrl-C or normal exit.
+trap "clean_exit 1" INT HUP QUIT TERM
+trap "clean_exit \$?" EXIT
+
+panic () {
+ echo "ERROR: $@" >&2
+ exit 1
+}
+
+fail_panic () {
+ if [ $? != 0 ]; then panic "$@"; fi
+}
+
+log () {
+ if [ "$VERBOSE" -gt 0 ]; then
+ echo "$@"
+ fi
+}
+
+DEFAULT_PULL_LIBS_DIR=/tmp/$USER-adb-gdb-libs
+
+# NOTE: Allow wrapper scripts to set various default through ADB_GDB_XXX
+# environment variables. This is only for cosmetic reasons, i.e. to
+# display proper
+
+# Allow wrapper scripts to set the default activity through
+# the ADB_GDB_ACTIVITY variable. Users are still able to change the
+# final activity name through --activity= option.
+#
+# This is only for cosmetic reasons, i.e. to display the proper default
+# in the --help output.
+#
+DEFAULT_ACTIVITY=${ADB_GDB_ACTIVITY:-".Main"}
+
+# Allow wrapper scripts to set the program name through ADB_GDB_PROGNAME
+PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")}
+
+ACTIVITY=$DEFAULT_ACTIVITY
+ADB=
+ANNOTATE=
+# Note: Ignore BUILDTYPE variable, because the Ninja build doesn't use it.
+BUILDTYPE=
+FORCE=
+GDBEXEPOSTFIX=gdb
+GDBINIT=
+GDBSERVER=
+HELP=
+NDK_DIR=
+NO_PULL_LIBS=
+PACKAGE_NAME=
+PID=
+PORT=
+PRIVILEGED=
+PRIVILEGED_INDEX=
+PROGRAM_NAME="activity"
+PULL_LIBS=
+PULL_LIBS_DIR=
+SANDBOXED=
+SANDBOXED_INDEX=
+START=
+SU_PREFIX=
+SYMBOL_DIR=
+TARGET_ARCH=
+TOOLCHAIN=
+VERBOSE=0
+
+for opt; do
+ optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)')
+ case $opt in
+ --adb=*)
+ ADB=$optarg
+ ;;
+ --activity=*)
+ ACTIVITY=$optarg
+ ;;
+ --annotate=3)
+ ANNOTATE=$optarg
+ ;;
+ --force)
+ FORCE=true
+ ;;
+ --gdbserver=*)
+ GDBSERVER=$optarg
+ ;;
+ --gdb=*)
+ GDB=$optarg
+ ;;
+ --help|-h|-?)
+ HELP=true
+ ;;
+ --ndk-dir=*)
+ NDK_DIR=$optarg
+ ;;
+ --no-pull-libs)
+ NO_PULL_LIBS=true
+ ;;
+ --package-name=*)
+ PACKAGE_NAME=$optarg
+ ;;
+ --pid=*)
+ PID=$optarg
+ ;;
+ --port=*)
+ PORT=$optarg
+ ;;
+ --privileged)
+ PRIVILEGED=true
+ ;;
+ --privileged=*)
+ PRIVILEGED=true
+ PRIVILEGED_INDEX=$optarg
+ ;;
+ --program-name=*)
+ PROGRAM_NAME=$optarg
+ ;;
+ --pull-libs)
+ PULL_LIBS=true
+ ;;
+ --pull-libs-dir=*)
+ PULL_LIBS_DIR=$optarg
+ ;;
+ --sandboxed)
+ SANDBOXED=true
+ ;;
+ --sandboxed=*)
+ SANDBOXED=true
+ SANDBOXED_INDEX=$optarg
+ ;;
+ --script=*)
+ GDBINIT=$optarg
+ ;;
+ --start)
+ START=true
+ ;;
+ --su-prefix=*)
+ SU_PREFIX=$optarg
+ ;;
+ --symbol-dir=*)
+ SYMBOL_DIR=$optarg
+ ;;
+ --out-dir=*)
+ CHROMIUM_OUT_DIR=$optarg
+ ;;
+ --target-arch=*)
+ TARGET_ARCH=$optarg
+ ;;
+ --toolchain=*)
+ TOOLCHAIN=$optarg
+ ;;
+ --ui)
+ GDBEXEPOSTFIX=gdbtui
+ ;;
+ --verbose)
+ VERBOSE=$(( $VERBOSE + 1 ))
+ ;;
+ --debug)
+ BUILDTYPE=Debug
+ ;;
+ --release)
+ BUILDTYPE=Release
+ ;;
+ -*)
+ panic "Unknown option $OPT, see --help." >&2
+ ;;
+ *)
+ if [ "$PACKAGE_NAME" ]; then
+ panic "You can only provide a single package name as argument!\
+ See --help."
+ fi
+ PACKAGE_NAME=$opt
+ ;;
+ esac
+done
+
+print_help_options () {
+ cat <]
+
+Attach gdb to a running Android $PROGRAM_NAME process.
+
+If provided, must be the name of the Android application's
+package name to be debugged. You can also use --package-name= to
+specify it.
+EOF
+ fi
+
+ cat < option) or a privileged (--privileged or
+--privileged=) service.
+
+This script needs several things to work properly. It will try to pick
+them up automatically for you though:
+
+ - target gdbserver binary
+ - host gdb client (e.g. arm-linux-androideabi-gdb)
+ - directory with symbolic version of $PROGRAM_NAME's shared libraries.
+
+You can also use --ndk-dir= to specify an alternative NDK installation
+directory.
+
+The script tries to find the most recent version of the debug version of
+shared libraries under one of the following directories:
+
+ \$CHROMIUM_SRC//Release/lib/ (used by Ninja builds)
+ \$CHROMIUM_SRC//Debug/lib/ (used by Ninja builds)
+ \$CHROMIUM_SRC//Release/lib.target/ (used by Make builds)
+ \$CHROMIUM_SRC//Debug/lib.target/ (used by Make builds)
+
+Where is 'out' by default, unless the --out= option is used or
+the CHROMIUM_OUT_DIR environment variable is defined.
+
+You can restrict this search by using --release or --debug to specify the
+build type, or simply use --symbol-dir= to specify the file manually.
+
+The script tries to extract the target architecture from your target device,
+but if this fails, will default to 'arm'. Use --target-arch= to force
+its value.
+
+Otherwise, the script will complain, but you can use the --gdbserver,
+--gdb and --symbol-lib options to specify everything manually.
+
+An alternative to --gdb= is to use --toollchain= to specify
+the path to the host target-specific cross-toolchain.
+
+You will also need the 'adb' tool in your path. Otherwise, use the --adb
+option. The script will complain if there is more than one device connected
+and ANDROID_SERIAL is not defined.
+
+The first time you use it on a device, the script will pull many system
+libraries required by the process into a temporary directory. This
+is done to strongly improve the debugging experience, like allowing
+readable thread stacks and more. The libraries are copied to the following
+directory by default:
+
+ $DEFAULT_PULL_LIBS_DIR/
+
+But you can use the --pull-libs-dir= option to specify an
+alternative. The script can detect when you change the connected device,
+and will re-pull the libraries only in this case. You can however force it
+with the --pull-libs option.
+
+Any local .gdbinit script will be ignored, but it is possible to pass a
+gdb command script with the --script= option. Note that its commands
+will be passed to gdb after the remote connection and library symbol
+loading have completed.
+
+Valid options:
+ --help|-h|-? Print this message.
+ --verbose Increase verbosity.
+
+ --sandboxed Debug first sandboxed process we find.
+ --sandboxed= Debug specific sandboxed process.
+ --symbol-dir= Specify directory with symbol shared libraries.
+ --out-dir= Specify the out directory.
+ --package-name= Specify package name (alternative to 1st argument).
+ --privileged Debug first privileged process we find.
+ --privileged= Debug specific privileged process.
+ --program-name= Specify program name (cosmetic only).
+ --pid= Specify application process pid.
+ --force Kill any previous debugging session, if any.
+ --start Start package's activity on device.
+ --ui Use gdbtui instead of gdb
+ --activity= Activity name for --start [$DEFAULT_ACTIVITY].
+ --annotate= Enable gdb annotation.
+ --script= Specify extra GDB init script.
+
+ --gdbserver= Specify target gdbserver binary.
+ --gdb= Specify host gdb client binary.
+ --target-arch= Specify NDK target arch.
+ --adb= Specify host ADB binary.
+ --port= Specify the tcp port to use.
+
+ --su-prefix= Prepend to 'adb shell' commands that are
+ run by this script. This can be useful to use
+ the 'su' program on rooted production devices.
+ e.g. --su-prefix="su -c"
+
+ --pull-libs Force system libraries extraction.
+ --no-pull-libs Do not extract any system library.
+ --libs-dir= Specify system libraries extraction directory.
+
+ --debug Use libraries under out/Debug.
+ --release Use libraries under out/Release.
+
+EOF
+ exit 0
+fi
+
+if [ -z "$PACKAGE_NAME" ]; then
+ panic "Please specify a package name on the command line. See --help."
+fi
+
+if [ -z "$NDK_DIR" ]; then
+ ANDROID_NDK_ROOT=$(PYTHONPATH=$CHROMIUM_SRC/build/android python -c \
+'from pylib.constants import ANDROID_NDK_ROOT; print ANDROID_NDK_ROOT,')
+else
+ if [ ! -d "$NDK_DIR" ]; then
+ panic "Invalid directory: $NDK_DIR"
+ fi
+ if [ ! -f "$NDK_DIR/ndk-build" ]; then
+ panic "Not a valid NDK directory: $NDK_DIR"
+ fi
+ ANDROID_NDK_ROOT=$NDK_DIR
+fi
+
+if [ "$GDBINIT" -a ! -f "$GDBINIT" ]; then
+ panic "Unknown --script file: $GDBINIT"
+fi
+
+# Check that ADB is in our path
+if [ -z "$ADB" ]; then
+ ADB=$(which adb 2>/dev/null)
+ if [ -z "$ADB" ]; then
+ panic "Can't find 'adb' tool in your path. Install it or use \
+--adb="
+ fi
+ log "Auto-config: --adb=$ADB"
+fi
+
+# Check that it works minimally
+ADB_VERSION=$($ADB version 2>/dev/null)
+echo "$ADB_VERSION" | fgrep -q -e "Android Debug Bridge"
+if [ $? != 0 ]; then
+ panic "Your 'adb' tool seems invalid, use --adb= to specify a \
+different one: $ADB"
+fi
+
+# If there are more than one device connected, and ANDROID_SERIAL is not
+# defined, print an error message.
+NUM_DEVICES_PLUS2=$($ADB devices 2>/dev/null | wc -l)
+if [ "$NUM_DEVICES_PLUS2" -lt 3 -a -z "$ANDROID_SERIAL" ]; then
+ echo "ERROR: There is more than one Android device connected to ADB."
+ echo "Please define ANDROID_SERIAL to specify which one to use."
+ exit 1
+fi
+
+# Run a command through adb shell, strip the extra \r from the output
+# and return the correct status code to detect failures. This assumes
+# that the adb shell command prints a final \n to stdout.
+# $1+: command to run
+# Out: command's stdout
+# Return: command's status
+# Note: the command's stderr is lost
+adb_shell () {
+ local TMPOUT="$(mktemp)"
+ local LASTLINE RET
+ local ADB=${ADB:-adb}
+
+ # The weird sed rule is to strip the final \r on each output line
+ # Since 'adb shell' never returns the command's proper exit/status code,
+ # we force it to print it as '%%' in the temporary output file,
+ # which we will later strip from it.
+ $ADB shell $@ ";" echo "%%\$?" 2>/dev/null | \
+ sed -e 's![[:cntrl:]]!!g' > $TMPOUT
+ # Get last line in log, which contains the exit code from the command
+ LASTLINE=$(sed -e '$!d' $TMPOUT)
+ # Extract the status code from the end of the line, which must
+ # be '%%'.
+ RET=$(echo "$LASTLINE" | \
+ awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }')
+ # Remove the status code from the last line. Note that this may result
+ # in an empty line.
+ LASTLINE=$(echo "$LASTLINE" | \
+ awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }')
+ # The output itself: all lines except the status code.
+ sed -e '$d' $TMPOUT && printf "%s" "$LASTLINE"
+ # Remove temp file.
+ rm -f $TMPOUT
+ # Exit with the appropriate status.
+ return $RET
+}
+
+# Find the target architecture from the target device.
+# This returns an NDK-compatible architecture name.
+# out: NDK Architecture name, or empty string.
+get_gyp_target_arch () {
+ local ARCH=$(adb_shell getprop ro.product.cpu.abi)
+ case $ARCH in
+ mips|x86|x86_64) echo "$ARCH";;
+ arm64*) echo "arm64";;
+ arm*) echo "arm";;
+ *) echo "";
+ esac
+}
+
+if [ -z "$TARGET_ARCH" ]; then
+ TARGET_ARCH=$(get_gyp_target_arch)
+ if [ -z "$TARGET_ARCH" ]; then
+ TARGET_ARCH=arm
+ fi
+else
+ # Nit: accept Chromium's 'ia32' as a valid target architecture. This
+ # script prefers the NDK 'x86' name instead because it uses it to find
+ # NDK-specific files (host gdb) with it.
+ if [ "$TARGET_ARCH" = "ia32" ]; then
+ TARGET_ARCH=x86
+ log "Auto-config: --arch=$TARGET_ARCH (equivalent to ia32)"
+ fi
+fi
+
+# Detect the NDK system name, i.e. the name used to identify the host.
+# out: NDK system name (e.g. 'linux' or 'darwin')
+get_ndk_host_system () {
+ local HOST_OS
+ if [ -z "$NDK_HOST_SYSTEM" ]; then
+ HOST_OS=$(uname -s)
+ case $HOST_OS in
+ Linux) NDK_HOST_SYSTEM=linux;;
+ Darwin) NDK_HOST_SYSTEM=darwin;;
+ *) panic "You can't run this script on this system: $HOST_OS";;
+ esac
+ fi
+ echo "$NDK_HOST_SYSTEM"
+}
+
+# Detect the NDK host architecture name.
+# out: NDK arch name (e.g. 'x86' or 'x86_64')
+get_ndk_host_arch () {
+ local HOST_ARCH HOST_OS
+ if [ -z "$NDK_HOST_ARCH" ]; then
+ HOST_OS=$(get_ndk_host_system)
+ HOST_ARCH=$(uname -p)
+ case $HOST_ARCH in
+ i?86) NDK_HOST_ARCH=x86;;
+ x86_64|amd64) NDK_HOST_ARCH=x86_64;;
+ *) panic "You can't run this script on this host architecture: $HOST_ARCH";;
+ esac
+ # Darwin trick: "uname -p" always returns i386 on 64-bit installations.
+ if [ "$HOST_OS" = darwin -a "$NDK_HOST_ARCH" = "x86" ]; then
+ # Use '/usr/bin/file', not just 'file' to avoid buggy MacPorts
+ # implementations of the tool. See http://b.android.com/53769
+ HOST_64BITS=$(/usr/bin/file -L "$SHELL" | grep -e "x86[_-]64")
+ if [ "$HOST_64BITS" ]; then
+ NDK_HOST_ARCH=x86_64
+ fi
+ fi
+ fi
+ echo "$NDK_HOST_ARCH"
+}
+
+# Convert an NDK architecture name into a GNU configure triplet.
+# $1: NDK architecture name (e.g. 'arm')
+# Out: Android GNU configure triplet (e.g. 'arm-linux-androideabi')
+get_arch_gnu_config () {
+ case $1 in
+ arm)
+ echo "arm-linux-androideabi"
+ ;;
+ arm64)
+ echo "aarch64-linux-android"
+ ;;
+ x86)
+ echo "i686-linux-android"
+ ;;
+ x86_64)
+ echo "x86_64-linux-android"
+ ;;
+ mips)
+ echo "mipsel-linux-android"
+ ;;
+ *)
+ echo "$ARCH-linux-android"
+ ;;
+ esac
+}
+
+# Convert an NDK architecture name into a toolchain name prefix
+# $1: NDK architecture name (e.g. 'arm')
+# Out: NDK toolchain name prefix (e.g. 'arm-linux-androideabi')
+get_arch_toolchain_prefix () {
+ # Return the configure triplet, except for x86!
+ if [ "$1" = "x86" ]; then
+ echo "$1"
+ else
+ get_arch_gnu_config $1
+ fi
+}
+
+# Find a NDK toolchain prebuilt file or sub-directory.
+# This will probe the various arch-specific toolchain directories
+# in the NDK for the needed file.
+# $1: NDK install path
+# $2: NDK architecture name
+# $3: prebuilt sub-path to look for.
+# Out: file path, or empty if none is found.
+get_ndk_toolchain_prebuilt () {
+ local NDK_DIR="${1%/}"
+ local ARCH="$2"
+ local SUBPATH="$3"
+ local NAME="$(get_arch_toolchain_prefix $ARCH)"
+ local FILE TARGET
+ FILE=$NDK_DIR/toolchains/$NAME-4.9/prebuilt/$SUBPATH
+ if [ ! -f "$FILE" ]; then
+ FILE=$NDK_DIR/toolchains/$NAME-4.8/prebuilt/$SUBPATH
+ if [ ! -f "$FILE" ]; then
+ FILE=
+ fi
+ fi
+ echo "$FILE"
+}
+
+# Find the path to an NDK's toolchain full prefix for a given architecture
+# $1: NDK install path
+# $2: NDK target architecture name
+# Out: install path + binary prefix (e.g.
+# ".../path/to/bin/arm-linux-androideabi-")
+get_ndk_toolchain_fullprefix () {
+ local NDK_DIR="$1"
+ local ARCH="$2"
+ local TARGET NAME HOST_OS HOST_ARCH GCC CONFIG
+
+ # NOTE: This will need to be updated if the NDK changes the names or moves
+ # the location of its prebuilt toolchains.
+ #
+ GCC=
+ HOST_OS=$(get_ndk_host_system)
+ HOST_ARCH=$(get_ndk_host_arch)
+ CONFIG=$(get_arch_gnu_config $ARCH)
+ GCC=$(get_ndk_toolchain_prebuilt \
+ "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-gcc")
+ if [ -z "$GCC" -a "$HOST_ARCH" = "x86_64" ]; then
+ GCC=$(get_ndk_toolchain_prebuilt \
+ "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-gcc")
+ fi
+ if [ ! -f "$GCC" -a "$ARCH" = "x86" ]; then
+ # Special case, the x86 toolchain used to be incorrectly
+ # named i686-android-linux-gcc!
+ GCC=$(get_ndk_toolchain_prebuilt \
+ "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-gcc")
+ fi
+ if [ -z "$GCC" ]; then
+ panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \
+Please verify your NDK installation!"
+ fi
+ echo "${GCC%%gcc}"
+}
+
+# $1: NDK install path
+# $2: target architecture.
+get_ndk_gdbserver () {
+ local NDK_DIR="$1"
+ local ARCH=$2
+ local BINARY
+
+ # The location has moved after NDK r8
+ BINARY=$NDK_DIR/prebuilt/android-$ARCH/gdbserver/gdbserver
+ if [ ! -f "$BINARY" ]; then
+ BINARY=$(get_ndk_toolchain_prebuilt "$NDK_DIR" "$ARCH" gdbserver)
+ fi
+ echo "$BINARY"
+}
+
+# Check/probe the path to the Android toolchain installation. Always
+# use the NDK versions of gdb and gdbserver. They must match to avoid
+# issues when both binaries do not speak the same wire protocol.
+#
+if [ -z "$TOOLCHAIN" ]; then
+ ANDROID_TOOLCHAIN=$(get_ndk_toolchain_fullprefix \
+ "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
+ ANDROID_TOOLCHAIN=$(dirname "$ANDROID_TOOLCHAIN")
+ log "Auto-config: --toolchain=$ANDROID_TOOLCHAIN"
+else
+ # Be flexible, allow one to specify either the install path or the bin
+ # sub-directory in --toolchain:
+ #
+ if [ -d "$TOOLCHAIN/bin" ]; then
+ TOOLCHAIN=$TOOLCHAIN/bin
+ fi
+ ANDROID_TOOLCHAIN=$TOOLCHAIN
+fi
+
+# Cosmetic: Remove trailing directory separator.
+ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN%/}
+
+# Find host GDB client binary
+if [ -z "$GDB" ]; then
+ GDB=$(which $ANDROID_TOOLCHAIN/*-$GDBEXEPOSTFIX 2>/dev/null | head -1)
+ if [ -z "$GDB" ]; then
+ panic "Can't find Android gdb client in your path, check your \
+--toolchain or --gdb path."
+ fi
+ log "Host gdb client: $GDB"
+fi
+
+# Find gdbserver binary, we will later push it to /data/local/tmp
+# This ensures that both gdbserver and $GDB talk the same binary protocol,
+# otherwise weird problems will appear.
+#
+if [ -z "$GDBSERVER" ]; then
+ GDBSERVER=$(get_ndk_gdbserver "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
+ if [ -z "$GDBSERVER" ]; then
+ panic "Can't find NDK gdbserver binary. use --gdbserver to specify \
+valid one!"
+ fi
+ log "Auto-config: --gdbserver=$GDBSERVER"
+fi
+
+# A unique ID for this script's session. This needs to be the same in all
+# sub-shell commands we're going to launch, so take the PID of the launcher
+# process.
+TMP_ID=$$
+
+# Temporary directory, will get cleaned up on exit.
+TMPDIR=/tmp/$USER-adb-gdb-tmp-$TMP_ID
+mkdir -p "$TMPDIR" && rm -rf "$TMPDIR"/*
+
+GDBSERVER_PIDFILE="$TMPDIR"/gdbserver-$TMP_ID.pid
+
+# If --force is specified, try to kill any gdbserver process started by the
+# same user on the device. Normally, these are killed automatically by the
+# script on exit, but there are a few corner cases where this would still
+# be needed.
+if [ "$FORCE" ]; then
+ GDBSERVER_PIDS=$(adb_shell ps | awk '$9 ~ /gdbserver/ { print $2; }')
+ for GDB_PID in $GDBSERVER_PIDS; do
+ log "Killing previous gdbserver (PID=$GDB_PID)"
+ adb_shell kill -9 $GDB_PID
+ done
+fi
+
+if [ "$START" ]; then
+ log "Starting $PROGRAM_NAME on device."
+ adb_shell am start -n $PACKAGE_NAME/$ACTIVITY 2>/dev/null
+ adb_shell ps | grep -q $PACKAGE_NAME
+ fail_panic "Could not start $PROGRAM_NAME on device. Are you sure the \
+package is installed?"
+fi
+
+# Return the timestamp of a given time, as number of seconds since epoch.
+# $1: file path
+# Out: file timestamp
+get_file_timestamp () {
+ stat -c %Y "$1" 2>/dev/null
+}
+
+# Detect the build type and symbol directory. This is done by finding
+# the most recent sub-directory containing debug shared libraries under
+# $CHROMIUM_SRC/$CHROMIUM_OUT_DIR/
+#
+# $1: $BUILDTYPE value, can be empty
+# Out: nothing, but this sets SYMBOL_DIR
+#
+detect_symbol_dir () {
+ local SUBDIRS SUBDIR LIST DIR DIR_LIBS TSTAMP
+ # Note: Ninja places debug libraries under out/$BUILDTYPE/lib/, while
+ # Make places then under out/$BUILDTYPE/lib.target.
+ if [ "$1" ]; then
+ SUBDIRS="$1/lib $1/lib.target"
+ else
+ SUBDIRS="Release/lib Debug/lib Release/lib.target Debug/lib.target"
+ fi
+ LIST=$TMPDIR/scan-subdirs-$$.txt
+ printf "" > "$LIST"
+ for SUBDIR in $SUBDIRS; do
+ DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
+ if [ -d "$DIR" ]; then
+ # Ignore build directories that don't contain symbol versions
+ # of the shared libraries.
+ DIR_LIBS=$(ls "$DIR"/lib*.so 2>/dev/null)
+ if [ -z "$DIR_LIBS" ]; then
+ echo "No shared libs: $DIR"
+ continue
+ fi
+ TSTAMP=$(get_file_timestamp "$DIR")
+ printf "%s %s\n" "$TSTAMP" "$SUBDIR" >> "$LIST"
+ fi
+ done
+ SUBDIR=$(cat $LIST | sort -r | head -1 | cut -d" " -f2)
+ rm -f "$LIST"
+
+ if [ -z "$SUBDIR" ]; then
+ if [ -z "$1" ]; then
+ panic "Could not find any build directory under \
+$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Please build the program first!"
+ else
+ panic "Could not find any $1 directory under \
+$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Check your build type!"
+ fi
+ fi
+
+ SYMBOL_DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
+ log "Auto-config: --symbol-dir=$SYMBOL_DIR"
+}
+
+if [ -z "$SYMBOL_DIR" ]; then
+ detect_symbol_dir "$BUILDTYPE"
+fi
+
+# Allow several concurrent debugging sessions
+TARGET_GDBSERVER=/data/data/$PACKAGE_NAME/gdbserver-adb-gdb-$TMP_ID
+TMP_TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID
+
+# Return the build fingerprint contained in a build.prop file.
+# $1: path to build.prop file
+get_build_fingerprint_from () {
+ cat "$1" | grep -e '^ro.build.fingerprint=' | cut -d= -f2
+}
+
+
+ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR
+PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR}
+
+HOST_FINGERPRINT=
+DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint)
+log "Device build fingerprint: $DEVICE_FINGERPRINT"
+
+# If --pull-libs-dir is not specified, and this is a platform build, look
+# if we can use the symbolic libraries under $ANDROID_PRODUCT_OUT/symbols/
+# directly, if the build fingerprint matches the device.
+if [ -z "$ORG_PULL_LIBS_DIR" -a \
+ "$ANDROID_PRODUCT_OUT" -a \
+ -f "$ANDROID_PRODUCT_OUT/system/build.prop" ]; then
+ ANDROID_FINGERPRINT=$(get_build_fingerprint_from \
+ "$ANDROID_PRODUCT_OUT"/system/build.prop)
+ log "Android build fingerprint: $ANDROID_FINGERPRINT"
+ if [ "$ANDROID_FINGERPRINT" = "$DEVICE_FINGERPRINT" ]; then
+ log "Perfect match!"
+ PULL_LIBS_DIR=$ANDROID_PRODUCT_OUT/symbols
+ HOST_FINGERPRINT=$ANDROID_FINGERPRINT
+ if [ "$PULL_LIBS" ]; then
+ log "Ignoring --pull-libs since the device and platform build \
+fingerprints match."
+ NO_PULL_LIBS=true
+ fi
+ fi
+fi
+
+# If neither --pull-libs an --no-pull-libs were specified, check the build
+# fingerprints of the device, and the cached system libraries on the host.
+#
+if [ -z "$NO_PULL_LIBS" -a -z "$PULL_LIBS" ]; then
+ if [ ! -f "$PULL_LIBS_DIR/build.prop" ]; then
+ log "Auto-config: --pull-libs (no cached libraries)"
+ PULL_LIBS=true
+ else
+ HOST_FINGERPRINT=$(get_build_fingerprint_from "$PULL_LIBS_DIR/build.prop")
+ log "Host build fingerprint: $HOST_FINGERPRINT"
+ if [ "$HOST_FINGERPRINT" == "$DEVICE_FINGERPRINT" ]; then
+ log "Auto-config: --no-pull-libs (fingerprint match)"
+ NO_PULL_LIBS=true
+ else
+ log "Auto-config: --pull-libs (fingerprint mismatch)"
+ PULL_LIBS=true
+ fi
+ fi
+fi
+
+# Extract the system libraries from the device if necessary.
+if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
+ echo "Extracting system libraries into: $PULL_LIBS_DIR"
+fi
+
+mkdir -p "$PULL_LIBS_DIR"
+fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR"
+
+# If requested, work for M-x gdb. The gdb indirections make it
+# difficult to pass --annotate=3 to the gdb binary itself.
+GDB_ARGS=
+if [ "$ANNOTATE" ]; then
+ GDB_ARGS=$GDB_ARGS" --annotate=$ANNOTATE"
+fi
+
+# Get the PID from the first argument or else find the PID of the
+# browser process.
+if [ -z "$PID" ]; then
+ PROCESSNAME=$PACKAGE_NAME
+ if [ "$SANDBOXED_INDEX" ]; then
+ PROCESSNAME=$PROCESSNAME:sandboxed_process$SANDBOXED_INDEX
+ elif [ "$SANDBOXED" ]; then
+ PROCESSNAME=$PROCESSNAME:sandboxed_process
+ PID=$(adb_shell ps | \
+ awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1)
+ elif [ "$PRIVILEGED_INDEX" ]; then
+ PROCESSNAME=$PROCESSNAME:privileged_process$PRIVILEGED_INDEX
+ elif [ "$PRIVILEGED" ]; then
+ PROCESSNAME=$PROCESSNAME:privileged_process
+ PID=$(adb_shell ps | \
+ awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1)
+ fi
+ if [ -z "$PID" ]; then
+ PID=$(adb_shell ps | \
+ awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1)
+ fi
+ if [ -z "$PID" ]; then
+ if [ "$START" ]; then
+ panic "Can't find application process PID, did it crash?"
+ else
+ panic "Can't find application process PID, are you sure it is \
+running? Try using --start."
+ fi
+ fi
+ log "Found process PID: $PID"
+elif [ "$SANDBOXED" ]; then
+ echo "WARNING: --sandboxed option ignored due to use of --pid."
+elif [ "$PRIVILEGED" ]; then
+ echo "WARNING: --privileged option ignored due to use of --pid."
+fi
+
+# Determine if 'adb shell' runs as root or not.
+# If so, we can launch gdbserver directly, otherwise, we have to
+# use run-as $PACKAGE_NAME ..., which requires the package to be debuggable.
+#
+if [ "$SU_PREFIX" ]; then
+ # Need to check that this works properly.
+ SU_PREFIX_TEST_LOG=$TMPDIR/su-prefix.log
+ adb_shell $SU_PREFIX \"echo "foo"\" > $SU_PREFIX_TEST_LOG 2>&1
+ if [ $? != 0 -o "$(cat $SU_PREFIX_TEST_LOG)" != "foo" ]; then
+ echo "ERROR: Cannot use '$SU_PREFIX' as a valid su prefix:"
+ echo "$ adb shell $SU_PREFIX \"echo foo\""
+ cat $SU_PREFIX_TEST_LOG
+ exit 1
+ fi
+ COMMAND_PREFIX="$SU_PREFIX \""
+ COMMAND_SUFFIX="\""
+else
+ SHELL_UID=$(adb shell cat /proc/self/status | \
+ awk '$1 == "Uid:" { print $2; }')
+ log "Shell UID: $SHELL_UID"
+ if [ "$SHELL_UID" != 0 -o -n "$NO_ROOT" ]; then
+ COMMAND_PREFIX="run-as $PACKAGE_NAME"
+ COMMAND_SUFFIX=
+ else
+ COMMAND_PREFIX=
+ COMMAND_SUFFIX=
+ fi
+fi
+log "Command prefix: '$COMMAND_PREFIX'"
+log "Command suffix: '$COMMAND_SUFFIX'"
+
+# Pull device's system libraries that are mapped by our process.
+# Pulling all system libraries is too long, so determine which ones
+# we need by looking at /proc/$PID/maps instead
+if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
+ echo "Extracting system libraries into: $PULL_LIBS_DIR"
+ rm -f $PULL_LIBS_DIR/build.prop
+ MAPPINGS=$(adb_shell $COMMAND_PREFIX cat /proc/$PID/maps $COMMAND_SUFFIX)
+ if [ $? != 0 ]; then
+ echo "ERROR: Could not list process's memory mappings."
+ if [ "$SU_PREFIX" ]; then
+ panic "Are you sure your --su-prefix is correct?"
+ else
+ panic "Use --su-prefix if the application is not debuggable."
+ fi
+ fi
+ SYSTEM_LIBS=$(echo "$MAPPINGS" | \
+ awk '$6 ~ /\/system\/.*\.so$/ { print $6; }' | sort -u)
+ for SYSLIB in /system/bin/linker $SYSTEM_LIBS; do
+ echo "Pulling from device: $SYSLIB"
+ DST_FILE=$PULL_LIBS_DIR$SYSLIB
+ DST_DIR=$(dirname "$DST_FILE")
+ mkdir -p "$DST_DIR" && adb pull $SYSLIB "$DST_FILE" 2>/dev/null
+ fail_panic "Could not pull $SYSLIB from device !?"
+ done
+ echo "Pulling device build.prop"
+ adb pull /system/build.prop $PULL_LIBS_DIR/build.prop
+ fail_panic "Could not pull device build.prop !?"
+fi
+
+# Find all the sub-directories of $PULL_LIBS_DIR, up to depth 4
+# so we can add them to solib-search-path later.
+SOLIB_DIRS=$(find $PULL_LIBS_DIR -mindepth 1 -maxdepth 4 -type d | \
+ grep -v "^$" | tr '\n' ':')
+
+# This is a re-implementation of gdbclient, where we use compatible
+# versions of gdbserver and $GDBNAME to ensure that everything works
+# properly.
+#
+
+# Push gdbserver to the device
+log "Pushing gdbserver $GDBSERVER to $TARGET_GDBSERVER"
+adb push $GDBSERVER $TMP_TARGET_GDBSERVER &>/dev/null
+adb shell $COMMAND_PREFIX cp $TMP_TARGET_GDBSERVER $TARGET_GDBSERVER
+adb shell rm $TMP_TARGET_GDBSERVER
+fail_panic "Could not copy gdbserver to the device!"
+
+if [ -z "$PORT" ]; then
+ PORT=5039
+fi
+HOST_PORT=$PORT
+TARGET_PORT=$PORT
+
+# Select correct app_process for architecture.
+case $TARGET_ARCH in
+ arm|x86|mips) GDBEXEC=app_process;;
+ arm64|x86_64) GDBEXEC=app_process64;;
+ *) fail_panic "Unknown app_process for architecture!";;
+esac
+
+# Detect AddressSanitizer setup on the device. In that case app_process is a
+# script, and the real executable is app_process.real.
+GDBEXEC_ASAN=app_process.real
+adb_shell ls /system/bin/$GDBEXEC_ASAN
+if [ $? == 0 ]; then
+ GDBEXEC=$GDBEXEC_ASAN
+fi
+
+# Pull the app_process binary from the device.
+log "Pulling $GDBEXEC from device"
+adb pull /system/bin/$GDBEXEC "$TMPDIR"/$GDBEXEC &>/dev/null
+fail_panic "Could not retrieve $GDBEXEC from the device!"
+
+# Setup network redirection
+log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_PORT)"
+adb forward tcp:$HOST_PORT tcp:$TARGET_PORT
+fail_panic "Could not setup network redirection from \
+host:localhost:$HOST_PORT to device:localhost:$TARGET_PORT!"
+
+# Start gdbserver in the background
+# Note that using run-as requires the package to be debuggable.
+#
+# If not, this will fail horribly. The alternative is to run the
+# program as root, which requires of course root privileges.
+# Maybe we should add a --root option to enable this?
+#
+log "Starting gdbserver in the background:"
+GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log
+log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
+--attach $PID $COMMAND_SUFFIX"
+("$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
+ --attach $PID $COMMAND_SUFFIX > $GDBSERVER_LOG 2>&1) &
+GDBSERVER_PID=$!
+echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE
+log "background job pid: $GDBSERVER_PID"
+
+# Check that it is still running after a few seconds. If not, this means we
+# could not properly attach to it
+sleep 2
+log "Job control: $(jobs -l)"
+STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }')
+if [ "$STATE" != "Running" ]; then
+ echo "ERROR: GDBServer could not attach to PID $PID!"
+ if [ $(adb_shell su -c getenforce) != "Permissive" ]; then
+ echo "Device mode is Enforcing. Changing Device mode to Permissive "
+ $(adb_shell su -c setenforce 0)
+ if [ $(adb_shell su -c getenforce) != "Permissive" ]; then
+ echo "ERROR: Failed to Change Device mode to Permissive"
+ echo "Failure log (use --verbose for more information):"
+ cat $GDBSERVER_LOG
+ exit 1
+ fi
+ else
+ echo "Failure log (use --verbose for more information):"
+ cat $GDBSERVER_LOG
+ exit 1
+ fi
+fi
+
+# Generate a file containing useful GDB initialization commands
+readonly COMMANDS=$TMPDIR/gdb.init
+log "Generating GDB initialization commands file: $COMMANDS"
+echo -n "" > $COMMANDS
+echo "set print pretty 1" >> $COMMANDS
+echo "python" >> $COMMANDS
+echo "import sys" >> $COMMANDS
+echo "sys.path.insert(0, '$CHROMIUM_SRC/tools/gdb/')" >> $COMMANDS
+echo "try:" >> $COMMANDS
+echo " import gdb_chrome" >> $COMMANDS
+echo "finally:" >> $COMMANDS
+echo " sys.path.pop(0)" >> $COMMANDS
+echo "end" >> $COMMANDS
+echo "file $TMPDIR/$GDBEXEC" >> $COMMANDS
+echo "directory $CHROMIUM_SRC" >> $COMMANDS
+echo "set solib-absolute-prefix $PULL_LIBS_DIR" >> $COMMANDS
+echo "set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR" \
+ >> $COMMANDS
+echo "echo Attaching and reading symbols, this may take a while.." \
+ >> $COMMANDS
+echo "target remote :$HOST_PORT" >> $COMMANDS
+
+if [ "$GDBINIT" ]; then
+ cat "$GDBINIT" >> $COMMANDS
+fi
+
+if [ "$VERBOSE" -gt 0 ]; then
+ echo "### START $COMMANDS"
+ cat $COMMANDS
+ echo "### END $COMMANDS"
+fi
+
+log "Launching gdb client: $GDB $GDB_ARGS -x $COMMANDS"
+$GDB $GDB_ARGS -x $COMMANDS &&
+rm -f "$GDBSERVER_PIDFILE"
diff --git a/build/android/adb_gdb_android_webview_shell b/build/android/adb_gdb_android_webview_shell
new file mode 100755
index 00000000000..f685fda77c5
--- /dev/null
+++ b/build/android/adb_gdb_android_webview_shell
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) 2013 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.
+#
+# Attach to or start a ContentShell process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=.AwShellActivity
+"$PROGDIR"/adb_gdb \
+ --program-name=AwShellApplication \
+ --package-name=org.chromium.android_webview.shell \
+ "$@"
diff --git a/build/android/adb_gdb_chrome_public b/build/android/adb_gdb_chrome_public
new file mode 100755
index 00000000000..4366c838e78
--- /dev/null
+++ b/build/android/adb_gdb_chrome_public
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+#
+# Attach to or start a ChromePublic process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=com.google.android.apps.chrome.Main
+"$PROGDIR"/adb_gdb \
+ --program-name=ChromePublic \
+ --package-name=org.chromium.chrome \
+ "$@"
diff --git a/build/android/adb_gdb_chrome_shell b/build/android/adb_gdb_chrome_shell
new file mode 100755
index 00000000000..e5c8a306be2
--- /dev/null
+++ b/build/android/adb_gdb_chrome_shell
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2014 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.
+#
+# Attach to or start a ChromeShell process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=.ChromeShellActivity
+"$PROGDIR"/adb_gdb \
+ --program-name=ChromeShell \
+ --package-name=org.chromium.chrome.shell \
+ "$@"
diff --git a/build/android/adb_gdb_content_shell b/build/android/adb_gdb_content_shell
new file mode 100755
index 00000000000..18e1a61d893
--- /dev/null
+++ b/build/android/adb_gdb_content_shell
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+#
+# Attach to or start a ContentShell process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=.ContentShellActivity
+"$PROGDIR"/adb_gdb \
+ --program-name=ContentShell \
+ --package-name=org.chromium.content_shell_apk \
+ "$@"
diff --git a/build/android/adb_gdb_cronet_sample b/build/android/adb_gdb_cronet_sample
new file mode 100755
index 00000000000..8d0c864d133
--- /dev/null
+++ b/build/android/adb_gdb_cronet_sample
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2014 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.
+#
+# Attach to or start a ContentShell process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=.CronetSampleActivity
+"$PROGDIR"/adb_gdb \
+ --program-name=CronetSample \
+ --package-name=org.chromium.cronet_sample_apk \
+ "$@"
diff --git a/build/android/adb_gdb_mojo_shell b/build/android/adb_gdb_mojo_shell
new file mode 100755
index 00000000000..ba91149cce9
--- /dev/null
+++ b/build/android/adb_gdb_mojo_shell
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2013 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.
+#
+# Attach to or start a ContentShell process and debug it.
+# See --help for details.
+#
+PROGDIR=$(dirname "$0")
+export ADB_GDB_PROGNAME=$(basename "$0")
+export ADB_GDB_ACTIVITY=.MojoShellActivity
+"$PROGDIR"/adb_gdb \
+ --program-name=MojoShell \
+ --package-name=org.chromium.mojo_shell_apk \
+ "$@"
diff --git a/build/android/adb_install_apk.py b/build/android/adb_install_apk.py
new file mode 100755
index 00000000000..50faea7d291
--- /dev/null
+++ b/build/android/adb_install_apk.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Utility script to install APKs from the command line quickly."""
+
+import argparse
+import glob
+import logging
+import os
+import sys
+
+from pylib import constants
+from pylib.device import device_blacklist
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import apk_helper
+from pylib.utils import run_tests_helper
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ apk_group = parser.add_mutually_exclusive_group(required=True)
+ apk_group.add_argument('--apk', dest='apk_name',
+ help='DEPRECATED The name of the apk containing the'
+ ' application (with the .apk extension).')
+ apk_group.add_argument('apk_path', nargs='?',
+ help='The path to the APK to install.')
+
+ # TODO(jbudorick): Remove once no clients pass --apk_package
+ parser.add_argument('--apk_package', help='DEPRECATED unused')
+ parser.add_argument('--split',
+ action='append',
+ dest='splits',
+ help='A glob matching the apk splits. '
+ 'Can be specified multiple times.')
+ parser.add_argument('--keep_data',
+ action='store_true',
+ default=False,
+ help='Keep the package data when installing '
+ 'the application.')
+ parser.add_argument('--debug', action='store_const', const='Debug',
+ dest='build_type',
+ default=os.environ.get('BUILDTYPE', 'Debug'),
+ help='If set, run test suites under out/Debug. '
+ 'Default is env var BUILDTYPE or Debug')
+ parser.add_argument('--release', action='store_const', const='Release',
+ dest='build_type',
+ help='If set, run test suites under out/Release. '
+ 'Default is env var BUILDTYPE or Debug.')
+ parser.add_argument('-d', '--device', dest='device',
+ help='Target device for apk to install on.')
+ parser.add_argument('-v', '--verbose', action='count',
+ help='Enable verbose logging.')
+
+ args = parser.parse_args()
+
+ run_tests_helper.SetLogLevel(args.verbose)
+ constants.SetBuildType(args.build_type)
+
+ apk = args.apk_path or args.apk_name
+ if not apk.endswith('.apk'):
+ apk += '.apk'
+ if not os.path.exists(apk):
+ apk = os.path.join(constants.GetOutDirectory(), 'apks', apk)
+ if not os.path.exists(apk):
+ parser.error('%s not found.' % apk)
+
+ if args.splits:
+ splits = []
+ base_apk_package = apk_helper.ApkHelper(apk).GetPackageName()
+ for split_glob in args.splits:
+ apks = [f for f in glob.glob(split_glob) if f.endswith('.apk')]
+ if not apks:
+ logging.warning('No apks matched for %s.' % split_glob)
+ for f in apks:
+ helper = apk_helper.ApkHelper(f)
+ if (helper.GetPackageName() == base_apk_package
+ and helper.GetSplitName()):
+ splits.append(f)
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+
+ if args.device:
+ devices = [d for d in devices if d == args.device]
+ if not devices:
+ raise device_errors.DeviceUnreachableError(args.device)
+ elif not devices:
+ raise device_errors.NoDevicesError()
+
+ def blacklisting_install(device):
+ try:
+ if args.splits:
+ device.InstallSplitApk(apk, splits, reinstall=args.keep_data)
+ else:
+ device.Install(apk, reinstall=args.keep_data)
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to install %s', args.apk_name)
+ device_blacklist.ExtendBlacklist([str(device)])
+ logging.warning('Blacklisting %s', str(device))
+ except device_errors.CommandTimeoutError:
+ logging.exception('Timed out while installing %s', args.apk_name)
+ device_blacklist.ExtendBlacklist([str(device)])
+ logging.warning('Blacklisting %s', str(device))
+
+ device_utils.DeviceUtils.parallel(devices).pMap(blacklisting_install)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/adb_kill_android_webview_shell b/build/android/adb_kill_android_webview_shell
new file mode 100755
index 00000000000..5f287f08266
--- /dev/null
+++ b/build/android/adb_kill_android_webview_shell
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+#
+# Kill a running android webview shell.
+#
+# Assumes you have sourced the build/android/envsetup.sh script.
+
+SHELL_PID_LINES=$(adb shell ps | grep ' org.chromium.android_webview.shell')
+VAL=$(echo "$SHELL_PID_LINES" | wc -l)
+if [ $VAL -lt 1 ] ; then
+ echo "Not running android webview shell."
+else
+ SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}')
+ if [ "$SHELL_PID" != "" ] ; then
+ set -x
+ adb shell kill $SHELL_PID
+ set -
+ else
+ echo "Android webview shell does not appear to be running."
+ fi
+fi
diff --git a/build/android/adb_kill_chrome_public b/build/android/adb_kill_chrome_public
new file mode 100755
index 00000000000..5b539a043d4
--- /dev/null
+++ b/build/android/adb_kill_chrome_public
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+#
+# Kill a running instance of ChromePublic.
+#
+# Assumes you have sourced the build/android/envsetup.sh script.
+
+SHELL_PID_LINES=$(adb shell ps | grep -w 'org.chromium.chrome')
+VAL=$(echo "$SHELL_PID_LINES" | wc -l)
+if [ $VAL -lt 1 ] ; then
+ echo "Not running ChromePublic."
+else
+ SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}')
+ if [ "$SHELL_PID" != "" ] ; then
+ set -x
+ adb shell kill $SHELL_PID
+ set -
+ else
+ echo "ChromePublic does not appear to be running."
+ fi
+fi
diff --git a/build/android/adb_kill_chrome_shell b/build/android/adb_kill_chrome_shell
new file mode 100755
index 00000000000..2b63c9af3d2
--- /dev/null
+++ b/build/android/adb_kill_chrome_shell
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+#
+# Kill a running chrome shell.
+#
+# Assumes you have sourced the build/android/envsetup.sh script.
+
+SHELL_PID_LINES=$(adb shell ps | grep ' org.chromium.chrome.shell')
+VAL=$(echo "$SHELL_PID_LINES" | wc -l)
+if [ $VAL -lt 1 ] ; then
+ echo "Not running Chrome shell."
+else
+ SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}')
+ if [ "$SHELL_PID" != "" ] ; then
+ set -x
+ adb shell kill $SHELL_PID
+ set -
+ else
+ echo "Chrome shell does not appear to be running."
+ fi
+fi
diff --git a/build/android/adb_kill_content_shell b/build/android/adb_kill_content_shell
new file mode 100755
index 00000000000..e379dd47149
--- /dev/null
+++ b/build/android/adb_kill_content_shell
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+#
+# Kill a running content shell.
+#
+# Assumes you have sourced the build/android/envsetup.sh script.
+
+SHELL_PID_LINES=$(adb shell ps | grep ' org.chromium.content_shell_apk')
+VAL=$(echo "$SHELL_PID_LINES" | wc -l)
+if [ $VAL -lt 1 ] ; then
+ echo "Not running Content shell."
+else
+ SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}')
+ if [ "$SHELL_PID" != "" ] ; then
+ set -x
+ adb shell kill $SHELL_PID
+ set -
+ else
+ echo "Content shell does not appear to be running."
+ fi
+fi
diff --git a/build/android/adb_logcat_monitor.py b/build/android/adb_logcat_monitor.py
new file mode 100755
index 00000000000..d3cc67dbcc8
--- /dev/null
+++ b/build/android/adb_logcat_monitor.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Saves logcats from all connected devices.
+
+Usage: adb_logcat_monitor.py []
+
+This script will repeatedly poll adb for new devices and save logcats
+inside the directory, which it attempts to create. The
+script will run until killed by an external signal. To test, run the
+script in a shell and -C it after a while. It should be
+resilient across phone disconnects and reconnects and start the logcat
+early enough to not miss anything.
+"""
+
+import logging
+import os
+import re
+import shutil
+import signal
+import subprocess
+import sys
+import time
+
+# Map from device_id -> (process, logcat_num)
+devices = {}
+
+
+class TimeoutException(Exception):
+ """Exception used to signal a timeout."""
+ pass
+
+
+class SigtermError(Exception):
+ """Exception used to catch a sigterm."""
+ pass
+
+
+def StartLogcatIfNecessary(device_id, adb_cmd, base_dir):
+ """Spawns a adb logcat process if one is not currently running."""
+ process, logcat_num = devices[device_id]
+ if process:
+ if process.poll() is None:
+ # Logcat process is still happily running
+ return
+ else:
+ logging.info('Logcat for device %s has died', device_id)
+ error_filter = re.compile('- waiting for device -')
+ for line in process.stderr:
+ if not error_filter.match(line):
+ logging.error(device_id + ': ' + line)
+
+ logging.info('Starting logcat %d for device %s', logcat_num,
+ device_id)
+ logcat_filename = 'logcat_%s_%03d' % (device_id, logcat_num)
+ logcat_file = open(os.path.join(base_dir, logcat_filename), 'w')
+ process = subprocess.Popen([adb_cmd, '-s', device_id,
+ 'logcat', '-v', 'threadtime'],
+ stdout=logcat_file,
+ stderr=subprocess.PIPE)
+ devices[device_id] = (process, logcat_num + 1)
+
+
+def GetAttachedDevices(adb_cmd):
+ """Gets the device list from adb.
+
+ We use an alarm in this function to avoid deadlocking from an external
+ dependency.
+
+ Args:
+ adb_cmd: binary to run adb
+
+ Returns:
+ list of devices or an empty list on timeout
+ """
+ signal.alarm(2)
+ try:
+ out, err = subprocess.Popen([adb_cmd, 'devices'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+ if err:
+ logging.warning('adb device error %s', err.strip())
+ return re.findall('^(\\S+)\tdevice$', out, re.MULTILINE)
+ except TimeoutException:
+ logging.warning('"adb devices" command timed out')
+ return []
+ except (IOError, OSError):
+ logging.exception('Exception from "adb devices"')
+ return []
+ finally:
+ signal.alarm(0)
+
+
+def main(base_dir, adb_cmd='adb'):
+ """Monitor adb forever. Expects a SIGINT (Ctrl-C) to kill."""
+ # We create the directory to ensure 'run once' semantics
+ if os.path.exists(base_dir):
+ print 'adb_logcat_monitor: %s already exists? Cleaning' % base_dir
+ shutil.rmtree(base_dir, ignore_errors=True)
+
+ os.makedirs(base_dir)
+ logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'),
+ level=logging.INFO,
+ format='%(asctime)-2s %(levelname)-8s %(message)s')
+
+ # Set up the alarm for calling 'adb devices'. This is to ensure
+ # our script doesn't get stuck waiting for a process response
+ def TimeoutHandler(_signum, _unused_frame):
+ raise TimeoutException()
+ signal.signal(signal.SIGALRM, TimeoutHandler)
+
+ # Handle SIGTERMs to ensure clean shutdown
+ def SigtermHandler(_signum, _unused_frame):
+ raise SigtermError()
+ signal.signal(signal.SIGTERM, SigtermHandler)
+
+ logging.info('Started with pid %d', os.getpid())
+ pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
+
+ try:
+ with open(pid_file_path, 'w') as f:
+ f.write(str(os.getpid()))
+ while True:
+ for device_id in GetAttachedDevices(adb_cmd):
+ if not device_id in devices:
+ subprocess.call([adb_cmd, '-s', device_id, 'logcat', '-c'])
+ devices[device_id] = (None, 0)
+
+ for device in devices:
+ # This will spawn logcat watchers for any device ever detected
+ StartLogcatIfNecessary(device, adb_cmd, base_dir)
+
+ time.sleep(5)
+ except SigtermError:
+ logging.info('Received SIGTERM, shutting down')
+ except: # pylint: disable=bare-except
+ logging.exception('Unexpected exception in main.')
+ finally:
+ for process, _ in devices.itervalues():
+ if process:
+ try:
+ process.terminate()
+ except OSError:
+ pass
+ os.remove(pid_file_path)
+
+
+if __name__ == '__main__':
+ if 2 <= len(sys.argv) <= 3:
+ print 'adb_logcat_monitor: Initializing'
+ sys.exit(main(*sys.argv[1:3]))
+
+ print 'Usage: %s []' % sys.argv[0]
diff --git a/build/android/adb_logcat_printer.py b/build/android/adb_logcat_printer.py
new file mode 100755
index 00000000000..55176ab9201
--- /dev/null
+++ b/build/android/adb_logcat_printer.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Shutdown adb_logcat_monitor and print accumulated logs.
+
+To test, call './adb_logcat_printer.py ' where
+ contains 'adb logcat -v threadtime' files named as
+logcat__
+
+The script will print the files to out, and will combine multiple
+logcats from a single device if there is overlap.
+
+Additionally, if a /LOGCAT_MONITOR_PID exists, the script
+will attempt to terminate the contained PID by sending a SIGINT and
+monitoring for the deletion of the aforementioned file.
+"""
+# pylint: disable=W0702
+
+import cStringIO
+import logging
+import optparse
+import os
+import re
+import signal
+import sys
+import time
+
+
+# Set this to debug for more verbose output
+LOG_LEVEL = logging.INFO
+
+
+def CombineLogFiles(list_of_lists, logger):
+ """Splices together multiple logcats from the same device.
+
+ Args:
+ list_of_lists: list of pairs (filename, list of timestamped lines)
+ logger: handler to log events
+
+ Returns:
+ list of lines with duplicates removed
+ """
+ cur_device_log = ['']
+ for cur_file, cur_file_lines in list_of_lists:
+ # Ignore files with just the logcat header
+ if len(cur_file_lines) < 2:
+ continue
+ common_index = 0
+ # Skip this step if list just has empty string
+ if len(cur_device_log) > 1:
+ try:
+ line = cur_device_log[-1]
+ # Used to make sure we only splice on a timestamped line
+ if re.match(r'^\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} ', line):
+ common_index = cur_file_lines.index(line)
+ else:
+ logger.warning('splice error - no timestamp in "%s"?', line.strip())
+ except ValueError:
+ # The last line was valid but wasn't found in the next file
+ cur_device_log += ['***** POSSIBLE INCOMPLETE LOGCAT *****']
+ logger.info('Unable to splice %s. Incomplete logcat?', cur_file)
+
+ cur_device_log += ['*'*30 + ' %s' % cur_file]
+ cur_device_log.extend(cur_file_lines[common_index:])
+
+ return cur_device_log
+
+
+def FindLogFiles(base_dir):
+ """Search a directory for logcat files.
+
+ Args:
+ base_dir: directory to search
+
+ Returns:
+ Mapping of device_id to a sorted list of file paths for a given device
+ """
+ logcat_filter = re.compile(r'^logcat_(\S+)_(\d+)$')
+ # list of tuples (, , )
+ filtered_list = []
+ for cur_file in os.listdir(base_dir):
+ matcher = logcat_filter.match(cur_file)
+ if matcher:
+ filtered_list += [(matcher.group(1), int(matcher.group(2)),
+ os.path.join(base_dir, cur_file))]
+ filtered_list.sort()
+ file_map = {}
+ for device_id, _, cur_file in filtered_list:
+ if device_id not in file_map:
+ file_map[device_id] = []
+
+ file_map[device_id] += [cur_file]
+ return file_map
+
+
+def GetDeviceLogs(log_filenames, logger):
+ """Read log files, combine and format.
+
+ Args:
+ log_filenames: mapping of device_id to sorted list of file paths
+ logger: logger handle for logging events
+
+ Returns:
+ list of formatted device logs, one for each device.
+ """
+ device_logs = []
+
+ for device, device_files in log_filenames.iteritems():
+ logger.debug('%s: %s', device, str(device_files))
+ device_file_lines = []
+ for cur_file in device_files:
+ with open(cur_file) as f:
+ device_file_lines += [(cur_file, f.read().splitlines())]
+ combined_lines = CombineLogFiles(device_file_lines, logger)
+ # Prepend each line with a short unique ID so it's easy to see
+ # when the device changes. We don't use the start of the device
+ # ID because it can be the same among devices. Example lines:
+ # AB324: foo
+ # AB324: blah
+ device_logs += [('\n' + device[-5:] + ': ').join(combined_lines)]
+ return device_logs
+
+
+def ShutdownLogcatMonitor(base_dir, logger):
+ """Attempts to shutdown adb_logcat_monitor and blocks while waiting."""
+ try:
+ monitor_pid_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
+ with open(monitor_pid_path) as f:
+ monitor_pid = int(f.readline())
+
+ logger.info('Sending SIGTERM to %d', monitor_pid)
+ os.kill(monitor_pid, signal.SIGTERM)
+ i = 0
+ while True:
+ time.sleep(.2)
+ if not os.path.exists(monitor_pid_path):
+ return
+ if not os.path.exists('/proc/%d' % monitor_pid):
+ logger.warning('Monitor (pid %d) terminated uncleanly?', monitor_pid)
+ return
+ logger.info('Waiting for logcat process to terminate.')
+ i += 1
+ if i >= 10:
+ logger.warning('Monitor pid did not terminate. Continuing anyway.')
+ return
+
+ except (ValueError, IOError, OSError):
+ logger.exception('Error signaling logcat monitor - continuing')
+
+
+def main(argv):
+ parser = optparse.OptionParser(usage='Usage: %prog [options] ')
+ parser.add_option('--output-path',
+ help='Output file path (if unspecified, prints to stdout)')
+ options, args = parser.parse_args(argv)
+ if len(args) != 1:
+ parser.error('Wrong number of unparsed args')
+ base_dir = args[0]
+ if options.output_path:
+ output_file = open(options.output_path, 'w')
+ else:
+ output_file = sys.stdout
+
+ log_stringio = cStringIO.StringIO()
+ logger = logging.getLogger('LogcatPrinter')
+ logger.setLevel(LOG_LEVEL)
+ sh = logging.StreamHandler(log_stringio)
+ sh.setFormatter(logging.Formatter('%(asctime)-2s %(levelname)-8s'
+ ' %(message)s'))
+ logger.addHandler(sh)
+
+ try:
+ # Wait at least 5 seconds after base_dir is created before printing.
+ #
+ # The idea is that 'adb logcat > file' output consists of 2 phases:
+ # 1 Dump all the saved logs to the file
+ # 2 Stream log messages as they are generated
+ #
+ # We want to give enough time for phase 1 to complete. There's no
+ # good method to tell how long to wait, but it usually only takes a
+ # second. On most bots, this code path won't occur at all, since
+ # adb_logcat_monitor.py command will have spawned more than 5 seconds
+ # prior to called this shell script.
+ try:
+ sleep_time = 5 - (time.time() - os.path.getctime(base_dir))
+ except OSError:
+ sleep_time = 5
+ if sleep_time > 0:
+ logger.warning('Monitor just started? Sleeping %.1fs', sleep_time)
+ time.sleep(sleep_time)
+
+ assert os.path.exists(base_dir), '%s does not exist' % base_dir
+ ShutdownLogcatMonitor(base_dir, logger)
+ separator = '\n' + '*' * 80 + '\n\n'
+ for log in GetDeviceLogs(FindLogFiles(base_dir), logger):
+ output_file.write(log)
+ output_file.write(separator)
+ with open(os.path.join(base_dir, 'eventlog')) as f:
+ output_file.write('\nLogcat Monitor Event Log\n')
+ output_file.write(f.read())
+ except:
+ logger.exception('Unexpected exception')
+
+ logger.info('Done.')
+ sh.flush()
+ output_file.write('\nLogcat Printer Event Log\n')
+ output_file.write(log_stringio.getvalue())
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/adb_profile_chrome b/build/android/adb_profile_chrome
new file mode 100755
index 00000000000..21f6faf7b98
--- /dev/null
+++ b/build/android/adb_profile_chrome
@@ -0,0 +1,8 @@
+#!/bin/bash
+#
+# Copyright 2013 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.
+#
+# Start / stop profiling in chrome.
+exec $(dirname $0)/../../tools/profile_chrome.py $@
diff --git a/build/android/adb_reverse_forwarder.py b/build/android/adb_reverse_forwarder.py
new file mode 100755
index 00000000000..3ce53595dae
--- /dev/null
+++ b/build/android/adb_reverse_forwarder.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+"""Command line tool for forwarding ports from a device to the host.
+
+Allows an Android device to connect to services running on the host machine,
+i.e., "adb forward" in reverse. Requires |host_forwarder| and |device_forwarder|
+to be built.
+"""
+
+import logging
+import optparse
+import sys
+import time
+
+from pylib import constants
+from pylib import forwarder
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import run_tests_helper
+
+
+def main(argv):
+ parser = optparse.OptionParser(usage='Usage: %prog [options] device_port '
+ 'host_port [device_port_2 host_port_2] ...',
+ description=__doc__)
+ parser.add_option('-v',
+ '--verbose',
+ dest='verbose_count',
+ default=0,
+ action='count',
+ help='Verbose level (multiple times for more)')
+ parser.add_option('--device',
+ help='Serial number of device we should use.')
+ parser.add_option('--debug', action='store_const', const='Debug',
+ dest='build_type', default='Release',
+ help='Use Debug build of host tools instead of Release.')
+
+ options, args = parser.parse_args(argv)
+ run_tests_helper.SetLogLevel(options.verbose_count)
+
+ if len(args) < 2 or not len(args) % 2:
+ parser.error('Need even number of port pairs')
+ sys.exit(1)
+
+ try:
+ port_pairs = map(int, args[1:])
+ port_pairs = zip(port_pairs[::2], port_pairs[1::2])
+ except ValueError:
+ parser.error('Bad port number')
+ sys.exit(1)
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+
+ if options.device:
+ device = next((d for d in devices if d == options.device), None)
+ if not device:
+ raise device_errors.DeviceUnreachableError(options.device)
+ elif devices:
+ device = devices[0]
+ logging.info('No device specified. Defaulting to %s', devices[0])
+ else:
+ raise device_errors.NoDevicesError()
+
+ constants.SetBuildType(options.build_type)
+ try:
+ forwarder.Forwarder.Map(port_pairs, device)
+ while True:
+ time.sleep(60)
+ except KeyboardInterrupt:
+ sys.exit(0)
+ finally:
+ forwarder.Forwarder.UnmapAllDevicePorts(device)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/build/android/adb_run_android_webview_shell b/build/android/adb_run_android_webview_shell
new file mode 100755
index 00000000000..1014a731f47
--- /dev/null
+++ b/build/android/adb_run_android_webview_shell
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright (c) 2013 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.
+
+optional_url=$1
+
+adb shell am start \
+ -a android.intent.action.VIEW \
+ -n org.chromium.android_webview.shell/.AwShellActivity \
+ ${optional_url:+-d "$optional_url"}
diff --git a/build/android/adb_run_chrome_public b/build/android/adb_run_chrome_public
new file mode 100755
index 00000000000..bf150711442
--- /dev/null
+++ b/build/android/adb_run_chrome_public
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+optional_url=$1
+
+adb shell am start \
+ -a android.intent.action.VIEW \
+ -n org.chromium.chrome/com.google.android.apps.chrome.Main \
+ ${optional_url:+-d "$optional_url"}
diff --git a/build/android/adb_run_chrome_shell b/build/android/adb_run_chrome_shell
new file mode 100755
index 00000000000..79c4c32b5c3
--- /dev/null
+++ b/build/android/adb_run_chrome_shell
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright 2014 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.
+
+optional_url=$1
+
+adb shell am start \
+ -a android.intent.action.VIEW \
+ -n org.chromium.chrome.shell/.ChromeShellActivity \
+ ${optional_url:+-d "$optional_url"}
diff --git a/build/android/adb_run_content_shell b/build/android/adb_run_content_shell
new file mode 100755
index 00000000000..3f01f3bf02f
--- /dev/null
+++ b/build/android/adb_run_content_shell
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright (c) 2012 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.
+
+optional_url=$1
+
+adb shell am start \
+ -a android.intent.action.VIEW \
+ -n org.chromium.content_shell_apk/.ContentShellActivity \
+ ${optional_url:+-d "$optional_url"}
diff --git a/build/android/adb_run_mojo_shell b/build/android/adb_run_mojo_shell
new file mode 100755
index 00000000000..b585e4a71f6
--- /dev/null
+++ b/build/android/adb_run_mojo_shell
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2013 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.
+
+optional_url=$1
+parameters=$2
+
+adb logcat -c
+adb shell am start -S \
+ -a android.intent.action.VIEW \
+ -n org.chromium.mojo_shell_apk/.MojoShellActivity \
+ ${parameters:+--esa parameters "$parameters"} \
+ ${optional_url:+-d "$optional_url"}
+adb logcat -s MojoShellApplication MojoShellActivity chromium
diff --git a/build/android/android_no_jni_exports.lst b/build/android/android_no_jni_exports.lst
new file mode 100644
index 00000000000..ffc6cf7028c
--- /dev/null
+++ b/build/android/android_no_jni_exports.lst
@@ -0,0 +1,17 @@
+# Copyright 2014 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 script makes all JNI exported symbols local, to prevent the JVM from
+# being able to find them, enforcing use of manual JNI function registration.
+# This is used for all Android binaries by default, unless they explicitly state
+# that they want JNI exported symbols to remain visible, as we need to ensure
+# the manual registration path is correct to maintain compatibility with the
+# crazy linker.
+# Check ld version script manual:
+# https://sourceware.org/binutils/docs-2.24/ld/VERSION.html#VERSION
+
+{
+ local:
+ Java_*;
+};
diff --git a/build/android/ant/BUILD.gn b/build/android/ant/BUILD.gn
new file mode 100644
index 00000000000..a30fb54bf4a
--- /dev/null
+++ b/build/android/ant/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2015 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.
+
+copy("keystore") {
+ sources = [
+ "chromium-debug.keystore",
+ ]
+
+ outputs = [
+ "$root_out_dir/chromium-debug.keystore",
+ ]
+}
diff --git a/build/android/ant/apk-package.xml b/build/android/ant/apk-package.xml
new file mode 100644
index 00000000000..e8b76f7e453
--- /dev/null
+++ b/build/android/ant/apk-package.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/android/ant/chromium-debug.keystore b/build/android/ant/chromium-debug.keystore
new file mode 100644
index 00000000000..67eb0aa34c5
Binary files /dev/null and b/build/android/ant/chromium-debug.keystore differ
diff --git a/build/android/ant/empty/res/.keep b/build/android/ant/empty/res/.keep
new file mode 100644
index 00000000000..1fd038b8cf3
--- /dev/null
+++ b/build/android/ant/empty/res/.keep
@@ -0,0 +1,2 @@
+# This empty res folder can be passed to aapt while building Java libraries or
+# APKs that don't have any resources.
diff --git a/build/android/apkbuilder_action.gypi b/build/android/apkbuilder_action.gypi
new file mode 100644
index 00000000000..27807d861fb
--- /dev/null
+++ b/build/android/apkbuilder_action.gypi
@@ -0,0 +1,79 @@
+# Copyright 2015 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 file is a helper to java_apk.gypi. It should be used to create an
+# action that runs ApkBuilder via ANT.
+#
+# Required variables:
+# apk_name - File name (minus path & extension) of the output apk.
+# apk_path - Path to output apk.
+# package_input_paths - Late-evaluated list of resource zips.
+# native_libs_dir - Path to lib/ directory to use. Set to an empty directory
+# if no native libs are needed.
+# Optional variables:
+# has_code - Whether to include classes.dex in the apk.
+# dex_path - Path to classes.dex. Used only when has_code=1.
+# extra_inputs - List of extra action inputs.
+{
+ 'variables': {
+ 'variables': {
+ 'has_code%': 1,
+ },
+ 'conditions': [
+ ['has_code == 0', {
+ 'has_code_str': 'false',
+ }, {
+ 'has_code_str': 'true',
+ }],
+ ],
+ 'has_code%': '<(has_code)',
+ 'extra_inputs%': [],
+ # Write the inputs list to a file, so that its mtime is updated when
+ # the list of inputs changes.
+ 'inputs_list_file': '>|(apk_package.<(_target_name).<(apk_name).gypcmd >@(package_input_paths))',
+ 'resource_packaged_apk_name': '<(apk_name)-resources.ap_',
+ 'resource_packaged_apk_path': '<(intermediate_dir)/<(resource_packaged_apk_name)',
+ },
+ 'action_name': 'apkbuilder_<(apk_name)',
+ 'message': 'Packaging <(apk_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/ant/apk-package.xml',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/ant.py',
+ '<(resource_packaged_apk_path)',
+ '<@(extra_inputs)',
+ '>@(package_input_paths)',
+ '>(inputs_list_file)',
+ ],
+ 'outputs': [
+ '<(apk_path)',
+ ],
+ 'conditions': [
+ ['has_code == 1', {
+ 'inputs': ['<(dex_path)'],
+ 'action': [
+ '-DDEX_FILE_PATH=<(dex_path)',
+ ]
+ }],
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/ant.py',
+ '--',
+ '-quiet',
+ '-DHAS_CODE=<(has_code_str)',
+ '-DANDROID_SDK_ROOT=<(android_sdk_root)',
+ '-DANDROID_SDK_TOOLS=<(android_sdk_tools)',
+ '-DRESOURCE_PACKAGED_APK_NAME=<(resource_packaged_apk_name)',
+ '-DNATIVE_LIBS_DIR=<(native_libs_dir)',
+ '-DAPK_NAME=<(apk_name)',
+ '-DCONFIGURATION_NAME=<(CONFIGURATION_NAME)',
+ '-DOUT_DIR=<(intermediate_dir)',
+ '-DUNSIGNED_APK_PATH=<(apk_path)',
+ '-DEMMA_INSTRUMENT=<(emma_instrument)',
+ '-DEMMA_DEVICE_JAR=<(emma_device_jar)',
+ '-Dbasedir=.',
+ '-buildfile',
+ '<(DEPTH)/build/android/ant/apk-package.xml',
+ ]
+}
diff --git a/build/android/asan_symbolize.py b/build/android/asan_symbolize.py
new file mode 100755
index 00000000000..10087a637ae
--- /dev/null
+++ b/build/android/asan_symbolize.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+
+import collections
+import optparse
+import os
+import re
+import sys
+
+from pylib import constants
+
+# Uses symbol.py from third_party/android_platform, not python's.
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT,
+ 'third_party/android_platform/development/scripts'))
+import symbol
+
+
+_RE_ASAN = re.compile(r'(.*?)(#\S*?) (\S*?) \((.*?)\+(.*?)\)')
+
+def _ParseAsanLogLine(line):
+ m = re.match(_RE_ASAN, line)
+ if not m:
+ return None
+ return {
+ 'prefix': m.group(1),
+ 'library': m.group(4),
+ 'pos': m.group(2),
+ 'rel_address': '%08x' % int(m.group(5), 16),
+ }
+
+
+def _FindASanLibraries():
+ asan_lib_dir = os.path.join(constants.DIR_SOURCE_ROOT,
+ 'third_party', 'llvm-build',
+ 'Release+Asserts', 'lib')
+ asan_libs = []
+ for src_dir, _, files in os.walk(asan_lib_dir):
+ asan_libs += [os.path.relpath(os.path.join(src_dir, f))
+ for f in files
+ if f.endswith('.so')]
+ return asan_libs
+
+
+def _TranslateLibPath(library, asan_libs):
+ for asan_lib in asan_libs:
+ if os.path.basename(library) == os.path.basename(asan_lib):
+ return '/' + asan_lib
+ return symbol.TranslateLibPath(library)
+
+
+def _Symbolize(asan_input):
+ asan_libs = _FindASanLibraries()
+ libraries = collections.defaultdict(list)
+ asan_lines = []
+ for asan_log_line in [a.rstrip() for a in asan_input]:
+ m = _ParseAsanLogLine(asan_log_line)
+ if m:
+ libraries[m['library']].append(m)
+ asan_lines.append({'raw_log': asan_log_line, 'parsed': m})
+
+ all_symbols = collections.defaultdict(dict)
+ for library, items in libraries.iteritems():
+ libname = _TranslateLibPath(library, asan_libs)
+ lib_relative_addrs = set([i['rel_address'] for i in items])
+ info_dict = symbol.SymbolInformationForSet(libname,
+ lib_relative_addrs,
+ True)
+ if info_dict:
+ all_symbols[library]['symbols'] = info_dict
+
+ for asan_log_line in asan_lines:
+ m = asan_log_line['parsed']
+ if not m:
+ print asan_log_line['raw_log']
+ continue
+ if (m['library'] in all_symbols and
+ m['rel_address'] in all_symbols[m['library']]['symbols']):
+ s = all_symbols[m['library']]['symbols'][m['rel_address']][0]
+ print '%s%s %s %s' % (m['prefix'], m['pos'], s[0], s[1])
+ else:
+ print asan_log_line['raw_log']
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-l', '--logcat',
+ help='File containing adb logcat output with ASan stacks. '
+ 'Use stdin if not specified.')
+ options, _ = parser.parse_args()
+ if options.logcat:
+ asan_input = file(options.logcat, 'r')
+ else:
+ asan_input = sys.stdin
+ _Symbolize(asan_input.readlines())
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/build/android/avd.py b/build/android/avd.py
new file mode 100755
index 00000000000..c45544f8bc7
--- /dev/null
+++ b/build/android/avd.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+"""Launches Android Virtual Devices with a set configuration for testing Chrome.
+
+The script will launch a specified number of Android Virtual Devices (AVD's).
+"""
+
+
+import install_emulator_deps
+import logging
+import optparse
+import os
+import re
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.utils import emulator
+
+
+def main(argv):
+ # ANDROID_SDK_ROOT needs to be set to the location of the SDK used to launch
+ # the emulator to find the system images upon launch.
+ emulator_sdk = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk')
+ os.environ['ANDROID_SDK_ROOT'] = emulator_sdk
+
+ opt_parser = optparse.OptionParser(description='AVD script.')
+ opt_parser.add_option('--name', help='Optinaly, name of existing AVD to '
+ 'launch. If not specified, new AVD\'s will be created')
+ opt_parser.add_option('-n', '--num', dest='emulator_count',
+ help='Number of emulators to launch (default is 1).',
+ type='int', default='1')
+ opt_parser.add_option('--abi', default='x86',
+ help='Platform of emulators to launch (x86 default).')
+ opt_parser.add_option('--api-level', dest='api_level',
+ help='API level for the image, e.g. 19 for Android 4.4',
+ type='int', default=constants.ANDROID_SDK_VERSION)
+
+ options, _ = opt_parser.parse_args(argv[1:])
+
+ logging.basicConfig(level=logging.INFO,
+ format='# %(asctime)-15s: %(message)s')
+ logging.root.setLevel(logging.INFO)
+
+ # Check if KVM is enabled for x86 AVD's and check for x86 system images.
+ # TODO(andrewhayden) Since we can fix all of these with install_emulator_deps
+ # why don't we just run it?
+ if options.abi == 'x86':
+ if not install_emulator_deps.CheckKVM():
+ logging.critical('ERROR: KVM must be enabled in BIOS, and installed. '
+ 'Enable KVM in BIOS and run install_emulator_deps.py')
+ return 1
+ elif not install_emulator_deps.CheckX86Image(options.api_level):
+ logging.critical('ERROR: System image for x86 AVD not installed. Run '
+ 'install_emulator_deps.py')
+ return 1
+
+ if not install_emulator_deps.CheckSDK():
+ logging.critical('ERROR: Emulator SDK not installed. Run '
+ 'install_emulator_deps.py.')
+ return 1
+
+ # If AVD is specified, check that the SDK has the required target. If not,
+ # check that the SDK has the desired target for the temporary AVD's.
+ api_level = options.api_level
+ if options.name:
+ android = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk', 'tools',
+ 'android')
+ avds_output = cmd_helper.GetCmdOutput([android, 'list', 'avd'])
+ names = re.findall(r'Name: (\w+)', avds_output)
+ api_levels = re.findall(r'API level (\d+)', avds_output)
+ try:
+ avd_index = names.index(options.name)
+ except ValueError:
+ logging.critical('ERROR: Specified AVD %s does not exist.' % options.name)
+ return 1
+ api_level = int(api_levels[avd_index])
+
+ if not install_emulator_deps.CheckSDKPlatform(api_level):
+ logging.critical('ERROR: Emulator SDK missing required target for API %d. '
+ 'Run install_emulator_deps.py.')
+ return 1
+
+ if options.name:
+ emulator.LaunchEmulator(options.name, options.abi)
+ else:
+ emulator.LaunchTempEmulators(options.emulator_count, options.abi,
+ options.api_level, True)
+
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/bb_run_sharded_steps.py b/build/android/bb_run_sharded_steps.py
new file mode 100755
index 00000000000..6aeba5b5f58
--- /dev/null
+++ b/build/android/bb_run_sharded_steps.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""DEPRECATED!
+TODO(bulach): remove me once all other repositories reference
+'test_runner.py perf' directly.
+"""
+
+import optparse
+import sys
+
+from pylib import cmd_helper
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-s', '--steps',
+ help='A JSON file containing all the steps to be '
+ 'sharded.')
+ parser.add_option('--flaky_steps',
+ help='A JSON file containing steps that are flaky and '
+ 'will have its exit code ignored.')
+ parser.add_option('-p', '--print_results',
+ help='Only prints the results for the previously '
+ 'executed step, do not run it again.')
+ options, _ = parser.parse_args(argv)
+ if options.print_results:
+ return cmd_helper.RunCmd(['build/android/test_runner.py', 'perf',
+ '--print-step', options.print_results])
+ flaky_options = []
+ if options.flaky_steps:
+ flaky_options = ['--flaky-steps', options.flaky_steps]
+ return cmd_helper.RunCmd(['build/android/test_runner.py', 'perf', '-v',
+ '--steps', options.steps] + flaky_options)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/buildbot/OWNERS b/build/android/buildbot/OWNERS
new file mode 100644
index 00000000000..f289720120e
--- /dev/null
+++ b/build/android/buildbot/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+cmp@chromium.org
+jbudorick@chromium.org
+navabi@chromium.org
+
diff --git a/build/android/buildbot/bb_annotations.py b/build/android/buildbot/bb_annotations.py
new file mode 100644
index 00000000000..059d673188d
--- /dev/null
+++ b/build/android/buildbot/bb_annotations.py
@@ -0,0 +1,46 @@
+# Copyright 2013 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.
+
+"""Helper functions to print buildbot messages."""
+
+def PrintLink(label, url):
+ """Adds a link with name |label| linking to |url| to current buildbot step.
+
+ Args:
+ label: A string with the name of the label.
+ url: A string of the URL.
+ """
+ print '@@@STEP_LINK@%s@%s@@@' % (label, url)
+
+
+def PrintMsg(msg):
+ """Appends |msg| to the current buildbot step text.
+
+ Args:
+ msg: String to be appended.
+ """
+ print '@@@STEP_TEXT@%s@@@' % msg
+
+
+def PrintSummaryText(msg):
+ """Appends |msg| to main build summary. Visible from waterfall.
+
+ Args:
+ msg: String to be appended.
+ """
+ print '@@@STEP_SUMMARY_TEXT@%s@@@' % msg
+
+
+def PrintError():
+ """Marks the current step as failed."""
+ print '@@@STEP_FAILURE@@@'
+
+
+def PrintWarning():
+ """Marks the current step with a warning."""
+ print '@@@STEP_WARNINGS@@@'
+
+
+def PrintNamedStep(step):
+ print '@@@BUILD_STEP %s@@@' % step
diff --git a/build/android/buildbot/bb_device_status_check.py b/build/android/buildbot/bb_device_status_check.py
new file mode 100755
index 00000000000..917c51e28dc
--- /dev/null
+++ b/build/android/buildbot/bb_device_status_check.py
@@ -0,0 +1,404 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""A class to keep track of devices across builds and report state."""
+import json
+import logging
+import optparse
+import os
+import psutil
+import re
+import signal
+import smtplib
+import subprocess
+import sys
+import time
+import urllib
+
+import bb_annotations
+import bb_utils
+
+sys.path.append(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, 'util', 'lib',
+ 'common'))
+import perf_tests_results_helper # pylint: disable=F0401
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pylib import constants
+from pylib.cmd_helper import GetCmdOutput
+from pylib.device import adb_wrapper
+from pylib.device import battery_utils
+from pylib.device import device_blacklist
+from pylib.device import device_errors
+from pylib.device import device_list
+from pylib.device import device_utils
+from pylib.utils import run_tests_helper
+
+_RE_DEVICE_ID = re.compile('Device ID = (\d+)')
+
+def DeviceInfo(device, options):
+ """Gathers info on a device via various adb calls.
+
+ Args:
+ device: A DeviceUtils instance for the device to construct info about.
+
+ Returns:
+ Tuple of device type, build id, report as a string, error messages, and
+ boolean indicating whether or not device can be used for testing.
+ """
+ battery = battery_utils.BatteryUtils(device)
+
+ build_product = ''
+ build_id = ''
+ battery_level = 100
+ errors = []
+ dev_good = True
+ json_data = {}
+
+ try:
+ build_product = device.build_product
+ build_id = device.build_id
+
+ json_data = {
+ 'serial': device.adb.GetDeviceSerial(),
+ 'type': build_product,
+ 'build': build_id,
+ 'build_detail': device.GetProp('ro.build.fingerprint'),
+ 'battery': {},
+ 'imei_slice': 'Unknown',
+ 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'),
+ }
+
+ battery_info = {}
+ try:
+ battery_info = battery.GetBatteryInfo(timeout=5)
+ battery_level = int(battery_info.get('level', battery_level))
+ json_data['battery'] = battery_info
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to get battery information for %s', str(device))
+
+ try:
+ for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'],
+ check_return=True, timeout=5):
+ m = _RE_DEVICE_ID.match(l)
+ if m:
+ json_data['imei_slice'] = m.group(1)[-6:]
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to get IMEI slice for %s', str(device))
+
+ if battery_level < 15:
+ errors += ['Device critically low in battery.']
+ dev_good = False
+ if not battery.GetCharging():
+ battery.SetCharging(True)
+ if not options.no_provisioning_check:
+ setup_wizard_disabled = (
+ device.GetProp('ro.setupwizard.mode') == 'DISABLED')
+ if not setup_wizard_disabled and device.build_type != 'user':
+ errors += ['Setup wizard not disabled. Was it provisioned correctly?']
+ if (device.product_name == 'mantaray' and
+ battery_info.get('AC powered', None) != 'true'):
+ errors += ['Mantaray device not connected to AC power.']
+ except device_errors.CommandFailedError:
+ logging.exception('Failure while getting device status.')
+ dev_good = False
+ except device_errors.CommandTimeoutError:
+ logging.exception('Timeout while getting device status.')
+ dev_good = False
+
+ return (build_product, build_id, battery_level, errors, dev_good, json_data)
+
+
+def CheckForMissingDevices(options, devices):
+ """Uses file of previous online devices to detect broken phones.
+
+ Args:
+ options: out_dir parameter of options argument is used as the base
+ directory to load and update the cache file.
+ devices: A list of DeviceUtils instance for the currently visible and
+ online attached devices.
+ """
+ out_dir = os.path.abspath(options.out_dir)
+ device_serials = set(d.adb.GetDeviceSerial() for d in devices)
+
+ # last_devices denotes all known devices prior to this run
+ last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME)
+ last_missing_devices_path = os.path.join(out_dir,
+ device_list.LAST_MISSING_DEVICES_FILENAME)
+ try:
+ last_devices = device_list.GetPersistentDeviceList(last_devices_path)
+ except IOError:
+ # Ignore error, file might not exist
+ last_devices = []
+
+ try:
+ last_missing_devices = device_list.GetPersistentDeviceList(
+ last_missing_devices_path)
+ except IOError:
+ last_missing_devices = []
+
+ missing_devs = list(set(last_devices) - device_serials)
+ new_missing_devs = list(set(missing_devs) - set(last_missing_devices))
+
+ if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'):
+ logging.info('new_missing_devs %s' % new_missing_devs)
+ devices_missing_msg = '%d devices not detected.' % len(missing_devs)
+ bb_annotations.PrintSummaryText(devices_missing_msg)
+
+ from_address = 'chrome-bot@chromium.org'
+ to_addresses = ['chrome-labs-tech-ticket@google.com',
+ 'chrome-android-device-alert@google.com']
+ cc_addresses = ['chrome-android-device-alert@google.com']
+ subject = 'Devices offline on %s, %s, %s' % (
+ os.environ.get('BUILDBOT_SLAVENAME'),
+ os.environ.get('BUILDBOT_BUILDERNAME'),
+ os.environ.get('BUILDBOT_BUILDNUMBER'))
+ msg = ('Please reboot the following devices:\n%s' %
+ '\n'.join(map(str, new_missing_devs)))
+ SendEmail(from_address, to_addresses, cc_addresses, subject, msg)
+
+ all_known_devices = list(device_serials | set(last_devices))
+ device_list.WritePersistentDeviceList(last_devices_path, all_known_devices)
+ device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs)
+
+ if not all_known_devices:
+ # This can happen if for some reason the .last_devices file is not
+ # present or if it was empty.
+ return ['No online devices. Have any devices been plugged in?']
+ if missing_devs:
+ devices_missing_msg = '%d devices not detected.' % len(missing_devs)
+ bb_annotations.PrintSummaryText(devices_missing_msg)
+ return ['Current online devices: %s' % ', '.join(d for d in device_serials),
+ '%s are no longer visible. Were they removed?' % missing_devs]
+ else:
+ new_devs = device_serials - set(last_devices)
+ if new_devs and os.path.exists(last_devices_path):
+ bb_annotations.PrintWarning()
+ bb_annotations.PrintSummaryText(
+ '%d new devices detected' % len(new_devs))
+ logging.info('New devices detected:')
+ for d in new_devs:
+ logging.info(' %s', d)
+
+
+def SendEmail(from_address, to_addresses, cc_addresses, subject, msg):
+ msg_body = '\r\n'.join(['From: %s' % from_address,
+ 'To: %s' % ', '.join(to_addresses),
+ 'CC: %s' % ', '.join(cc_addresses),
+ 'Subject: %s' % subject, '', msg])
+ try:
+ server = smtplib.SMTP('localhost')
+ server.sendmail(from_address, to_addresses, msg_body)
+ server.quit()
+ except Exception:
+ logging.exception('Failed to send alert email.')
+
+
+def RestartUsb():
+ if not os.path.isfile('/usr/bin/restart_usb'):
+ logging.error('Could not restart usb. ''/usr/bin/restart_usb not '
+ 'installed on host (see BUG=305769).')
+ return False
+
+ lsusb_proc = bb_utils.SpawnCmd(['lsusb'], stdout=subprocess.PIPE)
+ lsusb_output, _ = lsusb_proc.communicate()
+ if lsusb_proc.returncode:
+ logging.error('Could not get list of USB ports (i.e. lsusb).')
+ return lsusb_proc.returncode
+
+ usb_devices = [re.findall(r'Bus (\d\d\d) Device (\d\d\d)', lsusb_line)[0]
+ for lsusb_line in lsusb_output.strip().split('\n')]
+
+ all_restarted = True
+ # Walk USB devices from leaves up (i.e reverse sorted) restarting the
+ # connection. If a parent node (e.g. usb hub) is restarted before the
+ # devices connected to it, the (bus, dev) for the hub can change, making the
+ # output we have wrong. This way we restart the devices before the hub.
+ for (bus, dev) in reversed(sorted(usb_devices)):
+ # Can not restart root usb connections
+ if dev != '001':
+ return_code = bb_utils.RunCmd(['/usr/bin/restart_usb', bus, dev])
+ if return_code:
+ logging.error('Error restarting USB device /dev/bus/usb/%s/%s',
+ bus, dev)
+ all_restarted = False
+ else:
+ logging.info('Restarted USB device /dev/bus/usb/%s/%s', bus, dev)
+
+ return all_restarted
+
+
+def KillAllAdb():
+ def GetAllAdb():
+ for p in psutil.process_iter():
+ try:
+ if 'adb' in p.name:
+ yield p
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+
+ for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]:
+ for p in GetAllAdb():
+ try:
+ logging.info('kill %d %d (%s [%s])', sig, p.pid, p.name,
+ ' '.join(p.cmdline))
+ p.send_signal(sig)
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ for p in GetAllAdb():
+ try:
+ logging.error('Unable to kill %d (%s [%s])', p.pid, p.name,
+ ' '.join(p.cmdline))
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('', '--out-dir',
+ help='Directory where the device path is stored',
+ default=os.path.join(constants.DIR_SOURCE_ROOT, 'out'))
+ parser.add_option('--no-provisioning-check', action='store_true',
+ help='Will not check if devices are provisioned properly.')
+ parser.add_option('--device-status-dashboard', action='store_true',
+ help='Output device status data for dashboard.')
+ parser.add_option('--restart-usb', action='store_true',
+ help='Restart USB ports before running device check.')
+ parser.add_option('--json-output',
+ help='Output JSON information into a specified file.')
+ parser.add_option('-v', '--verbose', action='count', default=1,
+ help='Log more information.')
+
+ options, args = parser.parse_args()
+ if args:
+ parser.error('Unknown options %s' % args)
+
+ run_tests_helper.SetLogLevel(options.verbose)
+
+ # Remove the last build's "bad devices" before checking device statuses.
+ device_blacklist.ResetBlacklist()
+
+ try:
+ expected_devices = device_list.GetPersistentDeviceList(
+ os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME))
+ except IOError:
+ expected_devices = []
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ device_serials = [d.adb.GetDeviceSerial() for d in devices]
+ # Only restart usb if devices are missing.
+ if set(expected_devices) != set(device_serials):
+ logging.warning('expected_devices: %s', expected_devices)
+ logging.warning('devices: %s', device_serials)
+ KillAllAdb()
+ retries = 5
+ usb_restarted = True
+ if options.restart_usb:
+ if not RestartUsb():
+ usb_restarted = False
+ bb_annotations.PrintWarning()
+ logging.error('USB reset stage failed, '
+ 'wait for any device to come back.')
+ while retries:
+ logging.info('retry adb devices...')
+ time.sleep(1)
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ device_serials = [d.adb.GetDeviceSerial() for d in devices]
+ if set(expected_devices) == set(device_serials):
+ # All devices are online, keep going.
+ break
+ if not usb_restarted and devices:
+ # The USB wasn't restarted, but there's at least one device online.
+ # No point in trying to wait for all devices.
+ break
+ retries -= 1
+
+ types, builds, batteries, errors, devices_ok, json_data = (
+ [], [], [], [], [], [])
+ if devices:
+ types, builds, batteries, errors, devices_ok, json_data = (
+ zip(*[DeviceInfo(dev, options) for dev in devices]))
+
+ # Write device info to file for buildbot info display.
+ if os.path.exists('/home/chrome-bot'):
+ with open('/home/chrome-bot/.adb_device_info', 'w') as f:
+ for device in json_data:
+ try:
+ f.write('%s %s %s %.1fC %s%%\n' % (device['serial'], device['type'],
+ device['build'], float(device['battery']['temperature']) / 10,
+ device['battery']['level']))
+ except Exception:
+ pass
+
+ err_msg = CheckForMissingDevices(options, devices) or []
+
+ unique_types = list(set(types))
+ unique_builds = list(set(builds))
+
+ bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s'
+ % (len(devices), unique_types, unique_builds))
+
+ for j in json_data:
+ logging.info('Device %s (%s)', j.get('serial'), j.get('type'))
+ logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail'))
+ logging.info(' Current Battery Service state:')
+ for k, v in j.get('battery', {}).iteritems():
+ logging.info(' %s: %s', k, v)
+ logging.info(' IMEI slice: %s', j.get('imei_slice'))
+ logging.info(' WiFi IP: %s', j.get('wifi_ip'))
+
+
+ for dev, dev_errors in zip(devices, errors):
+ if dev_errors:
+ err_msg += ['%s errors:' % str(dev)]
+ err_msg += [' %s' % error for error in dev_errors]
+
+ if err_msg:
+ bb_annotations.PrintWarning()
+ for e in err_msg:
+ logging.error(e)
+ from_address = 'buildbot@chromium.org'
+ to_addresses = ['chromium-android-device-alerts@google.com']
+ bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
+ slave_name = os.environ.get('BUILDBOT_SLAVENAME')
+ subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
+ SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg))
+
+ if options.device_status_dashboard:
+ offline_devices = [
+ device_utils.DeviceUtils(a)
+ for a in adb_wrapper.AdbWrapper.Devices(is_ready=False)
+ if a.GetState() == 'offline']
+
+ perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
+ [len(devices)], 'devices')
+ perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
+ [len(offline_devices)], 'devices',
+ 'unimportant')
+ for dev, battery in zip(devices, batteries):
+ perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev),
+ [battery], '%',
+ 'unimportant')
+
+ if options.json_output:
+ with open(options.json_output, 'wb') as f:
+ f.write(json.dumps(json_data, indent=4))
+
+ num_failed_devs = 0
+ for device_ok, device in zip(devices_ok, devices):
+ if not device_ok:
+ logging.warning('Blacklisting %s', str(device))
+ device_blacklist.ExtendBlacklist([str(device)])
+ num_failed_devs += 1
+
+ if num_failed_devs == len(devices):
+ return 2
+
+ if not devices:
+ return 1
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/buildbot/bb_device_steps.py b/build/android/buildbot/bb_device_steps.py
new file mode 100755
index 00000000000..8ad42b90d15
--- /dev/null
+++ b/build/android/buildbot/bb_device_steps.py
@@ -0,0 +1,796 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+import collections
+import glob
+import hashlib
+import json
+import os
+import random
+import re
+import shutil
+import sys
+
+import bb_utils
+import bb_annotations
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+import provision_devices
+from pylib import constants
+from pylib.device import device_utils
+from pylib.gtest import gtest_config
+
+CHROME_SRC_DIR = bb_utils.CHROME_SRC
+DIR_BUILD_ROOT = os.path.dirname(CHROME_SRC_DIR)
+CHROME_OUT_DIR = bb_utils.CHROME_OUT_DIR
+BLINK_SCRIPTS_DIR = 'third_party/WebKit/Tools/Scripts'
+
+SLAVE_SCRIPTS_DIR = os.path.join(bb_utils.BB_BUILD_DIR, 'scripts', 'slave')
+LOGCAT_DIR = os.path.join(bb_utils.CHROME_OUT_DIR, 'logcat')
+GS_URL = 'https://storage.googleapis.com'
+GS_AUTH_URL = 'https://storage.cloud.google.com'
+
+# Describes an instrumation test suite:
+# test: Name of test we're running.
+# apk: apk to be installed.
+# apk_package: package for the apk to be installed.
+# test_apk: apk to run tests on.
+# test_data: data folder in format destination:source.
+# host_driven_root: The host-driven test root directory.
+# annotation: Annotation of the tests to include.
+# exclude_annotation: The annotation of the tests to exclude.
+I_TEST = collections.namedtuple('InstrumentationTest', [
+ 'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'isolate_file_path',
+ 'host_driven_root', 'annotation', 'exclude_annotation', 'extra_flags'])
+
+
+def SrcPath(*path):
+ return os.path.join(CHROME_SRC_DIR, *path)
+
+
+def I(name, apk, apk_package, test_apk, test_data, isolate_file_path=None,
+ host_driven_root=None, annotation=None, exclude_annotation=None,
+ extra_flags=None):
+ return I_TEST(name, apk, apk_package, test_apk, test_data, isolate_file_path,
+ host_driven_root, annotation, exclude_annotation, extra_flags)
+
+INSTRUMENTATION_TESTS = dict((suite.name, suite) for suite in [
+ I('ContentShell',
+ 'ContentShell.apk',
+ 'org.chromium.content_shell_apk',
+ 'ContentShellTest',
+ 'content:content/test/data/android/device_files',
+ isolate_file_path='content/content_shell_test_apk.isolate'),
+ I('ChromeShell',
+ 'ChromeShell.apk',
+ 'org.chromium.chrome.shell',
+ 'ChromeShellTest',
+ 'chrome:chrome/test/data/android/device_files',
+ isolate_file_path='chrome/chrome_shell_test_apk.isolate',
+ host_driven_root=constants.CHROME_SHELL_HOST_DRIVEN_DIR),
+ I('AndroidWebView',
+ 'AndroidWebView.apk',
+ 'org.chromium.android_webview.shell',
+ 'AndroidWebViewTest',
+ 'webview:android_webview/test/data/device_files',
+ isolate_file_path='android_webview/android_webview_test_apk.isolate'),
+ I('ChromeSyncShell',
+ 'ChromeSyncShell.apk',
+ 'org.chromium.chrome.browser.sync',
+ 'ChromeSyncShellTest',
+ None),
+ ])
+
+InstallablePackage = collections.namedtuple('InstallablePackage', [
+ 'name', 'apk', 'apk_package'])
+
+INSTALLABLE_PACKAGES = dict((package.name, package) for package in (
+ [InstallablePackage(i.name, i.apk, i.apk_package)
+ for i in INSTRUMENTATION_TESTS.itervalues()] +
+ [InstallablePackage('ChromeDriverWebViewShell',
+ 'ChromeDriverWebViewShell.apk',
+ 'org.chromium.chromedriver_webview_shell')]))
+
+VALID_TESTS = set([
+ 'base_junit_tests',
+ 'chromedriver',
+ 'chrome_proxy',
+ 'components_browsertests',
+ 'gfx_unittests',
+ 'gl_unittests',
+ 'gpu',
+ 'python_unittests',
+ 'telemetry_unittests',
+ 'telemetry_perf_unittests',
+ 'ui',
+ 'unit',
+ 'webkit',
+ 'webkit_layout'
+])
+
+RunCmd = bb_utils.RunCmd
+
+
+def _GetRevision(options):
+ """Get the SVN revision number.
+
+ Args:
+ options: options object.
+
+ Returns:
+ The revision number.
+ """
+ revision = options.build_properties.get('got_revision')
+ if not revision:
+ revision = options.build_properties.get('revision', 'testing')
+ return revision
+
+
+def _RunTest(options, cmd, suite):
+ """Run test command with runtest.py.
+
+ Args:
+ options: options object.
+ cmd: the command to run.
+ suite: test name.
+ """
+ property_args = bb_utils.EncodeProperties(options)
+ args = [os.path.join(SLAVE_SCRIPTS_DIR, 'runtest.py')] + property_args
+ args += ['--test-platform', 'android']
+ if options.factory_properties.get('generate_gtest_json'):
+ args.append('--generate-json-file')
+ args += ['-o', 'gtest-results/%s' % suite,
+ '--annotate', 'gtest',
+ '--build-number', str(options.build_properties.get('buildnumber',
+ '')),
+ '--builder-name', options.build_properties.get('buildername', '')]
+ if options.target == 'Release':
+ args += ['--target', 'Release']
+ else:
+ args += ['--target', 'Debug']
+ if options.flakiness_server:
+ args += ['--flakiness-dashboard-server=%s' %
+ options.flakiness_server]
+ args += cmd
+ RunCmd(args, cwd=DIR_BUILD_ROOT)
+
+
+def RunTestSuites(options, suites, suites_options=None):
+ """Manages an invocation of test_runner.py for gtests.
+
+ Args:
+ options: options object.
+ suites: List of suite names to run.
+ suites_options: Command line options dictionary for particular suites.
+ For example,
+ {'content_browsertests', ['--num_retries=1', '--release']}
+ will add the options only to content_browsertests.
+ """
+
+ if not suites_options:
+ suites_options = {}
+
+ args = ['--verbose']
+ if options.target == 'Release':
+ args.append('--release')
+ if options.asan:
+ args.append('--tool=asan')
+ if options.gtest_filter:
+ args.append('--gtest-filter=%s' % options.gtest_filter)
+
+ for suite in suites:
+ bb_annotations.PrintNamedStep(suite)
+ cmd = [suite] + args
+ cmd += suites_options.get(suite, [])
+ if suite == 'content_browsertests' or suite == 'components_browsertests':
+ cmd.append('--num_retries=1')
+ _RunTest(options, cmd, suite)
+
+
+def RunJunitSuite(suite):
+ bb_annotations.PrintNamedStep(suite)
+ RunCmd(['build/android/test_runner.py', 'junit', '-s', suite])
+
+
+def RunChromeDriverTests(options):
+ """Run all the steps for running chromedriver tests."""
+ bb_annotations.PrintNamedStep('chromedriver_annotation')
+ RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py',
+ '--android-packages=%s,%s,%s,%s' %
+ ('chrome_shell',
+ 'chrome_stable',
+ 'chrome_beta',
+ 'chromedriver_webview_shell'),
+ '--revision=%s' % _GetRevision(options),
+ '--update-log'])
+
+def RunChromeProxyTests(options):
+ """Run the chrome_proxy tests.
+
+ Args:
+ options: options object.
+ """
+ InstallApk(options, INSTRUMENTATION_TESTS['ChromeShell'], False)
+ args = ['--browser', 'android-chrome-shell']
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if devices:
+ args = args + ['--device', devices[0].adb.GetDeviceSerial()]
+ bb_annotations.PrintNamedStep('chrome_proxy')
+ RunCmd(['tools/chrome_proxy/run_tests'] + args)
+
+
+def RunTelemetryTests(options, step_name, run_tests_path):
+ """Runs either telemetry_perf_unittests or telemetry_unittests.
+
+ Args:
+ options: options object.
+ step_name: either 'telemetry_unittests' or 'telemetry_perf_unittests'
+ run_tests_path: path to run_tests script (tools/perf/run_tests for
+ perf_unittests and tools/telemetry/run_tests for
+ telemetry_unittests)
+ """
+ InstallApk(options, INSTRUMENTATION_TESTS['ChromeShell'], False)
+ args = ['--browser', 'android-chrome-shell']
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if devices:
+ args = args + ['--device', 'android']
+ bb_annotations.PrintNamedStep(step_name)
+ RunCmd([run_tests_path] + args)
+
+
+def InstallApk(options, test, print_step=False):
+ """Install an apk to all phones.
+
+ Args:
+ options: options object
+ test: An I_TEST namedtuple
+ print_step: Print a buildbot step
+ """
+ if print_step:
+ bb_annotations.PrintNamedStep('install_%s' % test.name.lower())
+
+ args = ['--apk_package', test.apk_package]
+ if options.target == 'Release':
+ args.append('--release')
+ args.append(test.apk)
+
+ RunCmd(['build/android/adb_install_apk.py'] + args, halt_on_failure=True)
+
+
+def RunInstrumentationSuite(options, test, flunk_on_failure=True,
+ python_only=False, official_build=False):
+ """Manages an invocation of test_runner.py for instrumentation tests.
+
+ Args:
+ options: options object
+ test: An I_TEST namedtuple
+ flunk_on_failure: Flunk the step if tests fail.
+ Python: Run only host driven Python tests.
+ official_build: Run official-build tests.
+ """
+ bb_annotations.PrintNamedStep('%s_instrumentation_tests' % test.name.lower())
+
+ if test.apk:
+ InstallApk(options, test)
+ args = ['--test-apk', test.test_apk, '--verbose']
+ if test.test_data:
+ args.extend(['--test_data', test.test_data])
+ if options.target == 'Release':
+ args.append('--release')
+ if options.asan:
+ args.append('--tool=asan')
+ if options.flakiness_server:
+ args.append('--flakiness-dashboard-server=%s' %
+ options.flakiness_server)
+ if options.coverage_bucket:
+ args.append('--coverage-dir=%s' % options.coverage_dir)
+ if test.isolate_file_path:
+ args.append('--isolate-file-path=%s' % test.isolate_file_path)
+ if test.host_driven_root:
+ args.append('--host-driven-root=%s' % test.host_driven_root)
+ if test.annotation:
+ args.extend(['-A', test.annotation])
+ if test.exclude_annotation:
+ args.extend(['-E', test.exclude_annotation])
+ if test.extra_flags:
+ args.extend(test.extra_flags)
+ if python_only:
+ args.append('-p')
+ if official_build:
+ # The option needs to be assigned 'True' as it does not have an action
+ # associated with it.
+ args.append('--official-build')
+
+ RunCmd(['build/android/test_runner.py', 'instrumentation'] + args,
+ flunk_on_failure=flunk_on_failure)
+
+
+def RunWebkitLint():
+ """Lint WebKit's TestExpectation files."""
+ bb_annotations.PrintNamedStep('webkit_lint')
+ RunCmd([SrcPath(os.path.join(BLINK_SCRIPTS_DIR, 'lint-test-expectations'))])
+
+
+def RunWebkitLayoutTests(options):
+ """Run layout tests on an actual device."""
+ bb_annotations.PrintNamedStep('webkit_tests')
+ cmd_args = [
+ '--no-show-results',
+ '--no-new-test-results',
+ '--full-results-html',
+ '--clobber-old-results',
+ '--exit-after-n-failures', '5000',
+ '--exit-after-n-crashes-or-timeouts', '100',
+ '--debug-rwt-logging',
+ '--results-directory', '../layout-test-results',
+ '--target', options.target,
+ '--builder-name', options.build_properties.get('buildername', ''),
+ '--build-number', str(options.build_properties.get('buildnumber', '')),
+ '--master-name', 'ChromiumWebkit', # TODO: Get this from the cfg.
+ '--build-name', options.build_properties.get('buildername', ''),
+ '--platform=android']
+
+ for flag in 'test_results_server', 'driver_name', 'additional_driver_flag':
+ if flag in options.factory_properties:
+ cmd_args.extend(['--%s' % flag.replace('_', '-'),
+ options.factory_properties.get(flag)])
+
+ for f in options.factory_properties.get('additional_expectations', []):
+ cmd_args.extend(
+ ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)])
+
+ # TODO(dpranke): Remove this block after
+ # https://codereview.chromium.org/12927002/ lands.
+ for f in options.factory_properties.get('additional_expectations_files', []):
+ cmd_args.extend(
+ ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)])
+
+ exit_code = RunCmd(
+ [SrcPath(os.path.join(BLINK_SCRIPTS_DIR, 'run-webkit-tests'))] + cmd_args)
+ if exit_code == 255: # test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
+ bb_annotations.PrintMsg('?? (crashed or hung)')
+ elif exit_code == 254: # test_run_results.NO_DEVICES_EXIT_STATUS
+ bb_annotations.PrintMsg('?? (no devices found)')
+ elif exit_code == 253: # test_run_results.NO_TESTS_EXIT_STATUS
+ bb_annotations.PrintMsg('?? (no tests found)')
+ else:
+ full_results_path = os.path.join('..', 'layout-test-results',
+ 'full_results.json')
+ if os.path.exists(full_results_path):
+ full_results = json.load(open(full_results_path))
+ unexpected_passes, unexpected_failures, unexpected_flakes = (
+ _ParseLayoutTestResults(full_results))
+ if unexpected_failures:
+ _PrintDashboardLink('failed', unexpected_failures.keys(),
+ max_tests=25)
+ elif unexpected_passes:
+ _PrintDashboardLink('unexpected passes', unexpected_passes.keys(),
+ max_tests=10)
+ if unexpected_flakes:
+ _PrintDashboardLink('unexpected flakes', unexpected_flakes.keys(),
+ max_tests=10)
+
+ if exit_code == 0 and (unexpected_passes or unexpected_flakes):
+ # If exit_code != 0, RunCmd() will have already printed an error.
+ bb_annotations.PrintWarning()
+ else:
+ bb_annotations.PrintError()
+ bb_annotations.PrintMsg('?? (results missing)')
+
+ if options.factory_properties.get('archive_webkit_results', False):
+ bb_annotations.PrintNamedStep('archive_webkit_results')
+ base = 'https://storage.googleapis.com/chromium-layout-test-archives'
+ builder_name = options.build_properties.get('buildername', '')
+ build_number = str(options.build_properties.get('buildnumber', ''))
+ results_link = '%s/%s/%s/layout-test-results/results.html' % (
+ base, EscapeBuilderName(builder_name), build_number)
+ bb_annotations.PrintLink('results', results_link)
+ bb_annotations.PrintLink('(zip)', '%s/%s/%s/layout-test-results.zip' % (
+ base, EscapeBuilderName(builder_name), build_number))
+ gs_bucket = 'gs://chromium-layout-test-archives'
+ RunCmd([os.path.join(SLAVE_SCRIPTS_DIR, 'chromium',
+ 'archive_layout_test_results.py'),
+ '--results-dir', '../../layout-test-results',
+ '--build-number', build_number,
+ '--builder-name', builder_name,
+ '--gs-bucket', gs_bucket],
+ cwd=DIR_BUILD_ROOT)
+
+
+def _ParseLayoutTestResults(results):
+ """Extract the failures from the test run."""
+ # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results
+ tests = _ConvertTrieToFlatPaths(results['tests'])
+ failures = {}
+ flakes = {}
+ passes = {}
+ for (test, result) in tests.iteritems():
+ if result.get('is_unexpected'):
+ actual_results = result['actual'].split()
+ expected_results = result['expected'].split()
+ if len(actual_results) > 1:
+ # We report the first failure type back, even if the second
+ # was more severe.
+ if actual_results[1] in expected_results:
+ flakes[test] = actual_results[0]
+ else:
+ failures[test] = actual_results[0]
+ elif actual_results[0] == 'PASS':
+ passes[test] = result
+ else:
+ failures[test] = actual_results[0]
+
+ return (passes, failures, flakes)
+
+
+def _ConvertTrieToFlatPaths(trie, prefix=None):
+ """Flatten the trie of failures into a list."""
+ # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results
+ result = {}
+ for name, data in trie.iteritems():
+ if prefix:
+ name = prefix + '/' + name
+
+ if len(data) and 'actual' not in data and 'expected' not in data:
+ result.update(_ConvertTrieToFlatPaths(data, name))
+ else:
+ result[name] = data
+
+ return result
+
+
+def _PrintDashboardLink(link_text, tests, max_tests):
+ """Add a link to the flakiness dashboard in the step annotations."""
+ if len(tests) > max_tests:
+ test_list_text = ' '.join(tests[:max_tests]) + ' and more'
+ else:
+ test_list_text = ' '.join(tests)
+
+ dashboard_base = ('http://test-results.appspot.com'
+ '/dashboards/flakiness_dashboard.html#'
+ 'master=ChromiumWebkit&tests=')
+
+ bb_annotations.PrintLink('%d %s: %s' %
+ (len(tests), link_text, test_list_text),
+ dashboard_base + ','.join(tests))
+
+
+def EscapeBuilderName(builder_name):
+ return re.sub('[ ()]', '_', builder_name)
+
+
+def SpawnLogcatMonitor():
+ shutil.rmtree(LOGCAT_DIR, ignore_errors=True)
+ bb_utils.SpawnCmd([
+ os.path.join(CHROME_SRC_DIR, 'build', 'android', 'adb_logcat_monitor.py'),
+ LOGCAT_DIR])
+
+ # Wait for logcat_monitor to pull existing logcat
+ RunCmd(['sleep', '5'])
+
+
+def ProvisionDevices(options):
+ bb_annotations.PrintNamedStep('provision_devices')
+
+ if not bb_utils.TESTING:
+ # Restart adb to work around bugs, sleep to wait for usb discovery.
+ device_utils.RestartServer()
+ RunCmd(['sleep', '1'])
+ provision_cmd = ['build/android/provision_devices.py', '-t', options.target]
+ if options.auto_reconnect:
+ provision_cmd.append('--auto-reconnect')
+ if options.skip_wipe:
+ provision_cmd.append('--skip-wipe')
+ if options.disable_location:
+ provision_cmd.append('--disable-location')
+ RunCmd(provision_cmd, halt_on_failure=True)
+
+
+def DeviceStatusCheck(options):
+ bb_annotations.PrintNamedStep('device_status_check')
+ cmd = ['build/android/buildbot/bb_device_status_check.py']
+ if options.restart_usb:
+ cmd.append('--restart-usb')
+ RunCmd(cmd, halt_on_failure=True)
+
+
+def GetDeviceSetupStepCmds():
+ return [
+ ('device_status_check', DeviceStatusCheck),
+ ('provision_devices', ProvisionDevices),
+ ]
+
+
+def RunUnitTests(options):
+ suites = gtest_config.STABLE_TEST_SUITES
+ if options.asan:
+ suites = [s for s in suites
+ if s not in gtest_config.ASAN_EXCLUDED_TEST_SUITES]
+ RunTestSuites(options, suites)
+
+
+def RunTelemetryUnitTests(options):
+ RunTelemetryTests(options, 'telemetry_unittests', 'tools/telemetry/run_tests')
+
+
+def RunTelemetryPerfUnitTests(options):
+ RunTelemetryTests(options, 'telemetry_perf_unittests', 'tools/perf/run_tests')
+
+
+def RunInstrumentationTests(options):
+ for test in INSTRUMENTATION_TESTS.itervalues():
+ RunInstrumentationSuite(options, test)
+
+
+def RunWebkitTests(options):
+ RunTestSuites(options, ['webkit_unit_tests', 'blink_heap_unittests'])
+ RunWebkitLint()
+
+
+def RunGPUTests(options):
+ revision = _GetRevision(options)
+ builder_name = options.build_properties.get('buildername', 'noname')
+
+ bb_annotations.PrintNamedStep('pixel_tests')
+ RunCmd(['content/test/gpu/run_gpu_test.py',
+ 'pixel', '-v',
+ '--browser',
+ 'android-content-shell',
+ '--build-revision',
+ str(revision),
+ '--upload-refimg-to-cloud-storage',
+ '--refimg-cloud-storage-bucket',
+ 'chromium-gpu-archive/reference-images',
+ '--os-type',
+ 'android',
+ '--test-machine-name',
+ EscapeBuilderName(builder_name)])
+
+ bb_annotations.PrintNamedStep('webgl_conformance_tests')
+ RunCmd(['content/test/gpu/run_gpu_test.py', '-v',
+ '--browser=android-content-shell', 'webgl_conformance',
+ '--webgl-conformance-version=1.0.1'])
+
+ bb_annotations.PrintNamedStep('android_webview_webgl_conformance_tests')
+ RunCmd(['content/test/gpu/run_gpu_test.py', '-v',
+ '--browser=android-webview-shell', 'webgl_conformance',
+ '--webgl-conformance-version=1.0.1'])
+
+ bb_annotations.PrintNamedStep('gpu_rasterization_tests')
+ RunCmd(['content/test/gpu/run_gpu_test.py',
+ 'gpu_rasterization', '-v',
+ '--browser',
+ 'android-content-shell',
+ '--build-revision',
+ str(revision),
+ '--test-machine-name',
+ EscapeBuilderName(builder_name)])
+
+
+def RunPythonUnitTests(_options):
+ for suite in constants.PYTHON_UNIT_TEST_SUITES:
+ bb_annotations.PrintNamedStep(suite)
+ RunCmd(['build/android/test_runner.py', 'python', '-s', suite])
+
+
+def GetTestStepCmds():
+ return [
+ ('base_junit_tests',
+ lambda _options: RunJunitSuite('base_junit_tests')),
+ ('chromedriver', RunChromeDriverTests),
+ ('chrome_proxy', RunChromeProxyTests),
+ ('components_browsertests',
+ lambda options: RunTestSuites(options, ['components_browsertests'])),
+ ('gfx_unittests',
+ lambda options: RunTestSuites(options, ['gfx_unittests'])),
+ ('gl_unittests',
+ lambda options: RunTestSuites(options, ['gl_unittests'])),
+ ('gpu', RunGPUTests),
+ ('python_unittests', RunPythonUnitTests),
+ ('telemetry_unittests', RunTelemetryUnitTests),
+ ('telemetry_perf_unittests', RunTelemetryPerfUnitTests),
+ ('ui', RunInstrumentationTests),
+ ('unit', RunUnitTests),
+ ('webkit', RunWebkitTests),
+ ('webkit_layout', RunWebkitLayoutTests),
+ ]
+
+
+def MakeGSPath(options, gs_base_dir):
+ revision = _GetRevision(options)
+ bot_id = options.build_properties.get('buildername', 'testing')
+ randhash = hashlib.sha1(str(random.random())).hexdigest()
+ gs_path = '%s/%s/%s/%s' % (gs_base_dir, bot_id, revision, randhash)
+ # remove double slashes, happens with blank revisions and confuses gsutil
+ gs_path = re.sub('/+', '/', gs_path)
+ return gs_path
+
+def UploadHTML(options, gs_base_dir, dir_to_upload, link_text,
+ link_rel_path='index.html', gs_url=GS_URL):
+ """Uploads directory at |dir_to_upload| to Google Storage and output a link.
+
+ Args:
+ options: Command line options.
+ gs_base_dir: The Google Storage base directory (e.g.
+ 'chromium-code-coverage/java')
+ dir_to_upload: Absolute path to the directory to be uploaded.
+ link_text: Link text to be displayed on the step.
+ link_rel_path: Link path relative to |dir_to_upload|.
+ gs_url: Google storage URL.
+ """
+ gs_path = MakeGSPath(options, gs_base_dir)
+ RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-R', dir_to_upload, 'gs://%s' % gs_path])
+ bb_annotations.PrintLink(link_text,
+ '%s/%s/%s' % (gs_url, gs_path, link_rel_path))
+
+
+def GenerateJavaCoverageReport(options):
+ """Generates an HTML coverage report using EMMA and uploads it."""
+ bb_annotations.PrintNamedStep('java_coverage_report')
+
+ coverage_html = os.path.join(options.coverage_dir, 'coverage_html')
+ RunCmd(['build/android/generate_emma_html.py',
+ '--coverage-dir', options.coverage_dir,
+ '--metadata-dir', os.path.join(CHROME_OUT_DIR, options.target),
+ '--cleanup',
+ '--output', os.path.join(coverage_html, 'index.html')])
+ return coverage_html
+
+
+def LogcatDump(options):
+ # Print logcat, kill logcat monitor
+ bb_annotations.PrintNamedStep('logcat_dump')
+ logcat_file = os.path.join(CHROME_OUT_DIR, options.target, 'full_log.txt')
+ RunCmd([SrcPath('build', 'android', 'adb_logcat_printer.py'),
+ '--output-path', logcat_file, LOGCAT_DIR])
+ gs_path = MakeGSPath(options, 'chromium-android/logcat_dumps')
+ RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-z', 'txt', logcat_file,
+ 'gs://%s' % gs_path])
+ bb_annotations.PrintLink('logcat dump', '%s/%s' % (GS_AUTH_URL, gs_path))
+
+
+def RunStackToolSteps(options):
+ """Run stack tool steps.
+
+ Stack tool is run for logcat dump, optionally for ASAN.
+ """
+ bb_annotations.PrintNamedStep('Run stack tool with logcat dump')
+ logcat_file = os.path.join(CHROME_OUT_DIR, options.target, 'full_log.txt')
+ RunCmd([os.path.join(CHROME_SRC_DIR, 'third_party', 'android_platform',
+ 'development', 'scripts', 'stack'),
+ '--more-info', logcat_file])
+ if options.asan_symbolize:
+ bb_annotations.PrintNamedStep('Run stack tool for ASAN')
+ RunCmd([
+ os.path.join(CHROME_SRC_DIR, 'build', 'android', 'asan_symbolize.py'),
+ '-l', logcat_file])
+
+
+def GenerateTestReport(options):
+ bb_annotations.PrintNamedStep('test_report')
+ for report in glob.glob(
+ os.path.join(CHROME_OUT_DIR, options.target, 'test_logs', '*.log')):
+ RunCmd(['cat', report])
+ os.remove(report)
+
+
+def MainTestWrapper(options):
+ try:
+ # Spawn logcat monitor
+ SpawnLogcatMonitor()
+
+ # Run all device setup steps
+ for _, cmd in GetDeviceSetupStepCmds():
+ cmd(options)
+
+ if options.install:
+ for i in options.install:
+ install_obj = INSTALLABLE_PACKAGES[i]
+ InstallApk(options, install_obj, print_step=True)
+
+ if options.test_filter:
+ bb_utils.RunSteps(options.test_filter, GetTestStepCmds(), options)
+
+ if options.coverage_bucket:
+ coverage_html = GenerateJavaCoverageReport(options)
+ UploadHTML(options, '%s/java' % options.coverage_bucket, coverage_html,
+ 'Coverage Report')
+ shutil.rmtree(coverage_html, ignore_errors=True)
+
+ if options.experimental:
+ RunTestSuites(options, gtest_config.EXPERIMENTAL_TEST_SUITES)
+
+ finally:
+ # Run all post test steps
+ LogcatDump(options)
+ if not options.disable_stack_tool:
+ RunStackToolSteps(options)
+ GenerateTestReport(options)
+ # KillHostHeartbeat() has logic to check if heartbeat process is running,
+ # and kills only if it finds the process is running on the host.
+ provision_devices.KillHostHeartbeat()
+ if options.cleanup:
+ shutil.rmtree(os.path.join(CHROME_OUT_DIR, options.target),
+ ignore_errors=True)
+
+
+def GetDeviceStepsOptParser():
+ parser = bb_utils.GetParser()
+ parser.add_option('--experimental', action='store_true',
+ help='Run experiemental tests')
+ parser.add_option('-f', '--test-filter', metavar='', default=[],
+ action='append',
+ help=('Run a test suite. Test suites: "%s"' %
+ '", "'.join(VALID_TESTS)))
+ parser.add_option('--gtest-filter',
+ help='Filter for running a subset of tests of a gtest test')
+ parser.add_option('--asan', action='store_true', help='Run tests with asan.')
+ parser.add_option('--install', metavar='', action="append",
+ help='Install an apk by name')
+ parser.add_option('--no-reboot', action='store_true',
+ help='Do not reboot devices during provisioning.')
+ parser.add_option('--coverage-bucket',
+ help=('Bucket name to store coverage results. Coverage is '
+ 'only run if this is set.'))
+ parser.add_option('--restart-usb', action='store_true',
+ help='Restart usb ports before device status check.')
+ parser.add_option(
+ '--flakiness-server',
+ help=('The flakiness dashboard server to which the results should be '
+ 'uploaded.'))
+ parser.add_option(
+ '--auto-reconnect', action='store_true',
+ help='Push script to device which restarts adbd on disconnections.')
+ parser.add_option('--skip-wipe', action='store_true',
+ help='Do not wipe devices during provisioning.')
+ parser.add_option('--disable-location', action='store_true',
+ help='Disable location settings.')
+ parser.add_option(
+ '--logcat-dump-output',
+ help='The logcat dump output will be "tee"-ed into this file')
+ # During processing perf bisects, a seperate working directory created under
+ # which builds are produced. Therefore we should look for relevent output
+ # file under this directory.(/b/build/slave//build/bisect/src/out)
+ parser.add_option(
+ '--chrome-output-dir',
+ help='Chrome output directory to be used while bisecting.')
+
+ parser.add_option('--disable-stack-tool', action='store_true',
+ help='Do not run stack tool.')
+ parser.add_option('--asan-symbolize', action='store_true',
+ help='Run stack tool for ASAN')
+ parser.add_option('--cleanup', action='store_true',
+ help='Delete out/ directory at the end of the run.')
+ return parser
+
+
+def main(argv):
+ parser = GetDeviceStepsOptParser()
+ options, args = parser.parse_args(argv[1:])
+
+ if args:
+ return sys.exit('Unused args %s' % args)
+
+ unknown_tests = set(options.test_filter) - VALID_TESTS
+ if unknown_tests:
+ return sys.exit('Unknown tests %s' % list(unknown_tests))
+
+ setattr(options, 'target', options.factory_properties.get('target', 'Debug'))
+
+ if options.chrome_output_dir:
+ global CHROME_OUT_DIR
+ global LOGCAT_DIR
+ CHROME_OUT_DIR = options.chrome_output_dir
+ LOGCAT_DIR = os.path.join(CHROME_OUT_DIR, 'logcat')
+
+ if options.coverage_bucket:
+ setattr(options, 'coverage_dir',
+ os.path.join(CHROME_OUT_DIR, options.target, 'coverage'))
+
+ MainTestWrapper(options)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/buildbot/bb_host_steps.py b/build/android/buildbot/bb_host_steps.py
new file mode 100755
index 00000000000..1e927fb399d
--- /dev/null
+++ b/build/android/buildbot/bb_host_steps.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# Copyright 2013 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.
+
+import os
+import json
+import sys
+
+import bb_utils
+import bb_annotations
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pylib import constants
+
+
+SLAVE_SCRIPTS_DIR = os.path.join(bb_utils.BB_BUILD_DIR, 'scripts', 'slave')
+VALID_HOST_TESTS = set(['check_webview_licenses'])
+
+DIR_BUILD_ROOT = os.path.dirname(constants.DIR_SOURCE_ROOT)
+
+# Short hand for RunCmd which is used extensively in this file.
+RunCmd = bb_utils.RunCmd
+
+
+def SrcPath(*path):
+ return os.path.join(constants.DIR_SOURCE_ROOT, *path)
+
+
+def CheckWebViewLicenses(_):
+ bb_annotations.PrintNamedStep('check_licenses')
+ RunCmd([SrcPath('android_webview', 'tools', 'webview_licenses.py'), 'scan'],
+ warning_code=1)
+
+
+def RunHooks(build_type):
+ RunCmd([SrcPath('build', 'landmines.py')])
+ build_path = SrcPath('out', build_type)
+ landmine_path = os.path.join(build_path, '.landmines_triggered')
+ clobber_env = os.environ.get('BUILDBOT_CLOBBER')
+ if clobber_env or os.path.isfile(landmine_path):
+ bb_annotations.PrintNamedStep('Clobber')
+ if not clobber_env:
+ print 'Clobbering due to triggered landmines:'
+ with open(landmine_path) as f:
+ print f.read()
+ RunCmd(['rm', '-rf', build_path])
+
+ bb_annotations.PrintNamedStep('runhooks')
+ RunCmd(['gclient', 'runhooks'], halt_on_failure=True)
+
+
+def Compile(options):
+ RunHooks(options.target)
+ cmd = [os.path.join(SLAVE_SCRIPTS_DIR, 'compile.py'),
+ '--build-tool=ninja',
+ '--compiler=goma',
+ '--target=%s' % options.target,
+ '--goma-dir=%s' % bb_utils.GOMA_DIR]
+ bb_annotations.PrintNamedStep('compile')
+ if options.build_targets:
+ build_targets = options.build_targets.split(',')
+ cmd += ['--build-args', ' '.join(build_targets)]
+ RunCmd(cmd, halt_on_failure=True, cwd=DIR_BUILD_ROOT)
+
+
+def ZipBuild(options):
+ bb_annotations.PrintNamedStep('zip_build')
+ RunCmd([
+ os.path.join(SLAVE_SCRIPTS_DIR, 'zip_build.py'),
+ '--src-dir', constants.DIR_SOURCE_ROOT,
+ '--exclude-files', 'lib.target,gen,android_webview,jingle_unittests']
+ + bb_utils.EncodeProperties(options), cwd=DIR_BUILD_ROOT)
+
+
+def ExtractBuild(options):
+ bb_annotations.PrintNamedStep('extract_build')
+ RunCmd([os.path.join(SLAVE_SCRIPTS_DIR, 'extract_build.py')]
+ + bb_utils.EncodeProperties(options), cwd=DIR_BUILD_ROOT)
+
+
+def BisectPerfRegression(options):
+ args = []
+ if options.extra_src:
+ args = ['--extra_src', options.extra_src]
+ RunCmd([SrcPath('tools', 'prepare-bisect-perf-regression.py'),
+ '-w', os.path.join(constants.DIR_SOURCE_ROOT, os.pardir)])
+ RunCmd([SrcPath('tools', 'run-bisect-perf-regression.py'),
+ '-w', os.path.join(constants.DIR_SOURCE_ROOT, os.pardir),
+ '--build-properties=%s' % json.dumps(options.build_properties)] +
+ args)
+
+
+def GetHostStepCmds():
+ return [
+ ('compile', Compile),
+ ('extract_build', ExtractBuild),
+ ('check_webview_licenses', CheckWebViewLicenses),
+ ('bisect_perf_regression', BisectPerfRegression),
+ ('zip_build', ZipBuild)
+ ]
+
+
+def GetHostStepsOptParser():
+ parser = bb_utils.GetParser()
+ parser.add_option('--steps', help='Comma separated list of host tests.')
+ parser.add_option('--build-targets', default='',
+ help='Comma separated list of build targets.')
+ parser.add_option('--experimental', action='store_true',
+ help='Indicate whether to compile experimental targets.')
+ parser.add_option('--extra_src', default='',
+ help='Path to extra source file. If this is supplied, '
+ 'bisect script will use it to override default behavior.')
+
+ return parser
+
+
+def main(argv):
+ parser = GetHostStepsOptParser()
+ options, args = parser.parse_args(argv[1:])
+ if args:
+ return sys.exit('Unused args %s' % args)
+
+ setattr(options, 'target', options.factory_properties.get('target', 'Debug'))
+ setattr(options, 'extra_src',
+ options.factory_properties.get('extra_src', ''))
+
+ if options.steps:
+ bb_utils.RunSteps(options.steps.split(','), GetHostStepCmds(), options)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/buildbot/bb_run_bot.py b/build/android/buildbot/bb_run_bot.py
new file mode 100755
index 00000000000..0c8a977c9d8
--- /dev/null
+++ b/build/android/buildbot/bb_run_bot.py
@@ -0,0 +1,320 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+import collections
+import copy
+import json
+import os
+import pipes
+import re
+import subprocess
+import sys
+
+import bb_utils
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pylib import constants
+
+
+CHROMIUM_COVERAGE_BUCKET = 'chromium-code-coverage'
+
+_BotConfig = collections.namedtuple(
+ 'BotConfig', ['bot_id', 'host_obj', 'test_obj'])
+
+HostConfig = collections.namedtuple(
+ 'HostConfig',
+ ['script', 'host_steps', 'extra_args', 'extra_gyp_defines', 'target_arch'])
+
+TestConfig = collections.namedtuple('Tests', ['script', 'tests', 'extra_args'])
+
+
+def BotConfig(bot_id, host_object, test_object=None):
+ return _BotConfig(bot_id, host_object, test_object)
+
+
+def DictDiff(d1, d2):
+ diff = []
+ for key in sorted(set(d1.keys() + d2.keys())):
+ if key in d1 and d1[key] != d2.get(key):
+ diff.append('- %s=%s' % (key, pipes.quote(d1[key])))
+ if key in d2 and d2[key] != d1.get(key):
+ diff.append('+ %s=%s' % (key, pipes.quote(d2[key])))
+ return '\n'.join(diff)
+
+
+def GetEnvironment(host_obj, testing, extra_env_vars=None):
+ init_env = dict(os.environ)
+ init_env['GYP_GENERATORS'] = 'ninja'
+ if extra_env_vars:
+ init_env.update(extra_env_vars)
+ envsetup_cmd = '. build/android/envsetup.sh'
+ if testing:
+ # Skip envsetup to avoid presubmit dependence on android deps.
+ print 'Testing mode - skipping "%s"' % envsetup_cmd
+ envsetup_cmd = ':'
+ else:
+ print 'Running %s' % envsetup_cmd
+ proc = subprocess.Popen(['bash', '-exc',
+ envsetup_cmd + ' >&2; python build/android/buildbot/env_to_json.py'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ cwd=bb_utils.CHROME_SRC, env=init_env)
+ json_env, envsetup_output = proc.communicate()
+ if proc.returncode != 0:
+ print >> sys.stderr, 'FATAL Failure in envsetup.'
+ print >> sys.stderr, envsetup_output
+ sys.exit(1)
+ env = json.loads(json_env)
+ env['GYP_DEFINES'] = env.get('GYP_DEFINES', '') + \
+ ' OS=android fastbuild=1 use_goma=1 gomadir=%s' % bb_utils.GOMA_DIR
+ if host_obj.target_arch:
+ env['GYP_DEFINES'] += ' target_arch=%s' % host_obj.target_arch
+ extra_gyp = host_obj.extra_gyp_defines
+ if extra_gyp:
+ env['GYP_DEFINES'] += ' %s' % extra_gyp
+ if re.search('(asan|clang)=1', extra_gyp):
+ env.pop('CXX_target', None)
+
+ # Bots checkout chrome in /b/build/slave//build/src
+ build_internal_android = os.path.abspath(os.path.join(
+ bb_utils.CHROME_SRC, '..', '..', '..', '..', '..', 'build_internal',
+ 'scripts', 'slave', 'android'))
+ if os.path.exists(build_internal_android):
+ env['PATH'] = os.pathsep.join([build_internal_android, env['PATH']])
+ return env
+
+
+def GetCommands(options, bot_config):
+ """Get a formatted list of commands.
+
+ Args:
+ options: Options object.
+ bot_config: A BotConfig named tuple.
+ host_step_script: Host step script.
+ device_step_script: Device step script.
+ Returns:
+ list of Command objects.
+ """
+ property_args = bb_utils.EncodeProperties(options)
+ commands = [[bot_config.host_obj.script,
+ '--steps=%s' % ','.join(bot_config.host_obj.host_steps)] +
+ property_args + (bot_config.host_obj.extra_args or [])]
+
+ test_obj = bot_config.test_obj
+ if test_obj:
+ run_test_cmd = [test_obj.script] + property_args
+ for test in test_obj.tests:
+ run_test_cmd.extend(['-f', test])
+ if test_obj.extra_args:
+ run_test_cmd.extend(test_obj.extra_args)
+ commands.append(run_test_cmd)
+ return commands
+
+
+def GetBotStepMap():
+ compile_step = ['compile']
+ chrome_proxy_tests = ['chrome_proxy']
+ python_unittests = ['python_unittests']
+ std_host_tests = ['check_webview_licenses']
+ std_build_steps = ['compile', 'zip_build']
+ std_test_steps = ['extract_build']
+ std_tests = ['ui', 'unit']
+ telemetry_tests = ['telemetry_perf_unittests']
+ telemetry_tests_user_build = ['telemetry_unittests',
+ 'telemetry_perf_unittests']
+ trial_tests = [
+ 'base_junit_tests',
+ 'components_browsertests',
+ 'gfx_unittests',
+ 'gl_unittests',
+ ]
+ flakiness_server = (
+ '--flakiness-server=%s' % constants.UPSTREAM_FLAKINESS_SERVER)
+ experimental = ['--experimental']
+ bisect_chrome_output_dir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ os.pardir, 'bisect', 'src', 'out'))
+ B = BotConfig
+ H = (lambda steps, extra_args=None, extra_gyp=None, target_arch=None:
+ HostConfig('build/android/buildbot/bb_host_steps.py', steps, extra_args,
+ extra_gyp, target_arch))
+ T = (lambda tests, extra_args=None:
+ TestConfig('build/android/buildbot/bb_device_steps.py', tests,
+ extra_args))
+
+ bot_configs = [
+ # Main builders
+ B('main-builder-dbg', H(std_build_steps + std_host_tests)),
+ B('main-builder-rel', H(std_build_steps)),
+ B('main-clang-builder',
+ H(compile_step, extra_gyp='clang=1 component=shared_library')),
+ B('main-clobber', H(compile_step)),
+ B('main-tests-rel', H(std_test_steps),
+ T(std_tests + telemetry_tests + chrome_proxy_tests,
+ ['--cleanup', flakiness_server])),
+ B('main-tests', H(std_test_steps),
+ T(std_tests, ['--cleanup', flakiness_server])),
+
+ # Other waterfalls
+ B('asan-builder-tests', H(compile_step,
+ extra_gyp='asan=1 component=shared_library'),
+ T(std_tests, ['--asan', '--asan-symbolize'])),
+ B('blink-try-builder', H(compile_step)),
+ B('chromedriver-fyi-tests-dbg', H(std_test_steps),
+ T(['chromedriver'],
+ ['--install=ChromeShell', '--install=ChromeDriverWebViewShell',
+ '--skip-wipe', '--disable-location', '--cleanup'])),
+ B('fyi-x86-builder-dbg',
+ H(compile_step + std_host_tests, experimental, target_arch='ia32')),
+ B('fyi-builder-dbg',
+ H(std_build_steps + std_host_tests, experimental,
+ extra_gyp='emma_coverage=1')),
+ B('x86-builder-dbg',
+ H(compile_step + std_host_tests, target_arch='ia32')),
+ B('fyi-builder-rel', H(std_build_steps, experimental)),
+ B('fyi-tests', H(std_test_steps),
+ T(std_tests + python_unittests,
+ ['--experimental', flakiness_server,
+ '--coverage-bucket', CHROMIUM_COVERAGE_BUCKET,
+ '--cleanup'])),
+ B('user-build-fyi-tests-dbg', H(std_test_steps),
+ T(sorted(telemetry_tests_user_build + trial_tests))),
+ B('fyi-component-builder-tests-dbg',
+ H(compile_step, extra_gyp='component=shared_library'),
+ T(std_tests, ['--experimental', flakiness_server])),
+ B('gpu-builder-tests-dbg',
+ H(compile_step),
+ T(['gpu'], ['--install=ContentShell'])),
+ # Pass empty T([]) so that logcat monitor and device status check are run.
+ B('perf-bisect-builder-tests-dbg',
+ H(['bisect_perf_regression']),
+ T([], ['--chrome-output-dir', bisect_chrome_output_dir])),
+ B('perf-tests-rel', H(std_test_steps),
+ T([], ['--install=ChromeShell', '--cleanup'])),
+ B('webkit-latest-webkit-tests', H(std_test_steps),
+ T(['webkit_layout', 'webkit'], ['--cleanup', '--auto-reconnect'])),
+ B('webkit-latest-contentshell', H(compile_step),
+ T(['webkit_layout'], ['--auto-reconnect'])),
+ B('builder-unit-tests', H(compile_step), T(['unit'])),
+
+ # Generic builder config (for substring match).
+ B('builder', H(std_build_steps)),
+ ]
+
+ bot_map = dict((config.bot_id, config) for config in bot_configs)
+
+ # These bots have identical configuration to ones defined earlier.
+ copy_map = [
+ ('lkgr-clobber', 'main-clobber'),
+ ('try-builder-dbg', 'main-builder-dbg'),
+ ('try-builder-rel', 'main-builder-rel'),
+ ('try-clang-builder', 'main-clang-builder'),
+ ('try-fyi-builder-dbg', 'fyi-builder-dbg'),
+ ('try-x86-builder-dbg', 'x86-builder-dbg'),
+ ('try-tests-rel', 'main-tests-rel'),
+ ('try-tests', 'main-tests'),
+ ('try-fyi-tests', 'fyi-tests'),
+ ('webkit-latest-tests', 'main-tests'),
+ ]
+ for to_id, from_id in copy_map:
+ assert to_id not in bot_map
+ # pylint: disable=W0212
+ bot_map[to_id] = copy.deepcopy(bot_map[from_id])._replace(bot_id=to_id)
+
+ # Trybots do not upload to flakiness dashboard. They should be otherwise
+ # identical in configuration to their trunk building counterparts.
+ test_obj = bot_map[to_id].test_obj
+ if to_id.startswith('try') and test_obj:
+ extra_args = test_obj.extra_args
+ if extra_args and flakiness_server in extra_args:
+ extra_args.remove(flakiness_server)
+ return bot_map
+
+
+# Return an object from the map, looking first for an exact id match.
+# If this fails, look for an id which is a substring of the specified id.
+# Choose the longest of all substring matches.
+# pylint: disable=W0622
+def GetBestMatch(id_map, id):
+ config = id_map.get(id)
+ if not config:
+ substring_matches = [x for x in id_map.iterkeys() if x in id]
+ if substring_matches:
+ max_id = max(substring_matches, key=len)
+ print 'Using config from id="%s" (substring match).' % max_id
+ config = id_map[max_id]
+ return config
+
+
+def GetRunBotOptParser():
+ parser = bb_utils.GetParser()
+ parser.add_option('--bot-id', help='Specify bot id directly.')
+ parser.add_option('--testing', action='store_true',
+ help='For testing: print, but do not run commands')
+
+ return parser
+
+
+def GetBotConfig(options, bot_step_map):
+ bot_id = options.bot_id or options.factory_properties.get('android_bot_id')
+ if not bot_id:
+ print (sys.stderr,
+ 'A bot id must be specified through option or factory_props.')
+ return
+
+ bot_config = GetBestMatch(bot_step_map, bot_id)
+ if not bot_config:
+ print 'Error: config for id="%s" cannot be inferred.' % bot_id
+ return bot_config
+
+
+def RunBotCommands(options, commands, env):
+ print 'Environment changes:'
+ print DictDiff(dict(os.environ), env)
+
+ for command in commands:
+ print bb_utils.CommandToString(command)
+ sys.stdout.flush()
+ if options.testing:
+ env['BUILDBOT_TESTING'] = '1'
+ return_code = subprocess.call(command, cwd=bb_utils.CHROME_SRC, env=env)
+ if return_code != 0:
+ return return_code
+
+
+def main(argv):
+ proc = subprocess.Popen(
+ ['/bin/hostname', '-f'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ hostname_stdout, hostname_stderr = proc.communicate()
+ if proc.returncode == 0:
+ print 'Running on: ' + hostname_stdout
+ else:
+ print >> sys.stderr, 'WARNING: failed to run hostname'
+ print >> sys.stderr, hostname_stdout
+ print >> sys.stderr, hostname_stderr
+ sys.exit(1)
+
+ parser = GetRunBotOptParser()
+ options, args = parser.parse_args(argv[1:])
+ if args:
+ parser.error('Unused args: %s' % args)
+
+ bot_config = GetBotConfig(options, GetBotStepMap())
+ if not bot_config:
+ sys.exit(1)
+
+ print 'Using config:', bot_config
+
+ commands = GetCommands(options, bot_config)
+ for command in commands:
+ print 'Will run: ', bb_utils.CommandToString(command)
+ print
+
+ env = GetEnvironment(bot_config.host_obj, options.testing)
+ return RunBotCommands(options, commands, env)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/buildbot/bb_utils.py b/build/android/buildbot/bb_utils.py
new file mode 100644
index 00000000000..3c16cc2bc5b
--- /dev/null
+++ b/build/android/buildbot/bb_utils.py
@@ -0,0 +1,100 @@
+# Copyright 2013 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.
+
+import json
+import optparse
+import os
+import pipes
+import subprocess
+import sys
+
+import bb_annotations
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pylib import constants
+
+
+TESTING = 'BUILDBOT_TESTING' in os.environ
+
+BB_BUILD_DIR = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ os.pardir, os.pardir, os.pardir, os.pardir))
+
+CHROME_SRC = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+
+# TODO: Figure out how to merge this with pylib.cmd_helper.OutDirectory().
+CHROME_OUT_DIR = os.path.join(CHROME_SRC, 'out')
+
+GOMA_DIR = os.environ.get('GOMA_DIR', os.path.join(BB_BUILD_DIR, 'goma'))
+
+GSUTIL_PATH = os.path.join(BB_BUILD_DIR, 'third_party', 'gsutil', 'gsutil')
+
+def CommandToString(command):
+ """Returns quoted command that can be run in bash shell."""
+ return ' '.join(map(pipes.quote, command))
+
+
+def SpawnCmd(command, stdout=None, cwd=CHROME_SRC):
+ """Spawn a process without waiting for termination."""
+ print '>', CommandToString(command)
+ sys.stdout.flush()
+ if TESTING:
+ class MockPopen(object):
+ @staticmethod
+ def wait():
+ return 0
+ @staticmethod
+ def communicate():
+ return '', ''
+ return MockPopen()
+ return subprocess.Popen(command, cwd=cwd, stdout=stdout)
+
+
+def RunCmd(command, flunk_on_failure=True, halt_on_failure=False,
+ warning_code=constants.WARNING_EXIT_CODE, stdout=None,
+ cwd=CHROME_SRC):
+ """Run a command relative to the chrome source root."""
+ code = SpawnCmd(command, stdout, cwd).wait()
+ print '<', CommandToString(command)
+ if code != 0:
+ print 'ERROR: process exited with code %d' % code
+ if code != warning_code and flunk_on_failure:
+ bb_annotations.PrintError()
+ else:
+ bb_annotations.PrintWarning()
+ # Allow steps to have both halting (i.e. 1) and non-halting exit codes.
+ if code != warning_code and halt_on_failure:
+ print 'FATAL %d != %d' % (code, warning_code)
+ sys.exit(1)
+ return code
+
+
+def GetParser():
+ def ConvertJson(option, _, value, parser):
+ setattr(parser.values, option.dest, json.loads(value))
+ parser = optparse.OptionParser()
+ parser.add_option('--build-properties', action='callback',
+ callback=ConvertJson, type='string', default={},
+ help='build properties in JSON format')
+ parser.add_option('--factory-properties', action='callback',
+ callback=ConvertJson, type='string', default={},
+ help='factory properties in JSON format')
+ return parser
+
+
+def EncodeProperties(options):
+ return ['--factory-properties=%s' % json.dumps(options.factory_properties),
+ '--build-properties=%s' % json.dumps(options.build_properties)]
+
+
+def RunSteps(steps, step_cmds, options):
+ unknown_steps = set(steps) - set(step for step, _ in step_cmds)
+ if unknown_steps:
+ print >> sys.stderr, 'FATAL: Unknown steps %s' % list(unknown_steps)
+ sys.exit(1)
+
+ for step, cmd in step_cmds:
+ if step in steps:
+ cmd(options)
diff --git a/build/android/buildbot/env_to_json.py b/build/android/buildbot/env_to_json.py
new file mode 100755
index 00000000000..f9a7a443d39
--- /dev/null
+++ b/build/android/buildbot/env_to_json.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+# Copyright 2013 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.
+
+# Encode current environment into json.
+
+import json
+import os
+
+print json.dumps(dict(os.environ))
diff --git a/build/android/buildbot/tests/bb_run_bot_test.py b/build/android/buildbot/tests/bb_run_bot_test.py
new file mode 100755
index 00000000000..810c60dac2f
--- /dev/null
+++ b/build/android/buildbot/tests/bb_run_bot_test.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+import os
+import subprocess
+import sys
+
+BUILDBOT_DIR = os.path.join(os.path.dirname(__file__), '..')
+sys.path.append(BUILDBOT_DIR)
+import bb_run_bot
+
+def RunBotProcesses(bot_process_map):
+ code = 0
+ for bot, proc in bot_process_map:
+ _, err = proc.communicate()
+ code |= proc.returncode
+ if proc.returncode != 0:
+ print 'Error running the bot script with id="%s"' % bot, err
+
+ return code
+
+
+def main():
+ procs = [
+ (bot, subprocess.Popen(
+ [os.path.join(BUILDBOT_DIR, 'bb_run_bot.py'), '--bot-id', bot,
+ '--testing'], stdout=subprocess.PIPE, stderr=subprocess.PIPE))
+ for bot in bb_run_bot.GetBotStepMap()]
+ return RunBotProcesses(procs)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/chrome_with_libs.gyp b/build/android/chrome_with_libs.gyp
new file mode 100644
index 00000000000..690be885f0a
--- /dev/null
+++ b/build/android/chrome_with_libs.gyp
@@ -0,0 +1,82 @@
+# Copyright 2013 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 file is meant to add more loadable libs into Chrome_apk.
+#
+# This is useful when building Chrome_apk with some loadable modules which are
+# not included in Chrome_apk.
+# As an example, when building Chrome_apk with
+# libpeer_target_type=loadable_module,
+# the libpeerconnection.so is not included in Chrome_apk. To add the missing
+# lib, follow the steps below:
+# - Run gyp:
+# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" CHROMIUM_GYP_FILE="build/android/chrome_with_libs.gyp" build/gyp_chromium
+# - Build chrome_with_libs:
+# ninja (or make) chrome_with_libs
+#
+# This tool also allows replacing the loadable module with a new one via the
+# following steps:
+# - Build Chrome_apk with the gyp define:
+# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" build/gyp_chromium
+# ninja (or make) Chrome_apk
+# - Replace libpeerconnection.so with a new one:
+# cp the_new_one path/to/libpeerconnection.so
+# - Run gyp:
+# GYP_DEFINES="$GYP_DEFINES libpeer_target_type=loadable_module" CHROMIUM_GYP_FILE="build/android/chrome_with_libs.gyp" build/gyp_chromium
+# - Build chrome_with_libs:
+# ninja (or make) chrome_with_libs
+{
+ 'targets': [
+ {
+ # An "All" target is required for a top-level gyp-file.
+ 'target_name': 'All',
+ 'type': 'none',
+ 'dependencies': [
+ 'chrome_with_libs',
+ ],
+ },
+ {
+ 'target_name': 'chrome_with_libs',
+ 'type': 'none',
+ 'variables': {
+ 'intermediate_dir': '<(PRODUCT_DIR)/prebuilt_libs/',
+ 'chrome_unsigned_path': '<(PRODUCT_DIR)/chrome_apk/Chrome-unsigned.apk',
+ 'chrome_with_libs_unsigned': '<(intermediate_dir)/Chrome-with-libs-unsigned.apk',
+ 'chrome_with_libs_final': '<(PRODUCT_DIR)/apks/Chrome-with-libs.apk',
+ },
+ 'dependencies': [
+ '<(DEPTH)/clank/native/framework/clank.gyp:chrome_apk'
+ ],
+ 'copies': [
+ {
+ 'destination': '<(intermediate_dir)/lib/<(android_app_abi)',
+ 'files': [
+ '<(PRODUCT_DIR)/libpeerconnection.so',
+ ],
+ },
+ ],
+ 'actions': [
+ {
+ 'action_name': 'put_libs_in_chrome',
+ 'variables': {
+ 'inputs': [
+ '<(intermediate_dir)/lib/<(android_app_abi)/libpeerconnection.so',
+ ],
+ 'input_apk_path': '<(chrome_unsigned_path)',
+ 'output_apk_path': '<(chrome_with_libs_unsigned)',
+ 'libraries_top_dir%': '<(intermediate_dir)',
+ },
+ 'includes': [ 'create_standalone_apk_action.gypi' ],
+ },
+ {
+ 'action_name': 'finalize_chrome_with_libs',
+ 'variables': {
+ 'input_apk_path': '<(chrome_with_libs_unsigned)',
+ 'output_apk_path': '<(chrome_with_libs_final)',
+ },
+ 'includes': [ 'finalize_apk_action.gypi'],
+ },
+ ],
+ }],
+}
diff --git a/build/android/create_standalone_apk_action.gypi b/build/android/create_standalone_apk_action.gypi
new file mode 100644
index 00000000000..d17af7c8e5d
--- /dev/null
+++ b/build/android/create_standalone_apk_action.gypi
@@ -0,0 +1,41 @@
+# Copyright 2013 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 file is meant to be included into an action to provide an action that
+# combines a directory of shared libraries and an incomplete APK into a
+# standalone APK.
+#
+# To use this, create a gyp action with the following form:
+# {
+# 'action_name': 'some descriptive action name',
+# 'variables': {
+# 'inputs': [ 'input_path1', 'input_path2' ],
+# 'input_apk_path': '<(unsigned_apk_path)',
+# 'output_apk_path': '<(unsigned_standalone_apk_path)',
+# 'libraries_top_dir': '<(libraries_top_dir)',
+# },
+# 'includes': [ 'relative/path/to/create_standalone_apk_action.gypi' ],
+# },
+
+{
+ 'message': 'Creating standalone APK: <(output_apk_path)',
+ 'variables': {
+ 'inputs': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/create_standalone_apk.py',
+ '<(input_apk_path)',
+ '>@(inputs)',
+ ],
+ 'outputs': [
+ '<(output_apk_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/create_standalone_apk.py',
+ '--libraries-top-dir=<(libraries_top_dir)',
+ '--input-apk-path=<(input_apk_path)',
+ '--output-apk-path=<(output_apk_path)',
+ ],
+}
diff --git a/build/android/developer_recommended_flags.gypi b/build/android/developer_recommended_flags.gypi
new file mode 100644
index 00000000000..79c201deccb
--- /dev/null
+++ b/build/android/developer_recommended_flags.gypi
@@ -0,0 +1,61 @@
+# Copyright 2013 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 the set of recommended gyp variable settings for Chrome for Android development.
+#
+# These can be used by copying this file to $CHROME_SRC/chrome/supplement.gypi.
+#
+# Even better, create chrome/supplement.gypi containing the following:
+# {
+# 'includes': [ '../build/android/developer_recommended_flags.gypi' ]
+# }
+# and you'll get new settings automatically.
+# When using this method, you can override individual settings by setting them unconditionally (with
+# no %) in chrome/supplement.gypi.
+# I.e. to disable gyp_managed_install but use everything else:
+# {
+# 'variables': {
+# 'gyp_managed_install': 0,
+# },
+# 'includes': [ '../build/android/developer_recommended_flags.gypi' ]
+# }
+
+{
+ 'variables': {
+ 'variables': {
+ # Set component to 'shared_library' to enable the component build. This builds native code as
+ # many small shared libraries instead of one monolithic library. This slightly reduces the time
+ # required for incremental builds.
+ 'component%': 'shared_library',
+ },
+ 'component%': '<(component)',
+
+ # When gyp_managed_install is set to 1, building an APK will install that APK on the connected
+ # device(/emulator). To install on multiple devices (or onto a new device), build the APK once
+ # with each device attached. This greatly reduces the time required for incremental builds.
+ #
+ # This comes with some caveats:
+ # Only works with a single device connected (it will print a warning if
+ # zero or multiple devices are attached).
+ # Device must be flashed with a user-debug unsigned Android build.
+ # Some actions are always run (i.e. ninja will never say "no work to do").
+ 'gyp_managed_install%': 1,
+
+ # With gyp_managed_install, we do not necessarily need a standalone APK.
+ # When create_standalone_apk is set to 1, we will build a standalone APK
+ # anyway. For even faster builds, you can set create_standalone_apk to 0.
+ 'create_standalone_apk%': 1,
+
+ # Set clang to 1 to use the clang compiler. Clang has much (much, much) better warning/error
+ # messages than gcc.
+ # TODO(cjhopman): Enable this when http://crbug.com/156420 is addressed. Until then, users can
+ # set clang to 1, but Android stack traces will sometimes be incomplete.
+ #'clang%': 1,
+
+ # Set fastbuild to 1 to build with less debugging information. This can greatly decrease linking
+ # time. The downside is that stack traces will be missing useful information (like line
+ # numbers).
+ #'fastbuild%': 1,
+ },
+}
diff --git a/build/android/dex_action.gypi b/build/android/dex_action.gypi
new file mode 100644
index 00000000000..56d386ffad1
--- /dev/null
+++ b/build/android/dex_action.gypi
@@ -0,0 +1,60 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule that dexes
+# compiled java files. If proguard_enabled == "true" and CONFIGURATION_NAME ==
+# "Release", then it will dex the proguard_enabled_input_path instead of the
+# normal dex_input_paths/dex_generated_input_paths.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'action_name': 'some name for the action'
+# 'actions': [
+# 'variables': {
+# 'dex_input_paths': [ 'files to dex (when proguard is not used) and add to input paths' ],
+# 'dex_generated_input_dirs': [ 'dirs that contain generated files to dex' ],
+#
+# # For targets that use proguard:
+# 'proguard_enabled': 'true',
+# 'proguard_enabled_input_path': 'path to dex when using proguard',
+# },
+# 'includes': [ 'relative/path/to/dex_action.gypi' ],
+# ],
+# },
+#
+
+{
+ 'message': 'Creating dex file: <(output_path)',
+ 'variables': {
+ 'dex_input_paths': [],
+ 'dex_generated_input_dirs': [],
+ 'proguard_enabled%': 'false',
+ 'proguard_enabled_input_path%': '',
+ 'dex_no_locals%': 0,
+ 'dex_additional_options': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/util/md5_check.py',
+ '<(DEPTH)/build/android/gyp/dex.py',
+ '>@(dex_input_paths)',
+ ],
+ 'outputs': [
+ '<(output_path)',
+ '<(output_path).inputs',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/dex.py',
+ '--dex-path=<(output_path)',
+ '--android-sdk-tools=<(android_sdk_tools)',
+ '--output-directory=<(PRODUCT_DIR)',
+ '--configuration-name=<(CONFIGURATION_NAME)',
+ '--proguard-enabled=>(proguard_enabled)',
+ '--proguard-enabled-input-path=<(proguard_enabled_input_path)',
+ '--no-locals=>(dex_no_locals)',
+ '>@(dex_additional_options)',
+ '>@(dex_input_paths)',
+ '>@(dex_generated_input_dirs)',
+ ]
+}
diff --git a/build/android/disable_lto.gypi b/build/android/disable_lto.gypi
new file mode 100644
index 00000000000..e379cfd3199
--- /dev/null
+++ b/build/android/disable_lto.gypi
@@ -0,0 +1,20 @@
+# Copyright (c) 2014 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 file is meant to be included to disable LTO on a target.
+
+{
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['OS=="android" and (use_lto==1 or use_lto_o2==1)', {
+ 'cflags!': [
+ '-flto',
+ '-ffat-lto-objects',
+ ],
+ }],
+ ],
+ }],
+ ],
+}
diff --git a/build/android/empty/src/.keep b/build/android/empty/src/.keep
new file mode 100644
index 00000000000..0f710b673dd
--- /dev/null
+++ b/build/android/empty/src/.keep
@@ -0,0 +1,6 @@
+This is a file that needs to live here until http://crbug.com/158155 has
+been fixed.
+
+The ant build system requires that a src folder is always present, and for
+some of our targets that is not the case. Giving it an empty src-folder works
+nicely though.
diff --git a/build/android/empty_proguard.flags b/build/android/empty_proguard.flags
new file mode 100644
index 00000000000..53484fe8154
--- /dev/null
+++ b/build/android/empty_proguard.flags
@@ -0,0 +1 @@
+# Used for apk targets that do not need proguard. See build/java_apk.gypi.
diff --git a/build/android/enable_asserts.py b/build/android/enable_asserts.py
new file mode 100755
index 00000000000..8fb7dca4725
--- /dev/null
+++ b/build/android/enable_asserts.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Enables dalvik vm asserts in the android device."""
+
+import argparse
+import sys
+
+from pylib.device import device_utils
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ set_asserts_group = parser.add_mutually_exclusive_group(required=True)
+ set_asserts_group.add_argument(
+ '--enable_asserts', dest='set_asserts', action='store_true',
+ help='Sets the dalvik.vm.enableassertions property to "all"')
+ set_asserts_group.add_argument(
+ '--disable_asserts', dest='set_asserts', action='store_false',
+ help='Removes the dalvik.vm.enableassertions property')
+
+ args = parser.parse_args()
+
+ # TODO(jbudorick): Accept optional serial number and run only for the
+ # specified device when present.
+ devices = device_utils.DeviceUtils.parallel()
+
+ def set_java_asserts_and_restart(device):
+ if device.SetJavaAsserts(args.set_asserts):
+ device.RunShellCommand('stop')
+ device.RunShellCommand('start')
+
+ devices.pMap(set_java_asserts_and_restart)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/envsetup.sh b/build/android/envsetup.sh
new file mode 100755
index 00000000000..0545330bb2b
--- /dev/null
+++ b/build/android/envsetup.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+# Copyright (c) 2012 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.
+
+# Sets up environment for building Chromium on Android.
+
+# Make sure we're being sourced (possibly by another script). Check for bash
+# since zsh sets $0 when sourcing.
+if [[ -n "$BASH_VERSION" && "${BASH_SOURCE:-$0}" == "$0" ]]; then
+ echo "ERROR: envsetup must be sourced."
+ exit 1
+fi
+
+# This only exists to set local variables. Don't call this manually.
+android_envsetup_main() {
+ local SCRIPT_PATH="$1"
+ local SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
+
+ local CURRENT_DIR="$(readlink -f "${SCRIPT_DIR}/../../")"
+ if [[ -z "${CHROME_SRC}" ]]; then
+ # If $CHROME_SRC was not set, assume current directory is CHROME_SRC.
+ local CHROME_SRC="${CURRENT_DIR}"
+ fi
+
+ if [[ "${CURRENT_DIR/"${CHROME_SRC}"/}" == "${CURRENT_DIR}" ]]; then
+ # If current directory is not in $CHROME_SRC, it might be set for other
+ # source tree. If $CHROME_SRC was set correctly and we are in the correct
+ # directory, "${CURRENT_DIR/"${CHROME_SRC}"/}" will be "".
+ # Otherwise, it will equal to "${CURRENT_DIR}"
+ echo "Warning: Current directory is out of CHROME_SRC, it may not be \
+ the one you want."
+ echo "${CHROME_SRC}"
+ fi
+
+ # Allow the caller to override a few environment variables. If any of them is
+ # unset, we default to a sane value that's known to work. This allows for
+ # experimentation with a custom SDK.
+ if [[ -z "${ANDROID_SDK_ROOT}" || ! -d "${ANDROID_SDK_ROOT}" ]]; then
+ local ANDROID_SDK_ROOT="${CHROME_SRC}/third_party/android_tools/sdk/"
+ fi
+
+ # Add Android SDK tools to system path.
+ export PATH=$PATH:${ANDROID_SDK_ROOT}/platform-tools
+
+ # Add Android utility tools to the system path.
+ export PATH=$PATH:${ANDROID_SDK_ROOT}/tools/
+
+ # Add Chromium Android development scripts to system path.
+ # Must be after CHROME_SRC is set.
+ export PATH=$PATH:${CHROME_SRC}/build/android
+
+ export ENVSETUP_GYP_CHROME_SRC=${CHROME_SRC} # TODO(thakis): Remove.
+}
+# In zsh, $0 is the name of the file being sourced.
+android_envsetup_main "${BASH_SOURCE:-$0}"
+unset -f android_envsetup_main
+
+android_gyp() {
+ echo "Please call build/gyp_chromium instead. android_gyp is going away."
+ "${ENVSETUP_GYP_CHROME_SRC}/build/gyp_chromium" --depth="${ENVSETUP_GYP_CHROME_SRC}" --check "$@"
+}
diff --git a/build/android/finalize_apk_action.gypi b/build/android/finalize_apk_action.gypi
new file mode 100644
index 00000000000..644f9e8ecc5
--- /dev/null
+++ b/build/android/finalize_apk_action.gypi
@@ -0,0 +1,49 @@
+# Copyright 2013 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 file is meant to be included into an action to provide an action that
+# signs and zipaligns an APK.
+#
+# To use this, create a gyp action with the following form:
+# {
+# 'action_name': 'some descriptive action name',
+# 'variables': {
+# 'input_apk_path': 'relative/path/to/input.apk',
+# 'output_apk_path': 'relative/path/to/output.apk',
+# },
+# 'includes': [ '../../build/android/finalize_apk_action.gypi' ],
+# },
+#
+
+{
+ 'message': 'Signing/aligning <(_target_name) APK: <(input_apk_path)',
+ 'variables': {
+ 'keystore_path%': '<(DEPTH)/build/android/ant/chromium-debug.keystore',
+ 'keystore_name%': 'chromiumdebugkey',
+ 'keystore_password%': 'chromium',
+ 'zipalign_path%': '<(android_sdk_tools)/zipalign',
+ 'rezip_apk_jar_path%': '<(PRODUCT_DIR)/lib.java/rezip_apk.jar',
+ 'load_library_from_zip%': 0,
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/finalize_apk.py',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(keystore_path)',
+ '<(input_apk_path)',
+ ],
+ 'outputs': [
+ '<(output_apk_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/finalize_apk.py',
+ '--zipalign-path=<(zipalign_path)',
+ '--unsigned-apk-path=<(input_apk_path)',
+ '--final-apk-path=<(output_apk_path)',
+ '--key-path=<(keystore_path)',
+ '--key-name=<(keystore_name)',
+ '--key-passwd=<(keystore_password)',
+ '--load-library-from-zip=<(load_library_from_zip)',
+ '--rezip-apk-jar-path=<(rezip_apk_jar_path)',
+ ],
+}
diff --git a/build/android/finalize_splits_action.gypi b/build/android/finalize_splits_action.gypi
new file mode 100644
index 00000000000..daa7f834ebb
--- /dev/null
+++ b/build/android/finalize_splits_action.gypi
@@ -0,0 +1,76 @@
+# Copyright 2015 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 file is meant to be included into an action to provide an action that
+# signs and zipaligns split APKs.
+#
+# Required variables:
+# apk_name - Base name of the apk.
+# Optional variables:
+# density_splits - Whether to process density splits
+# language_splits - Whether to language splits
+
+{
+ 'variables': {
+ 'keystore_path%': '<(DEPTH)/build/android/ant/chromium-debug.keystore',
+ 'keystore_name%': 'chromiumdebugkey',
+ 'keystore_password%': 'chromium',
+ 'zipalign_path%': '<(android_sdk_tools)/zipalign',
+ 'density_splits%': 0,
+ 'language_splits%': [],
+ 'resource_packaged_apk_name': '<(apk_name)-resources.ap_',
+ 'resource_packaged_apk_path': '<(intermediate_dir)/<(resource_packaged_apk_name)',
+ 'base_output_path': '<(PRODUCT_DIR)/apks/<(apk_name)',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/finalize_splits.py',
+ '<(DEPTH)/build/android/gyp/finalize_apk.py',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(keystore_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/finalize_splits.py',
+ '--resource-packaged-apk-path=<(resource_packaged_apk_path)',
+ '--base-output-path=<(base_output_path)',
+ '--zipalign-path=<(zipalign_path)',
+ '--key-path=<(keystore_path)',
+ '--key-name=<(keystore_name)',
+ '--key-passwd=<(keystore_password)',
+ ],
+ 'conditions': [
+ ['density_splits == 1', {
+ 'message': 'Signing/aligning <(_target_name) density splits',
+ 'inputs': [
+ '<(resource_packaged_apk_path)_hdpi',
+ '<(resource_packaged_apk_path)_xhdpi',
+ '<(resource_packaged_apk_path)_xxhdpi',
+ '<(resource_packaged_apk_path)_xxxhdpi',
+ '<(resource_packaged_apk_path)_tvdpi',
+ ],
+ 'outputs': [
+ '<(base_output_path)-density-hdpi.apk',
+ '<(base_output_path)-density-xhdpi.apk',
+ '<(base_output_path)-density-xxhdpi.apk',
+ '<(base_output_path)-density-xxxhdpi.apk',
+ '<(base_output_path)-density-tvdpi.apk',
+ ],
+ 'action': [
+ '--densities=hdpi,xhdpi,xxhdpi,xxxhdpi,tvdpi',
+ ],
+ }],
+ ['language_splits != []', {
+ 'message': 'Signing/aligning <(_target_name) language splits',
+ 'inputs': [
+ "(auxclasspath)',
+ '--stamp', '<(stamp_path)',
+ '<(findbugs_target_jar_path)',
+ ],
+}
diff --git a/build/android/findbugs_diff.py b/build/android/findbugs_diff.py
new file mode 100755
index 00000000000..f55e46261be
--- /dev/null
+++ b/build/android/findbugs_diff.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Runs findbugs, and returns an error code if there are new warnings.
+
+Other options
+ --only-analyze used to only analyze the class you are interested.
+ --relase-build analyze the classes in out/Release directory.
+ --findbugs-args used to passin other findbugs's options.
+
+Run
+ $CHROMIUM_SRC/third_party/findbugs/bin/findbugs -textui for details.
+
+"""
+
+import argparse
+import os
+import sys
+
+from pylib import constants
+from pylib.utils import findbugs
+
+_DEFAULT_BASE_DIR = os.path.join(
+ constants.DIR_SOURCE_ROOT, 'build', 'android', 'findbugs_filter')
+
+sys.path.append(
+ os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', 'gyp'))
+from util import build_utils
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument(
+ '-a', '--auxclasspath', default=None, dest='auxclasspath',
+ help='Set aux classpath for analysis.')
+ parser.add_argument(
+ '--auxclasspath-gyp', dest='auxclasspath_gyp',
+ help='A gyp list containing the aux classpath for analysis')
+ parser.add_argument(
+ '-o', '--only-analyze', default=None,
+ dest='only_analyze', help='Only analyze the given classes and packages.')
+ parser.add_argument(
+ '-e', '--exclude', default=None, dest='exclude',
+ help='Exclude bugs matching given filter.')
+ parser.add_argument(
+ '-l', '--release-build', action='store_true', dest='release_build',
+ help='Analyze release build instead of debug.')
+ parser.add_argument(
+ '-f', '--findbug-args', default=None, dest='findbug_args',
+ help='Additional findbug arguments.')
+ parser.add_argument(
+ '-b', '--base-dir', default=_DEFAULT_BASE_DIR,
+ dest='base_dir', help='Base directory for configuration file.')
+ parser.add_argument(
+ '--output-file', dest='output_file',
+ help='Path to save the output to.')
+ parser.add_argument(
+ '--stamp', help='Path to touch on success.')
+ parser.add_argument(
+ '--depfile', help='Path to the depfile. This must be specified as the '
+ "action's first output.")
+
+ parser.add_argument(
+ 'jar_paths', metavar='JAR_PATH', nargs='+',
+ help='JAR file to analyze')
+
+ args = parser.parse_args(build_utils.ExpandFileArgs(sys.argv[1:]))
+ if args.auxclasspath:
+ args.auxclasspath = args.auxclasspath.split(':')
+ elif args.auxclasspath_gyp:
+ args.auxclasspath = build_utils.ParseGypList(args.auxclasspath_gyp)
+
+ if args.base_dir:
+ if not args.exclude:
+ args.exclude = os.path.join(args.base_dir, 'findbugs_exclude.xml')
+
+ findbugs_command, findbugs_warnings = findbugs.Run(
+ args.exclude, args.only_analyze, args.auxclasspath,
+ args.output_file, args.findbug_args, args.jar_paths)
+
+ if findbugs_warnings:
+ print
+ print '*' * 80
+ print 'FindBugs run via:'
+ print findbugs_command
+ print
+ print 'FindBugs reported the following issues:'
+ for warning in sorted(findbugs_warnings):
+ print str(warning)
+ print '*' * 80
+ print
+ else:
+ if args.depfile:
+ build_utils.WriteDepfile(
+ args.depfile,
+ build_utils.GetPythonDependencies() + args.auxclasspath
+ + args.jar_paths)
+ if args.stamp:
+ build_utils.Touch(args.stamp)
+
+ return len(findbugs_warnings)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/findbugs_filter/findbugs_exclude.xml b/build/android/findbugs_filter/findbugs_exclude.xml
new file mode 100644
index 00000000000..dbff9d9667f
--- /dev/null
+++ b/build/android/findbugs_filter/findbugs_exclude.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/android/generate_emma_html.py b/build/android/generate_emma_html.py
new file mode 100755
index 00000000000..93b0b0e59c5
--- /dev/null
+++ b/build/android/generate_emma_html.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+# Copyright 2013 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.
+
+"""Aggregates EMMA coverage files to produce html output."""
+
+import fnmatch
+import json
+import optparse
+import os
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+
+
+def _GetFilesWithExt(root_dir, ext):
+ """Gets all files with a given extension.
+
+ Args:
+ root_dir: Directory in which to search for files.
+ ext: Extension to look for (including dot)
+
+ Returns:
+ A list of absolute paths to files that match.
+ """
+ files = []
+ for root, _, filenames in os.walk(root_dir):
+ basenames = fnmatch.filter(filenames, '*.' + ext)
+ files.extend([os.path.join(root, basename)
+ for basename in basenames])
+
+ return files
+
+
+def main():
+ option_parser = optparse.OptionParser()
+ option_parser.add_option('--output', help='HTML output filename.')
+ option_parser.add_option('--coverage-dir', default=None,
+ help=('Root of the directory in which to search for '
+ 'coverage data (.ec) files.'))
+ option_parser.add_option('--metadata-dir', default=None,
+ help=('Root of the directory in which to search for '
+ 'coverage metadata (.em) files.'))
+ option_parser.add_option('--cleanup', action='store_true',
+ help=('If set, removes coverage files generated at '
+ 'runtime.'))
+ options, _ = option_parser.parse_args()
+
+ if not (options.coverage_dir and options.metadata_dir and options.output):
+ option_parser.error('One or more mandatory options are missing.')
+
+ coverage_files = _GetFilesWithExt(options.coverage_dir, 'ec')
+ metadata_files = _GetFilesWithExt(options.metadata_dir, 'em')
+ print 'Found coverage files: %s' % str(coverage_files)
+ print 'Found metadata files: %s' % str(metadata_files)
+
+ sources = []
+ for f in metadata_files:
+ sources_file = os.path.splitext(f)[0] + '_sources.txt'
+ with open(sources_file, 'r') as sf:
+ sources.extend(json.load(sf))
+ sources = [os.path.join(constants.DIR_SOURCE_ROOT, s) for s in sources]
+ print 'Sources: %s' % sources
+
+ input_args = []
+ for f in coverage_files + metadata_files:
+ input_args.append('-in')
+ input_args.append(f)
+
+ output_args = ['-Dreport.html.out.file', options.output]
+ source_args = ['-sp', ','.join(sources)]
+
+ exit_code = cmd_helper.RunCmd(
+ ['java', '-cp',
+ os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'lib', 'emma.jar'),
+ 'emma', 'report', '-r', 'html']
+ + input_args + output_args + source_args)
+
+ if options.cleanup:
+ for f in coverage_files:
+ os.remove(f)
+
+ return exit_code
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gn/zip.py b/build/android/gn/zip.py
new file mode 100755
index 00000000000..5050ea07107
--- /dev/null
+++ b/build/android/gn/zip.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Archives a set of files.
+"""
+
+import ast
+import optparse
+import os
+import sys
+import zipfile
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
+from util import build_utils
+
+def DoZip(inputs, output, base_dir):
+ with zipfile.ZipFile(output, 'w') as outfile:
+ for f in inputs:
+ outfile.write(f, os.path.relpath(f, base_dir))
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--inputs', help='List of files to archive.')
+ parser.add_option('--output', help='Path to output archive.')
+ parser.add_option('--base-dir',
+ help='If provided, the paths in the archive will be '
+ 'relative to this directory', default='.')
+
+ options, _ = parser.parse_args()
+
+ inputs = ast.literal_eval(options.inputs)
+ output = options.output
+ base_dir = options.base_dir
+
+ DoZip(inputs, output, base_dir)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/aidl.py b/build/android/gyp/aidl.py
new file mode 100755
index 00000000000..d5aa5467709
--- /dev/null
+++ b/build/android/gyp/aidl.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Invokes Android's aidl
+"""
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+
+def main(argv):
+ option_parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(option_parser)
+ option_parser.add_option('--aidl-path', help='Path to the aidl binary.')
+ option_parser.add_option('--imports', help='Files to import.')
+ option_parser.add_option('--includes',
+ help='Directories to add as import search paths.')
+ option_parser.add_option('--srcjar', help='Path for srcjar output.')
+ options, args = option_parser.parse_args(argv[1:])
+
+ with build_utils.TempDir() as temp_dir:
+ for f in args:
+ classname = os.path.splitext(os.path.basename(f))[0]
+ output = os.path.join(temp_dir, classname + '.java')
+ aidl_cmd = [options.aidl_path]
+ aidl_cmd += [
+ '-p' + s for s in build_utils.ParseGypList(options.imports)
+ ]
+ if options.includes is not None:
+ aidl_cmd += [
+ '-I' + s for s in build_utils.ParseGypList(options.includes)
+ ]
+ aidl_cmd += [
+ f,
+ output
+ ]
+ build_utils.CheckOutput(aidl_cmd)
+
+ build_utils.ZipDir(options.srcjar, temp_dir)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/gyp/ant.py b/build/android/gyp/ant.py
new file mode 100755
index 00000000000..5394b9ec7d5
--- /dev/null
+++ b/build/android/gyp/ant.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""An Ant wrapper that suppresses useless Ant output.
+
+Ant build scripts output "BUILD SUCCESSFUL" and build timing at the end of
+every build. In the Android build, this just adds a lot of useless noise to the
+build output. This script forwards its arguments to ant, and prints Ant's
+output up until the BUILD SUCCESSFUL line.
+
+Also, when a command fails, this script will re-run that ant command with the
+'-verbose' argument so that the failure is easier to debug.
+"""
+
+import optparse
+import sys
+import traceback
+
+from util import build_utils
+
+
+def main(argv):
+ option_parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(option_parser)
+ options, args = option_parser.parse_args(argv[1:])
+
+ try:
+ stdout = build_utils.CheckOutput(['ant'] + args)
+ except build_utils.CalledProcessError:
+ # It is very difficult to diagnose ant failures without the '-verbose'
+ # argument. So, when an ant command fails, re-run it with '-verbose' so that
+ # the cause of the failure is easier to identify.
+ verbose_args = ['-verbose'] + [a for a in args if a != '-quiet']
+ try:
+ stdout = build_utils.CheckOutput(['ant'] + verbose_args)
+ except build_utils.CalledProcessError:
+ traceback.print_exc()
+ sys.exit(1)
+
+ # If this did sys.exit(1), building again would succeed (which would be
+ # awkward). Instead, just print a big warning.
+ build_utils.PrintBigWarning(
+ 'This is unexpected. `ant ' + ' '.join(args) + '` failed.' +
+ 'But, running `ant ' + ' '.join(verbose_args) + '` passed.')
+
+ stdout = stdout.strip().split('\n')
+ for line in stdout:
+ if line.strip() == 'BUILD SUCCESSFUL':
+ break
+ print line
+
+ if options.depfile:
+ assert '-buildfile' in args
+ ant_buildfile = args[args.index('-buildfile') + 1]
+
+ build_utils.WriteDepfile(
+ options.depfile,
+ [ant_buildfile] + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/gyp/apk_install.py b/build/android/gyp/apk_install.py
new file mode 100755
index 00000000000..a512e50e3d5
--- /dev/null
+++ b/build/android/gyp/apk_install.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Installs an APK.
+
+"""
+
+import optparse
+import os
+import re
+import sys
+
+from util import build_device
+from util import build_utils
+from util import md5_check
+
+BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
+sys.path.append(BUILD_ANDROID_DIR)
+
+from pylib import constants
+from pylib.utils import apk_helper
+
+
+def GetNewMetadata(device, apk_package):
+ """Gets the metadata on the device for the apk_package apk."""
+ output = device.RunShellCommand('ls -l /data/app/')
+ # Matches lines like:
+ # -rw-r--r-- system system 7376582 2013-04-19 16:34 \
+ # org.chromium.chrome.shell.apk
+ # -rw-r--r-- system system 7376582 2013-04-19 16:34 \
+ # org.chromium.chrome.shell-1.apk
+ apk_matcher = lambda s: re.match('.*%s(-[0-9]*)?(.apk)?$' % apk_package, s)
+ matches = filter(apk_matcher, output)
+ return matches[0] if matches else None
+
+def HasInstallMetadataChanged(device, apk_package, metadata_path):
+ """Checks if the metadata on the device for apk_package has changed."""
+ if not os.path.exists(metadata_path):
+ return True
+
+ with open(metadata_path, 'r') as expected_file:
+ return expected_file.read() != device.GetInstallMetadata(apk_package)
+
+
+def RecordInstallMetadata(device, apk_package, metadata_path):
+ """Records the metadata from the device for apk_package."""
+ metadata = GetNewMetadata(device, apk_package)
+ if not metadata:
+ raise Exception('APK install failed unexpectedly.')
+
+ with open(metadata_path, 'w') as outfile:
+ outfile.write(metadata)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--apk-path',
+ help='Path to .apk to install.')
+ parser.add_option('--split-apk-path',
+ help='Path to .apk splits (can specify multiple times, causes '
+ '--install-multiple to be used.',
+ action='append')
+ parser.add_option('--android-sdk-tools',
+ help='Path to the Android SDK build tools folder. ' +
+ 'Required when using --split-apk-path.')
+ parser.add_option('--install-record',
+ help='Path to install record (touched only when APK is installed).')
+ parser.add_option('--build-device-configuration',
+ help='Path to build device configuration.')
+ parser.add_option('--stamp',
+ help='Path to touch on success.')
+ parser.add_option('--configuration-name',
+ help='The build CONFIGURATION_NAME')
+ options, _ = parser.parse_args()
+
+ device = build_device.GetBuildDeviceFromPath(
+ options.build_device_configuration)
+ if not device:
+ return
+
+ constants.SetBuildType(options.configuration_name)
+
+ serial_number = device.GetSerialNumber()
+ apk_package = apk_helper.GetPackageName(options.apk_path)
+
+ metadata_path = '%s.%s.device.time.stamp' % (options.apk_path, serial_number)
+
+ # If the APK on the device does not match the one that was last installed by
+ # the build, then the APK has to be installed (regardless of the md5 record).
+ force_install = HasInstallMetadataChanged(device, apk_package, metadata_path)
+
+
+ def Install():
+ if options.split_apk_path:
+ device.InstallSplitApk(options.apk_path, options.split_apk_path)
+ else:
+ device.Install(options.apk_path, reinstall=True)
+
+ RecordInstallMetadata(device, apk_package, metadata_path)
+ build_utils.Touch(options.install_record)
+
+
+ record_path = '%s.%s.md5.stamp' % (options.apk_path, serial_number)
+ md5_check.CallAndRecordIfStale(
+ Install,
+ record_path=record_path,
+ input_paths=[options.apk_path],
+ force=force_install)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/apk_obfuscate.py b/build/android/gyp/apk_obfuscate.py
new file mode 100755
index 00000000000..b0757588007
--- /dev/null
+++ b/build/android/gyp/apk_obfuscate.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Generates the obfuscated jar and test jar for an apk.
+
+If proguard is not enabled or 'Release' is not in the configuration name,
+obfuscation will be a no-op.
+"""
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+from util import proguard_util
+
+
+def ParseArgs(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('--android-sdk', help='path to the Android SDK folder')
+ parser.add_option('--android-sdk-tools',
+ help='path to the Android SDK build tools folder')
+ parser.add_option('--android-sdk-jar',
+ help='path to Android SDK\'s android.jar')
+ parser.add_option('--proguard-jar-path',
+ help='Path to proguard.jar in the sdk')
+ parser.add_option('--input-jars-paths',
+ help='Path to jars to include in obfuscated jar')
+
+ parser.add_option('--proguard-configs',
+ help='Paths to proguard config files')
+
+ parser.add_option('--configuration-name',
+ help='Gyp configuration name (i.e. Debug, Release)')
+ parser.add_option('--proguard-enabled', action='store_true',
+ help='Set if proguard is enabled for this target.')
+
+ parser.add_option('--obfuscated-jar-path',
+ help='Output path for obfuscated jar.')
+
+ parser.add_option('--testapp', action='store_true',
+ help='Set this if building an instrumentation test apk')
+ parser.add_option('--tested-apk-obfuscated-jar-path',
+ help='Path to obfusctated jar of the tested apk')
+ parser.add_option('--test-jar-path',
+ help='Output path for jar containing all the test apk\'s '
+ 'code.')
+
+ parser.add_option('--stamp', help='File to touch on success')
+
+ (options, args) = parser.parse_args(argv)
+
+ if args:
+ parser.error('No positional arguments should be given. ' + str(args))
+
+ # Check that required options have been provided.
+ required_options = (
+ 'android_sdk',
+ 'android_sdk_tools',
+ 'android_sdk_jar',
+ 'proguard_jar_path',
+ 'input_jars_paths',
+ 'configuration_name',
+ 'obfuscated_jar_path',
+ )
+
+ if options.testapp:
+ required_options += (
+ 'test_jar_path',
+ )
+
+ build_utils.CheckOptions(options, parser, required=required_options)
+ return options, args
+
+
+def DoProguard(options):
+ proguard = proguard_util.ProguardCmdBuilder(options.proguard_jar_path)
+ proguard.outjar(options.obfuscated_jar_path)
+
+ library_classpath = [options.android_sdk_jar]
+ input_jars = build_utils.ParseGypList(options.input_jars_paths)
+
+ exclude_paths = []
+ configs = build_utils.ParseGypList(options.proguard_configs)
+ if options.tested_apk_obfuscated_jar_path:
+ # configs should only contain the process_resources.py generated config.
+ assert len(configs) == 1, (
+ 'test apks should not have custom proguard configs: ' + str(configs))
+ tested_jar_info = build_utils.ReadJson(
+ options.tested_apk_obfuscated_jar_path + '.info')
+ exclude_paths = tested_jar_info['inputs']
+ configs = tested_jar_info['configs']
+
+ proguard.is_test(True)
+ proguard.mapping(options.tested_apk_obfuscated_jar_path + '.mapping')
+ library_classpath.append(options.tested_apk_obfuscated_jar_path)
+
+ proguard.libraryjars(library_classpath)
+ proguard_injars = [p for p in input_jars if p not in exclude_paths]
+ proguard.injars(proguard_injars)
+ proguard.configs(configs)
+
+ proguard.CheckOutput()
+
+ this_info = {
+ 'inputs': proguard_injars,
+ 'configs': configs
+ }
+
+ build_utils.WriteJson(
+ this_info, options.obfuscated_jar_path + '.info')
+
+
+def main(argv):
+ options, _ = ParseArgs(argv)
+
+ input_jars = build_utils.ParseGypList(options.input_jars_paths)
+
+ if options.testapp:
+ dependency_class_filters = [
+ '*R.class', '*R$*.class', '*Manifest.class', '*BuildConfig.class']
+ build_utils.MergeZips(
+ options.test_jar_path, input_jars, dependency_class_filters)
+
+ if options.configuration_name == 'Release' and options.proguard_enabled:
+ DoProguard(options)
+ else:
+ output_files = [
+ options.obfuscated_jar_path,
+ options.obfuscated_jar_path + '.info',
+ options.obfuscated_jar_path + '.dump',
+ options.obfuscated_jar_path + '.seeds',
+ options.obfuscated_jar_path + '.usage',
+ options.obfuscated_jar_path + '.mapping']
+ for f in output_files:
+ if os.path.exists(f):
+ os.remove(f)
+ build_utils.Touch(f)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/copy_ex.py b/build/android/gyp/copy_ex.py
new file mode 100755
index 00000000000..a474e770654
--- /dev/null
+++ b/build/android/gyp/copy_ex.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Copies files to a directory."""
+
+import optparse
+import os
+import shutil
+import sys
+
+from util import build_utils
+
+
+def _get_all_files(base):
+ """Returns a list of all the files in |base|. Each entry is relative to the
+ last path entry of |base|."""
+ result = []
+ dirname = os.path.dirname(base)
+ for root, _, files in os.walk(base):
+ result.extend([os.path.join(root[len(dirname):], f) for f in files])
+ return result
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--dest', help='Directory to copy files to.')
+ parser.add_option('--files', action='append',
+ help='List of files to copy.')
+ parser.add_option('--clear', action='store_true',
+ help='If set, the destination directory will be deleted '
+ 'before copying files to it. This is highly recommended to '
+ 'ensure that no stale files are left in the directory.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, _ = parser.parse_args(args)
+
+ if options.clear:
+ build_utils.DeleteDirectory(options.dest)
+ build_utils.MakeDirectory(options.dest)
+
+ files = []
+ for file_arg in options.files:
+ files += build_utils.ParseGypList(file_arg)
+
+ deps = []
+
+ for f in files:
+ if os.path.isdir(f):
+ if not options.clear:
+ print ('To avoid stale files you must use --clear when copying '
+ 'directories')
+ sys.exit(-1)
+ shutil.copytree(f, os.path.join(options.dest, os.path.basename(f)))
+ deps.extend(_get_all_files(f))
+ else:
+ shutil.copy(f, options.dest)
+ deps.append(f)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ deps + build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
+
diff --git a/build/android/gyp/create_device_library_links.py b/build/android/gyp/create_device_library_links.py
new file mode 100755
index 00000000000..3e630b67c9f
--- /dev/null
+++ b/build/android/gyp/create_device_library_links.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Creates symlinks to native libraries for an APK.
+
+The native libraries should have previously been pushed to the device (in
+options.target_dir). This script then creates links in an apk's lib/ folder to
+those native libraries.
+"""
+
+import optparse
+import os
+import sys
+
+from util import build_device
+from util import build_utils
+
+BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
+sys.path.append(BUILD_ANDROID_DIR)
+
+from pylib import constants
+from pylib.utils import apk_helper
+
+def RunShellCommand(device, cmd):
+ output = device.RunShellCommand(cmd)
+
+ if output:
+ raise Exception(
+ 'Unexpected output running command: ' + cmd + '\n' +
+ '\n'.join(output))
+
+
+def CreateSymlinkScript(options):
+ libraries = build_utils.ParseGypList(options.libraries)
+
+ link_cmd = (
+ 'rm $APK_LIBRARIES_DIR/%(lib_basename)s > /dev/null 2>&1 \n'
+ 'ln -s $STRIPPED_LIBRARIES_DIR/%(lib_basename)s '
+ '$APK_LIBRARIES_DIR/%(lib_basename)s \n'
+ )
+
+ script = '#!/bin/sh \n'
+
+ for lib in libraries:
+ script += link_cmd % { 'lib_basename': lib }
+
+ with open(options.script_host_path, 'w') as scriptfile:
+ scriptfile.write(script)
+
+
+def TriggerSymlinkScript(options):
+ device = build_device.GetBuildDeviceFromPath(
+ options.build_device_configuration)
+ if not device:
+ return
+
+ apk_package = apk_helper.GetPackageName(options.apk)
+ apk_libraries_dir = '/data/data/%s/lib' % apk_package
+
+ device_dir = os.path.dirname(options.script_device_path)
+ mkdir_cmd = ('if [ ! -e %(dir)s ]; then mkdir -p %(dir)s; fi ' %
+ { 'dir': device_dir })
+ RunShellCommand(device, mkdir_cmd)
+ device.PushChangedFiles([(options.script_host_path,
+ options.script_device_path)])
+
+ trigger_cmd = (
+ 'APK_LIBRARIES_DIR=%(apk_libraries_dir)s; '
+ 'STRIPPED_LIBRARIES_DIR=%(target_dir)s; '
+ '. %(script_device_path)s'
+ ) % {
+ 'apk_libraries_dir': apk_libraries_dir,
+ 'target_dir': options.target_dir,
+ 'script_device_path': options.script_device_path
+ }
+ RunShellCommand(device, trigger_cmd)
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ parser.add_option('--apk', help='Path to the apk.')
+ parser.add_option('--script-host-path',
+ help='Path on the host for the symlink script.')
+ parser.add_option('--script-device-path',
+ help='Path on the device to push the created symlink script.')
+ parser.add_option('--libraries',
+ help='List of native libraries.')
+ parser.add_option('--target-dir',
+ help='Device directory that contains the target libraries for symlinks.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--build-device-configuration',
+ help='Path to build device configuration.')
+ parser.add_option('--configuration-name',
+ help='The build CONFIGURATION_NAME')
+ options, _ = parser.parse_args(args)
+
+ required_options = ['apk', 'libraries', 'script_host_path',
+ 'script_device_path', 'target_dir', 'configuration_name']
+ build_utils.CheckOptions(options, parser, required=required_options)
+ constants.SetBuildType(options.configuration_name)
+
+ CreateSymlinkScript(options)
+ TriggerSymlinkScript(options)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/create_dist_jar.py b/build/android/gyp/create_dist_jar.py
new file mode 100755
index 00000000000..0d31c5db93f
--- /dev/null
+++ b/build/android/gyp/create_dist_jar.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Merges a list of jars into a single jar."""
+
+import optparse
+import sys
+
+from util import build_utils
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--output', help='Path to output jar.')
+ parser.add_option('--inputs', action='append', help='List of jar inputs.')
+ options, _ = parser.parse_args(args)
+ build_utils.CheckOptions(options, parser, ['output', 'inputs'])
+
+ input_jars = []
+ for inputs_arg in options.inputs:
+ input_jars.extend(build_utils.ParseGypList(inputs_arg))
+
+ build_utils.MergeZips(options.output, input_jars)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ input_jars + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/create_flutter_jar.py b/build/android/gyp/create_flutter_jar.py
new file mode 100644
index 00000000000..c30bae2bd9f
--- /dev/null
+++ b/build/android/gyp/create_flutter_jar.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 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.
+
+"""Create a JAR incorporating all the components required to build a Flutter application"""
+
+import optparse
+import os
+import sys
+import zipfile
+
+from util import build_utils
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--output', help='Path to output jar.')
+ parser.add_option('--dist_jar', help='Flutter shell Java code jar.')
+ parser.add_option('--native_lib', action='append', help='Native code library.')
+ parser.add_option('--android_abi', help='Native code ABI.')
+ parser.add_option('--asset_dir', help='Path to assets.')
+ options, _ = parser.parse_args(args)
+ build_utils.CheckOptions(options, parser, [
+ 'output', 'dist_jar', 'native_lib', 'android_abi', 'asset_dir'
+ ])
+
+ input_deps = []
+
+ with zipfile.ZipFile(options.output, 'w', zipfile.ZIP_DEFLATED) as out_zip:
+ input_deps.append(options.dist_jar)
+ with zipfile.ZipFile(options.dist_jar, 'r') as dist_zip:
+ for dist_file in dist_zip.infolist():
+ if dist_file.filename.endswith('.class'):
+ out_zip.writestr(dist_file.filename, dist_zip.read(dist_file.filename))
+
+ for native_lib in options.native_lib:
+ input_deps.append(native_lib)
+ out_zip.write(native_lib,
+ 'lib/%s/%s' % (options.android_abi, os.path.basename(native_lib)))
+
+ for asset_file in os.listdir(options.asset_dir):
+ input_deps.append(asset_file)
+ out_zip.write(os.path.join(options.asset_dir, asset_file),
+ 'assets/%s' % asset_file)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ input_deps + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/create_java_binary_script.py b/build/android/gyp/create_java_binary_script.py
new file mode 100755
index 00000000000..5de43f2d4a4
--- /dev/null
+++ b/build/android/gyp/create_java_binary_script.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Creates a simple script to run a java "binary".
+
+This creates a script that sets up the java command line for running a java
+jar. This includes correctly setting the classpath and the main class.
+"""
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+# The java command must be executed in the current directory because there may
+# be user-supplied paths in the args. The script receives the classpath relative
+# to the directory that the script is written in and then, when run, must
+# recalculate the paths relative to the current directory.
+script_template = """\
+#!/usr/bin/env python
+#
+# This file was generated by build/android/gyp/create_java_binary_script.py
+
+import os
+import sys
+
+self_dir = os.path.dirname(__file__)
+classpath = [{classpath}]
+if os.getcwd() != self_dir:
+ offset = os.path.relpath(self_dir, os.getcwd())
+ classpath = [os.path.join(offset, p) for p in classpath]
+java_args = [
+ "java",
+ "-classpath", ":".join(classpath),
+ "-enableassertions",
+ \"{main_class}\"] + sys.argv[1:]
+os.execvp("java", java_args)
+"""
+
+def main(argv):
+ argv = build_utils.ExpandFileArgs(argv)
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--output', help='Output path for executable script.')
+ parser.add_option('--jar-path', help='Path to the main jar.')
+ parser.add_option('--main-class',
+ help='Name of the java class with the "main" entry point.')
+ parser.add_option('--classpath', action='append',
+ help='Classpath for running the jar.')
+ options, _ = parser.parse_args(argv)
+
+ classpath = [options.jar_path]
+ for cp_arg in options.classpath:
+ classpath += build_utils.ParseGypList(cp_arg)
+
+ run_dir = os.path.dirname(options.output)
+ classpath = [os.path.relpath(p, run_dir) for p in classpath]
+
+ with open(options.output, 'w') as script:
+ script.write(script_template.format(
+ classpath=('"%s"' % '", "'.join(classpath)),
+ main_class=options.main_class))
+
+ os.chmod(options.output, 0750)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/create_placeholder_files.py b/build/android/gyp/create_placeholder_files.py
new file mode 100755
index 00000000000..103e1df7f2d
--- /dev/null
+++ b/build/android/gyp/create_placeholder_files.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Create placeholder files.
+"""
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option(
+ '--dest-lib-dir',
+ help='Destination directory to have placeholder files.')
+ parser.add_option(
+ '--stamp',
+ help='Path to touch on success')
+
+ options, args = parser.parse_args()
+
+ for name in args:
+ target_path = os.path.join(options.dest_lib_dir, name)
+ build_utils.Touch(target_path)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/gyp/create_standalone_apk.py b/build/android/gyp/create_standalone_apk.py
new file mode 100755
index 00000000000..c5605992860
--- /dev/null
+++ b/build/android/gyp/create_standalone_apk.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Combines stripped libraries and incomplete APK into single standalone APK.
+
+"""
+
+import optparse
+import os
+import shutil
+import sys
+import tempfile
+
+from util import build_utils
+from util import md5_check
+
+def CreateStandaloneApk(options):
+ def DoZip():
+ with tempfile.NamedTemporaryFile(suffix='.zip') as intermediate_file:
+ intermediate_path = intermediate_file.name
+ shutil.copy(options.input_apk_path, intermediate_path)
+ apk_path_abs = os.path.abspath(intermediate_path)
+ build_utils.CheckOutput(
+ ['zip', '-r', '-1', apk_path_abs, 'lib'],
+ cwd=options.libraries_top_dir)
+ shutil.copy(intermediate_path, options.output_apk_path)
+
+ input_paths = [options.input_apk_path, options.libraries_top_dir]
+ record_path = '%s.standalone.stamp' % options.input_apk_path
+ md5_check.CallAndRecordIfStale(
+ DoZip,
+ record_path=record_path,
+ input_paths=input_paths)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--libraries-top-dir',
+ help='Top directory that contains libraries '
+ '(i.e. library paths are like '
+ 'libraries_top_dir/lib/android_app_abi/foo.so).')
+ parser.add_option('--input-apk-path', help='Path to incomplete APK.')
+ parser.add_option('--output-apk-path', help='Path for standalone APK.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ options, _ = parser.parse_args()
+
+ required_options = ['libraries_top_dir', 'input_apk_path', 'output_apk_path']
+ build_utils.CheckOptions(options, parser, required=required_options)
+
+ CreateStandaloneApk(options)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/create_test_runner_script.py b/build/android/gyp/create_test_runner_script.py
new file mode 100755
index 00000000000..247bf20ad52
--- /dev/null
+++ b/build/android/gyp/create_test_runner_script.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 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.
+
+"""Creates a script to run an android test using build/android/test_runner.py.
+"""
+
+import argparse
+import os
+import sys
+
+from util import build_utils
+
+SCRIPT_TEMPLATE = """\
+#!/usr/bin/env python
+#
+# This file was generated by build/android/gyp/create_test_runner_script.py
+
+import logging
+import os
+import sys
+
+def main():
+ script_directory = os.path.dirname(__file__)
+
+ def ResolvePath(path):
+ \"\"\"Returns an absolute filepath given a path relative to this script.
+ \"\"\"
+ return os.path.abspath(os.path.join(script_directory, path))
+
+ test_runner_path = ResolvePath('{test_runner_path}')
+ test_runner_args = {test_runner_args}
+ test_runner_path_args = {test_runner_path_args}
+ for arg, path in test_runner_path_args.iteritems():
+ test_runner_args.extend([arg, ResolvePath(path)])
+
+ test_runner_cmd = ' '.join(
+ [test_runner_path] + test_runner_args + sys.argv[1:])
+ logging.critical(test_runner_cmd)
+ os.system(test_runner_cmd)
+
+if __name__ == '__main__':
+ sys.exit(main())
+"""
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--script-output-path',
+ help='Output path for executable script.')
+ parser.add_argument('--depfile',
+ help='Path to the depfile. This must be specified as '
+ "the action's first output.")
+ # We need to intercept any test runner path arguments and make all
+ # of the paths relative to the output script directory.
+ group = parser.add_argument_group('Test runner path arguments.')
+ group.add_argument('--output-directory')
+ group.add_argument('--isolate-file-path')
+ group.add_argument('--support-apk')
+ args, test_runner_args = parser.parse_known_args()
+
+ def RelativizePathToScript(path):
+ """Returns the path relative to the output script directory."""
+ return os.path.relpath(path, os.path.dirname(args.script_output_path))
+
+ test_runner_path = os.path.join(
+ os.path.dirname(__file__), os.path.pardir, 'test_runner.py')
+ test_runner_path = RelativizePathToScript(test_runner_path)
+
+ test_runner_path_args = {}
+ if args.output_directory:
+ test_runner_path_args['--output-directory'] = RelativizePathToScript(
+ args.output_directory)
+ if args.isolate_file_path:
+ test_runner_path_args['--isolate-file-path'] = RelativizePathToScript(
+ args.isolate_file_path)
+ if args.support_apk:
+ test_runner_path_args['--support-apk'] = RelativizePathToScript(
+ args.support_apk)
+
+ with open(args.script_output_path, 'w') as script:
+ script.write(SCRIPT_TEMPLATE.format(
+ test_runner_path=str(test_runner_path),
+ test_runner_args=str(test_runner_args),
+ test_runner_path_args=str(test_runner_path_args)))
+
+ os.chmod(args.script_output_path, 0750)
+
+ if args.depfile:
+ build_utils.WriteDepfile(
+ args.depfile,
+ build_utils.GetPythonDependencies())
+
+if __name__ == '__main__':
+ sys.exit(main())
\ No newline at end of file
diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py
new file mode 100755
index 00000000000..c26d23a6116
--- /dev/null
+++ b/build/android/gyp/dex.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+from util import md5_check
+
+
+def DoDex(options, paths):
+ dx_binary = os.path.join(options.android_sdk_tools, 'dx')
+ # See http://crbug.com/272064 for context on --force-jumbo.
+ dex_cmd = [dx_binary, '--dex', '--force-jumbo', '--output', options.dex_path]
+ if options.no_locals != '0':
+ dex_cmd.append('--no-locals')
+
+ dex_cmd += paths
+
+ record_path = '%s.md5.stamp' % options.dex_path
+ md5_check.CallAndRecordIfStale(
+ lambda: build_utils.CheckOutput(dex_cmd, print_stderr=False),
+ record_path=record_path,
+ input_paths=paths,
+ input_strings=dex_cmd,
+ force=not os.path.exists(options.dex_path))
+ build_utils.WriteJson(
+ [os.path.relpath(p, options.output_directory) for p in paths],
+ options.dex_path + '.inputs')
+
+
+def main():
+ args = build_utils.ExpandFileArgs(sys.argv[1:])
+
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--android-sdk-tools',
+ help='Android sdk build tools directory.')
+ parser.add_option('--output-directory',
+ default=os.getcwd(),
+ help='Path to the output build directory.')
+ parser.add_option('--dex-path', help='Dex output path.')
+ parser.add_option('--configuration-name',
+ help='The build CONFIGURATION_NAME.')
+ parser.add_option('--proguard-enabled',
+ help='"true" if proguard is enabled.')
+ parser.add_option('--proguard-enabled-input-path',
+ help=('Path to dex in Release mode when proguard '
+ 'is enabled.'))
+ parser.add_option('--no-locals',
+ help='Exclude locals list from the dex file.')
+ parser.add_option('--inputs', help='A list of additional input paths.')
+ parser.add_option('--excluded-paths',
+ help='A list of paths to exclude from the dex file.')
+
+ options, paths = parser.parse_args(args)
+
+ required_options = ('android_sdk_tools',)
+ build_utils.CheckOptions(options, parser, required=required_options)
+
+ if (options.proguard_enabled == 'true'
+ and options.configuration_name == 'Release'):
+ paths = [options.proguard_enabled_input_path]
+
+ if options.inputs:
+ paths += build_utils.ParseGypList(options.inputs)
+
+ if options.excluded_paths:
+ # Excluded paths are relative to the output directory.
+ exclude_paths = build_utils.ParseGypList(options.excluded_paths)
+ paths = [p for p in paths if not
+ os.path.relpath(p, options.output_directory) in exclude_paths]
+
+ DoDex(options, paths)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ paths + build_utils.GetPythonDependencies())
+
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/emma_instr.py b/build/android/gyp/emma_instr.py
new file mode 100755
index 00000000000..6f3555a3276
--- /dev/null
+++ b/build/android/gyp/emma_instr.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Instruments classes and jar files.
+
+This script corresponds to the 'emma_instr' action in the java build process.
+Depending on whether emma_instrument is set, the 'emma_instr' action will either
+call one of the instrument commands, or the copy command.
+
+Possible commands are:
+- instrument_jar: Accepts a jar and instruments it using emma.jar.
+- instrument_classes: Accepts a directory containing java classes and
+ instruments it using emma.jar.
+- copy: Called when EMMA coverage is not enabled. This allows us to make
+ this a required step without necessarily instrumenting on every build.
+ Also removes any stale coverage files.
+"""
+
+import collections
+import json
+import os
+import shutil
+import sys
+import tempfile
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
+from pylib.utils import command_option_parser
+
+from util import build_utils
+
+
+def _AddCommonOptions(option_parser):
+ """Adds common options to |option_parser|."""
+ option_parser.add_option('--input-path',
+ help=('Path to input file(s). Either the classes '
+ 'directory, or the path to a jar.'))
+ option_parser.add_option('--output-path',
+ help=('Path to output final file(s) to. Either the '
+ 'final classes directory, or the directory in '
+ 'which to place the instrumented/copied jar.'))
+ option_parser.add_option('--stamp', help='Path to touch when done.')
+ option_parser.add_option('--coverage-file',
+ help='File to create with coverage metadata.')
+ option_parser.add_option('--sources-file',
+ help='File to create with the list of sources.')
+
+
+def _AddInstrumentOptions(option_parser):
+ """Adds options related to instrumentation to |option_parser|."""
+ _AddCommonOptions(option_parser)
+ option_parser.add_option('--sources',
+ help='Space separated list of sources.')
+ option_parser.add_option('--src-root',
+ help='Root of the src repository.')
+ option_parser.add_option('--emma-jar',
+ help='Path to emma.jar.')
+ option_parser.add_option(
+ '--filter-string', default='',
+ help=('Filter string consisting of a list of inclusion/exclusion '
+ 'patterns separated with whitespace and/or comma.'))
+
+
+def _RunCopyCommand(_command, options, _, option_parser):
+ """Copies the jar from input to output locations.
+
+ Also removes any old coverage/sources file.
+
+ Args:
+ command: String indicating the command that was received to trigger
+ this function.
+ options: optparse options dictionary.
+ args: List of extra args from optparse.
+ option_parser: optparse.OptionParser object.
+
+ Returns:
+ An exit code.
+ """
+ if not (options.input_path and options.output_path and
+ options.coverage_file and options.sources_file):
+ option_parser.error('All arguments are required.')
+
+ coverage_file = os.path.join(os.path.dirname(options.output_path),
+ options.coverage_file)
+ sources_file = os.path.join(os.path.dirname(options.output_path),
+ options.sources_file)
+ if os.path.exists(coverage_file):
+ os.remove(coverage_file)
+ if os.path.exists(sources_file):
+ os.remove(sources_file)
+
+ if os.path.isdir(options.input_path):
+ shutil.rmtree(options.output_path, ignore_errors=True)
+ shutil.copytree(options.input_path, options.output_path)
+ else:
+ shutil.copy(options.input_path, options.output_path)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+def _CreateSourcesFile(sources_string, sources_file, src_root):
+ """Adds all normalized source directories to |sources_file|.
+
+ Args:
+ sources_string: String generated from gyp containing the list of sources.
+ sources_file: File into which to write the JSON list of sources.
+ src_root: Root which sources added to the file should be relative to.
+
+ Returns:
+ An exit code.
+ """
+ src_root = os.path.abspath(src_root)
+ sources = build_utils.ParseGypList(sources_string)
+ relative_sources = []
+ for s in sources:
+ abs_source = os.path.abspath(s)
+ if abs_source[:len(src_root)] != src_root:
+ print ('Error: found source directory not under repository root: %s %s'
+ % (abs_source, src_root))
+ return 1
+ rel_source = os.path.relpath(abs_source, src_root)
+
+ relative_sources.append(rel_source)
+
+ with open(sources_file, 'w') as f:
+ json.dump(relative_sources, f)
+
+
+def _RunInstrumentCommand(command, options, _, option_parser):
+ """Instruments the classes/jar files using EMMA.
+
+ Args:
+ command: 'instrument_jar' or 'instrument_classes'. This distinguishes
+ whether we copy the output from the created lib/ directory, or classes/
+ directory.
+ options: optparse options dictionary.
+ args: List of extra args from optparse.
+ option_parser: optparse.OptionParser object.
+
+ Returns:
+ An exit code.
+ """
+ if not (options.input_path and options.output_path and
+ options.coverage_file and options.sources_file and options.sources and
+ options.src_root and options.emma_jar):
+ option_parser.error('All arguments are required.')
+
+ coverage_file = os.path.join(os.path.dirname(options.output_path),
+ options.coverage_file)
+ sources_file = os.path.join(os.path.dirname(options.output_path),
+ options.sources_file)
+ if os.path.exists(coverage_file):
+ os.remove(coverage_file)
+ temp_dir = tempfile.mkdtemp()
+ try:
+ cmd = ['java', '-cp', options.emma_jar,
+ 'emma', 'instr',
+ '-ip', options.input_path,
+ '-ix', options.filter_string,
+ '-d', temp_dir,
+ '-out', coverage_file,
+ '-m', 'fullcopy']
+ build_utils.CheckOutput(cmd)
+
+ if command == 'instrument_jar':
+ for jar in os.listdir(os.path.join(temp_dir, 'lib')):
+ shutil.copy(os.path.join(temp_dir, 'lib', jar),
+ options.output_path)
+ else: # 'instrument_classes'
+ if os.path.isdir(options.output_path):
+ shutil.rmtree(options.output_path, ignore_errors=True)
+ shutil.copytree(os.path.join(temp_dir, 'classes'),
+ options.output_path)
+ finally:
+ shutil.rmtree(temp_dir)
+
+ _CreateSourcesFile(options.sources, sources_file, options.src_root)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+ return 0
+
+
+CommandFunctionTuple = collections.namedtuple(
+ 'CommandFunctionTuple', ['add_options_func', 'run_command_func'])
+VALID_COMMANDS = {
+ 'copy': CommandFunctionTuple(_AddCommonOptions,
+ _RunCopyCommand),
+ 'instrument_jar': CommandFunctionTuple(_AddInstrumentOptions,
+ _RunInstrumentCommand),
+ 'instrument_classes': CommandFunctionTuple(_AddInstrumentOptions,
+ _RunInstrumentCommand),
+}
+
+
+def main():
+ option_parser = command_option_parser.CommandOptionParser(
+ commands_dict=VALID_COMMANDS)
+ command_option_parser.ParseAndExecute(option_parser)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/finalize_apk.py b/build/android/gyp/finalize_apk.py
new file mode 100755
index 00000000000..0a8003508b5
--- /dev/null
+++ b/build/android/gyp/finalize_apk.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+"""Signs and zipaligns APK.
+
+"""
+
+import optparse
+import shutil
+import sys
+import tempfile
+
+from util import build_utils
+
+def RenameInflateAndAddPageAlignment(
+ rezip_apk_jar_path, in_zip_file, out_zip_file):
+ rezip_apk_cmd = [
+ 'java',
+ '-classpath',
+ rezip_apk_jar_path,
+ 'RezipApk',
+ 'renamealign',
+ in_zip_file,
+ out_zip_file,
+ ]
+ build_utils.CheckOutput(rezip_apk_cmd)
+
+
+def ReorderAndAlignApk(rezip_apk_jar_path, in_zip_file, out_zip_file):
+ rezip_apk_cmd = [
+ 'java',
+ '-classpath',
+ rezip_apk_jar_path,
+ 'RezipApk',
+ 'reorder',
+ in_zip_file,
+ out_zip_file,
+ ]
+ build_utils.CheckOutput(rezip_apk_cmd)
+
+
+def JarSigner(key_path, key_name, key_passwd, unsigned_path, signed_path):
+ shutil.copy(unsigned_path, signed_path)
+ sign_cmd = [
+ 'jarsigner',
+ '-sigalg', 'MD5withRSA',
+ '-digestalg', 'SHA1',
+ '-keystore', key_path,
+ '-storepass', key_passwd,
+ signed_path,
+ key_name,
+ ]
+ build_utils.CheckOutput(sign_cmd)
+
+
+def AlignApk(zipalign_path, unaligned_path, final_path):
+ align_cmd = [
+ zipalign_path,
+ '-f', '4', # 4 bytes
+ unaligned_path,
+ final_path,
+ ]
+ build_utils.CheckOutput(align_cmd)
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--rezip-apk-jar-path',
+ help='Path to the RezipApk jar file.')
+ parser.add_option('--zipalign-path', help='Path to the zipalign tool.')
+ parser.add_option('--unsigned-apk-path', help='Path to input unsigned APK.')
+ parser.add_option('--final-apk-path',
+ help='Path to output signed and aligned APK.')
+ parser.add_option('--key-path', help='Path to keystore for signing.')
+ parser.add_option('--key-passwd', help='Keystore password')
+ parser.add_option('--key-name', help='Keystore name')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--load-library-from-zip', type='int',
+ help='If non-zero, build the APK such that the library can be loaded ' +
+ 'directly from the zip file using the crazy linker. The library ' +
+ 'will be renamed, uncompressed and page aligned.')
+
+ options, _ = parser.parse_args()
+
+ FinalizeApk(options)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile, build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+def FinalizeApk(options):
+ with tempfile.NamedTemporaryFile() as signed_apk_path_tmp, \
+ tempfile.NamedTemporaryFile() as apk_to_sign_tmp:
+
+ if options.load_library_from_zip:
+ # We alter the name of the library so that the Android Package Manager
+ # does not extract it into a separate file. This must be done before
+ # signing, as the filename is part of the signed manifest. At the same
+ # time we uncompress the library, which is necessary so that it can be
+ # loaded directly from the APK.
+ # Move the library to a page boundary by adding a page alignment file.
+ apk_to_sign = apk_to_sign_tmp.name
+ RenameInflateAndAddPageAlignment(
+ options.rezip_apk_jar_path, options.unsigned_apk_path, apk_to_sign)
+ else:
+ apk_to_sign = options.unsigned_apk_path
+
+ signed_apk_path = signed_apk_path_tmp.name
+ JarSigner(options.key_path, options.key_name, options.key_passwd,
+ apk_to_sign, signed_apk_path)
+
+ if options.load_library_from_zip:
+ # Reorder the contents of the APK. This re-establishes the canonical
+ # order which means the library will be back at its page aligned location.
+ # This step also aligns uncompressed items to 4 bytes.
+ ReorderAndAlignApk(
+ options.rezip_apk_jar_path, signed_apk_path, options.final_apk_path)
+ else:
+ # Align uncompressed items to 4 bytes
+ AlignApk(options.zipalign_path, signed_apk_path, options.final_apk_path)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/finalize_splits.py b/build/android/gyp/finalize_splits.py
new file mode 100755
index 00000000000..a6796bbe4f4
--- /dev/null
+++ b/build/android/gyp/finalize_splits.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 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.
+"""Signs and zipaligns split APKs.
+
+This script is require only by GYP (not GN).
+"""
+
+import optparse
+import sys
+
+import finalize_apk
+from util import build_utils
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--zipalign-path', help='Path to the zipalign tool.')
+ parser.add_option('--resource-packaged-apk-path',
+ help='Base path to input .ap_s.')
+ parser.add_option('--base-output-path',
+ help='Path to output .apk, minus extension.')
+ parser.add_option('--key-path', help='Path to keystore for signing.')
+ parser.add_option('--key-passwd', help='Keystore password')
+ parser.add_option('--key-name', help='Keystore name')
+ parser.add_option('--densities',
+ help='Comma separated list of densities finalize.')
+ parser.add_option('--languages',
+ help='GYP list of language splits to finalize.')
+
+ options, _ = parser.parse_args()
+ options.load_library_from_zip = 0
+
+ if options.densities:
+ for density in options.densities.split(','):
+ options.unsigned_apk_path = ("%s_%s" %
+ (options.resource_packaged_apk_path, density))
+ options.final_apk_path = ("%s-density-%s.apk" %
+ (options.base_output_path, density))
+ finalize_apk.FinalizeApk(options)
+
+ if options.languages:
+ for lang in build_utils.ParseGypList(options.languages):
+ options.unsigned_apk_path = ("%s_%s" %
+ (options.resource_packaged_apk_path, lang))
+ options.final_apk_path = ("%s-lang-%s.apk" %
+ (options.base_output_path, lang))
+ finalize_apk.FinalizeApk(options)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/find.py b/build/android/gyp/find.py
new file mode 100755
index 00000000000..a9f1d498556
--- /dev/null
+++ b/build/android/gyp/find.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Finds files in directories.
+"""
+
+import fnmatch
+import optparse
+import os
+import sys
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('--pattern', default='*', help='File pattern to match.')
+ options, directories = parser.parse_args(argv)
+
+ for d in directories:
+ if not os.path.exists(d):
+ print >> sys.stderr, '%s does not exist' % d
+ return 1
+ for root, _, filenames in os.walk(d):
+ for f in fnmatch.filter(filenames, options.pattern):
+ print os.path.join(root, f)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/find_sun_tools_jar.py b/build/android/gyp/find_sun_tools_jar.py
new file mode 100755
index 00000000000..2f15a154abd
--- /dev/null
+++ b/build/android/gyp/find_sun_tools_jar.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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 finds the java distribution's tools.jar and copies it somewhere.
+"""
+
+import argparse
+import os
+import re
+import shutil
+import sys
+
+from util import build_utils
+
+RT_JAR_FINDER = re.compile(r'\[Opened (.*)/jre/lib/rt.jar\]')
+
+def main():
+ parser = argparse.ArgumentParser(description='Find Sun Tools Jar')
+ parser.add_argument('--depfile',
+ help='Path to depfile. This must be specified as the '
+ 'action\'s first output.')
+ parser.add_argument('--output', required=True)
+ args = parser.parse_args()
+
+ sun_tools_jar_path = FindSunToolsJarPath()
+
+ if sun_tools_jar_path is None:
+ raise Exception("Couldn\'t find tools.jar")
+
+ # Using copyfile instead of copy() because copy() calls copymode()
+ # We don't want the locked mode because we may copy over this file again
+ shutil.copyfile(sun_tools_jar_path, args.output)
+
+ if args.depfile:
+ build_utils.WriteDepfile(
+ args.depfile,
+ [sun_tools_jar_path] + build_utils.GetPythonDependencies())
+
+
+def FindSunToolsJarPath():
+ # This works with at least openjdk 1.6, 1.7 and sun java 1.6, 1.7
+ stdout = build_utils.CheckOutput(
+ ["java", "-verbose", "-version"], print_stderr=False)
+ for ln in stdout.splitlines():
+ match = RT_JAR_FINDER.match(ln)
+ if match:
+ return os.path.join(match.group(1), 'lib', 'tools.jar')
+
+ return None
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/gcc_preprocess.py b/build/android/gyp/gcc_preprocess.py
new file mode 100755
index 00000000000..03becf918fe
--- /dev/null
+++ b/build/android/gyp/gcc_preprocess.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+def DoGcc(options):
+ build_utils.MakeDirectory(os.path.dirname(options.output))
+
+ gcc_cmd = [ 'gcc' ] # invoke host gcc.
+ if options.defines:
+ gcc_cmd.extend(sum(map(lambda w: ['-D', w], options.defines), []))
+ gcc_cmd.extend([
+ '-E', # stop after preprocessing.
+ '-D', 'ANDROID', # Specify ANDROID define for pre-processor.
+ '-x', 'c-header', # treat sources as C header files
+ '-P', # disable line markers, i.e. '#line 309'
+ '-I', options.include_path,
+ '-o', options.output,
+ options.template
+ ])
+
+ build_utils.CheckOutput(gcc_cmd)
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--include-path', help='Include path for gcc.')
+ parser.add_option('--template', help='Path to template.')
+ parser.add_option('--output', help='Path for generated file.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--defines', help='Pre-defines macros', action='append')
+
+ options, _ = parser.parse_args(args)
+
+ DoGcc(options)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/generate_split_manifest.py b/build/android/gyp/generate_split_manifest.py
new file mode 100755
index 00000000000..9cb3bca4b55
--- /dev/null
+++ b/build/android/gyp/generate_split_manifest.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 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.
+"""Creates an AndroidManifest.xml for an APK split.
+
+Given the manifest file for the main APK, generates an AndroidManifest.xml with
+the value required for a Split APK (package, versionCode, etc).
+"""
+
+import optparse
+import xml.etree.ElementTree
+
+from util import build_utils
+
+MANIFEST_TEMPLATE = """
+
+
+
+
+
+"""
+
+def ParseArgs():
+ """Parses command line options.
+
+ Returns:
+ An options object as from optparse.OptionsParser.parse_args()
+ """
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--main-manifest', help='The main manifest of the app')
+ parser.add_option('--out-manifest', help='The output manifest')
+ parser.add_option('--split', help='The name of the split')
+ parser.add_option(
+ '--has-code',
+ action='store_true',
+ default=False,
+ help='Whether the split will contain a .dex file')
+
+ (options, args) = parser.parse_args()
+
+ if args:
+ parser.error('No positional arguments should be given.')
+
+ # Check that required options have been provided.
+ required_options = ('main_manifest', 'out_manifest', 'split')
+ build_utils.CheckOptions(options, parser, required=required_options)
+
+ return options
+
+
+def Build(main_manifest, split, has_code):
+ """Builds a split manifest based on the manifest of the main APK.
+
+ Args:
+ main_manifest: the XML manifest of the main APK as a string
+ split: the name of the split as a string
+ has_code: whether this split APK will contain .dex files
+
+ Returns:
+ The XML split manifest as a string
+ """
+
+ doc = xml.etree.ElementTree.fromstring(main_manifest)
+ package = doc.get('package')
+
+ return MANIFEST_TEMPLATE % {
+ 'package': package,
+ 'split': split.replace('-', '_'),
+ 'has_code': str(has_code).lower()
+ }
+
+
+def main():
+ options = ParseArgs()
+ main_manifest = file(options.main_manifest).read()
+ split_manifest = Build(
+ main_manifest,
+ options.split,
+ options.has_code)
+
+ with file(options.out_manifest, 'w') as f:
+ f.write(split_manifest)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ [options.main_manifest] + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/gyp/generate_v14_compatible_resources.py b/build/android/gyp/generate_v14_compatible_resources.py
new file mode 100755
index 00000000000..9c8ff3b4e9d
--- /dev/null
+++ b/build/android/gyp/generate_v14_compatible_resources.py
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Convert Android xml resources to API 14 compatible.
+
+There are two reasons that we cannot just use API 17 attributes,
+so we are generating another set of resources by this script.
+
+1. paddingStart attribute can cause a crash on Galaxy Tab 2.
+2. There is a bug that paddingStart does not override paddingLeft on
+ JB-MR1. This is fixed on JB-MR2. b/8654490
+
+Therefore, this resource generation script can be removed when
+we drop the support for JB-MR1.
+
+Please refer to http://crbug.com/235118 for the details.
+"""
+
+import optparse
+import os
+import re
+import shutil
+import sys
+import xml.dom.minidom as minidom
+
+from util import build_utils
+
+# Note that we are assuming 'android:' is an alias of
+# the namespace 'http://schemas.android.com/apk/res/android'.
+
+GRAVITY_ATTRIBUTES = ('android:gravity', 'android:layout_gravity')
+
+# Almost all the attributes that has "Start" or "End" in
+# its name should be mapped.
+ATTRIBUTES_TO_MAP = {'paddingStart' : 'paddingLeft',
+ 'drawableStart' : 'drawableLeft',
+ 'layout_alignStart' : 'layout_alignLeft',
+ 'layout_marginStart' : 'layout_marginLeft',
+ 'layout_alignParentStart' : 'layout_alignParentLeft',
+ 'layout_toStartOf' : 'layout_toLeftOf',
+ 'paddingEnd' : 'paddingRight',
+ 'drawableEnd' : 'drawableRight',
+ 'layout_alignEnd' : 'layout_alignRight',
+ 'layout_marginEnd' : 'layout_marginRight',
+ 'layout_alignParentEnd' : 'layout_alignParentRight',
+ 'layout_toEndOf' : 'layout_toRightOf'}
+
+ATTRIBUTES_TO_MAP = dict(['android:' + k, 'android:' + v] for k, v
+ in ATTRIBUTES_TO_MAP.iteritems())
+
+ATTRIBUTES_TO_MAP_REVERSED = dict([v, k] for k, v
+ in ATTRIBUTES_TO_MAP.iteritems())
+
+
+def IterateXmlElements(node):
+ """minidom helper function that iterates all the element nodes.
+ Iteration order is pre-order depth-first."""
+ if node.nodeType == node.ELEMENT_NODE:
+ yield node
+ for child_node in node.childNodes:
+ for child_node_element in IterateXmlElements(child_node):
+ yield child_node_element
+
+
+def ParseAndReportErrors(filename):
+ try:
+ return minidom.parse(filename)
+ except Exception:
+ import traceback
+ traceback.print_exc()
+ sys.stderr.write('Failed to parse XML file: %s\n' % filename)
+ sys.exit(1)
+
+
+def AssertNotDeprecatedAttribute(name, value, filename):
+ """Raises an exception if the given attribute is deprecated."""
+ msg = None
+ if name in ATTRIBUTES_TO_MAP_REVERSED:
+ msg = '{0} should use {1} instead of {2}'.format(filename,
+ ATTRIBUTES_TO_MAP_REVERSED[name], name)
+ elif name in GRAVITY_ATTRIBUTES and ('left' in value or 'right' in value):
+ msg = '{0} should use start/end instead of left/right for {1}'.format(
+ filename, name)
+
+ if msg:
+ msg += ('\nFor background, see: http://android-developers.blogspot.com/'
+ '2013/03/native-rtl-support-in-android-42.html\n'
+ 'If you have a legitimate need for this attribute, discuss with '
+ 'kkimlabs@chromium.org or newt@chromium.org')
+ raise Exception(msg)
+
+
+def WriteDomToFile(dom, filename):
+ """Write the given dom to filename."""
+ build_utils.MakeDirectory(os.path.dirname(filename))
+ with open(filename, 'w') as f:
+ dom.writexml(f, '', ' ', '\n', encoding='utf-8')
+
+
+def HasStyleResource(dom):
+ """Return True if the dom is a style resource, False otherwise."""
+ root_node = IterateXmlElements(dom).next()
+ return bool(root_node.nodeName == 'resources' and
+ list(root_node.getElementsByTagName('style')))
+
+
+def ErrorIfStyleResourceExistsInDir(input_dir):
+ """If a style resource is in input_dir, raises an exception."""
+ for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
+ dom = ParseAndReportErrors(input_filename)
+ if HasStyleResource(dom):
+ raise Exception('error: style file ' + input_filename +
+ ' should be under ' + input_dir +
+ '-v17 directory. Please refer to '
+ 'http://crbug.com/243952 for the details.')
+
+
+def GenerateV14LayoutResourceDom(dom, filename, assert_not_deprecated=True):
+ """Convert layout resource to API 14 compatible layout resource.
+
+ Args:
+ dom: Parsed minidom object to be modified.
+ filename: Filename that the DOM was parsed from.
+ assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
+ cause an exception to be thrown.
+
+ Returns:
+ True if dom is modified, False otherwise.
+ """
+ is_modified = False
+
+ # Iterate all the elements' attributes to find attributes to convert.
+ for element in IterateXmlElements(dom):
+ for name, value in list(element.attributes.items()):
+ # Convert any API 17 Start/End attributes to Left/Right attributes.
+ # For example, from paddingStart="10dp" to paddingLeft="10dp"
+ # Note: gravity attributes are not necessary to convert because
+ # start/end values are backward-compatible. Explained at
+ # https://plus.sandbox.google.com/+RomanNurik/posts/huuJd8iVVXY?e=Showroom
+ if name in ATTRIBUTES_TO_MAP:
+ element.setAttribute(ATTRIBUTES_TO_MAP[name], value)
+ del element.attributes[name]
+ is_modified = True
+ elif assert_not_deprecated:
+ AssertNotDeprecatedAttribute(name, value, filename)
+
+ return is_modified
+
+
+def GenerateV14StyleResourceDom(dom, filename, assert_not_deprecated=True):
+ """Convert style resource to API 14 compatible style resource.
+
+ Args:
+ dom: Parsed minidom object to be modified.
+ filename: Filename that the DOM was parsed from.
+ assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
+ cause an exception to be thrown.
+
+ Returns:
+ True if dom is modified, False otherwise.
+ """
+ is_modified = False
+
+ for style_element in dom.getElementsByTagName('style'):
+ for item_element in style_element.getElementsByTagName('item'):
+ name = item_element.attributes['name'].value
+ value = item_element.childNodes[0].nodeValue
+ if name in ATTRIBUTES_TO_MAP:
+ item_element.attributes['name'].value = ATTRIBUTES_TO_MAP[name]
+ is_modified = True
+ elif assert_not_deprecated:
+ AssertNotDeprecatedAttribute(name, value, filename)
+
+ return is_modified
+
+
+def GenerateV14LayoutResource(input_filename, output_v14_filename,
+ output_v17_filename):
+ """Convert API 17 layout resource to API 14 compatible layout resource.
+
+ It's mostly a simple replacement, s/Start/Left s/End/Right,
+ on the attribute names.
+ If the generated resource is identical to the original resource,
+ don't do anything. If not, write the generated resource to
+ output_v14_filename, and copy the original resource to output_v17_filename.
+ """
+ dom = ParseAndReportErrors(input_filename)
+ is_modified = GenerateV14LayoutResourceDom(dom, input_filename)
+
+ if is_modified:
+ # Write the generated resource.
+ WriteDomToFile(dom, output_v14_filename)
+
+ # Copy the original resource.
+ build_utils.MakeDirectory(os.path.dirname(output_v17_filename))
+ shutil.copy2(input_filename, output_v17_filename)
+
+
+def GenerateV14StyleResource(input_filename, output_v14_filename):
+ """Convert API 17 style resources to API 14 compatible style resource.
+
+ Write the generated style resource to output_v14_filename.
+ It's mostly a simple replacement, s/Start/Left s/End/Right,
+ on the attribute names.
+ """
+ dom = ParseAndReportErrors(input_filename)
+ GenerateV14StyleResourceDom(dom, input_filename)
+
+ # Write the generated resource.
+ WriteDomToFile(dom, output_v14_filename)
+
+
+def GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, output_v17_dir):
+ """Convert layout resources to API 14 compatible resources in input_dir."""
+ for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
+ rel_filename = os.path.relpath(input_filename, input_dir)
+ output_v14_filename = os.path.join(output_v14_dir, rel_filename)
+ output_v17_filename = os.path.join(output_v17_dir, rel_filename)
+ GenerateV14LayoutResource(input_filename, output_v14_filename,
+ output_v17_filename)
+
+
+def GenerateV14StyleResourcesInDir(input_dir, output_v14_dir):
+ """Convert style resources to API 14 compatible resources in input_dir."""
+ for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
+ rel_filename = os.path.relpath(input_filename, input_dir)
+ output_v14_filename = os.path.join(output_v14_dir, rel_filename)
+ GenerateV14StyleResource(input_filename, output_v14_filename)
+
+
+def ParseArgs():
+ """Parses command line options.
+
+ Returns:
+ An options object as from optparse.OptionsParser.parse_args()
+ """
+ parser = optparse.OptionParser()
+ parser.add_option('--res-dir',
+ help='directory containing resources '
+ 'used to generate v14 compatible resources')
+ parser.add_option('--res-v14-compatibility-dir',
+ help='output directory into which '
+ 'v14 compatible resources will be generated')
+ parser.add_option('--stamp', help='File to touch on success')
+
+ options, args = parser.parse_args()
+
+ if args:
+ parser.error('No positional arguments should be given.')
+
+ # Check that required options have been provided.
+ required_options = ('res_dir', 'res_v14_compatibility_dir')
+ build_utils.CheckOptions(options, parser, required=required_options)
+ return options
+
+def GenerateV14Resources(res_dir, res_v14_dir):
+ for name in os.listdir(res_dir):
+ if not os.path.isdir(os.path.join(res_dir, name)):
+ continue
+
+ dir_pieces = name.split('-')
+ resource_type = dir_pieces[0]
+ qualifiers = dir_pieces[1:]
+
+ api_level_qualifier_index = -1
+ api_level_qualifier = ''
+ for index, qualifier in enumerate(qualifiers):
+ if re.match('v[0-9]+$', qualifier):
+ api_level_qualifier_index = index
+ api_level_qualifier = qualifier
+ break
+
+ # Android pre-v17 API doesn't support RTL. Skip.
+ if 'ldrtl' in qualifiers:
+ continue
+
+ input_dir = os.path.abspath(os.path.join(res_dir, name))
+
+ # We also need to copy the original v17 resource to *-v17 directory
+ # because the generated v14 resource will hide the original resource.
+ output_v14_dir = os.path.join(res_v14_dir, name)
+ output_v17_dir = os.path.join(res_v14_dir, name + '-v17')
+
+ # We only convert layout resources under layout*/, xml*/,
+ # and style resources under values*/.
+ if resource_type in ('layout', 'xml'):
+ if not api_level_qualifier:
+ GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir,
+ output_v17_dir)
+ elif resource_type == 'values':
+ if api_level_qualifier == 'v17':
+ output_qualifiers = qualifiers[:]
+ del output_qualifiers[api_level_qualifier_index]
+ output_v14_dir = os.path.join(res_v14_dir,
+ '-'.join([resource_type] +
+ output_qualifiers))
+ GenerateV14StyleResourcesInDir(input_dir, output_v14_dir)
+ elif not api_level_qualifier:
+ ErrorIfStyleResourceExistsInDir(input_dir)
+
+def main():
+ options = ParseArgs()
+
+ res_v14_dir = options.res_v14_compatibility_dir
+
+ build_utils.DeleteDirectory(res_v14_dir)
+ build_utils.MakeDirectory(res_v14_dir)
+
+ GenerateV14Resources(options.res_dir, res_v14_dir)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/gyp/get_device_configuration.py b/build/android/gyp/get_device_configuration.py
new file mode 100755
index 00000000000..390eb2ffcb8
--- /dev/null
+++ b/build/android/gyp/get_device_configuration.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Gets and writes the configurations of the attached devices.
+
+This configuration is used by later build steps to determine which devices to
+install to and what needs to be installed to those devices.
+"""
+
+import optparse
+import sys
+
+from util import build_utils
+from util import build_device
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('--stamp', action='store')
+ parser.add_option('--output', action='store')
+ options, _ = parser.parse_args(argv)
+
+ devices = build_device.GetAttachedDevices()
+
+ device_configurations = []
+ for d in devices:
+ configuration, is_online, has_root = (
+ build_device.GetConfigurationForDevice(d))
+
+ if not is_online:
+ build_utils.PrintBigWarning(
+ '%s is not online. Skipping managed install for this device. '
+ 'Try rebooting the device to fix this warning.' % d)
+ continue
+
+ if not has_root:
+ build_utils.PrintBigWarning(
+ '"adb root" failed on device: %s\n'
+ 'Skipping managed install for this device.'
+ % configuration['description'])
+ continue
+
+ device_configurations.append(configuration)
+
+ if len(device_configurations) == 0:
+ build_utils.PrintBigWarning(
+ 'No valid devices attached. Skipping managed install steps.')
+ elif len(devices) > 1:
+ # Note that this checks len(devices) and not len(device_configurations).
+ # This way, any time there are multiple devices attached it is
+ # explicitly stated which device we will install things to even if all but
+ # one device were rejected for other reasons (e.g. two devices attached,
+ # one w/o root).
+ build_utils.PrintBigWarning(
+ 'Multiple devices attached. '
+ 'Installing to the preferred device: '
+ '%(id)s (%(description)s)' % (device_configurations[0]))
+
+
+ build_device.WriteConfigurations(device_configurations, options.output)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/gyp/insert_chromium_version.py b/build/android/gyp/insert_chromium_version.py
new file mode 100755
index 00000000000..171f9d41b8d
--- /dev/null
+++ b/build/android/gyp/insert_chromium_version.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Insert a version string into a library as a section '.chromium.version'.
+"""
+
+import optparse
+import os
+import sys
+import tempfile
+
+from util import build_utils
+
+def InsertChromiumVersion(android_objcopy,
+ library_path,
+ version_string):
+ # Remove existing .chromium.version section from .so
+ objcopy_command = [android_objcopy,
+ '--remove-section=.chromium.version',
+ library_path]
+ build_utils.CheckOutput(objcopy_command)
+
+ # Add a .chromium.version section.
+ with tempfile.NamedTemporaryFile() as stream:
+ stream.write(version_string)
+ stream.flush()
+ objcopy_command = [android_objcopy,
+ '--add-section', '.chromium.version=%s' % stream.name,
+ library_path]
+ build_utils.CheckOutput(objcopy_command)
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+
+ parser.add_option('--android-objcopy',
+ help='Path to the toolchain\'s objcopy binary')
+ parser.add_option('--stripped-libraries-dir',
+ help='Directory of native libraries')
+ parser.add_option('--libraries',
+ help='List of libraries')
+ parser.add_option('--version-string',
+ help='Version string to be inserted')
+ parser.add_option('--stamp', help='Path to touch on success')
+
+ options, _ = parser.parse_args(args)
+ libraries = build_utils.ParseGypList(options.libraries)
+
+ for library in libraries:
+ library_path = os.path.join(options.stripped_libraries_dir, library)
+
+ InsertChromiumVersion(options.android_objcopy,
+ library_path,
+ options.version_string)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/jar.py b/build/android/gyp/jar.py
new file mode 100755
index 00000000000..48abf5edb7d
--- /dev/null
+++ b/build/android/gyp/jar.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import fnmatch
+import optparse
+import os
+import sys
+
+from util import build_utils
+from util import md5_check
+
+
+def Jar(class_files, classes_dir, jar_path, manifest_file=None):
+ jar_path = os.path.abspath(jar_path)
+
+ # The paths of the files in the jar will be the same as they are passed in to
+ # the command. Because of this, the command should be run in
+ # options.classes_dir so the .class file paths in the jar are correct.
+ jar_cwd = classes_dir
+ class_files_rel = [os.path.relpath(f, jar_cwd) for f in class_files]
+ jar_cmd = ['jar', 'cf0', jar_path]
+ if manifest_file:
+ jar_cmd[1] += 'm'
+ jar_cmd.append(os.path.abspath(manifest_file))
+ jar_cmd.extend(class_files_rel)
+
+ with build_utils.TempDir() as temp_dir:
+ empty_file = os.path.join(temp_dir, '.empty')
+ build_utils.Touch(empty_file)
+ jar_cmd.append(os.path.relpath(empty_file, jar_cwd))
+ record_path = '%s.md5.stamp' % jar_path
+ md5_check.CallAndRecordIfStale(
+ lambda: build_utils.CheckOutput(jar_cmd, cwd=jar_cwd),
+ record_path=record_path,
+ input_paths=class_files,
+ input_strings=jar_cmd,
+ force=not os.path.exists(jar_path),
+ )
+
+ build_utils.Touch(jar_path, fail_if_missing=True)
+
+
+def JarDirectory(classes_dir, excluded_classes, jar_path, manifest_file=None):
+ class_files = build_utils.FindInDirectory(classes_dir, '*.class')
+ for exclude in excluded_classes:
+ class_files = filter(
+ lambda f: not fnmatch.fnmatch(f, exclude), class_files)
+
+ Jar(class_files, classes_dir, jar_path, manifest_file=manifest_file)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--classes-dir', help='Directory containing .class files.')
+ parser.add_option('--jar-path', help='Jar output path.')
+ parser.add_option('--excluded-classes',
+ help='List of .class file patterns to exclude from the jar.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, _ = parser.parse_args()
+
+ if options.excluded_classes:
+ excluded_classes = build_utils.ParseGypList(options.excluded_classes)
+ else:
+ excluded_classes = []
+ JarDirectory(options.classes_dir,
+ excluded_classes,
+ options.jar_path)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/gyp/jar_toc.py b/build/android/gyp/jar_toc.py
new file mode 100755
index 00000000000..00d97d211f3
--- /dev/null
+++ b/build/android/gyp/jar_toc.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Creates a TOC file from a Java jar.
+
+The TOC file contains the non-package API of the jar. This includes all
+public/protected/package classes/functions/members and the values of static
+final variables (members with package access are kept because in some cases we
+have multiple libraries with the same package, particularly test+non-test). Some
+other information (major/minor javac version) is also included.
+
+This TOC file then can be used to determine if a dependent library should be
+rebuilt when this jar changes. I.e. any change to the jar that would require a
+rebuild, will have a corresponding change in the TOC file.
+"""
+
+import optparse
+import os
+import re
+import sys
+import zipfile
+
+from util import build_utils
+from util import md5_check
+
+
+def GetClassesInZipFile(zip_file):
+ classes = []
+ files = zip_file.namelist()
+ for f in files:
+ if f.endswith('.class'):
+ # f is of the form org/chromium/base/Class$Inner.class
+ classes.append(f.replace('/', '.')[:-6])
+ return classes
+
+
+def CallJavap(classpath, classes):
+ javap_cmd = [
+ 'javap',
+ '-package', # Show public/protected/package.
+ # -verbose is required to get constant values (which can be inlined in
+ # dependents).
+ '-verbose',
+ '-J-XX:NewSize=4m',
+ '-classpath', classpath
+ ] + classes
+ return build_utils.CheckOutput(javap_cmd)
+
+
+def ExtractToc(disassembled_classes):
+ # javap output is structured by indent (2-space) levels.
+ good_patterns = [
+ '^[^ ]', # This includes all class/function/member signatures.
+ '^ SourceFile:',
+ '^ minor version:',
+ '^ major version:',
+ '^ Constant value:',
+ ]
+ bad_patterns = [
+ '^const #', # Matches the constant pool (i.e. literals used in the class).
+ ]
+
+ def JavapFilter(line):
+ return (re.match('|'.join(good_patterns), line) and
+ not re.match('|'.join(bad_patterns), line))
+ toc = filter(JavapFilter, disassembled_classes.split('\n'))
+
+ return '\n'.join(toc)
+
+
+def UpdateToc(jar_path, toc_path):
+ classes = GetClassesInZipFile(zipfile.ZipFile(jar_path))
+ toc = []
+
+ limit = 1000 # Split into multiple calls to stay under command size limit
+ for i in xrange(0, len(classes), limit):
+ javap_output = CallJavap(classpath=jar_path, classes=classes[i:i+limit])
+ toc.append(ExtractToc(javap_output))
+
+ with open(toc_path, 'w') as tocfile:
+ tocfile.write(''.join(toc))
+
+
+def DoJarToc(options):
+ jar_path = options.jar_path
+ toc_path = options.toc_path
+ record_path = '%s.md5.stamp' % toc_path
+ md5_check.CallAndRecordIfStale(
+ lambda: UpdateToc(jar_path, toc_path),
+ record_path=record_path,
+ input_paths=[jar_path],
+ force=not os.path.exists(toc_path),
+ )
+ build_utils.Touch(toc_path, fail_if_missing=True)
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--jar-path', help='Input .jar path.')
+ parser.add_option('--toc-path', help='Output .jar.TOC path.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, _ = parser.parse_args()
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+ DoJarToc(options)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enum.py
new file mode 100755
index 00000000000..c2f1764b1be
--- /dev/null
+++ b/build/android/gyp/java_cpp_enum.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+import collections
+import re
+import optparse
+import os
+from string import Template
+import sys
+
+from util import build_utils
+
+# List of C++ types that are compatible with the Java code generated by this
+# script.
+#
+# This script can parse .idl files however, at present it ignores special
+# rules such as [cpp_enum_prefix_override="ax_attr"].
+ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
+ 'short', 'unsigned short',
+ 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
+
+class EnumDefinition(object):
+ def __init__(self, original_enum_name=None, class_name_override=None,
+ enum_package=None, entries=None, fixed_type=None):
+ self.original_enum_name = original_enum_name
+ self.class_name_override = class_name_override
+ self.enum_package = enum_package
+ self.entries = collections.OrderedDict(entries or [])
+ self.prefix_to_strip = None
+ self.fixed_type = fixed_type
+
+ def AppendEntry(self, key, value):
+ if key in self.entries:
+ raise Exception('Multiple definitions of key %s found.' % key)
+ self.entries[key] = value
+
+ @property
+ def class_name(self):
+ return self.class_name_override or self.original_enum_name
+
+ def Finalize(self):
+ self._Validate()
+ self._AssignEntryIndices()
+ self._StripPrefix()
+
+ def _Validate(self):
+ assert self.class_name
+ assert self.enum_package
+ assert self.entries
+ if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
+ raise Exception('Fixed type %s for enum %s not whitelisted.' %
+ (self.fixed_type, self.class_name))
+
+ def _AssignEntryIndices(self):
+ # Enums, if given no value, are given the value of the previous enum + 1.
+ if not all(self.entries.values()):
+ prev_enum_value = -1
+ for key, value in self.entries.iteritems():
+ if not value:
+ self.entries[key] = prev_enum_value + 1
+ elif value in self.entries:
+ self.entries[key] = self.entries[value]
+ else:
+ try:
+ self.entries[key] = int(value)
+ except ValueError:
+ raise Exception('Could not interpret integer from enum value "%s" '
+ 'for key %s.' % (value, key))
+ prev_enum_value = self.entries[key]
+
+
+ def _StripPrefix(self):
+ prefix_to_strip = self.prefix_to_strip
+ if not prefix_to_strip:
+ prefix_to_strip = self.original_enum_name
+ prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper()
+ prefix_to_strip += '_'
+ if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
+ prefix_to_strip = ''
+
+ entries = collections.OrderedDict()
+ for (k, v) in self.entries.iteritems():
+ stripped_key = k.replace(prefix_to_strip, '', 1)
+ if isinstance(v, basestring):
+ stripped_value = v.replace(prefix_to_strip, '', 1)
+ else:
+ stripped_value = v
+ entries[stripped_key] = stripped_value
+
+ self.entries = entries
+
+class DirectiveSet(object):
+ class_name_override_key = 'CLASS_NAME_OVERRIDE'
+ enum_package_key = 'ENUM_PACKAGE'
+ prefix_to_strip_key = 'PREFIX_TO_STRIP'
+
+ known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
+
+ def __init__(self):
+ self._directives = {}
+
+ def Update(self, key, value):
+ if key not in DirectiveSet.known_keys:
+ raise Exception("Unknown directive: " + key)
+ self._directives[key] = value
+
+ @property
+ def empty(self):
+ return len(self._directives) == 0
+
+ def UpdateDefinition(self, definition):
+ definition.class_name_override = self._directives.get(
+ DirectiveSet.class_name_override_key, '')
+ definition.enum_package = self._directives.get(
+ DirectiveSet.enum_package_key)
+ definition.prefix_to_strip = self._directives.get(
+ DirectiveSet.prefix_to_strip_key)
+
+
+class HeaderParser(object):
+ single_line_comment_re = re.compile(r'\s*//')
+ multi_line_comment_start_re = re.compile(r'\s*/\*')
+ enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
+ enum_end_re = re.compile(r'^\s*}\s*;\.*$')
+ generator_directive_re = re.compile(
+ r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
+ multi_line_generator_directive_start_re = re.compile(
+ r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
+ multi_line_directive_continuation_re = re.compile(
+ r'^\s*//\s+([\.\w]+)$')
+ multi_line_directive_end_re = re.compile(
+ r'^\s*//\s+([\.\w]*)\)$')
+
+ optional_class_or_struct_re = r'(class|struct)?'
+ enum_name_re = r'(\w+)'
+ optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
+ enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' +
+ optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' +
+ optional_fixed_type_re + '\s*{\s*$')
+
+ def __init__(self, lines, path=None):
+ self._lines = lines
+ self._path = path
+ self._enum_definitions = []
+ self._in_enum = False
+ self._current_definition = None
+ self._generator_directives = DirectiveSet()
+ self._multi_line_generator_directive = None
+
+ def _ApplyGeneratorDirectives(self):
+ self._generator_directives.UpdateDefinition(self._current_definition)
+ self._generator_directives = DirectiveSet()
+
+ def ParseDefinitions(self):
+ for line in self._lines:
+ self._ParseLine(line)
+ return self._enum_definitions
+
+ def _ParseLine(self, line):
+ if self._multi_line_generator_directive:
+ self._ParseMultiLineDirectiveLine(line)
+ elif not self._in_enum:
+ self._ParseRegularLine(line)
+ else:
+ self._ParseEnumLine(line)
+
+ def _ParseEnumLine(self, line):
+ if HeaderParser.single_line_comment_re.match(line):
+ return
+ if HeaderParser.multi_line_comment_start_re.match(line):
+ raise Exception('Multi-line comments in enums are not supported in ' +
+ self._path)
+ enum_end = HeaderParser.enum_end_re.match(line)
+ enum_entry = HeaderParser.enum_line_re.match(line)
+ if enum_end:
+ self._ApplyGeneratorDirectives()
+ self._current_definition.Finalize()
+ self._enum_definitions.append(self._current_definition)
+ self._in_enum = False
+ elif enum_entry:
+ enum_key = enum_entry.groups()[0]
+ enum_value = enum_entry.groups()[2]
+ self._current_definition.AppendEntry(enum_key, enum_value)
+
+ def _ParseMultiLineDirectiveLine(self, line):
+ multi_line_directive_continuation = (
+ HeaderParser.multi_line_directive_continuation_re.match(line))
+ multi_line_directive_end = (
+ HeaderParser.multi_line_directive_end_re.match(line))
+
+ if multi_line_directive_continuation:
+ value_cont = multi_line_directive_continuation.groups()[0]
+ self._multi_line_generator_directive[1].append(value_cont)
+ elif multi_line_directive_end:
+ directive_name = self._multi_line_generator_directive[0]
+ directive_value = "".join(self._multi_line_generator_directive[1])
+ directive_value += multi_line_directive_end.groups()[0]
+ self._multi_line_generator_directive = None
+ self._generator_directives.Update(directive_name, directive_value)
+ else:
+ raise Exception('Malformed multi-line directive declaration in ' +
+ self._path)
+
+ def _ParseRegularLine(self, line):
+ enum_start = HeaderParser.enum_start_re.match(line)
+ generator_directive = HeaderParser.generator_directive_re.match(line)
+ multi_line_generator_directive_start = (
+ HeaderParser.multi_line_generator_directive_start_re.match(line))
+
+ if generator_directive:
+ directive_name = generator_directive.groups()[0]
+ directive_value = generator_directive.groups()[1]
+ self._generator_directives.Update(directive_name, directive_value)
+ elif multi_line_generator_directive_start:
+ directive_name = multi_line_generator_directive_start.groups()[0]
+ directive_value = multi_line_generator_directive_start.groups()[1]
+ self._multi_line_generator_directive = (directive_name, [directive_value])
+ elif enum_start:
+ if self._generator_directives.empty:
+ return
+ self._current_definition = EnumDefinition(
+ original_enum_name=enum_start.groups()[1],
+ fixed_type=enum_start.groups()[3])
+ self._in_enum = True
+
+def GetScriptName():
+ script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
+ build_index = script_components.index('build')
+ return os.sep.join(script_components[build_index:])
+
+
+def DoGenerate(output_dir, source_paths, print_output_only=False):
+ output_paths = []
+ for source_path in source_paths:
+ enum_definitions = DoParseHeaderFile(source_path)
+ if not enum_definitions:
+ raise Exception('No enums found in %s\n'
+ 'Did you forget prefixing enums with '
+ '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' %
+ source_path)
+ for enum_definition in enum_definitions:
+ package_path = enum_definition.enum_package.replace('.', os.path.sep)
+ file_name = enum_definition.class_name + '.java'
+ output_path = os.path.join(output_dir, package_path, file_name)
+ output_paths.append(output_path)
+ if not print_output_only:
+ build_utils.MakeDirectory(os.path.dirname(output_path))
+ DoWriteOutput(source_path, output_path, enum_definition)
+ return output_paths
+
+
+def DoParseHeaderFile(path):
+ with open(path) as f:
+ return HeaderParser(f.readlines(), path).ParseDefinitions()
+
+
+def GenerateOutput(source_path, enum_definition):
+ template = Template("""
+// Copyright 2014 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 file is autogenerated by
+// ${SCRIPT_NAME}
+// From
+// ${SOURCE_PATH}
+
+package ${PACKAGE};
+
+public class ${CLASS_NAME} {
+${ENUM_ENTRIES}
+}
+""")
+
+ enum_template = Template(' public static final int ${NAME} = ${VALUE};')
+ enum_entries_string = []
+ for enum_name, enum_value in enum_definition.entries.iteritems():
+ values = {
+ 'NAME': enum_name,
+ 'VALUE': enum_value,
+ }
+ enum_entries_string.append(enum_template.substitute(values))
+ enum_entries_string = '\n'.join(enum_entries_string)
+
+ values = {
+ 'CLASS_NAME': enum_definition.class_name,
+ 'ENUM_ENTRIES': enum_entries_string,
+ 'PACKAGE': enum_definition.enum_package,
+ 'SCRIPT_NAME': GetScriptName(),
+ 'SOURCE_PATH': source_path,
+ }
+ return template.substitute(values)
+
+
+def DoWriteOutput(source_path, output_path, enum_definition):
+ with open(output_path, 'w') as out_file:
+ out_file.write(GenerateOutput(source_path, enum_definition))
+
+def AssertFilesList(output_paths, assert_files_list):
+ actual = set(output_paths)
+ expected = set(assert_files_list)
+ if not actual == expected:
+ need_to_add = list(actual - expected)
+ need_to_remove = list(expected - actual)
+ raise Exception('Output files list does not match expectations. Please '
+ 'add %s and remove %s.' % (need_to_add, need_to_remove))
+
+def DoMain(argv):
+ usage = 'usage: %prog [options] output_dir input_file(s)...'
+ parser = optparse.OptionParser(usage=usage)
+
+ parser.add_option('--assert_file', action="append", default=[],
+ dest="assert_files_list", help='Assert that the given '
+ 'file is an output. There can be multiple occurrences of '
+ 'this flag.')
+ parser.add_option('--print_output_only', help='Only print output paths.',
+ action='store_true')
+ parser.add_option('--verbose', help='Print more information.',
+ action='store_true')
+
+ options, args = parser.parse_args(argv)
+ if len(args) < 2:
+ parser.error('Need to specify output directory and at least one input file')
+ output_paths = DoGenerate(args[0], args[1:],
+ print_output_only=options.print_output_only)
+
+ if options.assert_files_list:
+ AssertFilesList(output_paths, options.assert_files_list)
+
+ if options.verbose:
+ print 'Output paths:'
+ print '\n'.join(output_paths)
+
+ return ' '.join(output_paths)
+
+if __name__ == '__main__':
+ DoMain(sys.argv[1:])
diff --git a/build/android/gyp/java_cpp_enum_tests.py b/build/android/gyp/java_cpp_enum_tests.py
new file mode 100755
index 00000000000..44f9766c82c
--- /dev/null
+++ b/build/android/gyp/java_cpp_enum_tests.py
@@ -0,0 +1,436 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Tests for enum_preprocess.py.
+
+This test suite containss various tests for the C++ -> Java enum generator.
+"""
+
+import collections
+import optparse
+import os
+import sys
+import unittest
+
+import java_cpp_enum
+from java_cpp_enum import EnumDefinition, GenerateOutput, GetScriptName
+from java_cpp_enum import HeaderParser
+
+sys.path.append(os.path.join(os.path.dirname(__file__), "gyp"))
+from util import build_utils
+
+class TestPreprocess(unittest.TestCase):
+ def testOutput(self):
+ definition = EnumDefinition(original_enum_name='ClassName',
+ enum_package='some.package',
+ entries=[('E1', 1), ('E2', '2 << 2')])
+ output = GenerateOutput('path/to/file', definition)
+ expected = """
+// Copyright 2014 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 file is autogenerated by
+// %s
+// From
+// path/to/file
+
+package some.package;
+
+public class ClassName {
+ public static final int E1 = 1;
+ public static final int E2 = 2 << 2;
+}
+"""
+ self.assertEqual(expected % GetScriptName(), output)
+
+ def testParseSimpleEnum(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum EnumName {
+ VALUE_ZERO,
+ VALUE_ONE,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('EnumName', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('VALUE_ZERO', 0),
+ ('VALUE_ONE', 1)]),
+ definition.entries)
+
+ def testParseBitShifts(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum EnumName {
+ VALUE_ZERO = 1 << 0,
+ VALUE_ONE = 1 << 1,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('EnumName', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('VALUE_ZERO', '1 << 0'),
+ ('VALUE_ONE', '1 << 1')]),
+ definition.entries)
+
+ def testParseClassNameOverride(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OverrideName
+ enum EnumName {
+ FOO
+ };
+
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OtherOverride
+ enum PrefixTest {
+ PREFIX_TEST_A,
+ PREFIX_TEST_B,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(2, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('OverrideName', definition.class_name)
+
+ definition = definitions[1]
+ self.assertEqual('OtherOverride', definition.class_name)
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 1)]),
+ definition.entries)
+
+ def testParseTwoEnums(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum EnumOne {
+ ENUM_ONE_A = 1,
+ // Comment there
+ ENUM_ONE_B = A,
+ };
+
+ enum EnumIgnore {
+ C, D, E
+ };
+
+ // GENERATED_JAVA_ENUM_PACKAGE: other.package
+ // GENERATED_JAVA_PREFIX_TO_STRIP: P_
+ enum EnumTwo {
+ P_A,
+ P_B
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(2, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('EnumOne', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('A', '1'),
+ ('B', 'A')]),
+ definition.entries)
+
+ definition = definitions[1]
+ self.assertEqual('EnumTwo', definition.class_name)
+ self.assertEqual('other.package', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 1)]),
+ definition.entries)
+
+ def testParseThrowsOnUnknownDirective(self):
+ test_data = """
+ // GENERATED_JAVA_UNKNOWN: Value
+ enum EnumName {
+ VALUE_ONE,
+ };
+ """.split('\n')
+ with self.assertRaises(Exception):
+ HeaderParser(test_data).ParseDefinitions()
+
+ def testParseReturnsEmptyListWithoutDirectives(self):
+ test_data = """
+ enum EnumName {
+ VALUE_ONE,
+ };
+ """.split('\n')
+ self.assertEqual([], HeaderParser(test_data).ParseDefinitions())
+
+ def testParseEnumClass(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum class Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('Foo', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('A', 0)]),
+ definition.entries)
+
+ def testParseEnumStruct(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum struct Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('Foo', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual(collections.OrderedDict([('A', 0)]),
+ definition.entries)
+
+ def testParseFixedTypeEnum(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum Foo : int {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('Foo', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual('int', definition.fixed_type)
+ self.assertEqual(collections.OrderedDict([('A', 0)]),
+ definition.entries)
+
+ def testParseFixedTypeEnumClass(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum class Foo: unsigned short {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual(1, len(definitions))
+ definition = definitions[0]
+ self.assertEqual('Foo', definition.class_name)
+ self.assertEqual('test.namespace', definition.enum_package)
+ self.assertEqual('unsigned short', definition.fixed_type)
+ self.assertEqual(collections.OrderedDict([('A', 0)]),
+ definition.entries)
+
+ def testParseUnknownFixedTypeRaises(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+ enum class Foo: foo_type {
+ FOO_A,
+ };
+ """.split('\n')
+ with self.assertRaises(Exception):
+ HeaderParser(test_data).ParseDefinitions()
+
+ def testParseSimpleMultiLineDirective(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (
+ // test.namespace)
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Bar
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual('test.namespace', definitions[0].enum_package)
+ self.assertEqual('Bar', definitions[0].class_name)
+
+ def testParseMultiLineDirective(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (te
+ // st.name
+ // space)
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual('test.namespace', definitions[0].enum_package)
+
+ def testParseMultiLineDirectiveWithOtherDirective(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (
+ // test.namespace)
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: (
+ // Ba
+ // r
+ // )
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ definitions = HeaderParser(test_data).ParseDefinitions()
+ self.assertEqual('test.namespace', definitions[0].enum_package)
+ self.assertEqual('Bar', definitions[0].class_name)
+
+ def testParseMalformedMultiLineDirectiveWithOtherDirective(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (
+ // test.name
+ // space
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Bar
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ with self.assertRaises(Exception):
+ HeaderParser(test_data).ParseDefinitions()
+
+ def testParseMalformedMultiLineDirective(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (
+ // test.name
+ // space
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ with self.assertRaises(Exception):
+ HeaderParser(test_data).ParseDefinitions()
+
+ def testParseMalformedMultiLineDirectiveShort(self):
+ test_data = """
+ // GENERATED_JAVA_ENUM_PACKAGE: (
+ enum Foo {
+ FOO_A,
+ };
+ """.split('\n')
+ with self.assertRaises(Exception):
+ HeaderParser(test_data).ParseDefinitions()
+
+ def testEnumValueAssignmentNoneDefined(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', None)
+ definition.AppendEntry('C', None)
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 1),
+ ('C', 2)]),
+ definition.entries)
+
+ def testEnumValueAssignmentAllDefined(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', '1')
+ definition.AppendEntry('B', '2')
+ definition.AppendEntry('C', '3')
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', '1'),
+ ('B', '2'),
+ ('C', '3')]),
+ definition.entries)
+
+ def testEnumValueAssignmentReferences(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', 'A')
+ definition.AppendEntry('C', None)
+ definition.AppendEntry('D', 'C')
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 0),
+ ('C', 1),
+ ('D', 1)]),
+ definition.entries)
+
+ def testEnumValueAssignmentSet(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', '2')
+ definition.AppendEntry('C', None)
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 2),
+ ('C', 3)]),
+ definition.entries)
+
+ def testEnumValueAssignmentSetReferences(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', 'A')
+ definition.AppendEntry('C', 'B')
+ definition.AppendEntry('D', None)
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 0),
+ ('C', 0),
+ ('D', 1)]),
+ definition.entries)
+
+ def testEnumValueAssignmentRaises(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', 'foo')
+ definition.AppendEntry('C', None)
+ with self.assertRaises(Exception):
+ definition.Finalize()
+
+ def testExplicitPrefixStripping(self):
+ definition = EnumDefinition(original_enum_name='c', enum_package='p')
+ definition.AppendEntry('P_A', None)
+ definition.AppendEntry('B', None)
+ definition.AppendEntry('P_C', None)
+ definition.AppendEntry('P_LAST', 'P_C')
+ definition.prefix_to_strip = 'P_'
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 1),
+ ('C', 2),
+ ('LAST', 2)]),
+ definition.entries)
+
+ def testImplicitPrefixStripping(self):
+ definition = EnumDefinition(original_enum_name='ClassName',
+ enum_package='p')
+ definition.AppendEntry('CLASS_NAME_A', None)
+ definition.AppendEntry('CLASS_NAME_B', None)
+ definition.AppendEntry('CLASS_NAME_C', None)
+ definition.AppendEntry('CLASS_NAME_LAST', 'CLASS_NAME_C')
+ definition.Finalize()
+ self.assertEqual(collections.OrderedDict([('A', 0),
+ ('B', 1),
+ ('C', 2),
+ ('LAST', 2)]),
+ definition.entries)
+
+ def testImplicitPrefixStrippingRequiresAllConstantsToBePrefixed(self):
+ definition = EnumDefinition(original_enum_name='Name',
+ enum_package='p')
+ definition.AppendEntry('A', None)
+ definition.AppendEntry('B', None)
+ definition.AppendEntry('NAME_LAST', None)
+ definition.Finalize()
+ self.assertEqual(['A', 'B', 'NAME_LAST'], definition.entries.keys())
+
+ def testGenerateThrowsOnEmptyInput(self):
+ with self.assertRaises(Exception):
+ original_do_parse = java_cpp_enum.DoParseHeaderFile
+ try:
+ java_cpp_enum.DoParseHeaderFile = lambda _: []
+ java_cpp_enum.DoGenerate('dir', ['file'])
+ finally:
+ java_cpp_enum.DoParseHeaderFile = original_do_parse
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option("--stamp", help="File to touch on success.")
+ options, _ = parser.parse_args(argv)
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestPreprocess)
+ unittest.TextTestRunner(verbosity=0).run(suite)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/build/android/gyp/javac.py b/build/android/gyp/javac.py
new file mode 100755
index 00000000000..dafe5dff0ab
--- /dev/null
+++ b/build/android/gyp/javac.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import fnmatch
+import optparse
+import os
+import shutil
+import re
+import sys
+import textwrap
+
+from util import build_utils
+from util import md5_check
+
+import jar
+
+sys.path.append(build_utils.COLORAMA_ROOT)
+import colorama
+
+
+def ColorJavacOutput(output):
+ fileline_prefix = r'(?P(?P[-.\w/\\]+.java):(?P[0-9]+):)'
+ warning_re = re.compile(
+ fileline_prefix + r'(?P warning: (?P.*))$')
+ error_re = re.compile(
+ fileline_prefix + r'(?P (?P.*))$')
+ marker_re = re.compile(r'\s*(?P\^)\s*$')
+
+ warning_color = ['full_message', colorama.Fore.YELLOW + colorama.Style.DIM]
+ error_color = ['full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT]
+ marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT]
+
+ def Colorize(line, regex, color):
+ match = regex.match(line)
+ start = match.start(color[0])
+ end = match.end(color[0])
+ return (line[:start]
+ + color[1] + line[start:end]
+ + colorama.Fore.RESET + colorama.Style.RESET_ALL
+ + line[end:])
+
+ def ApplyColor(line):
+ if warning_re.match(line):
+ line = Colorize(line, warning_re, warning_color)
+ elif error_re.match(line):
+ line = Colorize(line, error_re, error_color)
+ elif marker_re.match(line):
+ line = Colorize(line, marker_re, marker_color)
+ return line
+
+ return '\n'.join(map(ApplyColor, output.split('\n')))
+
+
+ERRORPRONE_OPTIONS = [
+ '-Xepdisable:'
+ # Something in chrome_private_java makes this check crash.
+ 'com.google.errorprone.bugpatterns.ClassCanBeStatic,'
+ # These crash on lots of targets.
+ 'com.google.errorprone.bugpatterns.WrongParameterPackage,'
+ 'com.google.errorprone.bugpatterns.GuiceOverridesGuiceInjectableMethod,'
+ 'com.google.errorprone.bugpatterns.GuiceOverridesJavaxInjectableMethod,'
+ 'com.google.errorprone.bugpatterns.ElementsCountedInLoop'
+]
+
+def DoJavac(
+ bootclasspath, classpath, classes_dir, chromium_code,
+ use_errorprone_path, java_files):
+ """Runs javac.
+
+ Builds |java_files| with the provided |classpath| and puts the generated
+ .class files into |classes_dir|. If |chromium_code| is true, extra lint
+ checking will be enabled.
+ """
+
+ jar_inputs = []
+ for path in classpath:
+ if os.path.exists(path + '.TOC'):
+ jar_inputs.append(path + '.TOC')
+ else:
+ jar_inputs.append(path)
+
+ javac_args = [
+ '-g',
+ # Chromium only allows UTF8 source files. Being explicit avoids
+ # javac pulling a default encoding from the user's environment.
+ '-encoding', 'UTF-8',
+ '-classpath', ':'.join(classpath),
+ '-d', classes_dir]
+
+ if bootclasspath:
+ javac_args.extend([
+ '-bootclasspath', ':'.join(bootclasspath),
+ '-source', '1.7',
+ '-target', '1.7',
+ ])
+
+ if chromium_code:
+ # TODO(aurimas): re-enable '-Xlint:deprecation' checks once they are fixed.
+ javac_args.extend(['-Xlint:unchecked'])
+ else:
+ # XDignore.symbol.file makes javac compile against rt.jar instead of
+ # ct.sym. This means that using a java internal package/class will not
+ # trigger a compile warning or error.
+ javac_args.extend(['-XDignore.symbol.file'])
+
+ if use_errorprone_path:
+ javac_cmd = [use_errorprone_path] + ERRORPRONE_OPTIONS
+ else:
+ javac_cmd = ['javac']
+
+ javac_cmd = javac_cmd + javac_args + java_files
+
+ def Compile():
+ build_utils.CheckOutput(
+ javac_cmd,
+ print_stdout=chromium_code,
+ stderr_filter=ColorJavacOutput)
+
+ record_path = os.path.join(classes_dir, 'javac.md5.stamp')
+ md5_check.CallAndRecordIfStale(
+ Compile,
+ record_path=record_path,
+ input_paths=java_files + jar_inputs,
+ input_strings=javac_cmd)
+
+
+_MAX_MANIFEST_LINE_LEN = 72
+
+
+def CreateManifest(manifest_path, classpath, main_class=None,
+ manifest_entries=None):
+ """Creates a manifest file with the given parameters.
+
+ This generates a manifest file that compiles with the spec found at
+ http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR_Manifest
+
+ Args:
+ manifest_path: The path to the manifest file that should be created.
+ classpath: The JAR files that should be listed on the manifest file's
+ classpath.
+ main_class: If present, the class containing the main() function.
+ manifest_entries: If present, a list of (key, value) pairs to add to
+ the manifest.
+
+ """
+ output = ['Manifest-Version: 1.0']
+ if main_class:
+ output.append('Main-Class: %s' % main_class)
+ if manifest_entries:
+ for k, v in manifest_entries:
+ output.append('%s: %s' % (k, v))
+ if classpath:
+ sanitized_paths = []
+ for path in classpath:
+ sanitized_paths.append(os.path.basename(path.strip('"')))
+ output.append('Class-Path: %s' % ' '.join(sanitized_paths))
+ output.append('Created-By: ')
+ output.append('')
+
+ wrapper = textwrap.TextWrapper(break_long_words=True,
+ drop_whitespace=False,
+ subsequent_indent=' ',
+ width=_MAX_MANIFEST_LINE_LEN - 2)
+ output = '\r\n'.join(w for l in output for w in wrapper.wrap(l))
+
+ with open(manifest_path, 'w') as f:
+ f.write(output)
+
+
+def main(argv):
+ colorama.init()
+
+ argv = build_utils.ExpandFileArgs(argv)
+
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option(
+ '--src-gendirs',
+ help='Directories containing generated java files.')
+ parser.add_option(
+ '--java-srcjars',
+ action='append',
+ default=[],
+ help='List of srcjars to include in compilation.')
+ parser.add_option(
+ '--bootclasspath',
+ action='append',
+ default=[],
+ help='Boot classpath for javac. If this is specified multiple times, '
+ 'they will all be appended to construct the classpath.')
+ parser.add_option(
+ '--classpath',
+ action='append',
+ help='Classpath for javac. If this is specified multiple times, they '
+ 'will all be appended to construct the classpath.')
+ parser.add_option(
+ '--javac-includes',
+ help='A list of file patterns. If provided, only java files that match'
+ 'one of the patterns will be compiled.')
+ parser.add_option(
+ '--jar-excluded-classes',
+ default='',
+ help='List of .class file patterns to exclude from the jar.')
+
+ parser.add_option(
+ '--chromium-code',
+ type='int',
+ help='Whether code being compiled should be built with stricter '
+ 'warnings for chromium code.')
+
+ parser.add_option(
+ '--use-errorprone-path',
+ help='Use the Errorprone compiler at this path.')
+
+ parser.add_option(
+ '--classes-dir',
+ help='Directory for compiled .class files.')
+ parser.add_option('--jar-path', help='Jar output path.')
+ parser.add_option(
+ '--main-class',
+ help='The class containing the main method.')
+ parser.add_option(
+ '--manifest-entry',
+ action='append',
+ help='Key:value pairs to add to the .jar manifest.')
+
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, args = parser.parse_args(argv)
+
+ if options.main_class and not options.jar_path:
+ parser.error('--main-class requires --jar-path')
+
+ bootclasspath = []
+ for arg in options.bootclasspath:
+ bootclasspath += build_utils.ParseGypList(arg)
+
+ classpath = []
+ for arg in options.classpath:
+ classpath += build_utils.ParseGypList(arg)
+
+ java_srcjars = []
+ for arg in options.java_srcjars:
+ java_srcjars += build_utils.ParseGypList(arg)
+
+ java_files = args
+ if options.src_gendirs:
+ src_gendirs = build_utils.ParseGypList(options.src_gendirs)
+ java_files += build_utils.FindInDirectories(src_gendirs, '*.java')
+
+ input_files = bootclasspath + classpath + java_srcjars + java_files
+ with build_utils.TempDir() as temp_dir:
+ classes_dir = os.path.join(temp_dir, 'classes')
+ os.makedirs(classes_dir)
+ if java_srcjars:
+ java_dir = os.path.join(temp_dir, 'java')
+ os.makedirs(java_dir)
+ for srcjar in java_srcjars:
+ build_utils.ExtractAll(srcjar, path=java_dir, pattern='*.java')
+ java_files += build_utils.FindInDirectory(java_dir, '*.java')
+
+ if options.javac_includes:
+ javac_includes = build_utils.ParseGypList(options.javac_includes)
+ filtered_java_files = []
+ for f in java_files:
+ for include in javac_includes:
+ if fnmatch.fnmatch(f, include):
+ filtered_java_files.append(f)
+ break
+ java_files = filtered_java_files
+
+ if len(java_files) != 0:
+ DoJavac(
+ bootclasspath,
+ classpath,
+ classes_dir,
+ options.chromium_code,
+ options.use_errorprone_path,
+ java_files)
+
+ if options.jar_path:
+ if options.main_class or options.manifest_entry:
+ if options.manifest_entry:
+ entries = map(lambda e: e.split(":"), options.manifest_entry)
+ else:
+ entries = []
+ manifest_file = os.path.join(temp_dir, 'manifest')
+ CreateManifest(manifest_file, classpath, options.main_class, entries)
+ else:
+ manifest_file = None
+ jar.JarDirectory(classes_dir,
+ build_utils.ParseGypList(options.jar_excluded_classes),
+ options.jar_path,
+ manifest_file=manifest_file)
+
+ if options.classes_dir:
+ # Delete the old classes directory. This ensures that all .class files in
+ # the output are actually from the input .java files. For example, if a
+ # .java file is deleted or an inner class is removed, the classes
+ # directory should not contain the corresponding old .class file after
+ # running this action.
+ build_utils.DeleteDirectory(options.classes_dir)
+ shutil.copytree(classes_dir, options.classes_dir)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ input_files + build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
+
+
diff --git a/build/android/gyp/jinja_template.py b/build/android/gyp/jinja_template.py
new file mode 100755
index 00000000000..e7c9a3436a4
--- /dev/null
+++ b/build/android/gyp/jinja_template.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Renders one or more template files using the Jinja template engine."""
+
+import codecs
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+# Import jinja2 from third_party/jinja2
+sys.path.append(os.path.join(os.path.dirname(__file__), '../../../third_party'))
+import jinja2 # pylint: disable=F0401
+
+
+class RecordingFileSystemLoader(jinja2.FileSystemLoader):
+ '''A FileSystemLoader that stores a list of loaded templates.'''
+ def __init__(self, searchpath):
+ jinja2.FileSystemLoader.__init__(self, searchpath)
+ self.loaded_templates = set()
+
+ def get_source(self, environment, template):
+ contents, filename, uptodate = jinja2.FileSystemLoader.get_source(
+ self, environment, template)
+ self.loaded_templates.add(os.path.relpath(filename))
+ return contents, filename, uptodate
+
+ def get_loaded_templates(self):
+ return list(self.loaded_templates)
+
+
+def ProcessFile(env, input_filename, loader_base_dir, output_filename,
+ variables):
+ input_rel_path = os.path.relpath(input_filename, loader_base_dir)
+ template = env.get_template(input_rel_path)
+ output = template.render(variables)
+ with codecs.open(output_filename, 'w', 'utf-8') as output_file:
+ output_file.write(output)
+
+
+def ProcessFiles(env, input_filenames, loader_base_dir, inputs_base_dir,
+ outputs_zip, variables):
+ with build_utils.TempDir() as temp_dir:
+ for input_filename in input_filenames:
+ relpath = os.path.relpath(os.path.abspath(input_filename),
+ os.path.abspath(inputs_base_dir))
+ if relpath.startswith(os.pardir):
+ raise Exception('input file %s is not contained in inputs base dir %s'
+ % (input_filename, inputs_base_dir))
+
+ output_filename = os.path.join(temp_dir, relpath)
+ parent_dir = os.path.dirname(output_filename)
+ build_utils.MakeDirectory(parent_dir)
+ ProcessFile(env, input_filename, loader_base_dir, output_filename,
+ variables)
+
+ build_utils.ZipDir(outputs_zip, temp_dir)
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--inputs', help='The template files to process.')
+ parser.add_option('--output', help='The output file to generate. Valid '
+ 'only if there is a single input.')
+ parser.add_option('--outputs-zip', help='A zip file containing the processed '
+ 'templates. Required if there are multiple inputs.')
+ parser.add_option('--inputs-base-dir', help='A common ancestor directory of '
+ 'the inputs. Each output\'s path in the output zip will '
+ 'match the relative path from INPUTS_BASE_DIR to the '
+ 'input. Required if --output-zip is given.')
+ parser.add_option('--loader-base-dir', help='Base path used by the template '
+ 'loader. Must be a common ancestor directory of '
+ 'the inputs. Defaults to CHROMIUM_SRC.',
+ default=build_utils.CHROMIUM_SRC)
+ parser.add_option('--variables', help='Variables to be made available in the '
+ 'template processing environment, as a GYP list (e.g. '
+ '--variables "channel=beta mstone=39")', default='')
+ options, args = parser.parse_args()
+
+ build_utils.CheckOptions(options, parser, required=['inputs'])
+ inputs = build_utils.ParseGypList(options.inputs)
+
+ if (options.output is None) == (options.outputs_zip is None):
+ parser.error('Exactly one of --output and --output-zip must be given')
+ if options.output and len(inputs) != 1:
+ parser.error('--output cannot be used with multiple inputs')
+ if options.outputs_zip and not options.inputs_base_dir:
+ parser.error('--inputs-base-dir must be given when --output-zip is used')
+ if args:
+ parser.error('No positional arguments should be given.')
+
+ variables = {}
+ for v in build_utils.ParseGypList(options.variables):
+ if '=' not in v:
+ parser.error('--variables argument must contain "=": ' + v)
+ name, _, value = v.partition('=')
+ variables[name] = value
+
+ loader = RecordingFileSystemLoader(options.loader_base_dir)
+ env = jinja2.Environment(loader=loader, undefined=jinja2.StrictUndefined,
+ line_comment_prefix='##')
+ if options.output:
+ ProcessFile(env, inputs[0], options.loader_base_dir, options.output,
+ variables)
+ else:
+ ProcessFiles(env, inputs, options.loader_base_dir, options.inputs_base_dir,
+ options.outputs_zip, variables)
+
+ if options.depfile:
+ deps = loader.get_loaded_templates() + build_utils.GetPythonDependencies()
+ build_utils.WriteDepfile(options.depfile, deps)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/gyp/lint.py b/build/android/gyp/lint.py
new file mode 100755
index 00000000000..6c4645abeab
--- /dev/null
+++ b/build/android/gyp/lint.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+"""Runs Android's lint tool."""
+
+
+import optparse
+import os
+import sys
+from xml.dom import minidom
+
+from util import build_utils
+
+
+_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '..', '..', '..'))
+
+
+def _RunLint(lint_path, config_path, processed_config_path, manifest_path,
+ result_path, product_dir, sources, jar_path, resource_dir=None):
+
+ def _RelativizePath(path):
+ """Returns relative path to top-level src dir.
+
+ Args:
+ path: A path relative to cwd.
+ """
+ return os.path.relpath(os.path.abspath(path), _SRC_ROOT)
+
+ def _ProcessConfigFile():
+ if not build_utils.IsTimeStale(processed_config_path, [config_path]):
+ return
+
+ with open(config_path, 'rb') as f:
+ content = f.read().replace(
+ 'PRODUCT_DIR', _RelativizePath(product_dir))
+
+ with open(processed_config_path, 'wb') as f:
+ f.write(content)
+
+ def _ProcessResultFile():
+ with open(result_path, 'rb') as f:
+ content = f.read().replace(
+ _RelativizePath(product_dir), 'PRODUCT_DIR')
+
+ with open(result_path, 'wb') as f:
+ f.write(content)
+
+ def _ParseAndShowResultFile():
+ dom = minidom.parse(result_path)
+ issues = dom.getElementsByTagName('issue')
+ print >> sys.stderr
+ for issue in issues:
+ issue_id = issue.attributes['id'].value
+ message = issue.attributes['message'].value
+ location_elem = issue.getElementsByTagName('location')[0]
+ path = location_elem.attributes['file'].value
+ line = location_elem.getAttribute('line')
+ if line:
+ error = '%s:%s %s: %s [warning]' % (path, line, message, issue_id)
+ else:
+ # Issues in class files don't have a line number.
+ error = '%s %s: %s [warning]' % (path, message, issue_id)
+ print >> sys.stderr, error
+ for attr in ['errorLine1', 'errorLine2']:
+ error_line = issue.getAttribute(attr)
+ if error_line:
+ print >> sys.stderr, error_line
+ return len(issues)
+
+ with build_utils.TempDir() as temp_dir:
+ _ProcessConfigFile()
+
+ cmd = [
+ _RelativizePath(lint_path), '-Werror', '--exitcode', '--showall',
+ '--config', _RelativizePath(processed_config_path),
+ '--classpath', _RelativizePath(jar_path),
+ '--xml', _RelativizePath(result_path),
+ ]
+ if resource_dir:
+ cmd.extend(['--resources', _RelativizePath(resource_dir)])
+
+ # There may be multiple source files with the same basename (but in
+ # different directories). It is difficult to determine what part of the path
+ # corresponds to the java package, and so instead just link the source files
+ # into temporary directories (creating a new one whenever there is a name
+ # conflict).
+ src_dirs = []
+ def NewSourceDir():
+ new_dir = os.path.join(temp_dir, str(len(src_dirs)))
+ os.mkdir(new_dir)
+ src_dirs.append(new_dir)
+ cmd.extend(['--sources', _RelativizePath(new_dir)])
+ return new_dir
+
+ def PathInDir(d, src):
+ return os.path.join(d, os.path.basename(src))
+
+ for src in sources:
+ src_dir = None
+ for d in src_dirs:
+ if not os.path.exists(PathInDir(d, src)):
+ src_dir = d
+ break
+ if not src_dir:
+ src_dir = NewSourceDir()
+ os.symlink(os.path.abspath(src), PathInDir(src_dir, src))
+
+ cmd.append(_RelativizePath(os.path.join(manifest_path, os.pardir)))
+
+ if os.path.exists(result_path):
+ os.remove(result_path)
+
+ try:
+ build_utils.CheckOutput(cmd, cwd=_SRC_ROOT)
+ except build_utils.CalledProcessError as e:
+ # There is a problem with lint usage
+ if not os.path.exists(result_path):
+ print 'Something is wrong:'
+ print e
+ return 1
+
+ # There are actual lint issues
+ else:
+ try:
+ num_issues = _ParseAndShowResultFile()
+ except Exception:
+ print 'Lint created unparseable xml file...'
+ print 'File contents:'
+ with open(result_path) as f:
+ print f.read()
+ return 1
+
+ _ProcessResultFile()
+ msg = ('\nLint found %d new issues.\n'
+ ' - For full explanation refer to %s\n'
+ ' - Wanna suppress these issues?\n'
+ ' 1. Read comment in %s\n'
+ ' 2. Run "python %s %s"\n' %
+ (num_issues,
+ _RelativizePath(result_path),
+ _RelativizePath(config_path),
+ _RelativizePath(os.path.join(_SRC_ROOT, 'build', 'android',
+ 'lint', 'suppress.py')),
+ _RelativizePath(result_path)))
+ print >> sys.stderr, msg
+ return 1
+
+ return 0
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--lint-path', help='Path to lint executable.')
+ parser.add_option('--config-path', help='Path to lint suppressions file.')
+ parser.add_option('--processed-config-path',
+ help='Path to processed lint suppressions file.')
+ parser.add_option('--manifest-path', help='Path to AndroidManifest.xml')
+ parser.add_option('--result-path', help='Path to XML lint result file.')
+ parser.add_option('--product-dir', help='Path to product dir.')
+ parser.add_option('--src-dirs', help='Directories containing java files.')
+ parser.add_option('--java-files', help='Paths to java files.')
+ parser.add_option('--jar-path', help='Jar file containing class files.')
+ parser.add_option('--resource-dir', help='Path to resource dir.')
+ parser.add_option('--can-fail-build', action='store_true',
+ help='If set, script will exit with nonzero exit status'
+ ' if lint errors are present')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--enable', action='store_true',
+ help='Run lint instead of just touching stamp.')
+
+ options, _ = parser.parse_args()
+
+ build_utils.CheckOptions(
+ options, parser, required=['lint_path', 'config_path',
+ 'processed_config_path', 'manifest_path',
+ 'result_path', 'product_dir',
+ 'jar_path'])
+
+ rc = 0
+
+ if options.enable:
+ sources = []
+ if options.src_dirs:
+ src_dirs = build_utils.ParseGypList(options.src_dirs)
+ sources = build_utils.FindInDirectories(src_dirs, '*.java')
+ elif options.java_files:
+ sources = build_utils.ParseGypList(options.java_files)
+ else:
+ print 'One of --src-dirs or --java-files must be specified.'
+ return 1
+ rc = _RunLint(options.lint_path, options.config_path,
+ options.processed_config_path,
+ options.manifest_path, options.result_path,
+ options.product_dir, sources, options.jar_path,
+ options.resource_dir)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+ if options.stamp and not rc:
+ build_utils.Touch(options.stamp)
+
+ return rc if options.can_fail_build else 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/locale_pak_resources.py b/build/android/gyp/locale_pak_resources.py
new file mode 100755
index 00000000000..84c4a37ba92
--- /dev/null
+++ b/build/android/gyp/locale_pak_resources.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 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.
+"""Creates a resources.zip for locale .pak files.
+
+Places the locale.pak files into appropriate resource configs
+(e.g. en-GB.pak -> res/raw-en/en_gb.lpak). Also generates a locale_paks
+TypedArray so that resource files can be enumerated at runtime.
+"""
+
+import collections
+import optparse
+import os
+import sys
+import zipfile
+
+from util import build_utils
+
+
+# This should stay in sync with:
+# base/android/java/src/org/chromium/base/LocaleUtils.java
+_CHROME_TO_ANDROID_LOCALE_MAP = {
+ 'he': 'iw',
+ 'id': 'in',
+ 'fil': 'tl',
+}
+
+
+def ToResourceFileName(name):
+ """Returns the resource-compatible file name for the given file."""
+ # Resources file names must consist of [a-z0-9_.].
+ # Changes extension to .lpak so that compression can be toggled separately for
+ # locale pak files vs other pak files.
+ return name.replace('-', '_').replace('.pak', '.lpak').lower()
+
+
+def CreateLocalePaksXml(names):
+ """Creates the contents for the locale-paks.xml files."""
+ VALUES_FILE_TEMPLATE = '''
+
+ %s
+
+
+'''
+ VALUES_ITEM_TEMPLATE = '''
+ - @raw/%s
'''
+
+ res_names = (os.path.splitext(name)[0] for name in names)
+ items = ''.join((VALUES_ITEM_TEMPLATE % name for name in res_names))
+ return VALUES_FILE_TEMPLATE % items
+
+
+def ComputeMappings(sources):
+ """Computes the mappings of sources -> resources.
+
+ Returns a tuple of:
+ - mappings: List of (src, dest) paths
+ - lang_to_locale_map: Map of language -> list of resource names
+ e.g. "en" -> ["en_gb.lpak"]
+ """
+ lang_to_locale_map = collections.defaultdict(list)
+ mappings = []
+ for src_path in sources:
+ basename = os.path.basename(src_path)
+ name = os.path.splitext(basename)[0]
+ res_name = ToResourceFileName(basename)
+ if name == 'en-US':
+ dest_dir = 'raw'
+ else:
+ # Chrome's uses different region mapping logic from Android, so include
+ # all regions for each language.
+ android_locale = _CHROME_TO_ANDROID_LOCALE_MAP.get(name, name)
+ lang = android_locale[0:2]
+ dest_dir = 'raw-' + lang
+ lang_to_locale_map[lang].append(res_name)
+ mappings.append((src_path, os.path.join(dest_dir, res_name)))
+ return mappings, lang_to_locale_map
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--locale-paks', help='List of files for res/raw-LOCALE')
+ parser.add_option('--resources-zip', help='Path to output resources.zip')
+ parser.add_option('--print-languages',
+ action='store_true',
+ help='Print out the list of languages that cover the given locale paks '
+ '(using Android\'s language codes)')
+
+ options, _ = parser.parse_args()
+ build_utils.CheckOptions(options, parser,
+ required=['locale_paks'])
+
+ sources = build_utils.ParseGypList(options.locale_paks)
+
+ if options.depfile:
+ deps = sources + build_utils.GetPythonDependencies()
+ build_utils.WriteDepfile(options.depfile, deps)
+
+ mappings, lang_to_locale_map = ComputeMappings(sources)
+ if options.print_languages:
+ print '\n'.join(sorted(lang_to_locale_map))
+
+ if options.resources_zip:
+ with zipfile.ZipFile(options.resources_zip, 'w', zipfile.ZIP_STORED) as out:
+ for mapping in mappings:
+ out.write(mapping[0], mapping[1])
+
+ # Create TypedArray resources so ResourceExtractor can enumerate files.
+ def WriteValuesFile(lang, names):
+ dest_dir = 'values'
+ if lang:
+ dest_dir += '-' + lang
+ # Always extract en-US.lpak since it's the fallback.
+ xml = CreateLocalePaksXml(names + ['en_us.lpak'])
+ out.writestr(os.path.join(dest_dir, 'locale-paks.xml'), xml)
+
+ for lang, names in lang_to_locale_map.iteritems():
+ WriteValuesFile(lang, names)
+ WriteValuesFile(None, [])
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/gyp/pack_relocations.py b/build/android/gyp/pack_relocations.py
new file mode 100755
index 00000000000..02e44999938
--- /dev/null
+++ b/build/android/gyp/pack_relocations.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Pack relocations in a library (or copy unchanged).
+
+If --enable-packing and --configuration-name=='Release', invoke the
+relocation_packer tool to pack the .rel.dyn or .rela.dyn section in the given
+library files. This step is inserted after the libraries are stripped.
+
+If --enable-packing is zero, the script copies files verbatim, with no
+attempt to pack relocations.
+
+Any library listed in --exclude-packing-list is also copied verbatim,
+irrespective of any --enable-packing setting. Typically this would be
+'libchromium_android_linker.so'.
+"""
+
+import optparse
+import os
+import shlex
+import shutil
+import sys
+import tempfile
+
+from util import build_utils
+
+def PackLibraryRelocations(android_pack_relocations, library_path, output_path):
+ shutil.copy(library_path, output_path)
+ pack_command = [android_pack_relocations, output_path]
+ build_utils.CheckOutput(pack_command)
+
+
+def CopyLibraryUnchanged(library_path, output_path):
+ shutil.copy(library_path, output_path)
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--clear-dir', action='store_true',
+ help='If set, the destination directory will be deleted '
+ 'before copying files to it. This is highly recommended to '
+ 'ensure that no stale files are left in the directory.')
+
+ parser.add_option('--configuration-name',
+ default='Release',
+ help='Gyp configuration name (i.e. Debug, Release)')
+ parser.add_option('--enable-packing',
+ choices=['0', '1'],
+ help=('Pack relocations if 1 and configuration name is \'Release\','
+ ' otherwise plain file copy'))
+ parser.add_option('--exclude-packing-list',
+ default='',
+ help='Names of any libraries explicitly not packed')
+ parser.add_option('--android-pack-relocations',
+ help='Path to the relocations packer binary')
+ parser.add_option('--stripped-libraries-dir',
+ help='Directory for stripped libraries')
+ parser.add_option('--packed-libraries-dir',
+ help='Directory for packed libraries')
+ parser.add_option('--libraries', action='append',
+ help='List of libraries')
+ parser.add_option('--stamp', help='Path to touch on success')
+
+ options, _ = parser.parse_args(args)
+ enable_packing = (options.enable_packing == '1' and
+ options.configuration_name == 'Release')
+ exclude_packing_set = set(shlex.split(options.exclude_packing_list))
+
+ libraries = []
+ for libs_arg in options.libraries:
+ libraries += build_utils.ParseGypList(libs_arg)
+
+ if options.clear_dir:
+ build_utils.DeleteDirectory(options.packed_libraries_dir)
+
+ build_utils.MakeDirectory(options.packed_libraries_dir)
+
+ for library in libraries:
+ library_path = os.path.join(options.stripped_libraries_dir, library)
+ output_path = os.path.join(
+ options.packed_libraries_dir, os.path.basename(library))
+
+ if enable_packing and library not in exclude_packing_set:
+ PackLibraryRelocations(options.android_pack_relocations,
+ library_path,
+ output_path)
+ else:
+ CopyLibraryUnchanged(library_path, output_path)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ libraries + build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/package_resources.py b/build/android/gyp/package_resources.py
new file mode 100755
index 00000000000..d17d1fe2f95
--- /dev/null
+++ b/build/android/gyp/package_resources.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+# pylint: disable=C0301
+"""Package resources into an apk.
+
+See https://android.googlesource.com/platform/tools/base/+/master/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java
+and
+https://android.googlesource.com/platform/sdk/+/master/files/ant/build.xml
+"""
+# pylint: enable=C0301
+
+import optparse
+import os
+import re
+import shutil
+import zipfile
+
+from util import build_utils
+
+
+# List is generated from the chrome_apk.apk_intermediates.ap_ via:
+# unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \
+# | uniq | grep -- -tvdpi- | cut -c10-
+# and then manually sorted.
+# Note that we can't just do a cross-product of dimentions because the filenames
+# become too big and aapt fails to create the files.
+# This leaves all default drawables (mdpi) in the main apk. Android gets upset
+# though if any drawables are missing from the default drawables/ directory.
+DENSITY_SPLITS = {
+ 'hdpi': (
+ 'hdpi-v4', # Order matters for output file names.
+ 'ldrtl-hdpi-v4',
+ 'sw600dp-hdpi-v13',
+ 'ldrtl-hdpi-v17',
+ 'ldrtl-sw600dp-hdpi-v17',
+ 'hdpi-v21',
+ ),
+ 'xhdpi': (
+ 'xhdpi-v4',
+ 'ldrtl-xhdpi-v4',
+ 'sw600dp-xhdpi-v13',
+ 'ldrtl-xhdpi-v17',
+ 'ldrtl-sw600dp-xhdpi-v17',
+ 'xhdpi-v21',
+ ),
+ 'xxhdpi': (
+ 'xxhdpi-v4',
+ 'ldrtl-xxhdpi-v4',
+ 'sw600dp-xxhdpi-v13',
+ 'ldrtl-xxhdpi-v17',
+ 'ldrtl-sw600dp-xxhdpi-v17',
+ 'xxhdpi-v21',
+ ),
+ 'xxxhdpi': (
+ 'xxxhdpi-v4',
+ 'ldrtl-xxxhdpi-v4',
+ 'sw600dp-xxxhdpi-v13',
+ 'ldrtl-xxxhdpi-v17',
+ 'ldrtl-sw600dp-xxxhdpi-v17',
+ 'xxxhdpi-v21',
+ ),
+ 'tvdpi': (
+ 'tvdpi-v4',
+ 'sw600dp-tvdpi-v13',
+ 'ldrtl-sw600dp-tvdpi-v17',
+ ),
+}
+
+
+def ParseArgs():
+ """Parses command line options.
+
+ Returns:
+ An options object as from optparse.OptionsParser.parse_args()
+ """
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--android-sdk', help='path to the Android SDK folder')
+ parser.add_option('--aapt-path',
+ help='path to the Android aapt tool')
+
+ parser.add_option('--configuration-name',
+ help='Gyp\'s configuration name (Debug or Release).')
+
+ parser.add_option('--android-manifest', help='AndroidManifest.xml path')
+ parser.add_option('--version-code', help='Version code for apk.')
+ parser.add_option('--version-name', help='Version name for apk.')
+ parser.add_option(
+ '--shared-resources',
+ action='store_true',
+ help='Make a resource package that can be loaded by a different'
+ 'application at runtime to access the package\'s resources.')
+ parser.add_option('--resource-zips',
+ help='zip files containing resources to be packaged')
+ parser.add_option('--asset-dir',
+ help='directories containing assets to be packaged')
+ parser.add_option('--no-compress', help='disables compression for the '
+ 'given comma separated list of extensions')
+ parser.add_option(
+ '--create-density-splits',
+ action='store_true',
+ help='Enables density splits')
+ parser.add_option('--language-splits',
+ help='GYP list of languages to create splits for')
+
+ parser.add_option('--apk-path',
+ help='Path to output (partial) apk.')
+
+ (options, args) = parser.parse_args()
+
+ if args:
+ parser.error('No positional arguments should be given.')
+
+ # Check that required options have been provided.
+ required_options = ('android_sdk', 'aapt_path', 'configuration_name',
+ 'android_manifest', 'version_code', 'version_name',
+ 'apk_path')
+
+ build_utils.CheckOptions(options, parser, required=required_options)
+
+ return options
+
+
+def MoveImagesToNonMdpiFolders(res_root):
+ """Move images from drawable-*-mdpi-* folders to drawable-* folders.
+
+ Why? http://crbug.com/289843
+ """
+ for src_dir_name in os.listdir(res_root):
+ src_components = src_dir_name.split('-')
+ if src_components[0] != 'drawable' or 'mdpi' not in src_components:
+ continue
+ src_dir = os.path.join(res_root, src_dir_name)
+ if not os.path.isdir(src_dir):
+ continue
+ dst_components = [c for c in src_components if c != 'mdpi']
+ assert dst_components != src_components
+ dst_dir_name = '-'.join(dst_components)
+ dst_dir = os.path.join(res_root, dst_dir_name)
+ build_utils.MakeDirectory(dst_dir)
+ for src_file_name in os.listdir(src_dir):
+ if not src_file_name.endswith('.png'):
+ continue
+ src_file = os.path.join(src_dir, src_file_name)
+ dst_file = os.path.join(dst_dir, src_file_name)
+ assert not os.path.lexists(dst_file)
+ shutil.move(src_file, dst_file)
+
+
+def PackageArgsForExtractedZip(d):
+ """Returns the aapt args for an extracted resources zip.
+
+ A resources zip either contains the resources for a single target or for
+ multiple targets. If it is multiple targets merged into one, the actual
+ resource directories will be contained in the subdirectories 0, 1, 2, ...
+ """
+ subdirs = [os.path.join(d, s) for s in os.listdir(d)]
+ subdirs = [s for s in subdirs if os.path.isdir(s)]
+ is_multi = '0' in [os.path.basename(s) for s in subdirs]
+ if is_multi:
+ res_dirs = sorted(subdirs, key=lambda p : int(os.path.basename(p)))
+ else:
+ res_dirs = [d]
+ package_command = []
+ for d in res_dirs:
+ MoveImagesToNonMdpiFolders(d)
+ package_command += ['-S', d]
+ return package_command
+
+
+def RenameDensitySplits(apk_path):
+ """Renames all density splits to have shorter / predictable names."""
+ for density, config in DENSITY_SPLITS.iteritems():
+ src_path = '%s_%s' % (apk_path, '_'.join(config))
+ dst_path = '%s_%s' % (apk_path, density)
+ if src_path != dst_path:
+ if os.path.exists(dst_path):
+ os.unlink(dst_path)
+ os.rename(src_path, dst_path)
+
+
+def CheckForMissedConfigs(apk_path, check_density, languages):
+ """Raises an exception if apk_path contains any unexpected configs."""
+ triggers = []
+ if check_density:
+ triggers.extend(re.compile('-%s' % density) for density in DENSITY_SPLITS)
+ if languages:
+ triggers.extend(re.compile(r'-%s\b' % lang) for lang in languages)
+ with zipfile.ZipFile(apk_path) as main_apk_zip:
+ for name in main_apk_zip.namelist():
+ for trigger in triggers:
+ if trigger.search(name) and not 'mipmap-' in name:
+ raise Exception(('Found config in main apk that should have been ' +
+ 'put into a split: %s\nYou need to update ' +
+ 'package_resources.py to include this new ' +
+ 'config (trigger=%s)') % (name, trigger.pattern))
+
+
+def main():
+ options = ParseArgs()
+ android_jar = os.path.join(options.android_sdk, 'android.jar')
+ aapt = options.aapt_path
+
+ with build_utils.TempDir() as temp_dir:
+ package_command = [aapt,
+ 'package',
+ '--version-code', options.version_code,
+ '--version-name', options.version_name,
+ '-M', options.android_manifest,
+ '--no-crunch',
+ '-f',
+ '--auto-add-overlay',
+ '-I', android_jar,
+ '-F', options.apk_path,
+ '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN,
+ ]
+
+ if options.no_compress:
+ for ext in options.no_compress.split(','):
+ package_command += ['-0', ext]
+ if options.shared_resources:
+ package_command.append('--shared-lib')
+
+ if options.asset_dir and os.path.exists(options.asset_dir):
+ package_command += ['-A', options.asset_dir]
+
+ if options.resource_zips:
+ dep_zips = build_utils.ParseGypList(options.resource_zips)
+ for z in dep_zips:
+ subdir = os.path.join(temp_dir, os.path.basename(z))
+ if os.path.exists(subdir):
+ raise Exception('Resource zip name conflict: ' + os.path.basename(z))
+ build_utils.ExtractAll(z, path=subdir)
+ package_command += PackageArgsForExtractedZip(subdir)
+
+ if options.create_density_splits:
+ for config in DENSITY_SPLITS.itervalues():
+ package_command.extend(('--split', ','.join(config)))
+
+ language_splits = None
+ if options.language_splits:
+ language_splits = build_utils.ParseGypList(options.language_splits)
+ for lang in language_splits:
+ package_command.extend(('--split', lang))
+
+ if 'Debug' in options.configuration_name:
+ package_command += ['--debug-mode']
+
+ build_utils.CheckOutput(
+ package_command, print_stdout=False, print_stderr=False)
+
+ if options.create_density_splits or language_splits:
+ CheckForMissedConfigs(
+ options.apk_path, options.create_density_splits, language_splits)
+
+ if options.create_density_splits:
+ RenameDensitySplits(options.apk_path)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/gyp/process_resources.py b/build/android/gyp/process_resources.py
new file mode 100755
index 00000000000..d227954ae96
--- /dev/null
+++ b/build/android/gyp/process_resources.py
@@ -0,0 +1,420 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Process Android resources to generate R.java, and prepare for packaging.
+
+This will crunch images and generate v14 compatible resources
+(see generate_v14_compatible_resources.py).
+"""
+
+import codecs
+import optparse
+import os
+import re
+import shutil
+import sys
+import zipfile
+
+import generate_v14_compatible_resources
+
+from util import build_utils
+
+# Import jinja2 from third_party/jinja2
+sys.path.insert(1,
+ os.path.join(os.path.dirname(__file__), '../../../third_party'))
+from jinja2 import Template # pylint: disable=F0401
+
+
+def ParseArgs(args):
+ """Parses command line options.
+
+ Returns:
+ An options object as from optparse.OptionsParser.parse_args()
+ """
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--android-sdk', help='path to the Android SDK folder')
+ parser.add_option('--aapt-path',
+ help='path to the Android aapt tool')
+ parser.add_option('--non-constant-id', action='store_true')
+
+ parser.add_option('--android-manifest', help='AndroidManifest.xml path')
+ parser.add_option('--custom-package', help='Java package for R.java')
+ parser.add_option(
+ '--shared-resources',
+ action='store_true',
+ help='Make a resource package that can be loaded by a different'
+ 'application at runtime to access the package\'s resources.')
+
+ parser.add_option('--resource-dirs',
+ help='Directories containing resources of this target.')
+ parser.add_option('--dependencies-res-zips',
+ help='Resources from dependents.')
+
+ parser.add_option('--resource-zip-out',
+ help='Path for output zipped resources.')
+
+ parser.add_option('--R-dir',
+ help='directory to hold generated R.java.')
+ parser.add_option('--srcjar-out',
+ help='Path to srcjar to contain generated R.java.')
+ parser.add_option('--r-text-out',
+ help='Path to store the R.txt file generated by appt.')
+
+ parser.add_option('--proguard-file',
+ help='Path to proguard.txt generated file')
+
+ parser.add_option(
+ '--v14-skip',
+ action="store_true",
+ help='Do not generate nor verify v14 resources')
+
+ parser.add_option(
+ '--extra-res-packages',
+ help='Additional package names to generate R.java files for')
+ parser.add_option(
+ '--extra-r-text-files',
+ help='For each additional package, the R.txt file should contain a '
+ 'list of resources to be included in the R.java file in the format '
+ 'generated by aapt')
+ parser.add_option(
+ '--include-all-resources',
+ action='store_true',
+ help='Include every resource ID in every generated R.java file '
+ '(ignoring R.txt).')
+
+ parser.add_option(
+ '--all-resources-zip-out',
+ help='Path for output of all resources. This includes resources in '
+ 'dependencies.')
+
+ parser.add_option('--stamp', help='File to touch on success')
+
+ (options, args) = parser.parse_args(args)
+
+ if args:
+ parser.error('No positional arguments should be given.')
+
+ # Check that required options have been provided.
+ required_options = (
+ 'android_sdk',
+ 'aapt_path',
+ 'android_manifest',
+ 'dependencies_res_zips',
+ 'resource_dirs',
+ 'resource_zip_out',
+ )
+ build_utils.CheckOptions(options, parser, required=required_options)
+
+ if (options.R_dir is None) == (options.srcjar_out is None):
+ raise Exception('Exactly one of --R-dir or --srcjar-out must be specified.')
+
+ return options
+
+
+def CreateExtraRJavaFiles(
+ r_dir, extra_packages, extra_r_text_files, shared_resources, include_all):
+ if include_all:
+ java_files = build_utils.FindInDirectory(r_dir, "R.java")
+ if len(java_files) != 1:
+ return
+ r_java_file = java_files[0]
+ r_java_contents = codecs.open(r_java_file, encoding='utf-8').read()
+
+ for package in extra_packages:
+ package_r_java_dir = os.path.join(r_dir, *package.split('.'))
+ build_utils.MakeDirectory(package_r_java_dir)
+ package_r_java_path = os.path.join(package_r_java_dir, 'R.java')
+ new_r_java = re.sub(r'package [.\w]*;', u'package %s;' % package,
+ r_java_contents)
+ codecs.open(package_r_java_path, 'w', encoding='utf-8').write(new_r_java)
+ else:
+ if len(extra_packages) != len(extra_r_text_files):
+ raise Exception('Need one R.txt file per extra package')
+
+ all_resources = {}
+ r_txt_file = os.path.join(r_dir, 'R.txt')
+ if not os.path.exists(r_txt_file):
+ return
+ with open(r_txt_file) as f:
+ for line in f:
+ m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line)
+ if not m:
+ raise Exception('Unexpected line in R.txt: %s' % line)
+ java_type, resource_type, name, value = m.groups()
+ all_resources[(resource_type, name)] = (java_type, value)
+
+ for package, r_text_file in zip(extra_packages, extra_r_text_files):
+ if os.path.exists(r_text_file):
+ package_r_java_dir = os.path.join(r_dir, *package.split('.'))
+ build_utils.MakeDirectory(package_r_java_dir)
+ package_r_java_path = os.path.join(package_r_java_dir, 'R.java')
+ CreateExtraRJavaFile(
+ package, package_r_java_path, r_text_file, all_resources,
+ shared_resources)
+
+
+def CreateExtraRJavaFile(
+ package, r_java_path, r_text_file, all_resources, shared_resources):
+ resources = {}
+ with open(r_text_file) as f:
+ for line in f:
+ m = re.match(r'int(?:\[\])? (\w+) (\w+) ', line)
+ if not m:
+ raise Exception('Unexpected line in R.txt: %s' % line)
+ resource_type, name = m.groups()
+ java_type, value = all_resources[(resource_type, name)]
+ if resource_type not in resources:
+ resources[resource_type] = []
+ resources[resource_type].append((name, java_type, value))
+
+ template = Template("""/* AUTO-GENERATED FILE. DO NOT MODIFY. */
+
+package {{ package }};
+
+public final class R {
+ {% for resource_type in resources %}
+ public static final class {{ resource_type }} {
+ {% for name, java_type, value in resources[resource_type] %}
+ {% if shared_resources %}
+ public static {{ java_type }} {{ name }} = {{ value }};
+ {% else %}
+ public static final {{ java_type }} {{ name }} = {{ value }};
+ {% endif %}
+ {% endfor %}
+ }
+ {% endfor %}
+ {% if shared_resources %}
+ public static void onResourcesLoaded(int packageId) {
+ {% for resource_type in resources %}
+ {% for name, java_type, value in resources[resource_type] %}
+ {% if java_type == 'int[]' %}
+ for(int i = 0; i < {{ resource_type }}.{{ name }}.length; ++i) {
+ {{ resource_type }}.{{ name }}[i] =
+ ({{ resource_type }}.{{ name }}[i] & 0x00ffffff)
+ | (packageId << 24);
+ }
+ {% else %}
+ {{ resource_type }}.{{ name }} =
+ ({{ resource_type }}.{{ name }} & 0x00ffffff)
+ | (packageId << 24);
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ }
+ {% endif %}
+}
+""", trim_blocks=True, lstrip_blocks=True)
+
+ output = template.render(package=package, resources=resources,
+ shared_resources=shared_resources)
+ with open(r_java_path, 'w') as f:
+ f.write(output)
+
+
+def CrunchDirectory(aapt, input_dir, output_dir):
+ """Crunches the images in input_dir and its subdirectories into output_dir.
+
+ If an image is already optimized, crunching often increases image size. In
+ this case, the crunched image is overwritten with the original image.
+ """
+ aapt_cmd = [aapt,
+ 'crunch',
+ '-C', output_dir,
+ '-S', input_dir,
+ '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
+ build_utils.CheckOutput(aapt_cmd, stderr_filter=FilterCrunchStderr,
+ fail_func=DidCrunchFail)
+
+ # Check for images whose size increased during crunching and replace them
+ # with their originals (except for 9-patches, which must be crunched).
+ for dir_, _, files in os.walk(output_dir):
+ for crunched in files:
+ if crunched.endswith('.9.png'):
+ continue
+ if not crunched.endswith('.png'):
+ raise Exception('Unexpected file in crunched dir: ' + crunched)
+ crunched = os.path.join(dir_, crunched)
+ original = os.path.join(input_dir, os.path.relpath(crunched, output_dir))
+ original_size = os.path.getsize(original)
+ crunched_size = os.path.getsize(crunched)
+ if original_size < crunched_size:
+ shutil.copyfile(original, crunched)
+
+
+def FilterCrunchStderr(stderr):
+ """Filters out lines from aapt crunch's stderr that can safely be ignored."""
+ filtered_lines = []
+ for line in stderr.splitlines(True):
+ # Ignore this libpng warning, which is a known non-error condition.
+ # http://crbug.com/364355
+ if ('libpng warning: iCCP: Not recognizing known sRGB profile that has '
+ + 'been edited' in line):
+ continue
+ filtered_lines.append(line)
+ return ''.join(filtered_lines)
+
+
+def DidCrunchFail(returncode, stderr):
+ """Determines whether aapt crunch failed from its return code and output.
+
+ Because aapt's return code cannot be trusted, any output to stderr is
+ an indication that aapt has failed (http://crbug.com/314885).
+ """
+ return returncode != 0 or stderr
+
+
+def ZipResources(resource_dirs, zip_path):
+ # Python zipfile does not provide a way to replace a file (it just writes
+ # another file with the same name). So, first collect all the files to put
+ # in the zip (with proper overriding), and then zip them.
+ files_to_zip = dict()
+ for d in resource_dirs:
+ for root, _, files in os.walk(d):
+ for f in files:
+ archive_path = os.path.join(os.path.relpath(root, d), f)
+ path = os.path.join(root, f)
+ files_to_zip[archive_path] = path
+ with zipfile.ZipFile(zip_path, 'w') as outzip:
+ for archive_path, path in files_to_zip.iteritems():
+ outzip.write(path, archive_path)
+
+
+def CombineZips(zip_files, output_path):
+ # When packaging resources, if the top-level directories in the zip file are
+ # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a
+ # resources directory. While some resources just clobber others (image files,
+ # etc), other resources (particularly .xml files) need to be more
+ # intelligently merged. That merging is left up to aapt.
+ with zipfile.ZipFile(output_path, 'w') as outzip:
+ for i, z in enumerate(zip_files):
+ with zipfile.ZipFile(z, 'r') as inzip:
+ for name in inzip.namelist():
+ new_name = '%d/%s' % (i, name)
+ outzip.writestr(new_name, inzip.read(name))
+
+
+def main():
+ args = build_utils.ExpandFileArgs(sys.argv[1:])
+
+ options = ParseArgs(args)
+ android_jar = os.path.join(options.android_sdk, 'android.jar')
+ aapt = options.aapt_path
+
+ input_files = []
+
+ with build_utils.TempDir() as temp_dir:
+ deps_dir = os.path.join(temp_dir, 'deps')
+ build_utils.MakeDirectory(deps_dir)
+ v14_dir = os.path.join(temp_dir, 'v14')
+ build_utils.MakeDirectory(v14_dir)
+
+ gen_dir = os.path.join(temp_dir, 'gen')
+ build_utils.MakeDirectory(gen_dir)
+
+ input_resource_dirs = build_utils.ParseGypList(options.resource_dirs)
+
+ if not options.v14_skip:
+ for resource_dir in input_resource_dirs:
+ generate_v14_compatible_resources.GenerateV14Resources(
+ resource_dir,
+ v14_dir)
+
+ dep_zips = build_utils.ParseGypList(options.dependencies_res_zips)
+ input_files += dep_zips
+ dep_subdirs = []
+ for z in dep_zips:
+ subdir = os.path.join(deps_dir, os.path.basename(z))
+ if os.path.exists(subdir):
+ raise Exception('Resource zip name conflict: ' + os.path.basename(z))
+ build_utils.ExtractAll(z, path=subdir)
+ dep_subdirs.append(subdir)
+
+ # Generate R.java. This R.java contains non-final constants and is used only
+ # while compiling the library jar (e.g. chromium_content.jar). When building
+ # an apk, a new R.java file with the correct resource -> ID mappings will be
+ # generated by merging the resources from all libraries and the main apk
+ # project.
+ package_command = [aapt,
+ 'package',
+ '-m',
+ '-M', options.android_manifest,
+ '--auto-add-overlay',
+ '-I', android_jar,
+ '--output-text-symbols', gen_dir,
+ '-J', gen_dir,
+ '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
+
+ for d in input_resource_dirs:
+ package_command += ['-S', d]
+
+ for d in dep_subdirs:
+ package_command += ['-S', d]
+
+ if options.non_constant_id:
+ package_command.append('--non-constant-id')
+ if options.custom_package:
+ package_command += ['--custom-package', options.custom_package]
+ if options.proguard_file:
+ package_command += ['-G', options.proguard_file]
+ if options.shared_resources:
+ package_command.append('--shared-lib')
+ build_utils.CheckOutput(package_command, print_stderr=False)
+
+ if options.extra_res_packages:
+ CreateExtraRJavaFiles(
+ gen_dir,
+ build_utils.ParseGypList(options.extra_res_packages),
+ build_utils.ParseGypList(options.extra_r_text_files),
+ options.shared_resources,
+ options.include_all_resources)
+
+ # This is the list of directories with resources to put in the final .zip
+ # file. The order of these is important so that crunched/v14 resources
+ # override the normal ones.
+ zip_resource_dirs = input_resource_dirs + [v14_dir]
+
+ base_crunch_dir = os.path.join(temp_dir, 'crunch')
+
+ # Crunch image resources. This shrinks png files and is necessary for
+ # 9-patch images to display correctly. 'aapt crunch' accepts only a single
+ # directory at a time and deletes everything in the output directory.
+ for idx, input_dir in enumerate(input_resource_dirs):
+ crunch_dir = os.path.join(base_crunch_dir, str(idx))
+ build_utils.MakeDirectory(crunch_dir)
+ zip_resource_dirs.append(crunch_dir)
+ CrunchDirectory(aapt, input_dir, crunch_dir)
+
+ ZipResources(zip_resource_dirs, options.resource_zip_out)
+
+ if options.all_resources_zip_out:
+ CombineZips([options.resource_zip_out] + dep_zips,
+ options.all_resources_zip_out)
+
+ if options.R_dir:
+ build_utils.DeleteDirectory(options.R_dir)
+ shutil.copytree(gen_dir, options.R_dir)
+ else:
+ build_utils.ZipDir(options.srcjar_out, gen_dir)
+
+ if options.r_text_out:
+ r_text_path = os.path.join(gen_dir, 'R.txt')
+ if os.path.exists(r_text_path):
+ shutil.copyfile(r_text_path, options.r_text_out)
+ else:
+ open(options.r_text_out, 'w').close()
+
+ if options.depfile:
+ input_files += build_utils.GetPythonDependencies()
+ build_utils.WriteDepfile(options.depfile, input_files)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
new file mode 100755
index 00000000000..5127100a890
--- /dev/null
+++ b/build/android/gyp/proguard.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import optparse
+import sys
+
+from util import build_utils
+from util import proguard_util
+
+def DoProguard(options):
+ proguard = proguard_util.ProguardCmdBuilder(options.proguard_path)
+ proguard.injars(build_utils.ParseGypList(options.input_paths))
+ proguard.configs(build_utils.ParseGypList(options.proguard_configs))
+ proguard.outjar(options.output_path)
+
+ if options.mapping:
+ proguard.mapping(options.mapping)
+
+ if options.is_test:
+ proguard.is_test(True)
+
+ classpath = []
+ for arg in options.classpath:
+ classpath += build_utils.ParseGypList(arg)
+ classpath = list(set(classpath))
+ proguard.libraryjars(classpath)
+
+ proguard.CheckOutput()
+
+ return proguard.GetInputs()
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--proguard-path',
+ help='Path to the proguard executable.')
+ parser.add_option('--input-paths',
+ help='Paths to the .jar files proguard should run on.')
+ parser.add_option('--output-path', help='Path to the generated .jar file.')
+ parser.add_option('--proguard-configs',
+ help='Paths to proguard configuration files.')
+ parser.add_option('--mapping', help='Path to proguard mapping to apply.')
+ parser.add_option('--is-test', action='store_true',
+ help='If true, extra proguard options for instrumentation tests will be '
+ 'added.')
+ parser.add_option('--classpath', action='append',
+ help='Classpath for proguard.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, _ = parser.parse_args(args)
+
+ inputs = DoProguard(options)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ inputs + build_utils.GetPythonDependencies())
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/push_libraries.py b/build/android/gyp/push_libraries.py
new file mode 100755
index 00000000000..6b31a2e19d6
--- /dev/null
+++ b/build/android/gyp/push_libraries.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Pushes native libraries to a device.
+
+"""
+
+import optparse
+import os
+import sys
+
+BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), os.pardir)
+sys.path.append(BUILD_ANDROID_DIR)
+
+from pylib import constants
+
+from util import build_device
+from util import build_utils
+from util import md5_check
+
+def DoPush(options):
+ libraries = build_utils.ParseGypList(options.libraries)
+
+ device = build_device.GetBuildDeviceFromPath(
+ options.build_device_configuration)
+ if not device:
+ return
+
+ serial_number = device.GetSerialNumber()
+ # A list so that it is modifiable in Push below.
+ needs_directory = [True]
+ for lib in libraries:
+ device_path = os.path.join(options.device_dir, lib)
+ host_path = os.path.join(options.libraries_dir, lib)
+
+ def Push():
+ if needs_directory:
+ device.RunShellCommand('mkdir -p ' + options.device_dir)
+ needs_directory[:] = [] # = False
+ device.PushChangedFiles([(host_path, device_path)])
+
+ record_path = '%s.%s.push.md5.stamp' % (host_path, serial_number)
+ md5_check.CallAndRecordIfStale(
+ Push,
+ record_path=record_path,
+ input_paths=[host_path],
+ input_strings=[device_path])
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+ parser = optparse.OptionParser()
+ parser.add_option('--libraries-dir',
+ help='Directory that contains stripped libraries.')
+ parser.add_option('--device-dir',
+ help='Device directory to push the libraries to.')
+ parser.add_option('--libraries',
+ help='List of native libraries.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--build-device-configuration',
+ help='Path to build device configuration.')
+ parser.add_option('--configuration-name',
+ help='The build CONFIGURATION_NAME')
+ options, _ = parser.parse_args(args)
+
+ required_options = ['libraries', 'device_dir', 'libraries']
+ build_utils.CheckOptions(options, parser, required=required_options)
+ constants.SetBuildType(options.configuration_name)
+
+ DoPush(options)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/strip_library_for_device.py b/build/android/gyp/strip_library_for_device.py
new file mode 100755
index 00000000000..9e2daae33ac
--- /dev/null
+++ b/build/android/gyp/strip_library_for_device.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+import optparse
+import os
+import sys
+
+from util import build_utils
+
+
+def StripLibrary(android_strip, android_strip_args, library_path, output_path):
+ if build_utils.IsTimeStale(output_path, [library_path]):
+ strip_cmd = ([android_strip] +
+ android_strip_args +
+ ['-o', output_path, library_path])
+ build_utils.CheckOutput(strip_cmd)
+
+
+def main(args):
+ args = build_utils.ExpandFileArgs(args)
+
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--android-strip',
+ help='Path to the toolchain\'s strip binary')
+ parser.add_option('--android-strip-arg', action='append',
+ help='Argument to be passed to strip')
+ parser.add_option('--libraries-dir',
+ help='Directory for un-stripped libraries')
+ parser.add_option('--stripped-libraries-dir',
+ help='Directory for stripped libraries')
+ parser.add_option('--libraries',
+ help='List of libraries to strip')
+ parser.add_option('--stamp', help='Path to touch on success')
+
+ options, _ = parser.parse_args(args)
+
+ libraries = build_utils.ParseGypList(options.libraries)
+
+ build_utils.MakeDirectory(options.stripped_libraries_dir)
+
+ for library in libraries:
+ for base_path in options.libraries_dir.split(','):
+ library_path = os.path.join(base_path, library)
+ if (os.path.exists(library_path)):
+ break
+ stripped_library_path = os.path.join(
+ options.stripped_libraries_dir, library)
+ StripLibrary(options.android_strip, options.android_strip_arg, library_path,
+ stripped_library_path)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/test/BUILD.gn b/build/android/gyp/test/BUILD.gn
new file mode 100644
index 00000000000..2deac1d56f2
--- /dev/null
+++ b/build/android/gyp/test/BUILD.gn
@@ -0,0 +1,13 @@
+import("//build/config/android/rules.gni")
+
+java_library("hello_world_java") {
+ java_files = [ "java/org/chromium/helloworld/HelloWorldPrinter.java" ]
+}
+
+java_binary("hello_world") {
+ deps = [
+ ":hello_world_java",
+ ]
+ java_files = [ "java/org/chromium/helloworld/HelloWorldMain.java" ]
+ main_class = "org.chromium.helloworld.HelloWorldMain"
+}
diff --git a/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java b/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java
new file mode 100644
index 00000000000..10860d8332d
--- /dev/null
+++ b/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java
@@ -0,0 +1,15 @@
+// Copyright 2014 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.
+
+package org.chromium.helloworld;
+
+public class HelloWorldMain {
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ System.exit(Integer.parseInt(args[0]));
+ }
+ HelloWorldPrinter.print();
+ }
+}
+
diff --git a/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java b/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java
new file mode 100644
index 00000000000..b09673e21f4
--- /dev/null
+++ b/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java
@@ -0,0 +1,12 @@
+// Copyright 2014 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.
+
+package org.chromium.helloworld;
+
+public class HelloWorldPrinter {
+ public static void print() {
+ System.out.println("Hello, world!");
+ }
+}
+
diff --git a/build/android/gyp/touch.py b/build/android/gyp/touch.py
new file mode 100755
index 00000000000..7b4375e40ab
--- /dev/null
+++ b/build/android/gyp/touch.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+import sys
+
+from util import build_utils
+
+def main(argv):
+ for f in argv[1:]:
+ build_utils.Touch(f)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/gyp/util/__init__.py b/build/android/gyp/util/__init__.py
new file mode 100644
index 00000000000..727e987e6b6
--- /dev/null
+++ b/build/android/gyp/util/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2012 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.
+
diff --git a/build/android/gyp/util/build_device.py b/build/android/gyp/util/build_device.py
new file mode 100644
index 00000000000..8ab11124f74
--- /dev/null
+++ b/build/android/gyp/util/build_device.py
@@ -0,0 +1,108 @@
+# Copyright 2013 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.
+
+""" A simple device interface for build steps.
+
+"""
+
+import logging
+import os
+import re
+import sys
+
+from util import build_utils
+
+BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..', '..')
+sys.path.append(BUILD_ANDROID_DIR)
+
+from pylib import android_commands
+from pylib.device import device_errors
+from pylib.device import device_utils
+
+GetAttachedDevices = android_commands.GetAttachedDevices
+
+
+class BuildDevice(object):
+ def __init__(self, configuration):
+ self.id = configuration['id']
+ self.description = configuration['description']
+ self.install_metadata = configuration['install_metadata']
+ self.device = device_utils.DeviceUtils(self.id)
+
+ def RunShellCommand(self, *args, **kwargs):
+ return self.device.RunShellCommand(*args, **kwargs)
+
+ def PushChangedFiles(self, *args, **kwargs):
+ return self.device.PushChangedFiles(*args, **kwargs)
+
+ def GetSerialNumber(self):
+ return self.id
+
+ def Install(self, *args, **kwargs):
+ return self.device.Install(*args, **kwargs)
+
+ def InstallSplitApk(self, *args, **kwargs):
+ return self.device.InstallSplitApk(*args, **kwargs)
+
+ def GetInstallMetadata(self, apk_package):
+ """Gets the metadata on the device for the apk_package apk."""
+ # Matches lines like:
+ # -rw-r--r-- system system 7376582 2013-04-19 16:34 \
+ # org.chromium.chrome.shell.apk
+ # -rw-r--r-- system system 7376582 2013-04-19 16:34 \
+ # org.chromium.chrome.shell-1.apk
+ apk_matcher = lambda s: re.match('.*%s(-[0-9]*)?.apk$' % apk_package, s)
+ matches = filter(apk_matcher, self.install_metadata)
+ return matches[0] if matches else None
+
+
+def GetConfigurationForDevice(device_id):
+ device = device_utils.DeviceUtils(device_id)
+ configuration = None
+ has_root = False
+ is_online = device.IsOnline()
+ if is_online:
+ cmd = 'ls -l /data/app; getprop ro.build.description'
+ cmd_output = device.RunShellCommand(cmd)
+ has_root = not 'Permission denied' in cmd_output[0]
+ if not has_root:
+ # Disable warning log messages from EnableRoot()
+ logging.getLogger().disabled = True
+ try:
+ device.EnableRoot()
+ has_root = True
+ except device_errors.CommandFailedError:
+ has_root = False
+ finally:
+ logging.getLogger().disabled = False
+ cmd_output = device.RunShellCommand(cmd)
+
+ configuration = {
+ 'id': device_id,
+ 'description': cmd_output[-1],
+ 'install_metadata': cmd_output[:-1],
+ }
+ return configuration, is_online, has_root
+
+
+def WriteConfigurations(configurations, path):
+ # Currently we only support installing to the first device.
+ build_utils.WriteJson(configurations[:1], path, only_if_changed=True)
+
+
+def ReadConfigurations(path):
+ return build_utils.ReadJson(path)
+
+
+def GetBuildDevice(configurations):
+ assert len(configurations) == 1
+ return BuildDevice(configurations[0])
+
+
+def GetBuildDeviceFromPath(path):
+ configurations = ReadConfigurations(path)
+ if len(configurations) > 0:
+ return GetBuildDevice(ReadConfigurations(path))
+ return None
+
diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py
new file mode 100644
index 00000000000..65b1a643c26
--- /dev/null
+++ b/build/android/gyp/util/build_utils.py
@@ -0,0 +1,376 @@
+# Copyright 2013 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.
+
+import ast
+import contextlib
+import fnmatch
+import json
+import os
+import pipes
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tempfile
+import zipfile
+
+
+CHROMIUM_SRC = os.path.normpath(
+ os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, os.pardir, os.pardir))
+COLORAMA_ROOT = os.path.join(CHROMIUM_SRC,
+ 'third_party', 'colorama', 'src')
+# aapt should ignore OWNERS files in addition the default ignore pattern.
+AAPT_IGNORE_PATTERN = ('!OWNERS:!.svn:!.git:!.ds_store:!*.scc:.*:_*:' +
+ '!CVS:!thumbs.db:!picasa.ini:!*~:!*.d.stamp')
+
+
+@contextlib.contextmanager
+def TempDir():
+ dirname = tempfile.mkdtemp()
+ try:
+ yield dirname
+ finally:
+ shutil.rmtree(dirname)
+
+
+def MakeDirectory(dir_path):
+ try:
+ os.makedirs(dir_path)
+ except OSError:
+ pass
+
+
+def DeleteDirectory(dir_path):
+ if os.path.exists(dir_path):
+ shutil.rmtree(dir_path)
+
+
+def Touch(path, fail_if_missing=False):
+ if fail_if_missing and not os.path.exists(path):
+ raise Exception(path + ' doesn\'t exist.')
+
+ MakeDirectory(os.path.dirname(path))
+ with open(path, 'a'):
+ os.utime(path, None)
+
+
+def FindInDirectory(directory, filename_filter):
+ files = []
+ for root, _dirnames, filenames in os.walk(directory):
+ matched_files = fnmatch.filter(filenames, filename_filter)
+ files.extend((os.path.join(root, f) for f in matched_files))
+ return files
+
+
+def FindInDirectories(directories, filename_filter):
+ all_files = []
+ for directory in directories:
+ all_files.extend(FindInDirectory(directory, filename_filter))
+ return all_files
+
+
+def ParseGnList(gn_string):
+ return ast.literal_eval(gn_string)
+
+
+def ParseGypList(gyp_string):
+ # The ninja generator doesn't support $ in strings, so use ## to
+ # represent $.
+ # TODO(cjhopman): Remove when
+ # https://code.google.com/p/gyp/issues/detail?id=327
+ # is addressed.
+ gyp_string = gyp_string.replace('##', '$')
+
+ if gyp_string.startswith('['):
+ return ParseGnList(gyp_string)
+ return shlex.split(gyp_string)
+
+
+def CheckOptions(options, parser, required=None):
+ if not required:
+ return
+ for option_name in required:
+ if getattr(options, option_name) is None:
+ parser.error('--%s is required' % option_name.replace('_', '-'))
+
+
+def WriteJson(obj, path, only_if_changed=False):
+ old_dump = None
+ if os.path.exists(path):
+ with open(path, 'r') as oldfile:
+ old_dump = oldfile.read()
+
+ new_dump = json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '))
+
+ if not only_if_changed or old_dump != new_dump:
+ with open(path, 'w') as outfile:
+ outfile.write(new_dump)
+
+
+def ReadJson(path):
+ with open(path, 'r') as jsonfile:
+ return json.load(jsonfile)
+
+
+class CalledProcessError(Exception):
+ """This exception is raised when the process run by CheckOutput
+ exits with a non-zero exit code."""
+
+ def __init__(self, cwd, args, output):
+ super(CalledProcessError, self).__init__()
+ self.cwd = cwd
+ self.args = args
+ self.output = output
+
+ def __str__(self):
+ # A user should be able to simply copy and paste the command that failed
+ # into their shell.
+ copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd),
+ ' '.join(map(pipes.quote, self.args)))
+ return 'Command failed: {}\n{}'.format(copyable_command, self.output)
+
+
+# This can be used in most cases like subprocess.check_output(). The output,
+# particularly when the command fails, better highlights the command's failure.
+# If the command fails, raises a build_utils.CalledProcessError.
+def CheckOutput(args, cwd=None,
+ print_stdout=False, print_stderr=True,
+ stdout_filter=None,
+ stderr_filter=None,
+ fail_func=lambda returncode, stderr: returncode != 0):
+ if not cwd:
+ cwd = os.getcwd()
+
+ child = subprocess.Popen(args,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
+ stdout, stderr = child.communicate()
+
+ if stdout_filter is not None:
+ stdout = stdout_filter(stdout)
+
+ if stderr_filter is not None:
+ stderr = stderr_filter(stderr)
+
+ if fail_func(child.returncode, stderr):
+ raise CalledProcessError(cwd, args, stdout + stderr)
+
+ if print_stdout:
+ sys.stdout.write(stdout)
+ if print_stderr:
+ sys.stderr.write(stderr)
+
+ return stdout
+
+
+def GetModifiedTime(path):
+ # For a symlink, the modified time should be the greater of the link's
+ # modified time and the modified time of the target.
+ return max(os.lstat(path).st_mtime, os.stat(path).st_mtime)
+
+
+def IsTimeStale(output, inputs):
+ if not os.path.exists(output):
+ return True
+
+ output_time = GetModifiedTime(output)
+ for i in inputs:
+ if GetModifiedTime(i) > output_time:
+ return True
+ return False
+
+
+def IsDeviceReady():
+ device_state = CheckOutput(['adb', 'get-state'])
+ return device_state.strip() == 'device'
+
+
+def CheckZipPath(name):
+ if os.path.normpath(name) != name:
+ raise Exception('Non-canonical zip path: %s' % name)
+ if os.path.isabs(name):
+ raise Exception('Absolute zip path: %s' % name)
+
+
+def ExtractAll(zip_path, path=None, no_clobber=True, pattern=None):
+ if path is None:
+ path = os.getcwd()
+ elif not os.path.exists(path):
+ MakeDirectory(path)
+
+ with zipfile.ZipFile(zip_path) as z:
+ for name in z.namelist():
+ if name.endswith('/'):
+ continue
+ if pattern is not None:
+ if not fnmatch.fnmatch(name, pattern):
+ continue
+ CheckZipPath(name)
+ if no_clobber:
+ output_path = os.path.join(path, name)
+ if os.path.exists(output_path):
+ raise Exception(
+ 'Path already exists from zip: %s %s %s'
+ % (zip_path, name, output_path))
+
+ z.extractall(path=path)
+
+
+def DoZip(inputs, output, base_dir):
+ with zipfile.ZipFile(output, 'w') as outfile:
+ for f in inputs:
+ CheckZipPath(os.path.relpath(f, base_dir))
+ outfile.write(f, os.path.relpath(f, base_dir))
+
+
+def ZipDir(output, base_dir):
+ with zipfile.ZipFile(output, 'w') as outfile:
+ for root, _, files in os.walk(base_dir):
+ for f in files:
+ path = os.path.join(root, f)
+ archive_path = os.path.relpath(path, base_dir)
+ CheckZipPath(archive_path)
+ outfile.write(path, archive_path)
+
+
+def MergeZips(output, inputs, exclude_patterns=None):
+ added_names = set()
+ def Allow(name):
+ if exclude_patterns is not None:
+ for p in exclude_patterns:
+ if fnmatch.fnmatch(name, p):
+ return False
+ return True
+
+ with zipfile.ZipFile(output, 'w') as out_zip:
+ for in_file in inputs:
+ with zipfile.ZipFile(in_file, 'r') as in_zip:
+ for name in in_zip.namelist():
+ if name not in added_names and Allow(name):
+ out_zip.writestr(name, in_zip.read(name))
+ added_names.add(name)
+
+
+def PrintWarning(message):
+ print 'WARNING: ' + message
+
+
+def PrintBigWarning(message):
+ print '***** ' * 8
+ PrintWarning(message)
+ print '***** ' * 8
+
+
+def GetSortedTransitiveDependencies(top, deps_func):
+ """Gets the list of all transitive dependencies in sorted order.
+
+ There should be no cycles in the dependency graph.
+
+ Args:
+ top: a list of the top level nodes
+ deps_func: A function that takes a node and returns its direct dependencies.
+ Returns:
+ A list of all transitive dependencies of nodes in top, in order (a node will
+ appear in the list at a higher index than all of its dependencies).
+ """
+ def Node(dep):
+ return (dep, deps_func(dep))
+
+ # First: find all deps
+ unchecked_deps = list(top)
+ all_deps = set(top)
+ while unchecked_deps:
+ dep = unchecked_deps.pop()
+ new_deps = deps_func(dep).difference(all_deps)
+ unchecked_deps.extend(new_deps)
+ all_deps = all_deps.union(new_deps)
+
+ # Then: simple, slow topological sort.
+ sorted_deps = []
+ unsorted_deps = dict(map(Node, all_deps))
+ while unsorted_deps:
+ for library, dependencies in unsorted_deps.items():
+ if not dependencies.intersection(unsorted_deps.keys()):
+ sorted_deps.append(library)
+ del unsorted_deps[library]
+
+ return sorted_deps
+
+
+def GetPythonDependencies():
+ """Gets the paths of imported non-system python modules.
+
+ A path is assumed to be a "system" import if it is outside of chromium's
+ src/. The paths will be relative to the current directory.
+ """
+ module_paths = (m.__file__ for m in sys.modules.itervalues()
+ if m is not None and hasattr(m, '__file__'))
+
+ abs_module_paths = map(os.path.abspath, module_paths)
+
+ non_system_module_paths = [
+ p for p in abs_module_paths if p.startswith(CHROMIUM_SRC)]
+ def ConvertPycToPy(s):
+ if s.endswith('.pyc'):
+ return s[:-1]
+ return s
+
+ non_system_module_paths = map(ConvertPycToPy, non_system_module_paths)
+ non_system_module_paths = map(os.path.relpath, non_system_module_paths)
+ return sorted(set(non_system_module_paths))
+
+
+def AddDepfileOption(parser):
+ parser.add_option('--depfile',
+ help='Path to depfile. This must be specified as the '
+ 'action\'s first output.')
+
+
+def WriteDepfile(path, dependencies):
+ with open(path, 'w') as depfile:
+ depfile.write(path)
+ depfile.write(': ')
+ depfile.write(' '.join(dependencies))
+ depfile.write('\n')
+
+
+def ExpandFileArgs(args):
+ """Replaces file-arg placeholders in args.
+
+ These placeholders have the form:
+ @FileArg(filename:key1:key2:...:keyn)
+
+ The value of such a placeholder is calculated by reading 'filename' as json.
+ And then extracting the value at [key1][key2]...[keyn].
+
+ Note: This intentionally does not return the list of files that appear in such
+ placeholders. An action that uses file-args *must* know the paths of those
+ files prior to the parsing of the arguments (typically by explicitly listing
+ them in the action's inputs in build files).
+ """
+ new_args = list(args)
+ file_jsons = dict()
+ r = re.compile('@FileArg\((.*?)\)')
+ for i, arg in enumerate(args):
+ match = r.search(arg)
+ if not match:
+ continue
+
+ if match.end() != len(arg):
+ raise Exception('Unexpected characters after FileArg: ' + arg)
+
+ lookup_path = match.group(1).split(':')
+ file_path = lookup_path[0]
+ if not file_path in file_jsons:
+ file_jsons[file_path] = ReadJson(file_path)
+
+ expansion = file_jsons[file_path]
+ for k in lookup_path[1:]:
+ expansion = expansion[k]
+
+ new_args[i] = arg[:match.start()] + str(expansion)
+
+ return new_args
+
diff --git a/build/android/gyp/util/md5_check.py b/build/android/gyp/util/md5_check.py
new file mode 100644
index 00000000000..9f365aa0816
--- /dev/null
+++ b/build/android/gyp/util/md5_check.py
@@ -0,0 +1,86 @@
+# Copyright 2013 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.
+
+import hashlib
+import os
+
+
+def CallAndRecordIfStale(
+ function, record_path=None, input_paths=None, input_strings=None,
+ force=False):
+ """Calls function if the md5sum of the input paths/strings has changed.
+
+ The md5sum of the inputs is compared with the one stored in record_path. If
+ this has changed (or the record doesn't exist), function will be called and
+ the new md5sum will be recorded.
+
+ If force is True, the function will be called regardless of whether the
+ md5sum is out of date.
+ """
+ if not input_paths:
+ input_paths = []
+ if not input_strings:
+ input_strings = []
+ md5_checker = _Md5Checker(
+ record_path=record_path,
+ input_paths=input_paths,
+ input_strings=input_strings)
+ if force or md5_checker.IsStale():
+ function()
+ md5_checker.Write()
+
+
+def _UpdateMd5ForFile(md5, path, block_size=2**16):
+ with open(path, 'rb') as infile:
+ while True:
+ data = infile.read(block_size)
+ if not data:
+ break
+ md5.update(data)
+
+
+def _UpdateMd5ForDirectory(md5, dir_path):
+ for root, _, files in os.walk(dir_path):
+ for f in files:
+ _UpdateMd5ForFile(md5, os.path.join(root, f))
+
+
+def _UpdateMd5ForPath(md5, path):
+ if os.path.isdir(path):
+ _UpdateMd5ForDirectory(md5, path)
+ else:
+ _UpdateMd5ForFile(md5, path)
+
+
+class _Md5Checker(object):
+ def __init__(self, record_path=None, input_paths=None, input_strings=None):
+ if not input_paths:
+ input_paths = []
+ if not input_strings:
+ input_strings = []
+
+ assert record_path.endswith('.stamp'), (
+ 'record paths must end in \'.stamp\' so that they are easy to find '
+ 'and delete')
+
+ self.record_path = record_path
+
+ md5 = hashlib.md5()
+ for i in sorted(input_paths):
+ _UpdateMd5ForPath(md5, i)
+ for s in input_strings:
+ md5.update(s)
+ self.new_digest = md5.hexdigest()
+
+ self.old_digest = ''
+ if os.path.exists(self.record_path):
+ with open(self.record_path, 'r') as old_record:
+ self.old_digest = old_record.read()
+
+ def IsStale(self):
+ return self.old_digest != self.new_digest
+
+ def Write(self):
+ with open(self.record_path, 'w') as new_record:
+ new_record.write(self.new_digest)
diff --git a/build/android/gyp/util/md5_check_test.py b/build/android/gyp/util/md5_check_test.py
new file mode 100644
index 00000000000..4f89fc2be83
--- /dev/null
+++ b/build/android/gyp/util/md5_check_test.py
@@ -0,0 +1,72 @@
+# Copyright 2013 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.
+
+import tempfile
+import unittest
+
+import md5_check # pylint: disable=W0403
+
+
+class TestMd5Check(unittest.TestCase):
+ def setUp(self):
+ self.called = False
+
+ def testCallAndRecordIfStale(self):
+ input_strings = ['string1', 'string2']
+ input_file1 = tempfile.NamedTemporaryFile()
+ input_file2 = tempfile.NamedTemporaryFile()
+ file1_contents = 'input file 1'
+ file2_contents = 'input file 2'
+ input_file1.write(file1_contents)
+ input_file1.flush()
+ input_file2.write(file2_contents)
+ input_file2.flush()
+ input_files = [input_file1.name, input_file2.name]
+
+ record_path = tempfile.NamedTemporaryFile(suffix='.stamp')
+
+ def CheckCallAndRecord(should_call, message, force=False):
+ self.called = False
+ def MarkCalled():
+ self.called = True
+ md5_check.CallAndRecordIfStale(
+ MarkCalled,
+ record_path=record_path.name,
+ input_paths=input_files,
+ input_strings=input_strings,
+ force=force)
+ self.failUnlessEqual(should_call, self.called, message)
+
+ CheckCallAndRecord(True, 'should call when record doesn\'t exist')
+ CheckCallAndRecord(False, 'should not call when nothing changed')
+ CheckCallAndRecord(True, force=True, message='should call when forced')
+
+ input_file1.write('some more input')
+ input_file1.flush()
+ CheckCallAndRecord(True, 'changed input file should trigger call')
+
+ input_files = input_files[::-1]
+ CheckCallAndRecord(False, 'reordering of inputs shouldn\'t trigger call')
+
+ input_files = input_files[:1]
+ CheckCallAndRecord(True, 'removing file should trigger call')
+
+ input_files.append(input_file2.name)
+ CheckCallAndRecord(True, 'added input file should trigger call')
+
+ input_strings[0] = input_strings[0] + ' a bit longer'
+ CheckCallAndRecord(True, 'changed input string should trigger call')
+
+ input_strings = input_strings[::-1]
+ CheckCallAndRecord(True, 'reordering of string inputs should trigger call')
+
+ input_strings = input_strings[:1]
+ CheckCallAndRecord(True, 'removing a string should trigger call')
+
+ input_strings.append('a brand new string')
+ CheckCallAndRecord(True, 'added input string should trigger call')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/gyp/util/proguard_util.py b/build/android/gyp/util/proguard_util.py
new file mode 100644
index 00000000000..901cd9f2a8f
--- /dev/null
+++ b/build/android/gyp/util/proguard_util.py
@@ -0,0 +1,128 @@
+# Copyright 2015 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.
+
+import os
+from util import build_utils
+
+def FilterProguardOutput(output):
+ '''ProGuard outputs boring stuff to stdout (proguard version, jar path, etc)
+ as well as interesting stuff (notes, warnings, etc). If stdout is entirely
+ boring, this method suppresses the output.
+ '''
+ ignore_patterns = [
+ 'ProGuard, version ',
+ 'Reading program jar [',
+ 'Reading library jar [',
+ 'Preparing output jar [',
+ ' Copying resources from program jar [',
+ ]
+ for line in output.splitlines():
+ for pattern in ignore_patterns:
+ if line.startswith(pattern):
+ break
+ else:
+ # line doesn't match any of the patterns; it's probably something worth
+ # printing out.
+ return output
+ return ''
+
+
+class ProguardCmdBuilder(object):
+ def __init__(self, proguard_jar):
+ assert os.path.exists(proguard_jar)
+ self._proguard_jar_path = proguard_jar
+ self._test = None
+ self._mapping = None
+ self._libraries = None
+ self._injars = None
+ self._configs = None
+ self._outjar = None
+
+ def outjar(self, path):
+ assert self._outjar is None
+ self._outjar = path
+
+ def is_test(self, enable):
+ assert self._test is None
+ self._test = enable
+
+ def mapping(self, path):
+ assert self._mapping is None
+ assert os.path.exists(path), path
+ self._mapping = path
+
+ def libraryjars(self, paths):
+ assert self._libraries is None
+ for p in paths:
+ assert os.path.exists(p), p
+ self._libraries = paths
+
+ def injars(self, paths):
+ assert self._injars is None
+ for p in paths:
+ assert os.path.exists(p), p
+ self._injars = paths
+
+ def configs(self, paths):
+ assert self._configs is None
+ for p in paths:
+ assert os.path.exists(p), p
+ self._configs = paths
+
+ def build(self):
+ assert self._injars is not None
+ assert self._outjar is not None
+ assert self._configs is not None
+ cmd = [
+ 'java', '-jar', self._proguard_jar_path,
+ '-forceprocessing',
+ ]
+ if self._test:
+ cmd += [
+ '-dontobfuscate',
+ '-dontoptimize',
+ '-dontshrink',
+ '-dontskipnonpubliclibraryclassmembers',
+ ]
+
+ if self._mapping:
+ cmd += [
+ '-applymapping', self._mapping,
+ ]
+
+ if self._libraries:
+ cmd += [
+ '-libraryjars', ':'.join(self._libraries),
+ ]
+
+ cmd += [
+ '-injars', ':'.join(self._injars)
+ ]
+
+ for config_file in self._configs:
+ cmd += ['-include', config_file]
+
+ # The output jar must be specified after inputs.
+ cmd += [
+ '-outjars', self._outjar,
+ '-dump', self._outjar + '.dump',
+ '-printseeds', self._outjar + '.seeds',
+ '-printusage', self._outjar + '.usage',
+ '-printmapping', self._outjar + '.mapping',
+ ]
+ return cmd
+
+ def GetInputs(self):
+ inputs = [self._proguard_jar_path] + self._configs + self._injars
+ if self._mapping:
+ inputs.append(self._mapping)
+ if self._libraries:
+ inputs += self._libraries
+ return inputs
+
+
+ def CheckOutput(self):
+ build_utils.CheckOutput(self.build(), print_stdout=True,
+ stdout_filter=FilterProguardOutput)
+
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
new file mode 100755
index 00000000000..3773e98bc70
--- /dev/null
+++ b/build/android/gyp/write_build_config.py
@@ -0,0 +1,357 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Writes a build_config file.
+
+The build_config file for a target is a json file containing information about
+how to build that target based on the target's dependencies. This includes
+things like: the javac classpath, the list of android resources dependencies,
+etc. It also includes the information needed to create the build_config for
+other targets that depend on that one.
+
+Android build scripts should not refer to the build_config directly, and the
+build specification should instead pass information in using the special
+file-arg syntax (see build_utils.py:ExpandFileArgs). That syntax allows passing
+of values in a json dict in a file and looks like this:
+ --python-arg=@FileArg(build_config_path:javac:classpath)
+
+Note: If paths to input files are passed in this way, it is important that:
+ 1. inputs/deps of the action ensure that the files are available the first
+ time the action runs.
+ 2. Either (a) or (b)
+ a. inputs/deps ensure that the action runs whenever one of the files changes
+ b. the files are added to the action's depfile
+"""
+
+import optparse
+import os
+import sys
+import xml.dom.minidom
+
+from util import build_utils
+
+import write_ordered_libraries
+
+class AndroidManifest(object):
+ def __init__(self, path):
+ self.path = path
+ dom = xml.dom.minidom.parse(path)
+ manifests = dom.getElementsByTagName('manifest')
+ assert len(manifests) == 1
+ self.manifest = manifests[0]
+
+ def GetInstrumentation(self):
+ instrumentation_els = self.manifest.getElementsByTagName('instrumentation')
+ if len(instrumentation_els) == 0:
+ return None
+ if len(instrumentation_els) != 1:
+ raise Exception(
+ 'More than one element found in %s' % self.path)
+ return instrumentation_els[0]
+
+ def CheckInstrumentation(self, expected_package):
+ instr = self.GetInstrumentation()
+ if not instr:
+ raise Exception('No elements found in %s' % self.path)
+ instrumented_package = instr.getAttributeNS(
+ 'http://schemas.android.com/apk/res/android', 'targetPackage')
+ if instrumented_package != expected_package:
+ raise Exception(
+ 'Wrong instrumented package. Expected %s, got %s'
+ % (expected_package, instrumented_package))
+
+ def GetPackageName(self):
+ return self.manifest.getAttribute('package')
+
+
+dep_config_cache = {}
+def GetDepConfig(path):
+ if not path in dep_config_cache:
+ dep_config_cache[path] = build_utils.ReadJson(path)['deps_info']
+ return dep_config_cache[path]
+
+
+def DepsOfType(wanted_type, configs):
+ return [c for c in configs if c['type'] == wanted_type]
+
+
+def GetAllDepsConfigsInOrder(deps_config_paths):
+ def GetDeps(path):
+ return set(GetDepConfig(path)['deps_configs'])
+ return build_utils.GetSortedTransitiveDependencies(deps_config_paths, GetDeps)
+
+
+class Deps(object):
+ def __init__(self, direct_deps_config_paths):
+ self.all_deps_config_paths = GetAllDepsConfigsInOrder(
+ direct_deps_config_paths)
+ self.direct_deps_configs = [
+ GetDepConfig(p) for p in direct_deps_config_paths]
+ self.all_deps_configs = [
+ GetDepConfig(p) for p in self.all_deps_config_paths]
+
+ def All(self, wanted_type=None):
+ if type is None:
+ return self.all_deps_configs
+ return DepsOfType(wanted_type, self.all_deps_configs)
+
+ def Direct(self, wanted_type=None):
+ if wanted_type is None:
+ return self.direct_deps_configs
+ return DepsOfType(wanted_type, self.direct_deps_configs)
+
+ def AllConfigPaths(self):
+ return self.all_deps_config_paths
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option('--build-config', help='Path to build_config output.')
+ parser.add_option(
+ '--type',
+ help='Type of this target (e.g. android_library).')
+ parser.add_option(
+ '--possible-deps-configs',
+ help='List of paths for dependency\'s build_config files. Some '
+ 'dependencies may not write build_config files. Missing build_config '
+ 'files are handled differently based on the type of this target.')
+
+ # android_resources options
+ parser.add_option('--srcjar', help='Path to target\'s resources srcjar.')
+ parser.add_option('--resources-zip', help='Path to target\'s resources zip.')
+ parser.add_option('--r-text', help='Path to target\'s R.txt file.')
+ parser.add_option('--package-name',
+ help='Java package name for these resources.')
+ parser.add_option('--android-manifest', help='Path to android manifest.')
+
+ # java library options
+ parser.add_option('--jar-path', help='Path to target\'s jar output.')
+ parser.add_option('--supports-android', action='store_true',
+ help='Whether this library supports running on the Android platform.')
+ parser.add_option('--requires-android', action='store_true',
+ help='Whether this library requires running on the Android platform.')
+ parser.add_option('--bypass-platform-checks', action='store_true',
+ help='Bypass checks for support/require Android platform.')
+
+ # android library options
+ parser.add_option('--dex-path', help='Path to target\'s dex output.')
+
+ # native library options
+ parser.add_option('--native-libs', help='List of top-level native libs.')
+ parser.add_option('--readelf-path', help='Path to toolchain\'s readelf.')
+
+ parser.add_option('--tested-apk-config',
+ help='Path to the build config of the tested apk (for an instrumentation '
+ 'test apk).')
+
+ options, args = parser.parse_args(argv)
+
+ if args:
+ parser.error('No positional arguments should be given.')
+
+
+ if not options.type in [
+ 'java_library', 'android_resources', 'android_apk', 'deps_dex']:
+ raise Exception('Unknown type: <%s>' % options.type)
+
+ required_options = ['build_config'] + {
+ 'java_library': ['jar_path'],
+ 'android_resources': ['resources_zip'],
+ 'android_apk': ['jar_path', 'dex_path', 'resources_zip'],
+ 'deps_dex': ['dex_path']
+ }[options.type]
+
+ if options.native_libs:
+ required_options.append('readelf_path')
+
+ build_utils.CheckOptions(options, parser, required_options)
+
+ if options.type == 'java_library':
+ if options.supports_android and not options.dex_path:
+ raise Exception('java_library that supports Android requires a dex path.')
+
+ if options.requires_android and not options.supports_android:
+ raise Exception(
+ '--supports-android is required when using --requires-android')
+
+ possible_deps_config_paths = build_utils.ParseGypList(
+ options.possible_deps_configs)
+
+ allow_unknown_deps = (options.type == 'android_apk' or
+ options.type == 'android_resources')
+ unknown_deps = [
+ c for c in possible_deps_config_paths if not os.path.exists(c)]
+ if unknown_deps and not allow_unknown_deps:
+ raise Exception('Unknown deps: ' + str(unknown_deps))
+
+ direct_deps_config_paths = [
+ c for c in possible_deps_config_paths if not c in unknown_deps]
+
+ deps = Deps(direct_deps_config_paths)
+ direct_library_deps = deps.Direct('java_library')
+ all_library_deps = deps.All('java_library')
+
+ direct_resources_deps = deps.Direct('android_resources')
+ all_resources_deps = deps.All('android_resources')
+ # Resources should be ordered with the highest-level dependency first so that
+ # overrides are done correctly.
+ all_resources_deps.reverse()
+
+ if options.type == 'android_apk' and options.tested_apk_config:
+ tested_apk_deps = Deps([options.tested_apk_config])
+ tested_apk_resources_deps = tested_apk_deps.All('android_resources')
+ all_resources_deps = [
+ d for d in all_resources_deps if not d in tested_apk_resources_deps]
+
+ # Initialize some common config.
+ config = {
+ 'deps_info': {
+ 'name': os.path.basename(options.build_config),
+ 'path': options.build_config,
+ 'type': options.type,
+ 'deps_configs': direct_deps_config_paths,
+ }
+ }
+ deps_info = config['deps_info']
+
+ if options.type == 'java_library' and not options.bypass_platform_checks:
+ deps_info['requires_android'] = options.requires_android
+ deps_info['supports_android'] = options.supports_android
+
+ deps_require_android = (all_resources_deps +
+ [d['name'] for d in all_library_deps if d['requires_android']])
+ deps_not_support_android = (
+ [d['name'] for d in all_library_deps if not d['supports_android']])
+
+ if deps_require_android and not options.requires_android:
+ raise Exception('Some deps require building for the Android platform: ' +
+ str(deps_require_android))
+
+ if deps_not_support_android and options.supports_android:
+ raise Exception('Not all deps support the Android platform: ' +
+ str(deps_not_support_android))
+
+ if options.type in ['java_library', 'android_apk']:
+ javac_classpath = [c['jar_path'] for c in direct_library_deps]
+ java_full_classpath = [c['jar_path'] for c in all_library_deps]
+ deps_info['resources_deps'] = [c['path'] for c in all_resources_deps]
+ deps_info['jar_path'] = options.jar_path
+ if options.type == 'android_apk' or options.supports_android:
+ deps_info['dex_path'] = options.dex_path
+ config['javac'] = {
+ 'classpath': javac_classpath,
+ }
+ config['java'] = {
+ 'full_classpath': java_full_classpath
+ }
+
+ if options.type == 'java_library':
+ # Only resources might have srcjars (normal srcjar targets are listed in
+ # srcjar_deps). A resource's srcjar contains the R.java file for those
+ # resources, and (like Android's default build system) we allow a library to
+ # refer to the resources in any of its dependents.
+ config['javac']['srcjars'] = [
+ c['srcjar'] for c in direct_resources_deps if 'srcjar' in c]
+
+ if options.type == 'android_apk':
+ # Apks will get their resources srcjar explicitly passed to the java step.
+ config['javac']['srcjars'] = []
+
+ if options.type == 'android_resources':
+ deps_info['resources_zip'] = options.resources_zip
+ if options.srcjar:
+ deps_info['srcjar'] = options.srcjar
+ if options.android_manifest:
+ manifest = AndroidManifest(options.android_manifest)
+ deps_info['package_name'] = manifest.GetPackageName()
+ if options.package_name:
+ deps_info['package_name'] = options.package_name
+ if options.r_text:
+ deps_info['r_text'] = options.r_text
+
+ if options.type == 'android_resources' or options.type == 'android_apk':
+ config['resources'] = {}
+ config['resources']['dependency_zips'] = [
+ c['resources_zip'] for c in all_resources_deps]
+ config['resources']['extra_package_names'] = []
+ config['resources']['extra_r_text_files'] = []
+
+ if options.type == 'android_apk':
+ config['resources']['extra_package_names'] = [
+ c['package_name'] for c in all_resources_deps if 'package_name' in c]
+ config['resources']['extra_r_text_files'] = [
+ c['r_text'] for c in all_resources_deps if 'r_text' in c]
+
+ if options.type in ['android_apk', 'deps_dex']:
+ deps_dex_files = [c['dex_path'] for c in all_library_deps]
+
+ # An instrumentation test apk should exclude the dex files that are in the apk
+ # under test.
+ if options.type == 'android_apk' and options.tested_apk_config:
+ tested_apk_deps = Deps([options.tested_apk_config])
+ tested_apk_library_deps = tested_apk_deps.All('java_library')
+ tested_apk_deps_dex_files = [c['dex_path'] for c in tested_apk_library_deps]
+ deps_dex_files = [
+ p for p in deps_dex_files if not p in tested_apk_deps_dex_files]
+
+ tested_apk_config = GetDepConfig(options.tested_apk_config)
+ expected_tested_package = tested_apk_config['package_name']
+ AndroidManifest(options.android_manifest).CheckInstrumentation(
+ expected_tested_package)
+
+ # Dependencies for the final dex file of an apk or a 'deps_dex'.
+ if options.type in ['android_apk', 'deps_dex']:
+ config['final_dex'] = {}
+ dex_config = config['final_dex']
+ # TODO(cjhopman): proguard version
+ dex_config['dependency_dex_files'] = deps_dex_files
+
+ if options.type == 'android_apk':
+ config['dist_jar'] = {
+ 'dependency_jars': [
+ c['jar_path'] for c in all_library_deps
+ ]
+ }
+ manifest = AndroidManifest(options.android_manifest)
+ deps_info['package_name'] = manifest.GetPackageName()
+ if not options.tested_apk_config and manifest.GetInstrumentation():
+ # This must then have instrumentation only for itself.
+ manifest.CheckInstrumentation(manifest.GetPackageName())
+
+ library_paths = []
+ java_libraries_list = []
+ if options.native_libs:
+ libraries = build_utils.ParseGypList(options.native_libs)
+ if libraries:
+ libraries_dir = os.path.dirname(libraries[0])
+ write_ordered_libraries.SetReadelfPath(options.readelf_path)
+ write_ordered_libraries.SetLibraryDirs([libraries_dir])
+ all_native_library_deps = (
+ write_ordered_libraries.GetSortedTransitiveDependenciesForBinaries(
+ libraries))
+ # Create a java literal array with the "base" library names:
+ # e.g. libfoo.so -> foo
+ java_libraries_list = '{%s}' % ','.join(
+ ['"%s"' % s[3:-3] for s in all_native_library_deps])
+ library_paths = map(
+ write_ordered_libraries.FullLibraryPath, all_native_library_deps)
+
+ config['native'] = {
+ 'libraries': library_paths,
+ 'java_libraries_list': java_libraries_list
+ }
+
+ build_utils.WriteJson(config, options.build_config, only_if_changed=True)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ deps.AllConfigPaths() + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/android/gyp/write_ordered_libraries.py b/build/android/gyp/write_ordered_libraries.py
new file mode 100755
index 00000000000..0fc9a8ca3ef
--- /dev/null
+++ b/build/android/gyp/write_ordered_libraries.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Writes dependency ordered list of native libraries.
+
+The list excludes any Android system libraries, as those are not bundled with
+the APK.
+
+This list of libraries is used for several steps of building an APK.
+In the component build, the --input-libraries only needs to be the top-level
+library (i.e. libcontent_shell_content_view). This will then use readelf to
+inspect the shared libraries and determine the full list of (non-system)
+libraries that should be included in the APK.
+"""
+
+# TODO(cjhopman): See if we can expose the list of library dependencies from
+# gyp, rather than calculating it ourselves.
+# http://crbug.com/225558
+
+import optparse
+import os
+import re
+import sys
+
+from util import build_utils
+
+_readelf = None
+_library_dirs = None
+
+_library_re = re.compile(
+ '.*NEEDED.*Shared library: \[(?P.+)\]')
+
+
+def SetReadelfPath(path):
+ global _readelf
+ _readelf = path
+
+
+def SetLibraryDirs(dirs):
+ global _library_dirs
+ _library_dirs = dirs
+
+
+def FullLibraryPath(library_name):
+ assert _library_dirs is not None
+ for directory in _library_dirs:
+ path = '%s/%s' % (directory, library_name)
+ if os.path.exists(path):
+ return path
+ return library_name
+
+
+def IsSystemLibrary(library_name):
+ # If the library doesn't exist in the libraries directory, assume that it is
+ # an Android system library.
+ return not os.path.exists(FullLibraryPath(library_name))
+
+
+def CallReadElf(library_or_executable):
+ assert _readelf is not None
+ readelf_cmd = [_readelf,
+ '-d',
+ FullLibraryPath(library_or_executable)]
+ return build_utils.CheckOutput(readelf_cmd)
+
+
+def GetDependencies(library_or_executable):
+ elf = CallReadElf(library_or_executable)
+ return set(_library_re.findall(elf))
+
+
+def GetNonSystemDependencies(library_name):
+ all_deps = GetDependencies(library_name)
+ return set((lib for lib in all_deps if not IsSystemLibrary(lib)))
+
+
+def GetSortedTransitiveDependencies(libraries):
+ """Returns all transitive library dependencies in dependency order."""
+ return build_utils.GetSortedTransitiveDependencies(
+ libraries, GetNonSystemDependencies)
+
+
+def GetSortedTransitiveDependenciesForBinaries(binaries):
+ if binaries[0].endswith('.so'):
+ libraries = [os.path.basename(lib) for lib in binaries]
+ else:
+ assert len(binaries) == 1
+ all_deps = GetDependencies(binaries[0])
+ libraries = [lib for lib in all_deps if not IsSystemLibrary(lib)]
+
+ return GetSortedTransitiveDependencies(libraries)
+
+
+def main():
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+
+ parser.add_option('--input-libraries',
+ help='A list of top-level input libraries.')
+ parser.add_option('--libraries-dir',
+ help='The directory which contains shared libraries.')
+ parser.add_option('--readelf', help='Path to the readelf binary.')
+ parser.add_option('--output', help='Path to the generated .json file.')
+ parser.add_option('--stamp', help='Path to touch on success.')
+
+ options, _ = parser.parse_args()
+
+ SetReadelfPath(options.readelf)
+ SetLibraryDirs(options.libraries_dir.split(','))
+
+ libraries = build_utils.ParseGypList(options.input_libraries)
+ if len(libraries):
+ libraries = GetSortedTransitiveDependenciesForBinaries(libraries)
+
+ # Convert to "base" library names: e.g. libfoo.so -> foo
+ java_libraries_list = (
+ '{%s}' % ','.join(['"%s"' % s[3:-3] for s in libraries]))
+
+ out_json = {
+ 'libraries': libraries,
+ 'lib_paths': [FullLibraryPath(l) for l in libraries],
+ 'java_libraries_list': java_libraries_list
+ }
+ build_utils.WriteJson(
+ out_json,
+ options.output,
+ only_if_changed=True)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+ if options.depfile:
+ build_utils.WriteDepfile(
+ options.depfile,
+ libraries + build_utils.GetPythonDependencies())
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
+
diff --git a/build/android/gyp/zip.py b/build/android/gyp/zip.py
new file mode 100755
index 00000000000..51322dfd5b2
--- /dev/null
+++ b/build/android/gyp/zip.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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.
+
+"""Archives a set of files.
+"""
+
+import optparse
+import sys
+
+from util import build_utils
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--input-dir', help='Directory of files to archive.')
+ parser.add_option('--output', help='Path to output archive.')
+ options, _ = parser.parse_args()
+
+ inputs = build_utils.FindInDirectory(options.input_dir, '*')
+ build_utils.DoZip(inputs, options.output, options.input_dir)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/host_heartbeat.py b/build/android/host_heartbeat.py
new file mode 100755
index 00000000000..6a7cdd1d8e6
--- /dev/null
+++ b/build/android/host_heartbeat.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+"""Sends a heart beat pulse to the currently online Android devices.
+This heart beat lets the devices know that they are connected to a host.
+"""
+# pylint: disable=W0702
+
+import sys
+import time
+
+from pylib.device import device_utils
+
+PULSE_PERIOD = 20
+
+def main():
+ while True:
+ try:
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ for d in devices:
+ d.RunShellCommand(['touch', '/sdcard/host_heartbeat'],
+ check_return=True)
+ except:
+ # Keep the heatbeat running bypassing all errors.
+ pass
+ time.sleep(PULSE_PERIOD)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/increase_size_for_speed.gypi b/build/android/increase_size_for_speed.gypi
new file mode 100644
index 00000000000..48d17f5f19c
--- /dev/null
+++ b/build/android/increase_size_for_speed.gypi
@@ -0,0 +1,42 @@
+# Copyright (c) 2014 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 file is meant to be included to optimize a target for speed
+# rather than for size on Android.
+# This is used in some carefully tailored targets and is not meant
+# to be included everywhere. Before adding the template to another target,
+# please ask in chromium-dev@. See crbug.com/411909
+
+{
+ 'configurations': {
+ 'Release': {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['OS=="android"', {
+ 'cflags!': ['-Os'],
+ 'cflags': ['-O2'],
+ }],
+ # Do not merge -Os and -O2 in LTO.
+ # LTO merges all optimization options at link-time. -O2 takes
+ # precedence over -Os. Avoid using LTO simultaneously
+ # on -Os and -O2 parts for that reason.
+ ['OS=="android" and use_lto==1', {
+ 'cflags!': [
+ '-flto',
+ '-ffat-lto-objects',
+ ],
+ }],
+ ['OS=="android" and use_lto_o2==1', {
+ 'cflags': [
+ '-flto',
+ '-ffat-lto-objects',
+ ],
+ }],
+ ],
+ }],
+ ],
+ },
+ },
+}
diff --git a/build/android/insert_chromium_version.gypi b/build/android/insert_chromium_version.gypi
new file mode 100644
index 00000000000..a6ff9081086
--- /dev/null
+++ b/build/android/insert_chromium_version.gypi
@@ -0,0 +1,53 @@
+# Copyright 2014 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 file is meant to be included into an action to provide a rule that
+# inserts a chromium version string into native libraries.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'action_name': 'insert_chromium_version',
+# 'actions': [
+# 'variables': {
+# 'ordered_libraries_file': 'file generated by write_ordered_libraries'
+# 'stripped_libraries_dir': 'the directory contains native libraries'
+# 'input_paths': 'files to be added to the list of inputs'
+# 'stamp': 'file to touch when the action is complete'
+# 'version_string': 'chromium version string to be inserted'
+# 'includes': [ '../../build/android/insert_chromium_version.gypi' ],
+# ],
+# },
+#
+
+{
+ 'message': 'Inserting chromium version string into native libraries',
+ 'variables': {
+ 'input_paths': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/insert_chromium_version.py',
+ '<(ordered_libraries_file)',
+ '>@(input_paths)',
+ ],
+ 'outputs': [
+ '<(stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/insert_chromium_version.py',
+ '--android-objcopy=<(android_objcopy)',
+ '--stripped-libraries-dir=<(stripped_libraries_dir)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--version-string=<(version_string)',
+ '--stamp=<(stamp)',
+ ],
+ 'conditions': [
+ ['component == "shared_library"', {
+ # Add a fake output to force the build to always re-run this step. This
+ # is required because the real inputs are not known at gyp-time and
+ # changing base.so may not trigger changes to dependent libraries.
+ 'outputs': [ '<(stamp).fake' ]
+ }],
+ ],
+}
diff --git a/build/android/install_emulator_deps.py b/build/android/install_emulator_deps.py
new file mode 100755
index 00000000000..82d1c751373
--- /dev/null
+++ b/build/android/install_emulator_deps.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+"""Installs deps for using SDK emulator for testing.
+
+The script will download the SDK and system images, if they are not present, and
+install and enable KVM, if virtualization has been enabled in the BIOS.
+"""
+
+
+import logging
+import optparse
+import os
+import re
+import shutil
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib import pexpect
+from pylib.utils import run_tests_helper
+
+# Android API level
+DEFAULT_ANDROID_API_LEVEL = constants.ANDROID_SDK_VERSION
+
+# From the Android Developer's website.
+# Keep this up to date; the user can install older API levels as necessary.
+SDK_BASE_URL = 'http://dl.google.com/android/adt'
+SDK_ZIP = 'adt-bundle-linux-x86_64-20131030.zip'
+
+# pylint: disable=line-too-long
+# Android x86 system image from the Intel website:
+# http://software.intel.com/en-us/articles/intel-eula-x86-android-4-2-jelly-bean-bin
+# These don't exist prior to Android-15.
+# As of 08 Nov 2013, Android-19 is not yet available either.
+X86_IMG_URLS = {
+ 15: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-15_r01.zip',
+ 16: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-16_r01.zip',
+ 17: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-17_r01.zip',
+ 18: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-18_r01.zip',
+ 19: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-19_r01.zip'}
+#pylint: enable=line-too-long
+
+def CheckSDK():
+ """Check if SDK is already installed.
+
+ Returns:
+ True if the emulator SDK directory (src/android_emulator_sdk/) exists.
+ """
+ return os.path.exists(constants.EMULATOR_SDK_ROOT)
+
+
+def CheckSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL):
+ """Check if the "SDK Platform" for the specified API level is installed.
+ This is necessary in order for the emulator to run when the target
+ is specified.
+
+ Args:
+ api_level: the Android API level to check; defaults to the latest API.
+
+ Returns:
+ True if the platform is already installed.
+ """
+ android_binary = os.path.join(constants.EMULATOR_SDK_ROOT,
+ 'sdk', 'tools', 'android')
+ pattern = re.compile('id: [0-9]+ or "android-%d"' % api_level)
+ try:
+ exit_code, stdout = cmd_helper.GetCmdStatusAndOutput(
+ [android_binary, 'list'])
+ if exit_code != 0:
+ raise Exception('\'android list\' command failed')
+ for line in stdout.split('\n'):
+ if pattern.match(line):
+ return True
+ return False
+ except OSError:
+ logging.exception('Unable to execute \'android list\'')
+ return False
+
+
+def CheckX86Image(api_level=DEFAULT_ANDROID_API_LEVEL):
+ """Check if Android system images have been installed.
+
+ Args:
+ api_level: the Android API level to check for; defaults to the latest API.
+
+ Returns:
+ True if sdk/system-images/android-/x86 exists inside
+ EMULATOR_SDK_ROOT.
+ """
+ api_target = 'android-%d' % api_level
+ return os.path.exists(os.path.join(constants.EMULATOR_SDK_ROOT,
+ 'sdk', 'system-images',
+ api_target, 'x86'))
+
+
+def CheckKVM():
+ """Quickly check whether KVM is enabled.
+
+ Returns:
+ True iff /dev/kvm exists (Linux only).
+ """
+ return os.path.exists('/dev/kvm')
+
+
+def RunKvmOk():
+ """Run kvm-ok as root to check that KVM is properly enabled after installation
+ of the required packages.
+
+ Returns:
+ True iff KVM is enabled (/dev/kvm exists). On failure, returns False
+ but also print detailed information explaining why KVM isn't enabled
+ (e.g. CPU doesn't support it, or BIOS disabled it).
+ """
+ try:
+ # Note: kvm-ok is in /usr/sbin, so always use 'sudo' to run it.
+ return not cmd_helper.RunCmd(['sudo', 'kvm-ok'])
+ except OSError:
+ logging.info('kvm-ok not installed')
+ return False
+
+
+def GetSDK():
+ """Download the SDK and unzip it into EMULATOR_SDK_ROOT."""
+ logging.info('Download Android SDK.')
+ sdk_url = '%s/%s' % (SDK_BASE_URL, SDK_ZIP)
+ try:
+ cmd_helper.RunCmd(['curl', '-o', '/tmp/sdk.zip', sdk_url])
+ print 'curled unzipping...'
+ rc = cmd_helper.RunCmd(['unzip', '-o', '/tmp/sdk.zip', '-d', '/tmp/'])
+ if rc:
+ raise Exception('ERROR: could not download/unzip Android SDK.')
+ # Get the name of the sub-directory that everything will be extracted to.
+ dirname, _ = os.path.splitext(SDK_ZIP)
+ zip_dir = '/tmp/%s' % dirname
+ # Move the extracted directory to EMULATOR_SDK_ROOT
+ shutil.move(zip_dir, constants.EMULATOR_SDK_ROOT)
+ finally:
+ os.unlink('/tmp/sdk.zip')
+
+
+def InstallKVM():
+ """Installs KVM packages."""
+ rc = cmd_helper.RunCmd(['sudo', 'apt-get', 'install', 'kvm'])
+ if rc:
+ logging.critical('ERROR: Did not install KVM. Make sure hardware '
+ 'virtualization is enabled in BIOS (i.e. Intel VT-x or '
+ 'AMD SVM).')
+ # TODO(navabi): Use modprobe kvm-amd on AMD processors.
+ rc = cmd_helper.RunCmd(['sudo', 'modprobe', 'kvm-intel'])
+ if rc:
+ logging.critical('ERROR: Did not add KVM module to Linux Kernel. Make sure '
+ 'hardware virtualization is enabled in BIOS.')
+ # Now check to ensure KVM acceleration can be used.
+ if not RunKvmOk():
+ logging.critical('ERROR: Can not use KVM acceleration. Make sure hardware '
+ 'virtualization is enabled in BIOS (i.e. Intel VT-x or '
+ 'AMD SVM).')
+
+
+def GetX86Image(api_level=DEFAULT_ANDROID_API_LEVEL):
+ """Download x86 system image from Intel's website.
+
+ Args:
+ api_level: the Android API level to download for.
+ """
+ logging.info('Download x86 system image directory into sdk directory.')
+ # TODO(andrewhayden): Use python tempfile lib instead
+ temp_file = '/tmp/x86_img_android-%d.zip' % api_level
+ if api_level not in X86_IMG_URLS:
+ raise Exception('ERROR: no URL known for x86 image for android-%s' %
+ api_level)
+ try:
+ cmd_helper.RunCmd(['curl', '-o', temp_file, X86_IMG_URLS[api_level]])
+ rc = cmd_helper.RunCmd(['unzip', '-o', temp_file, '-d', '/tmp/'])
+ if rc:
+ raise Exception('ERROR: Could not download/unzip image zip.')
+ api_target = 'android-%d' % api_level
+ sys_imgs = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk',
+ 'system-images', api_target, 'x86')
+ logging.info('Deploying system image to %s' % sys_imgs)
+ shutil.move('/tmp/x86', sys_imgs)
+ finally:
+ os.unlink(temp_file)
+
+
+def GetSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL):
+ """Update the SDK to include the platform specified.
+
+ Args:
+ api_level: the Android API level to download
+ """
+ android_binary = os.path.join(constants.EMULATOR_SDK_ROOT,
+ 'sdk', 'tools', 'android')
+ pattern = re.compile(
+ r'\s*([0-9]+)- SDK Platform Android [\.,0-9]+, API %d.*' % api_level)
+ # Example:
+ # 2- SDK Platform Android 4.3, API 18, revision 2
+ exit_code, stdout = cmd_helper.GetCmdStatusAndOutput(
+ [android_binary, 'list', 'sdk'])
+ if exit_code != 0:
+ raise Exception('\'android list sdk\' command return %d' % exit_code)
+ for line in stdout.split('\n'):
+ match = pattern.match(line)
+ if match:
+ index = match.group(1)
+ print 'package %s corresponds to platform level %d' % (index, api_level)
+ # update sdk --no-ui --filter $INDEX
+ update_command = [android_binary,
+ 'update', 'sdk', '--no-ui', '--filter', index]
+ update_command_str = ' '.join(update_command)
+ logging.info('running update command: %s' % update_command_str)
+ update_process = pexpect.spawn(update_command_str)
+ # TODO(andrewhayden): Do we need to bug the user about this?
+ if update_process.expect('Do you accept the license') != 0:
+ raise Exception('License agreement check failed')
+ update_process.sendline('y')
+ if update_process.expect('Done. 1 package installed.') == 0:
+ print 'Successfully installed platform for API level %d' % api_level
+ return
+ else:
+ raise Exception('Failed to install platform update')
+ raise Exception('Could not find android-%d update for the SDK!' % api_level)
+
+
+def main(argv):
+ opt_parser = optparse.OptionParser(
+ description='Install dependencies for running the Android emulator')
+ opt_parser.add_option('--api-level', dest='api_level',
+ help='The API level (e.g., 19 for Android 4.4) to ensure is available',
+ type='int', default=DEFAULT_ANDROID_API_LEVEL)
+ opt_parser.add_option('-v', dest='verbose', action='store_true',
+ help='enable verbose logging')
+ options, _ = opt_parser.parse_args(argv[1:])
+
+ # run_tests_helper will set logging to INFO or DEBUG
+ # We achieve verbose output by configuring it with 2 (==DEBUG)
+ verbosity = 1
+ if options.verbose:
+ verbosity = 2
+ logging.basicConfig(level=logging.INFO,
+ format='# %(asctime)-15s: %(message)s')
+ run_tests_helper.SetLogLevel(verbose_count=verbosity)
+
+ # Calls below will download emulator SDK and/or system images only if needed.
+ if CheckSDK():
+ logging.info('android_emulator_sdk/ already exists, skipping download.')
+ else:
+ GetSDK()
+
+ # Check target. The target has to be installed in order to run the emulator.
+ if CheckSDKPlatform(options.api_level):
+ logging.info('SDK platform android-%d already present, skipping.' %
+ options.api_level)
+ else:
+ logging.info('SDK platform android-%d not present, installing.' %
+ options.api_level)
+ GetSDKPlatform(options.api_level)
+
+ # Download the x86 system image only if needed.
+ if CheckX86Image(options.api_level):
+ logging.info('x86 image for android-%d already present, skipping.' %
+ options.api_level)
+ else:
+ GetX86Image(options.api_level)
+
+ # Make sure KVM packages are installed and enabled.
+ if CheckKVM():
+ logging.info('KVM already installed and enabled.')
+ else:
+ InstallKVM()
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/instr_action.gypi b/build/android/instr_action.gypi
new file mode 100644
index 00000000000..fa6d06235ea
--- /dev/null
+++ b/build/android/instr_action.gypi
@@ -0,0 +1,53 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule that
+# instruments either java class files, or jars.
+
+{
+ 'variables': {
+ 'instr_type%': 'jar',
+ 'input_path%': '',
+ 'output_path%': '',
+ 'stamp_path%': '',
+ 'extra_instr_args': [
+ '--coverage-file=<(_target_name).em',
+ '--sources-file=<(_target_name)_sources.txt',
+ ],
+ 'emma_jar': '<(android_sdk_root)/tools/lib/emma.jar',
+ 'conditions': [
+ ['emma_instrument != 0', {
+ 'extra_instr_args': [
+ '--sources=<(java_in_dir)/src >(additional_src_dirs) >(generated_src_dirs)',
+ '--src-root=<(DEPTH)',
+ '--emma-jar=<(emma_jar)',
+ '--filter-string=<(emma_filter)',
+ ],
+ 'conditions': [
+ ['instr_type == "jar"', {
+ 'instr_action': 'instrument_jar',
+ }, {
+ 'instr_action': 'instrument_classes',
+ }]
+ ],
+ }, {
+ 'instr_action': 'copy',
+ 'extra_instr_args': [],
+ }]
+ ]
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/emma_instr.py',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/pylib/utils/command_option_parser.py',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/emma_instr.py',
+ '<(instr_action)',
+ '--input-path=<(input_path)',
+ '--output-path=<(output_path)',
+ '--stamp=<(stamp_path)',
+ '<@(extra_instr_args)',
+ ]
+}
diff --git a/build/android/java_cpp_enum.gypi b/build/android/java_cpp_enum.gypi
new file mode 100644
index 00000000000..d4abafa812f
--- /dev/null
+++ b/build/android/java_cpp_enum.gypi
@@ -0,0 +1,64 @@
+# Copyright 2014 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 file is meant to be included into a target to provide an action
+# to generate Java source files from a C++ header file containing annotated
+# enum definitions using a Python script.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'bitmap_format_java',
+# 'type': 'none',
+# 'variables': {
+# 'source_file': 'ui/android/bitmap_format.h',
+# },
+# 'includes': [ '../build/android/java_cpp_enum.gypi' ],
+# },
+#
+# Then have the gyp target which compiles the java code depend on the newly
+# created target.
+
+{
+ 'variables': {
+ # Location where all generated Java sources will be placed.
+ 'output_dir': '<(SHARED_INTERMEDIATE_DIR)/enums/<(_target_name)',
+ 'generator_path': '<(DEPTH)/build/android/gyp/java_cpp_enum.py',
+ 'generator_args': '<(output_dir) <(source_file)',
+ },
+ 'direct_dependent_settings': {
+ 'variables': {
+ # Ensure that the output directory is used in the class path
+ # when building targets that depend on this one.
+ 'generated_src_dirs': [
+ '<(output_dir)/',
+ ],
+ # Ensure that the targets depending on this one are rebuilt if the sources
+ # of this one are modified.
+ 'additional_input_paths': [
+ '<(source_file)',
+ ],
+ },
+ },
+ 'actions': [
+ {
+ 'action_name': 'generate_java_constants',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(generator_path)',
+ '<(source_file)',
+ ],
+ 'outputs': [
+ # This is the main reason this is an action and not a rule. Gyp doesn't
+ # properly expand RULE_INPUT_PATH here and so it's impossible to
+ # calculate the list of outputs.
+ '//ClassName.java. The files which
+# template dependents on and typically included by the template should be listed
+# in template_deps variables. Any change to them will force a rebuild of
+# the template, and hence of any source that depends on it.
+#
+
+{
+ # Location where all generated Java sources will be placed.
+ 'variables': {
+ 'include_path%': '<(DEPTH)',
+ 'output_dir': '<(SHARED_INTERMEDIATE_DIR)/templates/<(_target_name)/<(package_name)',
+ },
+ 'direct_dependent_settings': {
+ 'variables': {
+ # Ensure that the output directory is used in the class path
+ # when building targets that depend on this one.
+ 'generated_src_dirs': [
+ '<(output_dir)/',
+ ],
+ # Ensure dependents are rebuilt when sources for this rule change.
+ 'additional_input_paths': [
+ '<@(_sources)',
+ '<@(template_deps)',
+ ],
+ },
+ },
+ # Define a single rule that will be apply to each .template file
+ # listed in 'sources'.
+ 'rules': [
+ {
+ 'rule_name': 'generate_java_constants',
+ 'extension': 'template',
+ # Set template_deps as additional dependencies.
+ 'variables': {
+ 'output_path': '<(output_dir)/<(RULE_INPUT_ROOT).java',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/gcc_preprocess.py',
+ '<@(template_deps)'
+ ],
+ 'outputs': [
+ '<(output_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/gcc_preprocess.py',
+ '--include-path=<(include_path)',
+ '--output=<(output_path)',
+ '--template=<(RULE_INPUT_PATH)',
+ ],
+ 'message': 'Generating Java from cpp template <(RULE_INPUT_PATH)',
+ }
+ ],
+}
diff --git a/build/android/jinja_template.gypi b/build/android/jinja_template.gypi
new file mode 100644
index 00000000000..9c49360dc6e
--- /dev/null
+++ b/build/android/jinja_template.gypi
@@ -0,0 +1,85 @@
+# Copyright 2014 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 file is meant to be included into a target to process one or more
+# Jinja templates.
+#
+# To process a single template file, create a gyp target with the following
+# form:
+# {
+# 'target_name': 'chrome_shell_manifest',
+# 'type': 'none',
+# 'variables': {
+# 'jinja_inputs': ['android/shell/java/AndroidManifest.xml'],
+# 'jinja_output': '<(SHARED_INTERMEDIATE_DIR)/chrome_shell_manifest/AndroidManifest.xml',
+# 'jinja_variables': ['app_name=ChromeShell'],
+# },
+# 'includes': [ '../build/android/jinja_template.gypi' ],
+# },
+#
+# To process multiple template files and package the results into a zip file,
+# create a gyp target with the following form:
+# {
+# 'target_name': 'chrome_template_resources',
+# 'type': 'none',
+# 'variables': {
+# 'jinja_inputs_base_dir': 'android/shell/java/res_template',
+# 'jinja_inputs': [
+# '<(jinja_inputs_base_dir)/xml/searchable.xml',
+# '<(jinja_inputs_base_dir)/xml/syncadapter.xml',
+# ],
+# 'jinja_outputs_zip': '<(PRODUCT_DIR)/res.java/<(_target_name).zip',
+# 'jinja_variables': ['app_name=ChromeShell'],
+# },
+# 'includes': [ '../build/android/jinja_template.gypi' ],
+# },
+#
+
+{
+ 'actions': [
+ {
+ 'action_name': '<(_target_name)_jinja_template',
+ 'message': 'processing jinja template',
+ 'variables': {
+ 'jinja_output%': '',
+ 'jinja_outputs_zip%': '',
+ 'jinja_inputs_base_dir%': '',
+ 'jinja_includes%': [],
+ 'jinja_variables%': [],
+ 'jinja_args': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/jinja_template.py',
+ '<@(jinja_inputs)',
+ '<@(jinja_includes)',
+ ],
+ 'conditions': [
+ ['jinja_output != ""', {
+ 'outputs': [ '<(jinja_output)' ],
+ 'variables': {
+ 'jinja_args': ['--output', '<(jinja_output)'],
+ },
+ }],
+ ['jinja_outputs_zip != ""', {
+ 'outputs': [ '<(jinja_outputs_zip)' ],
+ 'variables': {
+ 'jinja_args': ['--outputs-zip', '<(jinja_outputs_zip)'],
+ },
+ }],
+ ['jinja_inputs_base_dir != ""', {
+ 'variables': {
+ 'jinja_args': ['--inputs-base-dir', '<(jinja_inputs_base_dir)'],
+ },
+ }],
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/jinja_template.py',
+ '--inputs', '<(jinja_inputs)',
+ '--variables', '<(jinja_variables)',
+ '<@(jinja_args)',
+ ],
+ },
+ ],
+}
diff --git a/build/android/lighttpd_server.py b/build/android/lighttpd_server.py
new file mode 100755
index 00000000000..a5195ac815e
--- /dev/null
+++ b/build/android/lighttpd_server.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 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.
+
+"""Provides a convenient wrapper for spawning a test lighttpd instance.
+
+Usage:
+ lighttpd_server PATH_TO_DOC_ROOT
+"""
+
+import codecs
+import contextlib
+import httplib
+import os
+import random
+import shutil
+import socket
+import subprocess
+import sys
+import tempfile
+import time
+
+from pylib import constants
+from pylib import pexpect
+
+class LighttpdServer(object):
+ """Wraps lighttpd server, providing robust startup.
+
+ Args:
+ document_root: Path to root of this server's hosted files.
+ port: TCP port on the _host_ machine that the server will listen on. If
+ ommitted it will attempt to use 9000, or if unavailable it will find
+ a free port from 8001 - 8999.
+ lighttpd_path, lighttpd_module_path: Optional paths to lighttpd binaries.
+ base_config_path: If supplied this file will replace the built-in default
+ lighttpd config file.
+ extra_config_contents: If specified, this string will be appended to the
+ base config (default built-in, or from base_config_path).
+ config_path, error_log, access_log: Optional paths where the class should
+ place temprary files for this session.
+ """
+
+ def __init__(self, document_root, port=None,
+ lighttpd_path=None, lighttpd_module_path=None,
+ base_config_path=None, extra_config_contents=None,
+ config_path=None, error_log=None, access_log=None):
+ self.temp_dir = tempfile.mkdtemp(prefix='lighttpd_for_chrome_android')
+ self.document_root = os.path.abspath(document_root)
+ self.fixed_port = port
+ self.port = port or constants.LIGHTTPD_DEFAULT_PORT
+ self.server_tag = 'LightTPD ' + str(random.randint(111111, 999999))
+ self.lighttpd_path = lighttpd_path or '/usr/sbin/lighttpd'
+ self.lighttpd_module_path = lighttpd_module_path or '/usr/lib/lighttpd'
+ self.base_config_path = base_config_path
+ self.extra_config_contents = extra_config_contents
+ self.config_path = config_path or self._Mktmp('config')
+ self.error_log = error_log or self._Mktmp('error_log')
+ self.access_log = access_log or self._Mktmp('access_log')
+ self.pid_file = self._Mktmp('pid_file')
+ self.process = None
+
+ def _Mktmp(self, name):
+ return os.path.join(self.temp_dir, name)
+
+ @staticmethod
+ def _GetRandomPort():
+ # The ports of test server is arranged in constants.py.
+ return random.randint(constants.LIGHTTPD_RANDOM_PORT_FIRST,
+ constants.LIGHTTPD_RANDOM_PORT_LAST)
+
+ def StartupHttpServer(self):
+ """Starts up a http server with specified document root and port."""
+ # If we want a specific port, make sure no one else is listening on it.
+ if self.fixed_port:
+ self._KillProcessListeningOnPort(self.fixed_port)
+ while True:
+ if self.base_config_path:
+ # Read the config
+ with codecs.open(self.base_config_path, 'r', 'utf-8') as f:
+ config_contents = f.read()
+ else:
+ config_contents = self._GetDefaultBaseConfig()
+ if self.extra_config_contents:
+ config_contents += self.extra_config_contents
+ # Write out the config, filling in placeholders from the members of |self|
+ with codecs.open(self.config_path, 'w', 'utf-8') as f:
+ f.write(config_contents % self.__dict__)
+ if (not os.path.exists(self.lighttpd_path) or
+ not os.access(self.lighttpd_path, os.X_OK)):
+ raise EnvironmentError(
+ 'Could not find lighttpd at %s.\n'
+ 'It may need to be installed (e.g. sudo apt-get install lighttpd)'
+ % self.lighttpd_path)
+ self.process = pexpect.spawn(self.lighttpd_path,
+ ['-D', '-f', self.config_path,
+ '-m', self.lighttpd_module_path],
+ cwd=self.temp_dir)
+ client_error, server_error = self._TestServerConnection()
+ if not client_error:
+ assert int(open(self.pid_file, 'r').read()) == self.process.pid
+ break
+ self.process.close()
+
+ if self.fixed_port or not 'in use' in server_error:
+ print 'Client error:', client_error
+ print 'Server error:', server_error
+ return False
+ self.port = self._GetRandomPort()
+ return True
+
+ def ShutdownHttpServer(self):
+ """Shuts down our lighttpd processes."""
+ if self.process:
+ self.process.terminate()
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
+
+ def _TestServerConnection(self):
+ # Wait for server to start
+ server_msg = ''
+ for timeout in xrange(1, 5):
+ client_error = None
+ try:
+ with contextlib.closing(httplib.HTTPConnection(
+ '127.0.0.1', self.port, timeout=timeout)) as http:
+ http.set_debuglevel(timeout > 3)
+ http.request('HEAD', '/')
+ r = http.getresponse()
+ r.read()
+ if (r.status == 200 and r.reason == 'OK' and
+ r.getheader('Server') == self.server_tag):
+ return (None, server_msg)
+ client_error = ('Bad response: %s %s version %s\n ' %
+ (r.status, r.reason, r.version) +
+ '\n '.join([': '.join(h) for h in r.getheaders()]))
+ except (httplib.HTTPException, socket.error) as client_error:
+ pass # Probably too quick connecting: try again
+ # Check for server startup error messages
+ ix = self.process.expect([pexpect.TIMEOUT, pexpect.EOF, '.+'],
+ timeout=timeout)
+ if ix == 2: # stdout spew from the server
+ server_msg += self.process.match.group(0)
+ elif ix == 1: # EOF -- server has quit so giveup.
+ client_error = client_error or 'Server exited'
+ break
+ return (client_error or 'Timeout', server_msg)
+
+ @staticmethod
+ def _KillProcessListeningOnPort(port):
+ """Checks if there is a process listening on port number |port| and
+ terminates it if found.
+
+ Args:
+ port: Port number to check.
+ """
+ if subprocess.call(['fuser', '-kv', '%d/tcp' % port]) == 0:
+ # Give the process some time to terminate and check that it is gone.
+ time.sleep(2)
+ assert subprocess.call(['fuser', '-v', '%d/tcp' % port]) != 0, \
+ 'Unable to kill process listening on port %d.' % port
+
+ @staticmethod
+ def _GetDefaultBaseConfig():
+ return """server.tag = "%(server_tag)s"
+server.modules = ( "mod_access",
+ "mod_accesslog",
+ "mod_alias",
+ "mod_cgi",
+ "mod_rewrite" )
+
+# default document root required
+#server.document-root = "."
+
+# files to check for if .../ is requested
+index-file.names = ( "index.php", "index.pl", "index.cgi",
+ "index.html", "index.htm", "default.htm" )
+# mimetype mapping
+mimetype.assign = (
+ ".gif" => "image/gif",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".svg" => "image/svg+xml",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".xhtml" => "application/xhtml+xml",
+ ".xhtmlmp" => "application/vnd.wap.xhtml+xml",
+ ".js" => "application/x-javascript",
+ ".log" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".manifest" => "text/cache-manifest",
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+mimetype.use-xattr = "enable"
+
+##
+# which extensions should not be handle via static-file transfer
+#
+# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
+static-file.exclude-extensions = ( ".php", ".pl", ".cgi" )
+
+server.bind = "127.0.0.1"
+server.port = %(port)s
+
+## virtual directory listings
+dir-listing.activate = "enable"
+#dir-listing.encoding = "iso-8859-2"
+#dir-listing.external-css = "style/oldstyle.css"
+
+## enable debugging
+#debug.log-request-header = "enable"
+#debug.log-response-header = "enable"
+#debug.log-request-handling = "enable"
+#debug.log-file-not-found = "enable"
+
+#### SSL engine
+#ssl.engine = "enable"
+#ssl.pemfile = "server.pem"
+
+# Autogenerated test-specific config follows.
+
+cgi.assign = ( ".cgi" => "/usr/bin/env",
+ ".pl" => "/usr/bin/env",
+ ".asis" => "/bin/cat",
+ ".php" => "/usr/bin/php-cgi" )
+
+server.errorlog = "%(error_log)s"
+accesslog.filename = "%(access_log)s"
+server.upload-dirs = ( "/tmp" )
+server.pid-file = "%(pid_file)s"
+server.document-root = "%(document_root)s"
+
+"""
+
+
+def main(argv):
+ server = LighttpdServer(*argv[1:])
+ try:
+ if server.StartupHttpServer():
+ raw_input('Server running at http://127.0.0.1:%s -'
+ ' press Enter to exit it.' % server.port)
+ else:
+ print 'Server exit code:', server.process.exitstatus
+ finally:
+ server.ShutdownHttpServer()
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/android/lint/OWNERS b/build/android/lint/OWNERS
new file mode 100644
index 00000000000..cd396e7e571
--- /dev/null
+++ b/build/android/lint/OWNERS
@@ -0,0 +1,2 @@
+newt@chromium.org
+aurimas@chromium.org
diff --git a/build/android/lint/suppress.py b/build/android/lint/suppress.py
new file mode 100755
index 00000000000..52d7579b964
--- /dev/null
+++ b/build/android/lint/suppress.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+"""Add all generated lint_result.xml files to suppressions.xml"""
+
+
+import collections
+import optparse
+import os
+import sys
+from xml.dom import minidom
+
+_BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__), '..')
+sys.path.append(_BUILD_ANDROID_DIR)
+
+from pylib import constants
+
+
+_THIS_FILE = os.path.abspath(__file__)
+_CONFIG_PATH = os.path.join(os.path.dirname(_THIS_FILE), 'suppressions.xml')
+_DOC = (
+ '\nSTOP! It looks like you want to suppress some lint errors:\n'
+ '- Have you tried identifing the offending patch?\n'
+ ' Ask the author for a fix and/or revert the patch.\n'
+ '- It is preferred to add suppressions in the code instead of\n'
+ ' sweeping it under the rug here. See:\n\n'
+ ' http://developer.android.com/tools/debugging/improving-w-lint.html\n'
+ '\n'
+ 'Still reading?\n'
+ '- You can edit this file manually to suppress an issue\n'
+ ' globally if it is not applicable to the project.\n'
+ '- You can also automatically add issues found so for in the\n'
+ ' build process by running:\n\n'
+ ' ' + os.path.relpath(_THIS_FILE, constants.DIR_SOURCE_ROOT) + '\n\n'
+ ' which will generate this file (Comments are not preserved).\n'
+ ' Note: PRODUCT_DIR will be substituted at run-time with actual\n'
+ ' directory path (e.g. out/Debug)\n'
+)
+
+
+_Issue = collections.namedtuple('Issue', ['severity', 'paths'])
+
+
+def _ParseConfigFile(config_path):
+ print 'Parsing %s' % config_path
+ issues_dict = {}
+ dom = minidom.parse(config_path)
+ for issue in dom.getElementsByTagName('issue'):
+ issue_id = issue.attributes['id'].value
+ severity = issue.getAttribute('severity')
+ paths = set(
+ [p.attributes['path'].value for p in
+ issue.getElementsByTagName('ignore')])
+ issues_dict[issue_id] = _Issue(severity, paths)
+ return issues_dict
+
+
+def _ParseAndMergeResultFile(result_path, issues_dict):
+ print 'Parsing and merging %s' % result_path
+ dom = minidom.parse(result_path)
+ for issue in dom.getElementsByTagName('issue'):
+ issue_id = issue.attributes['id'].value
+ severity = issue.attributes['severity'].value
+ path = issue.getElementsByTagName('location')[0].attributes['file'].value
+ if issue_id not in issues_dict:
+ issues_dict[issue_id] = _Issue(severity, set())
+ issues_dict[issue_id].paths.add(path)
+
+
+def _WriteConfigFile(config_path, issues_dict):
+ new_dom = minidom.getDOMImplementation().createDocument(None, 'lint', None)
+ top_element = new_dom.documentElement
+ top_element.appendChild(new_dom.createComment(_DOC))
+ for issue_id in sorted(issues_dict.keys()):
+ severity = issues_dict[issue_id].severity
+ paths = issues_dict[issue_id].paths
+ issue = new_dom.createElement('issue')
+ issue.attributes['id'] = issue_id
+ if severity:
+ issue.attributes['severity'] = severity
+ if severity == 'ignore':
+ print 'Warning: [%s] is suppressed globally.' % issue_id
+ else:
+ for path in sorted(paths):
+ ignore = new_dom.createElement('ignore')
+ ignore.attributes['path'] = path
+ issue.appendChild(ignore)
+ top_element.appendChild(issue)
+
+ with open(config_path, 'w') as f:
+ f.write(new_dom.toprettyxml(indent=' ', encoding='utf-8'))
+ print 'Updated %s' % config_path
+
+
+def _Suppress(config_path, result_path):
+ issues_dict = _ParseConfigFile(config_path)
+ _ParseAndMergeResultFile(result_path, issues_dict)
+ _WriteConfigFile(config_path, issues_dict)
+
+
+def main():
+ parser = optparse.OptionParser(usage='%prog RESULT-FILE')
+ _, args = parser.parse_args()
+
+ if len(args) != 1 or not os.path.exists(args[0]):
+ parser.error('Must provide RESULT-FILE')
+
+ _Suppress(_CONFIG_PATH, args[0])
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
new file mode 100644
index 00000000000..cb77c1f1e1f
--- /dev/null
+++ b/build/android/lint/suppressions.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/android/lint_action.gypi b/build/android/lint_action.gypi
new file mode 100644
index 00000000000..e1adf1f842c
--- /dev/null
+++ b/build/android/lint_action.gypi
@@ -0,0 +1,43 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule to
+# run lint on java/class files.
+
+{
+ 'action_name': 'lint_<(_target_name)',
+ 'message': 'Linting <(_target_name)',
+ 'variables': {
+ 'conditions': [
+ ['chromium_code != 0 and android_lint != 0 and never_lint == 0', {
+ 'is_enabled': '--enable',
+ }, {
+ 'is_enabled': '',
+ }]
+ ],
+ 'android_manifest_path%': '<(DEPTH)/build/android/AndroidManifest.xml',
+ 'resource_dir%': '<(DEPTH)/build/android/ant/empty/res',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/lint.py',
+ '<(DEPTH)/build/android/lint/suppressions.xml',
+ '<(lint_jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/lint.py',
+ '--lint-path=<(android_sdk_root)/tools/lint',
+ '--config-path=<(DEPTH)/build/android/lint/suppressions.xml',
+ '--processed-config-path=<(config_path)',
+ '--manifest-path=<(android_manifest_path)',
+ '--result-path=<(result_path)',
+ '--resource-dir=<(resource_dir)',
+ '--product-dir=<(PRODUCT_DIR)',
+ '--src-dirs=>(src_dirs)',
+ '--jar-path=<(lint_jar_path)',
+ '--can-fail-build',
+ '--stamp=<(stamp_path)',
+ '<(is_enabled)',
+ ],
+}
diff --git a/build/android/locale_pak_resources.gypi b/build/android/locale_pak_resources.gypi
new file mode 100644
index 00000000000..6f8e56f8f15
--- /dev/null
+++ b/build/android/locale_pak_resources.gypi
@@ -0,0 +1,52 @@
+# Copyright 2015 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.
+
+# Creates a resources.zip with locale.pak files placed into appropriate
+# resource configs (e.g. en-GB.pak -> res/raw-en/en_gb.pak). Also generates
+# a locale_paks TypedArray so that resource files can be enumerated at runtime.
+#
+# If this target is included in the deps of an android resources/library/apk,
+# the resources will be included with that target.
+#
+# Variables:
+# locale_pak_files - List of .pak files to process.
+# Names must be of the form "en.pak" or "en-US.pak".
+#
+# Example
+# {
+# 'target_name': 'my_locale_resources',
+# 'type': 'none',
+# 'variables': {
+# 'locale_paks_files': ['path1/fr.pak'],
+# },
+# 'includes': [ '../build/android/locale_pak_resources.gypi' ],
+# },
+#
+{
+ 'variables': {
+ 'resources_zip_path': '<(PRODUCT_DIR)/res.java/<(_target_name).zip',
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'additional_input_paths': ['<(resources_zip_path)'],
+ 'dependencies_res_zip_paths': ['<(resources_zip_path)'],
+ },
+ },
+ 'actions': [{
+ 'action_name': '<(_target_name)_locale_pak_resources',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/locale_pak_resources.py',
+ '<@(locale_pak_files)',
+ ],
+ 'outputs': [
+ '<(resources_zip_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/locale_pak_resources.py',
+ '--locale-paks', '<(locale_pak_files)',
+ '--resources-zip', '<(resources_zip_path)',
+ ],
+ }],
+}
diff --git a/build/android/method_count.py b/build/android/method_count.py
new file mode 100755
index 00000000000..93250b5c542
--- /dev/null
+++ b/build/android/method_count.py
@@ -0,0 +1,55 @@
+#! /usr/bin/env python
+# Copyright 2015 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.
+
+import argparse
+import os
+import re
+import sys
+
+from pylib import constants
+from pylib.sdk import dexdump
+
+sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
+ 'common'))
+import perf_tests_results_helper
+
+
+_METHOD_IDS_SIZE_RE = re.compile(r'^method_ids_size +: +(\d+)$')
+
+def MethodCount(dexfile):
+ for line in dexdump.DexDump(dexfile, file_summary=True):
+ m = _METHOD_IDS_SIZE_RE.match(line)
+ if m:
+ return m.group(1)
+ raise Exception('"method_ids_size" not found in dex dump of %s' % dexfile)
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--apk-name', help='Name of the APK to which the dexfile corresponds.')
+ parser.add_argument('dexfile')
+
+ args = parser.parse_args()
+
+ if not args.apk_name:
+ dirname, basename = os.path.split(args.dexfile)
+ while basename:
+ if 'apk' in basename:
+ args.apk_name = basename
+ break
+ dirname, basename = os.path.split(dirname)
+ else:
+ parser.error(
+ 'Unable to determine apk name from %s, '
+ 'and --apk-name was not provided.' % args.dexfile)
+
+ method_count = MethodCount(args.dexfile)
+ perf_tests_results_helper.PrintPerfResult(
+ '%s_methods' % args.apk_name, 'total', [method_count], 'methods')
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/android/native_app_dependencies.gypi b/build/android/native_app_dependencies.gypi
new file mode 100644
index 00000000000..6032274dbba
--- /dev/null
+++ b/build/android/native_app_dependencies.gypi
@@ -0,0 +1,67 @@
+# Copyright 2013 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 file is meant to be included into a target to provide a rule
+# to strip and place dependent shared libraries required by a native binary in a
+# single folder that can later be pushed to the device.
+#
+# NOTE: consider packaging your binary as an apk instead of running a native
+# library.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'target_that_depends_on_my_binary',
+# 'type': 'none',
+# 'dependencies': [
+# 'my_binary',
+# ],
+# 'variables': {
+# 'native_binary': '<(PRODUCT_DIR)/my_binary',
+# 'output_dir': 'location to place binary and dependent libraries'
+# },
+# 'includes': [ '../../build/android/native_app_dependencies.gypi' ],
+# },
+#
+
+{
+ 'variables': {
+ 'include_main_binary%': 1,
+ },
+ 'conditions': [
+ ['component == "shared_library"', {
+ 'dependencies': [
+ '<(DEPTH)/build/android/setup.gyp:copy_system_libraries',
+ ],
+ 'variables': {
+ 'intermediate_dir': '<(PRODUCT_DIR)/<(_target_name)',
+ 'ordered_libraries_file': '<(intermediate_dir)/native_libraries.json',
+ },
+ 'actions': [
+ {
+ 'variables': {
+ 'input_libraries': ['<(native_binary)'],
+ },
+ 'includes': ['../../build/android/write_ordered_libraries.gypi'],
+ },
+ {
+ 'action_name': 'stripping native libraries',
+ 'variables': {
+ 'stripped_libraries_dir%': '<(output_dir)',
+ 'input_paths': ['<(native_binary)'],
+ 'stamp': '<(intermediate_dir)/strip.stamp',
+ },
+ 'includes': ['../../build/android/strip_native_libraries.gypi'],
+ },
+ ],
+ }],
+ ['include_main_binary==1', {
+ 'copies': [
+ {
+ 'destination': '<(output_dir)',
+ 'files': [ '<(native_binary)' ],
+ }
+ ],
+ }],
+ ],
+}
diff --git a/build/android/ndk.gyp b/build/android/ndk.gyp
new file mode 100644
index 00000000000..2838a9857e9
--- /dev/null
+++ b/build/android/ndk.gyp
@@ -0,0 +1,20 @@
+# Copyright (c) 2015 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'cpu_features',
+ 'type': 'static_library',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(android_ndk_root)/sources/android/cpufeatures',
+ ],
+ },
+ 'sources': [
+ '<(android_ndk_root)/sources/android/cpufeatures/cpu-features.c',
+ ],
+ },
+ ],
+}
diff --git a/build/android/pack_relocations.gypi b/build/android/pack_relocations.gypi
new file mode 100644
index 00000000000..8567fa6e45f
--- /dev/null
+++ b/build/android/pack_relocations.gypi
@@ -0,0 +1,77 @@
+# Copyright 2014 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 file is meant to be included into an action to provide a rule that
+# packs relocations in Release builds of native libraries.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'action_name': 'pack_relocations',
+# 'actions': [
+# 'variables': {
+# 'enable_packing': 'pack relocations if 1, plain file copy if 0'
+# 'exclude_packing_list': 'names of libraries explicitly not packed',
+# 'ordered_libraries_file': 'file generated by write_ordered_libraries'
+# 'input_paths': 'files to be added to the list of inputs'
+# 'stamp': 'file to touch when the action is complete'
+# 'stripped_libraries_dir': 'directory holding stripped libraries',
+# 'packed_libraries_dir': 'directory holding packed libraries',
+# 'includes': [ '../../build/android/pack_relocations.gypi' ],
+# ],
+# },
+#
+
+{
+ 'variables': {
+ 'input_paths': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/pack_relocations.py',
+ '<(ordered_libraries_file)',
+ '>@(input_paths)',
+ ],
+ 'outputs': [
+ '<(stamp)',
+ ],
+ 'conditions': [
+ ['enable_packing == 1', {
+ 'message': 'Packing relocations for <(_target_name)',
+ 'dependencies': [
+ '<(DEPTH)/third_party/android_platform/relocation_packer.gyp:android_relocation_packer#host',
+ ],
+ 'inputs': [
+ '<(PRODUCT_DIR)/android_relocation_packer',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/pack_relocations.py',
+ '--configuration-name=<(CONFIGURATION_NAME)',
+ '--enable-packing=1',
+ '--exclude-packing-list=<@(exclude_packing_list)',
+ '--android-pack-relocations=<(PRODUCT_DIR)/android_relocation_packer',
+ '--stripped-libraries-dir=<(stripped_libraries_dir)',
+ '--packed-libraries-dir=<(packed_libraries_dir)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--stamp=<(stamp)',
+ ],
+ }, {
+ 'message': 'Copying libraries (no relocation packing) for <(_target_name)',
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/pack_relocations.py',
+ '--configuration-name=<(CONFIGURATION_NAME)',
+ '--enable-packing=0',
+ '--stripped-libraries-dir=<(stripped_libraries_dir)',
+ '--packed-libraries-dir=<(packed_libraries_dir)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--stamp=<(stamp)',
+ ],
+ }],
+ ['component == "shared_library"', {
+ # Add a fake output to force the build to always re-run this step. This
+ # is required because the real inputs are not known at gyp-time and
+ # changing base.so may not trigger changes to dependent libraries.
+ 'outputs': [ '<(stamp).fake' ]
+ }],
+ ],
+}
diff --git a/build/android/package_resources_action.gypi b/build/android/package_resources_action.gypi
new file mode 100644
index 00000000000..eb608718951
--- /dev/null
+++ b/build/android/package_resources_action.gypi
@@ -0,0 +1,97 @@
+# Copyright 2015 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 file is a helper to java_apk.gypi. It should be used to create an
+# action that runs ApkBuilder via ANT.
+#
+# Required variables:
+# apk_name - File name (minus path & extension) of the output apk.
+# android_manifest_path - Path to AndroidManifest.xml.
+# app_manifest_version_name - set the apps 'human readable' version number.
+# app_manifest_version_code - set the apps version number.
+# Optional variables:
+# asset_location - The directory where assets are located (if any).
+# create_density_splits - Whether to create density-based apk splits. Splits
+# are supported only for minSdkVersion >= 21.
+# language_splits - List of languages to create apk splits for.
+# resource_zips - List of paths to resource zip files.
+# shared_resources - Make a resource package that can be loaded by a different
+# application at runtime to access the package's resources.
+# extensions_to_not_compress - E.g.: 'pak,dat,bin'
+# extra_inputs - List of extra action inputs.
+{
+ 'variables': {
+ 'asset_location%': '',
+ 'create_density_splits%': 0,
+ 'resource_zips%': [],
+ 'shared_resources%': 0,
+ 'extensions_to_not_compress%': '',
+ 'extra_inputs%': [],
+ 'resource_packaged_apk_name': '<(apk_name)-resources.ap_',
+ 'resource_packaged_apk_path': '<(intermediate_dir)/<(resource_packaged_apk_name)',
+ },
+ 'action_name': 'package_resources_<(apk_name)',
+ 'message': 'packaging resources for <(apk_name)',
+ 'inputs': [
+ # TODO: This isn't always rerun correctly, http://crbug.com/351928
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/package_resources.py',
+ '<(android_manifest_path)',
+ '<@(extra_inputs)',
+ ],
+ 'outputs': [
+ '<(resource_packaged_apk_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/package_resources.py',
+ '--android-sdk', '<(android_sdk)',
+ '--aapt-path', '<(android_aapt_path)',
+ '--configuration-name', '<(CONFIGURATION_NAME)',
+ '--android-manifest', '<(android_manifest_path)',
+ '--version-code', '<(app_manifest_version_code)',
+ '--version-name', '<(app_manifest_version_name)',
+ '--no-compress', '<(extensions_to_not_compress)',
+ '--apk-path', '<(resource_packaged_apk_path)',
+ ],
+ 'conditions': [
+ ['shared_resources == 1', {
+ 'action': [
+ '--shared-resources',
+ ],
+ }],
+ ['asset_location != ""', {
+ 'action': [
+ '--asset-dir', '<(asset_location)',
+ ],
+ }],
+ ['create_density_splits == 1', {
+ 'action': [
+ '--create-density-splits',
+ ],
+ 'outputs': [
+ '<(resource_packaged_apk_path)_hdpi',
+ '<(resource_packaged_apk_path)_xhdpi',
+ '<(resource_packaged_apk_path)_xxhdpi',
+ '<(resource_packaged_apk_path)_xxxhdpi',
+ '<(resource_packaged_apk_path)_tvdpi',
+ ],
+ }],
+ ['language_splits != []', {
+ 'action': [
+ '--language-splits=<(language_splits)',
+ ],
+ 'outputs': [
+ "(resource_zips)',
+ ],
+ 'inputs': [
+ '>@(resource_zips)',
+ ],
+ }],
+ ],
+}
diff --git a/build/android/preprocess_google_play_services.config.json b/build/android/preprocess_google_play_services.config.json
new file mode 100644
index 00000000000..8b3198be7ae
--- /dev/null
+++ b/build/android/preprocess_google_play_services.config.json
@@ -0,0 +1,90 @@
+{
+ "lib_version": "7.3.0",
+ "clients": [
+ "play-services-base",
+ "play-services-cast",
+ "play-services-identity"
+ ],
+ "client_filter": [
+ "res/drawable*",
+ "res/values-af",
+ "res/values-az",
+ "res/values-be",
+ "res/values-bn",
+ "res/values-bn-rBD",
+ "res/values-de-rAT",
+ "res/values-de-rCH",
+ "res/values-en-rIE",
+ "res/values-en-rIN",
+ "res/values-en-rSG",
+ "res/values-en-rZA",
+ "res/values-es-rAR",
+ "res/values-es-rBO",
+ "res/values-es-rCL",
+ "res/values-es-rCO",
+ "res/values-es-rCR",
+ "res/values-es-rDO",
+ "res/values-es-rEC",
+ "res/values-es-rGT",
+ "res/values-es-rHN",
+ "res/values-es-rMX",
+ "res/values-es-rNI",
+ "res/values-es-rPA",
+ "res/values-es-rPE",
+ "res/values-es-rPR",
+ "res/values-es-rPY",
+ "res/values-es-rSV",
+ "res/values-es-rUS",
+ "res/values-es-rUY",
+ "res/values-es-rVE",
+ "res/values-eu-rES",
+ "res/values-fr-rCA",
+ "res/values-fr-rCH",
+ "res/values-gl",
+ "res/values-gl-rES",
+ "res/values-gu",
+ "res/values-he",
+ "res/values-hy",
+ "res/values-hy-rAM",
+ "res/values-in",
+ "res/values-is",
+ "res/values-is-rIS",
+ "res/values-ka",
+ "res/values-ka-rGE",
+ "res/values-kk-rKZ",
+ "res/values-km",
+ "res/values-km-rKH",
+ "res/values-kn",
+ "res/values-kn-rIN",
+ "res/values-ky",
+ "res/values-ky-rKG",
+ "res/values-lo",
+ "res/values-lo-rLA",
+ "res/values-mk-rMK",
+ "res/values-ml",
+ "res/values-ml-rIN",
+ "res/values-mn",
+ "res/values-mn-rMN",
+ "res/values-mo",
+ "res/values-mr",
+ "res/values-mr-rIN",
+ "res/values-ms",
+ "res/values-ms-rMY",
+ "res/values-my-rMM",
+ "res/values-nb",
+ "res/values-ne",
+ "res/values-ne-rNP",
+ "res/values-si",
+ "res/values-si-rLK",
+ "res/values-ta",
+ "res/values-ta-rIN",
+ "res/values-te",
+ "res/values-te-rIN",
+ "res/values-tl",
+ "res/values-ur-rPK",
+ "res/values-uz-rUZ",
+ "res/values-zh",
+ "res/values-zh-rHK",
+ "res/values-zu"
+ ]
+}
diff --git a/build/android/preprocess_google_play_services.py b/build/android/preprocess_google_play_services.py
new file mode 100755
index 00000000000..85d239ad302
--- /dev/null
+++ b/build/android/preprocess_google_play_services.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 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.
+
+'''Prepares the Google Play services split client libraries before usage by
+Chrome's build system.
+
+We need to preprocess Google Play services before using it in Chrome
+builds for 2 main reasons:
+
+- Getting rid of unused resources: unsupported languages, unused
+drawables, etc.
+
+- Merging the differents jars so that it can be proguarded more
+easily. This is necessary since debug and test apks get very close
+to the dex limit.
+
+The script is supposed to be used with the maven repository that can be obtained
+by downloading the "extra-google-m2repository" from the Android SDK Manager. It
+also supports importing from already extracted AAR files using the
+--is-extracted-repo flag.
+
+The json config (see the -c argument) file should provide the following fields:
+
+- lib_version: String. Used when building from the maven repository. It should
+ be the package's version (e.g. "7.3.0")
+
+- clients: String array. List of clients to pick. For example, when building
+ from the maven repository, it's the artifactId (e.g. "play-services-base") of
+ each client.
+
+- client_filter: String array. Pattern of files to prune from the clients once
+ extracted. Metacharacters are allowed. (e.g. "res/drawable*")
+
+The output is a directory with the following structure:
+
+ OUT_DIR
+ +-- google-play-services.jar
+ +-- res
+ | +-- CLIENT_1
+ | | +-- color
+ | | +-- values
+ | | +-- etc.
+ | +-- CLIENT_2
+ | +-- ...
+ +-- stub
+ +-- res/[.git-keep-directory]
+ +-- src/android/UnusedStub.java
+
+Requires the `jar` utility in the path.
+
+'''
+
+import argparse
+import glob
+import itertools
+import json
+import os
+import shutil
+import stat
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+
+sys.path.append(
+ os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', 'gyp'))
+from util import build_utils
+
+
+M2_PKG_PATH = os.path.join('com', 'google', 'android', 'gms')
+
+
+def main():
+ parser = argparse.ArgumentParser(description=("Prepares the Google Play "
+ "services split client libraries before usage by Chrome's build system"))
+ parser.add_argument('-r',
+ '--repository',
+ help='The Google Play services repository location',
+ required=True,
+ metavar='FILE')
+ parser.add_argument('-o',
+ '--out-dir',
+ help='The output directory',
+ required=True,
+ metavar='FILE')
+ parser.add_argument('-c',
+ '--config-file',
+ help='Config file path',
+ required=True,
+ metavar='FILE')
+ parser.add_argument('-g',
+ '--git-friendly',
+ action='store_true',
+ default=False,
+ help='Add a .gitkeep file to the empty directories')
+ parser.add_argument('-x',
+ '--is-extracted-repo',
+ action='store_true',
+ default=False,
+ help='The provided repository is not made of AAR files.')
+
+ args = parser.parse_args()
+
+ ProcessGooglePlayServices(args.repository,
+ args.out_dir,
+ args.config_file,
+ args.git_friendly,
+ args.is_extracted_repo)
+
+
+def ProcessGooglePlayServices(repo, out_dir, config_path, git_friendly,
+ is_extracted_repo):
+ with open(config_path, 'r') as json_file:
+ config = json.load(json_file)
+
+ with build_utils.TempDir() as tmp_root:
+ tmp_paths = _SetupTempDir(tmp_root)
+
+ if is_extracted_repo:
+ _ImportFromExtractedRepo(config, tmp_paths, repo)
+ else:
+ _ImportFromAars(config, tmp_paths, repo)
+
+ _GenerateCombinedJar(tmp_paths)
+ _ProcessResources(config, tmp_paths)
+ _BuildOutput(config, tmp_paths, out_dir, git_friendly)
+
+
+def _SetupTempDir(tmp_root):
+ tmp_paths = {
+ 'root': tmp_root,
+ 'imported_clients': os.path.join(tmp_root, 'imported_clients'),
+ 'extracted_jars': os.path.join(tmp_root, 'jar'),
+ 'combined_jar': os.path.join(tmp_root, 'google-play-services.jar'),
+ }
+ os.mkdir(tmp_paths['imported_clients'])
+ os.mkdir(tmp_paths['extracted_jars'])
+
+ return tmp_paths
+
+
+def _SetupOutputDir(out_dir):
+ out_paths = {
+ 'root': out_dir,
+ 'res': os.path.join(out_dir, 'res'),
+ 'jar': os.path.join(out_dir, 'google-play-services.jar'),
+ 'stub': os.path.join(out_dir, 'stub'),
+ }
+
+ shutil.rmtree(out_paths['jar'], ignore_errors=True)
+ shutil.rmtree(out_paths['res'], ignore_errors=True)
+ shutil.rmtree(out_paths['stub'], ignore_errors=True)
+
+ return out_paths
+
+
+def _MakeWritable(dir_path):
+ for root, dirs, files in os.walk(dir_path):
+ for path in itertools.chain(dirs, files):
+ st = os.stat(os.path.join(root, path))
+ os.chmod(os.path.join(root, path), st.st_mode | stat.S_IWUSR)
+
+
+def _ImportFromAars(config, tmp_paths, repo):
+ for client in config['clients']:
+ aar_name = '%s-%s.aar' % (client, config['lib_version'])
+ aar_path = os.path.join(repo, M2_PKG_PATH, client,
+ config['lib_version'], aar_name)
+ aar_out_path = os.path.join(tmp_paths['imported_clients'], client)
+ build_utils.ExtractAll(aar_path, aar_out_path)
+
+ client_jar_path = os.path.join(aar_out_path, 'classes.jar')
+ build_utils.ExtractAll(client_jar_path, tmp_paths['extracted_jars'],
+ no_clobber=False)
+
+
+def _ImportFromExtractedRepo(config, tmp_paths, repo):
+ # Import the clients
+ try:
+ for client in config['clients']:
+ client_out_dir = os.path.join(tmp_paths['imported_clients'], client)
+ shutil.copytree(os.path.join(repo, client), client_out_dir)
+
+ client_jar_path = os.path.join(client_out_dir, 'classes.jar')
+ build_utils.ExtractAll(client_jar_path, tmp_paths['extracted_jars'],
+ no_clobber=False)
+ finally:
+ _MakeWritable(tmp_paths['imported_clients'])
+
+
+def _GenerateCombinedJar(tmp_paths):
+ out_file_name = tmp_paths['combined_jar']
+ working_dir = tmp_paths['extracted_jars']
+ cmd_helper.Call(['jar', '-cf', out_file_name, '-C', working_dir, '.'])
+
+
+def _ProcessResources(config, tmp_paths):
+ # Prune unused resources
+ for res_filter in config['client_filter']:
+ glob_pattern = os.path.join(tmp_paths['imported_clients'], '*', res_filter)
+ for prune_target in glob.glob(glob_pattern):
+ shutil.rmtree(prune_target)
+
+
+def _BuildOutput(config, tmp_paths, out_dir, git_friendly):
+ out_paths = _SetupOutputDir(out_dir)
+
+ # Copy the resources to the output dir
+ for client in config['clients']:
+ res_in_tmp_dir = os.path.join(tmp_paths['imported_clients'], client, 'res')
+ if os.path.isdir(res_in_tmp_dir) and os.listdir(res_in_tmp_dir):
+ res_in_final_dir = os.path.join(out_paths['res'], client)
+ shutil.copytree(res_in_tmp_dir, res_in_final_dir)
+
+ # Copy the jar
+ shutil.copyfile(tmp_paths['combined_jar'], out_paths['jar'])
+
+ # Write the java dummy stub. Needed for gyp to create the resource jar
+ stub_location = os.path.join(out_paths['stub'], 'src', 'android')
+ os.makedirs(stub_location)
+ with open(os.path.join(stub_location, 'UnusedStub.java'), 'w') as stub:
+ stub.write('package android;'
+ 'public final class UnusedStub {'
+ ' private UnusedStub() {}'
+ '}')
+
+ # Create the main res directory. Will be empty but is needed by gyp
+ stub_res_location = os.path.join(out_paths['stub'], 'res')
+ os.makedirs(stub_res_location)
+ if git_friendly:
+ build_utils.Touch(os.path.join(stub_res_location, '.git-keep-directory'))
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/provision_devices.py b/build/android/provision_devices.py
new file mode 100755
index 00000000000..a5f8fc67236
--- /dev/null
+++ b/build/android/provision_devices.py
@@ -0,0 +1,349 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 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.
+
+"""Provisions Android devices with settings required for bots.
+
+Usage:
+ ./provision_devices.py [-d ]
+"""
+
+import argparse
+import json
+import logging
+import os
+import posixpath
+import re
+import subprocess
+import sys
+import time
+
+from pylib import constants
+from pylib import device_settings
+from pylib.device import battery_utils
+from pylib.device import device_blacklist
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import run_tests_helper
+from pylib.utils import timeout_retry
+
+sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
+ 'third_party', 'android_testrunner'))
+import errors
+
+
+class _DEFAULT_TIMEOUTS(object):
+ # L can take a while to reboot after a wipe.
+ LOLLIPOP = 600
+ PRE_LOLLIPOP = 180
+
+ HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
+
+
+class _PHASES(object):
+ WIPE = 'wipe'
+ PROPERTIES = 'properties'
+ FINISH = 'finish'
+
+ ALL = [WIPE, PROPERTIES, FINISH]
+
+
+def ProvisionDevices(options):
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if options.device:
+ devices = [d for d in devices if d == options.device]
+ if not devices:
+ raise device_errors.DeviceUnreachableError(options.device)
+
+ parallel_devices = device_utils.DeviceUtils.parallel(devices)
+ parallel_devices.pMap(ProvisionDevice, options)
+ if options.auto_reconnect:
+ _LaunchHostHeartbeat()
+ blacklist = device_blacklist.ReadBlacklist()
+ if options.output_device_blacklist:
+ with open(options.output_device_blacklist, 'w') as f:
+ json.dump(blacklist, f)
+ if all(d in blacklist for d in devices):
+ raise device_errors.NoDevicesError
+ return 0
+
+
+def ProvisionDevice(device, options):
+ if options.reboot_timeout:
+ reboot_timeout = options.reboot_timeout
+ elif (device.build_version_sdk >=
+ constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
+ reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
+ else:
+ reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
+
+ def should_run_phase(phase_name):
+ return not options.phases or phase_name in options.phases
+
+ def run_phase(phase_func, reboot=True):
+ try:
+ device.WaitUntilFullyBooted(timeout=reboot_timeout, retries=0)
+ except device_errors.CommandTimeoutError:
+ logging.error('Device did not finish booting. Will try to reboot.')
+ device.Reboot(timeout=reboot_timeout)
+ phase_func(device, options)
+ if reboot:
+ device.Reboot(False, retries=0)
+ device.adb.WaitForDevice()
+
+ try:
+ if should_run_phase(_PHASES.WIPE):
+ run_phase(WipeDevice)
+
+ if should_run_phase(_PHASES.PROPERTIES):
+ run_phase(SetProperties)
+
+ if should_run_phase(_PHASES.FINISH):
+ run_phase(FinishProvisioning, reboot=False)
+
+ except (errors.WaitForResponseTimedOutError,
+ device_errors.CommandTimeoutError):
+ logging.exception('Timed out waiting for device %s. Adding to blacklist.',
+ str(device))
+ device_blacklist.ExtendBlacklist([str(device)])
+
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to provision device %s. Adding to blacklist.',
+ str(device))
+ device_blacklist.ExtendBlacklist([str(device)])
+
+
+def WipeDevice(device, options):
+ """Wipes data from device, keeping only the adb_keys for authorization.
+
+ After wiping data on a device that has been authorized, adb can still
+ communicate with the device, but after reboot the device will need to be
+ re-authorized because the adb keys file is stored in /data/misc/adb/.
+ Thus, adb_keys file is rewritten so the device does not need to be
+ re-authorized.
+
+ Arguments:
+ device: the device to wipe
+ """
+ if options.skip_wipe:
+ return
+
+ try:
+ device.EnableRoot()
+ device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
+ if device_authorized:
+ adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
+ as_root=True).splitlines()
+ device.RunShellCommand(['wipe', 'data'],
+ as_root=True, check_return=True)
+ device.adb.WaitForDevice()
+
+ if device_authorized:
+ adb_keys_set = set(adb_keys)
+ for adb_key_file in options.adb_key_files or []:
+ try:
+ with open(adb_key_file, 'r') as f:
+ adb_public_keys = f.readlines()
+ adb_keys_set.update(adb_public_keys)
+ except IOError:
+ logging.warning('Unable to find adb keys file %s.' % adb_key_file)
+ _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
+ except device_errors.CommandFailedError:
+ logging.exception('Possible failure while wiping the device. '
+ 'Attempting to continue.')
+
+
+def _WriteAdbKeysFile(device, adb_keys_string):
+ dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
+ device.RunShellCommand(['mkdir', '-p', dir_path],
+ as_root=True, check_return=True)
+ device.RunShellCommand(['restorecon', dir_path],
+ as_root=True, check_return=True)
+ device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
+ device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
+ as_root=True, check_return=True)
+
+
+def SetProperties(device, options):
+ try:
+ device.EnableRoot()
+ except device_errors.CommandFailedError as e:
+ logging.warning(str(e))
+
+ _ConfigureLocalProperties(device, options.enable_java_debug)
+ device_settings.ConfigureContentSettings(
+ device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
+ if options.disable_location:
+ device_settings.ConfigureContentSettings(
+ device, device_settings.DISABLE_LOCATION_SETTINGS)
+ else:
+ device_settings.ConfigureContentSettings(
+ device, device_settings.ENABLE_LOCATION_SETTINGS)
+
+ if options.disable_mock_location:
+ device_settings.ConfigureContentSettings(
+ device, device_settings.DISABLE_MOCK_LOCATION_SETTINGS)
+ else:
+ device_settings.ConfigureContentSettings(
+ device, device_settings.ENABLE_MOCK_LOCATION_SETTINGS)
+
+ device_settings.SetLockScreenSettings(device)
+ if options.disable_network:
+ device_settings.ConfigureContentSettings(
+ device, device_settings.NETWORK_DISABLED_SETTINGS)
+
+def _ConfigureLocalProperties(device, java_debug=True):
+ """Set standard readonly testing device properties prior to reboot."""
+ local_props = [
+ 'persist.sys.usb.config=adb',
+ 'ro.monkey=1',
+ 'ro.test_harness=1',
+ 'ro.audio.silent=1',
+ 'ro.setupwizard.mode=DISABLED',
+ ]
+ if java_debug:
+ local_props.append(
+ '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
+ local_props.append('debug.checkjni=1')
+ try:
+ device.WriteFile(
+ constants.DEVICE_LOCAL_PROPERTIES_PATH,
+ '\n'.join(local_props), as_root=True)
+ # Android will not respect the local props file if it is world writable.
+ device.RunShellCommand(
+ ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
+ as_root=True, check_return=True)
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to configure local properties.')
+
+
+def FinishProvisioning(device, options):
+ if options.min_battery_level is not None:
+ try:
+ battery = battery_utils.BatteryUtils(device)
+ battery.ChargeDeviceToLevel(options.min_battery_level)
+ except device_errors.CommandFailedError:
+ logging.exception('Unable to charge device to specified level.')
+
+ if options.max_battery_temp is not None:
+ try:
+ battery = battery_utils.BatteryUtils(device)
+ battery.LetBatteryCoolToTemperature(options.max_battery_temp)
+ except device_errors.CommandFailedError:
+ logging.exception('Unable to let battery cool to specified temperature.')
+
+ device.RunShellCommand(
+ ['date', '-s', time.strftime('%Y%m%d.%H%M%S', time.gmtime())],
+ as_root=True, check_return=True)
+ props = device.RunShellCommand('getprop', check_return=True)
+ for prop in props:
+ logging.info(' %s' % prop)
+ if options.auto_reconnect:
+ _PushAndLaunchAdbReboot(device, options.target)
+
+
+def _PushAndLaunchAdbReboot(device, target):
+ """Pushes and launches the adb_reboot binary on the device.
+
+ Arguments:
+ device: The DeviceUtils instance for the device to which the adb_reboot
+ binary should be pushed.
+ target: The build target (example, Debug or Release) which helps in
+ locating the adb_reboot binary.
+ """
+ logging.info('Will push and launch adb_reboot on %s' % str(device))
+ # Kill if adb_reboot is already running.
+ device.KillAll('adb_reboot', blocking=True, timeout=2, quiet=True)
+ # Push adb_reboot
+ logging.info(' Pushing adb_reboot ...')
+ adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
+ 'out/%s/adb_reboot' % target)
+ device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
+ # Launch adb_reboot
+ logging.info(' Launching adb_reboot ...')
+ device.RunShellCommand(
+ ['/data/local/tmp/adb_reboot'],
+ check_return=True)
+
+
+def _LaunchHostHeartbeat():
+ # Kill if existing host_heartbeat
+ KillHostHeartbeat()
+ # Launch a new host_heartbeat
+ logging.info('Spawning host heartbeat...')
+ subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
+ 'build/android/host_heartbeat.py')])
+
+
+def KillHostHeartbeat():
+ ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+ stdout, _ = ps.communicate()
+ matches = re.findall('\\n.*host_heartbeat.*', stdout)
+ for match in matches:
+ logging.info('An instance of host heart beart running... will kill')
+ pid = re.findall(r'(\S+)', match)[1]
+ subprocess.call(['kill', str(pid)])
+
+
+def main():
+ # Recommended options on perf bots:
+ # --disable-network
+ # TODO(tonyg): We eventually want network on. However, currently radios
+ # can cause perfbots to drain faster than they charge.
+ # --min-battery-level 95
+ # Some perf bots run benchmarks with USB charging disabled which leads
+ # to gradual draining of the battery. We must wait for a full charge
+ # before starting a run in order to keep the devices online.
+
+ parser = argparse.ArgumentParser(
+ description='Provision Android devices with settings required for bots.')
+ parser.add_argument('-d', '--device', metavar='SERIAL',
+ help='the serial number of the device to be provisioned'
+ ' (the default is to provision all devices attached)')
+ parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
+ dest='phases',
+ help='Phases of provisioning to run. '
+ '(If omitted, all phases will be run.)')
+ parser.add_argument('--skip-wipe', action='store_true', default=False,
+ help="don't wipe device data during provisioning")
+ parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
+ help='when wiping the device, max number of seconds to'
+ ' wait after each reboot '
+ '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
+ parser.add_argument('--min-battery-level', type=int, metavar='NUM',
+ help='wait for the device to reach this minimum battery'
+ ' level before trying to continue')
+ parser.add_argument('--disable-location', action='store_true',
+ help='disable Google location services on devices')
+ parser.add_argument('--disable-mock-location', action='store_true',
+ default=False, help='Set ALLOW_MOCK_LOCATION to false')
+ parser.add_argument('--disable-network', action='store_true',
+ help='disable network access on devices')
+ parser.add_argument('--disable-java-debug', action='store_false',
+ dest='enable_java_debug', default=True,
+ help='disable Java property asserts and JNI checking')
+ parser.add_argument('-t', '--target', default='Debug',
+ help='the build target (default: %(default)s)')
+ parser.add_argument('-r', '--auto-reconnect', action='store_true',
+ help='push binary which will reboot the device on adb'
+ ' disconnections')
+ parser.add_argument('--adb-key-files', type=str, nargs='+',
+ help='list of adb keys to push to device')
+ parser.add_argument('-v', '--verbose', action='count', default=1,
+ help='Log more information.')
+ parser.add_argument('--max-battery-temp', type=int, metavar='NUM',
+ help='Wait for the battery to have this temp or lower.')
+ parser.add_argument('--output-device-blacklist',
+ help='Json file to output the device blacklist.')
+ args = parser.parse_args()
+ constants.SetBuildType(args.target)
+
+ run_tests_helper.SetLogLevel(args.verbose)
+
+ return ProvisionDevices(args)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/push_libraries.gypi b/build/android/push_libraries.gypi
new file mode 100644
index 00000000000..773c44f8ab1
--- /dev/null
+++ b/build/android/push_libraries.gypi
@@ -0,0 +1,49 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule that
+# pushes stripped shared libraries to the attached Android device. This should
+# only be used with the gyp_managed_install flag set.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'actions': [
+# 'variables': {
+# 'ordered_libraries_file': 'file generated by write_ordered_libraries'
+# 'strip_stamp': 'stamp from strip action to block on'
+# 'libraries_source_dir': 'location where stripped libraries are stored'
+# 'device_library_dir': 'location on the device where to put pushed libraries',
+# 'push_stamp': 'file to touch when the action is complete'
+# 'configuration_name': 'The build CONFIGURATION_NAME'
+# },
+# 'includes': [ '../../build/android/push_libraries.gypi' ],
+# ],
+# },
+#
+
+{
+ 'action_name': 'push_libraries_<(_target_name)',
+ 'message': 'Pushing libraries to device for <(_target_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/util/md5_check.py',
+ '<(DEPTH)/build/android/gyp/push_libraries.py',
+ '<(strip_stamp)',
+ '<(strip_additional_stamp)',
+ '<(build_device_config_path)',
+ '<(pack_relocations_stamp)',
+ ],
+ 'outputs': [
+ '<(push_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/push_libraries.py',
+ '--build-device-configuration=<(build_device_config_path)',
+ '--libraries-dir=<(libraries_source_dir)',
+ '--device-dir=<(device_library_dir)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--stamp=<(push_stamp)',
+ '--configuration-name=<(configuration_name)',
+ ],
+}
diff --git a/build/android/pylib/OWNERS b/build/android/pylib/OWNERS
new file mode 100644
index 00000000000..dbbbba7f275
--- /dev/null
+++ b/build/android/pylib/OWNERS
@@ -0,0 +1,4 @@
+jbudorick@chromium.org
+klundberg@chromium.org
+navabi@chromium.org
+skyostil@chromium.org
diff --git a/build/android/pylib/__init__.py b/build/android/pylib/__init__.py
new file mode 100644
index 00000000000..96196cffb27
--- /dev/null
+++ b/build/android/pylib/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2012 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.
diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py
new file mode 100644
index 00000000000..f7191f7935d
--- /dev/null
+++ b/build/android/pylib/android_commands.py
@@ -0,0 +1,1976 @@
+# Copyright (c) 2012 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.
+
+"""Provides an interface to communicate with the device via the adb command.
+
+Assumes adb binary is currently on system path.
+
+Note that this module is deprecated.
+"""
+# TODO(jbudorick): Delete this file once no clients use it.
+
+# pylint: skip-file
+
+import collections
+import datetime
+import inspect
+import logging
+import os
+import random
+import re
+import shlex
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+
+import cmd_helper
+import constants
+import system_properties
+from utils import host_utils
+
+try:
+ from pylib import pexpect
+except ImportError:
+ pexpect = None
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
+import adb_interface
+import am_instrument_parser
+import errors
+
+from pylib.device import device_blacklist
+from pylib.device import device_errors
+
+# Pattern to search for the next whole line of pexpect output and capture it
+# into a match group. We can't use ^ and $ for line start end with pexpect,
+# see http://www.noah.org/python/pexpect/#doc for explanation why.
+PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
+
+# Set the adb shell prompt to be a unique marker that will [hopefully] not
+# appear at the start of any line of a command's output.
+SHELL_PROMPT = '~+~PQ\x17RS~+~'
+
+# Java properties file
+LOCAL_PROPERTIES_PATH = constants.DEVICE_LOCAL_PROPERTIES_PATH
+
+# Property in /data/local.prop that controls Java assertions.
+JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
+
+# Keycode "enum" suitable for passing to AndroidCommands.SendKey().
+KEYCODE_HOME = 3
+KEYCODE_BACK = 4
+KEYCODE_DPAD_UP = 19
+KEYCODE_DPAD_DOWN = 20
+KEYCODE_DPAD_RIGHT = 22
+KEYCODE_ENTER = 66
+KEYCODE_MENU = 82
+
+MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
+MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
+
+PIE_WRAPPER_PATH = constants.TEST_EXECUTABLE_DIR + '/run_pie'
+
+CONTROL_USB_CHARGING_COMMANDS = [
+ {
+ # Nexus 4
+ 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
+ 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
+ 'disable_command':
+ 'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
+ },
+ {
+ # Nexus 5
+ # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
+ # energy coming from USB. Setting the power_supply offline just updates the
+ # Android system to reflect that.
+ 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
+ 'enable_command': (
+ 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'echo 1 > /sys/class/power_supply/usb/online'),
+ 'disable_command': (
+ 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'chmod 644 /sys/class/power_supply/usb/online && '
+ 'echo 0 > /sys/class/power_supply/usb/online'),
+ },
+]
+
+class DeviceTempFile(object):
+ def __init__(self, android_commands, prefix='temp_file', suffix=''):
+ """Find an unused temporary file path in the devices external directory.
+
+ When this object is closed, the file will be deleted on the device.
+ """
+ self.android_commands = android_commands
+ while True:
+ # TODO(cjhopman): This could actually return the same file in multiple
+ # calls if the caller doesn't write to the files immediately. This is
+ # expected to never happen.
+ i = random.randint(0, 1000000)
+ self.name = '%s/%s-%d-%010d%s' % (
+ android_commands.GetExternalStorage(),
+ prefix, int(time.time()), i, suffix)
+ if not android_commands.FileExistsOnDevice(self.name):
+ break
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+ def close(self):
+ self.android_commands.RunShellCommand('rm ' + self.name)
+
+
+def GetAVDs():
+ """Returns a list of AVDs."""
+ re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
+ avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
+ return avds
+
+def ResetBadDevices():
+ """Removes the blacklist that keeps track of bad devices for a current
+ build.
+ """
+ device_blacklist.ResetBlacklist()
+
+def ExtendBadDevices(devices):
+ """Adds devices to the blacklist that keeps track of bad devices for a
+ current build.
+
+ The devices listed in the bad devices file will not be returned by
+ GetAttachedDevices.
+
+ Args:
+ devices: list of bad devices to be added to the bad devices file.
+ """
+ device_blacklist.ExtendBlacklist(devices)
+
+
+def GetAttachedDevices(hardware=True, emulator=True, offline=False):
+ """Returns a list of attached, android devices and emulators.
+
+ If a preferred device has been set with ANDROID_SERIAL, it will be first in
+ the returned list. The arguments specify what devices to include in the list.
+
+ Example output:
+
+ * daemon not running. starting it now on port 5037 *
+ * daemon started successfully *
+ List of devices attached
+ 027c10494100b4d7 device
+ emulator-5554 offline
+
+ Args:
+ hardware: Include attached actual devices that are online.
+ emulator: Include emulators (i.e. AVD's) currently on host.
+ offline: Include devices and emulators that are offline.
+
+ Returns: List of devices.
+ """
+ adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(),
+ 'devices'])
+
+ re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
+ online_devices = re_device.findall(adb_devices_output)
+
+ re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE)
+ emulator_devices = re_device.findall(adb_devices_output)
+
+ re_device = re.compile('^([a-zA-Z0-9_:.-]+)\t(?:offline|unauthorized)$',
+ re.MULTILINE)
+ offline_devices = re_device.findall(adb_devices_output)
+
+ devices = []
+ # First determine list of online devices (e.g. hardware and/or emulator).
+ if hardware and emulator:
+ devices = online_devices
+ elif hardware:
+ devices = [device for device in online_devices
+ if device not in emulator_devices]
+ elif emulator:
+ devices = emulator_devices
+
+ # Now add offline devices if offline is true
+ if offline:
+ devices = devices + offline_devices
+
+ # Remove any devices in the blacklist.
+ blacklist = device_blacklist.ReadBlacklist()
+ if len(blacklist):
+ logging.info('Avoiding bad devices %s', ' '.join(blacklist))
+ devices = [device for device in devices if device not in blacklist]
+
+ preferred_device = os.environ.get('ANDROID_SERIAL')
+ if preferred_device in devices:
+ devices.remove(preferred_device)
+ devices.insert(0, preferred_device)
+ return devices
+
+
+def IsDeviceAttached(device):
+ """Return true if the device is attached and online."""
+ return device in GetAttachedDevices()
+
+
+def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
+ """Gets a list of files from `ls` command output.
+
+ Python's os.walk isn't used because it doesn't work over adb shell.
+
+ Args:
+ path: The path to list.
+ ls_output: A list of lines returned by an `ls -lR` command.
+ re_file: A compiled regular expression which parses a line into named groups
+ consisting of at minimum "filename", "date", "time", "size" and
+ optionally "timezone".
+ utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
+ 2-digit string giving the number of UTC offset hours, and MM is a
+ 2-digit string giving the number of UTC offset minutes. If the input
+ utc_offset is None, will try to look for the value of "timezone" if it
+ is specified in re_file.
+
+ Returns:
+ A dict of {"name": (size, lastmod), ...} where:
+ name: The file name relative to |path|'s directory.
+ size: The file size in bytes (0 for directories).
+ lastmod: The file last modification date in UTC.
+ """
+ re_directory = re.compile('^%s/(?P[^:]+):$' % re.escape(path))
+ path_dir = os.path.dirname(path)
+
+ current_dir = ''
+ files = {}
+ for line in ls_output:
+ directory_match = re_directory.match(line)
+ if directory_match:
+ current_dir = directory_match.group('dir')
+ continue
+ file_match = re_file.match(line)
+ if file_match:
+ filename = os.path.join(current_dir, file_match.group('filename'))
+ if filename.startswith(path_dir):
+ filename = filename[len(path_dir) + 1:]
+ lastmod = datetime.datetime.strptime(
+ file_match.group('date') + ' ' + file_match.group('time')[:5],
+ '%Y-%m-%d %H:%M')
+ if not utc_offset and 'timezone' in re_file.groupindex:
+ utc_offset = file_match.group('timezone')
+ if isinstance(utc_offset, str) and len(utc_offset) == 5:
+ utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
+ minutes=int(utc_offset[3:5]))
+ if utc_offset[0:1] == '-':
+ utc_delta = -utc_delta
+ lastmod -= utc_delta
+ files[filename] = (int(file_match.group('size')), lastmod)
+ return files
+
+
+def _ParseMd5SumOutput(md5sum_output):
+ """Returns a list of tuples from the provided md5sum output.
+
+ Args:
+ md5sum_output: output directly from md5sum binary.
+
+ Returns:
+ List of namedtuples with attributes |hash| and |path|, where |path| is the
+ absolute path to the file with an Md5Sum of |hash|.
+ """
+ HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
+ split_lines = [line.split(' ') for line in md5sum_output]
+ return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
+
+
+def _HasAdbPushSucceeded(command_output):
+ """Returns whether adb push has succeeded from the provided output."""
+ # TODO(frankf): We should look at the return code instead of the command
+ # output for many of the commands in this file.
+ if not command_output:
+ return True
+ # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
+ # Errors look like this: "failed to copy ... "
+ if not re.search('^[0-9]', command_output.splitlines()[-1]):
+ logging.critical('PUSH FAILED: ' + command_output)
+ return False
+ return True
+
+
+def GetLogTimestamp(log_line, year):
+ """Returns the timestamp of the given |log_line| in the given year."""
+ try:
+ return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
+ '%Y-%m-%d %H:%M:%S.%f')
+ except (ValueError, IndexError):
+ logging.critical('Error reading timestamp from ' + log_line)
+ return None
+
+
+class AndroidCommands(object):
+ """Helper class for communicating with Android device via adb."""
+
+ def __init__(self, device=None):
+ """Constructor.
+
+ Args:
+ device: If given, adb commands are only send to the device of this ID.
+ Otherwise commands are sent to all attached devices.
+ """
+ self._adb = adb_interface.AdbInterface(constants.GetAdbPath())
+ if device:
+ self._adb.SetTargetSerial(device)
+ self._device = device
+ self._logcat = None
+ self.logcat_process = None
+ self._logcat_tmpoutfile = None
+ self._pushed_files = []
+ self._device_utc_offset = None
+ self._potential_push_size = 0
+ self._actual_push_size = 0
+ self._external_storage = ''
+ self._util_wrapper = ''
+ self._system_properties = system_properties.SystemProperties(self.Adb())
+ self._push_if_needed_cache = {}
+ self._control_usb_charging_command = {
+ 'command': None,
+ 'cached': False,
+ }
+ self._protected_file_access_method_initialized = None
+ self._privileged_command_runner = None
+ self._pie_wrapper = None
+
+ @property
+ def system_properties(self):
+ return self._system_properties
+
+ def _LogShell(self, cmd):
+ """Logs the adb shell command."""
+ if self._device:
+ device_repr = self._device[-4:]
+ else:
+ device_repr = '????'
+ logging.info('[%s]> %s', device_repr, cmd)
+
+ def Adb(self):
+ """Returns our AdbInterface to avoid us wrapping all its methods."""
+ # TODO(tonyg): Goal should be to git rid of this method by making this API
+ # complete and alleviating the need.
+ return self._adb
+
+ def GetDevice(self):
+ """Returns the device serial."""
+ return self._device
+
+ def IsOnline(self):
+ """Checks whether the device is online.
+
+ Returns:
+ True if device is in 'device' mode, False otherwise.
+ """
+ # TODO(aurimas): revert to using adb get-state when android L adb is fixed.
+ #out = self._adb.SendCommand('get-state')
+ #return out.strip() == 'device'
+
+ out = self._adb.SendCommand('devices')
+ for line in out.split('\n'):
+ if self._device in line and 'device' in line:
+ return True
+ return False
+
+ def IsRootEnabled(self):
+ """Checks if root is enabled on the device."""
+ root_test_output = self.RunShellCommand('ls /root') or ['']
+ return not 'Permission denied' in root_test_output[0]
+
+ def EnableAdbRoot(self):
+ """Enables adb root on the device.
+
+ Returns:
+ True: if output from executing adb root was as expected.
+ False: otherwise.
+ """
+ if self.GetBuildType() == 'user':
+ logging.warning("Can't enable root in production builds with type user")
+ return False
+ else:
+ return_value = self._adb.EnableAdbRoot()
+ # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
+ # output matches what is expected. Just to be safe add a call to
+ # wait-for-device.
+ self._adb.SendCommand('wait-for-device')
+ return return_value
+
+ def GetDeviceYear(self):
+ """Returns the year information of the date on device."""
+ return self.RunShellCommand('date +%Y')[0]
+
+ def GetExternalStorage(self):
+ if not self._external_storage:
+ self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
+ if not self._external_storage:
+ raise device_errors.CommandFailedError(
+ ['shell', "'echo $EXTERNAL_STORAGE'"],
+ 'Unable to find $EXTERNAL_STORAGE')
+ return self._external_storage
+
+ def WaitForDevicePm(self, timeout=120):
+ """Blocks until the device's package manager is available.
+
+ To workaround http://b/5201039, we restart the shell and retry if the
+ package manager isn't back after 120 seconds.
+
+ Raises:
+ errors.WaitForResponseTimedOutError after max retries reached.
+ """
+ last_err = None
+ retries = 3
+ while retries:
+ try:
+ self._adb.WaitForDevicePm(wait_time=timeout)
+ return # Success
+ except errors.WaitForResponseTimedOutError as e:
+ last_err = e
+ logging.warning('Restarting and retrying after timeout: %s', e)
+ retries -= 1
+ self.RestartShell()
+ raise last_err # Only reached after max retries, re-raise the last error.
+
+ def RestartShell(self):
+ """Restarts the shell on the device. Does not block for it to return."""
+ self.RunShellCommand('stop')
+ self.RunShellCommand('start')
+
+ def Reboot(self, full_reboot=True):
+ """Reboots the device and waits for the package manager to return.
+
+ Args:
+ full_reboot: Whether to fully reboot the device or just restart the shell.
+ """
+ # TODO(torne): hive can't reboot the device either way without breaking the
+ # connection; work out if we can handle this better
+ if os.environ.get('USING_HIVE'):
+ logging.warning('Ignoring reboot request as we are on hive')
+ return
+ if full_reboot or not self.IsRootEnabled():
+ self._adb.SendCommand('reboot')
+ self._system_properties = system_properties.SystemProperties(self.Adb())
+ timeout = 300
+ retries = 1
+ # Wait for the device to disappear.
+ while retries < 10 and self.IsOnline():
+ time.sleep(1)
+ retries += 1
+ else:
+ self.RestartShell()
+ timeout = 120
+ # To run tests we need at least the package manager and the sd card (or
+ # other external storage) to be ready.
+ self.WaitForDevicePm(timeout)
+ self.WaitForSdCardReady(timeout)
+
+ def Shutdown(self):
+ """Shuts down the device."""
+ self._adb.SendCommand('reboot -p')
+ self._system_properties = system_properties.SystemProperties(self.Adb())
+
+ def Uninstall(self, package):
+ """Uninstalls the specified package from the device.
+
+ Args:
+ package: Name of the package to remove.
+
+ Returns:
+ A status string returned by adb uninstall
+ """
+ uninstall_command = 'uninstall %s' % package
+
+ self._LogShell(uninstall_command)
+ return self._adb.SendCommand(uninstall_command, timeout_time=60)
+
+ def Install(self, package_file_path, reinstall=False):
+ """Installs the specified package to the device.
+
+ Args:
+ package_file_path: Path to .apk file to install.
+ reinstall: Reinstall an existing apk, keeping the data.
+
+ Returns:
+ A status string returned by adb install
+ """
+ assert os.path.isfile(package_file_path), ('<%s> is not file' %
+ package_file_path)
+
+ install_cmd = ['install']
+
+ if reinstall:
+ install_cmd.append('-r')
+
+ install_cmd.append(package_file_path)
+ install_cmd = ' '.join(install_cmd)
+
+ self._LogShell(install_cmd)
+ return self._adb.SendCommand(install_cmd,
+ timeout_time=2 * 60,
+ retry_count=0)
+
+ def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
+ reboots_on_timeout=2):
+ """Installs specified package and reboots device on timeouts.
+
+ If package_name is supplied, checks if the package is already installed and
+ doesn't reinstall if the apk md5sums match.
+
+ Args:
+ apk_path: Path to .apk file to install.
+ keep_data: Reinstalls instead of uninstalling first, preserving the
+ application data.
+ package_name: Package name (only needed if keep_data=False).
+ reboots_on_timeout: number of time to reboot if package manager is frozen.
+ """
+ # Check if package is already installed and up to date.
+ if package_name:
+ installed_apk_path = self.GetApplicationPath(package_name)
+ if (installed_apk_path and
+ not self.GetFilesChanged(apk_path, installed_apk_path,
+ ignore_filenames=True)):
+ logging.info('Skipped install: identical %s APK already installed' %
+ package_name)
+ return
+ # Install.
+ reboots_left = reboots_on_timeout
+ while True:
+ try:
+ if not keep_data:
+ assert package_name
+ self.Uninstall(package_name)
+ install_status = self.Install(apk_path, reinstall=keep_data)
+ if 'Success' in install_status:
+ return
+ else:
+ raise Exception('Install failure: %s' % install_status)
+ except errors.WaitForResponseTimedOutError:
+ print '@@@STEP_WARNINGS@@@'
+ logging.info('Timeout on installing %s on device %s', apk_path,
+ self._device)
+
+ if reboots_left <= 0:
+ raise Exception('Install timed out')
+
+ # Force a hard reboot on last attempt
+ self.Reboot(full_reboot=(reboots_left == 1))
+ reboots_left -= 1
+
+ def MakeSystemFolderWritable(self):
+ """Remounts the /system folder rw."""
+ out = self._adb.SendCommand('remount')
+ if out.strip() != 'remount succeeded':
+ raise errors.MsgException('Remount failed: %s' % out)
+
+ def RestartAdbdOnDevice(self):
+ logging.info('Restarting adbd on the device...')
+ with DeviceTempFile(self, suffix=".sh") as temp_script_file:
+ host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
+ 'build',
+ 'android',
+ 'pylib',
+ 'restart_adbd.sh')
+ self._adb.Push(host_script_path, temp_script_file.name)
+ self.RunShellCommand('. %s' % temp_script_file.name)
+ self._adb.SendCommand('wait-for-device')
+
+ def RestartAdbServer(self):
+ """Restart the adb server."""
+ ret = self.KillAdbServer()
+ if ret != 0:
+ raise errors.MsgException('KillAdbServer: %d' % ret)
+
+ ret = self.StartAdbServer()
+ if ret != 0:
+ raise errors.MsgException('StartAdbServer: %d' % ret)
+
+ @staticmethod
+ def KillAdbServer():
+ """Kill adb server."""
+ adb_cmd = [constants.GetAdbPath(), 'kill-server']
+ ret = cmd_helper.RunCmd(adb_cmd)
+ retry = 0
+ while retry < 3:
+ ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
+ if ret != 0:
+ # pgrep didn't find adb, kill-server succeeded.
+ return 0
+ retry += 1
+ time.sleep(retry)
+ return ret
+
+ def StartAdbServer(self):
+ """Start adb server."""
+ adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server']
+ ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd)
+ retry = 0
+ while retry < 3:
+ ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
+ if ret == 0:
+ # pgrep found adb, start-server succeeded.
+ # Waiting for device to reconnect before returning success.
+ self._adb.SendCommand('wait-for-device')
+ return 0
+ retry += 1
+ time.sleep(retry)
+ return ret
+
+ def WaitForSystemBootCompleted(self, wait_time):
+ """Waits for targeted system's boot_completed flag to be set.
+
+ Args:
+ wait_time: time in seconds to wait
+
+ Raises:
+ WaitForResponseTimedOutError if wait_time elapses and flag still not
+ set.
+ """
+ logging.info('Waiting for system boot completed...')
+ self._adb.SendCommand('wait-for-device')
+ # Now the device is there, but system not boot completed.
+ # Query the sys.boot_completed flag with a basic command
+ boot_completed = False
+ attempts = 0
+ wait_period = 5
+ while not boot_completed and (attempts * wait_period) < wait_time:
+ output = self.system_properties['sys.boot_completed']
+ output = output.strip()
+ if output == '1':
+ boot_completed = True
+ else:
+ # If 'error: xxx' returned when querying the flag, it means
+ # adb server lost the connection to the emulator, so restart the adb
+ # server.
+ if 'error:' in output:
+ self.RestartAdbServer()
+ time.sleep(wait_period)
+ attempts += 1
+ if not boot_completed:
+ raise errors.WaitForResponseTimedOutError(
+ 'sys.boot_completed flag was not set after %s seconds' % wait_time)
+
+ def WaitForSdCardReady(self, timeout_time):
+ """Wait for the SD card ready before pushing data into it."""
+ logging.info('Waiting for SD card ready...')
+ sdcard_ready = False
+ attempts = 0
+ wait_period = 5
+ external_storage = self.GetExternalStorage()
+ while not sdcard_ready and attempts * wait_period < timeout_time:
+ output = self.RunShellCommand('ls ' + external_storage)
+ if output:
+ sdcard_ready = True
+ else:
+ time.sleep(wait_period)
+ attempts += 1
+ if not sdcard_ready:
+ raise errors.WaitForResponseTimedOutError(
+ 'SD card not ready after %s seconds' % timeout_time)
+
+ def GetAndroidToolStatusAndOutput(self, command, lib_path=None, *args, **kw):
+ """Runs a native Android binary, wrapping the command as necessary.
+
+ This is a specialization of GetShellCommandStatusAndOutput, which is meant
+ for running tools/android/ binaries and handle properly: (1) setting the
+ lib path (for component=shared_library), (2) using the PIE wrapper on ICS.
+ See crbug.com/373219 for more context.
+
+ Args:
+ command: String containing the command to send.
+ lib_path: (optional) path to the folder containing the dependent libs.
+ Same other arguments of GetCmdStatusAndOutput.
+ """
+ # The first time this command is run the device is inspected to check
+ # whether a wrapper for running PIE executable is needed (only Android ICS)
+ # or not. The results is cached, so the wrapper is pushed only once.
+ if self._pie_wrapper is None:
+ # None: did not check; '': did check and not needed; '/path': use /path.
+ self._pie_wrapper = ''
+ if self.GetBuildId().startswith('I'): # Ixxxx = Android ICS.
+ run_pie_dist_path = os.path.join(constants.GetOutDirectory(), 'run_pie')
+ assert os.path.exists(run_pie_dist_path), 'Please build run_pie'
+ # The PIE loader must be pushed manually (i.e. no PushIfNeeded) because
+ # PushIfNeeded requires md5sum and md5sum requires the wrapper as well.
+ adb_command = 'push %s %s' % (run_pie_dist_path, PIE_WRAPPER_PATH)
+ assert _HasAdbPushSucceeded(self._adb.SendCommand(adb_command))
+ self._pie_wrapper = PIE_WRAPPER_PATH
+
+ if self._pie_wrapper:
+ command = '%s %s' % (self._pie_wrapper, command)
+ if lib_path:
+ command = 'LD_LIBRARY_PATH=%s %s' % (lib_path, command)
+ return self.GetShellCommandStatusAndOutput(command, *args, **kw)
+
+ # It is tempting to turn this function into a generator, however this is not
+ # possible without using a private (local) adb_shell instance (to ensure no
+ # other command interleaves usage of it), which would defeat the main aim of
+ # being able to reuse the adb shell instance across commands.
+ def RunShellCommand(self, command, timeout_time=20, log_result=False):
+ """Send a command to the adb shell and return the result.
+
+ Args:
+ command: String containing the shell command to send.
+ timeout_time: Number of seconds to wait for command to respond before
+ retrying, used by AdbInterface.SendShellCommand.
+ log_result: Boolean to indicate whether we should log the result of the
+ shell command.
+
+ Returns:
+ list containing the lines of output received from running the command
+ """
+ self._LogShell(command)
+ if "'" in command:
+ command = command.replace('\'', '\'\\\'\'')
+ result = self._adb.SendShellCommand(
+ "'%s'" % command, timeout_time).splitlines()
+ # TODO(b.kelemen): we should really be able to drop the stderr of the
+ # command or raise an exception based on what the caller wants.
+ result = [ l for l in result if not l.startswith('WARNING') ]
+ if ['error: device not found'] == result:
+ raise errors.DeviceUnresponsiveError('device not found')
+ if log_result:
+ self._LogShell('\n'.join(result))
+ return result
+
+ def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
+ log_result=False):
+ """See RunShellCommand() above.
+
+ Returns:
+ The tuple (exit code, list of output lines).
+ """
+ lines = self.RunShellCommand(
+ command + '; echo %$?', timeout_time, log_result)
+ last_line = lines[-1]
+ status_pos = last_line.rfind('%')
+ assert status_pos >= 0
+ status = int(last_line[status_pos + 1:])
+ if status_pos == 0:
+ lines = lines[:-1]
+ else:
+ lines = lines[:-1] + [last_line[:status_pos]]
+ return (status, lines)
+
+ def KillAll(self, process, signum=9, with_su=False):
+ """Android version of killall, connected via adb.
+
+ Args:
+ process: name of the process to kill off.
+ signum: signal to use, 9 (SIGKILL) by default.
+ with_su: wether or not to use su to kill the processes.
+
+ Returns:
+ the number of processes killed
+ """
+ pids = self.ExtractPid(process)
+ if pids:
+ cmd = 'kill -%d %s' % (signum, ' '.join(pids))
+ if with_su:
+ self.RunShellCommandWithSU(cmd)
+ else:
+ self.RunShellCommand(cmd)
+ return len(pids)
+
+ def KillAllBlocking(self, process, timeout_sec, signum=9, with_su=False):
+ """Blocking version of killall, connected via adb.
+
+ This waits until no process matching the corresponding name appears in ps'
+ output anymore.
+
+ Args:
+ process: name of the process to kill off
+ timeout_sec: the timeout in seconds
+ signum: same as |KillAll|
+ with_su: same as |KillAll|
+ Returns:
+ the number of processes killed
+ """
+ processes_killed = self.KillAll(process, signum=signum, with_su=with_su)
+ if processes_killed:
+ elapsed = 0
+ wait_period = 0.1
+ # Note that this doesn't take into account the time spent in ExtractPid().
+ while self.ExtractPid(process) and elapsed < timeout_sec:
+ time.sleep(wait_period)
+ elapsed += wait_period
+ if elapsed >= timeout_sec:
+ return processes_killed - self.ExtractPid(process)
+ return processes_killed
+
+ @staticmethod
+ def _GetActivityCommand(package, activity, wait_for_completion, action,
+ category, data, extras, trace_file_name, force_stop,
+ flags):
+ """Creates command to start |package|'s activity on the device.
+
+ Args - as for StartActivity
+
+ Returns:
+ the command to run on the target to start the activity
+ """
+ cmd = 'am start -a %s' % action
+ if force_stop:
+ cmd += ' -S'
+ if wait_for_completion:
+ cmd += ' -W'
+ if category:
+ cmd += ' -c %s' % category
+ if package and activity:
+ cmd += ' -n %s/%s' % (package, activity)
+ if data:
+ cmd += ' -d "%s"' % data
+ if extras:
+ for key in extras:
+ value = extras[key]
+ if isinstance(value, str):
+ cmd += ' --es'
+ elif isinstance(value, bool):
+ cmd += ' --ez'
+ elif isinstance(value, int):
+ cmd += ' --ei'
+ else:
+ raise NotImplementedError(
+ 'Need to teach StartActivity how to pass %s extras' % type(value))
+ cmd += ' %s %s' % (key, value)
+ if trace_file_name:
+ cmd += ' --start-profiler ' + trace_file_name
+ if flags:
+ cmd += ' -f %s' % flags
+ return cmd
+
+ def StartActivity(self, package, activity, wait_for_completion=False,
+ action='android.intent.action.VIEW',
+ category=None, data=None,
+ extras=None, trace_file_name=None,
+ force_stop=False, flags=None):
+ """Starts |package|'s activity on the device.
+
+ Args:
+ package: Name of package to start (e.g. 'com.google.android.apps.chrome').
+ activity: Name of activity (e.g. '.Main' or
+ 'com.google.android.apps.chrome.Main').
+ wait_for_completion: wait for the activity to finish launching (-W flag).
+ action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
+ category: string (e.g. "android.intent.category.HOME")
+ data: Data string to pass to activity (e.g. 'http://www.example.com/').
+ extras: Dict of extras to pass to activity. Values are significant.
+ trace_file_name: If used, turns on and saves the trace to this file name.
+ force_stop: force stop the target app before starting the activity (-S
+ flag).
+ Returns:
+ The output of the underlying command as a list of lines.
+ """
+ cmd = self._GetActivityCommand(package, activity, wait_for_completion,
+ action, category, data, extras,
+ trace_file_name, force_stop, flags)
+ return self.RunShellCommand(cmd)
+
+ def StartActivityTimed(self, package, activity, wait_for_completion=False,
+ action='android.intent.action.VIEW',
+ category=None, data=None,
+ extras=None, trace_file_name=None,
+ force_stop=False, flags=None):
+ """Starts |package|'s activity on the device, returning the start time
+
+ Args - as for StartActivity
+
+ Returns:
+ A tuple containing:
+ - the output of the underlying command as a list of lines, and
+ - a timestamp string for the time at which the activity started
+ """
+ cmd = self._GetActivityCommand(package, activity, wait_for_completion,
+ action, category, data, extras,
+ trace_file_name, force_stop, flags)
+ self.StartMonitoringLogcat()
+ out = self.RunShellCommand('log starting activity; ' + cmd)
+ activity_started_re = re.compile('.*starting activity.*')
+ m = self.WaitForLogMatch(activity_started_re, None)
+ assert m
+ start_line = m.group(0)
+ return (out, GetLogTimestamp(start_line, self.GetDeviceYear()))
+
+ def StartCrashUploadService(self, package):
+ # TODO(frankf): We really need a python wrapper around Intent
+ # to be shared with StartActivity/BroadcastIntent.
+ cmd = (
+ 'am startservice -a %s.crash.ACTION_FIND_ALL -n '
+ '%s/%s.crash.MinidumpUploadService' %
+ (constants.PACKAGE_INFO['chrome'].package,
+ package,
+ constants.PACKAGE_INFO['chrome'].package))
+ am_output = self.RunShellCommandWithSU(cmd)
+ assert am_output and 'Starting' in am_output[-1], (
+ 'Service failed to start: %s' % am_output)
+ time.sleep(15)
+
+ def BroadcastIntent(self, package, intent, *args):
+ """Send a broadcast intent.
+
+ Args:
+ package: Name of package containing the intent.
+ intent: Name of the intent.
+ args: Optional extra arguments for the intent.
+ """
+ cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
+ self.RunShellCommand(cmd)
+
+ def GoHome(self):
+ """Tell the device to return to the home screen. Blocks until completion."""
+ self.RunShellCommand('am start -W '
+ '-a android.intent.action.MAIN -c android.intent.category.HOME')
+
+ def CloseApplication(self, package):
+ """Attempt to close down the application, using increasing violence.
+
+ Args:
+ package: Name of the process to kill off, e.g.
+ com.google.android.apps.chrome
+ """
+ self.RunShellCommand('am force-stop ' + package)
+
+ def GetApplicationPath(self, package):
+ """Get the installed apk path on the device for the given package.
+
+ Args:
+ package: Name of the package.
+
+ Returns:
+ Path to the apk on the device if it exists, None otherwise.
+ """
+ pm_path_output = self.RunShellCommand('pm path ' + package)
+ # The path output contains anything if and only if the package
+ # exists.
+ if pm_path_output:
+ # pm_path_output is of the form: "package:/path/to/foo.apk"
+ return pm_path_output[0].split(':')[1]
+ else:
+ return None
+
+ def ClearApplicationState(self, package):
+ """Closes and clears all state for the given |package|."""
+ # Check that the package exists before clearing it. Necessary because
+ # calling pm clear on a package that doesn't exist may never return.
+ pm_path_output = self.RunShellCommand('pm path ' + package)
+ # The path output only contains anything if and only if the package exists.
+ if pm_path_output:
+ self.RunShellCommand('pm clear ' + package)
+
+ def SendKeyEvent(self, keycode):
+ """Sends keycode to the device.
+
+ Args:
+ keycode: Numeric keycode to send (see "enum" at top of file).
+ """
+ self.RunShellCommand('input keyevent %d' % keycode)
+
+ def _RunMd5Sum(self, host_path, device_path):
+ """Gets the md5sum of a host path and device path.
+
+ Args:
+ host_path: Path (file or directory) on the host.
+ device_path: Path on the device.
+
+ Returns:
+ A tuple containing lists of the host and device md5sum results as
+ created by _ParseMd5SumOutput().
+ """
+ md5sum_dist_path = os.path.join(constants.GetOutDirectory(),
+ 'md5sum_dist')
+ assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
+ md5sum_dist_mtime = os.stat(md5sum_dist_path).st_mtime
+ if (md5sum_dist_path not in self._push_if_needed_cache or
+ self._push_if_needed_cache[md5sum_dist_path] != md5sum_dist_mtime):
+ command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
+ assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
+ self._push_if_needed_cache[md5sum_dist_path] = md5sum_dist_mtime
+
+ (_, md5_device_output) = self.GetAndroidToolStatusAndOutput(
+ self._util_wrapper + ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path,
+ lib_path=MD5SUM_DEVICE_FOLDER,
+ timeout_time=2 * 60)
+ device_hash_tuples = _ParseMd5SumOutput(md5_device_output)
+ assert os.path.exists(host_path), 'Local path not found %s' % host_path
+ md5sum_output = cmd_helper.GetCmdOutput(
+ [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'),
+ host_path])
+ host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
+ return (host_hash_tuples, device_hash_tuples)
+
+ def GetFilesChanged(self, host_path, device_path, ignore_filenames=False):
+ """Compares the md5sum of a host path against a device path.
+
+ Note: Ignores extra files on the device.
+
+ Args:
+ host_path: Path (file or directory) on the host.
+ device_path: Path on the device.
+ ignore_filenames: If True only the file contents are considered when
+ checking whether a file has changed, otherwise the relative path
+ must also match.
+
+ Returns:
+ A list of tuples of the form (host_path, device_path) for files whose
+ md5sums do not match.
+ """
+
+ # Md5Sum resolves symbolic links in path names so the calculation of
+ # relative path names from its output will need the real path names of the
+ # base directories. Having calculated these they are used throughout the
+ # function since this makes us less subject to any future changes to Md5Sum.
+ real_host_path = os.path.realpath(host_path)
+ real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0]
+
+ host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
+ real_host_path, real_device_path)
+
+ if len(host_hash_tuples) > len(device_hash_tuples):
+ logging.info('%s files do not exist on the device' %
+ (len(host_hash_tuples) - len(device_hash_tuples)))
+
+ host_rel = [(os.path.relpath(os.path.normpath(t.path), real_host_path),
+ t.hash)
+ for t in host_hash_tuples]
+
+ if os.path.isdir(real_host_path):
+ def RelToRealPaths(rel_path):
+ return (os.path.join(real_host_path, rel_path),
+ os.path.join(real_device_path, rel_path))
+ else:
+ assert len(host_rel) == 1
+ def RelToRealPaths(_):
+ return (real_host_path, real_device_path)
+
+ if ignore_filenames:
+ # If we are ignoring file names, then we want to push any file for which
+ # a file with an equivalent MD5 sum does not exist on the device.
+ device_hashes = set([h.hash for h in device_hash_tuples])
+ ShouldPush = lambda p, h: h not in device_hashes
+ else:
+ # Otherwise, we want to push any file on the host for which a file with
+ # an equivalent MD5 sum does not exist at the same relative path on the
+ # device.
+ device_rel = dict([(os.path.relpath(os.path.normpath(t.path),
+ real_device_path),
+ t.hash)
+ for t in device_hash_tuples])
+ ShouldPush = lambda p, h: p not in device_rel or h != device_rel[p]
+
+ return [RelToRealPaths(path) for path, host_hash in host_rel
+ if ShouldPush(path, host_hash)]
+
+ def PushIfNeeded(self, host_path, device_path):
+ """Pushes |host_path| to |device_path|.
+
+ Works for files and directories. This method skips copying any paths in
+ |test_data_paths| that already exist on the device with the same hash.
+
+ All pushed files can be removed by calling RemovePushedFiles().
+ """
+ MAX_INDIVIDUAL_PUSHES = 50
+ if not os.path.exists(host_path):
+ raise device_errors.CommandFailedError(
+ 'Local path not found %s' % host_path, device=str(self))
+
+ # See if the file on the host changed since the last push (if any) and
+ # return early if it didn't. Note that this shortcut assumes that the tests
+ # on the device don't modify the files.
+ if not os.path.isdir(host_path):
+ if host_path in self._push_if_needed_cache:
+ host_path_mtime = self._push_if_needed_cache[host_path]
+ if host_path_mtime == os.stat(host_path).st_mtime:
+ return
+
+ size = host_utils.GetRecursiveDiskUsage(host_path)
+ self._pushed_files.append(device_path)
+ self._potential_push_size += size
+
+ if os.path.isdir(host_path):
+ self.RunShellCommand('mkdir -p "%s"' % device_path)
+
+ changed_files = self.GetFilesChanged(host_path, device_path)
+ logging.info('Found %d files that need to be pushed to %s',
+ len(changed_files), device_path)
+ if not changed_files:
+ return
+
+ def Push(host, device):
+ # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
+ # of 60 seconds which isn't sufficient for a lot of users of this method.
+ push_command = 'push %s %s' % (host, device)
+ self._LogShell(push_command)
+
+ # Retry push with increasing backoff if the device is busy.
+ retry = 0
+ while True:
+ output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
+ if _HasAdbPushSucceeded(output):
+ if not os.path.isdir(host_path):
+ self._push_if_needed_cache[host] = os.stat(host).st_mtime
+ return
+ if retry < 3:
+ retry += 1
+ wait_time = 5 * retry
+ logging.error('Push failed, retrying in %d seconds: %s' %
+ (wait_time, output))
+ time.sleep(wait_time)
+ else:
+ raise Exception('Push failed: %s' % output)
+
+ diff_size = 0
+ if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
+ diff_size = sum(host_utils.GetRecursiveDiskUsage(f[0])
+ for f in changed_files)
+
+ # TODO(craigdh): Replace this educated guess with a heuristic that
+ # approximates the push time for each method.
+ if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
+ self._actual_push_size += size
+ Push(host_path, device_path)
+ else:
+ for f in changed_files:
+ Push(f[0], f[1])
+ self._actual_push_size += diff_size
+
+ def GetPushSizeInfo(self):
+ """Get total size of pushes to the device done via PushIfNeeded()
+
+ Returns:
+ A tuple:
+ 1. Total size of push requests to PushIfNeeded (MB)
+ 2. Total size that was actually pushed (MB)
+ """
+ return (self._potential_push_size, self._actual_push_size)
+
+ def GetFileContents(self, filename, log_result=False):
+ """Gets contents from the file specified by |filename|."""
+ return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
+ log_result=log_result)
+
+ def SetFileContents(self, filename, contents):
+ """Writes |contents| to the file specified by |filename|."""
+ with tempfile.NamedTemporaryFile() as f:
+ f.write(contents)
+ f.flush()
+ self._adb.Push(f.name, filename)
+
+ def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
+ return self.RunShellCommand('su -c %s' % command, timeout_time, log_result)
+
+ def CanAccessProtectedFileContents(self):
+ """Returns True if Get/SetProtectedFileContents would work via "su" or adb
+ shell running as root.
+
+ Devices running user builds don't have adb root, but may provide "su" which
+ can be used for accessing protected files.
+ """
+ return (self._GetProtectedFileCommandRunner() != None)
+
+ def _GetProtectedFileCommandRunner(self):
+ """Finds the best method to access protected files on the device.
+
+ Returns:
+ 1. None when privileged files cannot be accessed on the device.
+ 2. Otherwise: A function taking a single parameter: a string with command
+ line arguments. Running that function executes the command with
+ the appropriate method.
+ """
+ if self._protected_file_access_method_initialized:
+ return self._privileged_command_runner
+
+ self._privileged_command_runner = None
+ self._protected_file_access_method_initialized = True
+
+ for cmd in [self.RunShellCommand, self.RunShellCommandWithSU]:
+ # Get contents of the auxv vector for the init(8) process from a small
+ # binary file that always exists on linux and is always read-protected.
+ contents = cmd('cat /proc/1/auxv')
+ # The leading 4 or 8-bytes of auxv vector is a_type. There are not many
+ # reserved a_type values, hence byte 2 must always be '\0' for a realistic
+ # auxv. See /usr/include/elf.h.
+ if len(contents) > 0 and (contents[0][2] == '\0'):
+ self._privileged_command_runner = cmd
+ break
+ return self._privileged_command_runner
+
+ def GetProtectedFileContents(self, filename):
+ """Gets contents from the protected file specified by |filename|.
+
+ This is potentially less efficient than GetFileContents.
+ """
+ command = 'cat "%s" 2> /dev/null' % filename
+ command_runner = self._GetProtectedFileCommandRunner()
+ if command_runner:
+ return command_runner(command)
+ else:
+ logging.warning('Could not access protected file: %s' % filename)
+ return []
+
+ def SetProtectedFileContents(self, filename, contents):
+ """Writes |contents| to the protected file specified by |filename|.
+
+ This is less efficient than SetFileContents.
+ """
+ with DeviceTempFile(self) as temp_file:
+ with DeviceTempFile(self, suffix=".sh") as temp_script:
+ # Put the contents in a temporary file
+ self.SetFileContents(temp_file.name, contents)
+ # Create a script to copy the file contents to its final destination
+ self.SetFileContents(temp_script.name,
+ 'cat %s > %s' % (temp_file.name, filename))
+
+ command = 'sh %s' % temp_script.name
+ command_runner = self._GetProtectedFileCommandRunner()
+ if command_runner:
+ return command_runner(command)
+ else:
+ logging.warning(
+ 'Could not set contents of protected file: %s' % filename)
+
+
+ def RemovePushedFiles(self):
+ """Removes all files pushed with PushIfNeeded() from the device."""
+ for p in self._pushed_files:
+ self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
+
+ def ListPathContents(self, path):
+ """Lists files in all subdirectories of |path|.
+
+ Args:
+ path: The path to list.
+
+ Returns:
+ A dict of {"name": (size, lastmod), ...}.
+ """
+ # Example output:
+ # /foo/bar:
+ # -rw-r----- user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
+ re_file = re.compile('^-(?P[^\s]+)\s+'
+ '(?P[^\s]+)\s+'
+ '(?P[^\s]+)\s+'
+ '(?P[^\s]+)\s+'
+ '(?P[^\s]+)\s+'
+ '(?P[^\s]+)\s+'
+ '(?P[^\s]+)$')
+ return _GetFilesFromRecursiveLsOutput(
+ path, self.RunShellCommand('ls -lR %s' % path), re_file,
+ self.GetUtcOffset())
+
+ def GetUtcOffset(self):
+ if not self._device_utc_offset:
+ self._device_utc_offset = self.RunShellCommand('date +%z')[0]
+ return self._device_utc_offset
+
+ def SetJavaAssertsEnabled(self, enable):
+ """Sets or removes the device java assertions property.
+
+ Args:
+ enable: If True the property will be set.
+
+ Returns:
+ True if the file was modified (reboot is required for it to take effect).
+ """
+ # First ensure the desired property is persisted.
+ temp_props_file = tempfile.NamedTemporaryFile()
+ properties = ''
+ if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
+ with open(temp_props_file.name) as f:
+ properties = f.read()
+ re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
+ r'\s*=\s*all\s*$', re.MULTILINE)
+ if enable != bool(re.search(re_search, properties)):
+ re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
+ r'\s*=\s*\w+\s*$', re.MULTILINE)
+ properties = re.sub(re_replace, '', properties)
+ if enable:
+ properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
+
+ file(temp_props_file.name, 'w').write(properties)
+ self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
+
+ # Next, check the current runtime value is what we need, and
+ # if not, set it and report that a reboot is required.
+ was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY]
+ if was_set == enable:
+ return False
+ self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or ''
+ return True
+
+ def GetBuildId(self):
+ """Returns the build ID of the system (e.g. JRM79C)."""
+ build_id = self.system_properties['ro.build.id']
+ assert build_id
+ return build_id
+
+ def GetBuildType(self):
+ """Returns the build type of the system (e.g. eng)."""
+ build_type = self.system_properties['ro.build.type']
+ assert build_type
+ return build_type
+
+ def GetBuildProduct(self):
+ """Returns the build product of the device (e.g. maguro)."""
+ build_product = self.system_properties['ro.build.product']
+ assert build_product
+ return build_product
+
+ def GetProductName(self):
+ """Returns the product name of the device (e.g. takju)."""
+ name = self.system_properties['ro.product.name']
+ assert name
+ return name
+
+ def GetBuildFingerprint(self):
+ """Returns the build fingerprint of the device."""
+ build_fingerprint = self.system_properties['ro.build.fingerprint']
+ assert build_fingerprint
+ return build_fingerprint
+
+ def GetDescription(self):
+ """Returns the description of the system.
+
+ For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys".
+ """
+ description = self.system_properties['ro.build.description']
+ assert description
+ return description
+
+ def GetProductModel(self):
+ """Returns the name of the product model (e.g. "Galaxy Nexus") """
+ model = self.system_properties['ro.product.model']
+ assert model
+ return model
+
+ def GetWifiIP(self):
+ """Returns the wifi IP on the device."""
+ wifi_ip = self.system_properties['dhcp.wlan0.ipaddress']
+ # Do not assert here. Devices (e.g. emulators) may not have a WifiIP.
+ return wifi_ip
+
+ def GetSubscriberInfo(self):
+ """Returns the device subscriber info (e.g. GSM and device ID) as string."""
+ iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo')
+ # Do not assert here. Devices (e.g. Nakasi on K) may not have iphonesubinfo.
+ return '\n'.join(iphone_sub)
+
+ def GetBatteryInfo(self):
+ """Returns a {str: str} dict of battery info (e.g. status, level, etc)."""
+ battery = self.RunShellCommand('dumpsys battery')
+ assert battery
+ battery_info = {}
+ for line in battery[1:]:
+ k, _, v = line.partition(': ')
+ battery_info[k.strip()] = v.strip()
+ return battery_info
+
+ def GetSetupWizardStatus(self):
+ """Returns the status of the device setup wizard (e.g. DISABLED)."""
+ status = self.system_properties['ro.setupwizard.mode']
+ # On some devices, the status is empty if not otherwise set. In such cases
+ # the caller should expect an empty string to be returned.
+ return status
+
+ def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
+ """Starts monitoring the output of logcat, for use with WaitForLogMatch.
+
+ Args:
+ clear: If True the existing logcat output will be cleared, to avoiding
+ matching historical output lurking in the log.
+ filters: A list of logcat filters to be used.
+ """
+ if clear:
+ self.RunShellCommand('logcat -c')
+ args = []
+ if self._adb._target_arg:
+ args += shlex.split(self._adb._target_arg)
+ args += ['logcat', '-v', 'threadtime']
+ if filters:
+ args.extend(filters)
+ else:
+ args.append('*:v')
+
+ if logfile:
+ logfile = NewLineNormalizer(logfile)
+
+ # Spawn logcat and synchronize with it.
+ for _ in range(4):
+ self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10,
+ logfile=logfile)
+ if not clear or self.SyncLogCat():
+ break
+ self._logcat.close(force=True)
+ else:
+ logging.critical('Error reading from logcat: ' + str(self._logcat.match))
+ sys.exit(1)
+
+ def SyncLogCat(self):
+ """Synchronize with logcat.
+
+ Synchronize with the monitored logcat so that WaitForLogMatch will only
+ consider new message that are received after this point in time.
+
+ Returns:
+ True if the synchronization succeeded.
+ """
+ assert self._logcat
+ tag = 'logcat_sync_%s' % time.time()
+ self.RunShellCommand('log ' + tag)
+ return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
+
+ def GetMonitoredLogCat(self):
+ """Returns an "adb logcat" command as created by pexpected.spawn."""
+ if not self._logcat:
+ self.StartMonitoringLogcat(clear=False)
+ return self._logcat
+
+ def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
+ """Blocks until a matching line is logged or a timeout occurs.
+
+ Args:
+ success_re: A compiled re to search each line for.
+ error_re: A compiled re which, if found, terminates the search for
+ |success_re|. If None is given, no error condition will be detected.
+ clear: If True the existing logcat output will be cleared, defaults to
+ false.
+ timeout: Timeout in seconds to wait for a log match.
+
+ Raises:
+ pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
+ or |error_re|.
+
+ Returns:
+ The re match object if |success_re| is matched first or None if |error_re|
+ is matched first.
+ """
+ logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
+ t0 = time.time()
+ while True:
+ if not self._logcat:
+ self.StartMonitoringLogcat(clear)
+ try:
+ while True:
+ # Note this will block for upto the timeout _per log line_, so we need
+ # to calculate the overall timeout remaining since t0.
+ time_remaining = t0 + timeout - time.time()
+ if time_remaining < 0:
+ raise pexpect.TIMEOUT(self._logcat)
+ self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
+ line = self._logcat.match.group(1)
+ if error_re:
+ error_match = error_re.search(line)
+ if error_match:
+ return None
+ success_match = success_re.search(line)
+ if success_match:
+ return success_match
+ logging.info('<<< Skipped Logcat Line:' + str(line))
+ except pexpect.TIMEOUT:
+ raise pexpect.TIMEOUT(
+ 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
+ 'to debug)' %
+ (timeout, success_re.pattern))
+ except pexpect.EOF:
+ # It seems that sometimes logcat can end unexpectedly. This seems
+ # to happen during Chrome startup after a reboot followed by a cache
+ # clean. I don't understand why this happens, but this code deals with
+ # getting EOF in logcat.
+ logging.critical('Found EOF in adb logcat. Restarting...')
+ # Rerun spawn with original arguments. Note that self._logcat.args[0] is
+ # the path of adb, so we don't want it in the arguments.
+ self._logcat = pexpect.spawn(constants.GetAdbPath(),
+ self._logcat.args[1:],
+ timeout=self._logcat.timeout,
+ logfile=self._logcat.logfile)
+
+ def StartRecordingLogcat(self, clear=True, filters=None):
+ """Starts recording logcat output to eventually be saved as a string.
+
+ This call should come before some series of tests are run, with either
+ StopRecordingLogcat or SearchLogcatRecord following the tests.
+
+ Args:
+ clear: True if existing log output should be cleared.
+ filters: A list of logcat filters to be used.
+ """
+ if not filters:
+ filters = ['*:v']
+ if clear:
+ self._adb.SendCommand('logcat -c')
+ logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
+ ' '.join(filters))
+ self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0)
+ self.logcat_process = subprocess.Popen(logcat_command, shell=True,
+ stdout=self._logcat_tmpoutfile)
+
+ def GetCurrentRecordedLogcat(self):
+ """Return the current content of the logcat being recorded.
+ Call this after StartRecordingLogcat() and before StopRecordingLogcat().
+ This can be useful to perform timed polling/parsing.
+ Returns:
+ Current logcat output as a single string, or None if
+ StopRecordingLogcat() was already called.
+ """
+ if not self._logcat_tmpoutfile:
+ return None
+
+ with open(self._logcat_tmpoutfile.name) as f:
+ return f.read()
+
+ def StopRecordingLogcat(self):
+ """Stops an existing logcat recording subprocess and returns output.
+
+ Returns:
+ The logcat output as a string or an empty string if logcat was not
+ being recorded at the time.
+ """
+ if not self.logcat_process:
+ return ''
+ # Cannot evaluate directly as 0 is a possible value.
+ # Better to read the self.logcat_process.stdout before killing it,
+ # Otherwise the communicate may return incomplete output due to pipe break.
+ if self.logcat_process.poll() is None:
+ self.logcat_process.kill()
+ self.logcat_process.wait()
+ self.logcat_process = None
+ self._logcat_tmpoutfile.seek(0)
+ output = self._logcat_tmpoutfile.read()
+ self._logcat_tmpoutfile.close()
+ self._logcat_tmpoutfile = None
+ return output
+
+ @staticmethod
+ def SearchLogcatRecord(record, message, thread_id=None, proc_id=None,
+ log_level=None, component=None):
+ """Searches the specified logcat output and returns results.
+
+ This method searches through the logcat output specified by record for a
+ certain message, narrowing results by matching them against any other
+ specified criteria. It returns all matching lines as described below.
+
+ Args:
+ record: A string generated by Start/StopRecordingLogcat to search.
+ message: An output string to search for.
+ thread_id: The thread id that is the origin of the message.
+ proc_id: The process that is the origin of the message.
+ log_level: The log level of the message.
+ component: The name of the component that would create the message.
+
+ Returns:
+ A list of dictionaries represeting matching entries, each containing keys
+ thread_id, proc_id, log_level, component, and message.
+ """
+ if thread_id:
+ thread_id = str(thread_id)
+ if proc_id:
+ proc_id = str(proc_id)
+ results = []
+ reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
+ re.MULTILINE)
+ log_list = reg.findall(record)
+ for (tid, pid, log_lev, comp, msg) in log_list:
+ if ((not thread_id or thread_id == tid) and
+ (not proc_id or proc_id == pid) and
+ (not log_level or log_level == log_lev) and
+ (not component or component == comp) and msg.find(message) > -1):
+ match = dict({'thread_id': tid, 'proc_id': pid,
+ 'log_level': log_lev, 'component': comp,
+ 'message': msg})
+ results.append(match)
+ return results
+
+ def ExtractPid(self, process_name):
+ """Extracts Process Ids for a given process name from Android Shell.
+
+ Args:
+ process_name: name of the process on the device.
+
+ Returns:
+ List of all the process ids (as strings) that match the given name.
+ If the name of a process exactly matches the given name, the pid of
+ that process will be inserted to the front of the pid list.
+ """
+ pids = []
+ for line in self.RunShellCommand('ps', log_result=False):
+ data = line.split()
+ try:
+ if process_name in data[-1]: # name is in the last column
+ if process_name == data[-1]:
+ pids.insert(0, data[1]) # PID is in the second column
+ else:
+ pids.append(data[1])
+ except IndexError:
+ pass
+ return pids
+
+ def GetIoStats(self):
+ """Gets cumulative disk IO stats since boot (for all processes).
+
+ Returns:
+ Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
+ was an error.
+ """
+ IoStats = collections.namedtuple(
+ 'IoStats',
+ ['device',
+ 'num_reads_issued',
+ 'num_reads_merged',
+ 'num_sectors_read',
+ 'ms_spent_reading',
+ 'num_writes_completed',
+ 'num_writes_merged',
+ 'num_sectors_written',
+ 'ms_spent_writing',
+ 'num_ios_in_progress',
+ 'ms_spent_doing_io',
+ 'ms_spent_doing_io_weighted',
+ ])
+
+ for line in self.GetFileContents('/proc/diskstats', log_result=False):
+ fields = line.split()
+ stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]])
+ if stats.device == 'mmcblk0':
+ return {
+ 'num_reads': stats.num_reads_issued,
+ 'num_writes': stats.num_writes_completed,
+ 'read_ms': stats.ms_spent_reading,
+ 'write_ms': stats.ms_spent_writing,
+ }
+ logging.warning('Could not find disk IO stats.')
+ return None
+
+ def GetMemoryUsageForPid(self, pid):
+ """Returns the memory usage for given pid.
+
+ Args:
+ pid: The pid number of the specific process running on device.
+
+ Returns:
+ Dict of {metric:usage_kb}, for the process which has specified pid.
+ The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
+ Shared_Dirty, Private_Clean, Private_Dirty, VmHWM.
+ """
+ showmap = self.RunShellCommand('showmap %d' % pid)
+ if not showmap or not showmap[-1].endswith('TOTAL'):
+ logging.warning('Invalid output for showmap %s', str(showmap))
+ return {}
+ items = showmap[-1].split()
+ if len(items) != 9:
+ logging.warning('Invalid TOTAL for showmap %s', str(items))
+ return {}
+ usage_dict = collections.defaultdict(int)
+ usage_dict.update({
+ 'Size': int(items[0].strip()),
+ 'Rss': int(items[1].strip()),
+ 'Pss': int(items[2].strip()),
+ 'Shared_Clean': int(items[3].strip()),
+ 'Shared_Dirty': int(items[4].strip()),
+ 'Private_Clean': int(items[5].strip()),
+ 'Private_Dirty': int(items[6].strip()),
+ })
+ peak_value_kb = 0
+ for line in self.GetProtectedFileContents('/proc/%s/status' % pid):
+ if not line.startswith('VmHWM:'): # Format: 'VmHWM: +[0-9]+ kB'
+ continue
+ peak_value_kb = int(line.split(':')[1].strip().split(' ')[0])
+ break
+ usage_dict['VmHWM'] = peak_value_kb
+ if not peak_value_kb:
+ logging.warning('Could not find memory peak value for pid ' + str(pid))
+
+ return usage_dict
+
+ def ProcessesUsingDevicePort(self, device_port):
+ """Lists processes using the specified device port on loopback interface.
+
+ Args:
+ device_port: Port on device we want to check.
+
+ Returns:
+ A list of (pid, process_name) tuples using the specified port.
+ """
+ tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
+ tcp_address = '0100007F:%04X' % device_port
+ pids = []
+ for single_connect in tcp_results:
+ connect_results = single_connect.split()
+ # Column 1 is the TCP port, and Column 9 is the inode of the socket
+ if connect_results[1] == tcp_address:
+ socket_inode = connect_results[9]
+ socket_name = 'socket:[%s]' % socket_inode
+ lsof_results = self.RunShellCommand('lsof', log_result=False)
+ for single_process in lsof_results:
+ process_results = single_process.split()
+ # Ignore the line if it has less than nine columns in it, which may
+ # be the case when a process stops while lsof is executing.
+ if len(process_results) <= 8:
+ continue
+ # Column 0 is the executable name
+ # Column 1 is the pid
+ # Column 8 is the Inode in use
+ if process_results[8] == socket_name:
+ pids.append((int(process_results[1]), process_results[0]))
+ break
+ logging.info('PidsUsingDevicePort: %s', pids)
+ return pids
+
+ def FileExistsOnDevice(self, file_name):
+ """Checks whether the given file exists on the device.
+
+ Args:
+ file_name: Full path of file to check.
+
+ Returns:
+ True if the file exists, False otherwise.
+ """
+ assert '"' not in file_name, 'file_name cannot contain double quotes'
+ try:
+ status = self._adb.SendShellCommand(
+ '\'test -e "%s"; echo $?\'' % (file_name))
+ if 'test: not found' not in status:
+ return int(status) == 0
+
+ status = self._adb.SendShellCommand(
+ '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
+ return int(status) == 0
+ except ValueError:
+ if IsDeviceAttached(self._device):
+ raise errors.DeviceUnresponsiveError('Device may be offline.')
+
+ return False
+
+ def IsFileWritableOnDevice(self, file_name):
+ """Checks whether the given file (or directory) is writable on the device.
+
+ Args:
+ file_name: Full path of file/directory to check.
+
+ Returns:
+ True if writable, False otherwise.
+ """
+ assert '"' not in file_name, 'file_name cannot contain double quotes'
+ try:
+ status = self._adb.SendShellCommand(
+ '\'test -w "%s"; echo $?\'' % (file_name))
+ if 'test: not found' not in status:
+ return int(status) == 0
+ raise errors.AbortError('"test" binary not found. OS too old.')
+
+ except ValueError:
+ if IsDeviceAttached(self._device):
+ raise errors.DeviceUnresponsiveError('Device may be offline.')
+
+ return False
+
+ @staticmethod
+ def GetTimestamp():
+ return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
+
+ @staticmethod
+ def EnsureHostDirectory(host_file):
+ host_dir = os.path.dirname(os.path.abspath(host_file))
+ if not os.path.exists(host_dir):
+ os.makedirs(host_dir)
+
+ def TakeScreenshot(self, host_file=None):
+ """Saves a screenshot image to |host_file| on the host.
+
+ Args:
+ host_file: Absolute path to the image file to store on the host or None to
+ use an autogenerated file name.
+
+ Returns:
+ Resulting host file name of the screenshot.
+ """
+ host_file = os.path.abspath(host_file or
+ 'screenshot-%s.png' % self.GetTimestamp())
+ self.EnsureHostDirectory(host_file)
+ device_file = '%s/screenshot.png' % self.GetExternalStorage()
+ self.RunShellCommand(
+ '/system/bin/screencap -p %s' % device_file)
+ self.PullFileFromDevice(device_file, host_file)
+ self.RunShellCommand('rm -f "%s"' % device_file)
+ return host_file
+
+ def PullFileFromDevice(self, device_file, host_file):
+ """Download |device_file| on the device from to |host_file| on the host.
+
+ Args:
+ device_file: Absolute path to the file to retrieve from the device.
+ host_file: Absolute path to the file to store on the host.
+ """
+ if not self._adb.Pull(device_file, host_file):
+ raise device_errors.AdbCommandFailedError(
+ ['pull', device_file, host_file], 'Failed to pull file from device.')
+ assert os.path.exists(host_file)
+
+ def SetUtilWrapper(self, util_wrapper):
+ """Sets a wrapper prefix to be used when running a locally-built
+ binary on the device (ex.: md5sum_bin).
+ """
+ self._util_wrapper = util_wrapper
+
+ def RunUIAutomatorTest(self, test, test_package, timeout):
+ """Runs a single uiautomator test.
+
+ Args:
+ test: Test class/method.
+ test_package: Name of the test jar.
+ timeout: Timeout time in seconds.
+
+ Returns:
+ An instance of am_instrument_parser.TestResult object.
+ """
+ cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
+ self._LogShell(cmd)
+ output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
+ # uiautomator doesn't fully conform to the instrumenation test runner
+ # convention and doesn't terminate with INSTRUMENTATION_CODE.
+ # Just assume the first result is valid.
+ (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
+ if not test_results:
+ raise errors.InstrumentationError(
+ 'no test results... device setup correctly?')
+ return test_results[0]
+
+ def DismissCrashDialogIfNeeded(self):
+ """Dismiss the error/ANR dialog if present.
+
+ Returns: Name of the crashed package if a dialog is focused,
+ None otherwise.
+ """
+ re_focus = re.compile(
+ r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
+
+ def _FindFocusedWindow():
+ match = None
+ for line in self.RunShellCommand('dumpsys window windows'):
+ match = re.match(re_focus, line)
+ if match:
+ break
+ return match
+
+ match = _FindFocusedWindow()
+ if not match:
+ return
+ package = match.group(2)
+ logging.warning('Trying to dismiss %s dialog for %s' % match.groups())
+ self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
+ self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
+ self.SendKeyEvent(KEYCODE_ENTER)
+ match = _FindFocusedWindow()
+ if match:
+ logging.error('Still showing a %s dialog for %s' % match.groups())
+ return package
+
+ def EfficientDeviceDirectoryCopy(self, source, dest):
+ """ Copy a directory efficiently on the device
+
+ Uses a shell script running on the target to copy new and changed files the
+ source directory to the destination directory and remove added files. This
+ is in some cases much faster than cp -r.
+
+ Args:
+ source: absolute path of source directory
+ dest: absolute path of destination directory
+ """
+ logging.info('In EfficientDeviceDirectoryCopy %s %s', source, dest)
+ with DeviceTempFile(self, suffix=".sh") as temp_script_file:
+ host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
+ 'build',
+ 'android',
+ 'pylib',
+ 'efficient_android_directory_copy.sh')
+ self._adb.Push(host_script_path, temp_script_file.name)
+ out = self.RunShellCommand(
+ 'sh %s %s %s' % (temp_script_file.name, source, dest),
+ timeout_time=120)
+ if self._device:
+ device_repr = self._device[-4:]
+ else:
+ device_repr = '????'
+ for line in out:
+ logging.info('[%s]> %s', device_repr, line)
+
+ def _GetControlUsbChargingCommand(self):
+ if self._control_usb_charging_command['cached']:
+ return self._control_usb_charging_command['command']
+ self._control_usb_charging_command['cached'] = True
+ if not self.IsRootEnabled():
+ return None
+ for command in CONTROL_USB_CHARGING_COMMANDS:
+ # Assert command is valid.
+ assert 'disable_command' in command
+ assert 'enable_command' in command
+ assert 'witness_file' in command
+ witness_file = command['witness_file']
+ if self.FileExistsOnDevice(witness_file):
+ self._control_usb_charging_command['command'] = command
+ return command
+ return None
+
+ def CanControlUsbCharging(self):
+ return self._GetControlUsbChargingCommand() is not None
+
+ def DisableUsbCharging(self, timeout=10):
+ command = self._GetControlUsbChargingCommand()
+ if not command:
+ raise Exception('Unable to act on usb charging.')
+ disable_command = command['disable_command']
+ t0 = time.time()
+ # Do not loop directly on self.IsDeviceCharging to cut the number of calls
+ # to the device.
+ while True:
+ if t0 + timeout - time.time() < 0:
+ raise pexpect.TIMEOUT('Unable to disable USB charging in time: %s' % (
+ self.GetBatteryInfo()))
+ self.RunShellCommand(disable_command)
+ if not self.IsDeviceCharging():
+ break
+
+ def EnableUsbCharging(self, timeout=10):
+ command = self._GetControlUsbChargingCommand()
+ if not command:
+ raise Exception('Unable to act on usb charging.')
+ disable_command = command['enable_command']
+ t0 = time.time()
+ # Do not loop directly on self.IsDeviceCharging to cut the number of calls
+ # to the device.
+ while True:
+ if t0 + timeout - time.time() < 0:
+ raise pexpect.TIMEOUT('Unable to enable USB charging in time.')
+ self.RunShellCommand(disable_command)
+ if self.IsDeviceCharging():
+ break
+
+ def IsDeviceCharging(self):
+ for line in self.RunShellCommand('dumpsys battery'):
+ if 'powered: ' in line:
+ if line.split('powered: ')[1] == 'true':
+ return True
+
+
+class NewLineNormalizer(object):
+ """A file-like object to normalize EOLs to '\n'.
+
+ Pexpect runs adb within a pseudo-tty device (see
+ http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
+ as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
+ lines, the log ends up having '\r\r\n' at the end of each line. This
+ filter replaces the above with a single '\n' in the data stream.
+ """
+ def __init__(self, output):
+ self._output = output
+
+ def write(self, data):
+ data = data.replace('\r\r\n', '\n')
+ self._output.write(data)
+
+ def flush(self):
+ self._output.flush()
diff --git a/build/android/pylib/android_commands_unittest.py b/build/android/pylib/android_commands_unittest.py
new file mode 100644
index 00000000000..21c34f93663
--- /dev/null
+++ b/build/android/pylib/android_commands_unittest.py
@@ -0,0 +1,191 @@
+# Copyright 2014 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.
+
+import os
+import shutil
+import sys
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
+
+from pylib import android_commands
+
+# pylint: disable=W0212,W0702
+
+class TestDeviceTempFile(unittest.TestCase):
+ def setUp(self):
+ if not os.getenv('BUILDTYPE'):
+ os.environ['BUILDTYPE'] = 'Debug'
+
+ devices = android_commands.GetAttachedDevices()
+ self.assertGreater(len(devices), 0, 'No device attached!')
+ self.ac = android_commands.AndroidCommands(device=devices[0])
+
+ def testTempFileDeleted(self):
+ """Tests that DeviceTempFile deletes files when closed."""
+ temp_file = android_commands.DeviceTempFile(self.ac)
+ self.assertFalse(self.ac.FileExistsOnDevice(temp_file.name))
+ self.ac.SetFileContents(temp_file.name, "contents")
+ self.assertTrue(self.ac.FileExistsOnDevice(temp_file.name))
+ temp_file.close()
+ self.assertFalse(self.ac.FileExistsOnDevice(temp_file.name))
+
+ with android_commands.DeviceTempFile(self.ac) as with_temp_file:
+ self.assertFalse(self.ac.FileExistsOnDevice(with_temp_file.name))
+ self.ac.SetFileContents(with_temp_file.name, "contents")
+ self.assertTrue(self.ac.FileExistsOnDevice(with_temp_file.name))
+
+ self.assertFalse(self.ac.FileExistsOnDevice(with_temp_file.name))
+
+ def testTempFileNotWritten(self):
+ """Tests that device temp files work successfully even if not written to."""
+ temp_file = android_commands.DeviceTempFile(self.ac)
+ temp_file.close()
+ self.assertFalse(self.ac.FileExistsOnDevice(temp_file.name))
+
+ with android_commands.DeviceTempFile(self.ac) as with_temp_file:
+ pass
+ self.assertFalse(self.ac.FileExistsOnDevice(with_temp_file.name))
+
+ def testNaming(self):
+ """Tests that returned filenames are as requested."""
+ temp_file = android_commands.DeviceTempFile(self.ac, prefix="cat")
+ self.assertTrue(os.path.basename(temp_file.name).startswith("cat"))
+
+ temp_file = android_commands.DeviceTempFile(self.ac, suffix="dog")
+ self.assertTrue(temp_file.name.endswith("dog"))
+
+ temp_file = android_commands.DeviceTempFile(
+ self.ac, prefix="cat", suffix="dog")
+ self.assertTrue(os.path.basename(temp_file.name).startswith("cat"))
+ self.assertTrue(temp_file.name.endswith("dog"))
+
+
+class TestGetFilesChanged(unittest.TestCase):
+
+ def setUp(self):
+ if not os.getenv('BUILDTYPE'):
+ os.environ['BUILDTYPE'] = 'Debug'
+
+ devices = android_commands.GetAttachedDevices()
+ self.assertGreater(len(devices), 0, 'No device attached!')
+ self.ac = android_commands.AndroidCommands(device=devices[0])
+ self.host_data_dir = os.path.realpath('test_push_data')
+ self.device_data_dir = '%s/test_push_data' % (
+ self.ac.RunShellCommand('realpath %s' %
+ self.ac.GetExternalStorage())[0])
+
+ os.mkdir(self.host_data_dir)
+ for i in xrange(1, 10):
+ with open('%s/%d.txt' % (self.host_data_dir, i), 'w') as f:
+ f.write('file #%d' % i)
+
+ self.ac.RunShellCommand('mkdir %s' % self.device_data_dir)
+
+ def testGetFilesChangedAllNeeded(self):
+ """ Tests GetFilesChanged when none of the files are on the device.
+ """
+ expected = [('%s/%d.txt' % (self.host_data_dir, i),
+ '%s/%d.txt' % (self.device_data_dir, i))
+ for i in xrange(1, 10)]
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedSomeIdentical(self):
+ """ Tests GetFilesChanged when some of the files are on the device.
+ """
+ for i in xrange(1, 5):
+ self.ac._adb.Push('%s/%d.txt' % (self.host_data_dir, i),
+ self.device_data_dir)
+ expected = [('%s/%d.txt' % (self.host_data_dir, i),
+ '%s/%d.txt' % (self.device_data_dir, i))
+ for i in xrange(5, 10)]
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedAllIdentical(self):
+ """ Tests GetFilesChanged when all of the files are on the device.
+ """
+ for i in xrange(1, 10):
+ self.ac._adb.Push('%s/%d.txt' % (self.host_data_dir, i),
+ self.device_data_dir)
+ expected = []
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedRename(self):
+ """ Tests GetFilesChanged when one of the files has been renamed.
+
+ This tests both with and without the ignore_filenames flag set.
+ """
+ for i in xrange(5, 10):
+ self.ac._adb.Push('%s/%d.txt' % (self.host_data_dir, i),
+ self.device_data_dir)
+ os.rename('%s/5.txt' % (self.host_data_dir),
+ '%s/99.txt' % (self.host_data_dir))
+
+ expected = [('%s/%d.txt' % (self.host_data_dir, i),
+ '%s/%d.txt' % (self.device_data_dir, i))
+ for i in xrange(1, 5)]
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir,
+ ignore_filenames=True)
+ self.assertSequenceEqual(expected, actual)
+
+ expected.append(('%s/99.txt' % self.host_data_dir,
+ '%s/99.txt' % self.device_data_dir))
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedCopy(self):
+ """ Tests GetFilesChanged when one of the files has been copied.
+
+ This tests both with and without the ignore_filenames flag set.
+ """
+ for i in xrange(5, 10):
+ self.ac._adb.Push('%s/%d.txt' % (self.host_data_dir, i),
+ self.device_data_dir)
+ shutil.copy('%s/5.txt' % self.host_data_dir,
+ '%s/99.txt' % self.host_data_dir)
+
+ expected = [('%s/%d.txt' % (self.host_data_dir, i),
+ '%s/%d.txt' % (self.device_data_dir, i))
+ for i in xrange(1, 5)]
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir,
+ ignore_filenames=True)
+ self.assertSequenceEqual(expected, actual)
+
+ expected.append(('%s/99.txt' % self.host_data_dir,
+ '%s/99.txt' % self.device_data_dir))
+ actual = self.ac.GetFilesChanged(self.host_data_dir, self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedIndividual(self):
+ """ Tests GetFilesChanged when provided one file.
+ """
+ expected = [('%s/1.txt' % self.host_data_dir,
+ '%s/1.txt' % self.device_data_dir)]
+ actual = self.ac.GetFilesChanged('%s/1.txt' % self.host_data_dir,
+ '%s/1.txt' % self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def testGetFilesChangedFileToDirectory(self):
+ """ Tests GetFilesChanged when provided a file from the host and a
+ directory on the device.
+ """
+ expected = [('%s/1.txt' % self.host_data_dir,
+ '%s' % self.device_data_dir)]
+ actual = self.ac.GetFilesChanged('%s/1.txt' % self.host_data_dir,
+ '%s' % self.device_data_dir)
+ self.assertSequenceEqual(expected, actual)
+
+ def tearDown(self):
+ try:
+ shutil.rmtree(self.host_data_dir)
+ self.ac.RunShellCommand('rm -rf %s' % self.device_data_dir)
+ except:
+ pass
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/build/android/pylib/base/__init__.py b/build/android/pylib/base/__init__.py
new file mode 100644
index 00000000000..727e987e6b6
--- /dev/null
+++ b/build/android/pylib/base/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2012 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.
+
diff --git a/build/android/pylib/base/base_setup.py b/build/android/pylib/base/base_setup.py
new file mode 100644
index 00000000000..a416380a949
--- /dev/null
+++ b/build/android/pylib/base/base_setup.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2014 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.
+
+"""Base script for doing test setup."""
+
+import logging
+import os
+
+from pylib import constants
+from pylib import valgrind_tools
+from pylib.utils import isolator
+
+def GenerateDepsDirUsingIsolate(suite_name, isolate_file_path,
+ isolate_file_paths, deps_exclusion_list):
+ """Generate the dependency dir for the test suite using isolate.
+
+ Args:
+ suite_name: Name of the test suite (e.g. base_unittests).
+ isolate_file_path: .isolate file path to use. If there is a default .isolate
+ file path for the suite_name, this will override it.
+ isolate_file_paths: Dictionary with the default .isolate file paths for
+ the test suites.
+ deps_exclusion_list: A list of files that are listed as dependencies in the
+ .isolate files but should not be pushed to the device.
+ Returns:
+ The Isolator instance used to remap the dependencies, or None.
+ """
+ if isolate_file_path:
+ if os.path.isabs(isolate_file_path):
+ isolate_abs_path = isolate_file_path
+ else:
+ isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT,
+ isolate_file_path)
+ else:
+ isolate_rel_path = isolate_file_paths.get(suite_name)
+ if not isolate_rel_path:
+ logging.info('Did not find an isolate file for the test suite.')
+ return
+ isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
+
+ isolated_abs_path = os.path.join(
+ constants.GetOutDirectory(), '%s.isolated' % suite_name)
+ assert os.path.exists(isolate_abs_path), 'Cannot find %s' % isolate_abs_path
+
+ i = isolator.Isolator(constants.ISOLATE_DEPS_DIR)
+ i.Clear()
+ i.Remap(isolate_abs_path, isolated_abs_path)
+ # We're relying on the fact that timestamps are preserved
+ # by the remap command (hardlinked). Otherwise, all the data
+ # will be pushed to the device once we move to using time diff
+ # instead of md5sum. Perform a sanity check here.
+ i.VerifyHardlinks()
+ i.PurgeExcluded(deps_exclusion_list)
+ i.MoveOutputDeps()
+ return i
+
+
+def PushDataDeps(device, device_dir, test_options):
+ valgrind_tools.PushFilesForTool(test_options.tool, device)
+ if os.path.exists(constants.ISOLATE_DEPS_DIR):
+ device.PushChangedFiles([(constants.ISOLATE_DEPS_DIR, device_dir)],
+ delete_device_stale=test_options.delete_stale_data)
diff --git a/build/android/pylib/base/base_test_result.py b/build/android/pylib/base/base_test_result.py
new file mode 100644
index 00000000000..58200f6d495
--- /dev/null
+++ b/build/android/pylib/base/base_test_result.py
@@ -0,0 +1,216 @@
+# Copyright (c) 2013 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.
+
+"""Module containing base test results classes."""
+
+class ResultType(object):
+ """Class enumerating test types."""
+ PASS = 'PASS'
+ SKIP = 'SKIP'
+ FAIL = 'FAIL'
+ CRASH = 'CRASH'
+ TIMEOUT = 'TIMEOUT'
+ UNKNOWN = 'UNKNOWN'
+
+ @staticmethod
+ def GetTypes():
+ """Get a list of all test types."""
+ return [ResultType.PASS, ResultType.SKIP, ResultType.FAIL,
+ ResultType.CRASH, ResultType.TIMEOUT, ResultType.UNKNOWN]
+
+
+class BaseTestResult(object):
+ """Base class for a single test result."""
+
+ def __init__(self, name, test_type, duration=0, log=''):
+ """Construct a BaseTestResult.
+
+ Args:
+ name: Name of the test which defines uniqueness.
+ test_type: Type of the test result as defined in ResultType.
+ duration: Time it took for the test to run in milliseconds.
+ log: An optional string listing any errors.
+ """
+ assert name
+ assert test_type in ResultType.GetTypes()
+ self._name = name
+ self._test_type = test_type
+ self._duration = duration
+ self._log = log
+
+ def __str__(self):
+ return self._name
+
+ def __repr__(self):
+ return self._name
+
+ def __cmp__(self, other):
+ # pylint: disable=W0212
+ return cmp(self._name, other._name)
+
+ def __hash__(self):
+ return hash(self._name)
+
+ def SetName(self, name):
+ """Set the test name.
+
+ Because we're putting this into a set, this should only be used if moving
+ this test result into another set.
+ """
+ self._name = name
+
+ def GetName(self):
+ """Get the test name."""
+ return self._name
+
+ def SetType(self, test_type):
+ """Set the test result type."""
+ assert test_type in ResultType.GetTypes()
+ self._test_type = test_type
+
+ def GetType(self):
+ """Get the test result type."""
+ return self._test_type
+
+ def GetDuration(self):
+ """Get the test duration."""
+ return self._duration
+
+ def SetLog(self, log):
+ """Set the test log."""
+ self._log = log
+
+ def GetLog(self):
+ """Get the test log."""
+ return self._log
+
+
+class TestRunResults(object):
+ """Set of results for a test run."""
+
+ def __init__(self):
+ self._results = set()
+
+ def GetLogs(self):
+ """Get the string representation of all test logs."""
+ s = []
+ for test_type in ResultType.GetTypes():
+ if test_type != ResultType.PASS:
+ for t in sorted(self._GetType(test_type)):
+ log = t.GetLog()
+ if log:
+ s.append('[%s] %s:' % (test_type, t))
+ s.append(log)
+ return '\n'.join(s)
+
+ def GetGtestForm(self):
+ """Get the gtest string representation of this object."""
+ s = []
+ plural = lambda n, s, p: '%d %s' % (n, p if n != 1 else s)
+ tests = lambda n: plural(n, 'test', 'tests')
+
+ s.append('[==========] %s ran.' % (tests(len(self.GetAll()))))
+ s.append('[ PASSED ] %s.' % (tests(len(self.GetPass()))))
+
+ skipped = self.GetSkip()
+ if skipped:
+ s.append('[ SKIPPED ] Skipped %s, listed below:' % tests(len(skipped)))
+ for t in sorted(skipped):
+ s.append('[ SKIPPED ] %s' % str(t))
+
+ all_failures = self.GetFail().union(self.GetCrash(), self.GetTimeout(),
+ self.GetUnknown())
+ if all_failures:
+ s.append('[ FAILED ] %s, listed below:' % tests(len(all_failures)))
+ for t in sorted(self.GetFail()):
+ s.append('[ FAILED ] %s' % str(t))
+ for t in sorted(self.GetCrash()):
+ s.append('[ FAILED ] %s (CRASHED)' % str(t))
+ for t in sorted(self.GetTimeout()):
+ s.append('[ FAILED ] %s (TIMEOUT)' % str(t))
+ for t in sorted(self.GetUnknown()):
+ s.append('[ FAILED ] %s (UNKNOWN)' % str(t))
+ s.append('')
+ s.append(plural(len(all_failures), 'FAILED TEST', 'FAILED TESTS'))
+ return '\n'.join(s)
+
+ def GetShortForm(self):
+ """Get the short string representation of this object."""
+ s = []
+ s.append('ALL: %d' % len(self._results))
+ for test_type in ResultType.GetTypes():
+ s.append('%s: %d' % (test_type, len(self._GetType(test_type))))
+ return ''.join([x.ljust(15) for x in s])
+
+ def __str__(self):
+ return self.GetLongForm()
+
+ def AddResult(self, result):
+ """Add |result| to the set.
+
+ Args:
+ result: An instance of BaseTestResult.
+ """
+ assert isinstance(result, BaseTestResult)
+ self._results.add(result)
+
+ def AddResults(self, results):
+ """Add |results| to the set.
+
+ Args:
+ results: An iterable of BaseTestResult objects.
+ """
+ for t in results:
+ self.AddResult(t)
+
+ def AddTestRunResults(self, results):
+ """Add the set of test results from |results|.
+
+ Args:
+ results: An instance of TestRunResults.
+ """
+ assert isinstance(results, TestRunResults)
+ # pylint: disable=W0212
+ self._results.update(results._results)
+
+ def GetAll(self):
+ """Get the set of all test results."""
+ return self._results.copy()
+
+ def _GetType(self, test_type):
+ """Get the set of test results with the given test type."""
+ return set(t for t in self._results if t.GetType() == test_type)
+
+ def GetPass(self):
+ """Get the set of all passed test results."""
+ return self._GetType(ResultType.PASS)
+
+ def GetSkip(self):
+ """Get the set of all skipped test results."""
+ return self._GetType(ResultType.SKIP)
+
+ def GetFail(self):
+ """Get the set of all failed test results."""
+ return self._GetType(ResultType.FAIL)
+
+ def GetCrash(self):
+ """Get the set of all crashed test results."""
+ return self._GetType(ResultType.CRASH)
+
+ def GetTimeout(self):
+ """Get the set of all timed out test results."""
+ return self._GetType(ResultType.TIMEOUT)
+
+ def GetUnknown(self):
+ """Get the set of all unknown test results."""
+ return self._GetType(ResultType.UNKNOWN)
+
+ def GetNotPass(self):
+ """Get the set of all non-passed test results."""
+ return self.GetAll() - self.GetPass()
+
+ def DidRunPass(self):
+ """Return whether the test run was successful."""
+ return not self.GetNotPass() - self.GetSkip()
+
diff --git a/build/android/pylib/base/base_test_result_unittest.py b/build/android/pylib/base/base_test_result_unittest.py
new file mode 100644
index 00000000000..6f0cba7726e
--- /dev/null
+++ b/build/android/pylib/base/base_test_result_unittest.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2013 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.
+
+"""Unittests for TestRunResults."""
+
+import unittest
+
+from pylib.base.base_test_result import BaseTestResult
+from pylib.base.base_test_result import TestRunResults
+from pylib.base.base_test_result import ResultType
+
+
+class TestTestRunResults(unittest.TestCase):
+ def setUp(self):
+ self.p1 = BaseTestResult('p1', ResultType.PASS, log='pass1')
+ other_p1 = BaseTestResult('p1', ResultType.PASS)
+ self.p2 = BaseTestResult('p2', ResultType.PASS)
+ self.f1 = BaseTestResult('f1', ResultType.FAIL, log='failure1')
+ self.c1 = BaseTestResult('c1', ResultType.CRASH, log='crash1')
+ self.u1 = BaseTestResult('u1', ResultType.UNKNOWN)
+ self.tr = TestRunResults()
+ self.tr.AddResult(self.p1)
+ self.tr.AddResult(other_p1)
+ self.tr.AddResult(self.p2)
+ self.tr.AddResults(set([self.f1, self.c1, self.u1]))
+
+ def testGetAll(self):
+ self.assertFalse(
+ self.tr.GetAll().symmetric_difference(
+ [self.p1, self.p2, self.f1, self.c1, self.u1]))
+
+ def testGetPass(self):
+ self.assertFalse(self.tr.GetPass().symmetric_difference(
+ [self.p1, self.p2]))
+
+ def testGetNotPass(self):
+ self.assertFalse(self.tr.GetNotPass().symmetric_difference(
+ [self.f1, self.c1, self.u1]))
+
+ def testGetAddTestRunResults(self):
+ tr2 = TestRunResults()
+ other_p1 = BaseTestResult('p1', ResultType.PASS)
+ f2 = BaseTestResult('f2', ResultType.FAIL)
+ tr2.AddResult(other_p1)
+ tr2.AddResult(f2)
+ tr2.AddTestRunResults(self.tr)
+ self.assertFalse(
+ tr2.GetAll().symmetric_difference(
+ [self.p1, self.p2, self.f1, self.c1, self.u1, f2]))
+
+ def testGetLogs(self):
+ log_print = ('[FAIL] f1:\n'
+ 'failure1\n'
+ '[CRASH] c1:\n'
+ 'crash1')
+ self.assertEqual(self.tr.GetLogs(), log_print)
+
+ def testGetShortForm(self):
+ short_print = ('ALL: 5 PASS: 2 FAIL: 1 '
+ 'CRASH: 1 TIMEOUT: 0 UNKNOWN: 1 ')
+ self.assertEqual(self.tr.GetShortForm(), short_print)
+
+ def testGetGtestForm(self):
+ gtest_print = ('[==========] 5 tests ran.\n'
+ '[ PASSED ] 2 tests.\n'
+ '[ FAILED ] 3 tests, listed below:\n'
+ '[ FAILED ] f1\n'
+ '[ FAILED ] c1 (CRASHED)\n'
+ '[ FAILED ] u1 (UNKNOWN)\n'
+ '\n'
+ '3 FAILED TESTS')
+ self.assertEqual(gtest_print, self.tr.GetGtestForm())
+
+ def testRunPassed(self):
+ self.assertFalse(self.tr.DidRunPass())
+ tr2 = TestRunResults()
+ self.assertTrue(tr2.DidRunPass())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/base/base_test_runner.py b/build/android/pylib/base/base_test_runner.py
new file mode 100644
index 00000000000..2a7fdd3d6ed
--- /dev/null
+++ b/build/android/pylib/base/base_test_runner.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2012 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.
+
+"""Base class for running tests on a single device."""
+
+# TODO(jbudorick) Deprecate and remove this class and all subclasses after
+# any relevant parts have been ported to the new environment + test instance
+# model.
+
+import logging
+
+from pylib import ports
+from pylib.device import device_utils
+from pylib.forwarder import Forwarder
+from pylib.valgrind_tools import CreateTool
+# TODO(frankf): Move this to pylib/utils
+import lighttpd_server
+
+
+# A file on device to store ports of net test server. The format of the file is
+# test-spawner-server-port:test-server-port
+NET_TEST_SERVER_PORT_INFO_FILE = 'net-test-server-ports'
+
+
+class BaseTestRunner(object):
+ """Base class for running tests on a single device."""
+
+ def __init__(self, device, tool):
+ """
+ Args:
+ device: An instance of DeviceUtils that the tests will run on.
+ tool: Name of the Valgrind tool.
+ """
+ assert isinstance(device, device_utils.DeviceUtils)
+ self.device = device
+ self.device_serial = self.device.adb.GetDeviceSerial()
+ self.tool = CreateTool(tool, self.device)
+ self._http_server = None
+ self._forwarder_device_port = 8000
+ self.forwarder_base_url = ('http://localhost:%d' %
+ self._forwarder_device_port)
+ # We will allocate port for test server spawner when calling method
+ # LaunchChromeTestServerSpawner and allocate port for test server when
+ # starting it in TestServerThread.
+ self.test_server_spawner_port = 0
+ self.test_server_port = 0
+
+ def _PushTestServerPortInfoToDevice(self):
+ """Pushes the latest port information to device."""
+ self.device.WriteFile(
+ self.device.GetExternalStoragePath() + '/' +
+ NET_TEST_SERVER_PORT_INFO_FILE,
+ '%d:%d' % (self.test_server_spawner_port, self.test_server_port))
+
+ def RunTest(self, test):
+ """Runs a test. Needs to be overridden.
+
+ Args:
+ test: A test to run.
+
+ Returns:
+ Tuple containing:
+ (base_test_result.TestRunResults, tests to rerun or None)
+ """
+ raise NotImplementedError
+
+ def InstallTestPackage(self):
+ """Installs the test package once before all tests are run."""
+ pass
+
+ def SetUp(self):
+ """Run once before all tests are run."""
+ self.InstallTestPackage()
+
+ def TearDown(self):
+ """Run once after all tests are run."""
+ self.ShutdownHelperToolsForTestSuite()
+
+ def LaunchTestHttpServer(self, document_root, port=None,
+ extra_config_contents=None):
+ """Launches an HTTP server to serve HTTP tests.
+
+ Args:
+ document_root: Document root of the HTTP server.
+ port: port on which we want to the http server bind.
+ extra_config_contents: Extra config contents for the HTTP server.
+ """
+ self._http_server = lighttpd_server.LighttpdServer(
+ document_root, port=port, extra_config_contents=extra_config_contents)
+ if self._http_server.StartupHttpServer():
+ logging.info('http server started: http://localhost:%s',
+ self._http_server.port)
+ else:
+ logging.critical('Failed to start http server')
+ self._ForwardPortsForHttpServer()
+ return (self._forwarder_device_port, self._http_server.port)
+
+ def _ForwardPorts(self, port_pairs):
+ """Forwards a port."""
+ Forwarder.Map(port_pairs, self.device, self.tool)
+
+ def _UnmapPorts(self, port_pairs):
+ """Unmap previously forwarded ports."""
+ for (device_port, _) in port_pairs:
+ Forwarder.UnmapDevicePort(device_port, self.device)
+
+ # Deprecated: Use ForwardPorts instead.
+ def StartForwarder(self, port_pairs):
+ """Starts TCP traffic forwarding for the given |port_pairs|.
+
+ Args:
+ host_port_pairs: A list of (device_port, local_port) tuples to forward.
+ """
+ self._ForwardPorts(port_pairs)
+
+ def _ForwardPortsForHttpServer(self):
+ """Starts a forwarder for the HTTP server.
+
+ The forwarder forwards HTTP requests and responses between host and device.
+ """
+ self._ForwardPorts([(self._forwarder_device_port, self._http_server.port)])
+
+ def _RestartHttpServerForwarderIfNecessary(self):
+ """Restarts the forwarder if it's not open."""
+ # Checks to see if the http server port is being used. If not forwards the
+ # request.
+ # TODO(dtrainor): This is not always reliable because sometimes the port
+ # will be left open even after the forwarder has been killed.
+ if not ports.IsDevicePortUsed(self.device, self._forwarder_device_port):
+ self._ForwardPortsForHttpServer()
+
+ def ShutdownHelperToolsForTestSuite(self):
+ """Shuts down the server and the forwarder."""
+ if self._http_server:
+ self._UnmapPorts([(self._forwarder_device_port, self._http_server.port)])
+ self._http_server.ShutdownHttpServer()
+
diff --git a/build/android/pylib/base/environment.py b/build/android/pylib/base/environment.py
new file mode 100644
index 00000000000..3f49f41f299
--- /dev/null
+++ b/build/android/pylib/base/environment.py
@@ -0,0 +1,34 @@
+# Copyright 2014 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.
+
+
+class Environment(object):
+ """An environment in which tests can be run.
+
+ This is expected to handle all logic that is applicable to an entire specific
+ environment but is independent of the test type.
+
+ Examples include:
+ - The local device environment, for running tests on devices attached to
+ the local machine.
+ - The local machine environment, for running tests directly on the local
+ machine.
+ """
+
+ def __init__(self):
+ pass
+
+ def SetUp(self):
+ raise NotImplementedError
+
+ def TearDown(self):
+ raise NotImplementedError
+
+ def __enter__(self):
+ self.SetUp()
+ return self
+
+ def __exit__(self, _exc_type, _exc_val, _exc_tb):
+ self.TearDown()
+
diff --git a/build/android/pylib/base/environment_factory.py b/build/android/pylib/base/environment_factory.py
new file mode 100644
index 00000000000..31b4952ac16
--- /dev/null
+++ b/build/android/pylib/base/environment_factory.py
@@ -0,0 +1,18 @@
+# Copyright 2014 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.
+
+from pylib import constants
+from pylib.local.device import local_device_environment
+from pylib.remote.device import remote_device_environment
+
+def CreateEnvironment(args, error_func):
+
+ if args.environment == 'local':
+ if args.command not in constants.LOCAL_MACHINE_TESTS:
+ return local_device_environment.LocalDeviceEnvironment(args, error_func)
+ # TODO(jbudorick) Add local machine environment.
+ if args.environment == 'remote_device':
+ return remote_device_environment.RemoteDeviceEnvironment(args,
+ error_func)
+ error_func('Unable to create %s environment.' % args.environment)
diff --git a/build/android/pylib/base/test_collection.py b/build/android/pylib/base/test_collection.py
new file mode 100644
index 00000000000..de510272bd7
--- /dev/null
+++ b/build/android/pylib/base/test_collection.py
@@ -0,0 +1,80 @@
+# Copyright 2013 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.
+
+import threading
+
+class TestCollection(object):
+ """A threadsafe collection of tests.
+
+ Args:
+ tests: List of tests to put in the collection.
+ """
+
+ def __init__(self, tests=None):
+ if not tests:
+ tests = []
+ self._lock = threading.Lock()
+ self._tests = []
+ self._tests_in_progress = 0
+ # Used to signal that an item is available or all items have been handled.
+ self._item_available_or_all_done = threading.Event()
+ for t in tests:
+ self.add(t)
+
+ def _pop(self):
+ """Pop a test from the collection.
+
+ Waits until a test is available or all tests have been handled.
+
+ Returns:
+ A test or None if all tests have been handled.
+ """
+ while True:
+ # Wait for a test to be available or all tests to have been handled.
+ self._item_available_or_all_done.wait()
+ with self._lock:
+ # Check which of the two conditions triggered the signal.
+ if self._tests_in_progress == 0:
+ return None
+ try:
+ return self._tests.pop(0)
+ except IndexError:
+ # Another thread beat us to the available test, wait again.
+ self._item_available_or_all_done.clear()
+
+ def add(self, test):
+ """Add a test to the collection.
+
+ Args:
+ test: A test to add.
+ """
+ with self._lock:
+ self._tests.append(test)
+ self._item_available_or_all_done.set()
+ self._tests_in_progress += 1
+
+ def test_completed(self):
+ """Indicate that a test has been fully handled."""
+ with self._lock:
+ self._tests_in_progress -= 1
+ if self._tests_in_progress == 0:
+ # All tests have been handled, signal all waiting threads.
+ self._item_available_or_all_done.set()
+
+ def __iter__(self):
+ """Iterate through tests in the collection until all have been handled."""
+ while True:
+ r = self._pop()
+ if r is None:
+ break
+ yield r
+
+ def __len__(self):
+ """Return the number of tests currently in the collection."""
+ return len(self._tests)
+
+ def test_names(self):
+ """Return a list of the names of the tests currently in the collection."""
+ with self._lock:
+ return list(t.test for t in self._tests)
diff --git a/build/android/pylib/base/test_dispatcher.py b/build/android/pylib/base/test_dispatcher.py
new file mode 100644
index 00000000000..f9199651286
--- /dev/null
+++ b/build/android/pylib/base/test_dispatcher.py
@@ -0,0 +1,332 @@
+# Copyright 2013 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.
+
+"""Dispatches tests, either sharding or replicating them.
+
+Performs the following steps:
+* Create a test collection factory, using the given tests
+ - If sharding: test collection factory returns the same shared test collection
+ to all test runners
+ - If replciating: test collection factory returns a unique test collection to
+ each test runner, with the same set of tests in each.
+* Create a test runner for each device.
+* Run each test runner in its own thread, grabbing tests from the test
+ collection until there are no tests left.
+"""
+
+# TODO(jbudorick) Deprecate and remove this class after any relevant parts have
+# been ported to the new environment / test instance model.
+
+import logging
+import threading
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import test_collection
+from pylib.device import device_errors
+from pylib.utils import reraiser_thread
+from pylib.utils import watchdog_timer
+
+
+DEFAULT_TIMEOUT = 7 * 60 # seven minutes
+
+
+class _ThreadSafeCounter(object):
+ """A threadsafe counter."""
+
+ def __init__(self):
+ self._lock = threading.Lock()
+ self._value = 0
+
+ def GetAndIncrement(self):
+ """Get the current value and increment it atomically.
+
+ Returns:
+ The value before incrementing.
+ """
+ with self._lock:
+ pre_increment = self._value
+ self._value += 1
+ return pre_increment
+
+
+class _Test(object):
+ """Holds a test with additional metadata."""
+
+ def __init__(self, test, tries=0):
+ """Initializes the _Test object.
+
+ Args:
+ test: The test.
+ tries: Number of tries so far.
+ """
+ self.test = test
+ self.tries = tries
+
+
+def _RunTestsFromQueue(runner, collection, out_results, watcher,
+ num_retries, tag_results_with_device=False):
+ """Runs tests from the collection until empty using the given runner.
+
+ Adds TestRunResults objects to the out_results list and may add tests to the
+ out_retry list.
+
+ Args:
+ runner: A TestRunner object used to run the tests.
+ collection: A TestCollection from which to get _Test objects to run.
+ out_results: A list to add TestRunResults to.
+ watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout.
+ num_retries: Number of retries for a test.
+ tag_results_with_device: If True, appends the name of the device on which
+ the test was run to the test name. Used when replicating to identify
+ which device ran each copy of the test, and to ensure each copy of the
+ test is recorded separately.
+ """
+
+ def TagTestRunResults(test_run_results):
+ """Tags all results with the last 4 digits of the device id.
+
+ Used when replicating tests to distinguish the same tests run on different
+ devices. We use a set to store test results, so the hash (generated from
+ name and tag) must be unique to be considered different results.
+ """
+ new_test_run_results = base_test_result.TestRunResults()
+ for test_result in test_run_results.GetAll():
+ test_result.SetName('%s_%s' % (runner.device_serial[-4:],
+ test_result.GetName()))
+ new_test_run_results.AddResult(test_result)
+ return new_test_run_results
+
+ for test in collection:
+ watcher.Reset()
+ try:
+ if not runner.device.IsOnline():
+ # Device is unresponsive, stop handling tests on this device.
+ msg = 'Device %s is unresponsive.' % runner.device_serial
+ logging.warning(msg)
+ raise device_errors.DeviceUnreachableError(msg)
+ result, retry = runner.RunTest(test.test)
+ if tag_results_with_device:
+ result = TagTestRunResults(result)
+ test.tries += 1
+ if retry and test.tries <= num_retries:
+ # Retry non-passing results, only record passing results.
+ pass_results = base_test_result.TestRunResults()
+ pass_results.AddResults(result.GetPass())
+ out_results.append(pass_results)
+ logging.warning('Will retry test %s, try #%s.', retry, test.tries)
+ collection.add(_Test(test=retry, tries=test.tries))
+ else:
+ # All tests passed or retry limit reached. Either way, record results.
+ out_results.append(result)
+ except:
+ # An unhandleable exception, ensure tests get run by another device and
+ # reraise this exception on the main thread.
+ collection.add(test)
+ raise
+ finally:
+ # Retries count as separate tasks so always mark the popped test as done.
+ collection.test_completed()
+
+
+def _SetUp(runner_factory, device, out_runners, threadsafe_counter):
+ """Creates a test runner for each device and calls SetUp() in parallel.
+
+ Note: if a device is unresponsive the corresponding TestRunner will not be
+ added to out_runners.
+
+ Args:
+ runner_factory: Callable that takes a device and index and returns a
+ TestRunner object.
+ device: The device serial number to set up.
+ out_runners: List to add the successfully set up TestRunner object.
+ threadsafe_counter: A _ThreadSafeCounter object used to get shard indices.
+ """
+ try:
+ index = threadsafe_counter.GetAndIncrement()
+ logging.warning('Creating shard %s for device %s.', index, device)
+ runner = runner_factory(device, index)
+ runner.SetUp()
+ out_runners.append(runner)
+ except device_errors.DeviceUnreachableError as e:
+ logging.warning('Failed to create shard for %s: [%s]', device, e)
+
+
+def _RunAllTests(runners, test_collection_factory, num_retries, timeout=None,
+ tag_results_with_device=False):
+ """Run all tests using the given TestRunners.
+
+ Args:
+ runners: A list of TestRunner objects.
+ test_collection_factory: A callable to generate a TestCollection object for
+ each test runner.
+ num_retries: Number of retries for a test.
+ timeout: Watchdog timeout in seconds.
+ tag_results_with_device: If True, appends the name of the device on which
+ the test was run to the test name. Used when replicating to identify
+ which device ran each copy of the test, and to ensure each copy of the
+ test is recorded separately.
+
+ Returns:
+ A tuple of (TestRunResults object, exit code)
+ """
+ logging.warning('Running tests with %s test runners.' % (len(runners)))
+ results = []
+ exit_code = 0
+ run_results = base_test_result.TestRunResults()
+ watcher = watchdog_timer.WatchdogTimer(timeout)
+ test_collections = [test_collection_factory() for _ in runners]
+
+ threads = [
+ reraiser_thread.ReraiserThread(
+ _RunTestsFromQueue,
+ [r, tc, results, watcher, num_retries, tag_results_with_device],
+ name=r.device_serial[-4:])
+ for r, tc in zip(runners, test_collections)]
+
+ workers = reraiser_thread.ReraiserThreadGroup(threads)
+ workers.StartAll()
+
+ # Catch DeviceUnreachableErrors and set a warning exit code
+ try:
+ workers.JoinAll(watcher)
+ except device_errors.DeviceUnreachableError as e:
+ logging.error(e)
+
+ if not all((len(tc) == 0 for tc in test_collections)):
+ logging.error('Only ran %d tests (all devices are likely offline).' %
+ len(results))
+ for tc in test_collections:
+ run_results.AddResults(base_test_result.BaseTestResult(
+ t, base_test_result.ResultType.UNKNOWN) for t in tc.test_names())
+
+ for r in results:
+ run_results.AddTestRunResults(r)
+ if not run_results.DidRunPass():
+ exit_code = constants.ERROR_EXIT_CODE
+ return (run_results, exit_code)
+
+
+def _CreateRunners(runner_factory, devices, timeout=None):
+ """Creates a test runner for each device and calls SetUp() in parallel.
+
+ Note: if a device is unresponsive the corresponding TestRunner will not be
+ included in the returned list.
+
+ Args:
+ runner_factory: Callable that takes a device and index and returns a
+ TestRunner object.
+ devices: List of device serial numbers as strings.
+ timeout: Watchdog timeout in seconds, defaults to the default timeout.
+
+ Returns:
+ A list of TestRunner objects.
+ """
+ logging.warning('Creating %s test runners.' % len(devices))
+ runners = []
+ counter = _ThreadSafeCounter()
+ threads = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(_SetUp,
+ [runner_factory, d, runners, counter],
+ name=str(d)[-4:])
+ for d in devices])
+ threads.StartAll()
+ threads.JoinAll(watchdog_timer.WatchdogTimer(timeout))
+ return runners
+
+
+def _TearDownRunners(runners, timeout=None):
+ """Calls TearDown() for each test runner in parallel.
+
+ Args:
+ runners: A list of TestRunner objects.
+ timeout: Watchdog timeout in seconds, defaults to the default timeout.
+ """
+ threads = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(r.TearDown, name=r.device_serial[-4:])
+ for r in runners])
+ threads.StartAll()
+ threads.JoinAll(watchdog_timer.WatchdogTimer(timeout))
+
+
+def ApplyMaxPerRun(tests, max_per_run):
+ """Rearrange the tests so that no group contains more than max_per_run tests.
+
+ Args:
+ tests:
+ max_per_run:
+
+ Returns:
+ A list of tests with no more than max_per_run per run.
+ """
+ tests_expanded = []
+ for test_group in tests:
+ if type(test_group) != str:
+ # Do not split test objects which are not strings.
+ tests_expanded.append(test_group)
+ else:
+ test_split = test_group.split(':')
+ for i in range(0, len(test_split), max_per_run):
+ tests_expanded.append(':'.join(test_split[i:i+max_per_run]))
+ return tests_expanded
+
+
+def RunTests(tests, runner_factory, devices, shard=True,
+ test_timeout=DEFAULT_TIMEOUT, setup_timeout=DEFAULT_TIMEOUT,
+ num_retries=2, max_per_run=256):
+ """Run all tests on attached devices, retrying tests that don't pass.
+
+ Args:
+ tests: List of tests to run.
+ runner_factory: Callable that takes a device and index and returns a
+ TestRunner object.
+ devices: List of attached devices.
+ shard: True if we should shard, False if we should replicate tests.
+ - Sharding tests will distribute tests across all test runners through a
+ shared test collection.
+ - Replicating tests will copy all tests to each test runner through a
+ unique test collection for each test runner.
+ test_timeout: Watchdog timeout in seconds for running tests.
+ setup_timeout: Watchdog timeout in seconds for creating and cleaning up
+ test runners.
+ num_retries: Number of retries for a test.
+ max_per_run: Maximum number of tests to run in any group.
+
+ Returns:
+ A tuple of (base_test_result.TestRunResults object, exit code).
+ """
+ if not tests:
+ logging.critical('No tests to run.')
+ return (base_test_result.TestRunResults(), constants.ERROR_EXIT_CODE)
+
+ tests_expanded = ApplyMaxPerRun(tests, max_per_run)
+ if shard:
+ # Generate a shared TestCollection object for all test runners, so they
+ # draw from a common pool of tests.
+ shared_test_collection = test_collection.TestCollection(
+ [_Test(t) for t in tests_expanded])
+ test_collection_factory = lambda: shared_test_collection
+ tag_results_with_device = False
+ log_string = 'sharded across devices'
+ else:
+ # Generate a unique TestCollection object for each test runner, but use
+ # the same set of tests.
+ test_collection_factory = lambda: test_collection.TestCollection(
+ [_Test(t) for t in tests_expanded])
+ tag_results_with_device = True
+ log_string = 'replicated on each device'
+
+ logging.info('Will run %d tests (%s): %s',
+ len(tests_expanded), log_string, str(tests_expanded))
+ runners = _CreateRunners(runner_factory, devices, setup_timeout)
+ try:
+ return _RunAllTests(runners, test_collection_factory,
+ num_retries, test_timeout, tag_results_with_device)
+ finally:
+ try:
+ _TearDownRunners(runners, setup_timeout)
+ except device_errors.DeviceUnreachableError as e:
+ logging.warning('Device unresponsive during TearDown: [%s]', e)
+ except Exception as e:
+ logging.error('Unexpected exception caught during TearDown: %s' % str(e))
diff --git a/build/android/pylib/base/test_dispatcher_unittest.py b/build/android/pylib/base/test_dispatcher_unittest.py
new file mode 100755
index 00000000000..cace9a627a2
--- /dev/null
+++ b/build/android/pylib/base/test_dispatcher_unittest.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python
+# Copyright 2013 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.
+
+"""Unittests for test_dispatcher.py."""
+# pylint: disable=R0201
+# pylint: disable=W0212
+
+import os
+import sys
+import unittest
+
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import test_collection
+from pylib.base import test_dispatcher
+from pylib.device import adb_wrapper
+from pylib.device import device_utils
+from pylib.utils import watchdog_timer
+
+sys.path.append(
+ os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock
+
+
+class TestException(Exception):
+ pass
+
+
+def _MockDevice(serial):
+ d = mock.MagicMock(spec=device_utils.DeviceUtils)
+ d.__str__.return_value = serial
+ d.adb = mock.MagicMock(spec=adb_wrapper.AdbWrapper)
+ d.adb.GetDeviceSerial = mock.MagicMock(return_value=serial)
+ d.IsOnline = mock.MagicMock(return_value=True)
+ return d
+
+
+class MockRunner(object):
+ """A mock TestRunner."""
+ def __init__(self, device=None, shard_index=0):
+ self.device = device or _MockDevice('0')
+ self.device_serial = self.device.adb.GetDeviceSerial()
+ self.shard_index = shard_index
+ self.setups = 0
+ self.teardowns = 0
+
+ def RunTest(self, test):
+ results = base_test_result.TestRunResults()
+ results.AddResult(
+ base_test_result.BaseTestResult(test, base_test_result.ResultType.PASS))
+ return (results, None)
+
+ def SetUp(self):
+ self.setups += 1
+
+ def TearDown(self):
+ self.teardowns += 1
+
+
+class MockRunnerFail(MockRunner):
+ def RunTest(self, test):
+ results = base_test_result.TestRunResults()
+ results.AddResult(
+ base_test_result.BaseTestResult(test, base_test_result.ResultType.FAIL))
+ return (results, test)
+
+
+class MockRunnerFailTwice(MockRunner):
+ def __init__(self, device=None, shard_index=0):
+ super(MockRunnerFailTwice, self).__init__(device, shard_index)
+ self._fails = 0
+
+ def RunTest(self, test):
+ self._fails += 1
+ results = base_test_result.TestRunResults()
+ if self._fails <= 2:
+ results.AddResult(base_test_result.BaseTestResult(
+ test, base_test_result.ResultType.FAIL))
+ return (results, test)
+ else:
+ results.AddResult(base_test_result.BaseTestResult(
+ test, base_test_result.ResultType.PASS))
+ return (results, None)
+
+
+class MockRunnerException(MockRunner):
+ def RunTest(self, test):
+ raise TestException
+
+
+class TestFunctions(unittest.TestCase):
+ """Tests test_dispatcher._RunTestsFromQueue."""
+ @staticmethod
+ def _RunTests(mock_runner, tests):
+ results = []
+ tests = test_collection.TestCollection(
+ [test_dispatcher._Test(t) for t in tests])
+ test_dispatcher._RunTestsFromQueue(mock_runner, tests, results,
+ watchdog_timer.WatchdogTimer(None), 2)
+ run_results = base_test_result.TestRunResults()
+ for r in results:
+ run_results.AddTestRunResults(r)
+ return run_results
+
+ def testRunTestsFromQueue(self):
+ results = TestFunctions._RunTests(MockRunner(), ['a', 'b'])
+ self.assertEqual(len(results.GetPass()), 2)
+ self.assertEqual(len(results.GetNotPass()), 0)
+
+ def testRunTestsFromQueueRetry(self):
+ results = TestFunctions._RunTests(MockRunnerFail(), ['a', 'b'])
+ self.assertEqual(len(results.GetPass()), 0)
+ self.assertEqual(len(results.GetFail()), 2)
+
+ def testRunTestsFromQueueFailTwice(self):
+ results = TestFunctions._RunTests(MockRunnerFailTwice(), ['a', 'b'])
+ self.assertEqual(len(results.GetPass()), 2)
+ self.assertEqual(len(results.GetNotPass()), 0)
+
+ def testSetUp(self):
+ runners = []
+ counter = test_dispatcher._ThreadSafeCounter()
+ test_dispatcher._SetUp(MockRunner, _MockDevice('0'), runners, counter)
+ self.assertEqual(len(runners), 1)
+ self.assertEqual(runners[0].setups, 1)
+
+ def testThreadSafeCounter(self):
+ counter = test_dispatcher._ThreadSafeCounter()
+ for i in xrange(5):
+ self.assertEqual(counter.GetAndIncrement(), i)
+
+ def testApplyMaxPerRun(self):
+ self.assertEqual(
+ ['A:B', 'C:D', 'E', 'F:G', 'H:I'],
+ test_dispatcher.ApplyMaxPerRun(['A:B', 'C:D:E', 'F:G:H:I'], 2))
+
+
+class TestThreadGroupFunctions(unittest.TestCase):
+ """Tests test_dispatcher._RunAllTests and test_dispatcher._CreateRunners."""
+ def setUp(self):
+ self.tests = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
+ shared_test_collection = test_collection.TestCollection(
+ [test_dispatcher._Test(t) for t in self.tests])
+ self.test_collection_factory = lambda: shared_test_collection
+
+ def testCreate(self):
+ runners = test_dispatcher._CreateRunners(
+ MockRunner, [_MockDevice('0'), _MockDevice('1')])
+ for runner in runners:
+ self.assertEqual(runner.setups, 1)
+ self.assertEqual(set([r.device_serial for r in runners]),
+ set(['0', '1']))
+ self.assertEqual(set([r.shard_index for r in runners]),
+ set([0, 1]))
+
+ def testRun(self):
+ runners = [MockRunner(_MockDevice('0')), MockRunner(_MockDevice('1'))]
+ results, exit_code = test_dispatcher._RunAllTests(
+ runners, self.test_collection_factory, 0)
+ self.assertEqual(len(results.GetPass()), len(self.tests))
+ self.assertEqual(exit_code, 0)
+
+ def testTearDown(self):
+ runners = [MockRunner(_MockDevice('0')), MockRunner(_MockDevice('1'))]
+ test_dispatcher._TearDownRunners(runners)
+ for runner in runners:
+ self.assertEqual(runner.teardowns, 1)
+
+ def testRetry(self):
+ runners = test_dispatcher._CreateRunners(
+ MockRunnerFail, [_MockDevice('0'), _MockDevice('1')])
+ results, exit_code = test_dispatcher._RunAllTests(
+ runners, self.test_collection_factory, 0)
+ self.assertEqual(len(results.GetFail()), len(self.tests))
+ self.assertEqual(exit_code, constants.ERROR_EXIT_CODE)
+
+ def testReraise(self):
+ runners = test_dispatcher._CreateRunners(
+ MockRunnerException, [_MockDevice('0'), _MockDevice('1')])
+ with self.assertRaises(TestException):
+ test_dispatcher._RunAllTests(runners, self.test_collection_factory, 0)
+
+
+class TestShard(unittest.TestCase):
+ """Tests test_dispatcher.RunTests with sharding."""
+ @staticmethod
+ def _RunShard(runner_factory):
+ return test_dispatcher.RunTests(
+ ['a', 'b', 'c'], runner_factory, [_MockDevice('0'), _MockDevice('1')],
+ shard=True)
+
+ def testShard(self):
+ results, exit_code = TestShard._RunShard(MockRunner)
+ self.assertEqual(len(results.GetPass()), 3)
+ self.assertEqual(exit_code, 0)
+
+ def testFailing(self):
+ results, exit_code = TestShard._RunShard(MockRunnerFail)
+ self.assertEqual(len(results.GetPass()), 0)
+ self.assertEqual(len(results.GetFail()), 3)
+ self.assertEqual(exit_code, constants.ERROR_EXIT_CODE)
+
+ def testNoTests(self):
+ results, exit_code = test_dispatcher.RunTests(
+ [], MockRunner, [_MockDevice('0'), _MockDevice('1')], shard=True)
+ self.assertEqual(len(results.GetAll()), 0)
+ self.assertEqual(exit_code, constants.ERROR_EXIT_CODE)
+
+
+class TestReplicate(unittest.TestCase):
+ """Tests test_dispatcher.RunTests with replication."""
+ @staticmethod
+ def _RunReplicate(runner_factory):
+ return test_dispatcher.RunTests(
+ ['a', 'b', 'c'], runner_factory, [_MockDevice('0'), _MockDevice('1')],
+ shard=False)
+
+ def testReplicate(self):
+ results, exit_code = TestReplicate._RunReplicate(MockRunner)
+ # We expect 6 results since each test should have been run on every device
+ self.assertEqual(len(results.GetPass()), 6)
+ self.assertEqual(exit_code, 0)
+
+ def testFailing(self):
+ results, exit_code = TestReplicate._RunReplicate(MockRunnerFail)
+ self.assertEqual(len(results.GetPass()), 0)
+ self.assertEqual(len(results.GetFail()), 6)
+ self.assertEqual(exit_code, constants.ERROR_EXIT_CODE)
+
+ def testNoTests(self):
+ results, exit_code = test_dispatcher.RunTests(
+ [], MockRunner, [_MockDevice('0'), _MockDevice('1')], shard=False)
+ self.assertEqual(len(results.GetAll()), 0)
+ self.assertEqual(exit_code, constants.ERROR_EXIT_CODE)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/base/test_instance.py b/build/android/pylib/base/test_instance.py
new file mode 100644
index 00000000000..cdf678f2d28
--- /dev/null
+++ b/build/android/pylib/base/test_instance.py
@@ -0,0 +1,35 @@
+# Copyright 2014 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.
+
+
+class TestInstance(object):
+ """A type of test.
+
+ This is expected to handle all logic that is test-type specific but
+ independent of the environment or device.
+
+ Examples include:
+ - gtests
+ - instrumentation tests
+ """
+
+ def __init__(self):
+ pass
+
+ def TestType(self):
+ raise NotImplementedError
+
+ def SetUp(self):
+ raise NotImplementedError
+
+ def TearDown(self):
+ raise NotImplementedError
+
+ def __enter__(self):
+ self.SetUp()
+ return self
+
+ def __exit__(self, _exc_type, _exc_val, _exc_tb):
+ self.TearDown()
+
diff --git a/build/android/pylib/base/test_instance_factory.py b/build/android/pylib/base/test_instance_factory.py
new file mode 100644
index 00000000000..7e7cb0c1ad1
--- /dev/null
+++ b/build/android/pylib/base/test_instance_factory.py
@@ -0,0 +1,24 @@
+# Copyright 2014 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.
+
+from pylib import constants
+from pylib.gtest import gtest_test_instance
+from pylib.instrumentation import instrumentation_test_instance
+from pylib.utils import isolator
+from pylib.uirobot import uirobot_test_instance
+
+
+
+def CreateTestInstance(args, error_func):
+
+ if args.command == 'gtest':
+ return gtest_test_instance.GtestTestInstance(
+ args, isolator.Isolator(constants.ISOLATE_DEPS_DIR), error_func)
+ elif args.command == 'instrumentation':
+ return instrumentation_test_instance.InstrumentationTestInstance(
+ args, isolator.Isolator(constants.ISOLATE_DEPS_DIR), error_func)
+ elif args.command == 'uirobot':
+ return uirobot_test_instance.UirobotTestInstance(args, error_func)
+
+ error_func('Unable to create %s test instance.' % args.command)
diff --git a/build/android/pylib/base/test_run.py b/build/android/pylib/base/test_run.py
new file mode 100644
index 00000000000..7380e787086
--- /dev/null
+++ b/build/android/pylib/base/test_run.py
@@ -0,0 +1,39 @@
+# Copyright 2014 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.
+
+
+class TestRun(object):
+ """An execution of a particular test on a particular device.
+
+ This is expected to handle all logic that is specific to the combination of
+ environment and test type.
+
+ Examples include:
+ - local gtests
+ - local instrumentation tests
+ """
+
+ def __init__(self, env, test_instance):
+ self._env = env
+ self._test_instance = test_instance
+
+ def TestPackage(self):
+ raise NotImplementedError
+
+ def SetUp(self):
+ raise NotImplementedError
+
+ def RunTests(self):
+ raise NotImplementedError
+
+ def TearDown(self):
+ raise NotImplementedError
+
+ def __enter__(self):
+ self.SetUp()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.TearDown()
+
diff --git a/build/android/pylib/base/test_run_factory.py b/build/android/pylib/base/test_run_factory.py
new file mode 100644
index 00000000000..8c71ebbd0a0
--- /dev/null
+++ b/build/android/pylib/base/test_run_factory.py
@@ -0,0 +1,41 @@
+# Copyright 2014 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.
+
+from pylib.gtest import gtest_test_instance
+from pylib.gtest import local_device_gtest_run
+from pylib.instrumentation import instrumentation_test_instance
+from pylib.local.device import local_device_environment
+from pylib.local.device import local_device_instrumentation_test_run
+from pylib.remote.device import remote_device_environment
+from pylib.remote.device import remote_device_gtest_run
+from pylib.remote.device import remote_device_instrumentation_test_run
+from pylib.remote.device import remote_device_uirobot_test_run
+from pylib.uirobot import uirobot_test_instance
+
+
+def CreateTestRun(_args, env, test_instance, error_func):
+ if isinstance(env, local_device_environment.LocalDeviceEnvironment):
+ if isinstance(test_instance, gtest_test_instance.GtestTestInstance):
+ return local_device_gtest_run.LocalDeviceGtestRun(env, test_instance)
+ if isinstance(test_instance,
+ instrumentation_test_instance.InstrumentationTestInstance):
+ return (local_device_instrumentation_test_run
+ .LocalDeviceInstrumentationTestRun(env, test_instance))
+
+ if isinstance(env, remote_device_environment.RemoteDeviceEnvironment):
+ if isinstance(test_instance, gtest_test_instance.GtestTestInstance):
+ return remote_device_gtest_run.RemoteDeviceGtestTestRun(
+ env, test_instance)
+ if isinstance(test_instance,
+ instrumentation_test_instance.InstrumentationTestInstance):
+ return (remote_device_instrumentation_test_run
+ .RemoteDeviceInstrumentationTestRun(env, test_instance))
+ if isinstance(test_instance, uirobot_test_instance.UirobotTestInstance):
+ return remote_device_uirobot_test_run.RemoteDeviceUirobotTestRun(
+ env, test_instance)
+
+
+ error_func('Unable to create test run for %s tests in %s environment'
+ % (str(test_instance), str(env)))
+
diff --git a/build/android/pylib/base/test_server.py b/build/android/pylib/base/test_server.py
new file mode 100644
index 00000000000..085a51e9bae
--- /dev/null
+++ b/build/android/pylib/base/test_server.py
@@ -0,0 +1,19 @@
+# Copyright 2014 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.
+
+class TestServer(object):
+ """Base class for any server that needs to be set up for the tests."""
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def SetUp(self):
+ raise NotImplementedError
+
+ def Reset(self):
+ raise NotImplementedError
+
+ def TearDown(self):
+ raise NotImplementedError
+
diff --git a/build/android/pylib/chrome_test_server_spawner.py b/build/android/pylib/chrome_test_server_spawner.py
new file mode 100644
index 00000000000..052c2fde78e
--- /dev/null
+++ b/build/android/pylib/chrome_test_server_spawner.py
@@ -0,0 +1,422 @@
+# Copyright 2013 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.
+
+"""A "Test Server Spawner" that handles killing/stopping per-test test servers.
+
+It's used to accept requests from the device to spawn and kill instances of the
+chrome test server on the host.
+"""
+# pylint: disable=W0702
+
+import BaseHTTPServer
+import json
+import logging
+import os
+import select
+import struct
+import subprocess
+import sys
+import threading
+import time
+import urlparse
+
+from pylib import constants
+from pylib import ports
+
+from pylib.forwarder import Forwarder
+
+
+# Path that are needed to import necessary modules when launching a testserver.
+os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + (':%s:%s:%s:%s:%s'
+ % (os.path.join(constants.DIR_SOURCE_ROOT, 'third_party'),
+ os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'tlslite'),
+ os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pyftpdlib',
+ 'src'),
+ os.path.join(constants.DIR_SOURCE_ROOT, 'net', 'tools', 'testserver'),
+ os.path.join(constants.DIR_SOURCE_ROOT, 'sync', 'tools', 'testserver')))
+
+
+SERVER_TYPES = {
+ 'http': '',
+ 'ftp': '-f',
+ 'sync': '', # Sync uses its own script, and doesn't take a server type arg.
+ 'tcpecho': '--tcp-echo',
+ 'udpecho': '--udp-echo',
+}
+
+
+# The timeout (in seconds) of starting up the Python test server.
+TEST_SERVER_STARTUP_TIMEOUT = 10
+
+def _WaitUntil(predicate, max_attempts=5):
+ """Blocks until the provided predicate (function) is true.
+
+ Returns:
+ Whether the provided predicate was satisfied once (before the timeout).
+ """
+ sleep_time_sec = 0.025
+ for _ in xrange(1, max_attempts):
+ if predicate():
+ return True
+ time.sleep(sleep_time_sec)
+ sleep_time_sec = min(1, sleep_time_sec * 2) # Don't wait more than 1 sec.
+ return False
+
+
+def _CheckPortAvailable(port):
+ """Returns True if |port| is available."""
+ return _WaitUntil(lambda: ports.IsHostPortAvailable(port))
+
+
+def _CheckPortNotAvailable(port):
+ """Returns True if |port| is not available."""
+ return _WaitUntil(lambda: not ports.IsHostPortAvailable(port))
+
+
+def _CheckDevicePortStatus(device, port):
+ """Returns whether the provided port is used."""
+ return _WaitUntil(lambda: ports.IsDevicePortUsed(device, port))
+
+
+def _GetServerTypeCommandLine(server_type):
+ """Returns the command-line by the given server type.
+
+ Args:
+ server_type: the server type to be used (e.g. 'http').
+
+ Returns:
+ A string containing the command-line argument.
+ """
+ if server_type not in SERVER_TYPES:
+ raise NotImplementedError('Unknown server type: %s' % server_type)
+ if server_type == 'udpecho':
+ raise Exception('Please do not run UDP echo tests because we do not have '
+ 'a UDP forwarder tool.')
+ return SERVER_TYPES[server_type]
+
+
+class TestServerThread(threading.Thread):
+ """A thread to run the test server in a separate process."""
+
+ def __init__(self, ready_event, arguments, device, tool):
+ """Initialize TestServerThread with the following argument.
+
+ Args:
+ ready_event: event which will be set when the test server is ready.
+ arguments: dictionary of arguments to run the test server.
+ device: An instance of DeviceUtils.
+ tool: instance of runtime error detection tool.
+ """
+ threading.Thread.__init__(self)
+ self.wait_event = threading.Event()
+ self.stop_flag = False
+ self.ready_event = ready_event
+ self.ready_event.clear()
+ self.arguments = arguments
+ self.device = device
+ self.tool = tool
+ self.test_server_process = None
+ self.is_ready = False
+ self.host_port = self.arguments['port']
+ assert isinstance(self.host_port, int)
+ # The forwarder device port now is dynamically allocated.
+ self.forwarder_device_port = 0
+ # Anonymous pipe in order to get port info from test server.
+ self.pipe_in = None
+ self.pipe_out = None
+ self.process = None
+ self.command_line = []
+
+ def _WaitToStartAndGetPortFromTestServer(self):
+ """Waits for the Python test server to start and gets the port it is using.
+
+ The port information is passed by the Python test server with a pipe given
+ by self.pipe_out. It is written as a result to |self.host_port|.
+
+ Returns:
+ Whether the port used by the test server was successfully fetched.
+ """
+ assert self.host_port == 0 and self.pipe_out and self.pipe_in
+ (in_fds, _, _) = select.select([self.pipe_in, ], [], [],
+ TEST_SERVER_STARTUP_TIMEOUT)
+ if len(in_fds) == 0:
+ logging.error('Failed to wait to the Python test server to be started.')
+ return False
+ # First read the data length as an unsigned 4-byte value. This
+ # is _not_ using network byte ordering since the Python test server packs
+ # size as native byte order and all Chromium platforms so far are
+ # configured to use little-endian.
+ # TODO(jnd): Change the Python test server and local_test_server_*.cc to
+ # use a unified byte order (either big-endian or little-endian).
+ data_length = os.read(self.pipe_in, struct.calcsize('=L'))
+ if data_length:
+ (data_length,) = struct.unpack('=L', data_length)
+ assert data_length
+ if not data_length:
+ logging.error('Failed to get length of server data.')
+ return False
+ port_json = os.read(self.pipe_in, data_length)
+ if not port_json:
+ logging.error('Failed to get server data.')
+ return False
+ logging.info('Got port json data: %s', port_json)
+ port_json = json.loads(port_json)
+ if port_json.has_key('port') and isinstance(port_json['port'], int):
+ self.host_port = port_json['port']
+ return _CheckPortNotAvailable(self.host_port)
+ logging.error('Failed to get port information from the server data.')
+ return False
+
+ def _GenerateCommandLineArguments(self):
+ """Generates the command line to run the test server.
+
+ Note that all options are processed by following the definitions in
+ testserver.py.
+ """
+ if self.command_line:
+ return
+
+ args_copy = dict(self.arguments)
+
+ # Translate the server type.
+ type_cmd = _GetServerTypeCommandLine(args_copy.pop('server-type'))
+ if type_cmd:
+ self.command_line.append(type_cmd)
+
+ # Use a pipe to get the port given by the instance of Python test server
+ # if the test does not specify the port.
+ assert self.host_port == args_copy['port']
+ if self.host_port == 0:
+ (self.pipe_in, self.pipe_out) = os.pipe()
+ self.command_line.append('--startup-pipe=%d' % self.pipe_out)
+
+ # Pass the remaining arguments as-is.
+ for key, values in args_copy.iteritems():
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ if value is None:
+ self.command_line.append('--%s' % key)
+ else:
+ self.command_line.append('--%s=%s' % (key, value))
+
+ def _CloseUnnecessaryFDsForTestServerProcess(self):
+ # This is required to avoid subtle deadlocks that could be caused by the
+ # test server child process inheriting undesirable file descriptors such as
+ # file lock file descriptors.
+ for fd in xrange(0, 1024):
+ if fd != self.pipe_out:
+ try:
+ os.close(fd)
+ except:
+ pass
+
+ def run(self):
+ logging.info('Start running the thread!')
+ self.wait_event.clear()
+ self._GenerateCommandLineArguments()
+ command = constants.DIR_SOURCE_ROOT
+ if self.arguments['server-type'] == 'sync':
+ command = [os.path.join(command, 'sync', 'tools', 'testserver',
+ 'sync_testserver.py')] + self.command_line
+ else:
+ command = [os.path.join(command, 'net', 'tools', 'testserver',
+ 'testserver.py')] + self.command_line
+ logging.info('Running: %s', command)
+ # Pass DIR_SOURCE_ROOT as the child's working directory so that relative
+ # paths in the arguments are resolved correctly.
+ self.process = subprocess.Popen(
+ command, preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess,
+ cwd=constants.DIR_SOURCE_ROOT)
+ if self.process:
+ if self.pipe_out:
+ self.is_ready = self._WaitToStartAndGetPortFromTestServer()
+ else:
+ self.is_ready = _CheckPortNotAvailable(self.host_port)
+ if self.is_ready:
+ Forwarder.Map([(0, self.host_port)], self.device, self.tool)
+ # Check whether the forwarder is ready on the device.
+ self.is_ready = False
+ device_port = Forwarder.DevicePortForHostPort(self.host_port)
+ if device_port and _CheckDevicePortStatus(self.device, device_port):
+ self.is_ready = True
+ self.forwarder_device_port = device_port
+ # Wake up the request handler thread.
+ self.ready_event.set()
+ # Keep thread running until Stop() gets called.
+ _WaitUntil(lambda: self.stop_flag, max_attempts=sys.maxint)
+ if self.process.poll() is None:
+ self.process.kill()
+ Forwarder.UnmapDevicePort(self.forwarder_device_port, self.device)
+ self.process = None
+ self.is_ready = False
+ if self.pipe_out:
+ os.close(self.pipe_in)
+ os.close(self.pipe_out)
+ self.pipe_in = None
+ self.pipe_out = None
+ logging.info('Test-server has died.')
+ self.wait_event.set()
+
+ def Stop(self):
+ """Blocks until the loop has finished.
+
+ Note that this must be called in another thread.
+ """
+ if not self.process:
+ return
+ self.stop_flag = True
+ self.wait_event.wait()
+
+
+class SpawningServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """A handler used to process http GET/POST request."""
+
+ def _SendResponse(self, response_code, response_reason, additional_headers,
+ contents):
+ """Generates a response sent to the client from the provided parameters.
+
+ Args:
+ response_code: number of the response status.
+ response_reason: string of reason description of the response.
+ additional_headers: dict of additional headers. Each key is the name of
+ the header, each value is the content of the header.
+ contents: string of the contents we want to send to client.
+ """
+ self.send_response(response_code, response_reason)
+ self.send_header('Content-Type', 'text/html')
+ # Specify the content-length as without it the http(s) response will not
+ # be completed properly (and the browser keeps expecting data).
+ self.send_header('Content-Length', len(contents))
+ for header_name in additional_headers:
+ self.send_header(header_name, additional_headers[header_name])
+ self.end_headers()
+ self.wfile.write(contents)
+ self.wfile.flush()
+
+ def _StartTestServer(self):
+ """Starts the test server thread."""
+ logging.info('Handling request to spawn a test server.')
+ content_type = self.headers.getheader('content-type')
+ if content_type != 'application/json':
+ raise Exception('Bad content-type for start request.')
+ content_length = self.headers.getheader('content-length')
+ if not content_length:
+ content_length = 0
+ try:
+ content_length = int(content_length)
+ except:
+ raise Exception('Bad content-length for start request.')
+ logging.info(content_length)
+ test_server_argument_json = self.rfile.read(content_length)
+ logging.info(test_server_argument_json)
+ assert not self.server.test_server_instance
+ ready_event = threading.Event()
+ self.server.test_server_instance = TestServerThread(
+ ready_event,
+ json.loads(test_server_argument_json),
+ self.server.device,
+ self.server.tool)
+ self.server.test_server_instance.setDaemon(True)
+ self.server.test_server_instance.start()
+ ready_event.wait()
+ if self.server.test_server_instance.is_ready:
+ self._SendResponse(200, 'OK', {}, json.dumps(
+ {'port': self.server.test_server_instance.forwarder_device_port,
+ 'message': 'started'}))
+ logging.info('Test server is running on port: %d.',
+ self.server.test_server_instance.host_port)
+ else:
+ self.server.test_server_instance.Stop()
+ self.server.test_server_instance = None
+ self._SendResponse(500, 'Test Server Error.', {}, '')
+ logging.info('Encounter problem during starting a test server.')
+
+ def _KillTestServer(self):
+ """Stops the test server instance."""
+ # There should only ever be one test server at a time. This may do the
+ # wrong thing if we try and start multiple test servers.
+ if not self.server.test_server_instance:
+ return
+ port = self.server.test_server_instance.host_port
+ logging.info('Handling request to kill a test server on port: %d.', port)
+ self.server.test_server_instance.Stop()
+ # Make sure the status of test server is correct before sending response.
+ if _CheckPortAvailable(port):
+ self._SendResponse(200, 'OK', {}, 'killed')
+ logging.info('Test server on port %d is killed', port)
+ else:
+ self._SendResponse(500, 'Test Server Error.', {}, '')
+ logging.info('Encounter problem during killing a test server.')
+ self.server.test_server_instance = None
+
+ def do_POST(self):
+ parsed_path = urlparse.urlparse(self.path)
+ action = parsed_path.path
+ logging.info('Action for POST method is: %s.', action)
+ if action == '/start':
+ self._StartTestServer()
+ else:
+ self._SendResponse(400, 'Unknown request.', {}, '')
+ logging.info('Encounter unknown request: %s.', action)
+
+ def do_GET(self):
+ parsed_path = urlparse.urlparse(self.path)
+ action = parsed_path.path
+ params = urlparse.parse_qs(parsed_path.query, keep_blank_values=1)
+ logging.info('Action for GET method is: %s.', action)
+ for param in params:
+ logging.info('%s=%s', param, params[param][0])
+ if action == '/kill':
+ self._KillTestServer()
+ elif action == '/ping':
+ # The ping handler is used to check whether the spawner server is ready
+ # to serve the requests. We don't need to test the status of the test
+ # server when handling ping request.
+ self._SendResponse(200, 'OK', {}, 'ready')
+ logging.info('Handled ping request and sent response.')
+ else:
+ self._SendResponse(400, 'Unknown request', {}, '')
+ logging.info('Encounter unknown request: %s.', action)
+
+
+class SpawningServer(object):
+ """The class used to start/stop a http server."""
+
+ def __init__(self, test_server_spawner_port, device, tool):
+ logging.info('Creating new spawner on port: %d.', test_server_spawner_port)
+ self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port),
+ SpawningServerRequestHandler)
+ self.server.device = device
+ self.server.tool = tool
+ self.server.test_server_instance = None
+ self.server.build_type = constants.GetBuildType()
+
+ def _Listen(self):
+ logging.info('Starting test server spawner')
+ self.server.serve_forever()
+
+ def Start(self):
+ """Starts the test server spawner."""
+ listener_thread = threading.Thread(target=self._Listen)
+ listener_thread.setDaemon(True)
+ listener_thread.start()
+
+ def Stop(self):
+ """Stops the test server spawner.
+
+ Also cleans the server state.
+ """
+ self.CleanupState()
+ self.server.shutdown()
+
+ def CleanupState(self):
+ """Cleans up the spawning server state.
+
+ This should be called if the test server spawner is reused,
+ to avoid sharing the test server instance.
+ """
+ if self.server.test_server_instance:
+ self.server.test_server_instance.Stop()
+ self.server.test_server_instance = None
diff --git a/build/android/pylib/cmd_helper.py b/build/android/pylib/cmd_helper.py
new file mode 100644
index 00000000000..f8815531a56
--- /dev/null
+++ b/build/android/pylib/cmd_helper.py
@@ -0,0 +1,261 @@
+# Copyright (c) 2012 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.
+
+"""A wrapper for subprocess to make calling shell commands easier."""
+
+import logging
+import os
+import pipes
+import select
+import signal
+import string
+import StringIO
+import subprocess
+import time
+
+# fcntl is not available on Windows.
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
+
+_SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./')
+
+def SingleQuote(s):
+ """Return an shell-escaped version of the string using single quotes.
+
+ Reliably quote a string which may contain unsafe characters (e.g. space,
+ quote, or other special characters such as '$').
+
+ The returned value can be used in a shell command line as one token that gets
+ to be interpreted literally.
+
+ Args:
+ s: The string to quote.
+
+ Return:
+ The string quoted using single quotes.
+ """
+ return pipes.quote(s)
+
+def DoubleQuote(s):
+ """Return an shell-escaped version of the string using double quotes.
+
+ Reliably quote a string which may contain unsafe characters (e.g. space
+ or quote characters), while retaining some shell features such as variable
+ interpolation.
+
+ The returned value can be used in a shell command line as one token that gets
+ to be further interpreted by the shell.
+
+ The set of characters that retain their special meaning may depend on the
+ shell implementation. This set usually includes: '$', '`', '\', '!', '*',
+ and '@'.
+
+ Args:
+ s: The string to quote.
+
+ Return:
+ The string quoted using double quotes.
+ """
+ if not s:
+ return '""'
+ elif all(c in _SafeShellChars for c in s):
+ return s
+ else:
+ return '"' + s.replace('"', '\\"') + '"'
+
+
+def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
+ return subprocess.Popen(
+ args=args, cwd=cwd, stdout=stdout, stderr=stderr,
+ shell=shell, close_fds=True, env=env,
+ preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
+
+
+def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
+ pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd,
+ env=env)
+ pipe.communicate()
+ return pipe.wait()
+
+
+def RunCmd(args, cwd=None):
+ """Opens a subprocess to execute a program and returns its return value.
+
+ Args:
+ args: A string or a sequence of program arguments. The program to execute is
+ the string or the first item in the args sequence.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+
+ Returns:
+ Return code from the command execution.
+ """
+ logging.info(str(args) + ' ' + (cwd or ''))
+ return Call(args, cwd=cwd)
+
+
+def GetCmdOutput(args, cwd=None, shell=False):
+ """Open a subprocess to execute a program and returns its output.
+
+ Args:
+ args: A string or a sequence of program arguments. The program to execute is
+ the string or the first item in the args sequence.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command.
+
+ Returns:
+ Captures and returns the command's stdout.
+ Prints the command's stderr to logger (which defaults to stdout).
+ """
+ (_, output) = GetCmdStatusAndOutput(args, cwd, shell)
+ return output
+
+
+def _ValidateAndLogCommand(args, cwd, shell):
+ if isinstance(args, basestring):
+ if not shell:
+ raise Exception('string args must be run with shell=True')
+ else:
+ if shell:
+ raise Exception('array args must be run with shell=False')
+ args = ' '.join(SingleQuote(c) for c in args)
+ if cwd is None:
+ cwd = ''
+ else:
+ cwd = ':' + cwd
+ logging.info('[host]%s> %s', cwd, args)
+ return args
+
+
+def GetCmdStatusAndOutput(args, cwd=None, shell=False):
+ """Executes a subprocess and returns its exit code and output.
+
+ Args:
+ args: A string or a sequence of program arguments. The program to execute is
+ the string or the first item in the args sequence.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command. Must be True if args
+ is a string and False if args is a sequence.
+
+ Returns:
+ The 2-tuple (exit code, output).
+ """
+ _ValidateAndLogCommand(args, cwd, shell)
+ pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ shell=shell, cwd=cwd)
+ stdout, stderr = pipe.communicate()
+
+ if stderr:
+ logging.critical(stderr)
+ if len(stdout) > 4096:
+ logging.debug('Truncated output:')
+ logging.debug(stdout[:4096])
+ return (pipe.returncode, stdout)
+
+
+class TimeoutError(Exception):
+ """Module-specific timeout exception."""
+ pass
+
+
+def _IterProcessStdout(process, timeout=None, buffer_size=4096,
+ poll_interval=1):
+ assert fcntl, 'fcntl module is required'
+ try:
+ # Enable non-blocking reads from the child's stdout.
+ child_fd = process.stdout.fileno()
+ fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
+ fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ end_time = (time.time() + timeout) if timeout else None
+ while True:
+ if end_time and time.time() > end_time:
+ raise TimeoutError
+ read_fds, _, _ = select.select([child_fd], [], [], poll_interval)
+ if child_fd in read_fds:
+ data = os.read(child_fd, buffer_size)
+ if not data:
+ break
+ yield data
+ if process.poll() is not None:
+ break
+ finally:
+ try:
+ # Make sure the process doesn't stick around if we fail with an
+ # exception.
+ process.kill()
+ except OSError:
+ pass
+ process.wait()
+
+
+def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
+ logfile=None):
+ """Executes a subprocess with a timeout.
+
+ Args:
+ args: List of arguments to the program, the program to execute is the first
+ element.
+ timeout: the timeout in seconds or None to wait forever.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command. Must be True if args
+ is a string and False if args is a sequence.
+ logfile: Optional file-like object that will receive output from the
+ command as it is running.
+
+ Returns:
+ The 2-tuple (exit code, output).
+ """
+ _ValidateAndLogCommand(args, cwd, shell)
+ output = StringIO.StringIO()
+ process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ for data in _IterProcessStdout(process, timeout=timeout):
+ if logfile:
+ logfile.write(data)
+ output.write(data)
+ return process.returncode, output.getvalue()
+
+
+def IterCmdOutputLines(args, timeout=None, cwd=None, shell=False,
+ check_status=True):
+ """Executes a subprocess and continuously yields lines from its output.
+
+ Args:
+ args: List of arguments to the program, the program to execute is the first
+ element.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command. Must be True if args
+ is a string and False if args is a sequence.
+ check_status: A boolean indicating whether to check the exit status of the
+ process after all output has been read.
+
+ Yields:
+ The output of the subprocess, line by line.
+
+ Raises:
+ CalledProcessError if check_status is True and the process exited with a
+ non-zero exit status.
+ """
+ cmd = _ValidateAndLogCommand(args, cwd, shell)
+ process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ buffer_output = ''
+ for data in _IterProcessStdout(process, timeout=timeout):
+ buffer_output += data
+ has_incomplete_line = buffer_output[-1] not in '\r\n'
+ lines = buffer_output.splitlines()
+ buffer_output = lines.pop() if has_incomplete_line else ''
+ for line in lines:
+ yield line
+ if buffer_output:
+ yield buffer_output
+ if check_status and process.returncode:
+ raise subprocess.CalledProcessError(process.returncode, cmd)
diff --git a/build/android/pylib/cmd_helper_test.py b/build/android/pylib/cmd_helper_test.py
new file mode 100644
index 00000000000..5155cea49ae
--- /dev/null
+++ b/build/android/pylib/cmd_helper_test.py
@@ -0,0 +1,83 @@
+# Copyright 2013 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.
+
+"""Tests for the cmd_helper module."""
+
+import unittest
+import subprocess
+
+from pylib import cmd_helper
+
+
+class CmdHelperSingleQuoteTest(unittest.TestCase):
+
+ def testSingleQuote_basic(self):
+ self.assertEquals('hello',
+ cmd_helper.SingleQuote('hello'))
+
+ def testSingleQuote_withSpaces(self):
+ self.assertEquals("'hello world'",
+ cmd_helper.SingleQuote('hello world'))
+
+ def testSingleQuote_withUnsafeChars(self):
+ self.assertEquals("""'hello'"'"'; rm -rf /'""",
+ cmd_helper.SingleQuote("hello'; rm -rf /"))
+
+ def testSingleQuote_dontExpand(self):
+ test_string = 'hello $TEST_VAR'
+ cmd = 'TEST_VAR=world; echo %s' % cmd_helper.SingleQuote(test_string)
+ self.assertEquals(test_string,
+ cmd_helper.GetCmdOutput(cmd, shell=True).rstrip())
+
+
+class CmdHelperDoubleQuoteTest(unittest.TestCase):
+
+ def testDoubleQuote_basic(self):
+ self.assertEquals('hello',
+ cmd_helper.DoubleQuote('hello'))
+
+ def testDoubleQuote_withSpaces(self):
+ self.assertEquals('"hello world"',
+ cmd_helper.DoubleQuote('hello world'))
+
+ def testDoubleQuote_withUnsafeChars(self):
+ self.assertEquals('''"hello\\"; rm -rf /"''',
+ cmd_helper.DoubleQuote('hello"; rm -rf /'))
+
+ def testSingleQuote_doExpand(self):
+ test_string = 'hello $TEST_VAR'
+ cmd = 'TEST_VAR=world; echo %s' % cmd_helper.DoubleQuote(test_string)
+ self.assertEquals('hello world',
+ cmd_helper.GetCmdOutput(cmd, shell=True).rstrip())
+
+
+class CmdHelperIterCmdOutputLinesTest(unittest.TestCase):
+ """Test IterCmdOutputLines with some calls to the unix 'seq' command."""
+
+ def testIterCmdOutputLines_success(self):
+ for num, line in enumerate(
+ cmd_helper.IterCmdOutputLines(['seq', '10']), 1):
+ self.assertEquals(num, int(line))
+
+ def testIterCmdOutputLines_exitStatusFail(self):
+ with self.assertRaises(subprocess.CalledProcessError):
+ for num, line in enumerate(
+ cmd_helper.IterCmdOutputLines('seq 10 && false', shell=True), 1):
+ self.assertEquals(num, int(line))
+ # after reading all the output we get an exit status of 1
+
+ def testIterCmdOutputLines_exitStatusIgnored(self):
+ for num, line in enumerate(
+ cmd_helper.IterCmdOutputLines('seq 10 && false', shell=True,
+ check_status=False), 1):
+ self.assertEquals(num, int(line))
+
+ def testIterCmdOutputLines_exitStatusSkipped(self):
+ for num, line in enumerate(
+ cmd_helper.IterCmdOutputLines('seq 10 && false', shell=True), 1):
+ self.assertEquals(num, int(line))
+ # no exception will be raised because we don't attempt to read past
+ # the end of the output and, thus, the status never gets checked
+ if num == 10:
+ break
diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py
new file mode 100644
index 00000000000..8821f97dd3f
--- /dev/null
+++ b/build/android/pylib/constants/__init__.py
@@ -0,0 +1,308 @@
+# Copyright (c) 2012 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.
+
+"""Defines a set of constants shared by test runners and other scripts."""
+
+# TODO(jbudorick): Split these constants into coherent modules.
+
+# pylint: disable=W0212
+
+import collections
+import logging
+import os
+import subprocess
+
+
+DIR_SOURCE_ROOT = os.environ.get('CHECKOUT_SOURCE_ROOT',
+ os.path.abspath(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, os.pardir, os.pardir)))
+ISOLATE_DEPS_DIR = os.path.join(DIR_SOURCE_ROOT, 'isolate_deps_dir')
+
+CHROME_SHELL_HOST_DRIVEN_DIR = os.path.join(
+ DIR_SOURCE_ROOT, 'chrome', 'android')
+
+
+PackageInfo = collections.namedtuple('PackageInfo',
+ ['package', 'activity', 'cmdline_file', 'devtools_socket',
+ 'test_package'])
+
+PACKAGE_INFO = {
+ 'chrome_document': PackageInfo(
+ 'com.google.android.apps.chrome.document',
+ 'com.google.android.apps.chrome.document.ChromeLauncherActivity',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chrome': PackageInfo(
+ 'com.google.android.apps.chrome',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ 'com.google.android.apps.chrome.tests'),
+ 'chrome_beta': PackageInfo(
+ 'com.chrome.beta',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chrome_stable': PackageInfo(
+ 'com.android.chrome',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chrome_dev': PackageInfo(
+ 'com.chrome.dev',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chrome_canary': PackageInfo(
+ 'com.chrome.canary',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chrome_work': PackageInfo(
+ 'com.chrome.work',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'chromium': PackageInfo(
+ 'org.chromium.chrome',
+ 'com.google.android.apps.chrome.Main',
+ '/data/local/chrome-command-line',
+ 'chrome_devtools_remote',
+ None),
+ 'legacy_browser': PackageInfo(
+ 'com.google.android.browser',
+ 'com.android.browser.BrowserActivity',
+ None,
+ None,
+ None),
+ 'chromecast_shell': PackageInfo(
+ 'com.google.android.apps.mediashell',
+ 'com.google.android.apps.mediashell.MediaShellActivity',
+ '/data/local/tmp/castshell-command-line',
+ None,
+ None),
+ 'content_shell': PackageInfo(
+ 'org.chromium.content_shell_apk',
+ 'org.chromium.content_shell_apk.ContentShellActivity',
+ '/data/local/tmp/content-shell-command-line',
+ None,
+ 'org.chromium.content_shell_apk.tests'),
+ 'chrome_shell': PackageInfo(
+ 'org.chromium.chrome.shell',
+ 'org.chromium.chrome.shell.ChromeShellActivity',
+ '/data/local/tmp/chrome-shell-command-line',
+ 'chrome_shell_devtools_remote',
+ 'org.chromium.chrome.shell.tests'),
+ 'android_webview_shell': PackageInfo(
+ 'org.chromium.android_webview.shell',
+ 'org.chromium.android_webview.shell.AwShellActivity',
+ '/data/local/tmp/android-webview-command-line',
+ None,
+ 'org.chromium.android_webview.test'),
+ 'gtest': PackageInfo(
+ 'org.chromium.native_test',
+ 'org.chromium.native_test.NativeUnitTestActivity',
+ '/data/local/tmp/chrome-native-tests-command-line',
+ None,
+ None),
+ 'components_browsertests': PackageInfo(
+ 'org.chromium.components_browsertests_apk',
+ ('org.chromium.components_browsertests_apk' +
+ '.ComponentsBrowserTestsActivity'),
+ '/data/local/tmp/chrome-native-tests-command-line',
+ None,
+ None),
+ 'content_browsertests': PackageInfo(
+ 'org.chromium.content_browsertests_apk',
+ 'org.chromium.content_browsertests_apk.ContentBrowserTestsActivity',
+ '/data/local/tmp/chrome-native-tests-command-line',
+ None,
+ None),
+ 'chromedriver_webview_shell': PackageInfo(
+ 'org.chromium.chromedriver_webview_shell',
+ 'org.chromium.chromedriver_webview_shell.Main',
+ None,
+ None,
+ None),
+}
+
+
+# Ports arrangement for various test servers used in Chrome for Android.
+# Lighttpd server will attempt to use 9000 as default port, if unavailable it
+# will find a free port from 8001 - 8999.
+LIGHTTPD_DEFAULT_PORT = 9000
+LIGHTTPD_RANDOM_PORT_FIRST = 8001
+LIGHTTPD_RANDOM_PORT_LAST = 8999
+TEST_SYNC_SERVER_PORT = 9031
+TEST_SEARCH_BY_IMAGE_SERVER_PORT = 9041
+TEST_POLICY_SERVER_PORT = 9051
+
+# The net test server is started from port 10201.
+# TODO(pliard): http://crbug.com/239014. Remove this dirty workaround once
+# http://crbug.com/239014 is fixed properly.
+TEST_SERVER_PORT_FIRST = 10201
+TEST_SERVER_PORT_LAST = 30000
+# A file to record next valid port of test server.
+TEST_SERVER_PORT_FILE = '/tmp/test_server_port'
+TEST_SERVER_PORT_LOCKFILE = '/tmp/test_server_port.lock'
+
+TEST_EXECUTABLE_DIR = '/data/local/tmp'
+# Directories for common java libraries for SDK build.
+# These constants are defined in build/android/ant/common.xml
+SDK_BUILD_JAVALIB_DIR = 'lib.java'
+SDK_BUILD_TEST_JAVALIB_DIR = 'test.lib.java'
+SDK_BUILD_APKS_DIR = 'apks'
+
+ADB_KEYS_FILE = '/data/misc/adb/adb_keys'
+
+PERF_OUTPUT_DIR = os.path.join(DIR_SOURCE_ROOT, 'out', 'step_results')
+# The directory on the device where perf test output gets saved to.
+DEVICE_PERF_OUTPUT_DIR = (
+ '/data/data/' + PACKAGE_INFO['chrome'].package + '/files')
+
+SCREENSHOTS_DIR = os.path.join(DIR_SOURCE_ROOT, 'out_screenshots')
+
+class ANDROID_SDK_VERSION_CODES(object):
+ """Android SDK version codes.
+
+ http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+ """
+
+ JELLY_BEAN = 16
+ JELLY_BEAN_MR1 = 17
+ JELLY_BEAN_MR2 = 18
+ KITKAT = 19
+ KITKAT_WATCH = 20
+ LOLLIPOP = 21
+ LOLLIPOP_MR1 = 22
+
+ANDROID_SDK_VERSION = ANDROID_SDK_VERSION_CODES.LOLLIPOP_MR1
+ANDROID_SDK_BUILD_TOOLS_VERSION = '22.0.1'
+ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT,
+ 'third_party/android_tools/sdk')
+ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT,
+ 'build-tools', ANDROID_SDK_BUILD_TOOLS_VERSION)
+ANDROID_NDK_ROOT = os.path.join(DIR_SOURCE_ROOT,
+ 'third_party/android_tools/ndk')
+
+EMULATOR_SDK_ROOT = os.environ.get('ANDROID_EMULATOR_SDK_ROOT',
+ os.path.join(DIR_SOURCE_ROOT,
+ 'android_emulator_sdk'))
+
+BAD_DEVICES_JSON = os.path.join(DIR_SOURCE_ROOT,
+ os.environ.get('CHROMIUM_OUT_DIR', 'out'),
+ 'bad_devices.json')
+
+UPSTREAM_FLAKINESS_SERVER = 'test-results.appspot.com'
+
+DEVICE_LOCAL_PROPERTIES_PATH = '/data/local.prop'
+
+PYTHON_UNIT_TEST_SUITES = {
+ 'pylib_py_unittests': {
+ 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android'),
+ 'test_modules': [
+ 'pylib.cmd_helper_test',
+ 'pylib.device.device_utils_test',
+ 'pylib.results.json_results_test',
+ 'pylib.utils.md5sum_test',
+ ]
+ },
+ 'gyp_py_unittests': {
+ 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'),
+ 'test_modules': [
+ 'java_cpp_enum_tests',
+ ]
+ },
+}
+
+LOCAL_MACHINE_TESTS = ['junit', 'python']
+VALID_ENVIRONMENTS = ['local', 'remote_device']
+VALID_TEST_TYPES = ['gtest', 'instrumentation', 'junit', 'linker', 'monkey',
+ 'perf', 'python', 'uiautomator', 'uirobot']
+VALID_DEVICE_TYPES = ['Android', 'iOS']
+
+
+def GetBuildType():
+ try:
+ return os.environ['BUILDTYPE']
+ except KeyError:
+ raise EnvironmentError(
+ 'The BUILDTYPE environment variable has not been set')
+
+
+def SetBuildType(build_type):
+ os.environ['BUILDTYPE'] = build_type
+
+
+def SetBuildDirectory(build_directory):
+ os.environ['CHROMIUM_OUT_DIR'] = build_directory
+
+
+def SetOutputDirectory(output_directory):
+ os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory
+
+
+def GetOutDirectory(build_type=None):
+ """Returns the out directory where the output binaries are built.
+
+ Args:
+ build_type: Build type, generally 'Debug' or 'Release'. Defaults to the
+ globally set build type environment variable BUILDTYPE.
+ """
+ if 'CHROMIUM_OUTPUT_DIR' in os.environ:
+ return os.path.abspath(os.path.join(
+ DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR')))
+
+ return os.path.abspath(os.path.join(
+ DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'),
+ GetBuildType() if build_type is None else build_type))
+
+
+def _Memoize(func):
+ def Wrapper():
+ try:
+ return func._result
+ except AttributeError:
+ func._result = func()
+ return func._result
+ return Wrapper
+
+
+def SetAdbPath(adb_path):
+ os.environ['ADB_PATH'] = adb_path
+
+
+def GetAdbPath():
+ # Check if a custom adb path as been set. If not, try to find adb
+ # on the system.
+ if os.environ.get('ADB_PATH'):
+ return os.environ.get('ADB_PATH')
+ else:
+ return _FindAdbPath()
+
+
+@_Memoize
+def _FindAdbPath():
+ if os.environ.get('ANDROID_SDK_ROOT'):
+ return 'adb'
+ # If envsetup.sh hasn't been sourced and there's no adb in the path,
+ # set it here.
+ try:
+ with file(os.devnull, 'w') as devnull:
+ subprocess.call(['adb', 'version'], stdout=devnull, stderr=devnull)
+ return 'adb'
+ except OSError:
+ logging.debug('No adb found in $PATH, fallback to checked in binary.')
+ return os.path.join(ANDROID_SDK_ROOT, 'platform-tools', 'adb')
+
+# Exit codes
+ERROR_EXIT_CODE = 1
+INFRA_EXIT_CODE = 87
+WARNING_EXIT_CODE = 88
diff --git a/build/android/pylib/constants/keyevent.py b/build/android/pylib/constants/keyevent.py
new file mode 100644
index 00000000000..06736b3d3dc
--- /dev/null
+++ b/build/android/pylib/constants/keyevent.py
@@ -0,0 +1,14 @@
+# Copyright 2015 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.
+
+"""Android KeyEvent constants.
+
+http://developer.android.com/reference/android/view/KeyEvent.html
+"""
+
+KEYCODE_BACK = 4
+KEYCODE_DPAD_RIGHT = 22
+KEYCODE_ENTER = 66
+KEYCODE_MENU = 82
+
diff --git a/build/android/pylib/content_settings.py b/build/android/pylib/content_settings.py
new file mode 100644
index 00000000000..8594140856d
--- /dev/null
+++ b/build/android/pylib/content_settings.py
@@ -0,0 +1,82 @@
+# Copyright 2014 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.
+
+from pylib import constants
+
+
+class ContentSettings(dict):
+
+ """A dict interface to interact with device content settings.
+
+ System properties are key/value pairs as exposed by adb shell content.
+ """
+
+ def __init__(self, table, device):
+ super(ContentSettings, self).__init__()
+ self._table = table
+ self._device = device
+
+ @staticmethod
+ def _GetTypeBinding(value):
+ if isinstance(value, bool):
+ return 'b'
+ if isinstance(value, float):
+ return 'f'
+ if isinstance(value, int):
+ return 'i'
+ if isinstance(value, long):
+ return 'l'
+ if isinstance(value, str):
+ return 's'
+ raise ValueError('Unsupported type %s' % type(value))
+
+ def iteritems(self):
+ # Example row:
+ # 'Row: 0 _id=13, name=logging_id2, value=-1fccbaa546705b05'
+ for row in self._device.RunShellCommand(
+ 'content query --uri content://%s' % self._table, as_root=True):
+ fields = row.split(', ')
+ key = None
+ value = None
+ for field in fields:
+ k, _, v = field.partition('=')
+ if k == 'name':
+ key = v
+ elif k == 'value':
+ value = v
+ if not key:
+ continue
+ if not value:
+ value = ''
+ yield key, value
+
+ def __getitem__(self, key):
+ return self._device.RunShellCommand(
+ 'content query --uri content://%s --where "name=\'%s\'" '
+ '--projection value' % (self._table, key), as_root=True).strip()
+
+ def __setitem__(self, key, value):
+ if key in self:
+ self._device.RunShellCommand(
+ 'content update --uri content://%s '
+ '--bind value:%s:%s --where "name=\'%s\'"' % (
+ self._table,
+ self._GetTypeBinding(value), value, key),
+ as_root=True)
+ else:
+ self._device.RunShellCommand(
+ 'content insert --uri content://%s '
+ '--bind name:%s:%s --bind value:%s:%s' % (
+ self._table,
+ self._GetTypeBinding(key), key,
+ self._GetTypeBinding(value), value),
+ as_root=True)
+
+ def __delitem__(self, key):
+ self._device.RunShellCommand(
+ 'content delete --uri content://%s '
+ '--bind name:%s:%s' % (
+ self._table,
+ self._GetTypeBinding(key), key),
+ as_root=True)
diff --git a/build/android/pylib/device/OWNERS b/build/android/pylib/device/OWNERS
new file mode 100644
index 00000000000..c35d7ace3c5
--- /dev/null
+++ b/build/android/pylib/device/OWNERS
@@ -0,0 +1,2 @@
+jbudorick@chromium.org
+perezju@chromium.org
diff --git a/build/android/pylib/device/__init__.py b/build/android/pylib/device/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/build/android/pylib/device/adb_wrapper.py b/build/android/pylib/device/adb_wrapper.py
new file mode 100644
index 00000000000..e8973260691
--- /dev/null
+++ b/build/android/pylib/device/adb_wrapper.py
@@ -0,0 +1,608 @@
+# Copyright 2013 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 module wraps Android's adb tool.
+
+This is a thin wrapper around the adb interface. Any additional complexity
+should be delegated to a higher level (ex. DeviceUtils).
+"""
+
+import collections
+import errno
+import logging
+import os
+import re
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.device import decorators
+from pylib.device import device_errors
+from pylib.utils import timeout_retry
+
+
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 2
+
+_EMULATOR_RE = re.compile(r'^emulator-[0-9]+$')
+
+_READY_STATE = 'device'
+
+
+def _VerifyLocalFileExists(path):
+ """Verifies a local file exists.
+
+ Args:
+ path: Path to the local file.
+
+ Raises:
+ IOError: If the file doesn't exist.
+ """
+ if not os.path.exists(path):
+ raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path)
+
+
+DeviceStat = collections.namedtuple('DeviceStat',
+ ['st_mode', 'st_size', 'st_time'])
+
+
+class AdbWrapper(object):
+ """A wrapper around a local Android Debug Bridge executable."""
+
+ def __init__(self, device_serial):
+ """Initializes the AdbWrapper.
+
+ Args:
+ device_serial: The device serial number as a string.
+ """
+ if not device_serial:
+ raise ValueError('A device serial must be specified')
+ self._device_serial = str(device_serial)
+
+ # pylint: disable=unused-argument
+ @classmethod
+ def _BuildAdbCmd(cls, args, device_serial, cpu_affinity=None):
+ if cpu_affinity is not None:
+ cmd = ['taskset', '-c', str(cpu_affinity)]
+ else:
+ cmd = []
+ cmd.append(constants.GetAdbPath())
+ if device_serial is not None:
+ cmd.extend(['-s', device_serial])
+ cmd.extend(args)
+ return cmd
+ # pylint: enable=unused-argument
+
+ # pylint: disable=unused-argument
+ @classmethod
+ @decorators.WithTimeoutAndRetries
+ def _RunAdbCmd(cls, args, timeout=None, retries=None, device_serial=None,
+ check_error=True, cpu_affinity=None):
+ status, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
+ cls._BuildAdbCmd(args, device_serial, cpu_affinity=cpu_affinity),
+ timeout_retry.CurrentTimeoutThread().GetRemainingTime())
+ if status != 0:
+ raise device_errors.AdbCommandFailedError(
+ args, output, status, device_serial)
+ # This catches some errors, including when the device drops offline;
+ # unfortunately adb is very inconsistent with error reporting so many
+ # command failures present differently.
+ if check_error and output.startswith('error:'):
+ raise device_errors.AdbCommandFailedError(args, output)
+ return output
+ # pylint: enable=unused-argument
+
+ def _RunDeviceAdbCmd(self, args, timeout, retries, check_error=True):
+ """Runs an adb command on the device associated with this object.
+
+ Args:
+ args: A list of arguments to adb.
+ timeout: Timeout in seconds.
+ retries: Number of retries.
+ check_error: Check that the command doesn't return an error message. This
+ does NOT check the exit status of shell commands.
+
+ Returns:
+ The output of the command.
+ """
+ return self._RunAdbCmd(args, timeout=timeout, retries=retries,
+ device_serial=self._device_serial,
+ check_error=check_error)
+
+ def _IterRunDeviceAdbCmd(self, args, timeout):
+ """Runs an adb command and returns an iterator over its output lines.
+
+ Args:
+ args: A list of arguments to adb.
+ timeout: Timeout in seconds.
+
+ Yields:
+ The output of the command line by line.
+ """
+ return cmd_helper.IterCmdOutputLines(
+ self._BuildAdbCmd(args, self._device_serial), timeout=timeout)
+
+ def __eq__(self, other):
+ """Consider instances equal if they refer to the same device.
+
+ Args:
+ other: The instance to compare equality with.
+
+ Returns:
+ True if the instances are considered equal, false otherwise.
+ """
+ return self._device_serial == str(other)
+
+ def __str__(self):
+ """The string representation of an instance.
+
+ Returns:
+ The device serial number as a string.
+ """
+ return self._device_serial
+
+ def __repr__(self):
+ return '%s(\'%s\')' % (self.__class__.__name__, self)
+
+ # pylint: disable=unused-argument
+ @classmethod
+ def IsServerOnline(cls):
+ status, output = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
+ output = [int(x) for x in output.split()]
+ logging.info('PIDs for adb found: %r', output)
+ return status == 0
+ # pylint: enable=unused-argument
+
+ @classmethod
+ def KillServer(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ cls._RunAdbCmd(['kill-server'], timeout=timeout, retries=retries)
+
+ @classmethod
+ def StartServer(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ # CPU affinity is used to reduce adb instability http://crbug.com/268450
+ cls._RunAdbCmd(['start-server'], timeout=timeout, retries=retries,
+ cpu_affinity=0)
+
+ @classmethod
+ def GetDevices(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """DEPRECATED. Refer to Devices(...) below."""
+ # TODO(jbudorick): Remove this function once no more clients are using it.
+ return cls.Devices(timeout=timeout, retries=retries)
+
+ @classmethod
+ def Devices(cls, is_ready=True, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Get the list of active attached devices.
+
+ Args:
+ is_ready: Whether the devices should be limited to only those that are
+ ready for use.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Yields:
+ AdbWrapper instances.
+ """
+ output = cls._RunAdbCmd(['devices'], timeout=timeout, retries=retries)
+ lines = (line.split() for line in output.splitlines())
+ return [AdbWrapper(line[0]) for line in lines
+ if len(line) == 2 and (not is_ready or line[1] == _READY_STATE)]
+
+ def GetDeviceSerial(self):
+ """Gets the device serial number associated with this object.
+
+ Returns:
+ Device serial number as a string.
+ """
+ return self._device_serial
+
+ def Push(self, local, remote, timeout=60*5, retries=_DEFAULT_RETRIES):
+ """Pushes a file from the host to the device.
+
+ Args:
+ local: Path on the host filesystem.
+ remote: Path on the device filesystem.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ _VerifyLocalFileExists(local)
+ self._RunDeviceAdbCmd(['push', local, remote], timeout, retries)
+
+ def Pull(self, remote, local, timeout=60*5, retries=_DEFAULT_RETRIES):
+ """Pulls a file from the device to the host.
+
+ Args:
+ remote: Path on the device filesystem.
+ local: Path on the host filesystem.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ cmd = ['pull', remote, local]
+ self._RunDeviceAdbCmd(cmd, timeout, retries)
+ try:
+ _VerifyLocalFileExists(local)
+ except IOError:
+ raise device_errors.AdbCommandFailedError(
+ cmd, 'File not found on host: %s' % local, device_serial=str(self))
+
+ def Shell(self, command, expect_status=0, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Runs a shell command on the device.
+
+ Args:
+ command: A string with the shell command to run.
+ expect_status: (optional) Check that the command's exit status matches
+ this value. Default is 0. If set to None the test is skipped.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ The output of the shell command as a string.
+
+ Raises:
+ device_errors.AdbCommandFailedError: If the exit status doesn't match
+ |expect_status|.
+ """
+ if expect_status is None:
+ args = ['shell', command]
+ else:
+ args = ['shell', '%s; echo %%$?;' % command.rstrip()]
+ output = self._RunDeviceAdbCmd(args, timeout, retries, check_error=False)
+ if expect_status is not None:
+ output_end = output.rfind('%')
+ if output_end < 0:
+ # causes the status string to become empty and raise a ValueError
+ output_end = len(output)
+
+ try:
+ status = int(output[output_end+1:])
+ except ValueError:
+ logging.warning('exit status of shell command %r missing.', command)
+ raise device_errors.AdbShellCommandFailedError(
+ command, output, status=None, device_serial=self._device_serial)
+ output = output[:output_end]
+ if status != expect_status:
+ raise device_errors.AdbShellCommandFailedError(
+ command, output, status=status, device_serial=self._device_serial)
+ return output
+
+ def IterShell(self, command, timeout):
+ """Runs a shell command and returns an iterator over its output lines.
+
+ Args:
+ command: A string with the shell command to run.
+ timeout: Timeout in seconds.
+
+ Yields:
+ The output of the command line by line.
+ """
+ args = ['shell', command]
+ return cmd_helper.IterCmdOutputLines(
+ self._BuildAdbCmd(args, self._device_serial), timeout=timeout)
+
+ def Ls(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """List the contents of a directory on the device.
+
+ Args:
+ path: Path on the device filesystem.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ A list of pairs (filename, stat) for each file found in the directory,
+ where the stat object has the properties: st_mode, st_size, and st_time.
+
+ Raises:
+ AdbCommandFailedError if |path| does not specify a valid and accessible
+ directory in the device.
+ """
+ def ParseLine(line):
+ cols = line.split(None, 3)
+ filename = cols.pop()
+ stat = DeviceStat(*[int(num, base=16) for num in cols])
+ return (filename, stat)
+
+ cmd = ['ls', path]
+ lines = self._RunDeviceAdbCmd(
+ cmd, timeout=timeout, retries=retries).splitlines()
+ if lines:
+ return [ParseLine(line) for line in lines]
+ else:
+ raise device_errors.AdbCommandFailedError(
+ cmd, 'path does not specify an accessible directory in the device',
+ device_serial=self._device_serial)
+
+ def Logcat(self, clear=False, dump=False, filter_specs=None,
+ logcat_format=None, ring_buffer=None, timeout=None,
+ retries=_DEFAULT_RETRIES):
+ """Get an iterable over the logcat output.
+
+ Args:
+ clear: If true, clear the logcat.
+ dump: If true, dump the current logcat contents.
+ filter_specs: If set, a list of specs to filter the logcat.
+ logcat_format: If set, the format in which the logcat should be output.
+ Options include "brief", "process", "tag", "thread", "raw", "time",
+ "threadtime", and "long"
+ ring_buffer: If set, a list of alternate ring buffers to request.
+ Options include "main", "system", "radio", "events", "crash" or "all".
+ The default is equivalent to ["main", "system", "crash"].
+ timeout: (optional) If set, timeout per try in seconds. If clear or dump
+ is set, defaults to _DEFAULT_TIMEOUT.
+ retries: (optional) If clear or dump is set, the number of retries to
+ attempt. Otherwise, does nothing.
+
+ Yields:
+ logcat output line by line.
+ """
+ cmd = ['logcat']
+ use_iter = True
+ if clear:
+ cmd.append('-c')
+ use_iter = False
+ if dump:
+ cmd.append('-d')
+ use_iter = False
+ if logcat_format:
+ cmd.extend(['-v', logcat_format])
+ if ring_buffer:
+ for buffer_name in ring_buffer:
+ cmd.extend(['-b', buffer_name])
+ if filter_specs:
+ cmd.extend(filter_specs)
+
+ if use_iter:
+ return self._IterRunDeviceAdbCmd(cmd, timeout)
+ else:
+ timeout = timeout if timeout is not None else _DEFAULT_TIMEOUT
+ return self._RunDeviceAdbCmd(cmd, timeout, retries).splitlines()
+
+ def Forward(self, local, remote, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Forward socket connections from the local socket to the remote socket.
+
+ Sockets are specified by one of:
+ tcp:
+ localabstract:
+ localreserved:
+ localfilesystem:
+ dev:
+ jdwp: (remote only)
+
+ Args:
+ local: The host socket.
+ remote: The device socket.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ self._RunDeviceAdbCmd(['forward', str(local), str(remote)], timeout,
+ retries)
+
+ def JDWP(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """List of PIDs of processes hosting a JDWP transport.
+
+ Args:
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ A list of PIDs as strings.
+ """
+ return [a.strip() for a in
+ self._RunDeviceAdbCmd(['jdwp'], timeout, retries).split('\n')]
+
+ def Install(self, apk_path, forward_lock=False, reinstall=False,
+ sd_card=False, timeout=60*2, retries=_DEFAULT_RETRIES):
+ """Install an apk on the device.
+
+ Args:
+ apk_path: Host path to the APK file.
+ forward_lock: (optional) If set forward-locks the app.
+ reinstall: (optional) If set reinstalls the app, keeping its data.
+ sd_card: (optional) If set installs on the SD card.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ _VerifyLocalFileExists(apk_path)
+ cmd = ['install']
+ if forward_lock:
+ cmd.append('-l')
+ if reinstall:
+ cmd.append('-r')
+ if sd_card:
+ cmd.append('-s')
+ cmd.append(apk_path)
+ output = self._RunDeviceAdbCmd(cmd, timeout, retries)
+ if 'Success' not in output:
+ raise device_errors.AdbCommandFailedError(
+ cmd, output, device_serial=self._device_serial)
+
+ def InstallMultiple(self, apk_paths, forward_lock=False, reinstall=False,
+ sd_card=False, allow_downgrade=False, partial=False,
+ timeout=60*2, retries=_DEFAULT_RETRIES):
+ """Install an apk with splits on the device.
+
+ Args:
+ apk_paths: Host path to the APK file.
+ forward_lock: (optional) If set forward-locks the app.
+ reinstall: (optional) If set reinstalls the app, keeping its data.
+ sd_card: (optional) If set installs on the SD card.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ allow_downgrade: (optional) Allow versionCode downgrade.
+ partial: (optional) Package ID if apk_paths doesn't include all .apks.
+ """
+ for path in apk_paths:
+ _VerifyLocalFileExists(path)
+ cmd = ['install-multiple']
+ if forward_lock:
+ cmd.append('-l')
+ if reinstall:
+ cmd.append('-r')
+ if sd_card:
+ cmd.append('-s')
+ if allow_downgrade:
+ cmd.append('-d')
+ if partial:
+ cmd.extend(('-p', partial))
+ cmd.extend(apk_paths)
+ output = self._RunDeviceAdbCmd(cmd, timeout, retries)
+ if 'Success' not in output:
+ raise device_errors.AdbCommandFailedError(
+ cmd, output, device_serial=self._device_serial)
+
+ def Uninstall(self, package, keep_data=False, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Remove the app |package| from the device.
+
+ Args:
+ package: The package to uninstall.
+ keep_data: (optional) If set keep the data and cache directories.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ cmd = ['uninstall']
+ if keep_data:
+ cmd.append('-k')
+ cmd.append(package)
+ output = self._RunDeviceAdbCmd(cmd, timeout, retries)
+ if 'Failure' in output:
+ raise device_errors.AdbCommandFailedError(
+ cmd, output, device_serial=self._device_serial)
+
+ def Backup(self, path, packages=None, apk=False, shared=False,
+ nosystem=True, include_all=False, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Write an archive of the device's data to |path|.
+
+ Args:
+ path: Local path to store the backup file.
+ packages: List of to packages to be backed up.
+ apk: (optional) If set include the .apk files in the archive.
+ shared: (optional) If set buckup the device's SD card.
+ nosystem: (optional) If set exclude system applications.
+ include_all: (optional) If set back up all installed applications and
+ |packages| is optional.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ cmd = ['backup', '-f', path]
+ if apk:
+ cmd.append('-apk')
+ if shared:
+ cmd.append('-shared')
+ if nosystem:
+ cmd.append('-nosystem')
+ if include_all:
+ cmd.append('-all')
+ if packages:
+ cmd.extend(packages)
+ assert bool(packages) ^ bool(include_all), (
+ 'Provide \'packages\' or set \'include_all\' but not both.')
+ ret = self._RunDeviceAdbCmd(cmd, timeout, retries)
+ _VerifyLocalFileExists(path)
+ return ret
+
+ def Restore(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """Restore device contents from the backup archive.
+
+ Args:
+ path: Host path to the backup archive.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ _VerifyLocalFileExists(path)
+ self._RunDeviceAdbCmd(['restore'] + [path], timeout, retries)
+
+ def WaitForDevice(self, timeout=60*5, retries=_DEFAULT_RETRIES):
+ """Block until the device is online.
+
+ Args:
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ self._RunDeviceAdbCmd(['wait-for-device'], timeout, retries)
+
+ def GetState(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """Get device state.
+
+ Args:
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ One of 'offline', 'bootloader', or 'device'.
+ """
+ return self._RunDeviceAdbCmd(['get-state'], timeout, retries).strip()
+
+ def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """Gets the device path.
+
+ Args:
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ The device path (e.g. usb:3-4)
+ """
+ return self._RunDeviceAdbCmd(['get-devpath'], timeout, retries)
+
+ def Remount(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """Remounts the /system partition on the device read-write."""
+ self._RunDeviceAdbCmd(['remount'], timeout, retries)
+
+ def Reboot(self, to_bootloader=False, timeout=60*5,
+ retries=_DEFAULT_RETRIES):
+ """Reboots the device.
+
+ Args:
+ to_bootloader: (optional) If set reboots to the bootloader.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ if to_bootloader:
+ cmd = ['reboot-bootloader']
+ else:
+ cmd = ['reboot']
+ self._RunDeviceAdbCmd(cmd, timeout, retries)
+
+ def Root(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
+ """Restarts the adbd daemon with root permissions, if possible.
+
+ Args:
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+ """
+ output = self._RunDeviceAdbCmd(['root'], timeout, retries)
+ if 'cannot' in output:
+ raise device_errors.AdbCommandFailedError(
+ ['root'], output, device_serial=self._device_serial)
+
+ def Emu(self, cmd, timeout=_DEFAULT_TIMEOUT,
+ retries=_DEFAULT_RETRIES):
+ """Runs an emulator console command.
+
+ See http://developer.android.com/tools/devices/emulator.html#console
+
+ Args:
+ cmd: The command to run on the emulator console.
+ timeout: (optional) Timeout per try in seconds.
+ retries: (optional) Number of retries to attempt.
+
+ Returns:
+ The output of the emulator console command.
+ """
+ if isinstance(cmd, basestring):
+ cmd = [cmd]
+ return self._RunDeviceAdbCmd(['emu'] + cmd, timeout, retries)
+
+ @property
+ def is_emulator(self):
+ return _EMULATOR_RE.match(self._device_serial)
+
+ @property
+ def is_ready(self):
+ try:
+ return self.GetState() == _READY_STATE
+ except device_errors.CommandFailedError:
+ return False
diff --git a/build/android/pylib/device/adb_wrapper_test.py b/build/android/pylib/device/adb_wrapper_test.py
new file mode 100644
index 00000000000..5fc9eb66308
--- /dev/null
+++ b/build/android/pylib/device/adb_wrapper_test.py
@@ -0,0 +1,96 @@
+# Copyright 2013 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.
+
+"""Tests for the AdbWrapper class."""
+
+import os
+import tempfile
+import time
+import unittest
+
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+
+
+class TestAdbWrapper(unittest.TestCase):
+
+ def setUp(self):
+ devices = adb_wrapper.AdbWrapper.Devices()
+ assert devices, 'A device must be attached'
+ self._adb = devices[0]
+ self._adb.WaitForDevice()
+
+ @staticmethod
+ def _MakeTempFile(contents):
+ """Make a temporary file with the given contents.
+
+ Args:
+ contents: string to write to the temporary file.
+
+ Returns:
+ The absolute path to the file.
+ """
+ fi, path = tempfile.mkstemp()
+ with os.fdopen(fi, 'wb') as f:
+ f.write(contents)
+ return path
+
+ def testShell(self):
+ output = self._adb.Shell('echo test', expect_status=0)
+ self.assertEqual(output.strip(), 'test')
+ output = self._adb.Shell('echo test')
+ self.assertEqual(output.strip(), 'test')
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self._adb.Shell('echo test', expect_status=1)
+
+ def testPushLsPull(self):
+ path = self._MakeTempFile('foo')
+ device_path = '/data/local/tmp/testfile.txt'
+ local_tmpdir = os.path.dirname(path)
+ self._adb.Push(path, device_path)
+ files = dict(self._adb.Ls('/data/local/tmp'))
+ self.assertTrue('testfile.txt' in files)
+ self.assertEquals(3, files['testfile.txt'].st_size)
+ self.assertEqual(self._adb.Shell('cat %s' % device_path), 'foo')
+ self._adb.Pull(device_path, local_tmpdir)
+ with open(os.path.join(local_tmpdir, 'testfile.txt'), 'r') as f:
+ self.assertEqual(f.read(), 'foo')
+
+ def testInstall(self):
+ path = self._MakeTempFile('foo')
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self._adb.Install(path)
+
+ def testForward(self):
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self._adb.Forward(0, 0)
+
+ def testUninstall(self):
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self._adb.Uninstall('some.nonexistant.package')
+
+ def testRebootWaitForDevice(self):
+ self._adb.Reboot()
+ print 'waiting for device to reboot...'
+ while self._adb.GetState() == 'device':
+ time.sleep(1)
+ self._adb.WaitForDevice()
+ self.assertEqual(self._adb.GetState(), 'device')
+ print 'waiting for package manager...'
+ while 'package:' not in self._adb.Shell('pm path android'):
+ time.sleep(1)
+
+ def testRootRemount(self):
+ self._adb.Root()
+ while True:
+ try:
+ self._adb.Shell('start')
+ break
+ except device_errors.AdbCommandFailedError:
+ time.sleep(1)
+ self._adb.Remount()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/device/battery_utils.py b/build/android/pylib/device/battery_utils.py
new file mode 100644
index 00000000000..eab558ea9a6
--- /dev/null
+++ b/build/android/pylib/device/battery_utils.py
@@ -0,0 +1,593 @@
+# Copyright 2015 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.
+
+"""Provides a variety of device interactions with power.
+"""
+# pylint: disable=unused-argument
+
+import collections
+import contextlib
+import csv
+import logging
+
+from pylib import constants
+from pylib.device import decorators
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import timeout_retry
+
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 3
+
+
+_DEVICE_PROFILES = [
+ {
+ 'name': 'Nexus 4',
+ 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
+ 'enable_command': (
+ 'echo 0 > /sys/module/pm8921_charger/parameters/disabled && '
+ 'dumpsys battery reset'),
+ 'disable_command': (
+ 'echo 1 > /sys/module/pm8921_charger/parameters/disabled && '
+ 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
+ 'charge_counter': None,
+ 'voltage': None,
+ 'current': None,
+ },
+ {
+ 'name': 'Nexus 5',
+ # Nexus 5
+ # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
+ # energy coming from USB. Setting the power_supply offline just updates the
+ # Android system to reflect that.
+ 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
+ 'enable_command': (
+ 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'echo 1 > /sys/class/power_supply/usb/online &&'
+ 'dumpsys battery reset'),
+ 'disable_command': (
+ 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'chmod 644 /sys/class/power_supply/usb/online && '
+ 'echo 0 > /sys/class/power_supply/usb/online && '
+ 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
+ 'charge_counter': None,
+ 'voltage': None,
+ 'current': None,
+ },
+ {
+ 'name': 'Nexus 6',
+ 'witness_file': None,
+ 'enable_command': (
+ 'echo 1 > /sys/class/power_supply/battery/charging_enabled && '
+ 'dumpsys battery reset'),
+ 'disable_command': (
+ 'echo 0 > /sys/class/power_supply/battery/charging_enabled && '
+ 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
+ 'charge_counter': (
+ '/sys/class/power_supply/max170xx_battery/charge_counter_ext'),
+ 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now',
+ 'current': '/sys/class/power_supply/max170xx_battery/current_now',
+ },
+ {
+ 'name': 'Nexus 9',
+ 'witness_file': None,
+ 'enable_command': (
+ 'echo Disconnected > '
+ '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && '
+ 'dumpsys battery reset'),
+ 'disable_command': (
+ 'echo Connected > '
+ '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && '
+ 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
+ 'charge_counter': '/sys/class/power_supply/battery/charge_counter_ext',
+ 'voltage': '/sys/class/power_supply/battery/voltage_now',
+ 'current': '/sys/class/power_supply/battery/current_now',
+ },
+ {
+ 'name': 'Nexus 10',
+ 'witness_file': None,
+ 'enable_command': None,
+ 'disable_command': None,
+ 'charge_counter': None,
+ 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now',
+ 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now',
+
+ },
+]
+
+# The list of useful dumpsys columns.
+# Index of the column containing the format version.
+_DUMP_VERSION_INDEX = 0
+# Index of the column containing the type of the row.
+_ROW_TYPE_INDEX = 3
+# Index of the column containing the uid.
+_PACKAGE_UID_INDEX = 4
+# Index of the column containing the application package.
+_PACKAGE_NAME_INDEX = 5
+# The column containing the uid of the power data.
+_PWI_UID_INDEX = 1
+# The column containing the type of consumption. Only consumtion since last
+# charge are of interest here.
+_PWI_AGGREGATION_INDEX = 2
+# The column containing the amount of power used, in mah.
+_PWI_POWER_CONSUMPTION_INDEX = 5
+
+
+class BatteryUtils(object):
+
+ def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
+ default_retries=_DEFAULT_RETRIES):
+ """BatteryUtils constructor.
+
+ Args:
+ device: A DeviceUtils instance.
+ default_timeout: An integer containing the default number of seconds to
+ wait for an operation to complete if no explicit value
+ is provided.
+ default_retries: An integer containing the default number or times an
+ operation should be retried on failure if no explicit
+ value is provided.
+
+ Raises:
+ TypeError: If it is not passed a DeviceUtils instance.
+ """
+ if not isinstance(device, device_utils.DeviceUtils):
+ raise TypeError('Must be initialized with DeviceUtils object.')
+ self._device = device
+ self._cache = device.GetClientCache(self.__class__.__name__)
+ self._default_timeout = default_timeout
+ self._default_retries = default_retries
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SupportsFuelGauge(self, timeout=None, retries=None):
+ """Detect if fuel gauge chip is present.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if known fuel gauge files are present.
+ False otherwise.
+ """
+ self._DiscoverDeviceProfile()
+ return (self._cache['profile']['enable_command'] != None
+ and self._cache['profile']['charge_counter'] != None)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetFuelGaugeChargeCounter(self, timeout=None, retries=None):
+ """Get value of charge_counter on fuel gauge chip.
+
+ Device must have charging disabled for this, not just battery updates
+ disabled. The only device that this currently works with is the nexus 5.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ value of charge_counter for fuel gauge chip in units of nAh.
+
+ Raises:
+ device_errors.CommandFailedError: If fuel gauge chip not found.
+ """
+ if self.SupportsFuelGauge():
+ return int(self._device.ReadFile(
+ self._cache['profile']['charge_counter']))
+ raise device_errors.CommandFailedError(
+ 'Unable to find fuel gauge.')
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetNetworkData(self, package, timeout=None, retries=None):
+ """Get network data for specific package.
+
+ Args:
+ package: package name you want network data for.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ Tuple of (sent_data, recieved_data)
+ None if no network data found
+ """
+ # If device_utils clears cache, cache['uids'] doesn't exist
+ if 'uids' not in self._cache:
+ self._cache['uids'] = {}
+ if package not in self._cache['uids']:
+ self.GetPowerData()
+ if package not in self._cache['uids']:
+ logging.warning('No UID found for %s. Can\'t get network data.',
+ package)
+ return None
+
+ network_data_path = '/proc/uid_stat/%s/' % self._cache['uids'][package]
+ try:
+ send_data = int(self._device.ReadFile(network_data_path + 'tcp_snd'))
+ # If ReadFile throws exception, it means no network data usage file for
+ # package has been recorded. Return 0 sent and 0 received.
+ except device_errors.AdbShellCommandFailedError:
+ logging.warning('No sent data found for package %s', package)
+ send_data = 0
+ try:
+ recv_data = int(self._device.ReadFile(network_data_path + 'tcp_rcv'))
+ except device_errors.AdbShellCommandFailedError:
+ logging.warning('No received data found for package %s', package)
+ recv_data = 0
+ return (send_data, recv_data)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetPowerData(self, timeout=None, retries=None):
+ """Get power data for device.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ Dict of power data, keyed on package names.
+ {
+ package_name: {
+ 'uid': uid,
+ 'data': [1,2,3]
+ },
+ }
+ """
+ if 'uids' not in self._cache:
+ self._cache['uids'] = {}
+ dumpsys_output = self._device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True)
+ csvreader = csv.reader(dumpsys_output)
+ pwi_entries = collections.defaultdict(list)
+ for entry in csvreader:
+ if entry[_DUMP_VERSION_INDEX] not in ['8', '9']:
+ # Wrong dumpsys version.
+ raise device_errors.DeviceVersionError(
+ 'Dumpsys version must be 8 or 9. %s found.'
+ % entry[_DUMP_VERSION_INDEX])
+ if _ROW_TYPE_INDEX < len(entry) and entry[_ROW_TYPE_INDEX] == 'uid':
+ current_package = entry[_PACKAGE_NAME_INDEX]
+ if (self._cache['uids'].get(current_package)
+ and self._cache['uids'].get(current_package)
+ != entry[_PACKAGE_UID_INDEX]):
+ raise device_errors.CommandFailedError(
+ 'Package %s found multiple times with differnt UIDs %s and %s'
+ % (current_package, self._cache['uids'][current_package],
+ entry[_PACKAGE_UID_INDEX]))
+ self._cache['uids'][current_package] = entry[_PACKAGE_UID_INDEX]
+ elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry)
+ and entry[_ROW_TYPE_INDEX] == 'pwi'
+ and entry[_PWI_AGGREGATION_INDEX] == 'l'):
+ pwi_entries[entry[_PWI_UID_INDEX]].append(
+ float(entry[_PWI_POWER_CONSUMPTION_INDEX]))
+
+ return {p: {'uid': uid, 'data': pwi_entries[uid]}
+ for p, uid in self._cache['uids'].iteritems()}
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetPackagePowerData(self, package, timeout=None, retries=None):
+ """Get power data for particular package.
+
+ Args:
+ package: Package to get power data on.
+
+ returns:
+ Dict of UID and power data.
+ {
+ 'uid': uid,
+ 'data': [1,2,3]
+ }
+ None if the package is not found in the power data.
+ """
+ return self.GetPowerData().get(package)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetBatteryInfo(self, timeout=None, retries=None):
+ """Gets battery info for the device.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+ Returns:
+ A dict containing various battery information as reported by dumpsys
+ battery.
+ """
+ result = {}
+ # Skip the first line, which is just a header.
+ for line in self._device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True)[1:]:
+ # If usb charging has been disabled, an extra line of header exists.
+ if 'UPDATES STOPPED' in line:
+ logging.warning('Dumpsys battery not receiving updates. '
+ 'Run dumpsys battery reset if this is in error.')
+ elif ':' not in line:
+ logging.warning('Unknown line found in dumpsys battery: "%s"', line)
+ else:
+ k, v = line.split(':', 1)
+ result[k.strip()] = v.strip()
+ return result
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetCharging(self, timeout=None, retries=None):
+ """Gets the charging state of the device.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+ Returns:
+ True if the device is charging, false otherwise.
+ """
+ battery_info = self.GetBatteryInfo()
+ for k in ('AC powered', 'USB powered', 'Wireless powered'):
+ if (k in battery_info and
+ battery_info[k].lower() in ('true', '1', 'yes')):
+ return True
+ return False
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SetCharging(self, enabled, timeout=None, retries=None):
+ """Enables or disables charging on the device.
+
+ Args:
+ enabled: A boolean indicating whether charging should be enabled or
+ disabled.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ device_errors.CommandFailedError: If method of disabling charging cannot
+ be determined.
+ """
+ self._DiscoverDeviceProfile()
+ if not self._cache['profile']['enable_command']:
+ raise device_errors.CommandFailedError(
+ 'Unable to find charging commands.')
+
+ if enabled:
+ command = self._cache['profile']['enable_command']
+ else:
+ command = self._cache['profile']['disable_command']
+
+ def set_and_verify_charging():
+ self._device.RunShellCommand(command, check_return=True)
+ return self.GetCharging() == enabled
+
+ timeout_retry.WaitFor(set_and_verify_charging, wait_period=1)
+
+ # TODO(rnephew): Make private when all use cases can use the context manager.
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def DisableBatteryUpdates(self, timeout=None, retries=None):
+ """Resets battery data and makes device appear like it is not
+ charging so that it will collect power data since last charge.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ device_errors.CommandFailedError: When resetting batterystats fails to
+ reset power values.
+ device_errors.DeviceVersionError: If device is not L or higher.
+ """
+ def battery_updates_disabled():
+ return self.GetCharging() is False
+
+ self._ClearPowerData()
+ self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '0'],
+ check_return=True)
+ self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'],
+ check_return=True)
+ timeout_retry.WaitFor(battery_updates_disabled, wait_period=1)
+
+ # TODO(rnephew): Make private when all use cases can use the context manager.
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def EnableBatteryUpdates(self, timeout=None, retries=None):
+ """Restarts device charging so that dumpsys no longer collects power data.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ device_errors.DeviceVersionError: If device is not L or higher.
+ """
+ def battery_updates_enabled():
+ return (self.GetCharging()
+ or not bool('UPDATES STOPPED' in self._device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True)))
+
+ self._device.RunShellCommand(['dumpsys', 'battery', 'reset'],
+ check_return=True)
+ timeout_retry.WaitFor(battery_updates_enabled, wait_period=1)
+
+ @contextlib.contextmanager
+ def BatteryMeasurement(self, timeout=None, retries=None):
+ """Context manager that enables battery data collection. It makes
+ the device appear to stop charging so that dumpsys will start collecting
+ power data since last charge. Once the with block is exited, charging is
+ resumed and power data since last charge is no longer collected.
+
+ Only for devices L and higher.
+
+ Example usage:
+ with BatteryMeasurement():
+ browser_actions()
+ get_power_data() # report usage within this block
+ after_measurements() # Anything that runs after power
+ # measurements are collected
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ device_errors.DeviceVersionError: If device is not L or higher.
+ """
+ if (self._device.build_version_sdk <
+ constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
+ raise device_errors.DeviceVersionError('Device must be L or higher.')
+ try:
+ self.DisableBatteryUpdates(timeout=timeout, retries=retries)
+ yield
+ finally:
+ self.EnableBatteryUpdates(timeout=timeout, retries=retries)
+
+ def ChargeDeviceToLevel(self, level, wait_period=60):
+ """Enables charging and waits for device to be charged to given level.
+
+ Args:
+ level: level of charge to wait for.
+ wait_period: time in seconds to wait between checking.
+ """
+ self.SetCharging(True)
+
+ def device_charged():
+ battery_level = self.GetBatteryInfo().get('level')
+ if battery_level is None:
+ logging.warning('Unable to find current battery level.')
+ battery_level = 100
+ else:
+ logging.info('current battery level: %s', battery_level)
+ battery_level = int(battery_level)
+ return battery_level >= level
+
+ timeout_retry.WaitFor(device_charged, wait_period=wait_period)
+
+ def LetBatteryCoolToTemperature(self, target_temp, wait_period=60):
+ """Lets device sit to give battery time to cool down
+ Args:
+ temp: maximum temperature to allow in tenths of degrees c.
+ wait_period: time in seconds to wait between checking.
+ """
+ def cool_device():
+ temp = self.GetBatteryInfo().get('temperature')
+ if temp is None:
+ logging.warning('Unable to find current battery temperature.')
+ temp = 0
+ else:
+ logging.info('Current battery temperature: %s', temp)
+ return int(temp) <= target_temp
+ self.EnableBatteryUpdates()
+ logging.info('Waiting for the device to cool down to %s (0.1 C)',
+ target_temp)
+ timeout_retry.WaitFor(cool_device, wait_period=wait_period)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def TieredSetCharging(self, enabled, timeout=None, retries=None):
+ """Enables or disables charging on the device.
+
+ Args:
+ enabled: A boolean indicating whether charging should be enabled or
+ disabled.
+ timeout: timeout in seconds
+ retries: number of retries
+ """
+ if self.GetCharging() == enabled:
+ logging.warning('Device charging already in expected state: %s', enabled)
+ return
+
+ if enabled:
+ try:
+ self.SetCharging(enabled)
+ except device_errors.CommandFailedError:
+ logging.info('Unable to enable charging via hardware.'
+ ' Falling back to software enabling.')
+ self.EnableBatteryUpdates()
+ else:
+ try:
+ self._ClearPowerData()
+ self.SetCharging(enabled)
+ except device_errors.CommandFailedError:
+ logging.info('Unable to disable charging via hardware.'
+ ' Falling back to software disabling.')
+ self.DisableBatteryUpdates()
+
+ @contextlib.contextmanager
+ def PowerMeasurement(self, timeout=None, retries=None):
+ """Context manager that enables battery power collection.
+
+ Once the with block is exited, charging is resumed. Will attempt to disable
+ charging at the hardware level, and if that fails will fall back to software
+ disabling of battery updates.
+
+ Only for devices L and higher.
+
+ Example usage:
+ with PowerMeasurement():
+ browser_actions()
+ get_power_data() # report usage within this block
+ after_measurements() # Anything that runs after power
+ # measurements are collected
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+ """
+ try:
+ self.TieredSetCharging(False, timeout=timeout, retries=retries)
+ yield
+ finally:
+ self.TieredSetCharging(True, timeout=timeout, retries=retries)
+
+ def _ClearPowerData(self):
+ """Resets battery data and makes device appear like it is not
+ charging so that it will collect power data since last charge.
+
+ Returns:
+ True if power data cleared.
+ False if power data clearing is not supported (pre-L)
+
+ Raises:
+ device_errors.DeviceVersionError: If power clearing is supported,
+ but fails.
+ """
+ if (self._device.build_version_sdk <
+ constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
+ logging.warning('Dumpsys power data only available on 5.0 and above. '
+ 'Cannot clear power data.')
+ return False
+
+ self._device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True)
+ self._device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True)
+ self._device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--reset'], check_return=True)
+ battery_data = self._device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--charged', '--checkin'],
+ check_return=True, large_output=True)
+ for line in battery_data:
+ l = line.split(',')
+ if (len(l) > _PWI_POWER_CONSUMPTION_INDEX and l[_ROW_TYPE_INDEX] == 'pwi'
+ and l[_PWI_POWER_CONSUMPTION_INDEX] != 0):
+ self._device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True)
+ raise device_errors.CommandFailedError(
+ 'Non-zero pmi value found after reset.')
+ self._device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True)
+ return True
+
+ def _DiscoverDeviceProfile(self):
+ """Checks and caches device information.
+
+ Returns:
+ True if profile is found, false otherwise.
+ """
+
+ if 'profile' in self._cache:
+ return True
+ for profile in _DEVICE_PROFILES:
+ if self._device.product_model == profile['name']:
+ self._cache['profile'] = profile
+ return True
+ self._cache['profile'] = {
+ 'name': None,
+ 'witness_file': None,
+ 'enable_command': None,
+ 'disable_command': None,
+ 'charge_counter': None,
+ 'voltage': None,
+ 'current': None,
+ }
+ return False
diff --git a/build/android/pylib/device/battery_utils_test.py b/build/android/pylib/device/battery_utils_test.py
new file mode 100755
index 00000000000..b968fa6bbfc
--- /dev/null
+++ b/build/android/pylib/device/battery_utils_test.py
@@ -0,0 +1,574 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""
+Unit tests for the contents of battery_utils.py
+"""
+
+# pylint: disable=W0613
+
+import logging
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.device import battery_utils
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.device import device_utils_test
+from pylib.utils import mock_calls
+
+# RunCommand from third_party/android_testrunner/run_command.py is mocked
+# below, so its path needs to be in sys.path.
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+_DUMPSYS_OUTPUT = [
+ '9,0,i,uid,1000,test_package1',
+ '9,0,i,uid,1001,test_package2',
+ '9,1000,l,pwi,uid,1',
+ '9,1001,l,pwi,uid,2'
+]
+
+
+class BatteryUtilsTest(mock_calls.TestCase):
+
+ _NEXUS_5 = {
+ 'name': 'Nexus 5',
+ 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
+ 'enable_command': (
+ 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'echo 1 > /sys/class/power_supply/usb/online'),
+ 'disable_command': (
+ 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'chmod 644 /sys/class/power_supply/usb/online && '
+ 'echo 0 > /sys/class/power_supply/usb/online'),
+ 'charge_counter': None,
+ 'voltage': None,
+ 'current': None,
+ }
+
+ _NEXUS_6 = {
+ 'name': 'Nexus 6',
+ 'witness_file': None,
+ 'enable_command': None,
+ 'disable_command': None,
+ 'charge_counter': (
+ '/sys/class/power_supply/max170xx_battery/charge_counter_ext'),
+ 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now',
+ 'current': '/sys/class/power_supply/max170xx_battery/current_now',
+ }
+
+ _NEXUS_10 = {
+ 'name': 'Nexus 10',
+ 'witness_file': None,
+ 'enable_command': None,
+ 'disable_command': None,
+ 'charge_counter': (
+ '/sys/class/power_supply/ds2784-fuelgauge/charge_counter_ext'),
+ 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now',
+ 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now',
+ }
+
+ def ShellError(self, output=None, status=1):
+ def action(cmd, *args, **kwargs):
+ raise device_errors.AdbShellCommandFailedError(
+ cmd, output, status, str(self.device))
+ if output is None:
+ output = 'Permission denied\n'
+ return action
+
+ def setUp(self):
+ self.adb = device_utils_test._AdbWrapperMock('0123456789abcdef')
+ self.device = device_utils.DeviceUtils(
+ self.adb, default_timeout=10, default_retries=0)
+ self.watchMethodCalls(self.call.adb, ignore=['GetDeviceSerial'])
+ self.battery = battery_utils.BatteryUtils(
+ self.device, default_timeout=10, default_retries=0)
+
+
+class BatteryUtilsInitTest(unittest.TestCase):
+
+ def testInitWithDeviceUtil(self):
+ serial = '0fedcba987654321'
+ d = device_utils.DeviceUtils(serial)
+ b = battery_utils.BatteryUtils(d)
+ self.assertEqual(d, b._device)
+
+ def testInitWithMissing_fails(self):
+ with self.assertRaises(TypeError):
+ battery_utils.BatteryUtils(None)
+ with self.assertRaises(TypeError):
+ battery_utils.BatteryUtils('')
+
+
+class BatteryUtilsSetChargingTest(BatteryUtilsTest):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testSetCharging_enabled(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(mock.ANY, check_return=True), []),
+ (self.call.battery.GetCharging(), True)):
+ self.battery.SetCharging(True)
+
+ def testSetCharging_alreadyEnabled(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, check_return=True), []),
+ (self.call.battery.GetCharging(), True)):
+ self.battery.SetCharging(True)
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testSetCharging_disabled(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, check_return=True), []),
+ (self.call.battery.GetCharging(), True),
+ (self.call.device.RunShellCommand(mock.ANY, check_return=True), []),
+ (self.call.battery.GetCharging(), False)):
+ self.battery.SetCharging(False)
+
+
+class BatteryUtilsSetBatteryMeasurementTest(BatteryUtilsTest):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testBatteryMeasurementWifi(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ mock.ANY, retries=0, single_line=True,
+ timeout=10, check_return=True), '22'),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), [])):
+ with self.battery.BatteryMeasurement():
+ pass
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testBatteryMeasurementUsb(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ mock.ANY, retries=0, single_line=True,
+ timeout=10, check_return=True), '22'),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
+ (self.call.battery.GetCharging(), True)):
+ with self.battery.BatteryMeasurement():
+ pass
+
+
+class BatteryUtilsGetPowerData(BatteryUtilsTest):
+
+ def testGetPowerData(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT)):
+ data = self.battery.GetPowerData()
+ check = {
+ 'test_package1': {'uid': '1000', 'data': [1.0]},
+ 'test_package2': {'uid': '1001', 'data': [2.0]}
+ }
+ self.assertEqual(data, check)
+
+ def testGetPowerData_packageCollisionSame(self):
+ self.battery._cache['uids'] = {'test_package1': '1000'}
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT):
+ data = self.battery.GetPowerData()
+ check = {
+ 'test_package1': {'uid': '1000', 'data': [1.0]},
+ 'test_package2': {'uid': '1001', 'data': [2.0]}
+ }
+ self.assertEqual(data, check)
+
+ def testGetPowerData_packageCollisionDifferent(self):
+ self.battery._cache['uids'] = {'test_package1': '1'}
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.battery.GetPowerData()
+
+ def testGetPowerData_cacheCleared(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT)):
+ self.battery._cache.clear()
+ data = self.battery.GetPowerData()
+ check = {
+ 'test_package1': {'uid': '1000', 'data': [1.0]},
+ 'test_package2': {'uid': '1001', 'data': [2.0]}
+ }
+ self.assertEqual(data, check)
+
+ def testGetPackagePowerData(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT)):
+ data = self.battery.GetPackagePowerData('test_package2')
+ self.assertEqual(data, {'uid': '1001', 'data': [2.0]})
+
+ def testGetPackagePowerData_badPackage(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT)):
+ data = self.battery.GetPackagePowerData('not_a_package')
+ self.assertEqual(data, None)
+
+
+class BatteryUtilsChargeDevice(BatteryUtilsTest):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testChargeDeviceToLevel(self):
+ with self.assertCalls(
+ (self.call.battery.SetCharging(True)),
+ (self.call.battery.GetBatteryInfo(), {'level': '50'}),
+ (self.call.battery.GetBatteryInfo(), {'level': '100'})):
+ self.battery.ChargeDeviceToLevel(95)
+
+
+class BatteryUtilsGetBatteryInfoTest(BatteryUtilsTest):
+
+ def testGetBatteryInfo_normal(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True),
+ [
+ 'Current Battery Service state:',
+ ' AC powered: false',
+ ' USB powered: true',
+ ' level: 100',
+ ' temperature: 321',
+ ]):
+ self.assertEquals(
+ {
+ 'AC powered': 'false',
+ 'USB powered': 'true',
+ 'level': '100',
+ 'temperature': '321',
+ },
+ self.battery.GetBatteryInfo())
+
+ def testGetBatteryInfo_nothing(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), []):
+ self.assertEquals({}, self.battery.GetBatteryInfo())
+
+
+class BatteryUtilsGetChargingTest(BatteryUtilsTest):
+
+ def testGetCharging_usb(self):
+ with self.assertCall(
+ self.call.battery.GetBatteryInfo(), {'USB powered': 'true'}):
+ self.assertTrue(self.battery.GetCharging())
+
+ def testGetCharging_usbFalse(self):
+ with self.assertCall(
+ self.call.battery.GetBatteryInfo(), {'USB powered': 'false'}):
+ self.assertFalse(self.battery.GetCharging())
+
+ def testGetCharging_ac(self):
+ with self.assertCall(
+ self.call.battery.GetBatteryInfo(), {'AC powered': 'true'}):
+ self.assertTrue(self.battery.GetCharging())
+
+ def testGetCharging_wireless(self):
+ with self.assertCall(
+ self.call.battery.GetBatteryInfo(), {'Wireless powered': 'true'}):
+ self.assertTrue(self.battery.GetCharging())
+
+ def testGetCharging_unknown(self):
+ with self.assertCall(
+ self.call.battery.GetBatteryInfo(), {'level': '42'}):
+ self.assertFalse(self.battery.GetCharging())
+
+
+class BatteryUtilsGetNetworkDataTest(BatteryUtilsTest):
+
+ def testGetNetworkData_noDataUsage(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'),
+ self.ShellError()),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'),
+ self.ShellError())):
+ self.assertEquals(self.battery.GetNetworkData('test_package1'), (0, 0))
+
+ def testGetNetworkData_badPackage(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT):
+ self.assertEqual(self.battery.GetNetworkData('asdf'), None)
+
+ def testGetNetworkData_packageNotCached(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
+ self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2))
+
+ def testGetNetworkData_packageCached(self):
+ self.battery._cache['uids'] = {'test_package1': '1000'}
+ with self.assertCalls(
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
+ self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2))
+
+ def testGetNetworkData_clearedCache(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '-c'], check_return=True),
+ _DUMPSYS_OUTPUT),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
+ (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
+ self.battery._cache.clear()
+ self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2))
+
+
+class BatteryUtilsLetBatteryCoolToTemperatureTest(BatteryUtilsTest):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testLetBatteryCoolToTemperature_startUnder(self):
+ with self.assertCalls(
+ (self.call.battery.EnableBatteryUpdates(), []),
+ (self.call.battery.GetBatteryInfo(), {'temperature': '500'})):
+ self.battery.LetBatteryCoolToTemperature(600)
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testLetBatteryCoolToTemperature_startOver(self):
+ with self.assertCalls(
+ (self.call.battery.EnableBatteryUpdates(), []),
+ (self.call.battery.GetBatteryInfo(), {'temperature': '500'}),
+ (self.call.battery.GetBatteryInfo(), {'temperature': '400'})):
+ self.battery.LetBatteryCoolToTemperature(400)
+
+class BatteryUtilsSupportsFuelGaugeTest(BatteryUtilsTest):
+
+ def testSupportsFuelGauge_false(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ self.assertFalse(self.battery.SupportsFuelGauge())
+
+ def testSupportsFuelGauge_trueMax(self):
+ self.battery._cache['profile'] = self._NEXUS_6
+ # TODO(rnephew): Change this to assertTrue when we have support for
+ # disabling hardware charging on nexus 6.
+ self.assertFalse(self.battery.SupportsFuelGauge())
+
+ def testSupportsFuelGauge_trueDS(self):
+ self.battery._cache['profile'] = self._NEXUS_10
+ # TODO(rnephew): Change this to assertTrue when we have support for
+ # disabling hardware charging on nexus 10.
+ self.assertFalse(self.battery.SupportsFuelGauge())
+
+
+class BatteryUtilsGetFuelGaugeChargeCounterTest(BatteryUtilsTest):
+
+ def testGetFuelGaugeChargeCounter_noFuelGauge(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.battery.GetFuelGaugeChargeCounter()
+
+ def testGetFuelGaugeChargeCounter_fuelGaugePresent(self):
+ self.battery._cache['profile']= self._NEXUS_6
+ with self.assertCalls(
+ (self.call.battery.SupportsFuelGauge(), True),
+ (self.call.device.ReadFile(mock.ANY), '123')):
+ self.assertEqual(self.battery.GetFuelGaugeChargeCounter(), 123)
+
+
+class BatteryUtilsTieredSetCharging(BatteryUtilsTest):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testTieredSetCharging_softwareSetTrue(self):
+ self.battery._cache['profile'] = self._NEXUS_6
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
+ (self.call.battery.GetCharging(), True)):
+ self.battery.TieredSetCharging(True)
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testTieredSetCharging_softwareSetFalse(self):
+ self.battery._cache['profile'] = self._NEXUS_6
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
+ (self.call.battery.GetCharging(), False)):
+ self.battery.TieredSetCharging(False)
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testTieredSetCharging_hardwareSetTrue(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), False),
+ (self.call.battery.SetCharging(True))):
+ self.battery.TieredSetCharging(True)
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testTieredSetCharging_hardwareSetFalse(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.battery.SetCharging(False))):
+ self.battery.TieredSetCharging(False)
+
+ def testTieredSetCharging_expectedStateAlreadyTrue(self):
+ with self.assertCalls((self.call.battery.GetCharging(), True)):
+ self.battery.TieredSetCharging(True)
+
+ def testTieredSetCharging_expectedStateAlreadyFalse(self):
+ with self.assertCalls((self.call.battery.GetCharging(), False)):
+ self.battery.TieredSetCharging(False)
+
+
+class BatteryUtilsPowerMeasurement(BatteryUtilsTest):
+
+ def testPowerMeasurement_hardware(self):
+ self.battery._cache['profile'] = self._NEXUS_5
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.battery.SetCharging(False)),
+ (self.call.battery.GetCharging(), False),
+ (self.call.battery.SetCharging(True))):
+ with self.battery.PowerMeasurement():
+ pass
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testPowerMeasurement_software(self):
+ self.battery._cache['profile'] = self._NEXUS_6
+ with self.assertCalls(
+ (self.call.battery.GetCharging(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.battery._ClearPowerData(), True),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), []),
+ (self.call.battery.GetCharging(), False),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
+ (self.call.battery.GetCharging(), True)):
+ with self.battery.PowerMeasurement():
+ pass
+
+
+class BatteryUtilsDiscoverDeviceProfile(BatteryUtilsTest):
+
+ def testDiscoverDeviceProfile_known(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.product.model'), "Nexus 4")):
+ self.battery._DiscoverDeviceProfile()
+ self.assertEqual(self.battery._cache['profile']['name'], "Nexus 4")
+
+ def testDiscoverDeviceProfile_unknown(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.product.model'), "Other")):
+ self.battery._DiscoverDeviceProfile()
+ self.assertEqual(self.battery._cache['profile']['name'], None)
+
+
+class BatteryUtilsClearPowerData(BatteryUtilsTest):
+
+ def testClearPowerData_preL(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, retries=0,
+ single_line=True, timeout=10, check_return=True), '20')):
+ self.assertFalse(self.battery._ClearPowerData())
+
+ def testClearPowerData_clearedL(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, retries=0,
+ single_line=True, timeout=10, check_return=True), '22'),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--charged', '--checkin'],
+ check_return=True, large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), [])):
+ self.assertTrue(self.battery._ClearPowerData())
+
+ def testClearPowerData_notClearedL(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(mock.ANY, retries=0,
+ single_line=True, timeout=10, check_return=True), '22'),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'batterystats', '--charged', '--checkin'],
+ check_return=True, large_output=True),
+ ['9,1000,l,pwi,uid,0.0327']),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'battery', 'reset'], check_return=True), [])):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.battery._ClearPowerData()
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/build/android/pylib/device/commands/BUILD.gn b/build/android/pylib/device/commands/BUILD.gn
new file mode 100644
index 00000000000..66e1010142a
--- /dev/null
+++ b/build/android/pylib/device/commands/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2014 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.
+
+import("//build/config/android/rules.gni")
+
+group("commands") {
+ datadeps = [
+ ":chromium_commands",
+ ]
+}
+
+# GYP: //build/android/pylib/device/commands/commands.gyp:chromium_commands
+android_library("chromium_commands") {
+ java_files = [ "java/src/org/chromium/android/commands/unzip/Unzip.java" ]
+ dex_path = "$root_build_dir/lib.java/chromium_commands.dex.jar"
+}
diff --git a/build/android/pylib/device/commands/__init__.py b/build/android/pylib/device/commands/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/device/commands/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/device/commands/commands.gyp b/build/android/pylib/device/commands/commands.gyp
new file mode 100644
index 00000000000..b5b5bc86043
--- /dev/null
+++ b/build/android/pylib/device/commands/commands.gyp
@@ -0,0 +1,20 @@
+# Copyright 2014 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.
+
+{
+ 'targets': [
+ {
+ # GN version: //build/android/pylib/devices/commands:chromium_commands
+ 'target_name': 'chromium_commands',
+ 'type': 'none',
+ 'variables': {
+ 'add_to_dependents_classpaths': 0,
+ 'java_in_dir': ['java'],
+ },
+ 'includes': [
+ '../../../../../build/java.gypi',
+ ],
+ }
+ ],
+}
diff --git a/build/android/pylib/device/commands/install_commands.py b/build/android/pylib/device/commands/install_commands.py
new file mode 100644
index 00000000000..58c56ccb1be
--- /dev/null
+++ b/build/android/pylib/device/commands/install_commands.py
@@ -0,0 +1,51 @@
+# Copyright 2014 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.
+
+import os
+
+from pylib import constants
+
+BIN_DIR = '%s/bin' % constants.TEST_EXECUTABLE_DIR
+_FRAMEWORK_DIR = '%s/framework' % constants.TEST_EXECUTABLE_DIR
+
+_COMMANDS = {
+ 'unzip': 'org.chromium.android.commands.unzip.Unzip',
+}
+
+_SHELL_COMMAND_FORMAT = (
+"""#!/system/bin/sh
+base=%s
+export CLASSPATH=$base/framework/chromium_commands.jar
+exec app_process $base/bin %s $@
+""")
+
+
+def Installed(device):
+ return (all(device.FileExists('%s/%s' % (BIN_DIR, c)) for c in _COMMANDS)
+ and device.FileExists('%s/chromium_commands.jar' % _FRAMEWORK_DIR))
+
+def InstallCommands(device):
+ if device.IsUserBuild():
+ raise Exception('chromium_commands currently requires a userdebug build.')
+
+ chromium_commands_jar_path = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_JAVALIB_DIR,
+ 'chromium_commands.dex.jar')
+ if not os.path.exists(chromium_commands_jar_path):
+ raise Exception('%s not found. Please build chromium_commands.'
+ % chromium_commands_jar_path)
+
+ device.RunShellCommand(['mkdir', BIN_DIR, _FRAMEWORK_DIR])
+ for command, main_class in _COMMANDS.iteritems():
+ shell_command = _SHELL_COMMAND_FORMAT % (
+ constants.TEST_EXECUTABLE_DIR, main_class)
+ shell_file = '%s/%s' % (BIN_DIR, command)
+ device.WriteFile(shell_file, shell_command)
+ device.RunShellCommand(
+ ['chmod', '755', shell_file], check_return=True)
+
+ device.adb.Push(
+ chromium_commands_jar_path,
+ '%s/chromium_commands.jar' % _FRAMEWORK_DIR)
+
diff --git a/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java b/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java
new file mode 100644
index 00000000000..7cbbb732ced
--- /dev/null
+++ b/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java
@@ -0,0 +1,95 @@
+// Copyright 2014 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.
+
+package org.chromium.android.commands.unzip;
+
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Minimal implementation of the command-line unzip utility for Android.
+ */
+public class Unzip {
+
+ private static final String TAG = "Unzip";
+
+ public static void main(String[] args) {
+ try {
+ (new Unzip()).run(args);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.toString());
+ System.exit(1);
+ }
+ }
+
+ private void showUsage(PrintStream s) {
+ s.println("Usage:");
+ s.println("unzip [zipfile]");
+ }
+
+ @SuppressWarnings("Finally")
+ private void unzip(String[] args) {
+ ZipInputStream zis = null;
+ try {
+ String zipfile = args[0];
+ zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipfile)));
+ ZipEntry ze = null;
+
+ byte[] bytes = new byte[1024];
+ while ((ze = zis.getNextEntry()) != null) {
+ File outputFile = new File(ze.getName());
+ if (ze.isDirectory()) {
+ if (!outputFile.exists() && !outputFile.mkdirs()) {
+ throw new RuntimeException(
+ "Failed to create directory: " + outputFile.toString());
+ }
+ } else {
+ File parentDir = outputFile.getParentFile();
+ if (!parentDir.exists() && !parentDir.mkdirs()) {
+ throw new RuntimeException(
+ "Failed to create directory: " + parentDir.toString());
+ }
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFile));
+ int actual_bytes = 0;
+ int total_bytes = 0;
+ while ((actual_bytes = zis.read(bytes)) != -1) {
+ out.write(bytes, 0, actual_bytes);
+ total_bytes += actual_bytes;
+ }
+ out.close();
+ }
+ zis.closeEntry();
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException("Error while unzipping: " + e.toString());
+ } finally {
+ try {
+ if (zis != null) zis.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Error while closing zip: " + e.toString());
+ }
+ }
+ }
+
+ public void run(String[] args) {
+ if (args.length != 1) {
+ showUsage(System.err);
+ throw new RuntimeException("Incorrect usage.");
+ }
+
+ unzip(args);
+ }
+}
+
diff --git a/build/android/pylib/device/decorators.py b/build/android/pylib/device/decorators.py
new file mode 100644
index 00000000000..73c13da8b09
--- /dev/null
+++ b/build/android/pylib/device/decorators.py
@@ -0,0 +1,157 @@
+# Copyright 2014 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.
+
+"""
+Function/method decorators that provide timeout and retry logic.
+"""
+
+import functools
+import os
+import sys
+import threading
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.device import device_errors
+from pylib.utils import reraiser_thread
+from pylib.utils import timeout_retry
+
+# TODO(jbudorick) Remove once the DeviceUtils implementations are no longer
+# backed by AndroidCommands / android_testrunner.
+sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'third_party',
+ 'android_testrunner'))
+import errors as old_errors
+
+DEFAULT_TIMEOUT_ATTR = '_default_timeout'
+DEFAULT_RETRIES_ATTR = '_default_retries'
+
+
+def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False):
+ """ Wraps a funcion with timeout and retry handling logic.
+
+ Args:
+ f: The function to wrap.
+ timeout_func: A callable that returns the timeout value.
+ retries_func: A callable that returns the retries value.
+ pass_values: If True, passes the values returned by |timeout_func| and
+ |retries_func| to the wrapped function as 'timeout' and
+ 'retries' kwargs, respectively.
+ Returns:
+ The wrapped function.
+ """
+ @functools.wraps(f)
+ def TimeoutRetryWrapper(*args, **kwargs):
+ timeout = timeout_func(*args, **kwargs)
+ retries = retries_func(*args, **kwargs)
+ if pass_values:
+ kwargs['timeout'] = timeout
+ kwargs['retries'] = retries
+ def impl():
+ return f(*args, **kwargs)
+ try:
+ if isinstance(threading.current_thread(),
+ timeout_retry.TimeoutRetryThread):
+ return impl()
+ else:
+ return timeout_retry.Run(impl, timeout, retries)
+ except old_errors.WaitForResponseTimedOutError as e:
+ raise device_errors.CommandTimeoutError(str(e)), None, (
+ sys.exc_info()[2])
+ except old_errors.DeviceUnresponsiveError as e:
+ raise device_errors.DeviceUnreachableError(str(e)), None, (
+ sys.exc_info()[2])
+ except reraiser_thread.TimeoutError as e:
+ raise device_errors.CommandTimeoutError(str(e)), None, (
+ sys.exc_info()[2])
+ except cmd_helper.TimeoutError as e:
+ raise device_errors.CommandTimeoutError(str(e)), None, (
+ sys.exc_info()[2])
+ return TimeoutRetryWrapper
+
+
+def WithTimeoutAndRetries(f):
+ """A decorator that handles timeouts and retries.
+
+ 'timeout' and 'retries' kwargs must be passed to the function.
+
+ Args:
+ f: The function to decorate.
+ Returns:
+ The decorated function.
+ """
+ get_timeout = lambda *a, **kw: kw['timeout']
+ get_retries = lambda *a, **kw: kw['retries']
+ return _TimeoutRetryWrapper(f, get_timeout, get_retries)
+
+
+def WithExplicitTimeoutAndRetries(timeout, retries):
+ """Returns a decorator that handles timeouts and retries.
+
+ The provided |timeout| and |retries| values are always used.
+
+ Args:
+ timeout: The number of seconds to wait for the decorated function to
+ return. Always used.
+ retries: The number of times the decorated function should be retried on
+ failure. Always used.
+ Returns:
+ The actual decorator.
+ """
+ def decorator(f):
+ get_timeout = lambda *a, **kw: timeout
+ get_retries = lambda *a, **kw: retries
+ return _TimeoutRetryWrapper(f, get_timeout, get_retries)
+ return decorator
+
+
+def WithTimeoutAndRetriesDefaults(default_timeout, default_retries):
+ """Returns a decorator that handles timeouts and retries.
+
+ The provided |default_timeout| and |default_retries| values are used only
+ if timeout and retries values are not provided.
+
+ Args:
+ default_timeout: The number of seconds to wait for the decorated function
+ to return. Only used if a 'timeout' kwarg is not passed
+ to the decorated function.
+ default_retries: The number of times the decorated function should be
+ retried on failure. Only used if a 'retries' kwarg is not
+ passed to the decorated function.
+ Returns:
+ The actual decorator.
+ """
+ def decorator(f):
+ get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout)
+ get_retries = lambda *a, **kw: kw.get('retries', default_retries)
+ return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
+ return decorator
+
+
+def WithTimeoutAndRetriesFromInstance(
+ default_timeout_name=DEFAULT_TIMEOUT_ATTR,
+ default_retries_name=DEFAULT_RETRIES_ATTR):
+ """Returns a decorator that handles timeouts and retries.
+
+ The provided |default_timeout_name| and |default_retries_name| are used to
+ get the default timeout value and the default retries value from the object
+ instance if timeout and retries values are not provided.
+
+ Note that this should only be used to decorate methods, not functions.
+
+ Args:
+ default_timeout_name: The name of the default timeout attribute of the
+ instance.
+ default_retries_name: The name of the default retries attribute of the
+ instance.
+ Returns:
+ The actual decorator.
+ """
+ def decorator(f):
+ def get_timeout(inst, *_args, **kwargs):
+ return kwargs.get('timeout', getattr(inst, default_timeout_name))
+ def get_retries(inst, *_args, **kwargs):
+ return kwargs.get('retries', getattr(inst, default_retries_name))
+ return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
+ return decorator
+
diff --git a/build/android/pylib/device/decorators_test.py b/build/android/pylib/device/decorators_test.py
new file mode 100644
index 00000000000..b75618b09e6
--- /dev/null
+++ b/build/android/pylib/device/decorators_test.py
@@ -0,0 +1,365 @@
+# Copyright 2014 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.
+
+"""
+Unit tests for decorators.py.
+"""
+
+# pylint: disable=W0613
+
+import os
+import sys
+import time
+import traceback
+import unittest
+
+from pylib import constants
+from pylib.device import decorators
+from pylib.device import device_errors
+from pylib.utils import reraiser_thread
+
+# TODO(jbudorick) Remove once the DeviceUtils implementations are no longer
+# backed by AndroidCommands / android_testrunner.
+sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'third_party',
+ 'android_testrunner'))
+import errors as old_errors
+
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 3
+
+class DecoratorsTest(unittest.TestCase):
+ _decorated_function_called_count = 0
+
+ def testFunctionDecoratorDoesTimeouts(self):
+ """Tests that the base decorator handles the timeout logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithTimeoutAndRetries
+ def alwaysTimesOut(timeout=None, retries=None):
+ DecoratorsTest._decorated_function_called_count += 1
+ time.sleep(100)
+
+ start_time = time.time()
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ alwaysTimesOut(timeout=1, retries=0)
+ elapsed_time = time.time() - start_time
+ self.assertTrue(elapsed_time >= 1)
+ self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
+
+ def testFunctionDecoratorDoesRetries(self):
+ """Tests that the base decorator handles the retries logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithTimeoutAndRetries
+ def alwaysRaisesCommandFailedError(timeout=None, retries=None):
+ DecoratorsTest._decorated_function_called_count += 1
+ raise device_errors.CommandFailedError('testCommand failed')
+
+ with self.assertRaises(device_errors.CommandFailedError):
+ alwaysRaisesCommandFailedError(timeout=30, retries=10)
+ self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
+
+ def testFunctionDecoratorRequiresParams(self):
+ """Tests that the base decorator requires timeout and retries params."""
+ @decorators.WithTimeoutAndRetries
+ def requiresExplicitTimeoutAndRetries(timeout=None, retries=None):
+ return (timeout, retries)
+
+ with self.assertRaises(KeyError):
+ requiresExplicitTimeoutAndRetries()
+ with self.assertRaises(KeyError):
+ requiresExplicitTimeoutAndRetries(timeout=10)
+ with self.assertRaises(KeyError):
+ requiresExplicitTimeoutAndRetries(retries=0)
+ expected_timeout = 10
+ expected_retries = 1
+ (actual_timeout, actual_retries) = (
+ requiresExplicitTimeoutAndRetries(timeout=expected_timeout,
+ retries=expected_retries))
+ self.assertEquals(expected_timeout, actual_timeout)
+ self.assertEquals(expected_retries, actual_retries)
+
+ def testFunctionDecoratorTranslatesOldExceptions(self):
+ """Tests that the explicit decorator translates old exceptions."""
+ @decorators.WithTimeoutAndRetries
+ def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
+ raise exception
+
+ exception_desc = 'Old response timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.WaitForResponseTimedOutError(exception_desc),
+ timeout=10, retries=1)
+ self.assertEquals(exception_desc, str(e.exception))
+
+ exception_desc = 'Old device error'
+ with self.assertRaises(device_errors.DeviceUnreachableError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.DeviceUnresponsiveError(exception_desc),
+ timeout=10, retries=1)
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testFunctionDecoratorTranslatesReraiserExceptions(self):
+ """Tests that the explicit decorator translates reraiser exceptions."""
+ @decorators.WithTimeoutAndRetries
+ def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
+ raise exception
+
+ exception_desc = 'Reraiser thread timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ reraiser_thread.TimeoutError(exception_desc),
+ timeout=10, retries=1)
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testDefaultsFunctionDecoratorDoesTimeouts(self):
+ """Tests that the defaults decorator handles timeout logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithTimeoutAndRetriesDefaults(1, 0)
+ def alwaysTimesOut(timeout=None, retries=None):
+ DecoratorsTest._decorated_function_called_count += 1
+ time.sleep(100)
+
+ start_time = time.time()
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ alwaysTimesOut()
+ elapsed_time = time.time() - start_time
+ self.assertTrue(elapsed_time >= 1)
+ self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
+
+ DecoratorsTest._decorated_function_called_count = 0
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ alwaysTimesOut(timeout=2)
+ elapsed_time = time.time() - start_time
+ self.assertTrue(elapsed_time >= 2)
+ self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
+
+ def testDefaultsFunctionDecoratorDoesRetries(self):
+ """Tests that the defaults decorator handles retries logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithTimeoutAndRetriesDefaults(30, 10)
+ def alwaysRaisesCommandFailedError(timeout=None, retries=None):
+ DecoratorsTest._decorated_function_called_count += 1
+ raise device_errors.CommandFailedError('testCommand failed')
+
+ with self.assertRaises(device_errors.CommandFailedError):
+ alwaysRaisesCommandFailedError()
+ self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
+
+ DecoratorsTest._decorated_function_called_count = 0
+ with self.assertRaises(device_errors.CommandFailedError):
+ alwaysRaisesCommandFailedError(retries=5)
+ self.assertEquals(6, DecoratorsTest._decorated_function_called_count)
+
+ def testDefaultsFunctionDecoratorPassesValues(self):
+ """Tests that the defaults decorator passes timeout and retries kwargs."""
+ @decorators.WithTimeoutAndRetriesDefaults(30, 10)
+ def alwaysReturnsTimeouts(timeout=None, retries=None):
+ return timeout
+
+ self.assertEquals(30, alwaysReturnsTimeouts())
+ self.assertEquals(120, alwaysReturnsTimeouts(timeout=120))
+
+ @decorators.WithTimeoutAndRetriesDefaults(30, 10)
+ def alwaysReturnsRetries(timeout=None, retries=None):
+ return retries
+
+ self.assertEquals(10, alwaysReturnsRetries())
+ self.assertEquals(1, alwaysReturnsRetries(retries=1))
+
+ def testDefaultsFunctionDecoratorTranslatesOldExceptions(self):
+ """Tests that the explicit decorator translates old exceptions."""
+ @decorators.WithTimeoutAndRetriesDefaults(30, 10)
+ def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
+ raise exception
+
+ exception_desc = 'Old response timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.WaitForResponseTimedOutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ exception_desc = 'Old device error'
+ with self.assertRaises(device_errors.DeviceUnreachableError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.DeviceUnresponsiveError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testDefaultsFunctionDecoratorTranslatesReraiserExceptions(self):
+ """Tests that the explicit decorator translates reraiser exceptions."""
+ @decorators.WithTimeoutAndRetriesDefaults(30, 10)
+ def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
+ raise exception
+
+ exception_desc = 'Reraiser thread timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ reraiser_thread.TimeoutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testExplicitFunctionDecoratorDoesTimeouts(self):
+ """Tests that the explicit decorator handles timeout logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithExplicitTimeoutAndRetries(1, 0)
+ def alwaysTimesOut():
+ DecoratorsTest._decorated_function_called_count += 1
+ time.sleep(100)
+
+ start_time = time.time()
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ alwaysTimesOut()
+ elapsed_time = time.time() - start_time
+ self.assertTrue(elapsed_time >= 1)
+ self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
+
+ def testExplicitFunctionDecoratorDoesRetries(self):
+ """Tests that the explicit decorator handles retries logic."""
+ DecoratorsTest._decorated_function_called_count = 0
+ @decorators.WithExplicitTimeoutAndRetries(30, 10)
+ def alwaysRaisesCommandFailedError():
+ DecoratorsTest._decorated_function_called_count += 1
+ raise device_errors.CommandFailedError('testCommand failed')
+
+ with self.assertRaises(device_errors.CommandFailedError):
+ alwaysRaisesCommandFailedError()
+ self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
+
+ def testExplicitDecoratorTranslatesOldExceptions(self):
+ """Tests that the explicit decorator translates old exceptions."""
+ @decorators.WithExplicitTimeoutAndRetries(30, 10)
+ def alwaysRaisesProvidedException(exception):
+ raise exception
+
+ exception_desc = 'Old response timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.WaitForResponseTimedOutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ exception_desc = 'Old device error'
+ with self.assertRaises(device_errors.DeviceUnreachableError) as e:
+ alwaysRaisesProvidedException(
+ old_errors.DeviceUnresponsiveError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testExplicitDecoratorTranslatesReraiserExceptions(self):
+ """Tests that the explicit decorator translates reraiser exceptions."""
+ @decorators.WithExplicitTimeoutAndRetries(30, 10)
+ def alwaysRaisesProvidedException(exception):
+ raise exception
+
+ exception_desc = 'Reraiser thread timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ alwaysRaisesProvidedException(
+ reraiser_thread.TimeoutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ class _MethodDecoratorTestObject(object):
+ """An object suitable for testing the method decorator."""
+
+ def __init__(self, test_case, default_timeout=_DEFAULT_TIMEOUT,
+ default_retries=_DEFAULT_RETRIES):
+ self._test_case = test_case
+ self.default_timeout = default_timeout
+ self.default_retries = default_retries
+ self.function_call_counters = {
+ 'alwaysRaisesCommandFailedError': 0,
+ 'alwaysTimesOut': 0,
+ 'requiresExplicitTimeoutAndRetries': 0,
+ }
+
+ @decorators.WithTimeoutAndRetriesFromInstance(
+ 'default_timeout', 'default_retries')
+ def alwaysTimesOut(self, timeout=None, retries=None):
+ self.function_call_counters['alwaysTimesOut'] += 1
+ time.sleep(100)
+ self._test_case.assertFalse(True, msg='Failed to time out?')
+
+ @decorators.WithTimeoutAndRetriesFromInstance(
+ 'default_timeout', 'default_retries')
+ def alwaysRaisesCommandFailedError(self, timeout=None, retries=None):
+ self.function_call_counters['alwaysRaisesCommandFailedError'] += 1
+ raise device_errors.CommandFailedError('testCommand failed')
+
+ # pylint: disable=no-self-use
+
+ @decorators.WithTimeoutAndRetriesFromInstance(
+ 'default_timeout', 'default_retries')
+ def alwaysReturnsTimeout(self, timeout=None, retries=None):
+ return timeout
+
+ @decorators.WithTimeoutAndRetriesFromInstance(
+ 'default_timeout', 'default_retries')
+ def alwaysReturnsRetries(self, timeout=None, retries=None):
+ return retries
+
+ @decorators.WithTimeoutAndRetriesFromInstance(
+ 'default_timeout', 'default_retries')
+ def alwaysRaisesProvidedException(self, exception, timeout=None,
+ retries=None):
+ raise exception
+
+ # pylint: enable=no-self-use
+
+
+ def testMethodDecoratorDoesTimeout(self):
+ """Tests that the method decorator handles timeout logic."""
+ test_obj = self._MethodDecoratorTestObject(self)
+ start_time = time.time()
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ try:
+ test_obj.alwaysTimesOut(timeout=1, retries=0)
+ except:
+ traceback.print_exc()
+ raise
+ elapsed_time = time.time() - start_time
+ self.assertTrue(elapsed_time >= 1)
+ self.assertEquals(1, test_obj.function_call_counters['alwaysTimesOut'])
+
+ def testMethodDecoratorDoesRetries(self):
+ """Tests that the method decorator handles retries logic."""
+ test_obj = self._MethodDecoratorTestObject(self)
+ with self.assertRaises(device_errors.CommandFailedError):
+ try:
+ test_obj.alwaysRaisesCommandFailedError(retries=10)
+ except:
+ traceback.print_exc()
+ raise
+ self.assertEquals(
+ 11, test_obj.function_call_counters['alwaysRaisesCommandFailedError'])
+
+ def testMethodDecoratorPassesValues(self):
+ """Tests that the method decorator passes timeout and retries kwargs."""
+ test_obj = self._MethodDecoratorTestObject(
+ self, default_timeout=42, default_retries=31)
+ self.assertEquals(42, test_obj.alwaysReturnsTimeout())
+ self.assertEquals(41, test_obj.alwaysReturnsTimeout(timeout=41))
+ self.assertEquals(31, test_obj.alwaysReturnsRetries())
+ self.assertEquals(32, test_obj.alwaysReturnsRetries(retries=32))
+
+ def testMethodDecoratorTranslatesOldExceptions(self):
+ test_obj = self._MethodDecoratorTestObject(self)
+
+ exception_desc = 'Old response timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ test_obj.alwaysRaisesProvidedException(
+ old_errors.WaitForResponseTimedOutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ exception_desc = 'Old device error'
+ with self.assertRaises(device_errors.DeviceUnreachableError) as e:
+ test_obj.alwaysRaisesProvidedException(
+ old_errors.DeviceUnresponsiveError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+ def testMethodDecoratorTranslatesReraiserExceptions(self):
+ test_obj = self._MethodDecoratorTestObject(self)
+
+ exception_desc = 'Reraiser thread timeout error'
+ with self.assertRaises(device_errors.CommandTimeoutError) as e:
+ test_obj.alwaysRaisesProvidedException(
+ reraiser_thread.TimeoutError(exception_desc))
+ self.assertEquals(exception_desc, str(e.exception))
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/device/device_blacklist.py b/build/android/pylib/device/device_blacklist.py
new file mode 100644
index 00000000000..a141d62b81b
--- /dev/null
+++ b/build/android/pylib/device/device_blacklist.py
@@ -0,0 +1,61 @@
+# Copyright 2014 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.
+
+import json
+import os
+import threading
+
+from pylib import constants
+_BLACKLIST_JSON = os.path.join(
+ constants.DIR_SOURCE_ROOT,
+ os.environ.get('CHROMIUM_OUT_DIR', 'out'),
+ 'bad_devices.json')
+
+# Note that this only protects against concurrent accesses to the blacklist
+# within a process.
+_blacklist_lock = threading.RLock()
+
+def ReadBlacklist():
+ """Reads the blacklist from the _BLACKLIST_JSON file.
+
+ Returns:
+ A list containing bad devices.
+ """
+ with _blacklist_lock:
+ if not os.path.exists(_BLACKLIST_JSON):
+ return []
+
+ with open(_BLACKLIST_JSON, 'r') as f:
+ return json.load(f)
+
+
+def WriteBlacklist(blacklist):
+ """Writes the provided blacklist to the _BLACKLIST_JSON file.
+
+ Args:
+ blacklist: list of bad devices to write to the _BLACKLIST_JSON file.
+ """
+ with _blacklist_lock:
+ with open(_BLACKLIST_JSON, 'w') as f:
+ json.dump(list(set(blacklist)), f)
+
+
+def ExtendBlacklist(devices):
+ """Adds devices to _BLACKLIST_JSON file.
+
+ Args:
+ devices: list of bad devices to be added to the _BLACKLIST_JSON file.
+ """
+ with _blacklist_lock:
+ blacklist = ReadBlacklist()
+ blacklist.extend(devices)
+ WriteBlacklist(blacklist)
+
+
+def ResetBlacklist():
+ """Erases the _BLACKLIST_JSON file if it exists."""
+ with _blacklist_lock:
+ if os.path.exists(_BLACKLIST_JSON):
+ os.remove(_BLACKLIST_JSON)
+
diff --git a/build/android/pylib/device/device_errors.py b/build/android/pylib/device/device_errors.py
new file mode 100644
index 00000000000..249201504da
--- /dev/null
+++ b/build/android/pylib/device/device_errors.py
@@ -0,0 +1,89 @@
+# Copyright 2014 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.
+
+"""
+Exception classes raised by AdbWrapper and DeviceUtils.
+"""
+
+from pylib import cmd_helper
+from pylib.utils import base_error
+
+
+class CommandFailedError(base_error.BaseError):
+ """Exception for command failures."""
+
+ def __init__(self, message, device_serial=None):
+ if device_serial is not None:
+ message = '(device: %s) %s' % (device_serial, message)
+ self.device_serial = device_serial
+ super(CommandFailedError, self).__init__(message)
+
+
+class AdbCommandFailedError(CommandFailedError):
+ """Exception for adb command failures."""
+
+ def __init__(self, args, output, status=None, device_serial=None,
+ message=None):
+ self.args = args
+ self.output = output
+ self.status = status
+ if not message:
+ adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in self.args)
+ message = ['adb %s: failed ' % adb_cmd]
+ if status:
+ message.append('with exit status %s ' % self.status)
+ if output:
+ message.append('and output:\n')
+ message.extend('- %s\n' % line for line in output.splitlines())
+ else:
+ message.append('and no output.')
+ message = ''.join(message)
+ super(AdbCommandFailedError, self).__init__(message, device_serial)
+
+
+class DeviceVersionError(CommandFailedError):
+ """Exception for device version failures."""
+
+ def __init__(self, message, device_serial=None):
+ super(DeviceVersionError, self).__init__(message, device_serial)
+
+
+class AdbShellCommandFailedError(AdbCommandFailedError):
+ """Exception for shell command failures run via adb."""
+
+ def __init__(self, command, output, status, device_serial=None):
+ self.command = command
+ message = ['shell command run via adb failed on the device:\n',
+ ' command: %s\n' % command]
+ message.append(' exit status: %s\n' % status)
+ if output:
+ message.append(' output:\n')
+ if isinstance(output, basestring):
+ output_lines = output.splitlines()
+ else:
+ output_lines = output
+ message.extend(' - %s\n' % line for line in output_lines)
+ else:
+ message.append(" output: ''\n")
+ message = ''.join(message)
+ super(AdbShellCommandFailedError, self).__init__(
+ ['shell', command], output, status, device_serial, message)
+
+
+class CommandTimeoutError(base_error.BaseError):
+ """Exception for command timeouts."""
+ pass
+
+
+class DeviceUnreachableError(base_error.BaseError):
+ """Exception for device unreachable failures."""
+ pass
+
+
+class NoDevicesError(base_error.BaseError):
+ """Exception for having no devices attached."""
+
+ def __init__(self):
+ super(NoDevicesError, self).__init__(
+ 'No devices attached.', is_infra_error=True)
diff --git a/build/android/pylib/device/device_list.py b/build/android/pylib/device/device_list.py
new file mode 100644
index 00000000000..0eb6acba2b2
--- /dev/null
+++ b/build/android/pylib/device/device_list.py
@@ -0,0 +1,30 @@
+# Copyright 2014 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.
+
+"""A module to keep track of devices across builds."""
+
+import os
+
+LAST_DEVICES_FILENAME = '.last_devices'
+LAST_MISSING_DEVICES_FILENAME = '.last_missing'
+
+
+def GetPersistentDeviceList(file_name):
+ """Returns a list of devices.
+
+ Args:
+ file_name: the file name containing a list of devices.
+
+ Returns: List of device serial numbers that were on the bot.
+ """
+ with open(file_name) as f:
+ return f.read().splitlines()
+
+
+def WritePersistentDeviceList(file_name, device_list):
+ path = os.path.dirname(file_name)
+ if not os.path.exists(path):
+ os.makedirs(path)
+ with open(file_name, 'w') as f:
+ f.write('\n'.join(set(device_list)))
diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py
new file mode 100644
index 00000000000..f201ef36b8b
--- /dev/null
+++ b/build/android/pylib/device/device_utils.py
@@ -0,0 +1,1754 @@
+# Copyright 2014 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.
+
+"""Provides a variety of device interactions based on adb.
+
+Eventually, this will be based on adb_wrapper.
+"""
+# pylint: disable=unused-argument
+
+import collections
+import contextlib
+import itertools
+import logging
+import multiprocessing
+import os
+import posixpath
+import re
+import shutil
+import sys
+import tempfile
+import time
+import zipfile
+
+import pylib.android_commands
+from pylib import cmd_helper
+from pylib import constants
+from pylib import device_signal
+from pylib.constants import keyevent
+from pylib.device import adb_wrapper
+from pylib.device import decorators
+from pylib.device import device_blacklist
+from pylib.device import device_errors
+from pylib.device import intent
+from pylib.device import logcat_monitor
+from pylib.device.commands import install_commands
+from pylib.sdk import split_select
+from pylib.utils import apk_helper
+from pylib.utils import base_error
+from pylib.utils import device_temp_file
+from pylib.utils import host_utils
+from pylib.utils import md5sum
+from pylib.utils import parallelizer
+from pylib.utils import timeout_retry
+from pylib.utils import zip_utils
+
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 3
+
+# A sentinel object for default values
+# TODO(jbudorick,perezju): revisit how default values are handled by
+# the timeout_retry decorators.
+DEFAULT = object()
+
+_CONTROL_CHARGING_COMMANDS = [
+ {
+ # Nexus 4
+ 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
+ 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
+ 'disable_command':
+ 'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
+ },
+ {
+ # Nexus 5
+ # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
+ # energy coming from USB. Setting the power_supply offline just updates the
+ # Android system to reflect that.
+ 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
+ 'enable_command': (
+ 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'echo 1 > /sys/class/power_supply/usb/online'),
+ 'disable_command': (
+ 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
+ 'chmod 644 /sys/class/power_supply/usb/online && '
+ 'echo 0 > /sys/class/power_supply/usb/online'),
+ },
+]
+
+
+@decorators.WithExplicitTimeoutAndRetries(
+ _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
+def GetAVDs():
+ """Returns a list of Android Virtual Devices.
+
+ Returns:
+ A list containing the configured AVDs.
+ """
+ lines = cmd_helper.GetCmdOutput([
+ os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android'),
+ 'list', 'avd']).splitlines()
+ avds = []
+ for line in lines:
+ if 'Name:' not in line:
+ continue
+ key, value = (s.strip() for s in line.split(':', 1))
+ if key == 'Name':
+ avds.append(value)
+ return avds
+
+
+@decorators.WithExplicitTimeoutAndRetries(
+ _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
+def RestartServer():
+ """Restarts the adb server.
+
+ Raises:
+ CommandFailedError if we fail to kill or restart the server.
+ """
+ def adb_killed():
+ return not adb_wrapper.AdbWrapper.IsServerOnline()
+
+ def adb_started():
+ return adb_wrapper.AdbWrapper.IsServerOnline()
+
+ adb_wrapper.AdbWrapper.KillServer()
+ if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5):
+ # TODO(perezju): raise an exception after fixng http://crbug.com/442319
+ logging.warning('Failed to kill adb server')
+ adb_wrapper.AdbWrapper.StartServer()
+ if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5):
+ raise device_errors.CommandFailedError('Failed to start adb server')
+
+
+def _GetTimeStamp():
+ """Return a basic ISO 8601 time stamp with the current local time."""
+ return time.strftime('%Y%m%dT%H%M%S', time.localtime())
+
+
+def _JoinLines(lines):
+ # makes sure that the last line is also terminated, and is more memory
+ # efficient than first appending an end-line to each line and then joining
+ # all of them together.
+ return ''.join(s for line in lines for s in (line, '\n'))
+
+
+class DeviceUtils(object):
+
+ _MAX_ADB_COMMAND_LENGTH = 512
+ _MAX_ADB_OUTPUT_LENGTH = 32768
+ _LAUNCHER_FOCUSED_RE = re.compile(
+ '\s*mCurrentFocus.*(Launcher|launcher).*')
+ _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
+
+ # Property in /data/local.prop that controls Java assertions.
+ JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
+
+ def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
+ default_retries=_DEFAULT_RETRIES):
+ """DeviceUtils constructor.
+
+ Args:
+ device: Either a device serial, an existing AdbWrapper instance, or an
+ an existing AndroidCommands instance.
+ default_timeout: An integer containing the default number of seconds to
+ wait for an operation to complete if no explicit value
+ is provided.
+ default_retries: An integer containing the default number or times an
+ operation should be retried on failure if no explicit
+ value is provided.
+ """
+ self.adb = None
+ self.old_interface = None
+ if isinstance(device, basestring):
+ self.adb = adb_wrapper.AdbWrapper(device)
+ self.old_interface = pylib.android_commands.AndroidCommands(device)
+ elif isinstance(device, adb_wrapper.AdbWrapper):
+ self.adb = device
+ self.old_interface = pylib.android_commands.AndroidCommands(str(device))
+ elif isinstance(device, pylib.android_commands.AndroidCommands):
+ self.adb = adb_wrapper.AdbWrapper(device.GetDevice())
+ self.old_interface = device
+ else:
+ raise ValueError('Unsupported device value: %r' % device)
+ self._commands_installed = None
+ self._default_timeout = default_timeout
+ self._default_retries = default_retries
+ self._cache = {}
+ self._client_caches = {}
+ assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
+ assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
+
+ def __eq__(self, other):
+ """Checks whether |other| refers to the same device as |self|.
+
+ Args:
+ other: The object to compare to. This can be a basestring, an instance
+ of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
+ Returns:
+ Whether |other| refers to the same device as |self|.
+ """
+ return self.adb.GetDeviceSerial() == str(other)
+
+ def __lt__(self, other):
+ """Compares two instances of DeviceUtils.
+
+ This merely compares their serial numbers.
+
+ Args:
+ other: The instance of DeviceUtils to compare to.
+ Returns:
+ Whether |self| is less than |other|.
+ """
+ return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial()
+
+ def __str__(self):
+ """Returns the device serial."""
+ return self.adb.GetDeviceSerial()
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def IsOnline(self, timeout=None, retries=None):
+ """Checks whether the device is online.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if the device is online, False otherwise.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ """
+ try:
+ return self.adb.GetState() == 'device'
+ except base_error.BaseError as exc:
+ logging.info('Failed to get state: %s', exc)
+ return False
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def HasRoot(self, timeout=None, retries=None):
+ """Checks whether or not adbd has root privileges.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if adbd has root privileges, False otherwise.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ try:
+ self.RunShellCommand('ls /root', check_return=True)
+ return True
+ except device_errors.AdbCommandFailedError:
+ return False
+
+ def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT):
+ """Checks whether 'su' is needed to access protected resources.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if 'su' is available on the device and is needed to to access
+ protected resources; False otherwise if either 'su' is not available
+ (e.g. because the device has a user build), or not needed (because adbd
+ already has root privileges).
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ if 'needs_su' not in self._cache:
+ try:
+ self.RunShellCommand(
+ 'su -c ls /root && ! ls /root', check_return=True,
+ timeout=self._default_timeout if timeout is DEFAULT else timeout,
+ retries=self._default_retries if retries is DEFAULT else retries)
+ self._cache['needs_su'] = True
+ except device_errors.AdbCommandFailedError:
+ self._cache['needs_su'] = False
+ return self._cache['needs_su']
+
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def EnableRoot(self, timeout=None, retries=None):
+ """Restarts adbd with root privileges.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if root could not be enabled.
+ CommandTimeoutError on timeout.
+ """
+ if self.IsUserBuild():
+ raise device_errors.CommandFailedError(
+ 'Cannot enable root in user builds.', str(self))
+ if 'needs_su' in self._cache:
+ del self._cache['needs_su']
+ self.adb.Root()
+ self.WaitUntilFullyBooted()
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def IsUserBuild(self, timeout=None, retries=None):
+ """Checks whether or not the device is running a user build.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if the device is running a user build, False otherwise (i.e. if
+ it's running a userdebug build).
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ return self.build_type == 'user'
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetExternalStoragePath(self, timeout=None, retries=None):
+ """Get the device's path to its SD card.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The device's path to its SD card.
+
+ Raises:
+ CommandFailedError if the external storage path could not be determined.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ if 'external_storage' in self._cache:
+ return self._cache['external_storage']
+
+ value = self.RunShellCommand('echo $EXTERNAL_STORAGE',
+ single_line=True,
+ check_return=True)
+ if not value:
+ raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
+ str(self))
+ self._cache['external_storage'] = value
+ return value
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetApplicationPaths(self, package, timeout=None, retries=None):
+ """Get the paths of the installed apks on the device for the given package.
+
+ Args:
+ package: Name of the package.
+
+ Returns:
+ List of paths to the apks on the device for the given package.
+ """
+ # 'pm path' is liable to incorrectly exit with a nonzero number starting
+ # in Lollipop.
+ # TODO(jbudorick): Check if this is fixed as new Android versions are
+ # released to put an upper bound on this.
+ should_check_return = (self.build_version_sdk <
+ constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
+ output = self.RunShellCommand(
+ ['pm', 'path', package], check_return=should_check_return)
+ apks = []
+ for line in output:
+ if not line.startswith('package:'):
+ raise device_errors.CommandFailedError(
+ 'pm path returned: %r' % '\n'.join(output), str(self))
+ apks.append(line[len('package:'):])
+ return apks
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
+ """Get the data directory on the device for the given package.
+
+ Args:
+ package: Name of the package.
+
+ Returns:
+ The package's data directory, or None if the package doesn't exist on the
+ device.
+ """
+ try:
+ output = self._RunPipedShellCommand(
+ 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
+ for line in output:
+ _, _, dataDir = line.partition('dataDir=')
+ if dataDir:
+ return dataDir
+ except device_errors.CommandFailedError:
+ logging.exception('Could not find data directory for %s', package)
+ return None
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
+ """Wait for the device to fully boot.
+
+ This means waiting for the device to boot, the package manager to be
+ available, and the SD card to be ready. It can optionally mean waiting
+ for wifi to come up, too.
+
+ Args:
+ wifi: A boolean indicating if we should wait for wifi to come up or not.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError if one of the component waits times out.
+ DeviceUnreachableError if the device becomes unresponsive.
+ """
+ def sd_card_ready():
+ try:
+ self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
+ check_return=True)
+ return True
+ except device_errors.AdbCommandFailedError:
+ return False
+
+ def pm_ready():
+ try:
+ return self.GetApplicationPaths('android')
+ except device_errors.CommandFailedError:
+ return False
+
+ def boot_completed():
+ return self.GetProp('sys.boot_completed') == '1'
+
+ def wifi_enabled():
+ return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
+ check_return=False)
+
+ self.adb.WaitForDevice()
+ timeout_retry.WaitFor(sd_card_ready)
+ timeout_retry.WaitFor(pm_ready)
+ timeout_retry.WaitFor(boot_completed)
+ if wifi:
+ timeout_retry.WaitFor(wifi_enabled)
+
+ REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
+ REBOOT_DEFAULT_RETRIES = _DEFAULT_RETRIES
+
+ @decorators.WithTimeoutAndRetriesDefaults(
+ REBOOT_DEFAULT_TIMEOUT,
+ REBOOT_DEFAULT_RETRIES)
+ def Reboot(self, block=True, wifi=False, timeout=None, retries=None):
+ """Reboot the device.
+
+ Args:
+ block: A boolean indicating if we should wait for the reboot to complete.
+ wifi: A boolean indicating if we should wait for wifi to be enabled after
+ the reboot. The option has no effect unless |block| is also True.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ def device_offline():
+ return not self.IsOnline()
+
+ self.adb.Reboot()
+ self._ClearCache()
+ timeout_retry.WaitFor(device_offline, wait_period=1)
+ if block:
+ self.WaitUntilFullyBooted(wifi=wifi)
+
+ INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT
+ INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES
+
+ @decorators.WithTimeoutAndRetriesDefaults(
+ INSTALL_DEFAULT_TIMEOUT,
+ INSTALL_DEFAULT_RETRIES)
+ def Install(self, apk_path, reinstall=False, timeout=None, retries=None):
+ """Install an APK.
+
+ Noop if an identical APK is already installed.
+
+ Args:
+ apk_path: A string containing the path to the APK to install.
+ reinstall: A boolean indicating if we should keep any existing app data.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if the installation fails.
+ CommandTimeoutError if the installation times out.
+ DeviceUnreachableError on missing device.
+ """
+ package_name = apk_helper.GetPackageName(apk_path)
+ device_paths = self.GetApplicationPaths(package_name)
+ if device_paths:
+ if len(device_paths) > 1:
+ logging.warning(
+ 'Installing single APK (%s) when split APKs (%s) are currently '
+ 'installed.', apk_path, ' '.join(device_paths))
+ (files_to_push, _) = self._GetChangedAndStaleFiles(
+ apk_path, device_paths[0])
+ should_install = bool(files_to_push)
+ if should_install and not reinstall:
+ self.adb.Uninstall(package_name)
+ else:
+ should_install = True
+ if should_install:
+ self.adb.Install(apk_path, reinstall=reinstall)
+
+ @decorators.WithTimeoutAndRetriesDefaults(
+ INSTALL_DEFAULT_TIMEOUT,
+ INSTALL_DEFAULT_RETRIES)
+ def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
+ timeout=None, retries=None):
+ """Install a split APK.
+
+ Noop if all of the APK splits are already installed.
+
+ Args:
+ base_apk: A string of the path to the base APK.
+ split_apks: A list of strings of paths of all of the APK splits.
+ reinstall: A boolean indicating if we should keep any existing app data.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if the installation fails.
+ CommandTimeoutError if the installation times out.
+ DeviceUnreachableError on missing device.
+ DeviceVersionError if device SDK is less than Android L.
+ """
+ self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
+
+ all_apks = [base_apk] + split_select.SelectSplits(
+ self, base_apk, split_apks)
+ package_name = apk_helper.GetPackageName(base_apk)
+ device_apk_paths = self.GetApplicationPaths(package_name)
+
+ if device_apk_paths:
+ partial_install_package = package_name
+ device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self)
+ host_checksums = md5sum.CalculateHostMd5Sums(all_apks)
+ apks_to_install = [k for (k, v) in host_checksums.iteritems()
+ if v not in device_checksums.values()]
+ if apks_to_install and not reinstall:
+ self.adb.Uninstall(package_name)
+ partial_install_package = None
+ apks_to_install = all_apks
+ else:
+ partial_install_package = None
+ apks_to_install = all_apks
+ if apks_to_install:
+ self.adb.InstallMultiple(
+ apks_to_install, partial=partial_install_package, reinstall=reinstall)
+
+ def _CheckSdkLevel(self, required_sdk_level):
+ """Raises an exception if the device does not have the required SDK level.
+ """
+ if self.build_version_sdk < required_sdk_level:
+ raise device_errors.DeviceVersionError(
+ ('Requires SDK level %s, device is SDK level %s' %
+ (required_sdk_level, self.build_version_sdk)),
+ device_serial=self.adb.GetDeviceSerial())
+
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
+ as_root=False, single_line=False, large_output=False,
+ timeout=None, retries=None):
+ """Run an ADB shell command.
+
+ The command to run |cmd| should be a sequence of program arguments or else
+ a single string.
+
+ When |cmd| is a sequence, it is assumed to contain the name of the command
+ to run followed by its arguments. In this case, arguments are passed to the
+ command exactly as given, without any further processing by the shell. This
+ allows to easily pass arguments containing spaces or special characters
+ without having to worry about getting quoting right. Whenever possible, it
+ is recomended to pass |cmd| as a sequence.
+
+ When |cmd| is given as a string, it will be interpreted and run by the
+ shell on the device.
+
+ This behaviour is consistent with that of command runners in cmd_helper as
+ well as Python's own subprocess.Popen.
+
+ TODO(perezju) Change the default of |check_return| to True when callers
+ have switched to the new behaviour.
+
+ Args:
+ cmd: A string with the full command to run on the device, or a sequence
+ containing the command and its arguments.
+ check_return: A boolean indicating whether or not the return code should
+ be checked.
+ cwd: The device directory in which the command should be run.
+ env: The environment variables with which the command should be run.
+ as_root: A boolean indicating whether the shell command should be run
+ with root privileges.
+ single_line: A boolean indicating if only a single line of output is
+ expected.
+ large_output: Uses a work-around for large shell command output. Without
+ this large output will be truncated.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ If single_line is False, the output of the command as a list of lines,
+ otherwise, a string with the unique line of output emmited by the command
+ (with the optional newline at the end stripped).
+
+ Raises:
+ AdbCommandFailedError if check_return is True and the exit code of
+ the command run on the device is non-zero.
+ CommandFailedError if single_line is True but the output contains two or
+ more lines.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ def env_quote(key, value):
+ if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
+ raise KeyError('Invalid shell variable name %r' % key)
+ # using double quotes here to allow interpolation of shell variables
+ return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
+
+ def run(cmd):
+ return self.adb.Shell(cmd)
+
+ def handle_check_return(cmd):
+ try:
+ return run(cmd)
+ except device_errors.AdbCommandFailedError as exc:
+ if check_return:
+ raise
+ else:
+ return exc.output
+
+ def handle_large_command(cmd):
+ if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
+ return handle_check_return(cmd)
+ else:
+ with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
+ self._WriteFileWithPush(script.name, cmd)
+ logging.info('Large shell command will be run from file: %s ...',
+ cmd[:100])
+ return handle_check_return('sh %s' % script.name_quoted)
+
+ def handle_large_output(cmd, large_output_mode):
+ if large_output_mode:
+ with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
+ cmd = '%s > %s' % (cmd, large_output_file.name)
+ logging.debug('Large output mode enabled. Will write output to '
+ 'device and read results from file.')
+ handle_large_command(cmd)
+ return self.ReadFile(large_output_file.name, force_pull=True)
+ else:
+ try:
+ return handle_large_command(cmd)
+ except device_errors.AdbCommandFailedError as exc:
+ if exc.status is None:
+ logging.exception('No output found for %s', cmd)
+ logging.warning('Attempting to run in large_output mode.')
+ logging.warning('Use RunShellCommand(..., large_output=True) for '
+ 'shell commands that expect a lot of output.')
+ return handle_large_output(cmd, True)
+ else:
+ raise
+
+ if not isinstance(cmd, basestring):
+ cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
+ if env:
+ env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
+ cmd = '%s %s' % (env, cmd)
+ if cwd:
+ cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
+ if as_root and self.NeedsSU():
+ # "su -c sh -c" allows using shell features in |cmd|
+ cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd)
+
+ output = handle_large_output(cmd, large_output).splitlines()
+
+ if single_line:
+ if not output:
+ return ''
+ elif len(output) == 1:
+ return output[0]
+ else:
+ msg = 'one line of output was expected, but got: %s'
+ raise device_errors.CommandFailedError(msg % output, str(self))
+ else:
+ return output
+
+ def _RunPipedShellCommand(self, script, **kwargs):
+ PIPESTATUS_LEADER = 'PIPESTATUS: '
+
+ script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
+ kwargs['check_return'] = True
+ output = self.RunShellCommand(script, **kwargs)
+ pipestatus_line = output[-1]
+
+ if not pipestatus_line.startswith(PIPESTATUS_LEADER):
+ logging.error('Pipe exit statuses of shell script missing.')
+ raise device_errors.AdbShellCommandFailedError(
+ script, output, status=None,
+ device_serial=self.adb.GetDeviceSerial())
+
+ output = output[:-1]
+ statuses = [
+ int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()]
+ if any(statuses):
+ raise device_errors.AdbShellCommandFailedError(
+ script, output, status=statuses,
+ device_serial=self.adb.GetDeviceSerial())
+ return output
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def KillAll(self, process_name, signum=device_signal.SIGKILL, as_root=False,
+ blocking=False, quiet=False, timeout=None, retries=None):
+ """Kill all processes with the given name on the device.
+
+ Args:
+ process_name: A string containing the name of the process to kill.
+ signum: An integer containing the signal number to send to kill. Defaults
+ to SIGKILL (9).
+ as_root: A boolean indicating whether the kill should be executed with
+ root privileges.
+ blocking: A boolean indicating whether we should wait until all processes
+ with the given |process_name| are dead.
+ quiet: A boolean indicating whether to ignore the fact that no processes
+ to kill were found.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The number of processes attempted to kill.
+
+ Raises:
+ CommandFailedError if no process was killed and |quiet| is False.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ pids = self.GetPids(process_name)
+ if not pids:
+ if quiet:
+ return 0
+ else:
+ raise device_errors.CommandFailedError(
+ 'No process "%s"' % process_name, str(self))
+
+ cmd = ['kill', '-%d' % signum] + pids.values()
+ self.RunShellCommand(cmd, as_root=as_root, check_return=True)
+
+ if blocking:
+ # TODO(perezu): use timeout_retry.WaitFor
+ wait_period = 0.1
+ while self.GetPids(process_name):
+ time.sleep(wait_period)
+
+ return len(pids)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def StartActivity(self, intent_obj, blocking=False, trace_file_name=None,
+ force_stop=False, timeout=None, retries=None):
+ """Start package's activity on the device.
+
+ Args:
+ intent_obj: An Intent object to send.
+ blocking: A boolean indicating whether we should wait for the activity to
+ finish launching.
+ trace_file_name: If present, a string that both indicates that we want to
+ profile the activity and contains the path to which the
+ trace should be saved.
+ force_stop: A boolean indicating whether we should stop the activity
+ before starting it.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if the activity could not be started.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ cmd = ['am', 'start']
+ if blocking:
+ cmd.append('-W')
+ if trace_file_name:
+ cmd.extend(['--start-profiler', trace_file_name])
+ if force_stop:
+ cmd.append('-S')
+ cmd.extend(intent_obj.am_args)
+ for line in self.RunShellCommand(cmd, check_return=True):
+ if line.startswith('Error:'):
+ raise device_errors.CommandFailedError(line, str(self))
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def StartInstrumentation(self, component, finish=True, raw=False,
+ extras=None, timeout=None, retries=None):
+ if extras is None:
+ extras = {}
+
+ cmd = ['am', 'instrument']
+ if finish:
+ cmd.append('-w')
+ if raw:
+ cmd.append('-r')
+ for k, v in extras.iteritems():
+ cmd.extend(['-e', str(k), str(v)])
+ cmd.append(component)
+ return self.RunShellCommand(cmd, check_return=True, large_output=True)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
+ """Send a broadcast intent.
+
+ Args:
+ intent: An Intent to broadcast.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ cmd = ['am', 'broadcast'] + intent_obj.am_args
+ self.RunShellCommand(cmd, check_return=True)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GoHome(self, timeout=None, retries=None):
+ """Return to the home screen and obtain launcher focus.
+
+ This command launches the home screen and attempts to obtain
+ launcher focus until the timeout is reached.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ def is_launcher_focused():
+ output = self.RunShellCommand(['dumpsys', 'window', 'windows'],
+ check_return=True, large_output=True)
+ return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output)
+
+ def dismiss_popups():
+ # There is a dialog present; attempt to get rid of it.
+ # Not all dialogs can be dismissed with back.
+ self.SendKeyEvent(keyevent.KEYCODE_ENTER)
+ self.SendKeyEvent(keyevent.KEYCODE_BACK)
+ return is_launcher_focused()
+
+ # If Home is already focused, return early to avoid unnecessary work.
+ if is_launcher_focused():
+ return
+
+ self.StartActivity(
+ intent.Intent(action='android.intent.action.MAIN',
+ category='android.intent.category.HOME'),
+ blocking=True)
+
+ if not is_launcher_focused():
+ timeout_retry.WaitFor(dismiss_popups, wait_period=1)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def ForceStop(self, package, timeout=None, retries=None):
+ """Close the application.
+
+ Args:
+ package: A string containing the name of the package to stop.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ self.RunShellCommand(['am', 'force-stop', package], check_return=True)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def ClearApplicationState(self, package, timeout=None, retries=None):
+ """Clear all state for the given package.
+
+ Args:
+ package: A string containing the name of the package to stop.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ # Check that the package exists before clearing it for android builds below
+ # JB MR2. Necessary because calling pm clear on a package that doesn't exist
+ # may never return.
+ if ((self.build_version_sdk >=
+ constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
+ or self.GetApplicationPaths(package)):
+ self.RunShellCommand(['pm', 'clear', package], check_return=True)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SendKeyEvent(self, keycode, timeout=None, retries=None):
+ """Sends a keycode to the device.
+
+ See the pylib.constants.keyevent module for suitable keycode values.
+
+ Args:
+ keycode: A integer keycode to send to the device.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')],
+ check_return=True)
+
+ PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
+ PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES
+
+ @decorators.WithTimeoutAndRetriesDefaults(
+ PUSH_CHANGED_FILES_DEFAULT_TIMEOUT,
+ PUSH_CHANGED_FILES_DEFAULT_RETRIES)
+ def PushChangedFiles(self, host_device_tuples, timeout=None,
+ retries=None, delete_device_stale=False):
+ """Push files to the device, skipping files that don't need updating.
+
+ When a directory is pushed, it is traversed recursively on the host and
+ all files in it are pushed to the device as needed.
+ Additionally, if delete_device_stale option is True,
+ files that exist on the device but don't exist on the host are deleted.
+
+ Args:
+ host_device_tuples: A list of (host_path, device_path) tuples, where
+ |host_path| is an absolute path of a file or directory on the host
+ that should be minimially pushed to the device, and |device_path| is
+ an absolute path of the destination on the device.
+ timeout: timeout in seconds
+ retries: number of retries
+ delete_device_stale: option to delete stale files on device
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+
+ all_changed_files = []
+ all_stale_files = []
+ for h, d in host_device_tuples:
+ if os.path.isdir(h):
+ self.RunShellCommand(['mkdir', '-p', d], check_return=True)
+ changed_files, stale_files = (
+ self._GetChangedAndStaleFiles(h, d, delete_device_stale))
+ all_changed_files += changed_files
+ all_stale_files += stale_files
+
+ if delete_device_stale:
+ self.RunShellCommand(['rm', '-f'] + all_stale_files,
+ check_return=True)
+
+ if not all_changed_files:
+ return
+
+ self._PushFilesImpl(host_device_tuples, all_changed_files)
+
+ def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False):
+ """Get files to push and delete
+
+ Args:
+ host_path: an absolute path of a file or directory on the host
+ device_path: an absolute path of a file or directory on the device
+ track_stale: whether to bother looking for stale files (slower)
+
+ Returns:
+ a two-element tuple
+ 1st element: a list of (host_files_path, device_files_path) tuples to push
+ 2nd element: a list of stale files under device_path, or [] when
+ track_stale == False
+ """
+ real_host_path = os.path.realpath(host_path)
+ try:
+ real_device_path = self.RunShellCommand(
+ ['realpath', device_path], single_line=True, check_return=True)
+ except device_errors.CommandFailedError:
+ real_device_path = None
+ if not real_device_path:
+ return ([(host_path, device_path)], [])
+
+ try:
+ host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
+ interesting_device_paths = [real_device_path]
+ if not track_stale and os.path.isdir(real_host_path):
+ interesting_device_paths = [
+ posixpath.join(real_device_path, os.path.relpath(p, real_host_path))
+ for p in host_checksums.keys()]
+ device_checksums = md5sum.CalculateDeviceMd5Sums(
+ interesting_device_paths, self)
+ except EnvironmentError as e:
+ logging.warning('Error calculating md5: %s', e)
+ return ([(host_path, device_path)], [])
+
+ if os.path.isfile(host_path):
+ host_checksum = host_checksums.get(real_host_path)
+ device_checksum = device_checksums.get(real_device_path)
+ if host_checksum != device_checksum:
+ return ([(host_path, device_path)], [])
+ else:
+ return ([], [])
+ else:
+ to_push = []
+ for host_abs_path, host_checksum in host_checksums.iteritems():
+ device_abs_path = '%s/%s' % (
+ real_device_path, os.path.relpath(host_abs_path, real_host_path))
+ device_checksum = device_checksums.pop(device_abs_path, None)
+ if device_checksum != host_checksum:
+ to_push.append((host_abs_path, device_abs_path))
+ to_delete = device_checksums.keys()
+ return (to_push, to_delete)
+
+ def _PushFilesImpl(self, host_device_tuples, files):
+ size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
+ file_count = len(files)
+ dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
+ for h, _ in host_device_tuples)
+ dir_file_count = 0
+ for h, _ in host_device_tuples:
+ if os.path.isdir(h):
+ dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
+ else:
+ dir_file_count += 1
+
+ push_duration = self._ApproximateDuration(
+ file_count, file_count, size, False)
+ dir_push_duration = self._ApproximateDuration(
+ len(host_device_tuples), dir_file_count, dir_size, False)
+ zip_duration = self._ApproximateDuration(1, 1, size, True)
+
+ self._InstallCommands()
+
+ if dir_push_duration < push_duration and (
+ dir_push_duration < zip_duration or not self._commands_installed):
+ self._PushChangedFilesIndividually(host_device_tuples)
+ elif push_duration < zip_duration or not self._commands_installed:
+ self._PushChangedFilesIndividually(files)
+ else:
+ self._PushChangedFilesZipped(files)
+ self.RunShellCommand(
+ ['chmod', '-R', '777'] + [d for _, d in host_device_tuples],
+ as_root=True, check_return=True)
+
+ def _InstallCommands(self):
+ if self._commands_installed is None:
+ try:
+ if not install_commands.Installed(self):
+ install_commands.InstallCommands(self)
+ self._commands_installed = True
+ except Exception as e:
+ logging.warning('unzip not available: %s' % str(e))
+ self._commands_installed = False
+
+ @staticmethod
+ def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping):
+ # We approximate the time to push a set of files to a device as:
+ # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where
+ # t: total time (sec)
+ # c1: adb call time delay (sec)
+ # a: number of times adb is called (unitless)
+ # c2: push time delay (sec)
+ # f: number of files pushed via adb (unitless)
+ # c3: zip time delay (sec)
+ # c4: zip rate (bytes/sec)
+ # b: total number of bytes (bytes)
+ # c5: transfer rate (bytes/sec)
+ # c6: compression ratio (unitless)
+
+ # All of these are approximations.
+ ADB_CALL_PENALTY = 0.1 # seconds
+ ADB_PUSH_PENALTY = 0.01 # seconds
+ ZIP_PENALTY = 2.0 # seconds
+ ZIP_RATE = 10000000.0 # bytes / second
+ TRANSFER_RATE = 2000000.0 # bytes / second
+ COMPRESSION_RATIO = 2.0 # unitless
+
+ adb_call_time = ADB_CALL_PENALTY * adb_calls
+ adb_push_setup_time = ADB_PUSH_PENALTY * file_count
+ if is_zipping:
+ zip_time = ZIP_PENALTY + byte_count / ZIP_RATE
+ transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO)
+ else:
+ zip_time = 0
+ transfer_time = byte_count / TRANSFER_RATE
+ return adb_call_time + adb_push_setup_time + zip_time + transfer_time
+
+ def _PushChangedFilesIndividually(self, files):
+ for h, d in files:
+ self.adb.Push(h, d)
+
+ def _PushChangedFilesZipped(self, files):
+ if not files:
+ return
+
+ with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file:
+ zip_proc = multiprocessing.Process(
+ target=DeviceUtils._CreateDeviceZip,
+ args=(zip_file.name, files))
+ zip_proc.start()
+ zip_proc.join()
+
+ zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath()
+ try:
+ self.adb.Push(zip_file.name, zip_on_device)
+ self.RunShellCommand(
+ ['unzip', zip_on_device],
+ as_root=True,
+ env={'PATH': '%s:$PATH' % install_commands.BIN_DIR},
+ check_return=True)
+ finally:
+ if zip_proc.is_alive():
+ zip_proc.terminate()
+ if self.IsOnline():
+ self.RunShellCommand(['rm', zip_on_device], check_return=True)
+
+ @staticmethod
+ def _CreateDeviceZip(zip_path, host_device_tuples):
+ with zipfile.ZipFile(zip_path, 'w') as zip_file:
+ for host_path, device_path in host_device_tuples:
+ zip_utils.WriteToZipFile(zip_file, host_path, device_path)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def FileExists(self, device_path, timeout=None, retries=None):
+ """Checks whether the given file exists on the device.
+
+ Args:
+ device_path: A string containing the absolute path to the file on the
+ device.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if the file exists on the device, False otherwise.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ try:
+ self.RunShellCommand(['test', '-e', device_path], check_return=True)
+ return True
+ except device_errors.AdbCommandFailedError:
+ return False
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def PullFile(self, device_path, host_path, timeout=None, retries=None):
+ """Pull a file from the device.
+
+ Args:
+ device_path: A string containing the absolute path of the file to pull
+ from the device.
+ host_path: A string containing the absolute path of the destination on
+ the host.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError on timeout.
+ """
+ # Create the base dir if it doesn't exist already
+ dirname = os.path.dirname(host_path)
+ if dirname and not os.path.exists(dirname):
+ os.makedirs(dirname)
+ self.adb.Pull(device_path, host_path)
+
+ def _ReadFileWithPull(self, device_path):
+ try:
+ d = tempfile.mkdtemp()
+ host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull')
+ self.adb.Pull(device_path, host_temp_path)
+ with open(host_temp_path, 'r') as host_temp:
+ return host_temp.read()
+ finally:
+ if os.path.exists(d):
+ shutil.rmtree(d)
+
+ _LS_RE = re.compile(
+ r'(?P\S+) +(?P\S+) +(?P\S+) +(?:(?P\d+) +)?'
+ + r'(?P\S+) +(?P\S+) +(?P.+)$')
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def ReadFile(self, device_path, as_root=False, force_pull=False,
+ timeout=None, retries=None):
+ """Reads the contents of a file from the device.
+
+ Args:
+ device_path: A string containing the absolute path of the file to read
+ from the device.
+ as_root: A boolean indicating whether the read should be executed with
+ root privileges.
+ force_pull: A boolean indicating whether to force the operation to be
+ performed by pulling a file from the device. The default is, when the
+ contents are short, to retrieve the contents using cat instead.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The contents of |device_path| as a string. Contents are intepreted using
+ universal newlines, so the caller will see them encoded as '\n'. Also,
+ all lines will be terminated.
+
+ Raises:
+ AdbCommandFailedError if the file can't be read.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ def get_size(path):
+ # TODO(jbudorick): Implement a generic version of Stat() that handles
+ # as_root=True, then switch this implementation to use that.
+ ls_out = self.RunShellCommand(['ls', '-l', device_path], as_root=as_root,
+ check_return=True)
+ for line in ls_out:
+ m = self._LS_RE.match(line)
+ if m and m.group('name') == posixpath.basename(device_path):
+ return int(m.group('size'))
+ logging.warning('Could not determine size of %s.', device_path)
+ return None
+
+ if (not force_pull
+ and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH):
+ return _JoinLines(self.RunShellCommand(
+ ['cat', device_path], as_root=as_root, check_return=True))
+ elif as_root and self.NeedsSU():
+ with device_temp_file.DeviceTempFile(self.adb) as device_temp:
+ self.RunShellCommand(['cp', device_path, device_temp.name],
+ as_root=True, check_return=True)
+ return self._ReadFileWithPull(device_temp.name)
+ else:
+ return self._ReadFileWithPull(device_path)
+
+ def _WriteFileWithPush(self, device_path, contents):
+ with tempfile.NamedTemporaryFile() as host_temp:
+ host_temp.write(contents)
+ host_temp.flush()
+ self.adb.Push(host_temp.name, device_path)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def WriteFile(self, device_path, contents, as_root=False, force_push=False,
+ timeout=None, retries=None):
+ """Writes |contents| to a file on the device.
+
+ Args:
+ device_path: A string containing the absolute path to the file to write
+ on the device.
+ contents: A string containing the data to write to the device.
+ as_root: A boolean indicating whether the write should be executed with
+ root privileges (if available).
+ force_push: A boolean indicating whether to force the operation to be
+ performed by pushing a file to the device. The default is, when the
+ contents are short, to pass the contents using a shell script instead.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if the file could not be written on the device.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH:
+ # If the contents are small, for efficieny we write the contents with
+ # a shell command rather than pushing a file.
+ cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
+ cmd_helper.SingleQuote(device_path))
+ self.RunShellCommand(cmd, as_root=as_root, check_return=True)
+ elif as_root and self.NeedsSU():
+ # Adb does not allow to "push with su", so we first push to a temp file
+ # on a safe location, and then copy it to the desired location with su.
+ with device_temp_file.DeviceTempFile(self.adb) as device_temp:
+ self._WriteFileWithPush(device_temp.name, contents)
+ # Here we need 'cp' rather than 'mv' because the temp and
+ # destination files might be on different file systems (e.g.
+ # on internal storage and an external sd card).
+ self.RunShellCommand(['cp', device_temp.name, device_path],
+ as_root=True, check_return=True)
+ else:
+ # If root is not needed, we can push directly to the desired location.
+ self._WriteFileWithPush(device_path, contents)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def Ls(self, device_path, timeout=None, retries=None):
+ """Lists the contents of a directory on the device.
+
+ Args:
+ device_path: A string containing the path of the directory on the device
+ to list.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A list of pairs (filename, stat) for each file found in the directory,
+ where the stat object has the properties: st_mode, st_size, and st_time.
+
+ Raises:
+ AdbCommandFailedError if |device_path| does not specify a valid and
+ accessible directory in the device.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ return self.adb.Ls(device_path)
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def Stat(self, device_path, timeout=None, retries=None):
+ """Get the stat attributes of a file or directory on the device.
+
+ Args:
+ device_path: A string containing the path of from which to get attributes
+ on the device.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A stat object with the properties: st_mode, st_size, and st_time
+
+ Raises:
+ CommandFailedError if device_path cannot be found on the device.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ dirname, target = device_path.rsplit('/', 1)
+ for filename, stat in self.adb.Ls(dirname):
+ if filename == target:
+ return stat
+ raise device_errors.CommandFailedError(
+ 'Cannot find file or directory: %r' % device_path, str(self))
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SetJavaAsserts(self, enabled, timeout=None, retries=None):
+ """Enables or disables Java asserts.
+
+ Args:
+ enabled: A boolean indicating whether Java asserts should be enabled
+ or disabled.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True if the device-side property changed and a restart is required as a
+ result, False otherwise.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ """
+ def find_property(lines, property_name):
+ for index, line in enumerate(lines):
+ if line.strip() == '':
+ continue
+ key, value = (s.strip() for s in line.split('=', 1))
+ if key == property_name:
+ return index, value
+ return None, ''
+
+ new_value = 'all' if enabled else ''
+
+ # First ensure the desired property is persisted.
+ try:
+ properties = self.ReadFile(
+ constants.DEVICE_LOCAL_PROPERTIES_PATH).splitlines()
+ except device_errors.CommandFailedError:
+ properties = []
+ index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY)
+ if new_value != value:
+ if new_value:
+ new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value)
+ if index is None:
+ properties.append(new_line)
+ else:
+ properties[index] = new_line
+ else:
+ assert index is not None # since new_value == '' and new_value != value
+ properties.pop(index)
+ self.WriteFile(constants.DEVICE_LOCAL_PROPERTIES_PATH,
+ _JoinLines(properties))
+
+ # Next, check the current runtime value is what we need, and
+ # if not, set it and report that a reboot is required.
+ value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
+ if new_value != value:
+ self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
+ return True
+ else:
+ return False
+
+ @property
+ def language(self):
+ """Returns the language setting on the device."""
+ return self.GetProp('persist.sys.language', cache=False)
+
+ @property
+ def country(self):
+ """Returns the country setting on the device."""
+ return self.GetProp('persist.sys.country', cache=False)
+
+ @property
+ def screen_density(self):
+ """Returns the screen density of the device."""
+ DPI_TO_DENSITY = {
+ 120: 'ldpi',
+ 160: 'mdpi',
+ 240: 'hdpi',
+ 320: 'xhdpi',
+ 480: 'xxhdpi',
+ 640: 'xxxhdpi',
+ }
+ dpi = int(self.GetProp('ro.sf.lcd_density', cache=True))
+ return DPI_TO_DENSITY.get(dpi, 'tvdpi')
+
+ @property
+ def build_description(self):
+ """Returns the build description of the system.
+
+ For example:
+ nakasi-user 4.4.4 KTU84P 1227136 release-keys
+ """
+ return self.GetProp('ro.build.description', cache=True)
+
+ @property
+ def build_fingerprint(self):
+ """Returns the build fingerprint of the system.
+
+ For example:
+ google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys
+ """
+ return self.GetProp('ro.build.fingerprint', cache=True)
+
+ @property
+ def build_id(self):
+ """Returns the build ID of the system (e.g. 'KTU84P')."""
+ return self.GetProp('ro.build.id', cache=True)
+
+ @property
+ def build_product(self):
+ """Returns the build product of the system (e.g. 'grouper')."""
+ return self.GetProp('ro.build.product', cache=True)
+
+ @property
+ def build_type(self):
+ """Returns the build type of the system (e.g. 'user')."""
+ return self.GetProp('ro.build.type', cache=True)
+
+ @property
+ def build_version_sdk(self):
+ """Returns the build version sdk of the system as a number (e.g. 19).
+
+ For version code numbers see:
+ http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+
+ For named constants see:
+ pylib.constants.ANDROID_SDK_VERSION_CODES
+
+ Raises:
+ CommandFailedError if the build version sdk is not a number.
+ """
+ value = self.GetProp('ro.build.version.sdk', cache=True)
+ try:
+ return int(value)
+ except ValueError:
+ raise device_errors.CommandFailedError(
+ 'Invalid build version sdk: %r' % value)
+
+ @property
+ def product_cpu_abi(self):
+ """Returns the product cpu abi of the device (e.g. 'armeabi-v7a')."""
+ return self.GetProp('ro.product.cpu.abi', cache=True)
+
+ @property
+ def product_model(self):
+ """Returns the name of the product model (e.g. 'Nexus 7')."""
+ return self.GetProp('ro.product.model', cache=True)
+
+ @property
+ def product_name(self):
+ """Returns the product name of the device (e.g. 'nakasi')."""
+ return self.GetProp('ro.product.name', cache=True)
+
+ def GetProp(self, property_name, cache=False, timeout=DEFAULT,
+ retries=DEFAULT):
+ """Gets a property from the device.
+
+ Args:
+ property_name: A string containing the name of the property to get from
+ the device.
+ cache: A boolean indicating whether to cache the value of this property.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The value of the device's |property_name| property.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ """
+ assert isinstance(property_name, basestring), (
+ "property_name is not a string: %r" % property_name)
+
+ cache_key = '_prop:' + property_name
+ if cache and cache_key in self._cache:
+ return self._cache[cache_key]
+ else:
+ # timeout and retries are handled down at run shell, because we don't
+ # want to apply them in the other branch when reading from the cache
+ value = self.RunShellCommand(
+ ['getprop', property_name], single_line=True, check_return=True,
+ timeout=self._default_timeout if timeout is DEFAULT else timeout,
+ retries=self._default_retries if retries is DEFAULT else retries)
+ if cache or cache_key in self._cache:
+ self._cache[cache_key] = value
+ return value
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SetProp(self, property_name, value, check=False, timeout=None,
+ retries=None):
+ """Sets a property on the device.
+
+ Args:
+ property_name: A string containing the name of the property to set on
+ the device.
+ value: A string containing the value to set to the property on the
+ device.
+ check: A boolean indicating whether to check that the property was
+ successfully set on the device.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError if check is true and the property was not correctly
+ set on the device (e.g. because it is not rooted).
+ CommandTimeoutError on timeout.
+ """
+ assert isinstance(property_name, basestring), (
+ "property_name is not a string: %r" % property_name)
+ assert isinstance(value, basestring), "value is not a string: %r" % value
+
+ self.RunShellCommand(['setprop', property_name, value], check_return=True)
+ if property_name in self._cache:
+ del self._cache[property_name]
+ # TODO(perezju) remove the option and make the check mandatory, but using a
+ # single shell script to both set- and getprop.
+ if check and value != self.GetProp(property_name):
+ raise device_errors.CommandFailedError(
+ 'Unable to set property %r on the device to %r'
+ % (property_name, value), str(self))
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetABI(self, timeout=None, retries=None):
+ """Gets the device main ABI.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The device's main ABI name.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ """
+ return self.GetProp('ro.product.cpu.abi')
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetPids(self, process_name, timeout=None, retries=None):
+ """Returns the PIDs of processes with the given name.
+
+ Note that the |process_name| is often the package name.
+
+ Args:
+ process_name: A string containing the process name to get the PIDs for.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A dict mapping process name to PID for each process that contained the
+ provided |process_name|.
+
+ Raises:
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ procs_pids = {}
+ try:
+ ps_output = self._RunPipedShellCommand(
+ 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name))
+ except device_errors.AdbShellCommandFailedError as e:
+ if e.status and isinstance(e.status, list) and not e.status[0]:
+ # If ps succeeded but grep failed, there were no processes with the
+ # given name.
+ return procs_pids
+ else:
+ raise
+
+ for line in ps_output:
+ try:
+ ps_data = line.split()
+ if process_name in ps_data[-1]:
+ procs_pids[ps_data[-1]] = ps_data[1]
+ except IndexError:
+ pass
+ return procs_pids
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
+ """Takes a screenshot of the device.
+
+ Args:
+ host_path: A string containing the path on the host to save the
+ screenshot to. If None, a file name in the current
+ directory will be generated.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ The name of the file on the host to which the screenshot was saved.
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ if not host_path:
+ host_path = os.path.abspath('screenshot-%s.png' % _GetTimeStamp())
+ with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp:
+ self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name],
+ check_return=True)
+ self.PullFile(device_tmp.name, host_path)
+ return host_path
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetMemoryUsageForPid(self, pid, timeout=None, retries=None):
+ """Gets the memory usage for the given PID.
+
+ Args:
+ pid: PID of the process.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A dict containing memory usage statistics for the PID. May include:
+ Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean,
+ Private_Dirty, VmHWM
+
+ Raises:
+ CommandTimeoutError on timeout.
+ """
+ result = collections.defaultdict(int)
+
+ try:
+ result.update(self._GetMemoryUsageForPidFromSmaps(pid))
+ except device_errors.CommandFailedError:
+ logging.exception('Error getting memory usage from smaps')
+
+ try:
+ result.update(self._GetMemoryUsageForPidFromStatus(pid))
+ except device_errors.CommandFailedError:
+ logging.exception('Error getting memory usage from status')
+
+ return result
+
+ def _GetMemoryUsageForPidFromSmaps(self, pid):
+ SMAPS_COLUMNS = (
+ 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean',
+ 'Private_Dirty')
+
+ showmap_out = self._RunPipedShellCommand(
+ 'showmap %d | grep TOTAL' % int(pid), as_root=True)
+
+ split_totals = showmap_out[-1].split()
+ if (not split_totals
+ or len(split_totals) != 9
+ or split_totals[-1] != 'TOTAL'):
+ raise device_errors.CommandFailedError(
+ 'Invalid output from showmap: %s' % '\n'.join(showmap_out))
+
+ return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals)))
+
+ def _GetMemoryUsageForPidFromStatus(self, pid):
+ for line in self.ReadFile(
+ '/proc/%s/status' % str(pid), as_root=True).splitlines():
+ if line.startswith('VmHWM:'):
+ return {'VmHWM': int(line.split()[1])}
+ else:
+ raise device_errors.CommandFailedError(
+ 'Could not find memory peak value for pid %s', str(pid))
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetLogcatMonitor(self, timeout=None, retries=None, *args, **kwargs):
+ """Returns a new LogcatMonitor associated with this device.
+
+ Parameters passed to this function are passed directly to
+ |logcat_monitor.LogcatMonitor| and are documented there.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+ """
+ return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs)
+
+ def GetClientCache(self, client_name):
+ """Returns client cache."""
+ if client_name not in self._client_caches:
+ self._client_caches[client_name] = {}
+ return self._client_caches[client_name]
+
+ def _ClearCache(self):
+ """Clears all caches."""
+ for client in self._client_caches:
+ self._client_caches[client].clear()
+ self._cache.clear()
+
+ @classmethod
+ def parallel(cls, devices=None, async=False):
+ """Creates a Parallelizer to operate over the provided list of devices.
+
+ If |devices| is either |None| or an empty list, the Parallelizer will
+ operate over all attached devices that have not been blacklisted.
+
+ Args:
+ devices: A list of either DeviceUtils instances or objects from
+ from which DeviceUtils instances can be constructed. If None,
+ all attached devices will be used.
+ async: If true, returns a Parallelizer that runs operations
+ asynchronously.
+
+ Returns:
+ A Parallelizer operating over |devices|.
+ """
+ if not devices:
+ devices = cls.HealthyDevices()
+ if not devices:
+ raise device_errors.NoDevicesError()
+
+ devices = [d if isinstance(d, cls) else cls(d) for d in devices]
+ if async:
+ return parallelizer.Parallelizer(devices)
+ else:
+ return parallelizer.SyncParallelizer(devices)
+
+ @classmethod
+ def HealthyDevices(cls):
+ blacklist = device_blacklist.ReadBlacklist()
+ def blacklisted(adb):
+ if adb.GetDeviceSerial() in blacklist:
+ logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial())
+ return True
+ return False
+
+ return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
+ if not blacklisted(adb)]
diff --git a/build/android/pylib/device/device_utils_device_test.py b/build/android/pylib/device/device_utils_device_test.py
new file mode 100755
index 00000000000..daae2b65fbf
--- /dev/null
+++ b/build/android/pylib/device/device_utils_device_test.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+"""
+Unit tests for the contents of device_utils.py (mostly DeviceUtils).
+The test will invoke real devices
+"""
+
+import os
+import tempfile
+import unittest
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.device import adb_wrapper
+from pylib.device import device_utils
+from pylib.utils import md5sum
+
+_OLD_CONTENTS = "foo"
+_NEW_CONTENTS = "bar"
+_DEVICE_DIR = "/data/local/tmp/device_utils_test"
+_SUB_DIR = "sub"
+_SUB_DIR1 = "sub1"
+_SUB_DIR2 = "sub2"
+
+class DeviceUtilsPushDeleteFilesTest(unittest.TestCase):
+
+ def setUp(self):
+ devices = adb_wrapper.AdbWrapper.Devices()
+ assert devices, 'A device must be attached'
+ self.adb = devices[0]
+ self.adb.WaitForDevice()
+ self.device = device_utils.DeviceUtils(
+ self.adb, default_timeout=10, default_retries=0)
+ default_build_type = os.environ.get('BUILDTYPE', 'Debug')
+ constants.SetBuildType(default_build_type)
+
+ @staticmethod
+ def _MakeTempFile(contents):
+ """Make a temporary file with the given contents.
+
+ Args:
+ contents: string to write to the temporary file.
+
+ Returns:
+ the tuple contains the absolute path to the file and the file name
+ """
+ fi, path = tempfile.mkstemp(text=True)
+ with os.fdopen(fi, 'w') as f:
+ f.write(contents)
+ file_name = os.path.basename(path)
+ return (path, file_name)
+
+ @staticmethod
+ def _MakeTempFileGivenDir(directory, contents):
+ """Make a temporary file under the given directory
+ with the given contents
+
+ Args:
+ directory: the temp directory to create the file
+ contents: string to write to the temp file
+
+ Returns:
+ the list contains the absolute path to the file and the file name
+ """
+ fi, path = tempfile.mkstemp(dir=directory, text=True)
+ with os.fdopen(fi, 'w') as f:
+ f.write(contents)
+ file_name = os.path.basename(path)
+ return (path, file_name)
+
+ @staticmethod
+ def _ChangeTempFile(path, contents):
+ with os.open(path, 'w') as f:
+ f.write(contents)
+
+ @staticmethod
+ def _DeleteTempFile(path):
+ os.remove(path)
+
+ def testPushChangedFiles_noFileChange(self):
+ (host_file_path, file_name) = self._MakeTempFile(_OLD_CONTENTS)
+ device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
+ self.adb.Push(host_file_path, device_file_path)
+ self.device.PushChangedFiles([(host_file_path, device_file_path)])
+ result = self.device.RunShellCommand(['cat', device_file_path],
+ single_line=True)
+ self.assertEqual(_OLD_CONTENTS, result)
+
+ cmd_helper.RunCmd(['rm', host_file_path])
+ self.device.RunShellCommand(['rm', '-rf', _DEVICE_DIR])
+
+ def testPushChangedFiles_singleFileChange(self):
+ (host_file_path, file_name) = self._MakeTempFile(_OLD_CONTENTS)
+ device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
+ self.adb.Push(host_file_path, device_file_path)
+
+ with open(host_file_path, 'w') as f:
+ f.write(_NEW_CONTENTS)
+ self.device.PushChangedFiles([(host_file_path, device_file_path)])
+ result = self.device.RunShellCommand(['cat', device_file_path],
+ single_line=True)
+ self.assertEqual(_NEW_CONTENTS, result)
+
+ cmd_helper.RunCmd(['rm', host_file_path])
+ self.device.RunShellCommand(['rm', '-rf', _DEVICE_DIR])
+
+ def testDeleteFiles(self):
+ host_tmp_dir = tempfile.mkdtemp()
+ (host_file_path, file_name) = self._MakeTempFileGivenDir(
+ host_tmp_dir, _OLD_CONTENTS)
+
+ device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
+ self.adb.Push(host_file_path, device_file_path)
+
+ cmd_helper.RunCmd(['rm', host_file_path])
+ self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
+ delete_device_stale=True)
+ result = self.device.RunShellCommand(['ls', _DEVICE_DIR], single_line=True)
+ self.assertEqual('', result)
+
+ cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
+ self.device.RunShellCommand(['rm', '-rf', _DEVICE_DIR])
+
+ def testPushAndDeleteFiles_noSubDir(self):
+ host_tmp_dir = tempfile.mkdtemp()
+ (host_file_path1, file_name1) = self._MakeTempFileGivenDir(
+ host_tmp_dir, _OLD_CONTENTS)
+ (host_file_path2, file_name2) = self._MakeTempFileGivenDir(
+ host_tmp_dir, _OLD_CONTENTS)
+
+ device_file_path1 = "%s/%s" % (_DEVICE_DIR, file_name1)
+ device_file_path2 = "%s/%s" % (_DEVICE_DIR, file_name2)
+ self.adb.Push(host_file_path1, device_file_path1)
+ self.adb.Push(host_file_path2, device_file_path2)
+
+ with open(host_file_path1, 'w') as f:
+ f.write(_NEW_CONTENTS)
+ cmd_helper.RunCmd(['rm', host_file_path2])
+
+ self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
+ delete_device_stale=True)
+ result = self.device.RunShellCommand(['cat', device_file_path1],
+ single_line=True)
+ self.assertEqual(_NEW_CONTENTS, result)
+ result = self.device.RunShellCommand(['ls', _DEVICE_DIR], single_line=True)
+ self.assertEqual(file_name1, result)
+
+ self.device.RunShellCommand(['rm', '-rf', _DEVICE_DIR])
+ cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
+
+ def testPushAndDeleteFiles_SubDir(self):
+ host_tmp_dir = tempfile.mkdtemp()
+ host_sub_dir1 = "%s/%s" % (host_tmp_dir, _SUB_DIR1)
+ host_sub_dir2 = "%s/%s/%s" % (host_tmp_dir, _SUB_DIR, _SUB_DIR2)
+ cmd_helper.RunCmd(['mkdir', '-p', host_sub_dir1])
+ cmd_helper.RunCmd(['mkdir', '-p', host_sub_dir2])
+
+ (host_file_path1, file_name1) = self._MakeTempFileGivenDir(
+ host_tmp_dir, _OLD_CONTENTS)
+ (host_file_path2, file_name2) = self._MakeTempFileGivenDir(
+ host_tmp_dir, _OLD_CONTENTS)
+ (host_file_path3, file_name3) = self._MakeTempFileGivenDir(
+ host_sub_dir1, _OLD_CONTENTS)
+ (host_file_path4, file_name4) = self._MakeTempFileGivenDir(
+ host_sub_dir2, _OLD_CONTENTS)
+
+ device_file_path1 = "%s/%s" % (_DEVICE_DIR, file_name1)
+ device_file_path2 = "%s/%s" % (_DEVICE_DIR, file_name2)
+ device_file_path3 = "%s/%s/%s" % (_DEVICE_DIR, _SUB_DIR1, file_name3)
+ device_file_path4 = "%s/%s/%s/%s" % (_DEVICE_DIR, _SUB_DIR,
+ _SUB_DIR2, file_name4)
+
+ self.adb.Push(host_file_path1, device_file_path1)
+ self.adb.Push(host_file_path2, device_file_path2)
+ self.adb.Push(host_file_path3, device_file_path3)
+ self.adb.Push(host_file_path4, device_file_path4)
+
+ with open(host_file_path1, 'w') as f:
+ f.write(_NEW_CONTENTS)
+ cmd_helper.RunCmd(['rm', host_file_path2])
+ cmd_helper.RunCmd(['rm', host_file_path4])
+
+ self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
+ delete_device_stale=True)
+ result = self.device.RunShellCommand(['cat', device_file_path1],
+ single_line=True)
+ self.assertEqual(_NEW_CONTENTS, result)
+
+ result = self.device.RunShellCommand(['ls', _DEVICE_DIR])
+ self.assertIn(file_name1, result)
+ self.assertIn(_SUB_DIR1, result)
+ self.assertIn(_SUB_DIR, result)
+ self.assertEqual(3, len(result))
+
+ result = self.device.RunShellCommand(['cat', device_file_path3],
+ single_line=True)
+ self.assertEqual(_OLD_CONTENTS, result)
+
+ result = self.device.RunShellCommand(["ls", "%s/%s/%s"
+ % (_DEVICE_DIR, _SUB_DIR, _SUB_DIR2)],
+ single_line=True)
+ self.assertEqual('', result)
+
+ self.device.RunShellCommand(['rm', '-rf', _DEVICE_DIR])
+ cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/device/device_utils_test.py b/build/android/pylib/device/device_utils_test.py
new file mode 100755
index 00000000000..6699673431d
--- /dev/null
+++ b/build/android/pylib/device/device_utils_test.py
@@ -0,0 +1,1845 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""
+Unit tests for the contents of device_utils.py (mostly DeviceUtils).
+"""
+
+# pylint: disable=C0321
+# pylint: disable=W0212
+# pylint: disable=W0613
+
+import collections
+import datetime
+import logging
+import os
+import re
+import sys
+import unittest
+
+from pylib import android_commands
+from pylib import cmd_helper
+from pylib import constants
+from pylib import device_signal
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.device import intent
+from pylib.sdk import split_select
+from pylib.utils import mock_calls
+
+# RunCommand from third_party/android_testrunner/run_command.py is mocked
+# below, so its path needs to be in sys.path.
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class DeviceUtilsInitTest(unittest.TestCase):
+
+ def testInitWithStr(self):
+ serial_as_str = str('0123456789abcdef')
+ d = device_utils.DeviceUtils('0123456789abcdef')
+ self.assertEqual(serial_as_str, d.adb.GetDeviceSerial())
+
+ def testInitWithUnicode(self):
+ serial_as_unicode = unicode('fedcba9876543210')
+ d = device_utils.DeviceUtils(serial_as_unicode)
+ self.assertEqual(serial_as_unicode, d.adb.GetDeviceSerial())
+
+ def testInitWithAdbWrapper(self):
+ serial = '123456789abcdef0'
+ a = adb_wrapper.AdbWrapper(serial)
+ d = device_utils.DeviceUtils(a)
+ self.assertEqual(serial, d.adb.GetDeviceSerial())
+
+ def testInitWithAndroidCommands(self):
+ serial = '0fedcba987654321'
+ a = android_commands.AndroidCommands(device=serial)
+ d = device_utils.DeviceUtils(a)
+ self.assertEqual(serial, d.adb.GetDeviceSerial())
+
+ def testInitWithMissing_fails(self):
+ with self.assertRaises(ValueError):
+ device_utils.DeviceUtils(None)
+ with self.assertRaises(ValueError):
+ device_utils.DeviceUtils('')
+
+
+class DeviceUtilsGetAVDsTest(mock_calls.TestCase):
+
+ def testGetAVDs(self):
+ with self.assertCall(
+ mock.call.pylib.cmd_helper.GetCmdOutput([mock.ANY, 'list', 'avd']),
+ 'Available Android Virtual Devices:\n'
+ ' Name: my_android5.0\n'
+ ' Path: /some/path/to/.android/avd/my_android5.0.avd\n'
+ ' Target: Android 5.0 (API level 21)\n'
+ ' Tag/ABI: default/x86\n'
+ ' Skin: WVGA800\n'):
+ self.assertEquals(['my_android5.0'],
+ device_utils.GetAVDs())
+
+
+class DeviceUtilsRestartServerTest(mock_calls.TestCase):
+
+ @mock.patch('time.sleep', mock.Mock())
+ def testRestartServer_succeeds(self):
+ with self.assertCalls(
+ mock.call.pylib.device.adb_wrapper.AdbWrapper.KillServer(),
+ (mock.call.pylib.cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb']),
+ (1, '')),
+ mock.call.pylib.device.adb_wrapper.AdbWrapper.StartServer(),
+ (mock.call.pylib.cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb']),
+ (1, '')),
+ (mock.call.pylib.cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb']),
+ (0, '123\n'))):
+ device_utils.RestartServer()
+
+
+class MockTempFile(object):
+
+ def __init__(self, name='/tmp/some/file'):
+ self.file = mock.MagicMock(spec=file)
+ self.file.name = name
+ self.file.name_quoted = cmd_helper.SingleQuote(name)
+
+ def __enter__(self):
+ return self.file
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
+ @property
+ def name(self):
+ return self.file.name
+
+
+class _PatchedFunction(object):
+ def __init__(self, patched=None, mocked=None):
+ self.patched = patched
+ self.mocked = mocked
+
+
+def _AdbWrapperMock(test_serial):
+ adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+ adb.__str__ = mock.Mock(return_value=test_serial)
+ adb.GetDeviceSerial.return_value = test_serial
+ return adb
+
+
+class DeviceUtilsTest(mock_calls.TestCase):
+
+ def setUp(self):
+ self.adb = _AdbWrapperMock('0123456789abcdef')
+ self.device = device_utils.DeviceUtils(
+ self.adb, default_timeout=10, default_retries=0)
+ self.watchMethodCalls(self.call.adb, ignore=['GetDeviceSerial'])
+
+ def AdbCommandError(self, args=None, output=None, status=None, msg=None):
+ if args is None:
+ args = ['[unspecified]']
+ return mock.Mock(side_effect=device_errors.AdbCommandFailedError(
+ args, output, status, msg, str(self.device)))
+
+ def CommandError(self, msg=None):
+ if msg is None:
+ msg = 'Command failed'
+ return mock.Mock(side_effect=device_errors.CommandFailedError(
+ msg, str(self.device)))
+
+ def ShellError(self, output=None, status=1):
+ def action(cmd, *args, **kwargs):
+ raise device_errors.AdbShellCommandFailedError(
+ cmd, output, status, str(self.device))
+ if output is None:
+ output = 'Permission denied\n'
+ return action
+
+ def TimeoutError(self, msg=None):
+ if msg is None:
+ msg = 'Operation timed out'
+ return mock.Mock(side_effect=device_errors.CommandTimeoutError(
+ msg, str(self.device)))
+
+
+class DeviceUtilsEqTest(DeviceUtilsTest):
+
+ def testEq_equal_deviceUtils(self):
+ other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdef'))
+ self.assertTrue(self.device == other)
+ self.assertTrue(other == self.device)
+
+ def testEq_equal_adbWrapper(self):
+ other = adb_wrapper.AdbWrapper('0123456789abcdef')
+ self.assertTrue(self.device == other)
+ self.assertTrue(other == self.device)
+
+ def testEq_equal_string(self):
+ other = '0123456789abcdef'
+ self.assertTrue(self.device == other)
+ self.assertTrue(other == self.device)
+
+ def testEq_devicesNotEqual(self):
+ other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdee'))
+ self.assertFalse(self.device == other)
+ self.assertFalse(other == self.device)
+
+ def testEq_identity(self):
+ self.assertTrue(self.device == self.device)
+
+ def testEq_serialInList(self):
+ devices = [self.device]
+ self.assertTrue('0123456789abcdef' in devices)
+
+
+class DeviceUtilsLtTest(DeviceUtilsTest):
+
+ def testLt_lessThan(self):
+ other = device_utils.DeviceUtils(_AdbWrapperMock('ffffffffffffffff'))
+ self.assertTrue(self.device < other)
+ self.assertTrue(other > self.device)
+
+ def testLt_greaterThan_lhs(self):
+ other = device_utils.DeviceUtils(_AdbWrapperMock('0000000000000000'))
+ self.assertFalse(self.device < other)
+ self.assertFalse(other > self.device)
+
+ def testLt_equal(self):
+ other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdef'))
+ self.assertFalse(self.device < other)
+ self.assertFalse(other > self.device)
+
+ def testLt_sorted(self):
+ devices = [
+ device_utils.DeviceUtils(_AdbWrapperMock('ffffffffffffffff')),
+ device_utils.DeviceUtils(_AdbWrapperMock('0000000000000000')),
+ ]
+ sorted_devices = sorted(devices)
+ self.assertEquals('0000000000000000',
+ sorted_devices[0].adb.GetDeviceSerial())
+ self.assertEquals('ffffffffffffffff',
+ sorted_devices[1].adb.GetDeviceSerial())
+
+
+class DeviceUtilsStrTest(DeviceUtilsTest):
+
+ def testStr_returnsSerial(self):
+ with self.assertCalls(
+ (self.call.adb.GetDeviceSerial(), '0123456789abcdef')):
+ self.assertEqual('0123456789abcdef', str(self.device))
+
+
+class DeviceUtilsIsOnlineTest(DeviceUtilsTest):
+
+ def testIsOnline_true(self):
+ with self.assertCall(self.call.adb.GetState(), 'device'):
+ self.assertTrue(self.device.IsOnline())
+
+ def testIsOnline_false(self):
+ with self.assertCall(self.call.adb.GetState(), 'offline'):
+ self.assertFalse(self.device.IsOnline())
+
+ def testIsOnline_error(self):
+ with self.assertCall(self.call.adb.GetState(), self.CommandError()):
+ self.assertFalse(self.device.IsOnline())
+
+
+class DeviceUtilsHasRootTest(DeviceUtilsTest):
+
+ def testHasRoot_true(self):
+ with self.assertCall(self.call.adb.Shell('ls /root'), 'foo\n'):
+ self.assertTrue(self.device.HasRoot())
+
+ def testHasRoot_false(self):
+ with self.assertCall(self.call.adb.Shell('ls /root'), self.ShellError()):
+ self.assertFalse(self.device.HasRoot())
+
+
+class DeviceUtilsEnableRootTest(DeviceUtilsTest):
+
+ def testEnableRoot_succeeds(self):
+ with self.assertCalls(
+ (self.call.device.IsUserBuild(), False),
+ self.call.adb.Root(),
+ self.call.device.WaitUntilFullyBooted()):
+ self.device.EnableRoot()
+
+ def testEnableRoot_userBuild(self):
+ with self.assertCalls(
+ (self.call.device.IsUserBuild(), True)):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.EnableRoot()
+
+ def testEnableRoot_rootFails(self):
+ with self.assertCalls(
+ (self.call.device.IsUserBuild(), False),
+ (self.call.adb.Root(), self.CommandError())):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.EnableRoot()
+
+
+class DeviceUtilsIsUserBuildTest(DeviceUtilsTest):
+
+ def testIsUserBuild_yes(self):
+ with self.assertCall(
+ self.call.device.GetProp('ro.build.type', cache=True), 'user'):
+ self.assertTrue(self.device.IsUserBuild())
+
+ def testIsUserBuild_no(self):
+ with self.assertCall(
+ self.call.device.GetProp('ro.build.type', cache=True), 'userdebug'):
+ self.assertFalse(self.device.IsUserBuild())
+
+
+class DeviceUtilsGetExternalStoragePathTest(DeviceUtilsTest):
+
+ def testGetExternalStoragePath_succeeds(self):
+ with self.assertCall(
+ self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '/fake/storage/path\n'):
+ self.assertEquals('/fake/storage/path',
+ self.device.GetExternalStoragePath())
+
+ def testGetExternalStoragePath_fails(self):
+ with self.assertCall(self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '\n'):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.GetExternalStoragePath()
+
+
+class DeviceUtilsGetApplicationPathsTest(DeviceUtilsTest):
+
+ def testGetApplicationPaths_exists(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '19\n'),
+ (self.call.adb.Shell('pm path android'),
+ 'package:/path/to/android.apk\n')):
+ self.assertEquals(['/path/to/android.apk'],
+ self.device.GetApplicationPaths('android'))
+
+ def testGetApplicationPaths_notExists(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '19\n'),
+ (self.call.adb.Shell('pm path not.installed.app'), '')):
+ self.assertEquals([],
+ self.device.GetApplicationPaths('not.installed.app'))
+
+ def testGetApplicationPaths_fails(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '19\n'),
+ (self.call.adb.Shell('pm path android'),
+ self.CommandError('ERROR. Is package manager running?\n'))):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.GetApplicationPaths('android')
+
+
+class DeviceUtilsGetApplicationDataDirectoryTest(DeviceUtilsTest):
+
+ def testGetApplicationDataDirectory_exists(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand(
+ 'pm dump foo.bar.baz | grep dataDir='),
+ ['dataDir=/data/data/foo.bar.baz']):
+ self.assertEquals(
+ '/data/data/foo.bar.baz',
+ self.device.GetApplicationDataDirectory('foo.bar.baz'))
+
+ def testGetApplicationDataDirectory_notExists(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand(
+ 'pm dump foo.bar.baz | grep dataDir='),
+ self.ShellError()):
+ self.assertIsNone(self.device.GetApplicationDataDirectory('foo.bar.baz'))
+
+
+@mock.patch('time.sleep', mock.Mock())
+class DeviceUtilsWaitUntilFullyBootedTest(DeviceUtilsTest):
+
+ def testWaitUntilFullyBooted_succeedsNoWifi(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'),
+ ['package:/some/fake/path']),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '1')):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_succeedsWithWifi(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'),
+ ['package:/some/fake/path']),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '1'),
+ # wifi_enabled
+ (self.call.adb.Shell('dumpsys wifi'),
+ 'stuff\nWi-Fi is enabled\nmore stuff\n')):
+ self.device.WaitUntilFullyBooted(wifi=True)
+
+ def testWaitUntilFullyBooted_deviceNotInitiallyAvailable(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'),
+ ['package:/some/fake/path']),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '1')):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_sdCardReadyFails_noPath(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), self.CommandError())):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_sdCardReadyFails_notExists(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'),
+ self.TimeoutError())):
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_devicePmFails(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'), self.CommandError()),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'), self.CommandError()),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'), self.TimeoutError())):
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_bootFails(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'),
+ ['package:/some/fake/path']),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '0'),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '0'),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), self.TimeoutError())):
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ self.device.WaitUntilFullyBooted(wifi=False)
+
+ def testWaitUntilFullyBooted_wifiFails(self):
+ with self.assertCalls(
+ self.call.adb.WaitForDevice(),
+ # sd_card_ready
+ (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+ (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+ # pm_ready
+ (self.call.device.GetApplicationPaths('android'),
+ ['package:/some/fake/path']),
+ # boot_completed
+ (self.call.device.GetProp('sys.boot_completed'), '1'),
+ # wifi_enabled
+ (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+ # wifi_enabled
+ (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+ # wifi_enabled
+ (self.call.adb.Shell('dumpsys wifi'), self.TimeoutError())):
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ self.device.WaitUntilFullyBooted(wifi=True)
+
+
+@mock.patch('time.sleep', mock.Mock())
+class DeviceUtilsRebootTest(DeviceUtilsTest):
+
+ def testReboot_nonBlocking(self):
+ with self.assertCalls(
+ self.call.adb.Reboot(),
+ (self.call.device.IsOnline(), True),
+ (self.call.device.IsOnline(), False)):
+ self.device.Reboot(block=False)
+
+ def testReboot_blocking(self):
+ with self.assertCalls(
+ self.call.adb.Reboot(),
+ (self.call.device.IsOnline(), True),
+ (self.call.device.IsOnline(), False),
+ self.call.device.WaitUntilFullyBooted(wifi=False)):
+ self.device.Reboot(block=True)
+
+ def testReboot_blockUntilWifi(self):
+ with self.assertCalls(
+ self.call.adb.Reboot(),
+ (self.call.device.IsOnline(), True),
+ (self.call.device.IsOnline(), False),
+ self.call.device.WaitUntilFullyBooted(wifi=True)):
+ self.device.Reboot(block=True, wifi=True)
+
+
+class DeviceUtilsInstallTest(DeviceUtilsTest):
+
+ def testInstall_noPriorInstall(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.apk_helper.GetPackageName('/fake/test/app.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'), []),
+ self.call.adb.Install('/fake/test/app.apk', reinstall=False)):
+ self.device.Install('/fake/test/app.apk', retries=0)
+
+ def testInstall_differentPriorInstall(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.apk_helper.GetPackageName('/fake/test/app.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'),
+ ['/fake/data/app/this.is.a.test.package.apk']),
+ (self.call.device._GetChangedAndStaleFiles(
+ '/fake/test/app.apk', '/fake/data/app/this.is.a.test.package.apk'),
+ ([('/fake/test/app.apk', '/fake/data/app/this.is.a.test.package.apk')],
+ [])),
+ self.call.adb.Uninstall('this.is.a.test.package'),
+ self.call.adb.Install('/fake/test/app.apk', reinstall=False)):
+ self.device.Install('/fake/test/app.apk', retries=0)
+
+ def testInstall_differentPriorInstall_reinstall(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.apk_helper.GetPackageName('/fake/test/app.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'),
+ ['/fake/data/app/this.is.a.test.package.apk']),
+ (self.call.device._GetChangedAndStaleFiles(
+ '/fake/test/app.apk', '/fake/data/app/this.is.a.test.package.apk'),
+ ([('/fake/test/app.apk', '/fake/data/app/this.is.a.test.package.apk')],
+ [])),
+ self.call.adb.Install('/fake/test/app.apk', reinstall=True)):
+ self.device.Install('/fake/test/app.apk', reinstall=True, retries=0)
+
+ def testInstall_identicalPriorInstall(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.apk_helper.GetPackageName('/fake/test/app.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'),
+ ['/fake/data/app/this.is.a.test.package.apk']),
+ (self.call.device._GetChangedAndStaleFiles(
+ '/fake/test/app.apk', '/fake/data/app/this.is.a.test.package.apk'),
+ ([], []))):
+ self.device.Install('/fake/test/app.apk', retries=0)
+
+ def testInstall_fails(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.apk_helper.GetPackageName('/fake/test/app.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'), []),
+ (self.call.adb.Install('/fake/test/app.apk', reinstall=False),
+ self.CommandError('Failure\r\n'))):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.Install('/fake/test/app.apk', retries=0)
+
+class DeviceUtilsInstallSplitApkTest(DeviceUtilsTest):
+
+ def testInstallSplitApk_noPriorInstall(self):
+ with self.assertCalls(
+ (self.call.device._CheckSdkLevel(21)),
+ (mock.call.pylib.sdk.split_select.SelectSplits(
+ self.device, 'base.apk',
+ ['split1.apk', 'split2.apk', 'split3.apk']),
+ ['split2.apk']),
+ (mock.call.pylib.utils.apk_helper.GetPackageName('base.apk'),
+ 'this.is.a.test.package'),
+ (self.call.device.GetApplicationPaths('this.is.a.test.package'), []),
+ (self.call.adb.InstallMultiple(
+ ['base.apk', 'split2.apk'], partial=None, reinstall=False))):
+ self.device.InstallSplitApk('base.apk',
+ ['split1.apk', 'split2.apk', 'split3.apk'], retries=0)
+
+ def testInstallSplitApk_partialInstall(self):
+ with self.assertCalls(
+ (self.call.device._CheckSdkLevel(21)),
+ (mock.call.pylib.sdk.split_select.SelectSplits(
+ self.device, 'base.apk',
+ ['split1.apk', 'split2.apk', 'split3.apk']),
+ ['split2.apk']),
+ (mock.call.pylib.utils.apk_helper.GetPackageName('base.apk'),
+ 'test.package'),
+ (self.call.device.GetApplicationPaths('test.package'),
+ ['base-on-device.apk', 'split2-on-device.apk']),
+ (mock.call.pylib.utils.md5sum.CalculateDeviceMd5Sums(
+ ['base-on-device.apk', 'split2-on-device.apk'], self.device),
+ {'base-on-device.apk': 'AAA', 'split2-on-device.apk': 'BBB'}),
+ (mock.call.pylib.utils.md5sum.CalculateHostMd5Sums(
+ ['base.apk', 'split2.apk']),
+ {'base.apk': 'AAA', 'split2.apk': 'CCC'}),
+ (self.call.adb.InstallMultiple(
+ ['split2.apk'], partial='test.package', reinstall=True))):
+ self.device.InstallSplitApk('base.apk',
+ ['split1.apk', 'split2.apk', 'split3.apk'], reinstall=True, retries=0)
+
+
+class DeviceUtilsRunShellCommandTest(DeviceUtilsTest):
+
+ def setUp(self):
+ super(DeviceUtilsRunShellCommandTest, self).setUp()
+ self.device.NeedsSU = mock.Mock(return_value=False)
+
+ def testRunShellCommand_commandAsList(self):
+ with self.assertCall(self.call.adb.Shell('pm list packages'), ''):
+ self.device.RunShellCommand(['pm', 'list', 'packages'])
+
+ def testRunShellCommand_commandAsListQuoted(self):
+ with self.assertCall(self.call.adb.Shell("echo 'hello world' '$10'"), ''):
+ self.device.RunShellCommand(['echo', 'hello world', '$10'])
+
+ def testRunShellCommand_commandAsString(self):
+ with self.assertCall(self.call.adb.Shell('echo "$VAR"'), ''):
+ self.device.RunShellCommand('echo "$VAR"')
+
+ def testNewRunShellImpl_withEnv(self):
+ with self.assertCall(
+ self.call.adb.Shell('VAR=some_string echo "$VAR"'), ''):
+ self.device.RunShellCommand('echo "$VAR"', env={'VAR': 'some_string'})
+
+ def testNewRunShellImpl_withEnvQuoted(self):
+ with self.assertCall(
+ self.call.adb.Shell('PATH="$PATH:/other/path" run_this'), ''):
+ self.device.RunShellCommand('run_this', env={'PATH': '$PATH:/other/path'})
+
+ def testNewRunShellImpl_withEnv_failure(self):
+ with self.assertRaises(KeyError):
+ self.device.RunShellCommand('some_cmd', env={'INVALID NAME': 'value'})
+
+ def testNewRunShellImpl_withCwd(self):
+ with self.assertCall(self.call.adb.Shell('cd /some/test/path && ls'), ''):
+ self.device.RunShellCommand('ls', cwd='/some/test/path')
+
+ def testNewRunShellImpl_withCwdQuoted(self):
+ with self.assertCall(
+ self.call.adb.Shell("cd '/some test/path with/spaces' && ls"), ''):
+ self.device.RunShellCommand('ls', cwd='/some test/path with/spaces')
+
+ def testRunShellCommand_withHugeCmd(self):
+ payload = 'hi! ' * 1024
+ expected_cmd = "echo '%s'" % payload
+ with self.assertCalls(
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(
+ self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
+ self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
+ (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
+ self.assertEquals([payload],
+ self.device.RunShellCommand(['echo', payload]))
+
+ def testRunShellCommand_withHugeCmdAmdSU(self):
+ payload = 'hi! ' * 1024
+ expected_cmd = """su -c sh -c 'echo '"'"'%s'"'"''""" % payload
+ with self.assertCalls(
+ (self.call.device.NeedsSU(), True),
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(
+ self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
+ self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
+ (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
+ self.assertEquals(
+ [payload],
+ self.device.RunShellCommand(['echo', payload], as_root=True))
+
+ def testRunShellCommand_withSu(self):
+ with self.assertCalls(
+ (self.call.device.NeedsSU(), True),
+ (self.call.adb.Shell("su -c sh -c 'setprop service.adb.root 0'"), '')):
+ self.device.RunShellCommand('setprop service.adb.root 0', as_root=True)
+
+ def testRunShellCommand_manyLines(self):
+ cmd = 'ls /some/path'
+ with self.assertCall(self.call.adb.Shell(cmd), 'file1\nfile2\nfile3\n'):
+ self.assertEquals(['file1', 'file2', 'file3'],
+ self.device.RunShellCommand(cmd))
+
+ def testRunShellCommand_singleLine_success(self):
+ cmd = 'echo $VALUE'
+ with self.assertCall(self.call.adb.Shell(cmd), 'some value\n'):
+ self.assertEquals('some value',
+ self.device.RunShellCommand(cmd, single_line=True))
+
+ def testRunShellCommand_singleLine_successEmptyLine(self):
+ cmd = 'echo $VALUE'
+ with self.assertCall(self.call.adb.Shell(cmd), '\n'):
+ self.assertEquals('',
+ self.device.RunShellCommand(cmd, single_line=True))
+
+ def testRunShellCommand_singleLine_successWithoutEndLine(self):
+ cmd = 'echo -n $VALUE'
+ with self.assertCall(self.call.adb.Shell(cmd), 'some value'):
+ self.assertEquals('some value',
+ self.device.RunShellCommand(cmd, single_line=True))
+
+ def testRunShellCommand_singleLine_successNoOutput(self):
+ cmd = 'echo -n $VALUE'
+ with self.assertCall(self.call.adb.Shell(cmd), ''):
+ self.assertEquals('',
+ self.device.RunShellCommand(cmd, single_line=True))
+
+ def testRunShellCommand_singleLine_failTooManyLines(self):
+ cmd = 'echo $VALUE'
+ with self.assertCall(self.call.adb.Shell(cmd),
+ 'some value\nanother value\n'):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.RunShellCommand(cmd, single_line=True)
+
+ def testRunShellCommand_checkReturn_success(self):
+ cmd = 'echo $ANDROID_DATA'
+ output = '/data\n'
+ with self.assertCall(self.call.adb.Shell(cmd), output):
+ self.assertEquals([output.rstrip()],
+ self.device.RunShellCommand(cmd, check_return=True))
+
+ def testRunShellCommand_checkReturn_failure(self):
+ cmd = 'ls /root'
+ output = 'opendir failed, Permission denied\n'
+ with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self.device.RunShellCommand(cmd, check_return=True)
+
+ def testRunShellCommand_checkReturn_disabled(self):
+ cmd = 'ls /root'
+ output = 'opendir failed, Permission denied\n'
+ with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
+ self.assertEquals([output.rstrip()],
+ self.device.RunShellCommand(cmd, check_return=False))
+
+ def testRunShellCommand_largeOutput_enabled(self):
+ cmd = 'echo $VALUE'
+ temp_file = MockTempFile('/sdcard/temp-123')
+ cmd_redirect = '%s > %s' % (cmd, temp_file.name)
+ with self.assertCalls(
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(self.adb),
+ temp_file),
+ (self.call.adb.Shell(cmd_redirect)),
+ (self.call.device.ReadFile(temp_file.name, force_pull=True),
+ 'something')):
+ self.assertEquals(
+ ['something'],
+ self.device.RunShellCommand(
+ cmd, large_output=True, check_return=True))
+
+ def testRunShellCommand_largeOutput_disabledNoTrigger(self):
+ cmd = 'something'
+ with self.assertCall(self.call.adb.Shell(cmd), self.ShellError('')):
+ with self.assertRaises(device_errors.AdbCommandFailedError):
+ self.device.RunShellCommand(cmd, check_return=True)
+
+ def testRunShellCommand_largeOutput_disabledTrigger(self):
+ cmd = 'echo $VALUE'
+ temp_file = MockTempFile('/sdcard/temp-123')
+ cmd_redirect = '%s > %s' % (cmd, temp_file.name)
+ with self.assertCalls(
+ (self.call.adb.Shell(cmd), self.ShellError('', None)),
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(self.adb),
+ temp_file),
+ (self.call.adb.Shell(cmd_redirect)),
+ (self.call.device.ReadFile(mock.ANY, force_pull=True),
+ 'something')):
+ self.assertEquals(['something'],
+ self.device.RunShellCommand(cmd, check_return=True))
+
+
+class DeviceUtilsRunPipedShellCommandTest(DeviceUtilsTest):
+
+ def testRunPipedShellCommand_success(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
+ check_return=True),
+ ['This line contains foo', 'PIPESTATUS: 0 0']):
+ self.assertEquals(['This line contains foo'],
+ self.device._RunPipedShellCommand('ps | grep foo'))
+
+ def testRunPipedShellCommand_firstCommandFails(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
+ check_return=True),
+ ['PIPESTATUS: 1 0']):
+ with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
+ self.device._RunPipedShellCommand('ps | grep foo')
+ self.assertEquals([1, 0], ec.exception.status)
+
+ def testRunPipedShellCommand_secondCommandFails(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
+ check_return=True),
+ ['PIPESTATUS: 0 1']):
+ with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
+ self.device._RunPipedShellCommand('ps | grep foo')
+ self.assertEquals([0, 1], ec.exception.status)
+
+ def testRunPipedShellCommand_outputCutOff(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
+ check_return=True),
+ ['foo.bar'] * 256 + ['foo.ba']):
+ with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
+ self.device._RunPipedShellCommand('ps | grep foo')
+ self.assertIs(None, ec.exception.status)
+
+
+@mock.patch('time.sleep', mock.Mock())
+class DeviceUtilsKillAllTest(DeviceUtilsTest):
+
+ def testKillAll_noMatchingProcessesFailure(self):
+ with self.assertCall(self.call.device.GetPids('test_process'), {}):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.KillAll('test_process')
+
+ def testKillAll_noMatchingProcessesQuiet(self):
+ with self.assertCall(self.call.device.GetPids('test_process'), {}):
+ self.assertEqual(0, self.device.KillAll('test_process', quiet=True))
+
+ def testKillAll_nonblocking(self):
+ with self.assertCalls(
+ (self.call.device.GetPids('some.process'), {'some.process': '1234'}),
+ (self.call.adb.Shell('kill -9 1234'), '')):
+ self.assertEquals(
+ 1, self.device.KillAll('some.process', blocking=False))
+
+ def testKillAll_blocking(self):
+ with self.assertCalls(
+ (self.call.device.GetPids('some.process'), {'some.process': '1234'}),
+ (self.call.adb.Shell('kill -9 1234'), ''),
+ (self.call.device.GetPids('some.process'), {'some.process': '1234'}),
+ (self.call.device.GetPids('some.process'), [])):
+ self.assertEquals(
+ 1, self.device.KillAll('some.process', blocking=True))
+
+ def testKillAll_root(self):
+ with self.assertCalls(
+ (self.call.device.GetPids('some.process'), {'some.process': '1234'}),
+ (self.call.device.NeedsSU(), True),
+ (self.call.adb.Shell("su -c sh -c 'kill -9 1234'"), '')):
+ self.assertEquals(
+ 1, self.device.KillAll('some.process', as_root=True))
+
+ def testKillAll_sigterm(self):
+ with self.assertCalls(
+ (self.call.device.GetPids('some.process'), {'some.process': '1234'}),
+ (self.call.adb.Shell('kill -15 1234'), '')):
+ self.assertEquals(
+ 1, self.device.KillAll('some.process', signum=device_signal.SIGTERM))
+
+
+class DeviceUtilsStartActivityTest(DeviceUtilsTest):
+
+ def testStartActivity_actionOnly(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_success(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_failure(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main'),
+ 'Error: Failed to start test activity'):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_blocking(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-W '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent, blocking=True)
+
+ def testStartActivity_withCategory(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ category='android.intent.category.HOME')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-c android.intent.category.HOME '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withMultipleCategories(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ category=['android.intent.category.HOME',
+ 'android.intent.category.BROWSABLE'])
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-c android.intent.category.HOME '
+ '-c android.intent.category.BROWSABLE '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withData(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ data='http://www.google.com/')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-d http://www.google.com/ '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withStringExtra(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ extras={'foo': 'test'})
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main '
+ '--es foo test'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withBoolExtra(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ extras={'foo': True})
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main '
+ '--ez foo True'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withIntExtra(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ extras={'foo': 123})
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main '
+ '--ei foo 123'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+ def testStartActivity_withTraceFile(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '--start-profiler test_trace_file.out '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent,
+ trace_file_name='test_trace_file.out')
+
+ def testStartActivity_withForceStop(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-S '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent, force_stop=True)
+
+ def testStartActivity_withFlags(self):
+ test_intent = intent.Intent(action='android.intent.action.VIEW',
+ package='this.is.a.test.package',
+ activity='.Main',
+ flags='0x10000000')
+ with self.assertCall(
+ self.call.adb.Shell('am start '
+ '-a android.intent.action.VIEW '
+ '-n this.is.a.test.package/.Main '
+ '-f 0x10000000'),
+ 'Starting: Intent { act=android.intent.action.VIEW }'):
+ self.device.StartActivity(test_intent)
+
+
+class DeviceUtilsStartInstrumentationTest(DeviceUtilsTest):
+
+ def testStartInstrumentation_nothing(self):
+ with self.assertCalls(
+ self.call.device.RunShellCommand(
+ ['am', 'instrument', 'test.package/.TestInstrumentation'],
+ check_return=True, large_output=True)):
+ self.device.StartInstrumentation(
+ 'test.package/.TestInstrumentation',
+ finish=False, raw=False, extras=None)
+
+ def testStartInstrumentation_finish(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['am', 'instrument', '-w', 'test.package/.TestInstrumentation'],
+ check_return=True, large_output=True),
+ ['OK (1 test)'])):
+ output = self.device.StartInstrumentation(
+ 'test.package/.TestInstrumentation',
+ finish=True, raw=False, extras=None)
+ self.assertEquals(['OK (1 test)'], output)
+
+ def testStartInstrumentation_raw(self):
+ with self.assertCalls(
+ self.call.device.RunShellCommand(
+ ['am', 'instrument', '-r', 'test.package/.TestInstrumentation'],
+ check_return=True, large_output=True)):
+ self.device.StartInstrumentation(
+ 'test.package/.TestInstrumentation',
+ finish=False, raw=True, extras=None)
+
+ def testStartInstrumentation_extras(self):
+ with self.assertCalls(
+ self.call.device.RunShellCommand(
+ ['am', 'instrument', '-e', 'foo', 'Foo', '-e', 'bar', 'Bar',
+ 'test.package/.TestInstrumentation'],
+ check_return=True, large_output=True)):
+ self.device.StartInstrumentation(
+ 'test.package/.TestInstrumentation',
+ finish=False, raw=False, extras={'foo': 'Foo', 'bar': 'Bar'})
+
+
+class DeviceUtilsBroadcastIntentTest(DeviceUtilsTest):
+
+ def testBroadcastIntent_noExtras(self):
+ test_intent = intent.Intent(action='test.package.with.an.INTENT')
+ with self.assertCall(
+ self.call.adb.Shell('am broadcast -a test.package.with.an.INTENT'),
+ 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
+ self.device.BroadcastIntent(test_intent)
+
+ def testBroadcastIntent_withExtra(self):
+ test_intent = intent.Intent(action='test.package.with.an.INTENT',
+ extras={'foo': 'bar value'})
+ with self.assertCall(
+ self.call.adb.Shell(
+ "am broadcast -a test.package.with.an.INTENT --es foo 'bar value'"),
+ 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
+ self.device.BroadcastIntent(test_intent)
+
+ def testBroadcastIntent_withExtra_noValue(self):
+ test_intent = intent.Intent(action='test.package.with.an.INTENT',
+ extras={'foo': None})
+ with self.assertCall(
+ self.call.adb.Shell(
+ 'am broadcast -a test.package.with.an.INTENT --esn foo'),
+ 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
+ self.device.BroadcastIntent(test_intent)
+
+
+class DeviceUtilsGoHomeTest(DeviceUtilsTest):
+
+ def testGoHome_popupsExist(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
+ '-c', 'android.intent.category.HOME'], check_return=True),
+ 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '66'], check_return=True)),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '4'], check_return=True)),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True),
+ ['mCurrentFocus Launcher'])):
+ self.device.GoHome()
+
+ def testGoHome_willRetry(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
+ '-c', 'android.intent.category.HOME'], check_return=True),
+ 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '66'], check_return=True,)),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '4'], check_return=True)),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '66'], check_return=True)),
+ (self.call.device.RunShellCommand(
+ ['input', 'keyevent', '4'], check_return=True)),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True),
+ self.TimeoutError())):
+ with self.assertRaises(device_errors.CommandTimeoutError):
+ self.device.GoHome()
+
+ def testGoHome_alreadyFocused(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True),
+ ['mCurrentFocus Launcher']):
+ self.device.GoHome()
+
+ def testGoHome_alreadyFocusedAlternateCase(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True),
+ [' mCurrentFocus .launcher/.']):
+ self.device.GoHome()
+
+ def testGoHome_obtainsFocusAfterGoingHome(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True), []),
+ (self.call.device.RunShellCommand(
+ ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
+ '-c', 'android.intent.category.HOME'], check_return=True),
+ 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
+ (self.call.device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True,
+ large_output=True),
+ ['mCurrentFocus Launcher'])):
+ self.device.GoHome()
+
+class DeviceUtilsForceStopTest(DeviceUtilsTest):
+
+ def testForceStop(self):
+ with self.assertCall(
+ self.call.adb.Shell('am force-stop this.is.a.test.package'),
+ ''):
+ self.device.ForceStop('this.is.a.test.package')
+
+
+class DeviceUtilsClearApplicationStateTest(DeviceUtilsTest):
+
+ def testClearApplicationState_packageDoesntExist(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '17\n'),
+ (self.call.device.GetApplicationPaths('this.package.does.not.exist'),
+ [])):
+ self.device.ClearApplicationState('this.package.does.not.exist')
+
+ def testClearApplicationState_packageDoesntExistOnAndroidJBMR2OrAbove(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '18\n'),
+ (self.call.adb.Shell('pm clear this.package.does.not.exist'),
+ 'Failed\r\n')):
+ self.device.ClearApplicationState('this.package.does.not.exist')
+
+ def testClearApplicationState_packageExists(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '17\n'),
+ (self.call.device.GetApplicationPaths('this.package.exists'),
+ ['/data/app/this.package.exists.apk']),
+ (self.call.adb.Shell('pm clear this.package.exists'),
+ 'Success\r\n')):
+ self.device.ClearApplicationState('this.package.exists')
+
+ def testClearApplicationState_packageExistsOnAndroidJBMR2OrAbove(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.version.sdk'), '18\n'),
+ (self.call.adb.Shell('pm clear this.package.exists'),
+ 'Success\r\n')):
+ self.device.ClearApplicationState('this.package.exists')
+
+
+class DeviceUtilsSendKeyEventTest(DeviceUtilsTest):
+
+ def testSendKeyEvent(self):
+ with self.assertCall(self.call.adb.Shell('input keyevent 66'), ''):
+ self.device.SendKeyEvent(66)
+
+
+class DeviceUtilsPushChangedFilesIndividuallyTest(DeviceUtilsTest):
+
+ def testPushChangedFilesIndividually_empty(self):
+ test_files = []
+ with self.assertCalls():
+ self.device._PushChangedFilesIndividually(test_files)
+
+ def testPushChangedFilesIndividually_single(self):
+ test_files = [('/test/host/path', '/test/device/path')]
+ with self.assertCalls(self.call.adb.Push(*test_files[0])):
+ self.device._PushChangedFilesIndividually(test_files)
+
+ def testPushChangedFilesIndividually_multiple(self):
+ test_files = [
+ ('/test/host/path/file1', '/test/device/path/file1'),
+ ('/test/host/path/file2', '/test/device/path/file2')]
+ with self.assertCalls(
+ self.call.adb.Push(*test_files[0]),
+ self.call.adb.Push(*test_files[1])):
+ self.device._PushChangedFilesIndividually(test_files)
+
+
+class DeviceUtilsPushChangedFilesZippedTest(DeviceUtilsTest):
+
+ def testPushChangedFilesZipped_empty(self):
+ test_files = []
+ with self.assertCalls():
+ self.device._PushChangedFilesZipped(test_files)
+
+ def _testPushChangedFilesZipped_spec(self, test_files):
+ mock_zip_temp = mock.mock_open()
+ mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
+ with self.assertCalls(
+ (mock.call.tempfile.NamedTemporaryFile(suffix='.zip'), mock_zip_temp),
+ (mock.call.multiprocessing.Process(
+ target=device_utils.DeviceUtils._CreateDeviceZip,
+ args=('/test/temp/file/tmp.zip', test_files)), mock.Mock()),
+ (self.call.device.GetExternalStoragePath(),
+ '/test/device/external_dir'),
+ self.call.adb.Push(
+ '/test/temp/file/tmp.zip', '/test/device/external_dir/tmp.zip'),
+ self.call.device.RunShellCommand(
+ ['unzip', '/test/device/external_dir/tmp.zip'],
+ as_root=True,
+ env={'PATH': '/data/local/tmp/bin:$PATH'},
+ check_return=True),
+ (self.call.device.IsOnline(), True),
+ self.call.device.RunShellCommand(
+ ['rm', '/test/device/external_dir/tmp.zip'], check_return=True)):
+ self.device._PushChangedFilesZipped(test_files)
+
+ def testPushChangedFilesZipped_single(self):
+ self._testPushChangedFilesZipped_spec(
+ [('/test/host/path/file1', '/test/device/path/file1')])
+
+ def testPushChangedFilesZipped_multiple(self):
+ self._testPushChangedFilesZipped_spec(
+ [('/test/host/path/file1', '/test/device/path/file1'),
+ ('/test/host/path/file2', '/test/device/path/file2')])
+
+
+class DeviceUtilsFileExistsTest(DeviceUtilsTest):
+
+ def testFileExists_usingTest_fileExists(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['test', '-e', '/path/file.exists'], check_return=True), ''):
+ self.assertTrue(self.device.FileExists('/path/file.exists'))
+
+ def testFileExists_usingTest_fileDoesntExist(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['test', '-e', '/does/not/exist'], check_return=True),
+ self.ShellError('', 1)):
+ self.assertFalse(self.device.FileExists('/does/not/exist'))
+
+
+class DeviceUtilsPullFileTest(DeviceUtilsTest):
+
+ def testPullFile_existsOnDevice(self):
+ with mock.patch('os.path.exists', return_value=True):
+ with self.assertCall(
+ self.call.adb.Pull('/data/app/test.file.exists',
+ '/test/file/host/path')):
+ self.device.PullFile('/data/app/test.file.exists',
+ '/test/file/host/path')
+
+ def testPullFile_doesntExistOnDevice(self):
+ with mock.patch('os.path.exists', return_value=True):
+ with self.assertCall(
+ self.call.adb.Pull('/data/app/test.file.does.not.exist',
+ '/test/file/host/path'),
+ self.CommandError('remote object does not exist')):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.PullFile('/data/app/test.file.does.not.exist',
+ '/test/file/host/path')
+
+
+class DeviceUtilsReadFileTest(DeviceUtilsTest):
+
+ def testReadFileWithPull_success(self):
+ tmp_host_dir = '/tmp/dir/on.host/'
+ tmp_host = MockTempFile('/tmp/dir/on.host/tmp_ReadFileWithPull')
+ tmp_host.file.read.return_value = 'some interesting contents'
+ with self.assertCalls(
+ (mock.call.tempfile.mkdtemp(), tmp_host_dir),
+ (self.call.adb.Pull('/path/to/device/file', mock.ANY)),
+ (mock.call.__builtin__.open(mock.ANY, 'r'), tmp_host),
+ (mock.call.os.path.exists(tmp_host_dir), True),
+ (mock.call.shutil.rmtree(tmp_host_dir), None)):
+ self.assertEquals('some interesting contents',
+ self.device._ReadFileWithPull('/path/to/device/file'))
+ tmp_host.file.read.assert_called_once_with()
+
+ def testReadFileWithPull_rejected(self):
+ tmp_host_dir = '/tmp/dir/on.host/'
+ with self.assertCalls(
+ (mock.call.tempfile.mkdtemp(), tmp_host_dir),
+ (self.call.adb.Pull('/path/to/device/file', mock.ANY),
+ self.CommandError()),
+ (mock.call.os.path.exists(tmp_host_dir), True),
+ (mock.call.shutil.rmtree(tmp_host_dir), None)):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device._ReadFileWithPull('/path/to/device/file')
+
+ def testReadFile_exists(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['ls', '-l', '/read/this/test/file'],
+ as_root=False, check_return=True),
+ ['-rw-rw---- root foo 256 1970-01-01 00:00 file']),
+ (self.call.device.RunShellCommand(
+ ['cat', '/read/this/test/file'],
+ as_root=False, check_return=True),
+ ['this is a test file'])):
+ self.assertEqual('this is a test file\n',
+ self.device.ReadFile('/read/this/test/file'))
+
+ def testReadFile_doesNotExist(self):
+ with self.assertCall(
+ self.call.device.RunShellCommand(
+ ['ls', '-l', '/this/file/does.not.exist'],
+ as_root=False, check_return=True),
+ self.CommandError('File does not exist')):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.ReadFile('/this/file/does.not.exist')
+
+ def testReadFile_zeroSize(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['ls', '-l', '/this/file/has/zero/size'],
+ as_root=False, check_return=True),
+ ['-r--r--r-- root foo 0 1970-01-01 00:00 zero_size_file']),
+ (self.call.device._ReadFileWithPull('/this/file/has/zero/size'),
+ 'but it has contents\n')):
+ self.assertEqual('but it has contents\n',
+ self.device.ReadFile('/this/file/has/zero/size'))
+
+ def testReadFile_withSU(self):
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['ls', '-l', '/this/file/can.be.read.with.su'],
+ as_root=True, check_return=True),
+ ['-rw------- root root 256 1970-01-01 00:00 can.be.read.with.su']),
+ (self.call.device.RunShellCommand(
+ ['cat', '/this/file/can.be.read.with.su'],
+ as_root=True, check_return=True),
+ ['this is a test file', 'read with su'])):
+ self.assertEqual(
+ 'this is a test file\nread with su\n',
+ self.device.ReadFile('/this/file/can.be.read.with.su',
+ as_root=True))
+
+ def testReadFile_withPull(self):
+ contents = 'a' * 123456
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['ls', '-l', '/read/this/big/test/file'],
+ as_root=False, check_return=True),
+ ['-rw-rw---- root foo 123456 1970-01-01 00:00 file']),
+ (self.call.device._ReadFileWithPull('/read/this/big/test/file'),
+ contents)):
+ self.assertEqual(
+ contents, self.device.ReadFile('/read/this/big/test/file'))
+
+ def testReadFile_withPullAndSU(self):
+ contents = 'b' * 123456
+ with self.assertCalls(
+ (self.call.device.RunShellCommand(
+ ['ls', '-l', '/this/big/file/can.be.read.with.su'],
+ as_root=True, check_return=True),
+ ['-rw------- root root 123456 1970-01-01 00:00 can.be.read.with.su']),
+ (self.call.device.NeedsSU(), True),
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(self.adb),
+ MockTempFile('/sdcard/tmp/on.device')),
+ self.call.device.RunShellCommand(
+ ['cp', '/this/big/file/can.be.read.with.su',
+ '/sdcard/tmp/on.device'],
+ as_root=True, check_return=True),
+ (self.call.device._ReadFileWithPull('/sdcard/tmp/on.device'),
+ contents)):
+ self.assertEqual(
+ contents,
+ self.device.ReadFile('/this/big/file/can.be.read.with.su',
+ as_root=True))
+
+ def testReadFile_forcePull(self):
+ contents = 'a' * 123456
+ with self.assertCall(
+ self.call.device._ReadFileWithPull('/read/this/big/test/file'),
+ contents):
+ self.assertEqual(
+ contents,
+ self.device.ReadFile('/read/this/big/test/file', force_pull=True))
+
+
+class DeviceUtilsWriteFileTest(DeviceUtilsTest):
+
+ def testWriteFileWithPush_success(self):
+ tmp_host = MockTempFile('/tmp/file/on.host')
+ contents = 'some interesting contents'
+ with self.assertCalls(
+ (mock.call.tempfile.NamedTemporaryFile(), tmp_host),
+ self.call.adb.Push('/tmp/file/on.host', '/path/to/device/file')):
+ self.device._WriteFileWithPush('/path/to/device/file', contents)
+ tmp_host.file.write.assert_called_once_with(contents)
+
+ def testWriteFileWithPush_rejected(self):
+ tmp_host = MockTempFile('/tmp/file/on.host')
+ contents = 'some interesting contents'
+ with self.assertCalls(
+ (mock.call.tempfile.NamedTemporaryFile(), tmp_host),
+ (self.call.adb.Push('/tmp/file/on.host', '/path/to/device/file'),
+ self.CommandError())):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device._WriteFileWithPush('/path/to/device/file', contents)
+
+ def testWriteFile_withPush(self):
+ contents = 'some large contents ' * 26 # 20 * 26 = 520 chars
+ with self.assertCalls(
+ self.call.device._WriteFileWithPush('/path/to/device/file', contents)):
+ self.device.WriteFile('/path/to/device/file', contents)
+
+ def testWriteFile_withPushForced(self):
+ contents = 'tiny contents'
+ with self.assertCalls(
+ self.call.device._WriteFileWithPush('/path/to/device/file', contents)):
+ self.device.WriteFile('/path/to/device/file', contents, force_push=True)
+
+ def testWriteFile_withPushAndSU(self):
+ contents = 'some large contents ' * 26 # 20 * 26 = 520 chars
+ with self.assertCalls(
+ (self.call.device.NeedsSU(), True),
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(self.adb),
+ MockTempFile('/sdcard/tmp/on.device')),
+ self.call.device._WriteFileWithPush('/sdcard/tmp/on.device', contents),
+ self.call.device.RunShellCommand(
+ ['cp', '/sdcard/tmp/on.device', '/path/to/device/file'],
+ as_root=True, check_return=True)):
+ self.device.WriteFile('/path/to/device/file', contents, as_root=True)
+
+ def testWriteFile_withEcho(self):
+ with self.assertCall(self.call.adb.Shell(
+ "echo -n the.contents > /test/file/to.write"), ''):
+ self.device.WriteFile('/test/file/to.write', 'the.contents')
+
+ def testWriteFile_withEchoAndQuotes(self):
+ with self.assertCall(self.call.adb.Shell(
+ "echo -n 'the contents' > '/test/file/to write'"), ''):
+ self.device.WriteFile('/test/file/to write', 'the contents')
+
+ def testWriteFile_withEchoAndSU(self):
+ with self.assertCalls(
+ (self.call.device.NeedsSU(), True),
+ (self.call.adb.Shell("su -c sh -c 'echo -n contents > /test/file'"),
+ '')):
+ self.device.WriteFile('/test/file', 'contents', as_root=True)
+
+
+class DeviceUtilsLsTest(DeviceUtilsTest):
+
+ def testLs_directory(self):
+ result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
+ ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
+ ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
+ with self.assertCalls(
+ (self.call.adb.Ls('/data/local/tmp'), result)):
+ self.assertEquals(result,
+ self.device.Ls('/data/local/tmp'))
+
+ def testLs_nothing(self):
+ with self.assertCalls(
+ (self.call.adb.Ls('/data/local/tmp/testfile.txt'), [])):
+ self.assertEquals([],
+ self.device.Ls('/data/local/tmp/testfile.txt'))
+
+
+class DeviceUtilsStatTest(DeviceUtilsTest):
+
+ def testStat_file(self):
+ result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
+ ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
+ ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
+ with self.assertCalls(
+ (self.call.adb.Ls('/data/local/tmp'), result)):
+ self.assertEquals(adb_wrapper.DeviceStat(33206, 3, 1417436122),
+ self.device.Stat('/data/local/tmp/testfile.txt'))
+
+ def testStat_directory(self):
+ result = [('.', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
+ ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
+ ('tmp', adb_wrapper.DeviceStat(16889, 4096, 1417436123))]
+ with self.assertCalls(
+ (self.call.adb.Ls('/data/local'), result)):
+ self.assertEquals(adb_wrapper.DeviceStat(16889, 4096, 1417436123),
+ self.device.Stat('/data/local/tmp'))
+
+ def testStat_doesNotExist(self):
+ result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
+ ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
+ ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
+ with self.assertCalls(
+ (self.call.adb.Ls('/data/local/tmp'), result)):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.Stat('/data/local/tmp/does.not.exist.txt')
+
+
+class DeviceUtilsSetJavaAssertsTest(DeviceUtilsTest):
+
+ def testSetJavaAsserts_enable(self):
+ with self.assertCalls(
+ (self.call.device.ReadFile(constants.DEVICE_LOCAL_PROPERTIES_PATH),
+ 'some.example.prop=with an example value\n'
+ 'some.other.prop=value_ok\n'),
+ self.call.device.WriteFile(
+ constants.DEVICE_LOCAL_PROPERTIES_PATH,
+ 'some.example.prop=with an example value\n'
+ 'some.other.prop=value_ok\n'
+ 'dalvik.vm.enableassertions=all\n'),
+ (self.call.device.GetProp('dalvik.vm.enableassertions'), ''),
+ self.call.device.SetProp('dalvik.vm.enableassertions', 'all')):
+ self.assertTrue(self.device.SetJavaAsserts(True))
+
+ def testSetJavaAsserts_disable(self):
+ with self.assertCalls(
+ (self.call.device.ReadFile(constants.DEVICE_LOCAL_PROPERTIES_PATH),
+ 'some.example.prop=with an example value\n'
+ 'dalvik.vm.enableassertions=all\n'
+ 'some.other.prop=value_ok\n'),
+ self.call.device.WriteFile(
+ constants.DEVICE_LOCAL_PROPERTIES_PATH,
+ 'some.example.prop=with an example value\n'
+ 'some.other.prop=value_ok\n'),
+ (self.call.device.GetProp('dalvik.vm.enableassertions'), 'all'),
+ self.call.device.SetProp('dalvik.vm.enableassertions', '')):
+ self.assertTrue(self.device.SetJavaAsserts(False))
+
+ def testSetJavaAsserts_alreadyEnabled(self):
+ with self.assertCalls(
+ (self.call.device.ReadFile(constants.DEVICE_LOCAL_PROPERTIES_PATH),
+ 'some.example.prop=with an example value\n'
+ 'dalvik.vm.enableassertions=all\n'
+ 'some.other.prop=value_ok\n'),
+ (self.call.device.GetProp('dalvik.vm.enableassertions'), 'all')):
+ self.assertFalse(self.device.SetJavaAsserts(True))
+
+
+class DeviceUtilsGetPropTest(DeviceUtilsTest):
+
+ def testGetProp_exists(self):
+ with self.assertCall(
+ self.call.adb.Shell('getprop test.property'), 'property_value\n'):
+ self.assertEqual('property_value',
+ self.device.GetProp('test.property'))
+
+ def testGetProp_doesNotExist(self):
+ with self.assertCall(
+ self.call.adb.Shell('getprop property.does.not.exist'), '\n'):
+ self.assertEqual('', self.device.GetProp('property.does.not.exist'))
+
+ def testGetProp_cachedRoProp(self):
+ with self.assertCall(
+ self.call.adb.Shell('getprop ro.build.type'), 'userdebug\n'):
+ self.assertEqual('userdebug',
+ self.device.GetProp('ro.build.type', cache=True))
+ self.assertEqual('userdebug',
+ self.device.GetProp('ro.build.type', cache=True))
+
+ def testGetProp_retryAndCache(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('getprop ro.build.type'), self.ShellError()),
+ (self.call.adb.Shell('getprop ro.build.type'), self.ShellError()),
+ (self.call.adb.Shell('getprop ro.build.type'), 'userdebug\n')):
+ self.assertEqual('userdebug',
+ self.device.GetProp('ro.build.type',
+ cache=True, retries=3))
+ self.assertEqual('userdebug',
+ self.device.GetProp('ro.build.type',
+ cache=True, retries=3))
+
+
+class DeviceUtilsSetPropTest(DeviceUtilsTest):
+
+ def testSetProp(self):
+ with self.assertCall(
+ self.call.adb.Shell("setprop test.property 'test value'"), ''):
+ self.device.SetProp('test.property', 'test value')
+
+ def testSetProp_check_succeeds(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('setprop test.property new_value'), ''),
+ (self.call.adb.Shell('getprop test.property'), 'new_value')):
+ self.device.SetProp('test.property', 'new_value', check=True)
+
+ def testSetProp_check_fails(self):
+ with self.assertCalls(
+ (self.call.adb.Shell('setprop test.property new_value'), ''),
+ (self.call.adb.Shell('getprop test.property'), 'old_value')):
+ with self.assertRaises(device_errors.CommandFailedError):
+ self.device.SetProp('test.property', 'new_value', check=True)
+
+
+class DeviceUtilsGetPidsTest(DeviceUtilsTest):
+
+ def testGetPids_noMatches(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand('ps | grep -F does.not.match'),
+ []):
+ self.assertEqual({}, self.device.GetPids('does.not.match'))
+
+ def testGetPids_oneMatch(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand('ps | grep -F one.match'),
+ ['user 1001 100 1024 1024 ffffffff 00000000 one.match']):
+ self.assertEqual({'one.match': '1001'}, self.device.GetPids('one.match'))
+
+ def testGetPids_mutlipleMatches(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand('ps | grep -F match'),
+ ['user 1001 100 1024 1024 ffffffff 00000000 one.match',
+ 'user 1002 100 1024 1024 ffffffff 00000000 two.match',
+ 'user 1003 100 1024 1024 ffffffff 00000000 three.match']):
+ self.assertEqual(
+ {'one.match': '1001', 'two.match': '1002', 'three.match': '1003'},
+ self.device.GetPids('match'))
+
+ def testGetPids_exactMatch(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand('ps | grep -F exact.match'),
+ ['user 1000 100 1024 1024 ffffffff 00000000 not.exact.match',
+ 'user 1234 100 1024 1024 ffffffff 00000000 exact.match']):
+ self.assertEqual(
+ {'not.exact.match': '1000', 'exact.match': '1234'},
+ self.device.GetPids('exact.match'))
+
+ def testGetPids_quotable(self):
+ with self.assertCall(
+ self.call.device._RunPipedShellCommand("ps | grep -F 'my$process'"),
+ ['user 1234 100 1024 1024 ffffffff 00000000 my$process']):
+ self.assertEqual(
+ {'my$process': '1234'}, self.device.GetPids('my$process'))
+
+
+class DeviceUtilsTakeScreenshotTest(DeviceUtilsTest):
+
+ def testTakeScreenshot_fileNameProvided(self):
+ with self.assertCalls(
+ (mock.call.pylib.utils.device_temp_file.DeviceTempFile(
+ self.adb, suffix='.png'),
+ MockTempFile('/tmp/path/temp-123.png')),
+ (self.call.adb.Shell('/system/bin/screencap -p /tmp/path/temp-123.png'),
+ ''),
+ self.call.device.PullFile('/tmp/path/temp-123.png',
+ '/test/host/screenshot.png')):
+ self.device.TakeScreenshot('/test/host/screenshot.png')
+
+
+class DeviceUtilsGetMemoryUsageForPidTest(DeviceUtilsTest):
+
+ def setUp(self):
+ super(DeviceUtilsGetMemoryUsageForPidTest, self).setUp()
+
+ def testGetMemoryUsageForPid_validPid(self):
+ with self.assertCalls(
+ (self.call.device._RunPipedShellCommand(
+ 'showmap 1234 | grep TOTAL', as_root=True),
+ ['100 101 102 103 104 105 106 107 TOTAL']),
+ (self.call.device.ReadFile('/proc/1234/status', as_root=True),
+ 'VmHWM: 1024 kB\n')):
+ self.assertEqual(
+ {
+ 'Size': 100,
+ 'Rss': 101,
+ 'Pss': 102,
+ 'Shared_Clean': 103,
+ 'Shared_Dirty': 104,
+ 'Private_Clean': 105,
+ 'Private_Dirty': 106,
+ 'VmHWM': 1024
+ },
+ self.device.GetMemoryUsageForPid(1234))
+
+ def testGetMemoryUsageForPid_noSmaps(self):
+ with self.assertCalls(
+ (self.call.device._RunPipedShellCommand(
+ 'showmap 4321 | grep TOTAL', as_root=True),
+ ['cannot open /proc/4321/smaps: No such file or directory']),
+ (self.call.device.ReadFile('/proc/4321/status', as_root=True),
+ 'VmHWM: 1024 kb\n')):
+ self.assertEquals({'VmHWM': 1024}, self.device.GetMemoryUsageForPid(4321))
+
+ def testGetMemoryUsageForPid_noStatus(self):
+ with self.assertCalls(
+ (self.call.device._RunPipedShellCommand(
+ 'showmap 4321 | grep TOTAL', as_root=True),
+ ['100 101 102 103 104 105 106 107 TOTAL']),
+ (self.call.device.ReadFile('/proc/4321/status', as_root=True),
+ self.CommandError())):
+ self.assertEquals(
+ {
+ 'Size': 100,
+ 'Rss': 101,
+ 'Pss': 102,
+ 'Shared_Clean': 103,
+ 'Shared_Dirty': 104,
+ 'Private_Clean': 105,
+ 'Private_Dirty': 106,
+ },
+ self.device.GetMemoryUsageForPid(4321))
+
+
+class DeviceUtilsClientCache(DeviceUtilsTest):
+
+ def testClientCache_twoCaches(self):
+ self.device._cache['test'] = 0
+ client_cache_one = self.device.GetClientCache('ClientOne')
+ client_cache_one['test'] = 1
+ client_cache_two = self.device.GetClientCache('ClientTwo')
+ client_cache_two['test'] = 2
+ self.assertEqual(self.device._cache, {'test': 0})
+ self.assertEqual(client_cache_one, {'test': 1})
+ self.assertEqual(client_cache_two, {'test': 2})
+ self.device._ClearCache()
+ self.assertEqual(self.device._cache, {})
+ self.assertEqual(client_cache_one, {})
+ self.assertEqual(client_cache_two, {})
+
+ def testClientCache_multipleInstances(self):
+ client_cache_one = self.device.GetClientCache('ClientOne')
+ client_cache_one['test'] = 1
+ client_cache_two = self.device.GetClientCache('ClientOne')
+ self.assertEqual(client_cache_one, {'test': 1})
+ self.assertEqual(client_cache_two, {'test': 1})
+ self.device._ClearCache()
+ self.assertEqual(client_cache_one, {})
+ self.assertEqual(client_cache_two, {})
+
+
+class DeviceUtilsParallelTest(mock_calls.TestCase):
+
+ def testParallel_default(self):
+ test_serials = ['0123456789abcdef', 'fedcba9876543210']
+ with self.assertCall(
+ mock.call.pylib.device.device_utils.DeviceUtils.HealthyDevices(),
+ [device_utils.DeviceUtils(s) for s in test_serials]):
+ parallel_devices = device_utils.DeviceUtils.parallel()
+ for serial, device in zip(test_serials, parallel_devices.pGet(None)):
+ self.assertTrue(isinstance(device, device_utils.DeviceUtils))
+ self.assertEquals(serial, device.adb.GetDeviceSerial())
+
+ def testParallel_noDevices(self):
+ with self.assertCall(
+ mock.call.pylib.device.device_utils.DeviceUtils.HealthyDevices(), []):
+ with self.assertRaises(device_errors.NoDevicesError):
+ device_utils.DeviceUtils.parallel()
+
+
+class DeviceUtilsHealthyDevicesTest(mock_calls.TestCase):
+
+ def _createAdbWrapperMock(self, serial, is_ready=True):
+ adb = _AdbWrapperMock(serial)
+ adb.is_ready = is_ready
+ return adb
+
+ def testHealthyDevices_default(self):
+ test_serials = ['0123456789abcdef', 'fedcba9876543210']
+ with self.assertCalls(
+ (mock.call.pylib.device.device_blacklist.ReadBlacklist(), []),
+ (mock.call.pylib.device.adb_wrapper.AdbWrapper.Devices(),
+ [self._createAdbWrapperMock(s) for s in test_serials])):
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ for serial, device in zip(test_serials, devices):
+ self.assertTrue(isinstance(device, device_utils.DeviceUtils))
+ self.assertEquals(serial, device.adb.GetDeviceSerial())
+
+ def testHealthyDevices_blacklisted(self):
+ test_serials = ['0123456789abcdef', 'fedcba9876543210']
+ with self.assertCalls(
+ (mock.call.pylib.device.device_blacklist.ReadBlacklist(),
+ ['fedcba9876543210']),
+ (mock.call.pylib.device.adb_wrapper.AdbWrapper.Devices(),
+ [self._createAdbWrapperMock(s) for s in test_serials])):
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ self.assertEquals(1, len(devices))
+ self.assertTrue(isinstance(devices[0], device_utils.DeviceUtils))
+ self.assertEquals('0123456789abcdef', devices[0].adb.GetDeviceSerial())
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/device/intent.py b/build/android/pylib/device/intent.py
new file mode 100644
index 00000000000..333b9f1f108
--- /dev/null
+++ b/build/android/pylib/device/intent.py
@@ -0,0 +1,113 @@
+# Copyright 2014 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.
+
+"""Manages intents and associated information.
+
+This is generally intended to be used with functions that calls Android's
+Am command.
+"""
+
+class Intent(object):
+
+ def __init__(self, action='android.intent.action.VIEW', activity=None,
+ category=None, component=None, data=None, extras=None,
+ flags=None, package=None):
+ """Creates an Intent.
+
+ Args:
+ action: A string containing the action.
+ activity: A string that, with |package|, can be used to specify the
+ component.
+ category: A string or list containing any categories.
+ component: A string that specifies the component to send the intent to.
+ data: A string containing a data URI.
+ extras: A dict containing extra parameters to be passed along with the
+ intent.
+ flags: A string containing flags to pass.
+ package: A string that, with activity, can be used to specify the
+ component.
+ """
+ self._action = action
+ self._activity = activity
+ if isinstance(category, list) or category is None:
+ self._category = category
+ else:
+ self._category = [category]
+ self._component = component
+ self._data = data
+ self._extras = extras
+ self._flags = flags
+ self._package = package
+
+ if self._component and '/' in component:
+ self._package, self._activity = component.split('/', 1)
+ elif self._package and self._activity:
+ self._component = '%s/%s' % (package, activity)
+
+ @property
+ def action(self):
+ return self._action
+
+ @property
+ def activity(self):
+ return self._activity
+
+ @property
+ def category(self):
+ return self._category
+
+ @property
+ def component(self):
+ return self._component
+
+ @property
+ def data(self):
+ return self._data
+
+ @property
+ def extras(self):
+ return self._extras
+
+ @property
+ def flags(self):
+ return self._flags
+
+ @property
+ def package(self):
+ return self._package
+
+ @property
+ def am_args(self):
+ """Returns the intent as a list of arguments for the activity manager.
+
+ For details refer to the specification at:
+ - http://developer.android.com/tools/help/adb.html#IntentSpec
+ """
+ args = []
+ if self.action:
+ args.extend(['-a', self.action])
+ if self.data:
+ args.extend(['-d', self.data])
+ if self.category:
+ args.extend(arg for cat in self.category for arg in ('-c', cat))
+ if self.component:
+ args.extend(['-n', self.component])
+ if self.flags:
+ args.extend(['-f', self.flags])
+ if self.extras:
+ for key, value in self.extras.iteritems():
+ if value is None:
+ args.extend(['--esn', key])
+ elif isinstance(value, str):
+ args.extend(['--es', key, value])
+ elif isinstance(value, bool):
+ args.extend(['--ez', key, str(value)])
+ elif isinstance(value, int):
+ args.extend(['--ei', key, str(value)])
+ elif isinstance(value, float):
+ args.extend(['--ef', key, str(value)])
+ else:
+ raise NotImplementedError(
+ 'Intent does not know how to pass %s extras' % type(value))
+ return args
diff --git a/build/android/pylib/device/logcat_monitor.py b/build/android/pylib/device/logcat_monitor.py
new file mode 100644
index 00000000000..2eebc2dbbbe
--- /dev/null
+++ b/build/android/pylib/device/logcat_monitor.py
@@ -0,0 +1,139 @@
+# Copyright 2015 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.
+
+# pylint: disable=unused-argument
+
+import collections
+import itertools
+import logging
+import subprocess
+import tempfile
+import time
+import re
+
+from pylib.device import adb_wrapper
+from pylib.device import decorators
+from pylib.device import device_errors
+
+
+class LogcatMonitor(object):
+
+ _THREADTIME_RE_FORMAT = (
+ r'(?P\S*) +(?P\S*) +(?P%s) +(?P%s) +'
+ r'(?P%s) +(?P%s) *: +(?P%s)$')
+
+ def __init__(self, adb, clear=True, filter_specs=None):
+ """Create a LogcatMonitor instance.
+
+ Args:
+ adb: An instance of adb_wrapper.AdbWrapper.
+ clear: If True, clear the logcat when monitoring starts.
+ filter_specs: An optional list of '[:priority]' strings.
+ """
+ if isinstance(adb, adb_wrapper.AdbWrapper):
+ self._adb = adb
+ else:
+ raise ValueError('Unsupported type passed for argument "device"')
+ self._clear = clear
+ self._filter_specs = filter_specs
+ self._logcat_out = None
+ self._logcat_out_file = None
+ self._logcat_proc = None
+
+ @decorators.WithTimeoutAndRetriesDefaults(10, 0)
+ def WaitFor(self, success_regex, failure_regex=None, timeout=None,
+ retries=None):
+ """Wait for a matching logcat line or until a timeout occurs.
+
+ This will attempt to match lines in the logcat against both |success_regex|
+ and |failure_regex| (if provided). Note that this calls re.search on each
+ logcat line, not re.match, so the provided regular expressions don't have
+ to match an entire line.
+
+ Args:
+ success_regex: The regular expression to search for.
+ failure_regex: An optional regular expression that, if hit, causes this
+ to stop looking for a match. Can be None.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A match object if |success_regex| matches a part of a logcat line, or
+ None if |failure_regex| matches a part of a logcat line.
+ Raises:
+ CommandFailedError on logcat failure (NOT on a |failure_regex| match).
+ CommandTimeoutError if no logcat line matching either |success_regex| or
+ |failure_regex| is found in |timeout| seconds.
+ DeviceUnreachableError if the device becomes unreachable.
+ """
+ if isinstance(success_regex, basestring):
+ success_regex = re.compile(success_regex)
+ if isinstance(failure_regex, basestring):
+ failure_regex = re.compile(failure_regex)
+
+ logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern)
+
+ # NOTE This will continue looping until:
+ # - success_regex matches a line, in which case the match object is
+ # returned.
+ # - failure_regex matches a line, in which case None is returned
+ # - the timeout is hit, in which case a CommandTimeoutError is raised.
+ for l in self._adb.Logcat(filter_specs=self._filter_specs):
+ m = success_regex.search(l)
+ if m:
+ return m
+ if failure_regex and failure_regex.search(l):
+ return None
+
+ def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None,
+ component=None):
+ """Finds all lines in the logcat that match the provided constraints.
+
+ Args:
+ message_regex: The regular expression that the section must
+ match.
+ proc_id: The process ID to match. If None, matches any process ID.
+ thread_id: The thread ID to match. If None, matches any thread ID.
+ log_level: The log level to match. If None, matches any log level.
+ component: The component to match. If None, matches any component.
+
+ Yields:
+ A match object for each matching line in the logcat. The match object
+ will always contain, in addition to groups defined in |message_regex|,
+ the following named groups: 'date', 'time', 'proc_id', 'thread_id',
+ 'log_level', 'component', and 'message'.
+ """
+ if proc_id is None:
+ proc_id = r'\d+'
+ if thread_id is None:
+ thread_id = r'\d+'
+ if log_level is None:
+ log_level = r'[VDIWEF]'
+ if component is None:
+ component = r'[^\s:]+'
+ threadtime_re = re.compile(
+ type(self)._THREADTIME_RE_FORMAT % (
+ proc_id, thread_id, log_level, component, message_regex))
+
+ for line in self._adb.Logcat(dump=True, logcat_format='threadtime'):
+ m = re.match(threadtime_re, line)
+ if m:
+ yield m
+
+ def Start(self):
+ """Starts the logcat monitor.
+
+ Clears the logcat if |clear| was set in |__init__|.
+ """
+ if self._clear:
+ self._adb.Logcat(clear=True)
+
+ def __enter__(self):
+ """Starts the logcat monitor."""
+ self.Start()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Stops the logcat monitor."""
+ pass
diff --git a/build/android/pylib/device/logcat_monitor_test.py b/build/android/pylib/device/logcat_monitor_test.py
new file mode 100755
index 00000000000..db397e57a2e
--- /dev/null
+++ b/build/android/pylib/device/logcat_monitor_test.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+import itertools
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.device import adb_wrapper
+from pylib.device import decorators
+from pylib.device import logcat_monitor
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class LogcatMonitorTest(unittest.TestCase):
+
+ _TEST_THREADTIME_LOGCAT_DATA = [
+ '01-01 01:02:03.456 7890 0987 V LogcatMonitorTest: '
+ 'verbose logcat monitor test message 1',
+ '01-01 01:02:03.457 8901 1098 D LogcatMonitorTest: '
+ 'debug logcat monitor test message 2',
+ '01-01 01:02:03.458 9012 2109 I LogcatMonitorTest: '
+ 'info logcat monitor test message 3',
+ '01-01 01:02:03.459 0123 3210 W LogcatMonitorTest: '
+ 'warning logcat monitor test message 4',
+ '01-01 01:02:03.460 1234 4321 E LogcatMonitorTest: '
+ 'error logcat monitor test message 5',
+ '01-01 01:02:03.461 2345 5432 F LogcatMonitorTest: '
+ 'fatal logcat monitor test message 6',
+ '01-01 01:02:03.462 3456 6543 D LogcatMonitorTest: '
+ 'ignore me',]
+
+ def _createTestLog(self, raw_logcat=None):
+ test_adb = adb_wrapper.AdbWrapper('0123456789abcdef')
+ test_adb.Logcat = mock.Mock(return_value=(l for l in raw_logcat))
+ test_log = logcat_monitor.LogcatMonitor(test_adb, clear=False)
+ return test_log
+
+ def assertIterEqual(self, expected_iter, actual_iter):
+ for expected, actual in itertools.izip_longest(expected_iter, actual_iter):
+ self.assertIsNotNone(
+ expected,
+ msg='actual has unexpected elements starting with %s' % str(actual))
+ self.assertIsNotNone(
+ actual,
+ msg='actual is missing elements starting with %s' % str(expected))
+ self.assertEqual(actual.group('proc_id'), expected[0])
+ self.assertEqual(actual.group('thread_id'), expected[1])
+ self.assertEqual(actual.group('log_level'), expected[2])
+ self.assertEqual(actual.group('component'), expected[3])
+ self.assertEqual(actual.group('message'), expected[4])
+
+ with self.assertRaises(StopIteration):
+ next(actual_iter)
+ with self.assertRaises(StopIteration):
+ next(expected_iter)
+
+ def testWaitFor_success(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_match = test_log.WaitFor(r'.*(fatal|error) logcat monitor.*', None)
+ self.assertTrue(actual_match)
+ self.assertEqual(
+ '01-01 01:02:03.460 1234 4321 E LogcatMonitorTest: '
+ 'error logcat monitor test message 5',
+ actual_match.group(0))
+ self.assertEqual('error', actual_match.group(1))
+
+ def testWaitFor_failure(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_match = test_log.WaitFor(
+ r'.*My Success Regex.*', r'.*(fatal|error) logcat monitor.*')
+ self.assertIsNone(actual_match)
+
+ def testFindAll_defaults(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ expected_results = [
+ ('7890', '0987', 'V', 'LogcatMonitorTest',
+ 'verbose logcat monitor test message 1'),
+ ('8901', '1098', 'D', 'LogcatMonitorTest',
+ 'debug logcat monitor test message 2'),
+ ('9012', '2109', 'I', 'LogcatMonitorTest',
+ 'info logcat monitor test message 3'),
+ ('0123', '3210', 'W', 'LogcatMonitorTest',
+ 'warning logcat monitor test message 4'),
+ ('1234', '4321', 'E', 'LogcatMonitorTest',
+ 'error logcat monitor test message 5'),
+ ('2345', '5432', 'F', 'LogcatMonitorTest',
+ 'fatal logcat monitor test message 6')]
+ actual_results = test_log.FindAll(r'\S* logcat monitor test message \d')
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+ def testFindAll_defaults_miss(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ expected_results = []
+ actual_results = test_log.FindAll(r'\S* nothing should match this \d')
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+ def testFindAll_filterProcId(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_results = test_log.FindAll(
+ r'\S* logcat monitor test message \d', proc_id=1234)
+ expected_results = [
+ ('1234', '4321', 'E', 'LogcatMonitorTest',
+ 'error logcat monitor test message 5')]
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+ def testFindAll_filterThreadId(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_results = test_log.FindAll(
+ r'\S* logcat monitor test message \d', thread_id=2109)
+ expected_results = [
+ ('9012', '2109', 'I', 'LogcatMonitorTest',
+ 'info logcat monitor test message 3')]
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+ def testFindAll_filterLogLevel(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_results = test_log.FindAll(
+ r'\S* logcat monitor test message \d', log_level=r'[DW]')
+ expected_results = [
+ ('8901', '1098', 'D', 'LogcatMonitorTest',
+ 'debug logcat monitor test message 2'),
+ ('0123', '3210', 'W', 'LogcatMonitorTest',
+ 'warning logcat monitor test message 4'),]
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+ def testFindAll_filterComponent(self):
+ test_log = self._createTestLog(
+ raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
+ actual_results = test_log.FindAll(r'.*', component='LogcatMonitorTest')
+ expected_results = [
+ ('7890', '0987', 'V', 'LogcatMonitorTest',
+ 'verbose logcat monitor test message 1'),
+ ('8901', '1098', 'D', 'LogcatMonitorTest',
+ 'debug logcat monitor test message 2'),
+ ('9012', '2109', 'I', 'LogcatMonitorTest',
+ 'info logcat monitor test message 3'),
+ ('0123', '3210', 'W', 'LogcatMonitorTest',
+ 'warning logcat monitor test message 4'),
+ ('1234', '4321', 'E', 'LogcatMonitorTest',
+ 'error logcat monitor test message 5'),
+ ('2345', '5432', 'F', 'LogcatMonitorTest',
+ 'fatal logcat monitor test message 6'),
+ ('3456', '6543', 'D', 'LogcatMonitorTest',
+ 'ignore me'),]
+ self.assertIterEqual(iter(expected_results), actual_results)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/device/shared_prefs.py b/build/android/pylib/device/shared_prefs.py
new file mode 100644
index 00000000000..32cef4b535b
--- /dev/null
+++ b/build/android/pylib/device/shared_prefs.py
@@ -0,0 +1,391 @@
+# Copyright 2015 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.
+
+"""Helper object to read and modify Shared Preferences from Android apps.
+
+See e.g.:
+ http://developer.android.com/reference/android/content/SharedPreferences.html
+"""
+
+import collections
+import logging
+import posixpath
+
+from xml.etree import ElementTree
+
+
+_XML_DECLARATION = "\n"
+
+
+class BasePref(object):
+ """Base class for getting/setting the value of a specific preference type.
+
+ Should not be instantiated directly. The SharedPrefs collection will
+ instantiate the appropriate subclasses, which directly manipulate the
+ underlying xml document, to parse and serialize values according to their
+ type.
+
+ Args:
+ elem: An xml ElementTree object holding the preference data.
+
+ Properties:
+ tag_name: A string with the tag that must be used for this preference type.
+ """
+ tag_name = None
+
+ def __init__(self, elem):
+ if elem.tag != type(self).tag_name:
+ raise TypeError('Property %r has type %r, but trying to access as %r' %
+ (elem.get('name'), elem.tag, type(self).tag_name))
+ self._elem = elem
+
+ def __str__(self):
+ """Get the underlying xml element as a string."""
+ return ElementTree.tostring(self._elem)
+
+ def get(self):
+ """Get the value of this preference."""
+ return self._elem.get('value')
+
+ def set(self, value):
+ """Set from a value casted as a string."""
+ self._elem.set('value', str(value))
+
+ @property
+ def has_value(self):
+ """Check whether the element has a value."""
+ return self._elem.get('value') is not None
+
+
+class BooleanPref(BasePref):
+ """Class for getting/setting a preference with a boolean value.
+
+ The underlying xml element has the form, e.g.:
+
+ """
+ tag_name = 'boolean'
+ VALUES = {'true': True, 'false': False}
+
+ def get(self):
+ """Get the value as a Python bool."""
+ return type(self).VALUES[super(BooleanPref, self).get()]
+
+ def set(self, value):
+ """Set from a value casted as a bool."""
+ super(BooleanPref, self).set('true' if value else 'false')
+
+
+class FloatPref(BasePref):
+ """Class for getting/setting a preference with a float value.
+
+ The underlying xml element has the form, e.g.:
+
+ """
+ tag_name = 'float'
+
+ def get(self):
+ """Get the value as a Python float."""
+ return float(super(FloatPref, self).get())
+
+
+class IntPref(BasePref):
+ """Class for getting/setting a preference with an int value.
+
+ The underlying xml element has the form, e.g.:
+
+ """
+ tag_name = 'int'
+
+ def get(self):
+ """Get the value as a Python int."""
+ return int(super(IntPref, self).get())
+
+
+class LongPref(IntPref):
+ """Class for getting/setting a preference with a long value.
+
+ The underlying xml element has the form, e.g.:
+
+
+ We use the same implementation from IntPref.
+ """
+ tag_name = 'long'
+
+
+class StringPref(BasePref):
+ """Class for getting/setting a preference with a string value.
+
+ The underlying xml element has the form, e.g.:
+ 249b3e5af13d4db2
+ """
+ tag_name = 'string'
+
+ def get(self):
+ """Get the value as a Python string."""
+ return self._elem.text
+
+ def set(self, value):
+ """Set from a value casted as a string."""
+ self._elem.text = str(value)
+
+
+class StringSetPref(StringPref):
+ """Class for getting/setting a preference with a set of string values.
+
+ The underlying xml element has the form, e.g.:
+
+ com.mine.app1
+ com.mine.app2
+ com.mine.app3
+
+ """
+ tag_name = 'set'
+
+ def get(self):
+ """Get a list with the string values contained."""
+ value = []
+ for child in self._elem:
+ assert child.tag == 'string'
+ value.append(child.text)
+ return value
+
+ def set(self, value):
+ """Set from a sequence of values, each casted as a string."""
+ for child in list(self._elem):
+ self._elem.remove(child)
+ for item in value:
+ ElementTree.SubElement(self._elem, 'string').text = str(item)
+
+
+_PREF_TYPES = {c.tag_name: c for c in [BooleanPref, FloatPref, IntPref,
+ LongPref, StringPref, StringSetPref]}
+
+
+class SharedPrefs(object):
+ def __init__(self, device, package, filename):
+ """Helper object to read and update "Shared Prefs" of Android apps.
+
+ Such files typically look like, e.g.:
+
+
+
+
+
+ 249b3e5af13d4db2
+
+
+ Example usage:
+
+ prefs = shared_prefs.SharedPrefs(device, 'com.my.app', 'my_prefs.xml')
+ prefs.Load()
+ prefs.GetString('someHashValue') # => '249b3e5af13d4db2'
+ prefs.SetInt('databaseVersion', 42)
+ prefs.Remove('featureEnabled')
+ prefs.Commit()
+
+ The object may also be used as a context manager to automatically load and
+ commit, respectively, upon entering and leaving the context.
+
+ Args:
+ device: A DeviceUtils object.
+ package: A string with the package name of the app that owns the shared
+ preferences file.
+ filename: A string with the name of the preferences file to read/write.
+ """
+ self._device = device
+ self._xml = None
+ self._package = package
+ self._filename = filename
+ self._path = '/data/data/%s/shared_prefs/%s' % (package, filename)
+ self._changed = False
+
+ def __repr__(self):
+ """Get a useful printable representation of the object."""
+ return '<{cls} file {filename} for {package} on {device}>'.format(
+ cls=type(self).__name__, filename=self.filename, package=self.package,
+ device=str(self._device))
+
+ def __str__(self):
+ """Get the underlying xml document as a string."""
+ return _XML_DECLARATION + ElementTree.tostring(self.xml)
+
+ @property
+ def package(self):
+ """Get the package name of the app that owns the shared preferences."""
+ return self._package
+
+ @property
+ def filename(self):
+ """Get the filename of the shared preferences file."""
+ return self._filename
+
+ @property
+ def path(self):
+ """Get the full path to the shared preferences file on the device."""
+ return self._path
+
+ @property
+ def changed(self):
+ """True if properties have changed and a commit would be needed."""
+ return self._changed
+
+ @property
+ def xml(self):
+ """Get the underlying xml document as an ElementTree object."""
+ if self._xml is None:
+ self._xml = ElementTree.Element('map')
+ return self._xml
+
+ def Load(self):
+ """Load the shared preferences file from the device.
+
+ A empty xml document, which may be modified and saved on |commit|, is
+ created if the file does not already exist.
+ """
+ if self._device.FileExists(self.path):
+ self._xml = ElementTree.fromstring(
+ self._device.ReadFile(self.path, as_root=True))
+ assert self._xml.tag == 'map'
+ else:
+ self._xml = None
+ self._changed = False
+
+ def Clear(self):
+ """Clear all of the preferences contained in this object."""
+ if self._xml is not None and len(self): # only clear if not already empty
+ self._xml = None
+ self._changed = True
+
+ def Commit(self):
+ """Save the current set of preferences to the device.
+
+ Only actually saves if some preferences have been modified.
+ """
+ if not self.changed:
+ return
+ self._device.RunShellCommand(
+ ['mkdir', '-p', posixpath.dirname(self.path)],
+ as_root=True, check_return=True)
+ self._device.WriteFile(self.path, str(self), as_root=True)
+ self._device.KillAll(self.package, as_root=True, quiet=True)
+ self._changed = False
+
+ def __len__(self):
+ """Get the number of preferences in this collection."""
+ return len(self.xml)
+
+ def PropertyType(self, key):
+ """Get the type (i.e. tag name) of a property in the collection."""
+ return self._GetChild(key).tag
+
+ def HasProperty(self, key):
+ try:
+ self._GetChild(key)
+ return True
+ except KeyError:
+ return False
+
+ def GetBoolean(self, key):
+ """Get a boolean property."""
+ return BooleanPref(self._GetChild(key)).get()
+
+ def SetBoolean(self, key, value):
+ """Set a boolean property."""
+ self._SetPrefValue(key, value, BooleanPref)
+
+ def GetFloat(self, key):
+ """Get a float property."""
+ return FloatPref(self._GetChild(key)).get()
+
+ def SetFloat(self, key, value):
+ """Set a float property."""
+ self._SetPrefValue(key, value, FloatPref)
+
+ def GetInt(self, key):
+ """Get an int property."""
+ return IntPref(self._GetChild(key)).get()
+
+ def SetInt(self, key, value):
+ """Set an int property."""
+ self._SetPrefValue(key, value, IntPref)
+
+ def GetLong(self, key):
+ """Get a long property."""
+ return LongPref(self._GetChild(key)).get()
+
+ def SetLong(self, key, value):
+ """Set a long property."""
+ self._SetPrefValue(key, value, LongPref)
+
+ def GetString(self, key):
+ """Get a string property."""
+ return StringPref(self._GetChild(key)).get()
+
+ def SetString(self, key, value):
+ """Set a string property."""
+ self._SetPrefValue(key, value, StringPref)
+
+ def GetStringSet(self, key):
+ """Get a string set property."""
+ return StringSetPref(self._GetChild(key)).get()
+
+ def SetStringSet(self, key, value):
+ """Set a string set property."""
+ self._SetPrefValue(key, value, StringSetPref)
+
+ def Remove(self, key):
+ """Remove a preference from the collection."""
+ self.xml.remove(self._GetChild(key))
+
+ def AsDict(self):
+ """Return the properties and their values as a dictionary."""
+ d = {}
+ for child in self.xml:
+ pref = _PREF_TYPES[child.tag](child)
+ d[child.get('name')] = pref.get()
+ return d
+
+ def __enter__(self):
+ """Load preferences file from the device when entering a context."""
+ self.Load()
+ return self
+
+ def __exit__(self, exc_type, _exc_value, _traceback):
+ """Save preferences file to the device when leaving a context."""
+ if not exc_type:
+ self.Commit()
+
+ def _GetChild(self, key):
+ """Get the underlying xml node that holds the property of a given key.
+
+ Raises:
+ KeyError when the key is not found in the collection.
+ """
+ for child in self.xml:
+ if child.get('name') == key:
+ return child
+ raise KeyError(key)
+
+ def _SetPrefValue(self, key, value, pref_cls):
+ """Set the value of a property.
+
+ Args:
+ key: The key of the property to set.
+ value: The new value of the property.
+ pref_cls: A subclass of BasePref used to access the property.
+
+ Raises:
+ TypeError when the key already exists but with a different type.
+ """
+ try:
+ pref = pref_cls(self._GetChild(key))
+ old_value = pref.get()
+ except KeyError:
+ pref = pref_cls(ElementTree.SubElement(
+ self.xml, pref_cls.tag_name, {'name': key}))
+ old_value = None
+ if old_value != value:
+ pref.set(value)
+ self._changed = True
+ logging.info('Setting property: %s', pref)
diff --git a/build/android/pylib/device/shared_prefs_test.py b/build/android/pylib/device/shared_prefs_test.py
new file mode 100755
index 00000000000..c5f0ec3cc71
--- /dev/null
+++ b/build/android/pylib/device/shared_prefs_test.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+"""
+Unit tests for the contents of shared_prefs.py (mostly SharedPrefs).
+"""
+
+import logging
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.device import device_utils
+from pylib.device import shared_prefs
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock
+
+
+def MockDeviceWithFiles(files=None):
+ if files is None:
+ files = {}
+
+ def file_exists(path):
+ return path in files
+
+ def write_file(path, contents, **_kwargs):
+ files[path] = contents
+
+ def read_file(path, **_kwargs):
+ return files[path]
+
+ device = mock.MagicMock(spec=device_utils.DeviceUtils)
+ device.FileExists = mock.Mock(side_effect=file_exists)
+ device.WriteFile = mock.Mock(side_effect=write_file)
+ device.ReadFile = mock.Mock(side_effect=read_file)
+ return device
+
+
+class SharedPrefsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.device = MockDeviceWithFiles({
+ '/data/data/com.some.package/shared_prefs/prefs.xml':
+ "\n"
+ '\n'
+ ' \n'
+ ' \n'
+ ' 249b3e5af13d4db2 \n'
+ ' '})
+ self.expected_data = {'databaseVersion': 107,
+ 'featureEnabled': False,
+ 'someHashValue': '249b3e5af13d4db2'}
+
+ def testPropertyLifetime(self):
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml')
+ self.assertEquals(len(prefs), 0) # collection is empty before loading
+ prefs.SetInt('myValue', 444)
+ self.assertEquals(len(prefs), 1)
+ self.assertEquals(prefs.GetInt('myValue'), 444)
+ self.assertTrue(prefs.HasProperty('myValue'))
+ prefs.Remove('myValue')
+ self.assertEquals(len(prefs), 0)
+ self.assertFalse(prefs.HasProperty('myValue'))
+ with self.assertRaises(KeyError):
+ prefs.GetInt('myValue')
+
+ def testPropertyType(self):
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml')
+ prefs.SetInt('myValue', 444)
+ self.assertEquals(prefs.PropertyType('myValue'), 'int')
+ with self.assertRaises(TypeError):
+ prefs.GetString('myValue')
+ with self.assertRaises(TypeError):
+ prefs.SetString('myValue', 'hello')
+
+ def testLoad(self):
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml')
+ self.assertEquals(len(prefs), 0) # collection is empty before loading
+ prefs.Load()
+ self.assertEquals(len(prefs), len(self.expected_data))
+ self.assertEquals(prefs.AsDict(), self.expected_data)
+ self.assertFalse(prefs.changed)
+
+ def testClear(self):
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml')
+ prefs.Load()
+ self.assertEquals(prefs.AsDict(), self.expected_data)
+ self.assertFalse(prefs.changed)
+ prefs.Clear()
+ self.assertEquals(len(prefs), 0) # collection is empty now
+ self.assertTrue(prefs.changed)
+
+ def testCommit(self):
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'other_prefs.xml')
+ self.assertFalse(self.device.FileExists(prefs.path)) # file does not exist
+ prefs.Load()
+ self.assertEquals(len(prefs), 0) # file did not exist, collection is empty
+ prefs.SetInt('magicNumber', 42)
+ prefs.SetFloat('myMetric', 3.14)
+ prefs.SetLong('bigNumner', 6000000000)
+ prefs.SetStringSet('apps', ['gmail', 'chrome', 'music'])
+ self.assertFalse(self.device.FileExists(prefs.path)) # still does not exist
+ self.assertTrue(prefs.changed)
+ prefs.Commit()
+ self.assertTrue(self.device.FileExists(prefs.path)) # should exist now
+ self.device.KillAll.assert_called_once_with(prefs.package, as_root=True,
+ quiet=True)
+ self.assertFalse(prefs.changed)
+
+ prefs = shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'other_prefs.xml')
+ self.assertEquals(len(prefs), 0) # collection is empty before loading
+ prefs.Load()
+ self.assertEquals(prefs.AsDict(), {
+ 'magicNumber': 42,
+ 'myMetric': 3.14,
+ 'bigNumner': 6000000000,
+ 'apps': ['gmail', 'chrome', 'music']}) # data survived roundtrip
+
+ def testAsContextManager_onlyReads(self):
+ with shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml') as prefs:
+ self.assertEquals(prefs.AsDict(), self.expected_data) # loaded and ready
+ self.assertEquals(self.device.WriteFile.call_args_list, []) # did not write
+
+ def testAsContextManager_readAndWrite(self):
+ with shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml') as prefs:
+ prefs.SetBoolean('featureEnabled', True)
+ prefs.Remove('someHashValue')
+ prefs.SetString('newString', 'hello')
+
+ self.assertTrue(self.device.WriteFile.called) # did write
+ with shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml') as prefs:
+ # changes persisted
+ self.assertTrue(prefs.GetBoolean('featureEnabled'))
+ self.assertFalse(prefs.HasProperty('someHashValue'))
+ self.assertEquals(prefs.GetString('newString'), 'hello')
+ self.assertTrue(prefs.HasProperty('databaseVersion')) # still there
+
+ def testAsContextManager_commitAborted(self):
+ with self.assertRaises(TypeError):
+ with shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml') as prefs:
+ prefs.SetBoolean('featureEnabled', True)
+ prefs.Remove('someHashValue')
+ prefs.SetString('newString', 'hello')
+ prefs.SetInt('newString', 123) # oops!
+
+ self.assertEquals(self.device.WriteFile.call_args_list, []) # did not write
+ with shared_prefs.SharedPrefs(
+ self.device, 'com.some.package', 'prefs.xml') as prefs:
+ # contents were not modified
+ self.assertEquals(prefs.AsDict(), self.expected_data)
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/build/android/pylib/device_settings.py b/build/android/pylib/device_settings.py
new file mode 100644
index 00000000000..beabcff1f3a
--- /dev/null
+++ b/build/android/pylib/device_settings.py
@@ -0,0 +1,198 @@
+# Copyright 2014 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.
+
+import logging
+
+from pylib import constants
+from pylib import content_settings
+from pylib.device import device_errors
+
+_LOCK_SCREEN_SETTINGS_PATH = '/data/system/locksettings.db'
+_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH = (
+ '/data/data/com.android.providers.settings/databases/settings.db')
+PASSWORD_QUALITY_UNSPECIFIED = '0'
+
+
+def ConfigureContentSettings(device, desired_settings):
+ """Configures device content setings from a list.
+
+ Many settings are documented at:
+ http://developer.android.com/reference/android/provider/Settings.Global.html
+ http://developer.android.com/reference/android/provider/Settings.Secure.html
+ http://developer.android.com/reference/android/provider/Settings.System.html
+
+ Many others are undocumented.
+
+ Args:
+ device: A DeviceUtils instance for the device to configure.
+ desired_settings: A list of (table, [(key: value), ...]) for all
+ settings to configure.
+ """
+ if device.build_type == 'userdebug':
+ for table, key_value in desired_settings:
+ settings = content_settings.ContentSettings(table, device)
+ for key, value in key_value:
+ settings[key] = value
+ logging.info('\n%s %s', table, (80 - len(table)) * '-')
+ for key, value in sorted(settings.iteritems()):
+ logging.info('\t%s: %s', key, value)
+
+
+def SetLockScreenSettings(device):
+ """Sets lock screen settings on the device.
+
+ On certain device/Android configurations we need to disable the lock screen in
+ a different database. Additionally, the password type must be set to
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED.
+ Lock screen settings are stored in sqlite on the device in:
+ /data/system/locksettings.db
+
+ IMPORTANT: The first column is used as a primary key so that all rows with the
+ same value for that column are removed from the table prior to inserting the
+ new values.
+
+ Args:
+ device: A DeviceUtils instance for the device to configure.
+
+ Raises:
+ Exception if the setting was not properly set.
+ """
+ if device.build_type != 'userdebug':
+ logging.warning('Unable to disable lockscreen on user builds.')
+ return
+
+ def get_lock_settings(table):
+ return [(table, 'lockscreen.disabled', '1'),
+ (table, 'lockscreen.password_type', PASSWORD_QUALITY_UNSPECIFIED),
+ (table, 'lockscreen.password_type_alternate',
+ PASSWORD_QUALITY_UNSPECIFIED)]
+
+ if device.FileExists(_LOCK_SCREEN_SETTINGS_PATH):
+ db = _LOCK_SCREEN_SETTINGS_PATH
+ locksettings = get_lock_settings('locksettings')
+ columns = ['name', 'user', 'value']
+ generate_values = lambda k, v: [k, '0', v]
+ elif device.FileExists(_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH):
+ db = _ALTERNATE_LOCK_SCREEN_SETTINGS_PATH
+ locksettings = get_lock_settings('secure') + get_lock_settings('system')
+ columns = ['name', 'value']
+ generate_values = lambda k, v: [k, v]
+ else:
+ logging.warning('Unable to find database file to set lock screen settings.')
+ return
+
+ for table, key, value in locksettings:
+ # Set the lockscreen setting for default user '0'
+ values = generate_values(key, value)
+
+ cmd = """begin transaction;
+delete from '%(table)s' where %(primary_key)s='%(primary_value)s';
+insert into '%(table)s' (%(columns)s) values (%(values)s);
+commit transaction;""" % {
+ 'table': table,
+ 'primary_key': columns[0],
+ 'primary_value': values[0],
+ 'columns': ', '.join(columns),
+ 'values': ', '.join(["'%s'" % value for value in values])
+ }
+ output_msg = device.RunShellCommand('sqlite3 %s "%s"' % (db, cmd),
+ as_root=True)
+ if output_msg:
+ logging.info(' '.join(output_msg))
+
+
+ENABLE_LOCATION_SETTINGS = [
+ # Note that setting these in this order is required in order for all of
+ # them to take and stick through a reboot.
+ ('com.google.settings/partner', [
+ ('use_location_for_services', 1),
+ ]),
+ ('settings/secure', [
+ # Ensure Geolocation is enabled and allowed for tests.
+ ('location_providers_allowed', 'gps,network'),
+ ]),
+ ('com.google.settings/partner', [
+ ('network_location_opt_in', 1),
+ ])
+]
+
+DISABLE_LOCATION_SETTINGS = [
+ ('com.google.settings/partner', [
+ ('use_location_for_services', 0),
+ ]),
+ ('settings/secure', [
+ # Ensure Geolocation is disabled.
+ ('location_providers_allowed', ''),
+ ]),
+]
+
+ENABLE_MOCK_LOCATION_SETTINGS = [
+ ('settings/secure', [
+ ('mock_location', 1),
+ ]),
+]
+
+DISABLE_MOCK_LOCATION_SETTINGS = [
+ ('settings/secure', [
+ ('mock_location', 0),
+ ]),
+]
+
+DETERMINISTIC_DEVICE_SETTINGS = [
+ ('settings/global', [
+ ('assisted_gps_enabled', 0),
+
+ # Disable "auto time" and "auto time zone" to avoid network-provided time
+ # to overwrite the device's datetime and timezone synchronized from host
+ # when running tests later. See b/6569849.
+ ('auto_time', 0),
+ ('auto_time_zone', 0),
+
+ ('development_settings_enabled', 1),
+
+ # Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
+ # on application crashes and ANRs. If this is disabled, the crash/ANR dialog
+ # will never display the "Report" button.
+ # Type: int ( 0 = disallow, 1 = allow )
+ ('send_action_app_error', 0),
+
+ ('stay_on_while_plugged_in', 3),
+
+ ('verifier_verify_adb_installs', 0),
+ ]),
+ ('settings/secure', [
+ ('allowed_geolocation_origins',
+ 'http://www.google.co.uk http://www.google.com'),
+
+ # Ensure that we never get random dialogs like "Unfortunately the process
+ # android.process.acore has stopped", which steal the focus, and make our
+ # automation fail (because the dialog steals the focus then mistakenly
+ # receives the injected user input events).
+ ('anr_show_background', 0),
+
+ ('lockscreen.disabled', 1),
+
+ ('screensaver_enabled', 0),
+ ]),
+ ('settings/system', [
+ # Don't want devices to accidentally rotate the screen as that could
+ # affect performance measurements.
+ ('accelerometer_rotation', 0),
+
+ ('lockscreen.disabled', 1),
+
+ # Turn down brightness and disable auto-adjust so that devices run cooler.
+ ('screen_brightness', 5),
+ ('screen_brightness_mode', 0),
+
+ ('user_rotation', 0),
+ ]),
+]
+
+NETWORK_DISABLED_SETTINGS = [
+ ('settings/global', [
+ ('airplane_mode_on', 1),
+ ('wifi_on', 0),
+ ]),
+]
diff --git a/build/android/pylib/device_signal.py b/build/android/pylib/device_signal.py
new file mode 100644
index 00000000000..6a5b70999f8
--- /dev/null
+++ b/build/android/pylib/device_signal.py
@@ -0,0 +1,41 @@
+# Copyright 2015 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.
+
+"""Defines constants for signals that should be supported on devices.
+
+Note: Obtained by running `kill -l` on a user device.
+"""
+
+
+SIGHUP = 1 # Hangup
+SIGINT = 2 # Interrupt
+SIGQUIT = 3 # Quit
+SIGILL = 4 # Illegal instruction
+SIGTRAP = 5 # Trap
+SIGABRT = 6 # Aborted
+SIGBUS = 7 # Bus error
+SIGFPE = 8 # Floating point exception
+SIGKILL = 9 # Killed
+SIGUSR1 = 10 # User signal 1
+SIGSEGV = 11 # Segmentation fault
+SIGUSR2 = 12 # User signal 2
+SIGPIPE = 13 # Broken pipe
+SIGALRM = 14 # Alarm clock
+SIGTERM = 15 # Terminated
+SIGSTKFLT = 16 # Stack fault
+SIGCHLD = 17 # Child exited
+SIGCONT = 18 # Continue
+SIGSTOP = 19 # Stopped (signal)
+SIGTSTP = 20 # Stopped
+SIGTTIN = 21 # Stopped (tty input)
+SIGTTOU = 22 # Stopped (tty output)
+SIGURG = 23 # Urgent I/O condition
+SIGXCPU = 24 # CPU time limit exceeded
+SIGXFSZ = 25 # File size limit exceeded
+SIGVTALRM = 26 # Virtual timer expired
+SIGPROF = 27 # Profiling timer expired
+SIGWINCH = 28 # Window size changed
+SIGIO = 29 # I/O possible
+SIGPWR = 30 # Power failure
+SIGSYS = 31 # Bad system call
diff --git a/build/android/pylib/efficient_android_directory_copy.sh b/build/android/pylib/efficient_android_directory_copy.sh
new file mode 100755
index 00000000000..7021109e273
--- /dev/null
+++ b/build/android/pylib/efficient_android_directory_copy.sh
@@ -0,0 +1,78 @@
+#!/system/bin/sh
+
+# Copyright 2014 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.
+
+# Android shell script to make the destination directory identical with the
+# source directory, without doing unnecessary copies. This assumes that the
+# the destination directory was originally a copy of the source directory, and
+# has since been modified.
+
+source=$1
+dest=$2
+echo copying $source to $dest
+
+delete_extra() {
+ # Don't delete symbolic links, since doing so deletes the vital lib link.
+ if [ ! -L "$1" ]
+ then
+ if [ ! -e "$source/$1" ]
+ then
+ echo rm -rf "$dest/$1"
+ rm -rf "$dest/$1"
+ elif [ -d "$1" ]
+ then
+ for f in "$1"/*
+ do
+ delete_extra "$f"
+ done
+ fi
+ fi
+}
+
+copy_if_older() {
+ if [ -d "$1" ] && [ -e "$dest/$1" ]
+ then
+ if [ ! -e "$dest/$1" ]
+ then
+ echo cp -a "$1" "$dest/$1"
+ cp -a "$1" "$dest/$1"
+ else
+ for f in "$1"/*
+ do
+ copy_if_older "$f"
+ done
+ fi
+ elif [ ! -e "$dest/$1" ] || [ "$1" -ot "$dest/$1" ] || [ "$1" -nt "$dest/$1" ]
+ then
+ # dates are different, so either the destination of the source has changed.
+ echo cp -a "$1" "$dest/$1"
+ cp -a "$1" "$dest/$1"
+ fi
+}
+
+if [ -e "$dest" ]
+then
+ echo cd "$dest"
+ cd "$dest"
+ for f in ./*
+ do
+ if [ -e "$f" ]
+ then
+ delete_extra "$f"
+ fi
+ done
+else
+ echo mkdir "$dest"
+ mkdir "$dest"
+fi
+echo cd "$source"
+cd "$source"
+for f in ./*
+do
+ if [ -e "$f" ]
+ then
+ copy_if_older "$f"
+ fi
+done
diff --git a/build/android/pylib/flag_changer.py b/build/android/pylib/flag_changer.py
new file mode 100644
index 00000000000..718bc397150
--- /dev/null
+++ b/build/android/pylib/flag_changer.py
@@ -0,0 +1,166 @@
+# Copyright (c) 2012 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.
+
+import logging
+
+import pylib.android_commands
+import pylib.device.device_utils
+
+from pylib.device import device_errors
+
+
+class FlagChanger(object):
+ """Changes the flags Chrome runs with.
+
+ There are two different use cases for this file:
+ * Flags are permanently set by calling Set().
+ * Flags can be temporarily set for a particular set of unit tests. These
+ tests should call Restore() to revert the flags to their original state
+ once the tests have completed.
+ """
+
+ def __init__(self, device, cmdline_file):
+ """Initializes the FlagChanger and records the original arguments.
+
+ Args:
+ device: A DeviceUtils instance.
+ cmdline_file: Path to the command line file on the device.
+ """
+ # TODO(jbudorick) Remove once telemetry switches over.
+ if isinstance(device, pylib.android_commands.AndroidCommands):
+ device = pylib.device.device_utils.DeviceUtils(device)
+ self._device = device
+ self._cmdline_file = cmdline_file
+
+ # Save the original flags.
+ try:
+ self._orig_line = self._device.ReadFile(self._cmdline_file).strip()
+ except device_errors.CommandFailedError:
+ self._orig_line = ''
+
+ # Parse out the flags into a list to facilitate adding and removing flags.
+ self._current_flags = self._TokenizeFlags(self._orig_line)
+
+ def Get(self):
+ """Returns list of current flags."""
+ return self._current_flags
+
+ def Set(self, flags):
+ """Replaces all flags on the current command line with the flags given.
+
+ Args:
+ flags: A list of flags to set, eg. ['--single-process'].
+ """
+ if flags:
+ assert flags[0] != 'chrome'
+
+ self._current_flags = flags
+ self._UpdateCommandLineFile()
+
+ def AddFlags(self, flags):
+ """Appends flags to the command line if they aren't already there.
+
+ Args:
+ flags: A list of flags to add on, eg. ['--single-process'].
+ """
+ if flags:
+ assert flags[0] != 'chrome'
+
+ # Avoid appending flags that are already present.
+ for flag in flags:
+ if flag not in self._current_flags:
+ self._current_flags.append(flag)
+ self._UpdateCommandLineFile()
+
+ def RemoveFlags(self, flags):
+ """Removes flags from the command line, if they exist.
+
+ Args:
+ flags: A list of flags to remove, eg. ['--single-process']. Note that we
+ expect a complete match when removing flags; if you want to remove
+ a switch with a value, you must use the exact string used to add
+ it in the first place.
+ """
+ if flags:
+ assert flags[0] != 'chrome'
+
+ for flag in flags:
+ if flag in self._current_flags:
+ self._current_flags.remove(flag)
+ self._UpdateCommandLineFile()
+
+ def Restore(self):
+ """Restores the flags to their original state."""
+ self._current_flags = self._TokenizeFlags(self._orig_line)
+ self._UpdateCommandLineFile()
+
+ def _UpdateCommandLineFile(self):
+ """Writes out the command line to the file, or removes it if empty."""
+ logging.info('Current flags: %s', self._current_flags)
+ # Root is not required to write to /data/local/tmp/.
+ use_root = '/data/local/tmp/' not in self._cmdline_file
+ if self._current_flags:
+ # The first command line argument doesn't matter as we are not actually
+ # launching the chrome executable using this command line.
+ cmd_line = ' '.join(['_'] + self._current_flags)
+ self._device.WriteFile(
+ self._cmdline_file, cmd_line, as_root=use_root)
+ file_contents = self._device.ReadFile(
+ self._cmdline_file, as_root=use_root).rstrip()
+ assert file_contents == cmd_line, (
+ 'Failed to set the command line file at %s' % self._cmdline_file)
+ else:
+ self._device.RunShellCommand('rm ' + self._cmdline_file,
+ as_root=use_root)
+ assert not self._device.FileExists(self._cmdline_file), (
+ 'Failed to remove the command line file at %s' % self._cmdline_file)
+
+ @staticmethod
+ def _TokenizeFlags(line):
+ """Changes the string containing the command line into a list of flags.
+
+ Follows similar logic to CommandLine.java::tokenizeQuotedArguments:
+ * Flags are split using whitespace, unless the whitespace is within a
+ pair of quotation marks.
+ * Unlike the Java version, we keep the quotation marks around switch
+ values since we need them to re-create the file when new flags are
+ appended.
+
+ Args:
+ line: A string containing the entire command line. The first token is
+ assumed to be the program name.
+ """
+ if not line:
+ return []
+
+ tokenized_flags = []
+ current_flag = ""
+ within_quotations = False
+
+ # Move through the string character by character and build up each flag
+ # along the way.
+ for c in line.strip():
+ if c is '"':
+ if len(current_flag) > 0 and current_flag[-1] == '\\':
+ # Last char was a backslash; pop it, and treat this " as a literal.
+ current_flag = current_flag[0:-1] + '"'
+ else:
+ within_quotations = not within_quotations
+ current_flag += c
+ elif not within_quotations and (c is ' ' or c is '\t'):
+ if current_flag is not "":
+ tokenized_flags.append(current_flag)
+ current_flag = ""
+ else:
+ current_flag += c
+
+ # Tack on the last flag.
+ if not current_flag:
+ if within_quotations:
+ logging.warn('Unterminated quoted argument: ' + line)
+ else:
+ tokenized_flags.append(current_flag)
+
+ # Return everything but the program name.
+ return tokenized_flags[1:]
diff --git a/build/android/pylib/forwarder.py b/build/android/pylib/forwarder.py
new file mode 100644
index 00000000000..c8c47d60601
--- /dev/null
+++ b/build/android/pylib/forwarder.py
@@ -0,0 +1,331 @@
+# Copyright (c) 2012 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.
+
+# pylint: disable=W0212
+
+import fcntl
+import logging
+import os
+import psutil
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib import valgrind_tools
+
+# TODO(jbudorick) Remove once telemetry gets switched over.
+import pylib.android_commands
+import pylib.device.device_utils
+
+
+def _GetProcessStartTime(pid):
+ return psutil.Process(pid).create_time
+
+
+class _FileLock(object):
+ """With statement-aware implementation of a file lock.
+
+ File locks are needed for cross-process synchronization when the
+ multiprocessing Python module is used.
+ """
+ def __init__(self, path):
+ self._fd = -1
+ self._path = path
+
+ def __enter__(self):
+ self._fd = os.open(self._path, os.O_RDONLY | os.O_CREAT)
+ if self._fd < 0:
+ raise Exception('Could not open file %s for reading' % self._path)
+ fcntl.flock(self._fd, fcntl.LOCK_EX)
+
+ def __exit__(self, _exception_type, _exception_value, traceback):
+ fcntl.flock(self._fd, fcntl.LOCK_UN)
+ os.close(self._fd)
+
+
+class Forwarder(object):
+ """Thread-safe class to manage port forwards from the device to the host."""
+
+ _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR +
+ '/forwarder/')
+ _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR +
+ '/forwarder/device_forwarder')
+ _LOCK_PATH = '/tmp/chrome.forwarder.lock'
+ # Defined in host_forwarder_main.cc
+ _HOST_FORWARDER_LOG = '/tmp/host_forwarder_log'
+
+ _instance = None
+
+ @staticmethod
+ def Map(port_pairs, device, tool=None):
+ """Runs the forwarder.
+
+ Args:
+ port_pairs: A list of tuples (device_port, host_port) to forward. Note
+ that you can specify 0 as a device_port, in which case a
+ port will by dynamically assigned on the device. You can
+ get the number of the assigned port using the
+ DevicePortForHostPort method.
+ device: A DeviceUtils instance.
+ tool: Tool class to use to get wrapper, if necessary, for executing the
+ forwarder (see valgrind_tools.py).
+
+ Raises:
+ Exception on failure to forward the port.
+ """
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, pylib.android_commands.AndroidCommands):
+ device = pylib.device.device_utils.DeviceUtils(device)
+ if not tool:
+ tool = valgrind_tools.CreateTool(None, device)
+ with _FileLock(Forwarder._LOCK_PATH):
+ instance = Forwarder._GetInstanceLocked(tool)
+ instance._InitDeviceLocked(device, tool)
+
+ device_serial = str(device)
+ redirection_commands = [
+ ['--adb=' + constants.GetAdbPath(),
+ '--serial-id=' + device_serial,
+ '--map', str(device_port), str(host_port)]
+ for device_port, host_port in port_pairs]
+ logging.info('Forwarding using commands: %s', redirection_commands)
+
+ for redirection_command in redirection_commands:
+ try:
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ [instance._host_forwarder_path] + redirection_command)
+ except OSError as e:
+ if e.errno == 2:
+ raise Exception('Unable to start host forwarder. Make sure you have'
+ ' built host_forwarder.')
+ else: raise
+ if exit_code != 0:
+ Forwarder._KillDeviceLocked(device, tool)
+ raise Exception('%s exited with %d:\n%s' % (
+ instance._host_forwarder_path, exit_code, '\n'.join(output)))
+ tokens = output.split(':')
+ if len(tokens) != 2:
+ raise Exception('Unexpected host forwarder output "%s", '
+ 'expected "device_port:host_port"' % output)
+ device_port = int(tokens[0])
+ host_port = int(tokens[1])
+ serial_with_port = (device_serial, device_port)
+ instance._device_to_host_port_map[serial_with_port] = host_port
+ instance._host_to_device_port_map[host_port] = serial_with_port
+ logging.info('Forwarding device port: %d to host port: %d.',
+ device_port, host_port)
+
+ @staticmethod
+ def UnmapDevicePort(device_port, device):
+ """Unmaps a previously forwarded device port.
+
+ Args:
+ device: A DeviceUtils instance.
+ device_port: A previously forwarded port (through Map()).
+ """
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, pylib.android_commands.AndroidCommands):
+ device = pylib.device.device_utils.DeviceUtils(device)
+ with _FileLock(Forwarder._LOCK_PATH):
+ Forwarder._UnmapDevicePortLocked(device_port, device)
+
+ @staticmethod
+ def UnmapAllDevicePorts(device):
+ """Unmaps all the previously forwarded ports for the provided device.
+
+ Args:
+ device: A DeviceUtils instance.
+ port_pairs: A list of tuples (device_port, host_port) to unmap.
+ """
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, pylib.android_commands.AndroidCommands):
+ device = pylib.device.device_utils.DeviceUtils(device)
+ with _FileLock(Forwarder._LOCK_PATH):
+ if not Forwarder._instance:
+ return
+ adb_serial = str(device)
+ if adb_serial not in Forwarder._instance._initialized_devices:
+ return
+ port_map = Forwarder._GetInstanceLocked(
+ None)._device_to_host_port_map
+ for (device_serial, device_port) in port_map.keys():
+ if adb_serial == device_serial:
+ Forwarder._UnmapDevicePortLocked(device_port, device)
+ # There are no more ports mapped, kill the device_forwarder.
+ tool = valgrind_tools.CreateTool(None, device)
+ Forwarder._KillDeviceLocked(device, tool)
+
+ @staticmethod
+ def DevicePortForHostPort(host_port):
+ """Returns the device port that corresponds to a given host port."""
+ with _FileLock(Forwarder._LOCK_PATH):
+ (_device_serial, device_port) = Forwarder._GetInstanceLocked(
+ None)._host_to_device_port_map.get(host_port)
+ return device_port
+
+ @staticmethod
+ def RemoveHostLog():
+ if os.path.exists(Forwarder._HOST_FORWARDER_LOG):
+ os.unlink(Forwarder._HOST_FORWARDER_LOG)
+
+ @staticmethod
+ def GetHostLog():
+ if not os.path.exists(Forwarder._HOST_FORWARDER_LOG):
+ return ''
+ with file(Forwarder._HOST_FORWARDER_LOG, 'r') as f:
+ return f.read()
+
+ @staticmethod
+ def _GetInstanceLocked(tool):
+ """Returns the singleton instance.
+
+ Note that the global lock must be acquired before calling this method.
+
+ Args:
+ tool: Tool class to use to get wrapper, if necessary, for executing the
+ forwarder (see valgrind_tools.py).
+ """
+ if not Forwarder._instance:
+ Forwarder._instance = Forwarder(tool)
+ return Forwarder._instance
+
+ def __init__(self, tool):
+ """Constructs a new instance of Forwarder.
+
+ Note that Forwarder is a singleton therefore this constructor should be
+ called only once.
+
+ Args:
+ tool: Tool class to use to get wrapper, if necessary, for executing the
+ forwarder (see valgrind_tools.py).
+ """
+ assert not Forwarder._instance
+ self._tool = tool
+ self._initialized_devices = set()
+ self._device_to_host_port_map = dict()
+ self._host_to_device_port_map = dict()
+ self._host_forwarder_path = os.path.join(
+ constants.GetOutDirectory(), 'host_forwarder')
+ assert os.path.exists(self._host_forwarder_path), 'Please build forwarder2'
+ self._device_forwarder_path_on_host = os.path.join(
+ constants.GetOutDirectory(), 'forwarder_dist')
+ self._InitHostLocked()
+
+ @staticmethod
+ def _UnmapDevicePortLocked(device_port, device):
+ """Internal method used by UnmapDevicePort().
+
+ Note that the global lock must be acquired before calling this method.
+ """
+ instance = Forwarder._GetInstanceLocked(None)
+ serial = str(device)
+ serial_with_port = (serial, device_port)
+ if not serial_with_port in instance._device_to_host_port_map:
+ logging.error('Trying to unmap non-forwarded port %d' % device_port)
+ return
+ redirection_command = ['--adb=' + constants.GetAdbPath(),
+ '--serial-id=' + serial,
+ '--unmap', str(device_port)]
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ [instance._host_forwarder_path] + redirection_command)
+ if exit_code != 0:
+ logging.error('%s exited with %d:\n%s' % (
+ instance._host_forwarder_path, exit_code, '\n'.join(output)))
+ host_port = instance._device_to_host_port_map[serial_with_port]
+ del instance._device_to_host_port_map[serial_with_port]
+ del instance._host_to_device_port_map[host_port]
+
+ @staticmethod
+ def _GetPidForLock():
+ """Returns the PID used for host_forwarder initialization.
+
+ The PID of the "sharder" is used to handle multiprocessing. The "sharder"
+ is the initial process that forks that is the parent process.
+ """
+ return os.getpgrp()
+
+ def _InitHostLocked(self):
+ """Initializes the host forwarder daemon.
+
+ Note that the global lock must be acquired before calling this method. This
+ method kills any existing host_forwarder process that could be stale.
+ """
+ # See if the host_forwarder daemon was already initialized by a concurrent
+ # process or thread (in case multi-process sharding is not used).
+ pid_for_lock = Forwarder._GetPidForLock()
+ fd = os.open(Forwarder._LOCK_PATH, os.O_RDWR | os.O_CREAT)
+ with os.fdopen(fd, 'r+') as pid_file:
+ pid_with_start_time = pid_file.readline()
+ if pid_with_start_time:
+ (pid, process_start_time) = pid_with_start_time.split(':')
+ if pid == str(pid_for_lock):
+ if process_start_time == str(_GetProcessStartTime(pid_for_lock)):
+ return
+ self._KillHostLocked()
+ pid_file.seek(0)
+ pid_file.write(
+ '%s:%s' % (pid_for_lock, str(_GetProcessStartTime(pid_for_lock))))
+ pid_file.truncate()
+
+ def _InitDeviceLocked(self, device, tool):
+ """Initializes the device_forwarder daemon for a specific device (once).
+
+ Note that the global lock must be acquired before calling this method. This
+ method kills any existing device_forwarder daemon on the device that could
+ be stale, pushes the latest version of the daemon (to the device) and starts
+ it.
+
+ Args:
+ device: A DeviceUtils instance.
+ tool: Tool class to use to get wrapper, if necessary, for executing the
+ forwarder (see valgrind_tools.py).
+ """
+ device_serial = str(device)
+ if device_serial in self._initialized_devices:
+ return
+ Forwarder._KillDeviceLocked(device, tool)
+ device.PushChangedFiles([(
+ self._device_forwarder_path_on_host,
+ Forwarder._DEVICE_FORWARDER_FOLDER)])
+ cmd = '%s %s' % (tool.GetUtilWrapper(), Forwarder._DEVICE_FORWARDER_PATH)
+ device.RunShellCommand(
+ cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER},
+ check_return=True)
+ self._initialized_devices.add(device_serial)
+
+ def _KillHostLocked(self):
+ """Kills the forwarder process running on the host.
+
+ Note that the global lock must be acquired before calling this method.
+ """
+ logging.info('Killing host_forwarder.')
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ [self._host_forwarder_path, '--kill-server'])
+ if exit_code != 0:
+ (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
+ ['pkill', '-9', 'host_forwarder'])
+ if exit_code != 0:
+ raise Exception('%s exited with %d:\n%s' % (
+ self._host_forwarder_path, exit_code, '\n'.join(output)))
+
+ @staticmethod
+ def _KillDeviceLocked(device, tool):
+ """Kills the forwarder process running on the device.
+
+ Note that the global lock must be acquired before calling this method.
+
+ Args:
+ device: Instance of DeviceUtils for talking to the device.
+ tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
+ forwarder (see valgrind_tools.py).
+ """
+ logging.info('Killing device_forwarder.')
+ Forwarder._instance._initialized_devices.discard(str(device))
+ if not device.FileExists(Forwarder._DEVICE_FORWARDER_PATH):
+ return
+
+ cmd = '%s %s --kill-server' % (tool.GetUtilWrapper(),
+ Forwarder._DEVICE_FORWARDER_PATH)
+ device.RunShellCommand(
+ cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER},
+ check_return=True)
diff --git a/build/android/pylib/gtest/__init__.py b/build/android/pylib/gtest/__init__.py
new file mode 100644
index 00000000000..727e987e6b6
--- /dev/null
+++ b/build/android/pylib/gtest/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2012 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.
+
diff --git a/build/android/pylib/gtest/filter/OWNERS b/build/android/pylib/gtest/filter/OWNERS
new file mode 100644
index 00000000000..72e8ffc0db8
--- /dev/null
+++ b/build/android/pylib/gtest/filter/OWNERS
@@ -0,0 +1 @@
+*
diff --git a/build/android/pylib/gtest/filter/base_unittests_disabled b/build/android/pylib/gtest/filter/base_unittests_disabled
new file mode 100644
index 00000000000..bf2311d0dd6
--- /dev/null
+++ b/build/android/pylib/gtest/filter/base_unittests_disabled
@@ -0,0 +1,28 @@
+# List of suppressions
+
+# Android will not support StackTrace.
+StackTrace.*
+#
+# Sometimes this is automatically generated by run_tests.py
+VerifyPathControlledByUserTest.Symlinks
+
+# http://crbug.com/138845
+MessagePumpLibeventTest.TestWatchingFromBadThread
+
+StringPrintfTest.StringPrintfMisc
+StringPrintfTest.StringAppendfString
+StringPrintfTest.StringAppendfInt
+StringPrintfTest.StringPrintfBounds
+ProcessUtilTest.GetAppOutputRestrictedSIGPIPE
+# TODO(jrg): Fails on bots. Works locally. Figure out why. 2/6/12
+FieldTrialTest.*
+# Flaky?
+ScopedJavaRefTest.RefCounts
+# Death tests are not supported with apks.
+*DeathTest*
+FileTest.MemoryCorruption
+MessagePumpLibeventTest.QuitOutsideOfRun
+ScopedFD.ScopedFDCrashesOnCloseFailure
+
+# http://crbug.com/245043
+StackContainer.BufferAlignment
diff --git a/build/android/pylib/gtest/filter/base_unittests_emulator_additional_disabled b/build/android/pylib/gtest/filter/base_unittests_emulator_additional_disabled
new file mode 100644
index 00000000000..85e8fd63634
--- /dev/null
+++ b/build/android/pylib/gtest/filter/base_unittests_emulator_additional_disabled
@@ -0,0 +1,10 @@
+# Addtional list of suppressions from emulator
+#
+# Automatically generated by run_tests.py
+PathServiceTest.Get
+SharedMemoryTest.OpenClose
+StringPrintfTest.StringAppendfInt
+StringPrintfTest.StringAppendfString
+StringPrintfTest.StringPrintfBounds
+StringPrintfTest.StringPrintfMisc
+VerifyPathControlledByUserTest.Symlinks
diff --git a/build/android/pylib/gtest/filter/blink_heap_unittests_disabled b/build/android/pylib/gtest/filter/blink_heap_unittests_disabled
new file mode 100644
index 00000000000..7a43fb1d5b3
--- /dev/null
+++ b/build/android/pylib/gtest/filter/blink_heap_unittests_disabled
@@ -0,0 +1,2 @@
+# List of suppressions
+
diff --git a/build/android/pylib/gtest/filter/breakpad_unittests_disabled b/build/android/pylib/gtest/filter/breakpad_unittests_disabled
new file mode 100644
index 00000000000..cefc64fd5ee
--- /dev/null
+++ b/build/android/pylib/gtest/filter/breakpad_unittests_disabled
@@ -0,0 +1,9 @@
+FileIDStripTest.StripSelf
+# crbug.com/303960
+ExceptionHandlerTest.InstructionPointerMemoryNullPointer
+# crbug.com/171419
+MinidumpWriterTest.MappingInfoContained
+# crbug.com/310088
+MinidumpWriterTest.MinidumpSizeLimit
+# crbug.com/375838
+ElfCoreDumpTest.ValidCoreFile
diff --git a/build/android/pylib/gtest/filter/cc_unittests_disabled b/build/android/pylib/gtest/filter/cc_unittests_disabled
new file mode 100644
index 00000000000..b49d2c684ef
--- /dev/null
+++ b/build/android/pylib/gtest/filter/cc_unittests_disabled
@@ -0,0 +1,5 @@
+# Death tests are not supported with apks.
+BeginFrameObserverBaseTest.OnBeginFrameImplementation
+BeginFrameSourceBaseTest.ObserverManipulation
+BeginFrameSourceMultiplexerTest.SourcesManipulation
+BeginFrameSourceMultiplexerTest.MinimumIntervalNegativeFails
diff --git a/build/android/pylib/gtest/filter/content_browsertests_disabled b/build/android/pylib/gtest/filter/content_browsertests_disabled
new file mode 100644
index 00000000000..dcad240a40e
--- /dev/null
+++ b/build/android/pylib/gtest/filter/content_browsertests_disabled
@@ -0,0 +1,62 @@
+# List of suppressions
+# Timeouts
+Http/MediaTest.*
+File/MediaTest.*
+MediaTest.*
+DatabaseTest.*
+
+# Crashes
+RenderFrameHostManagerTest.IgnoreRendererDebugURLsWhenCrashed
+
+# Plugins are not supported.
+BrowserPluginThreadedCompositorPixelTest.*
+BrowserPluginHostTest.*
+BrowserPluginTest.*
+PluginTest.*
+
+# http://crbug.com/463740
+CrossPlatformAccessibilityBrowserTest.SelectedEditableTextAccessibility
+
+# http://crbug.com/297230
+DumpAccessibilityTreeTest.AccessibilityAriaLevel
+DumpAccessibilityTreeTest.AccessibilityAriaProgressbar
+DumpAccessibilityTreeTest.AccessibilityListMarkers
+DumpAccessibilityTreeTest.AccessibilityUl
+DumpAccessibilityTreeTest.AccessibilityCanvas
+RendererAccessibilityTest.DetachAccessibilityObject
+DumpAccessibilityTreeTest.AccessibilityDialog
+DumpAccessibilityTreeTest.AccessibilityModalDialogClosed
+DumpAccessibilityTreeTest.AccessibilityModalDialogInIframeOpened
+RendererAccessibilityTest.EventOnObjectNotInTree
+
+# http://crbug.com/187500
+RenderViewImplTest.*
+RendererAccessibilityTest.SendFullAccessibilityTreeOnReload
+RendererAccessibilityTest.HideAccessibilityObject
+RendererAccessibilityTest.ShowAccessibilityObject
+
+# http://crbug.com/215894
+DownloadContentTest.CancelInterruptedDownload
+DownloadContentTest.CancelResumingDownload
+DownloadContentTest.RemoveDownload
+DownloadContentTest.RemoveResumingDownload
+DownloadContentTest.ResumeInterruptedDownload
+DownloadContentTest.ResumeInterruptedDownloadNoRange
+DownloadContentTest.ResumeInterruptedDownloadNoVerifiers
+DownloadContentTest.ResumeInterruptedDownloadBadPrecondition
+DownloadContentTest.ResumeWithDeletedFile
+
+# http://crbug.com/386227
+IndexedDBBrowserTest.VersionChangeCrashResilience
+
+# http://crbug.com/233118
+IndexedDBBrowserTest.NullKeyPathPersistence
+
+# http://crbug.com/342525
+IndexedDBBrowserTestSingleProcess.RenderThreadShutdownTest
+
+# http://crbug.com/338421
+GinBrowserTest.GinAndGarbageCollection
+
+# http://crbug.com/343604
+MSE_ClearKey/EncryptedMediaTest.ConfigChangeVideo/0
diff --git a/build/android/pylib/gtest/filter/content_unittests_disabled b/build/android/pylib/gtest/filter/content_unittests_disabled
new file mode 100644
index 00000000000..925a7d1b78c
--- /dev/null
+++ b/build/android/pylib/gtest/filter/content_unittests_disabled
@@ -0,0 +1,13 @@
+# List of suppressions
+
+# crbug.com/139095
+RenderWidgetTest.OnMsgPaintAtSize
+# crbug.com/147549
+GamepadProviderTest.PollingAccess
+PepperGamepadHostTest.WaitForReply
+# crbug.com/159234
+WebContentsVideoCaptureDeviceTest.*
+# crbug.com/167045
+ContentViewPopupZoomerTest.testPopupZoomerShowsUp
+# crbug.com/254034
+PageStateSerializationTest.BackwardsCompat_v11
diff --git a/build/android/pylib/gtest/filter/gfx_unittests_disabled b/build/android/pylib/gtest/filter/gfx_unittests_disabled
new file mode 100644
index 00000000000..b9aec9e8d0b
--- /dev/null
+++ b/build/android/pylib/gtest/filter/gfx_unittests_disabled
@@ -0,0 +1,10 @@
+CanvasTest.StringSizeEmptyString
+CanvasTest.StringWidth
+FontTest.Ascent
+FontTest.AvgWidths
+FontTest.CapHeight
+FontTest.GetActualFontNameForTesting
+FontTest.Height
+FontTest.LoadArial
+FontTest.LoadArialBold
+TextUtilsTest.GetStringWidth
diff --git a/build/android/pylib/gtest/filter/ipc_tests_disabled b/build/android/pylib/gtest/filter/ipc_tests_disabled
new file mode 100644
index 00000000000..e8d069113d6
--- /dev/null
+++ b/build/android/pylib/gtest/filter/ipc_tests_disabled
@@ -0,0 +1,18 @@
+# Times out
+IPCSyncChannelTest.ChattyServer
+
+# MultiProcessTest related failures. These tests fail if DCHECK is enabled.
+IPCChannelPosixTest.AdvancedConnected
+IPCChannelPosixTest.ResetState
+IPCChannelPosixTest.MultiConnection
+IPCFuzzingTest.SanityTest
+IPCFuzzingTest.MsgBadPayloadArgs
+IPCFuzzingTest.MsgBadPayloadShort
+IPCSendFdsTest.DescriptorTest
+IPCChannelProxyTest.MessageClassFilters
+IPCChannelProxyTest.GlobalAndMessageClassFilters
+IPCChannelProxyTest.FilterRemoval
+IPCChannelTest.ChannelTest
+IPCChannelTest.ChannelProxyTest
+IPCChannelTest.SendMessageInChannelConnected
+SyncSocketTest.SanityTest
diff --git a/build/android/pylib/gtest/filter/media_unittests_disabled b/build/android/pylib/gtest/filter/media_unittests_disabled
new file mode 100644
index 00000000000..ed3b9aac651
--- /dev/null
+++ b/build/android/pylib/gtest/filter/media_unittests_disabled
@@ -0,0 +1,8 @@
+# List of suppressions
+
+# Death tests are not supported on APK
+# http://crbug.com/138855
+CompositeFilterDeathTest.*
+
+# http://crbug.com/138833
+AesDecryptorTest.*
diff --git a/build/android/pylib/gtest/filter/net_unittests_disabled b/build/android/pylib/gtest/filter/net_unittests_disabled
new file mode 100644
index 00000000000..75a1c86e82a
--- /dev/null
+++ b/build/android/pylib/gtest/filter/net_unittests_disabled
@@ -0,0 +1,41 @@
+# List of suppressions.
+
+PythonUtils.PythonRunTime
+VerifyEndEntity/CertVerifyProcWeakDigestTest.Verify/0
+VerifyEndEntity/CertVerifyProcWeakDigestTest.Verify/1
+VerifyEndEntity/CertVerifyProcWeakDigestTest.Verify/2
+VerifyIncompleteEndEntity/CertVerifyProcWeakDigestTest.Verify/0
+VerifyIncompleteEndEntity/CertVerifyProcWeakDigestTest.Verify/1
+VerifyIncompleteEndEntity/CertVerifyProcWeakDigestTest.Verify/2
+VerifyIncompleteIntermediate/CertVerifyProcWeakDigestTest.Verify/0
+VerifyIncompleteIntermediate/CertVerifyProcWeakDigestTest.Verify/1
+VerifyIncompleteIntermediate/CertVerifyProcWeakDigestTest.Verify/2
+VerifyIntermediate/CertVerifyProcWeakDigestTest.Verify/0
+VerifyIntermediate/CertVerifyProcWeakDigestTest.Verify/1
+VerifyIntermediate/CertVerifyProcWeakDigestTest.Verify/2
+VerifyMixed/CertVerifyProcWeakDigestTest.Verify/0
+VerifyMixed/CertVerifyProcWeakDigestTest.Verify/1
+VerifyMixed/CertVerifyProcWeakDigestTest.Verify/2
+VerifyRoot/CertVerifyProcWeakDigestTest.Verify/0
+VerifyRoot/CertVerifyProcWeakDigestTest.Verify/1
+VerifyRoot/CertVerifyProcWeakDigestTest.Verify/2
+
+# Can't spin up more than one SpawnedTestServer on Android.
+URLRequestTestReferrerPolicy.HTTPToCrossOriginHTTP
+URLRequestTestReferrerPolicy.HTTPSToCrossOriginHTTPS
+URLRequestTestReferrerPolicy.HTTPToHTTPS
+URLRequestTestReferrerPolicy.HTTPSToHTTP
+
+# Fail only on bots.
+HttpCache.RangeGET_Cancel
+HttpCache.RangeGET_Cancel2
+HttpCache.RangeGET_OK
+HttpCache.RangeGET_Previous200
+HttpCache.RangeGET_Revalidate2
+HttpCache.RangeGET_SyncOK
+HttpCache.TypicalGET_ConditionalRequest
+# Death tests are not supported with apks.
+*DeathTest*
+# These are death tests and thus also disabled.
+PrioritizedDispatcherTest.CancelNull
+PrioritizedDispatcherTest.CancelMissing
diff --git a/build/android/pylib/gtest/filter/sync_unit_tests_disabled b/build/android/pylib/gtest/filter/sync_unit_tests_disabled
new file mode 100644
index 00000000000..cc4b72d1313
--- /dev/null
+++ b/build/android/pylib/gtest/filter/sync_unit_tests_disabled
@@ -0,0 +1,4 @@
+SyncHttpBridgeTest.*
+
+# crbug.com/144422
+OnDiskSyncableDirectory.FailInitialWrite
diff --git a/build/android/pylib/gtest/filter/unit_tests_disabled b/build/android/pylib/gtest/filter/unit_tests_disabled
new file mode 100644
index 00000000000..c7851fdcb6d
--- /dev/null
+++ b/build/android/pylib/gtest/filter/unit_tests_disabled
@@ -0,0 +1,119 @@
+# List of suppressions
+
+# The UDP related tests currently do not work on Android because
+# we lack a UDP forwarder tool.
+NetworkStatsTestUDP.*
+
+# Missing test resource of 16MB.
+HistoryProfileTest.TypicalProfileVersion
+
+# crbug.com/139408
+SQLitePersistentCookieStoreTest.TestDontLoadOldSessionCookies
+SQLitePersistentCookieStoreTest.PersistIsPersistent
+
+# crbug.com/139433
+AutofillTableTest.AutofillProfile*
+AutofillTableTest.UpdateAutofillProfile
+
+# crbug.com/139400
+AutofillProfileTest.*
+CreditCardTest.SetInfoExpirationMonth
+
+# crbug.com/139398
+DownloadItemModelTest.InterruptTooltip
+
+# Tests crashing in the APK
+# l10n_util.cc(655)] Check failed: std::string::npos != pos
+DownloadItemModelTest.InterruptStatus
+# l10n_util.cc(655)] Check failed: std::string::npos != pos
+WebsiteSettingsTest.OnSiteDataAccessed
+
+# crbug.com/139423
+ValueStoreFrontendTest.GetExistingData
+
+# crbug.com/139421
+ChromeSelectFilePolicyTest.ExpectAsynchronousListenerCall
+
+# http://crbug.com/139033
+ChromeDownloadManagerDelegateTest.StartDownload_PromptAlways
+
+# Extension support is limited on Android.
+# Some of these can be enabled if we register extension related prefs in
+# browser_prefs.cc
+ExtensionTest.*
+ExtensionAPI.*
+ExtensionFileUtilTest.*
+ExtensionPermissionsTest.*
+ExtensionUnpackerTest.*
+ActiveTabTest.*
+ExtensionAppsPromo.*
+ComponentLoaderTest.*
+ExtensionFromUserScript.*
+ExtensionFromWebApp.*
+ExtensionIconManagerTest.*
+ExtensionServiceTest.*
+ExtensionServiceTestSimple.*
+ExtensionSourcePriorityTest.*
+ExtensionSpecialStoragePolicyTest.*
+ExternalPolicyProviderTest.*
+ExternalProviderImplTest.*
+MenuManagerTest.*
+PageActionControllerTest.*
+PermissionsUpdaterTest.*
+ImageLoaderTest.*
+ImageLoadingTrackerTest.*
+ExtensionSettingsFrontendTest.*
+ExtensionSettingsSyncTest.*
+ExtensionUpdaterTest.*
+UserScriptListenerTest.*
+WebApplicationTest.GetShortcutInfoForTab
+ExtensionActionIconFactoryTest.*
+
+# crbug.com/139411
+AutocompleteProviderTest.*
+HistoryContentsProviderBodyOnlyTest.*
+HistoryContentsProviderTest.*
+HQPOrderingTest.*
+SearchProviderTest.*
+
+ProtocolHandlerRegistryTest.TestOSRegistrationFailure
+
+# crbug.com/139418
+SQLiteServerBoundCertStoreTest.TestUpgradeV1
+SQLiteServerBoundCertStoreTest.TestUpgradeV2
+
+ProfileSyncComponentsFactoryImplTest.*
+PermissionsTest.GetWarningMessages_Plugins
+ImageOperations.ResizeShouldAverageColors
+
+# crbug.com/138275
+PrerenderTest.*
+RenderWidgetTest.OnMsgPaintAtSize
+
+# crbug.com/139643
+VariationsUtilTest.DisableAfterInitialization
+VariationsUtilTest.AssociateGoogleVariationID
+VariationsUtilTest.NoAssociation
+
+# crbug.com/141473
+AutofillManagerTest.UpdatePasswordSyncState
+AutofillManagerTest.UpdatePasswordGenerationState
+
+# crbug.com/144227
+ExtensionIconImageTest.*
+
+# crbug.com/145843
+EntropyProviderTest.UseOneTimeRandomizationSHA1
+EntropyProviderTest.UseOneTimeRandomizationPermuted
+
+# crbug.com/147500
+ManifestTest.RestrictedKeys
+
+# crbug.com/152599
+SyncSearchEngineDataTypeControllerTest.*
+
+# crbug.com/256259
+DiagnosticsModelTest.RunAll
+
+# Death tests are not supported with apks.
+*DeathTest*
diff --git a/build/android/pylib/gtest/filter/webkit_unit_tests_disabled b/build/android/pylib/gtest/filter/webkit_unit_tests_disabled
new file mode 100644
index 00000000000..1ffa325899b
--- /dev/null
+++ b/build/android/pylib/gtest/filter/webkit_unit_tests_disabled
@@ -0,0 +1,25 @@
+# List of suppressions
+
+# crbug.com/159935
+WebCompositorInputHandlerImplTest.gestureFlingAnimates
+WebCompositorInputHandlerImplTest.gestureFlingTransferResets
+WebPageSerializerTest.HTMLNodes
+
+# crbug.com/241730
+ScrollAnimatorNoneTest.CurveMathQuartic
+ScrollAnimatorNoneTest.ScrollDownToBumper
+ScrollAnimatorNoneTest.ScrollQuadraticSmoothed
+ScrollAnimatorNoneTest.ScrollTwiceCubic
+ScrollAnimatorNoneTest.VaryingInputsEquivalencyCoastSteep
+WebViewTest.VisitedLinkCrash
+
+# Disabled until blink roll r151682
+DeferredImageDecoderTest.drawScaledIntoSkPicture
+
+# Disabled until blink roll r173540
+DeferredImageDecoderTest.decodeOnOtherThread
+DeferredImageDecoderTest.drawIntoSkPicture
+DeferredImageDecoderTest.drawIntoSkPictureProgressive
+
+# crbug.com/320005
+CoreAnimationCompositorAnimationsTest.ConvertTimingForCompositorIterationCount
diff --git a/build/android/pylib/gtest/gtest_config.py b/build/android/pylib/gtest/gtest_config.py
new file mode 100644
index 00000000000..76e0f505957
--- /dev/null
+++ b/build/android/pylib/gtest/gtest_config.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2013 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.
+
+"""Configuration file for android gtest suites."""
+
+# Add new suites here before upgrading them to the stable list below.
+EXPERIMENTAL_TEST_SUITES = [
+ 'components_browsertests',
+ 'content_gl_tests',
+ 'heap_profiler_unittests',
+ 'devtools_bridge_tests',
+]
+
+TELEMETRY_EXPERIMENTAL_TEST_SUITES = [
+ 'telemetry_unittests',
+]
+
+# Do not modify this list without approval of an android owner.
+# This list determines which suites are run by default, both for local
+# testing and on android trybots running on commit-queue.
+STABLE_TEST_SUITES = [
+ 'android_webview_unittests',
+ 'base_unittests',
+ 'breakpad_unittests',
+ 'cc_unittests',
+ 'components_unittests',
+ 'content_browsertests',
+ 'content_unittests',
+ 'events_unittests',
+ 'gl_tests',
+ 'gl_unittests',
+ 'gpu_unittests',
+ 'ipc_tests',
+ 'media_unittests',
+ 'midi_unittests',
+ 'net_unittests',
+ 'sandbox_linux_unittests',
+ 'skia_unittests',
+ 'sql_unittests',
+ 'sync_unit_tests',
+ 'ui_android_unittests',
+ 'ui_base_unittests',
+ 'ui_touch_selection_unittests',
+ 'unit_tests',
+ 'webkit_unit_tests',
+]
+
+# Tests fail in component=shared_library build, which is required for ASan.
+# http://crbug.com/344868
+ASAN_EXCLUDED_TEST_SUITES = [
+ 'breakpad_unittests',
+ 'sandbox_linux_unittests'
+]
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
new file mode 100644
index 00000000000..3285e0b6985
--- /dev/null
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -0,0 +1,329 @@
+# Copyright 2014 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.
+
+import logging
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import test_instance
+from pylib.utils import apk_helper
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common'))
+import unittest_util
+
+
+BROWSER_TEST_SUITES = [
+ 'components_browsertests',
+ 'content_browsertests',
+]
+
+
+_DEFAULT_ISOLATE_FILE_PATHS = {
+ 'base_unittests': 'base/base_unittests.isolate',
+ 'blink_heap_unittests':
+ 'third_party/WebKit/Source/platform/heap/BlinkHeapUnitTests.isolate',
+ 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
+ 'cc_perftests': 'cc/cc_perftests.isolate',
+ 'components_browsertests': 'components/components_browsertests.isolate',
+ 'components_unittests': 'components/components_unittests.isolate',
+ 'content_browsertests': 'content/content_browsertests.isolate',
+ 'content_unittests': 'content/content_unittests.isolate',
+ 'media_perftests': 'media/media_perftests.isolate',
+ 'media_unittests': 'media/media_unittests.isolate',
+ 'midi_unittests': 'media/midi/midi_unittests.isolate',
+ 'net_unittests': 'net/net_unittests.isolate',
+ 'sql_unittests': 'sql/sql_unittests.isolate',
+ 'sync_unit_tests': 'sync/sync_unit_tests.isolate',
+ 'ui_base_unittests': 'ui/base/ui_base_tests.isolate',
+ 'unit_tests': 'chrome/unit_tests.isolate',
+ 'webkit_unit_tests':
+ 'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
+}
+
+
+# Used for filtering large data deps at a finer grain than what's allowed in
+# isolate files since pushing deps to devices is expensive.
+# Wildcards are allowed.
+_DEPS_EXCLUSION_LIST = [
+ 'chrome/test/data/extensions/api_test',
+ 'chrome/test/data/extensions/secure_shell',
+ 'chrome/test/data/firefox*',
+ 'chrome/test/data/gpu',
+ 'chrome/test/data/image_decoding',
+ 'chrome/test/data/import',
+ 'chrome/test/data/page_cycler',
+ 'chrome/test/data/perf',
+ 'chrome/test/data/pyauto_private',
+ 'chrome/test/data/safari_import',
+ 'chrome/test/data/scroll',
+ 'chrome/test/data/third_party',
+ 'third_party/hunspell_dictionaries/*.dic',
+ # crbug.com/258690
+ 'webkit/data/bmp_decoder',
+ 'webkit/data/ico_decoder',
+]
+
+
+_EXTRA_NATIVE_TEST_ACTIVITY = (
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
+ 'NativeTestActivity')
+_EXTRA_SHARD_SIZE_LIMIT =(
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
+ 'ShardSizeLimit')
+
+# TODO(jbudorick): Remove these once we're no longer parsing stdout to generate
+# results.
+_RE_TEST_STATUS = re.compile(
+ r'\[ +((?:RUN)|(?:FAILED)|(?:OK)) +\] ?([^ ]+)(?: \((\d+) ms\))?$')
+_RE_TEST_RUN_STATUS = re.compile(
+ r'\[ +(PASSED|RUNNER_FAILED|CRASHED) \] ?[^ ]+')
+
+
+# TODO(jbudorick): Make this a class method of GtestTestInstance once
+# test_package_apk and test_package_exe are gone.
+def ParseGTestListTests(raw_list):
+ """Parses a raw test list as provided by --gtest_list_tests.
+
+ Args:
+ raw_list: The raw test listing with the following format:
+
+ IPCChannelTest.
+ SendMessageInChannelConnected
+ IPCSyncChannelTest.
+ Simple
+ DISABLED_SendWithTimeoutMixedOKAndTimeout
+
+ Returns:
+ A list of all tests. For the above raw listing:
+
+ [IPCChannelTest.SendMessageInChannelConnected, IPCSyncChannelTest.Simple,
+ IPCSyncChannelTest.DISABLED_SendWithTimeoutMixedOKAndTimeout]
+ """
+ ret = []
+ current = ''
+ for test in raw_list:
+ if not test:
+ continue
+ if test[0] != ' ':
+ test_case = test.split()[0]
+ if test_case.endswith('.'):
+ current = test_case
+ elif not 'YOU HAVE' in test:
+ test_name = test.split()[0]
+ ret += [current + test_name]
+ return ret
+
+
+class GtestTestInstance(test_instance.TestInstance):
+
+ def __init__(self, args, isolate_delegate, error_func):
+ super(GtestTestInstance, self).__init__()
+ # TODO(jbudorick): Support multiple test suites.
+ if len(args.suite_name) > 1:
+ raise ValueError('Platform mode currently supports only 1 gtest suite')
+ self._suite = args.suite_name[0]
+
+ self._apk_path = os.path.join(
+ constants.GetOutDirectory(), '%s_apk' % self._suite,
+ '%s-debug.apk' % self._suite)
+ self._exe_path = os.path.join(constants.GetOutDirectory(),
+ self._suite)
+ if not os.path.exists(self._apk_path):
+ self._apk_path = None
+ self._activity = None
+ self._package = None
+ self._runner = None
+ else:
+ helper = apk_helper.ApkHelper(self._apk_path)
+ self._activity = helper.GetActivityName()
+ self._package = helper.GetPackageName()
+ self._runner = helper.GetInstrumentationName()
+ self._extras = {
+ _EXTRA_NATIVE_TEST_ACTIVITY: self._activity,
+ }
+ if self._suite in BROWSER_TEST_SUITES:
+ self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1
+
+ if not os.path.exists(self._exe_path):
+ self._exe_path = None
+ if not self._apk_path and not self._exe_path:
+ error_func('Could not find apk or executable for %s' % self._suite)
+
+ self._data_deps = []
+ if args.test_filter:
+ self._gtest_filter = args.test_filter
+ elif args.test_filter_file:
+ with open(args.test_filter_file, 'r') as f:
+ self._gtest_filter = ':'.join(l.strip() for l in f)
+ else:
+ self._gtest_filter = None
+
+ if not args.isolate_file_path:
+ default_isolate_file_path = _DEFAULT_ISOLATE_FILE_PATHS.get(self._suite)
+ if default_isolate_file_path:
+ args.isolate_file_path = os.path.join(
+ constants.DIR_SOURCE_ROOT, default_isolate_file_path)
+
+ if args.isolate_file_path:
+ self._isolate_abs_path = os.path.abspath(args.isolate_file_path)
+ self._isolate_delegate = isolate_delegate
+ self._isolated_abs_path = os.path.join(
+ constants.GetOutDirectory(), '%s.isolated' % self._suite)
+ else:
+ logging.warning('No isolate file provided. No data deps will be pushed.');
+ self._isolate_delegate = None
+
+ if args.app_data_files:
+ self._app_data_files = args.app_data_files
+ if args.app_data_file_dir:
+ self._app_data_file_dir = args.app_data_file_dir
+ else:
+ self._app_data_file_dir = tempfile.mkdtemp()
+ logging.critical('Saving app files to %s', self._app_data_file_dir)
+ else:
+ self._app_data_files = None
+ self._app_data_file_dir = None
+
+ #override
+ def TestType(self):
+ return 'gtest'
+
+ #override
+ def SetUp(self):
+ """Map data dependencies via isolate."""
+ if self._isolate_delegate:
+ self._isolate_delegate.Remap(
+ self._isolate_abs_path, self._isolated_abs_path)
+ self._isolate_delegate.PurgeExcluded(_DEPS_EXCLUSION_LIST)
+ self._isolate_delegate.MoveOutputDeps()
+ dest_dir = None
+ if self._suite == 'breakpad_unittests':
+ dest_dir = '/data/local/tmp/'
+ self._data_deps.extend([(constants.ISOLATE_DEPS_DIR, dest_dir)])
+
+
+ def GetDataDependencies(self):
+ """Returns the test suite's data dependencies.
+
+ Returns:
+ A list of (host_path, device_path) tuples to push. If device_path is
+ None, the client is responsible for determining where to push the file.
+ """
+ return self._data_deps
+
+ def FilterTests(self, test_list, disabled_prefixes=None):
+ """Filters |test_list| based on prefixes and, if present, a filter string.
+
+ Args:
+ test_list: The list of tests to filter.
+ disabled_prefixes: A list of test prefixes to filter. Defaults to
+ DISABLED_, FLAKY_, FAILS_, PRE_, and MANUAL_
+ Returns:
+ A filtered list of tests to run.
+ """
+ gtest_filter_strings = [
+ self._GenerateDisabledFilterString(disabled_prefixes)]
+ if self._gtest_filter:
+ gtest_filter_strings.append(self._gtest_filter)
+
+ filtered_test_list = test_list
+ for gtest_filter_string in gtest_filter_strings:
+ logging.debug('Filtering tests using: %s', gtest_filter_string)
+ filtered_test_list = unittest_util.FilterTestNames(
+ filtered_test_list, gtest_filter_string)
+ return filtered_test_list
+
+ def _GenerateDisabledFilterString(self, disabled_prefixes):
+ disabled_filter_items = []
+
+ if disabled_prefixes is None:
+ disabled_prefixes = ['DISABLED_', 'FLAKY_', 'FAILS_', 'PRE_', 'MANUAL_']
+ disabled_filter_items += ['%s*' % dp for dp in disabled_prefixes]
+ disabled_filter_items += ['*.%s*' % dp for dp in disabled_prefixes]
+
+ disabled_tests_file_path = os.path.join(
+ constants.DIR_SOURCE_ROOT, 'build', 'android', 'pylib', 'gtest',
+ 'filter', '%s_disabled' % self._suite)
+ if disabled_tests_file_path and os.path.exists(disabled_tests_file_path):
+ with open(disabled_tests_file_path) as disabled_tests_file:
+ disabled_filter_items += [
+ '%s' % l for l in (line.strip() for line in disabled_tests_file)
+ if l and not l.startswith('#')]
+
+ return '*-%s' % ':'.join(disabled_filter_items)
+
+ def ParseGTestOutput(self, output):
+ """Parses raw gtest output and returns a list of results.
+
+ Args:
+ output: A list of output lines.
+ Returns:
+ A list of base_test_result.BaseTestResults.
+ """
+ results = []
+ for l in output:
+ matcher = _RE_TEST_STATUS.match(l)
+ if matcher:
+ result_type = None
+ if matcher.group(1) == 'OK':
+ result_type = base_test_result.ResultType.PASS
+ elif matcher.group(1) == 'FAILED':
+ result_type = base_test_result.ResultType.FAIL
+
+ if result_type:
+ test_name = matcher.group(2)
+ duration = matcher.group(3) if matcher.group(3) else 0
+ results.append(base_test_result.BaseTestResult(
+ test_name, result_type, duration))
+ logging.info(l)
+ return results
+
+ #override
+ def TearDown(self):
+ """Clear the mappings created by SetUp."""
+ if self._isolate_delegate:
+ self._isolate_delegate.Clear()
+
+ @property
+ def activity(self):
+ return self._activity
+
+ @property
+ def apk(self):
+ return self._apk_path
+
+ @property
+ def app_file_dir(self):
+ return self._app_data_file_dir
+
+ @property
+ def app_files(self):
+ return self._app_data_files
+
+ @property
+ def exe(self):
+ return self._exe_path
+
+ @property
+ def extras(self):
+ return self._extras
+
+ @property
+ def package(self):
+ return self._package
+
+ @property
+ def runner(self):
+ return self._runner
+
+ @property
+ def suite(self):
+ return self._suite
+
diff --git a/build/android/pylib/gtest/gtest_test_instance_test.py b/build/android/pylib/gtest/gtest_test_instance_test.py
new file mode 100755
index 00000000000..c52b2354c41
--- /dev/null
+++ b/build/android/pylib/gtest/gtest_test_instance_test.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import unittest
+
+from pylib.gtest import gtest_test_instance
+
+
+class GtestTestInstanceTests(unittest.TestCase):
+
+ def testParseGTestListTests_simple(self):
+ raw_output = [
+ 'TestCaseOne.',
+ ' testOne',
+ ' testTwo',
+ 'TestCaseTwo.',
+ ' testThree',
+ ' testFour',
+ ]
+ actual = gtest_test_instance.ParseGTestListTests(raw_output)
+ expected = [
+ 'TestCaseOne.testOne',
+ 'TestCaseOne.testTwo',
+ 'TestCaseTwo.testThree',
+ 'TestCaseTwo.testFour',
+ ]
+ self.assertEqual(expected, actual)
+
+ def testParseGTestListTests_typeParameterized_old(self):
+ raw_output = [
+ 'TPTestCase/WithTypeParam/0.',
+ ' testOne',
+ ' testTwo',
+ ]
+ actual = gtest_test_instance.ParseGTestListTests(raw_output)
+ expected = [
+ 'TPTestCase/WithTypeParam/0.testOne',
+ 'TPTestCase/WithTypeParam/0.testTwo',
+ ]
+ self.assertEqual(expected, actual)
+
+ def testParseGTestListTests_typeParameterized_new(self):
+ raw_output = [
+ 'TPTestCase/WithTypeParam/0. # TypeParam = TypeParam0',
+ ' testOne',
+ ' testTwo',
+ ]
+ actual = gtest_test_instance.ParseGTestListTests(raw_output)
+ expected = [
+ 'TPTestCase/WithTypeParam/0.testOne',
+ 'TPTestCase/WithTypeParam/0.testTwo',
+ ]
+ self.assertEqual(expected, actual)
+
+ def testParseGTestListTests_valueParameterized_old(self):
+ raw_output = [
+ 'VPTestCase.',
+ ' testWithValueParam/0',
+ ' testWithValueParam/1',
+ ]
+ actual = gtest_test_instance.ParseGTestListTests(raw_output)
+ expected = [
+ 'VPTestCase.testWithValueParam/0',
+ 'VPTestCase.testWithValueParam/1',
+ ]
+ self.assertEqual(expected, actual)
+
+ def testParseGTestListTests_valueParameterized_new(self):
+ raw_output = [
+ 'VPTestCase.',
+ ' testWithValueParam/0 # GetParam() = 0',
+ ' testWithValueParam/1 # GetParam() = 1',
+ ]
+ actual = gtest_test_instance.ParseGTestListTests(raw_output)
+ expected = [
+ 'VPTestCase.testWithValueParam/0',
+ 'VPTestCase.testWithValueParam/1',
+ ]
+ self.assertEqual(expected, actual)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/gtest/local_device_gtest_run.py b/build/android/pylib/gtest/local_device_gtest_run.py
new file mode 100644
index 00000000000..f1cea4e03f4
--- /dev/null
+++ b/build/android/pylib/gtest/local_device_gtest_run.py
@@ -0,0 +1,241 @@
+# Copyright 2014 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.
+
+import itertools
+import logging
+import os
+import posixpath
+
+from pylib import constants
+from pylib import ports
+from pylib.base import test_run
+from pylib.device import device_errors
+from pylib.gtest import gtest_test_instance
+
+from pylib.local import local_test_server_spawner
+from pylib.local.device import local_device_environment
+from pylib.local.device import local_device_test_run
+from pylib.utils import device_temp_file
+
+_COMMAND_LINE_FLAGS_SUPPORTED = True
+
+_EXTRA_COMMAND_LINE_FILE = (
+ 'org.chromium.native_test.NativeTestActivity.CommandLineFile')
+_EXTRA_COMMAND_LINE_FLAGS = (
+ 'org.chromium.native_test.NativeTestActivity.CommandLineFlags')
+_EXTRA_TEST_LIST = (
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner'
+ '.TestList')
+
+_MAX_SHARD_SIZE = 256
+
+# TODO(jbudorick): Move this up to the test instance if the net test server is
+# handled outside of the APK for the remote_device environment.
+_SUITE_REQUIRES_TEST_SERVER_SPAWNER = [
+ 'components_browsertests', 'content_unittests', 'content_browsertests',
+ 'net_unittests', 'unit_tests'
+]
+
+# TODO(jbudorick): Move this inside _ApkDelegate once TestPackageApk is gone.
+def PullAppFilesImpl(device, package, files, directory):
+ device_dir = device.GetApplicationDataDirectory(package)
+ host_dir = os.path.join(directory, str(device))
+ for f in files:
+ device_file = posixpath.join(device_dir, f)
+ host_file = os.path.join(host_dir, *f.split(posixpath.sep))
+ host_file_base, ext = os.path.splitext(host_file)
+ for i in itertools.count():
+ host_file = '%s_%d%s' % (host_file_base, i, ext)
+ if not os.path.exists(host_file):
+ break
+ device.PullFile(device_file, host_file)
+
+class _ApkDelegate(object):
+ def __init__(self, test_instance):
+ self._activity = test_instance.activity
+ self._apk = test_instance.apk
+ self._package = test_instance.package
+ self._runner = test_instance.runner
+
+ self._component = '%s/%s' % (self._package, self._runner)
+ self._extras = test_instance.extras
+
+ def Install(self, device):
+ device.Install(self._apk)
+
+ def Run(self, test, device, flags=None, **kwargs):
+ extras = dict(self._extras)
+
+ with device_temp_file.DeviceTempFile(device.adb) as command_line_file:
+ device.WriteFile(command_line_file.name, '_ %s' % flags if flags else '_')
+ extras[_EXTRA_COMMAND_LINE_FILE] = command_line_file.name
+
+ with device_temp_file.DeviceTempFile(device.adb) as test_list_file:
+ if test:
+ device.WriteFile(test_list_file.name, '\n'.join(test))
+ extras[_EXTRA_TEST_LIST] = test_list_file.name
+
+ return device.StartInstrumentation(
+ self._component, extras=extras, raw=False, **kwargs)
+
+ def PullAppFiles(self, device, files, directory):
+ PullAppFilesImpl(device, self._package, files, directory)
+
+ def Clear(self, device):
+ device.ClearApplicationState(self._package)
+
+
+class _ExeDelegate(object):
+ def __init__(self, tr, exe):
+ self._exe_host_path = exe
+ self._exe_file_name = os.path.split(exe)[-1]
+ self._exe_device_path = '%s/%s' % (
+ constants.TEST_EXECUTABLE_DIR, self._exe_file_name)
+ deps_host_path = self._exe_host_path + '_deps'
+ if os.path.exists(deps_host_path):
+ self._deps_host_path = deps_host_path
+ self._deps_device_path = self._exe_device_path + '_deps'
+ else:
+ self._deps_host_path = None
+ self._test_run = tr
+
+ def Install(self, device):
+ # TODO(jbudorick): Look into merging this with normal data deps pushing if
+ # executables become supported on nonlocal environments.
+ host_device_tuples = [(self._exe_host_path, self._exe_device_path)]
+ if self._deps_host_path:
+ host_device_tuples.append((self._deps_host_path, self._deps_device_path))
+ device.PushChangedFiles(host_device_tuples)
+
+ def Run(self, test, device, flags=None, **kwargs):
+ cmd = [
+ self._test_run.GetTool(device).GetTestWrapper(),
+ self._exe_device_path,
+ ]
+ if test:
+ cmd.append('--gtest_filter=%s' % ':'.join(test))
+ if flags:
+ cmd.append(flags)
+ cwd = constants.TEST_EXECUTABLE_DIR
+
+ env = {
+ 'LD_LIBRARY_PATH':
+ '%s/%s_deps' % (constants.TEST_EXECUTABLE_DIR, self._exe_file_name),
+ }
+ try:
+ gcov_strip_depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP']
+ external = device.GetExternalStoragePath()
+ env['GCOV_PREFIX'] = '%s/gcov' % external
+ env['GCOV_PREFIX_STRIP'] = gcov_strip_depth
+ except (device_errors.CommandFailedError, KeyError):
+ pass
+
+ # TODO(jbudorick): Switch to just RunShellCommand once perezju@'s CL
+ # for long shell commands lands.
+ with device_temp_file.DeviceTempFile(device.adb) as script_file:
+ script_contents = ' '.join(cmd)
+ logging.info('script contents: %r' % script_contents)
+ device.WriteFile(script_file.name, script_contents)
+ output = device.RunShellCommand(['sh', script_file.name], cwd=cwd,
+ env=env, **kwargs)
+ return output
+
+ def PullAppFiles(self, device, files, directory):
+ pass
+
+ def Clear(self, device):
+ device.KillAll(self._exe_file_name, blocking=True, timeout=30, quiet=True)
+
+
+class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
+
+ def __init__(self, env, test_instance):
+ assert isinstance(env, local_device_environment.LocalDeviceEnvironment)
+ assert isinstance(test_instance, gtest_test_instance.GtestTestInstance)
+ super(LocalDeviceGtestRun, self).__init__(env, test_instance)
+
+ if self._test_instance.apk:
+ self._delegate = _ApkDelegate(self._test_instance)
+ elif self._test_instance.exe:
+ self._delegate = _ExeDelegate(self, self._test_instance.exe)
+
+ self._servers = {}
+
+ #override
+ def TestPackage(self):
+ return self._test_instance.suite
+
+ #override
+ def SetUp(self):
+
+ def individual_device_set_up(dev, host_device_tuples):
+ # Install test APK.
+ self._delegate.Install(dev)
+
+ # Push data dependencies.
+ external_storage = dev.GetExternalStoragePath()
+ host_device_tuples = [
+ (h, d if d is not None else external_storage)
+ for h, d in host_device_tuples]
+ dev.PushChangedFiles(host_device_tuples)
+
+ self._servers[str(dev)] = []
+ if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER:
+ self._servers[str(dev)].append(
+ local_test_server_spawner.LocalTestServerSpawner(
+ ports.AllocateTestServerPort(), dev, self.GetTool(dev)))
+
+ for s in self._servers[str(dev)]:
+ s.SetUp()
+
+ self._env.parallel_devices.pMap(individual_device_set_up,
+ self._test_instance.GetDataDependencies())
+
+ #override
+ def _ShouldShard(self):
+ return True
+
+ #override
+ def _CreateShards(self, tests):
+ device_count = len(self._env.devices)
+ shards = []
+ for i in xrange(0, device_count):
+ unbounded_shard = tests[i::device_count]
+ shards += [unbounded_shard[j:j+_MAX_SHARD_SIZE]
+ for j in xrange(0, len(unbounded_shard), _MAX_SHARD_SIZE)]
+ return shards
+
+ #override
+ def _GetTests(self):
+ tests = self._delegate.Run(
+ None, self._env.devices[0], flags='--gtest_list_tests')
+ tests = gtest_test_instance.ParseGTestListTests(tests)
+ tests = self._test_instance.FilterTests(tests)
+ return tests
+
+ #override
+ def _RunTest(self, device, test):
+ # Run the test.
+ output = self._delegate.Run(
+ test, device, timeout=900, retries=0)
+ for s in self._servers[str(device)]:
+ s.Reset()
+ if self._test_instance.app_files:
+ self._delegate.PullAppFiles(device, self._test_instance.app_files,
+ self._test_instance.app_file_dir)
+ self._delegate.Clear(device)
+
+ # Parse the output.
+ # TODO(jbudorick): Transition test scripts away from parsing stdout.
+ results = self._test_instance.ParseGTestOutput(output)
+ return results
+
+ #override
+ def TearDown(self):
+ def individual_device_tear_down(dev):
+ for s in self._servers[str(dev)]:
+ s.TearDown()
+
+ self._env.parallel_devices.pMap(individual_device_tear_down)
+
diff --git a/build/android/pylib/gtest/setup.py b/build/android/pylib/gtest/setup.py
new file mode 100644
index 00000000000..f563ccf9931
--- /dev/null
+++ b/build/android/pylib/gtest/setup.py
@@ -0,0 +1,230 @@
+# Copyright 2013 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.
+
+"""Generates test runner factory and tests for GTests."""
+# pylint: disable=W0212
+
+import logging
+import os
+import sys
+
+from pylib import constants
+
+from pylib.base import base_setup
+from pylib.base import base_test_result
+from pylib.base import test_dispatcher
+from pylib.device import device_utils
+from pylib.gtest import gtest_test_instance
+from pylib.gtest import test_package_apk
+from pylib.gtest import test_package_exe
+from pylib.gtest import test_runner
+
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
+ 'common'))
+import unittest_util # pylint: disable=F0401
+
+
+ISOLATE_FILE_PATHS = gtest_test_instance._DEFAULT_ISOLATE_FILE_PATHS
+
+
+# Used for filtering large data deps at a finer grain than what's allowed in
+# isolate files since pushing deps to devices is expensive.
+# Wildcards are allowed.
+DEPS_EXCLUSION_LIST = [
+ 'chrome/test/data/extensions/api_test',
+ 'chrome/test/data/extensions/secure_shell',
+ 'chrome/test/data/firefox*',
+ 'chrome/test/data/gpu',
+ 'chrome/test/data/image_decoding',
+ 'chrome/test/data/import',
+ 'chrome/test/data/page_cycler',
+ 'chrome/test/data/perf',
+ 'chrome/test/data/pyauto_private',
+ 'chrome/test/data/safari_import',
+ 'chrome/test/data/scroll',
+ 'chrome/test/data/third_party',
+ 'third_party/hunspell_dictionaries/*.dic',
+ # crbug.com/258690
+ 'webkit/data/bmp_decoder',
+ 'webkit/data/ico_decoder',
+]
+
+
+def _GetDisabledTestsFilterFromFile(suite_name):
+ """Returns a gtest filter based on the *_disabled file.
+
+ Args:
+ suite_name: Name of the test suite (e.g. base_unittests).
+
+ Returns:
+ A gtest filter which excludes disabled tests.
+ Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc'
+ """
+ filter_file_path = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ 'filter', '%s_disabled' % suite_name)
+
+ if not filter_file_path or not os.path.exists(filter_file_path):
+ logging.info('No filter file found at %s', filter_file_path)
+ return '*'
+
+ filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()]
+ if x and x[0] != '#']
+ disabled_filter = '*-%s' % ':'.join(filters)
+ logging.info('Applying filter "%s" obtained from %s',
+ disabled_filter, filter_file_path)
+ return disabled_filter
+
+
+def _GetTests(test_options, test_package, devices):
+ """Get a list of tests.
+
+ Args:
+ test_options: A GTestOptions object.
+ test_package: A TestPackageApk object.
+ devices: A list of attached devices.
+
+ Returns:
+ A list of all the tests in the test suite.
+ """
+ class TestListResult(base_test_result.BaseTestResult):
+ def __init__(self):
+ super(TestListResult, self).__init__(
+ 'gtest_list_tests', base_test_result.ResultType.PASS)
+ self.test_list = []
+
+ def TestListerRunnerFactory(device, _shard_index):
+ class TestListerRunner(test_runner.TestRunner):
+ def RunTest(self, _test):
+ result = TestListResult()
+ self.test_package.Install(self.device)
+ result.test_list = self.test_package.GetAllTests(self.device)
+ results = base_test_result.TestRunResults()
+ results.AddResult(result)
+ return results, None
+ return TestListerRunner(test_options, device, test_package)
+
+ results, _no_retry = test_dispatcher.RunTests(
+ ['gtest_list_tests'], TestListerRunnerFactory, devices)
+ tests = []
+ for r in results.GetAll():
+ tests.extend(r.test_list)
+ return tests
+
+
+def _FilterTestsUsingPrefixes(all_tests, pre=False, manual=False):
+ """Removes tests with disabled prefixes.
+
+ Args:
+ all_tests: List of tests to filter.
+ pre: If True, include tests with PRE_ prefix.
+ manual: If True, include tests with MANUAL_ prefix.
+
+ Returns:
+ List of tests remaining.
+ """
+ filtered_tests = []
+ filter_prefixes = ['DISABLED_', 'FLAKY_', 'FAILS_']
+
+ if not pre:
+ filter_prefixes.append('PRE_')
+
+ if not manual:
+ filter_prefixes.append('MANUAL_')
+
+ for t in all_tests:
+ test_case, test = t.split('.', 1)
+ if not any([test_case.startswith(prefix) or test.startswith(prefix) for
+ prefix in filter_prefixes]):
+ filtered_tests.append(t)
+ return filtered_tests
+
+
+def _FilterDisabledTests(tests, suite_name, has_gtest_filter):
+ """Removes disabled tests from |tests|.
+
+ Applies the following filters in order:
+ 1. Remove tests with disabled prefixes.
+ 2. Remove tests specified in the *_disabled files in the 'filter' dir
+
+ Args:
+ tests: List of tests.
+ suite_name: Name of the test suite (e.g. base_unittests).
+ has_gtest_filter: Whether a gtest_filter is provided.
+
+ Returns:
+ List of tests remaining.
+ """
+ tests = _FilterTestsUsingPrefixes(
+ tests, has_gtest_filter, has_gtest_filter)
+ tests = unittest_util.FilterTestNames(
+ tests, _GetDisabledTestsFilterFromFile(suite_name))
+
+ return tests
+
+
+def Setup(test_options, devices):
+ """Create the test runner factory and tests.
+
+ Args:
+ test_options: A GTestOptions object.
+ devices: A list of attached devices.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+ test_package = test_package_apk.TestPackageApk(test_options.suite_name)
+ if not os.path.exists(test_package.suite_path):
+ exe_test_package = test_package_exe.TestPackageExecutable(
+ test_options.suite_name)
+ if not os.path.exists(exe_test_package.suite_path):
+ raise Exception(
+ 'Did not find %s target. Ensure it has been built.\n'
+ '(not found at %s or %s)'
+ % (test_options.suite_name,
+ test_package.suite_path,
+ exe_test_package.suite_path))
+ test_package = exe_test_package
+ logging.warning('Found target %s', test_package.suite_path)
+
+ i = base_setup.GenerateDepsDirUsingIsolate(test_options.suite_name,
+ test_options.isolate_file_path,
+ ISOLATE_FILE_PATHS,
+ DEPS_EXCLUSION_LIST)
+ def push_data_deps_to_device_dir(device):
+ device_dir = (constants.TEST_EXECUTABLE_DIR
+ if test_package.suite_name == 'breakpad_unittests'
+ else device.GetExternalStoragePath())
+ base_setup.PushDataDeps(device, device_dir, test_options)
+ device_utils.DeviceUtils.parallel(devices).pMap(push_data_deps_to_device_dir)
+ if i:
+ i.Clear()
+
+ tests = _GetTests(test_options, test_package, devices)
+
+ # Constructs a new TestRunner with the current options.
+ def TestRunnerFactory(device, _shard_index):
+ return test_runner.TestRunner(
+ test_options,
+ device,
+ test_package)
+
+ if test_options.run_disabled:
+ test_options = test_options._replace(
+ test_arguments=('%s --gtest_also_run_disabled_tests' %
+ test_options.test_arguments))
+ else:
+ tests = _FilterDisabledTests(tests, test_options.suite_name,
+ bool(test_options.gtest_filter))
+ if test_options.gtest_filter:
+ tests = unittest_util.FilterTestNames(tests, test_options.gtest_filter)
+
+ # Coalesce unit tests into a single test per device
+ if test_options.suite_name not in gtest_test_instance.BROWSER_TEST_SUITES:
+ num_devices = len(devices)
+ tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)]
+ tests = [t for t in tests if t]
+
+ return (TestRunnerFactory, tests)
diff --git a/build/android/pylib/gtest/test_options.py b/build/android/pylib/gtest/test_options.py
new file mode 100644
index 00000000000..8bc699630a1
--- /dev/null
+++ b/build/android/pylib/gtest/test_options.py
@@ -0,0 +1,19 @@
+# Copyright 2013 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.
+
+"""Defines the GTestOptions named tuple."""
+
+import collections
+
+GTestOptions = collections.namedtuple('GTestOptions', [
+ 'tool',
+ 'gtest_filter',
+ 'run_disabled',
+ 'test_arguments',
+ 'timeout',
+ 'isolate_file_path',
+ 'suite_name',
+ 'app_data_files',
+ 'app_data_file_dir',
+ 'delete_stale_data'])
diff --git a/build/android/pylib/gtest/test_package.py b/build/android/pylib/gtest/test_package.py
new file mode 100644
index 00000000000..4042a98a132
--- /dev/null
+++ b/build/android/pylib/gtest/test_package.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2012 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.
+
+"""Base class representing GTest test packages."""
+# pylint: disable=R0201
+
+
+class TestPackage(object):
+
+ """A helper base class for both APK and stand-alone executables.
+
+ Args:
+ suite_name: Name of the test suite (e.g. base_unittests).
+ """
+ def __init__(self, suite_name):
+ self.suite_name = suite_name
+
+ def ClearApplicationState(self, device):
+ """Clears the application state.
+
+ Args:
+ device: Instance of DeviceUtils.
+ """
+ raise NotImplementedError('Method must be overridden.')
+
+ def CreateCommandLineFileOnDevice(self, device, test_filter, test_arguments):
+ """Creates a test runner script and pushes to the device.
+
+ Args:
+ device: Instance of DeviceUtils.
+ test_filter: A test_filter flag.
+ test_arguments: Additional arguments to pass to the test binary.
+ """
+ raise NotImplementedError('Method must be overridden.')
+
+ def GetAllTests(self, device):
+ """Returns a list of all tests available in the test suite.
+
+ Args:
+ device: Instance of DeviceUtils.
+ """
+ raise NotImplementedError('Method must be overridden.')
+
+ def GetGTestReturnCode(self, _device):
+ return None
+
+ def SpawnTestProcess(self, device):
+ """Spawn the test process.
+
+ Args:
+ device: Instance of DeviceUtils.
+
+ Returns:
+ An instance of pexpect spawn class.
+ """
+ raise NotImplementedError('Method must be overridden.')
+
+ def Install(self, device):
+ """Install the test package to the device.
+
+ Args:
+ device: Instance of DeviceUtils.
+ """
+ raise NotImplementedError('Method must be overridden.')
+
+ def PullAppFiles(self, device, files, directory):
+ """Pull application data from the device.
+
+ Args:
+ device: Instance of DeviceUtils.
+ files: A list of paths relative to the application data directory to
+ retrieve from the device.
+ directory: The host directory to which files should be pulled.
+ """
+ raise NotImplementedError('Method must be overridden.')
diff --git a/build/android/pylib/gtest/test_package_apk.py b/build/android/pylib/gtest/test_package_apk.py
new file mode 100644
index 00000000000..a679b0358ed
--- /dev/null
+++ b/build/android/pylib/gtest/test_package_apk.py
@@ -0,0 +1,157 @@
+# Copyright (c) 2012 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.
+
+"""Defines TestPackageApk to help run APK-based native tests."""
+# pylint: disable=W0212
+
+import itertools
+import logging
+import os
+import posixpath
+import shlex
+import sys
+import tempfile
+import time
+
+from pylib import android_commands
+from pylib import constants
+from pylib import pexpect
+from pylib.device import device_errors
+from pylib.device import intent
+from pylib.gtest import gtest_test_instance
+from pylib.gtest import local_device_gtest_run
+from pylib.gtest.test_package import TestPackage
+
+
+class TestPackageApk(TestPackage):
+ """A helper class for running APK-based native tests."""
+
+ def __init__(self, suite_name):
+ """
+ Args:
+ suite_name: Name of the test suite (e.g. base_unittests).
+ """
+ TestPackage.__init__(self, suite_name)
+ self.suite_path = os.path.join(
+ constants.GetOutDirectory(), '%s_apk' % suite_name,
+ '%s-debug.apk' % suite_name)
+ if suite_name == 'content_browsertests':
+ self._package_info = constants.PACKAGE_INFO['content_browsertests']
+ elif suite_name == 'components_browsertests':
+ self._package_info = constants.PACKAGE_INFO['components_browsertests']
+ else:
+ self._package_info = constants.PACKAGE_INFO['gtest']
+
+ if suite_name == 'net_unittests':
+ self._extras = {'RunInSubThread': ''}
+ else:
+ self._extras = []
+
+ def _CreateCommandLineFileOnDevice(self, device, options):
+ device.WriteFile(self._package_info.cmdline_file,
+ self.suite_name + ' ' + options)
+
+ def _GetFifo(self):
+ # The test.fifo path is determined by:
+ # testing/android/native_test/java/src/org/chromium/native_test/
+ # NativeTestActivity.java and
+ # testing/android/native_test_launcher.cc
+ return '/data/data/' + self._package_info.package + '/files/test.fifo'
+
+ def _ClearFifo(self, device):
+ device.RunShellCommand('rm -f ' + self._GetFifo())
+
+ def _WatchFifo(self, device, timeout, logfile=None):
+ for i in range(100):
+ if device.FileExists(self._GetFifo()):
+ logging.info('Fifo created. Slept for %f secs' % (i * 0.5))
+ break
+ time.sleep(0.5)
+ else:
+ raise device_errors.DeviceUnreachableError(
+ 'Unable to find fifo on device %s ' % self._GetFifo())
+ args = shlex.split(device.old_interface.Adb()._target_arg)
+ args += ['shell', 'cat', self._GetFifo()]
+ return pexpect.spawn('adb', args, timeout=timeout, logfile=logfile)
+
+ def _StartActivity(self, device, force_stop=True):
+ device.StartActivity(
+ intent.Intent(package=self._package_info.package,
+ activity=self._package_info.activity,
+ action='android.intent.action.MAIN',
+ extras=self._extras),
+ # No wait since the runner waits for FIFO creation anyway.
+ blocking=False,
+ force_stop=force_stop)
+
+ #override
+ def ClearApplicationState(self, device):
+ device.ClearApplicationState(self._package_info.package)
+ # Content shell creates a profile on the sdscard which accumulates cache
+ # files over time.
+ if self.suite_name == 'content_browsertests':
+ try:
+ device.RunShellCommand(
+ 'rm -r %s/content_shell' % device.GetExternalStoragePath(),
+ timeout=60 * 2)
+ except device_errors.CommandFailedError:
+ # TODO(jbudorick) Handle this exception appropriately once the
+ # conversions are done.
+ pass
+ elif self.suite_name == 'components_browsertests':
+ try:
+ device.RunShellCommand(
+ 'rm -r %s/components_shell' % device.GetExternalStoragePath(),
+ timeout=60 * 2)
+ except device_errors.CommandFailedError:
+ # TODO(jbudorick) Handle this exception appropriately once the
+ # conversions are done.
+ pass
+
+ #override
+ def CreateCommandLineFileOnDevice(self, device, test_filter, test_arguments):
+ self._CreateCommandLineFileOnDevice(
+ device, '--gtest_filter=%s %s' % (test_filter, test_arguments))
+
+ #override
+ def GetAllTests(self, device):
+ self._CreateCommandLineFileOnDevice(device, '--gtest_list_tests')
+ try:
+ self.tool.SetupEnvironment()
+ # Clear and start monitoring logcat.
+ self._ClearFifo(device)
+ self._StartActivity(device)
+ # Wait for native test to complete.
+ p = self._WatchFifo(device, timeout=30 * self.tool.GetTimeoutScale())
+ p.expect('< %s' %
+ (constants.TEST_EXECUTABLE_DIR,
+ self._AddNativeCoverageExports(device),
+ tool_wrapper,
+ constants.TEST_EXECUTABLE_DIR,
+ self.suite_name,
+ constants.TEST_EXECUTABLE_DIR,
+ self.suite_name,
+ test_filter, test_arguments,
+ TestPackageExecutable._TEST_RUNNER_RET_VAL_FILE))
+ sh_script_file.flush()
+ cmd_helper.RunCmd(['chmod', '+x', sh_script_file.name])
+ device.PushChangedFiles([(
+ sh_script_file.name,
+ constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh')])
+ logging.info('Conents of the test runner script: ')
+ for line in open(sh_script_file.name).readlines():
+ logging.info(' ' + line.rstrip())
+
+ #override
+ def GetAllTests(self, device):
+ lib_path = posixpath.join(
+ constants.TEST_EXECUTABLE_DIR, '%s_deps' % self.suite_name)
+
+ cmd = []
+ if self.tool.GetTestWrapper():
+ cmd.append(self.tool.GetTestWrapper())
+ cmd.extend([
+ posixpath.join(constants.TEST_EXECUTABLE_DIR, self.suite_name),
+ '--gtest_list_tests'])
+
+ output = device.RunShellCommand(
+ cmd, check_return=True, env={'LD_LIBRARY_PATH': lib_path})
+ return gtest_test_instance.ParseGTestListTests(output)
+
+ #override
+ def SpawnTestProcess(self, device):
+ args = ['adb', '-s', str(device), 'shell', 'sh',
+ constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh']
+ logging.info(args)
+ return pexpect.spawn(args[0], args[1:], logfile=sys.stdout)
+
+ #override
+ def Install(self, device):
+ if self.tool.NeedsDebugInfo():
+ target_name = self.suite_path
+ else:
+ target_name = self.suite_path + '_stripped'
+ if not os.path.isfile(target_name):
+ raise Exception('Did not find %s, build target %s' %
+ (target_name, self.suite_name + '_stripped'))
+
+ target_mtime = os.stat(target_name).st_mtime
+ source_mtime = os.stat(self.suite_path).st_mtime
+ if target_mtime < source_mtime:
+ raise Exception(
+ 'stripped binary (%s, timestamp %d) older than '
+ 'source binary (%s, timestamp %d), build target %s' %
+ (target_name, target_mtime, self.suite_path, source_mtime,
+ self.suite_name + '_stripped'))
+
+ test_binary_path = constants.TEST_EXECUTABLE_DIR + '/' + self.suite_name
+ device.PushChangedFiles([(target_name, test_binary_path)])
+ deps_path = self.suite_path + '_deps'
+ if os.path.isdir(deps_path):
+ device.PushChangedFiles([(deps_path, test_binary_path + '_deps')])
+
+ #override
+ def PullAppFiles(self, device, files, directory):
+ pass
diff --git a/build/android/pylib/gtest/test_runner.py b/build/android/pylib/gtest/test_runner.py
new file mode 100644
index 00000000000..a48f18a70f6
--- /dev/null
+++ b/build/android/pylib/gtest/test_runner.py
@@ -0,0 +1,217 @@
+# Copyright (c) 2012 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.
+
+import logging
+import os
+import re
+import tempfile
+
+from pylib import pexpect
+from pylib import ports
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.device import device_errors
+from pylib.gtest import gtest_test_instance
+from pylib.local import local_test_server_spawner
+from pylib.perf import perf_control
+
+# Test case statuses.
+RE_RUN = re.compile('\\[ RUN \\] ?(.*)\r\n')
+RE_FAIL = re.compile('\\[ FAILED \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
+RE_OK = re.compile('\\[ OK \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
+
+# Test run statuses.
+RE_PASSED = re.compile('\\[ PASSED \\] ?(.*)\r\n')
+RE_RUNNER_FAIL = re.compile('\\[ RUNNER_FAILED \\] ?(.*)\r\n')
+# Signal handlers are installed before starting tests
+# to output the CRASHED marker when a crash happens.
+RE_CRASH = re.compile('\\[ CRASHED \\](.*)\r\n')
+
+# Bots that don't output anything for 20 minutes get timed out, so that's our
+# hard cap.
+_INFRA_STDOUT_TIMEOUT = 20 * 60
+
+
+def _TestSuiteRequiresMockTestServer(suite_name):
+ """Returns True if the test suite requires mock test server."""
+ tests_require_net_test_server = ['unit_tests', 'net_unittests',
+ 'components_browsertests',
+ 'content_unittests',
+ 'content_browsertests']
+ return (suite_name in
+ tests_require_net_test_server)
+
+def _TestSuiteRequiresHighPerfMode(suite_name):
+ """Returns True if the test suite requires high performance mode."""
+ return 'perftests' in suite_name
+
+class TestRunner(base_test_runner.BaseTestRunner):
+ def __init__(self, test_options, device, test_package):
+ """Single test suite attached to a single device.
+
+ Args:
+ test_options: A GTestOptions object.
+ device: Device to run the tests.
+ test_package: An instance of TestPackage class.
+ """
+
+ super(TestRunner, self).__init__(device, test_options.tool)
+
+ self.test_package = test_package
+ self.test_package.tool = self.tool
+ self._test_arguments = test_options.test_arguments
+
+ timeout = test_options.timeout
+ if timeout == 0:
+ timeout = 60
+ # On a VM (e.g. chromium buildbots), this timeout is way too small.
+ if os.environ.get('BUILDBOT_SLAVENAME'):
+ timeout = timeout * 2
+
+ self._timeout = min(timeout * self.tool.GetTimeoutScale(),
+ _INFRA_STDOUT_TIMEOUT)
+ if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
+ self._perf_controller = perf_control.PerfControl(self.device)
+
+ if _TestSuiteRequiresMockTestServer(self.test_package.suite_name):
+ self._servers = [
+ local_test_server_spawner.LocalTestServerSpawner(
+ ports.AllocateTestServerPort(), self.device, self.tool)]
+ else:
+ self._servers = []
+
+ if test_options.app_data_files:
+ self._app_data_files = test_options.app_data_files
+ if test_options.app_data_file_dir:
+ self._app_data_file_dir = test_options.app_data_file_dir
+ else:
+ self._app_data_file_dir = tempfile.mkdtemp()
+ logging.critical('Saving app files to %s', self._app_data_file_dir)
+ else:
+ self._app_data_files = None
+ self._app_data_file_dir = None
+
+ #override
+ def InstallTestPackage(self):
+ self.test_package.Install(self.device)
+
+ def _ParseTestOutput(self, p):
+ """Process the test output.
+
+ Args:
+ p: An instance of pexpect spawn class.
+
+ Returns:
+ A TestRunResults object.
+ """
+ results = base_test_result.TestRunResults()
+
+ log = ''
+ try:
+ while True:
+ full_test_name = None
+
+ found = p.expect([RE_RUN, RE_PASSED, RE_RUNNER_FAIL],
+ timeout=self._timeout)
+ if found == 1: # RE_PASSED
+ break
+ elif found == 2: # RE_RUNNER_FAIL
+ break
+ else: # RE_RUN
+ full_test_name = p.match.group(1).replace('\r', '')
+ found = p.expect([RE_OK, RE_FAIL, RE_CRASH], timeout=self._timeout)
+ log = p.before.replace('\r', '')
+ if found == 0: # RE_OK
+ if full_test_name == p.match.group(1).replace('\r', ''):
+ duration_ms = int(p.match.group(3)) if p.match.group(3) else 0
+ results.AddResult(base_test_result.BaseTestResult(
+ full_test_name, base_test_result.ResultType.PASS,
+ duration=duration_ms, log=log))
+ elif found == 2: # RE_CRASH
+ results.AddResult(base_test_result.BaseTestResult(
+ full_test_name, base_test_result.ResultType.CRASH,
+ log=log))
+ break
+ else: # RE_FAIL
+ duration_ms = int(p.match.group(3)) if p.match.group(3) else 0
+ results.AddResult(base_test_result.BaseTestResult(
+ full_test_name, base_test_result.ResultType.FAIL,
+ duration=duration_ms, log=log))
+ except pexpect.EOF:
+ logging.error('Test terminated - EOF')
+ # We're here because either the device went offline, or the test harness
+ # crashed without outputting the CRASHED marker (crbug.com/175538).
+ if not self.device.IsOnline():
+ raise device_errors.DeviceUnreachableError(
+ 'Device %s went offline.' % str(self.device))
+ if full_test_name:
+ results.AddResult(base_test_result.BaseTestResult(
+ full_test_name, base_test_result.ResultType.CRASH,
+ log=p.before.replace('\r', '')))
+ except pexpect.TIMEOUT:
+ logging.error('Test terminated after %d second timeout.',
+ self._timeout)
+ if full_test_name:
+ results.AddResult(base_test_result.BaseTestResult(
+ full_test_name, base_test_result.ResultType.TIMEOUT,
+ log=p.before.replace('\r', '')))
+ finally:
+ p.close()
+
+ ret_code = self.test_package.GetGTestReturnCode(self.device)
+ if ret_code:
+ logging.critical(
+ 'gtest exit code: %d\npexpect.before: %s\npexpect.after: %s',
+ ret_code, p.before, p.after)
+
+ return results
+
+ #override
+ def RunTest(self, test):
+ test_results = base_test_result.TestRunResults()
+ if not test:
+ return test_results, None
+
+ try:
+ self.test_package.ClearApplicationState(self.device)
+ self.test_package.CreateCommandLineFileOnDevice(
+ self.device, test, self._test_arguments)
+ test_results = self._ParseTestOutput(
+ self.test_package.SpawnTestProcess(self.device))
+ if self._app_data_files:
+ self.test_package.PullAppFiles(self.device, self._app_data_files,
+ self._app_data_file_dir)
+ finally:
+ for s in self._servers:
+ s.Reset()
+ # Calculate unknown test results.
+ all_tests = set(test.split(':'))
+ all_tests_ran = set([t.GetName() for t in test_results.GetAll()])
+ unknown_tests = all_tests - all_tests_ran
+ test_results.AddResults(
+ [base_test_result.BaseTestResult(t, base_test_result.ResultType.UNKNOWN)
+ for t in unknown_tests])
+ retry = ':'.join([t.GetName() for t in test_results.GetNotPass()])
+ return test_results, retry
+
+ #override
+ def SetUp(self):
+ """Sets up necessary test enviroment for the test suite."""
+ super(TestRunner, self).SetUp()
+ for s in self._servers:
+ s.SetUp()
+ if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
+ self._perf_controller.SetHighPerfMode()
+ self.tool.SetupEnvironment()
+
+ #override
+ def TearDown(self):
+ """Cleans up the test enviroment for the test suite."""
+ for s in self._servers:
+ s.TearDown()
+ if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
+ self._perf_controller.SetDefaultPerfMode()
+ self.test_package.ClearApplicationState(self.device)
+ self.tool.CleanUpEnvironment()
+ super(TestRunner, self).TearDown()
diff --git a/build/android/pylib/host_driven/__init__.py b/build/android/pylib/host_driven/__init__.py
new file mode 100644
index 00000000000..727e987e6b6
--- /dev/null
+++ b/build/android/pylib/host_driven/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2012 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.
+
diff --git a/build/android/pylib/host_driven/setup.py b/build/android/pylib/host_driven/setup.py
new file mode 100644
index 00000000000..b2ed348aeb0
--- /dev/null
+++ b/build/android/pylib/host_driven/setup.py
@@ -0,0 +1,200 @@
+# Copyright 2013 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.
+
+"""Setup for instrumentation host-driven tests."""
+
+import logging
+import os
+import sys
+import types
+
+from pylib.host_driven import test_case
+from pylib.host_driven import test_info_collection
+from pylib.host_driven import test_runner
+
+
+def _GetPythonFiles(root, files):
+ """Returns all files from |files| that end in 'Test.py'.
+
+ Args:
+ root: A directory name with python files.
+ files: A list of file names.
+
+ Returns:
+ A list with all python files that match the testing naming scheme.
+ """
+ return [os.path.join(root, f) for f in files if f.endswith('Test.py')]
+
+
+def _InferImportNameFromFile(python_file):
+ """Given a file, infer the import name for that file.
+
+ Example: /usr/foo/bar/baz.py -> baz.
+
+ Args:
+ python_file: Path to the Python file, ostensibly to import later.
+
+ Returns:
+ The module name for the given file.
+ """
+ return os.path.splitext(os.path.basename(python_file))[0]
+
+
+def _GetTestModules(host_driven_test_root, is_official_build):
+ """Retrieve a list of python modules that match the testing naming scheme.
+
+ Walks the location of host-driven tests, imports them, and provides the list
+ of imported modules to the caller.
+
+ Args:
+ host_driven_test_root: The path to walk, looking for the
+ pythonDrivenTests or host_driven_tests directory
+ is_official_build: Whether to run only those tests marked 'official'
+
+ Returns:
+ A list of python modules under |host_driven_test_root| which match the
+ testing naming scheme. Each module should define one or more classes that
+ derive from HostDrivenTestCase.
+ """
+ # By default run all host-driven tests under pythonDrivenTests or
+ # host_driven_tests.
+ host_driven_test_file_list = []
+ for root, _, files in os.walk(host_driven_test_root):
+ if (root.endswith('host_driven_tests') or
+ root.endswith('pythonDrivenTests') or
+ (is_official_build and (root.endswith('pythonDrivenTests/official') or
+ root.endswith('host_driven_tests/official')))):
+ host_driven_test_file_list += _GetPythonFiles(root, files)
+ host_driven_test_file_list.sort()
+
+ test_module_list = [_GetModuleFromFile(test_file)
+ for test_file in host_driven_test_file_list]
+ return test_module_list
+
+
+def _GetModuleFromFile(python_file):
+ """Gets the python module associated with a file by importing it.
+
+ Args:
+ python_file: File to import.
+
+ Returns:
+ The module object.
+ """
+ sys.path.append(os.path.dirname(python_file))
+ import_name = _InferImportNameFromFile(python_file)
+ return __import__(import_name)
+
+
+def _GetTestsFromClass(test_case_class, **kwargs):
+ """Returns one test object for each test method in |test_case_class|.
+
+ Test methods are methods on the class which begin with 'test'.
+
+ Args:
+ test_case_class: Class derived from HostDrivenTestCase which contains zero
+ or more test methods.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ A list of test case objects, each initialized for a particular test method.
+ """
+ test_names = [m for m in dir(test_case_class)
+ if _IsTestMethod(m, test_case_class)]
+ return [test_case_class(name, **kwargs) for name in test_names]
+
+
+def _GetTestsFromModule(test_module, **kwargs):
+ """Gets a list of test objects from |test_module|.
+
+ Args:
+ test_module: Module from which to get the set of test methods.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ A list of test case objects each initialized for a particular test method
+ defined in |test_module|.
+ """
+
+ tests = []
+ for name in dir(test_module):
+ attr = getattr(test_module, name)
+ if _IsTestCaseClass(attr):
+ tests.extend(_GetTestsFromClass(attr, **kwargs))
+ return tests
+
+
+def _IsTestCaseClass(test_class):
+ return (type(test_class) is types.TypeType and
+ issubclass(test_class, test_case.HostDrivenTestCase) and
+ test_class is not test_case.HostDrivenTestCase)
+
+
+def _IsTestMethod(attrname, test_case_class):
+ """Checks whether this is a valid test method.
+
+ Args:
+ attrname: The method name.
+ test_case_class: The test case class.
+
+ Returns:
+ True if test_case_class.'attrname' is callable and it starts with 'test';
+ False otherwise.
+ """
+ attr = getattr(test_case_class, attrname)
+ return callable(attr) and attrname.startswith('test')
+
+
+def _GetAllTests(test_root, is_official_build, **kwargs):
+ """Retrieve a list of host-driven tests defined under |test_root|.
+
+ Args:
+ test_root: Path which contains host-driven test files.
+ is_official_build: Whether this is an official build.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ List of test case objects, one for each available test method.
+ """
+ if not test_root:
+ return []
+ all_tests = []
+ test_module_list = _GetTestModules(test_root, is_official_build)
+ for module in test_module_list:
+ all_tests.extend(_GetTestsFromModule(module, **kwargs))
+ return all_tests
+
+
+def InstrumentationSetup(host_driven_test_root, official_build,
+ instrumentation_options):
+ """Creates a list of host-driven instrumentation tests and a runner factory.
+
+ Args:
+ host_driven_test_root: Directory where the host-driven tests are.
+ official_build: True if this is an official build.
+ instrumentation_options: An InstrumentationOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+
+ test_collection = test_info_collection.TestInfoCollection()
+ all_tests = _GetAllTests(
+ host_driven_test_root, official_build,
+ instrumentation_options=instrumentation_options)
+ test_collection.AddTests(all_tests)
+
+ available_tests = test_collection.GetAvailableTests(
+ instrumentation_options.annotations,
+ instrumentation_options.exclude_annotations,
+ instrumentation_options.test_filter)
+ logging.debug('All available tests: ' + str(
+ [t.tagged_name for t in available_tests]))
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.HostDrivenTestRunner(
+ device, shard_index,
+ instrumentation_options.tool)
+
+ return (TestRunnerFactory, available_tests)
diff --git a/build/android/pylib/host_driven/test_case.py b/build/android/pylib/host_driven/test_case.py
new file mode 100644
index 00000000000..6ff4c5fec53
--- /dev/null
+++ b/build/android/pylib/host_driven/test_case.py
@@ -0,0 +1,189 @@
+# Copyright 2013 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.
+
+"""Base class for host-driven test cases.
+
+This test case is intended to serve as the base class for any host-driven
+test cases. It is similar to the Python unitttest module in that test cases
+inherit from this class and add methods which will be run as tests.
+
+When a HostDrivenTestCase object is instantiated, its purpose is to run only one
+test method in the derived class. The test runner gives it the name of the test
+method the instance will run. The test runner calls SetUp with the device ID
+which the test method will run against. The test runner runs the test method
+itself, collecting the result, and calls TearDown.
+
+Tests can perform arbitrary Python commands and asserts in test methods. Tests
+that run instrumentation tests can make use of the _RunJavaTestFilters helper
+function to trigger Java tests and convert results into a single host-driven
+test result.
+"""
+
+import logging
+import os
+import time
+
+from pylib import constants
+from pylib import forwarder
+from pylib import valgrind_tools
+from pylib.base import base_test_result
+from pylib.device import device_utils
+from pylib.instrumentation import test_package
+from pylib.instrumentation import test_result
+from pylib.instrumentation import test_runner
+
+# aka the parent of com.google.android
+BASE_ROOT = 'src' + os.sep
+
+
+class HostDrivenTestCase(object):
+ """Base class for host-driven test cases."""
+
+ _HOST_DRIVEN_TAG = 'HostDriven'
+
+ def __init__(self, test_name, instrumentation_options=None):
+ """Create a test case initialized to run |test_name|.
+
+ Args:
+ test_name: The name of the method to run as the test.
+ instrumentation_options: An InstrumentationOptions object.
+ """
+ class_name = self.__class__.__name__
+ self.device = None
+ self.device_id = ''
+ self.has_forwarded_ports = False
+ self.instrumentation_options = instrumentation_options
+ self.ports_to_forward = []
+ self.shard_index = 0
+
+ # Use tagged_name when creating results, so that we can identify host-driven
+ # tests in the overall results.
+ self.test_name = test_name
+ self.qualified_name = '%s.%s' % (class_name, self.test_name)
+ self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name)
+
+ # TODO(bulach): make ports_to_forward not optional and move the Forwarder
+ # mapping here.
+ def SetUp(self, device, shard_index, ports_to_forward=None):
+ if not ports_to_forward:
+ ports_to_forward = []
+ self.device = device
+ self.shard_index = shard_index
+ self.device_id = str(self.device)
+ if ports_to_forward:
+ self.ports_to_forward = ports_to_forward
+
+ def TearDown(self):
+ pass
+
+ # TODO(craigdh): Remove GetOutDir once references have been removed
+ # downstream.
+ @staticmethod
+ def GetOutDir():
+ return constants.GetOutDirectory()
+
+ def Run(self):
+ logging.info('Running host-driven test: %s', self.tagged_name)
+ # Get the test method on the derived class and execute it
+ return getattr(self, self.test_name)()
+
+ @staticmethod
+ def __GetHostForwarderLog():
+ return ('-- Begin Full HostForwarder log\n'
+ '%s\n'
+ '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog())
+
+ def __StartForwarder(self):
+ logging.warning('Forwarding %s %s', self.ports_to_forward,
+ self.has_forwarded_ports)
+ if self.ports_to_forward and not self.has_forwarded_ports:
+ self.has_forwarded_ports = True
+ tool = valgrind_tools.CreateTool(None, self.device)
+ forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward],
+ self.device, tool)
+
+ def __RunJavaTest(self, test, test_pkg, additional_flags=None):
+ """Runs a single Java test in a Java TestRunner.
+
+ Args:
+ test: Fully qualified test name (ex. foo.bar.TestClass#testMethod)
+ test_pkg: TestPackage object.
+ additional_flags: A list of additional flags to add to the command line.
+
+ Returns:
+ TestRunResults object with a single test result.
+ """
+ # TODO(bulach): move this to SetUp() stage.
+ self.__StartForwarder()
+
+ java_test_runner = test_runner.TestRunner(
+ self.instrumentation_options, self.device, self.shard_index,
+ test_pkg, additional_flags=additional_flags)
+ try:
+ java_test_runner.SetUp()
+ return java_test_runner.RunTest(test)[0]
+ finally:
+ java_test_runner.TearDown()
+
+ def _RunJavaTestFilters(self, test_filters, additional_flags=None):
+ """Calls a list of tests and stops at the first test failure.
+
+ This method iterates until either it encounters a non-passing test or it
+ exhausts the list of tests. Then it returns the appropriate overall result.
+
+ Test cases may make use of this method internally to assist in running
+ instrumentation tests. This function relies on instrumentation_options
+ being defined.
+
+ Args:
+ test_filters: A list of Java test filters.
+ additional_flags: A list of addition flags to add to the command line.
+
+ Returns:
+ A TestRunResults object containing an overall result for this set of Java
+ tests. If any Java tests do not pass, this is a fail overall.
+ """
+ test_type = base_test_result.ResultType.PASS
+ log = ''
+
+ test_pkg = test_package.TestPackage(
+ self.instrumentation_options.test_apk_path,
+ self.instrumentation_options.test_apk_jar_path,
+ self.instrumentation_options.test_support_apk_path)
+
+ start_ms = int(time.time()) * 1000
+ done = False
+ for test_filter in test_filters:
+ tests = test_pkg.GetAllMatchingTests(None, None, test_filter)
+ # Filters should always result in >= 1 test.
+ if len(tests) == 0:
+ raise Exception('Java test filter "%s" returned no tests.'
+ % test_filter)
+ for test in tests:
+ # We're only running one test at a time, so this TestRunResults object
+ # will hold only one result.
+ java_result = self.__RunJavaTest(test, test_pkg, additional_flags)
+ assert len(java_result.GetAll()) == 1
+ if not java_result.DidRunPass():
+ result = java_result.GetNotPass().pop()
+ log = result.GetLog()
+ log += self.__GetHostForwarderLog()
+ test_type = result.GetType()
+ done = True
+ break
+ if done:
+ break
+ duration_ms = int(time.time()) * 1000 - start_ms
+
+ overall_result = base_test_result.TestRunResults()
+ overall_result.AddResult(
+ test_result.InstrumentationTestResult(
+ self.tagged_name, test_type, start_ms, duration_ms, log=log))
+ return overall_result
+
+ def __str__(self):
+ return self.tagged_name
+
+ def __repr__(self):
+ return self.tagged_name
diff --git a/build/android/pylib/host_driven/test_info_collection.py b/build/android/pylib/host_driven/test_info_collection.py
new file mode 100644
index 00000000000..c65d417df72
--- /dev/null
+++ b/build/android/pylib/host_driven/test_info_collection.py
@@ -0,0 +1,144 @@
+# Copyright (c) 2012 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.
+
+"""Module containing information about the host-driven tests."""
+
+import logging
+import os
+import sys
+
+from pylib.host_driven import tests_annotations
+
+from pylib import constants
+
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT,
+ 'build', 'util', 'lib', 'common'))
+
+import unittest_util # pylint: disable=F0401
+
+class TestInfo(object):
+ """An object containing and representing a test function, plus metadata."""
+
+ def __init__(self, runnable, set_up=None, tear_down=None):
+ # The actual test function/method.
+ self.runnable = runnable
+ # Qualified name of test function/method (e.g. FooModule.testBar).
+ self.qualified_name = self._GetQualifiedName(runnable)
+ # setUp and teardown functions, if any.
+ self.set_up = set_up
+ self.tear_down = tear_down
+
+ @staticmethod
+ def _GetQualifiedName(runnable):
+ """Helper method to infer a runnable's name and module name.
+
+ Many filters and lists presuppose a format of module_name.testMethodName.
+ To make this easy on everyone, we use some reflection magic to infer this
+ name automatically.
+
+ Args:
+ runnable: the test method to get the qualified name for
+
+ Returns:
+ qualified name for this runnable, incl. module name and method name.
+ """
+ runnable_name = runnable.__name__
+ # See also tests_annotations.
+ module_name = os.path.splitext(
+ os.path.basename(runnable.__globals__['__file__']))[0]
+ return '.'.join([module_name, runnable_name])
+
+ def __str__(self):
+ return self.qualified_name
+
+
+class TestInfoCollection(object):
+ """A collection of TestInfo objects which facilitates filtering."""
+
+ def __init__(self):
+ """Initialize a new TestInfoCollection."""
+ # Master list of all valid tests.
+ self.all_tests = []
+
+ def AddTests(self, test_infos):
+ """Adds a set of tests to this collection.
+
+ The user may then retrieve them, optionally according to criteria, via
+ GetAvailableTests().
+
+ Args:
+ test_infos: a list of TestInfos representing test functions/methods.
+ """
+ self.all_tests = test_infos
+
+ def GetAvailableTests(self, annotations, exclude_annotations, name_filter):
+ """Get a collection of TestInfos which match the supplied criteria.
+
+ Args:
+ annotations: List of annotations. Each test in the returned list is
+ annotated with atleast one of these annotations.
+ exclude_annotations: List of annotations. The tests in the returned
+ list are not annotated with any of these annotations.
+ name_filter: name filter which tests must match, if any
+
+ Returns:
+ List of available tests.
+ """
+ available_tests = self.all_tests
+
+ # Filter out tests which match neither the requested annotation, nor the
+ # requested name filter, if any.
+ available_tests = [t for t in available_tests if
+ self._AnnotationIncludesTest(t, annotations)]
+ if annotations and len(annotations) == 1 and annotations[0] == 'SmallTest':
+ tests_without_annotation = [
+ t for t in self.all_tests if
+ not tests_annotations.AnnotatedFunctions.GetTestAnnotations(
+ t.qualified_name)]
+ test_names = [t.qualified_name for t in tests_without_annotation]
+ logging.warning('The following tests do not contain any annotation. '
+ 'Assuming "SmallTest":\n%s',
+ '\n'.join(test_names))
+ available_tests += tests_without_annotation
+ if exclude_annotations:
+ excluded_tests = [t for t in available_tests if
+ self._AnnotationIncludesTest(t, exclude_annotations)]
+ available_tests = list(set(available_tests) - set(excluded_tests))
+
+ if name_filter:
+ available_test_names = unittest_util.FilterTestNames(
+ [t.qualified_name for t in available_tests], name_filter)
+ available_tests = [
+ t for t in available_tests if
+ t.qualified_name in available_test_names]
+ return available_tests
+
+ @staticmethod
+ def _AnnotationIncludesTest(test_info, annotation_filter_list):
+ """Checks whether a given test represented by test_info matches annotation.
+
+ Args:
+ test_info: TestInfo object representing the test
+ annotation_filter_list: list of annotation filters to match (e.g. Smoke)
+
+ Returns:
+ True if no annotation was supplied or the test matches; false otherwise.
+ """
+ if not annotation_filter_list:
+ return True
+ for annotation_filter in annotation_filter_list:
+ filters = annotation_filter.split('=')
+ if len(filters) == 2:
+ key = filters[0]
+ value_list = filters[1].split(',')
+ for value in value_list:
+ if tests_annotations.AnnotatedFunctions.IsAnnotated(
+ key + ':' + value, test_info.qualified_name):
+ return True
+ elif tests_annotations.AnnotatedFunctions.IsAnnotated(
+ annotation_filter, test_info.qualified_name):
+ return True
+ return False
+
diff --git a/build/android/pylib/host_driven/test_runner.py b/build/android/pylib/host_driven/test_runner.py
new file mode 100644
index 00000000000..8620aa15209
--- /dev/null
+++ b/build/android/pylib/host_driven/test_runner.py
@@ -0,0 +1,133 @@
+# Copyright 2013 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.
+
+"""Runs host-driven tests on a particular device."""
+
+import logging
+import sys
+import time
+import traceback
+
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.host_driven import test_case
+from pylib.instrumentation import test_result
+
+
+class HostDrivenExceptionTestResult(test_result.InstrumentationTestResult):
+ """Test result corresponding to a python exception in a host-driven test."""
+
+ def __init__(self, test_name, start_date_ms, exc_info):
+ """Constructs a HostDrivenExceptionTestResult object.
+
+ Args:
+ test_name: name of the test which raised an exception.
+ start_date_ms: the starting time for the test.
+ exc_info: exception info, ostensibly from sys.exc_info().
+ """
+ exc_type, exc_value, exc_traceback = exc_info
+ trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
+ exc_traceback))
+ log_msg = 'Exception:\n' + trace_info
+ duration_ms = (int(time.time()) * 1000) - start_date_ms
+
+ super(HostDrivenExceptionTestResult, self).__init__(
+ test_name,
+ base_test_result.ResultType.FAIL,
+ start_date_ms,
+ duration_ms,
+ log=str(exc_type) + ' ' + log_msg)
+
+
+class HostDrivenTestRunner(base_test_runner.BaseTestRunner):
+ """Orchestrates running a set of host-driven tests.
+
+ Any Python exceptions in the tests are caught and translated into a failed
+ result, rather than being re-raised on the main thread.
+ """
+
+ # TODO(jbudorick): Remove cleanup_test_files once it's no longer used.
+ # pylint: disable=unused-argument
+ #override
+ def __init__(self, device, shard_index, tool, cleanup_test_files=None):
+ """Creates a new HostDrivenTestRunner.
+
+ Args:
+ device: Attached android device.
+ shard_index: Shard index.
+ tool: Name of the Valgrind tool.
+ cleanup_test_files: Deprecated.
+ """
+
+ super(HostDrivenTestRunner, self).__init__(device, tool)
+
+ # The shard index affords the ability to create unique port numbers (e.g.
+ # DEFAULT_PORT + shard_index) if the test so wishes.
+ self.shard_index = shard_index
+
+ # pylint: enable=unused-argument
+
+ #override
+ def RunTest(self, test):
+ """Sets up and runs a test case.
+
+ Args:
+ test: An object which is ostensibly a subclass of HostDrivenTestCase.
+
+ Returns:
+ A TestRunResults object which contains the result produced by the test
+ and, in the case of a failure, the test that should be retried.
+ """
+
+ assert isinstance(test, test_case.HostDrivenTestCase)
+
+ start_date_ms = int(time.time()) * 1000
+ exception_raised = False
+
+ try:
+ test.SetUp(self.device, self.shard_index)
+ except Exception:
+ logging.exception(
+ 'Caught exception while trying to run SetUp() for test: ' +
+ test.tagged_name)
+ # Tests whose SetUp() method has failed are likely to fail, or at least
+ # yield invalid results.
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+ return results, test
+
+ try:
+ results = test.Run()
+ except Exception:
+ # Setting this lets TearDown() avoid stomping on our stack trace from
+ # Run() should TearDown() also raise an exception.
+ exception_raised = True
+ logging.exception('Caught exception while trying to run test: ' +
+ test.tagged_name)
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+
+ try:
+ test.TearDown()
+ except Exception:
+ logging.exception(
+ 'Caught exception while trying run TearDown() for test: ' +
+ test.tagged_name)
+ if not exception_raised:
+ # Don't stomp the error during the test if TearDown blows up. This is a
+ # trade-off: if the test fails, this will mask any problem with TearDown
+ # until the test is fixed.
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+
+ if not results.DidRunPass():
+ return results, test
+ else:
+ return results, None
diff --git a/build/android/pylib/host_driven/test_server.py b/build/android/pylib/host_driven/test_server.py
new file mode 100644
index 00000000000..07835000fd9
--- /dev/null
+++ b/build/android/pylib/host_driven/test_server.py
@@ -0,0 +1,130 @@
+# Copyright 2013 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.
+
+"""Host driven test server controller.
+
+This class controls the startup and shutdown of a python driven test server that
+runs in a separate process.
+
+The server starts up automatically when the object is created.
+
+After it starts up, it is possible to retreive the hostname it started on
+through accessing the member field |host| and the port name through |port|.
+
+For shutting down the server, call TearDown().
+"""
+
+import logging
+import subprocess
+import os
+import os.path
+import time
+import urllib2
+
+from pylib import constants
+
+# NOTE: when adding or modifying these lines, omit any leading slashes!
+# Otherwise os.path.join() will (correctly) treat them as absolute paths
+# instead of relative paths, and will do nothing.
+_PYTHONPATH_DIRS = [
+ 'net/tools/testserver/',
+ 'third_party/',
+ 'third_party/pyftpdlib/src/',
+ 'third_party/pywebsocket/src',
+ 'third_party/tlslite/',
+]
+
+# Python files in these directories are generated as part of the build.
+# These dirs are located in out/(Debug|Release) directory.
+# The correct path is determined based on the build type. E.g. out/Debug for
+# debug builds and out/Release for release builds.
+_GENERATED_PYTHONPATH_DIRS = [
+ 'pyproto/policy/proto/',
+ 'pyproto/sync/protocol/',
+ 'pyproto/'
+]
+
+_TEST_SERVER_HOST = '127.0.0.1'
+# Paths for supported test server executables.
+TEST_NET_SERVER_PATH = 'net/tools/testserver/testserver.py'
+TEST_SYNC_SERVER_PATH = 'sync/tools/testserver/sync_testserver.py'
+TEST_POLICY_SERVER_PATH = 'chrome/browser/policy/test/policy_testserver.py'
+# Parameters to check that the server is up and running.
+TEST_SERVER_CHECK_PARAMS = {
+ TEST_NET_SERVER_PATH: {
+ 'url_path': '/',
+ 'response': 'Default response given for path'
+ },
+ TEST_SYNC_SERVER_PATH: {
+ 'url_path': 'chromiumsync/time',
+ 'response': '0123456789'
+ },
+ TEST_POLICY_SERVER_PATH: {
+ 'url_path': 'test/ping',
+ 'response': 'Policy server is up.'
+ },
+}
+
+class TestServer(object):
+ """Sets up a host driven test server on the host machine.
+
+ For shutting down the server, call TearDown().
+ """
+
+ def __init__(self, shard_index, test_server_port, test_server_path,
+ test_server_flags=None):
+ """Sets up a Python driven test server on the host machine.
+
+ Args:
+ shard_index: Index of the current shard.
+ test_server_port: Port to run the test server on. This is multiplexed with
+ the shard index. To retrieve the real port access the
+ member variable |port|.
+ test_server_path: The path (relative to the root src dir) of the server
+ test_server_flags: Optional list of additional flags to the test server
+ """
+ self.host = _TEST_SERVER_HOST
+ self.port = test_server_port + shard_index
+
+ src_dir = constants.DIR_SOURCE_ROOT
+ # Make dirs into a list of absolute paths.
+ abs_dirs = [os.path.join(src_dir, d) for d in _PYTHONPATH_DIRS]
+ # Add the generated python files to the path
+ abs_dirs.extend([os.path.join(src_dir, constants.GetOutDirectory(), d)
+ for d in _GENERATED_PYTHONPATH_DIRS])
+ current_python_path = os.environ.get('PYTHONPATH')
+ extra_python_path = ':'.join(abs_dirs)
+ if current_python_path:
+ python_path = current_python_path + ':' + extra_python_path
+ else:
+ python_path = extra_python_path
+
+ # NOTE: A separate python process is used to simplify getting the right
+ # system path for finding includes.
+ test_server_flags = test_server_flags or []
+ cmd = ['python', os.path.join(src_dir, test_server_path),
+ '--log-to-console',
+ ('--host=%s' % self.host),
+ ('--port=%d' % self.port),
+ '--on-remote-server'] + test_server_flags
+ self._test_server_process = subprocess.Popen(
+ cmd, env={'PYTHONPATH': python_path})
+ test_url = 'http://%s:%d/%s' % (self.host, self.port,
+ TEST_SERVER_CHECK_PARAMS[test_server_path]['url_path'])
+ expected_response = TEST_SERVER_CHECK_PARAMS[test_server_path]['response']
+ retries = 0
+ while retries < 5:
+ try:
+ d = urllib2.urlopen(test_url).read()
+ logging.info('URL %s GOT: %s' % (test_url, d))
+ if d.startswith(expected_response):
+ break
+ except Exception as e:
+ logging.info('URL %s GOT: %s' % (test_url, e))
+ time.sleep(retries * 0.1)
+ retries += 1
+
+ def TearDown(self):
+ self._test_server_process.kill()
+ self._test_server_process.wait()
diff --git a/build/android/pylib/host_driven/tests_annotations.py b/build/android/pylib/host_driven/tests_annotations.py
new file mode 100644
index 00000000000..533114065e5
--- /dev/null
+++ b/build/android/pylib/host_driven/tests_annotations.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2012 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.
+
+"""Annotations for host-driven tests."""
+# pylint: disable=W0212
+
+import os
+
+
+class AnnotatedFunctions(object):
+ """A container for annotated methods."""
+ _ANNOTATED = {}
+
+ @staticmethod
+ def _AddFunction(annotation, function):
+ """Adds an annotated function to our container.
+
+ Args:
+ annotation: the annotation string.
+ function: the function.
+ Returns:
+ The function passed in.
+ """
+ module_name = os.path.splitext(os.path.basename(
+ function.__globals__['__file__']))[0]
+ qualified_function_name = '.'.join([module_name, function.func_name])
+ function_list = AnnotatedFunctions._ANNOTATED.get(annotation, [])
+ function_list.append(qualified_function_name)
+ AnnotatedFunctions._ANNOTATED[annotation] = function_list
+ return function
+
+ @staticmethod
+ def IsAnnotated(annotation, qualified_function_name):
+ """True if function name (module.function) contains the annotation.
+
+ Args:
+ annotation: the annotation string.
+ qualified_function_name: the qualified function name.
+ Returns:
+ True if module.function contains the annotation.
+ """
+ return qualified_function_name in AnnotatedFunctions._ANNOTATED.get(
+ annotation, [])
+
+ @staticmethod
+ def GetTestAnnotations(qualified_function_name):
+ """Returns a list containing all annotations for the given function.
+
+ Args:
+ qualified_function_name: the qualified function name.
+ Returns:
+ List of all annotations for this function.
+ """
+ return [annotation
+ for annotation, tests in AnnotatedFunctions._ANNOTATED.iteritems()
+ if qualified_function_name in tests]
+
+
+# The following functions are annotations used for the host-driven tests.
+def Smoke(function):
+ return AnnotatedFunctions._AddFunction('Smoke', function)
+
+
+def SmallTest(function):
+ return AnnotatedFunctions._AddFunction('SmallTest', function)
+
+
+def MediumTest(function):
+ return AnnotatedFunctions._AddFunction('MediumTest', function)
+
+
+def LargeTest(function):
+ return AnnotatedFunctions._AddFunction('LargeTest', function)
+
+
+def EnormousTest(function):
+ return AnnotatedFunctions._AddFunction('EnormousTest', function)
+
+
+def FlakyTest(function):
+ return AnnotatedFunctions._AddFunction('FlakyTest', function)
+
+
+def DisabledTest(function):
+ return AnnotatedFunctions._AddFunction('DisabledTest', function)
+
+
+def Feature(feature_list):
+ def _AddFeatures(function):
+ for feature in feature_list:
+ AnnotatedFunctions._AddFunction('Feature:%s' % feature, function)
+ return AnnotatedFunctions._AddFunction('Feature', function)
+ return _AddFeatures
diff --git a/build/android/pylib/instrumentation/__init__.py b/build/android/pylib/instrumentation/__init__.py
new file mode 100644
index 00000000000..727e987e6b6
--- /dev/null
+++ b/build/android/pylib/instrumentation/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2012 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.
+
diff --git a/build/android/pylib/instrumentation/instrumentation_parser.py b/build/android/pylib/instrumentation/instrumentation_parser.py
new file mode 100644
index 00000000000..1859f1423ef
--- /dev/null
+++ b/build/android/pylib/instrumentation/instrumentation_parser.py
@@ -0,0 +1,96 @@
+# Copyright 2015 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.
+
+import logging
+import re
+
+# http://developer.android.com/reference/android/test/InstrumentationTestRunner.html
+STATUS_CODE_START = 1
+STATUS_CODE_OK = 0
+STATUS_CODE_ERROR = -1
+STATUS_CODE_FAILURE = -2
+
+# http://developer.android.com/reference/android/app/Activity.html
+RESULT_CODE_OK = -1
+RESULT_CODE_CANCELED = 0
+
+_INSTR_LINE_RE = re.compile('^\s*INSTRUMENTATION_([A-Z_]+): (.*)$')
+
+
+class InstrumentationParser(object):
+
+ def __init__(self, stream):
+ """An incremental parser for the output of Android instrumentation tests.
+
+ Example:
+
+ stream = adb.IterShell('am instrument -r ...')
+ parser = InstrumentationParser(stream)
+
+ for code, bundle in parser.IterStatus():
+ # do something with each instrumentation status
+ print 'status:', code, bundle
+
+ # do something with the final instrumentation result
+ code, bundle = parser.GetResult()
+ print 'result:', code, bundle
+
+ Args:
+ stream: a sequence of lines as produced by the raw output of an
+ instrumentation test (e.g. by |am instrument -r| or |uiautomator|).
+ """
+ self._stream = stream
+ self._code = None
+ self._bundle = None
+
+ def IterStatus(self):
+ """Iterate over statuses as they are produced by the instrumentation test.
+
+ Yields:
+ A tuple (code, bundle) for each instrumentation status found in the
+ output.
+ """
+ def join_bundle_values(bundle):
+ for key in bundle:
+ bundle[key] = '\n'.join(bundle[key])
+ return bundle
+
+ bundle = {'STATUS': {}, 'RESULT': {}}
+ header = None
+ key = None
+ for line in self._stream:
+ m = _INSTR_LINE_RE.match(line)
+ if m:
+ header, value = m.groups()
+ key = None
+ if header in ['STATUS', 'RESULT'] and '=' in value:
+ key, value = value.split('=', 1)
+ bundle[header][key] = [value]
+ elif header == 'STATUS_CODE':
+ yield int(value), join_bundle_values(bundle['STATUS'])
+ bundle['STATUS'] = {}
+ elif header == 'CODE':
+ self._code = int(value)
+ else:
+ logging.warning('Unknown INSTRUMENTATION_%s line: %s', header, value)
+ elif key is not None:
+ bundle[header][key].append(line)
+
+ self._bundle = join_bundle_values(bundle['RESULT'])
+
+ def GetResult(self):
+ """Return the final instrumentation result.
+
+ Returns:
+ A pair (code, bundle) with the final instrumentation result. The |code|
+ may be None if no instrumentation result was found in the output.
+
+ Raises:
+ AssertionError if attempting to get the instrumentation result before
+ exhausting |IterStatus| first.
+ """
+ assert self._bundle is not None, (
+ 'The IterStatus generator must be exhausted before reading the final'
+ ' instrumentation result.')
+ return self._code, self._bundle
diff --git a/build/android/pylib/instrumentation/instrumentation_parser_test.py b/build/android/pylib/instrumentation/instrumentation_parser_test.py
new file mode 100755
index 00000000000..092d10fc938
--- /dev/null
+++ b/build/android/pylib/instrumentation/instrumentation_parser_test.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+
+"""Unit tests for instrumentation.InstrumentationParser."""
+
+import unittest
+
+from pylib.instrumentation import instrumentation_parser
+
+
+class InstrumentationParserTest(unittest.TestCase):
+
+ def testInstrumentationParser_nothing(self):
+ parser = instrumentation_parser.InstrumentationParser([''])
+ statuses = list(parser.IterStatus())
+ code, bundle = parser.GetResult()
+ self.assertEqual(None, code)
+ self.assertEqual({}, bundle)
+ self.assertEqual([], statuses)
+
+ def testInstrumentationParser_noMatchingStarts(self):
+ raw_output = [
+ '',
+ 'this.is.a.test.package.TestClass:.',
+ 'Test result for =.',
+ 'Time: 1.234',
+ '',
+ 'OK (1 test)',
+ ]
+
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+ code, bundle = parser.GetResult()
+ self.assertEqual(None, code)
+ self.assertEqual({}, bundle)
+ self.assertEqual([], statuses)
+
+ def testInstrumentationParser_resultAndCode(self):
+ raw_output = [
+ 'INSTRUMENTATION_RESULT: shortMsg=foo bar',
+ 'INSTRUMENTATION_RESULT: longMsg=a foo',
+ 'walked into',
+ 'a bar',
+ 'INSTRUMENTATION_CODE: -1',
+ ]
+
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+ code, bundle = parser.GetResult()
+ self.assertEqual(-1, code)
+ self.assertEqual(
+ {'shortMsg': 'foo bar', 'longMsg': 'a foo\nwalked into\na bar'}, bundle)
+ self.assertEqual([], statuses)
+
+ def testInstrumentationParser_oneStatus(self):
+ raw_output = [
+ 'INSTRUMENTATION_STATUS: foo=1',
+ 'INSTRUMENTATION_STATUS: bar=hello',
+ 'INSTRUMENTATION_STATUS: world=false',
+ 'INSTRUMENTATION_STATUS: class=this.is.a.test.package.TestClass',
+ 'INSTRUMENTATION_STATUS: test=testMethod',
+ 'INSTRUMENTATION_STATUS_CODE: 0',
+ ]
+
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+
+ expected = [
+ (0, {
+ 'foo': '1',
+ 'bar': 'hello',
+ 'world': 'false',
+ 'class': 'this.is.a.test.package.TestClass',
+ 'test': 'testMethod',
+ })
+ ]
+ self.assertEqual(expected, statuses)
+
+ def testInstrumentationParser_multiStatus(self):
+ raw_output = [
+ 'INSTRUMENTATION_STATUS: class=foo',
+ 'INSTRUMENTATION_STATUS: test=bar',
+ 'INSTRUMENTATION_STATUS_CODE: 1',
+ 'INSTRUMENTATION_STATUS: test_skipped=true',
+ 'INSTRUMENTATION_STATUS_CODE: 0',
+ 'INSTRUMENTATION_STATUS: class=hello',
+ 'INSTRUMENTATION_STATUS: test=world',
+ 'INSTRUMENTATION_STATUS: stack=',
+ 'foo/bar.py (27)',
+ 'hello/world.py (42)',
+ 'test/file.py (1)',
+ 'INSTRUMENTATION_STATUS_CODE: -1',
+ ]
+
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+
+ expected = [
+ (1, {'class': 'foo', 'test': 'bar',}),
+ (0, {'test_skipped': 'true'}),
+ (-1, {
+ 'class': 'hello',
+ 'test': 'world',
+ 'stack': '\nfoo/bar.py (27)\nhello/world.py (42)\ntest/file.py (1)',
+ }),
+ ]
+ self.assertEqual(expected, statuses)
+
+ def testInstrumentationParser_statusResultAndCode(self):
+ raw_output = [
+ 'INSTRUMENTATION_STATUS: class=foo',
+ 'INSTRUMENTATION_STATUS: test=bar',
+ 'INSTRUMENTATION_STATUS_CODE: 1',
+ 'INSTRUMENTATION_RESULT: result=hello',
+ 'world',
+ '',
+ '',
+ 'INSTRUMENTATION_CODE: 0',
+ ]
+
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+ code, bundle = parser.GetResult()
+
+ self.assertEqual(0, code)
+ self.assertEqual({'result': 'hello\nworld\n\n'}, bundle)
+ self.assertEqual([(1, {'class': 'foo', 'test': 'bar'})], statuses)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
new file mode 100644
index 00000000000..f9957b089cb
--- /dev/null
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -0,0 +1,525 @@
+# Copyright 2015 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.
+
+import logging
+import os
+import pickle
+import re
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib import flag_changer
+from pylib.base import base_test_result
+from pylib.base import test_instance
+from pylib.instrumentation import test_result
+from pylib.instrumentation import instrumentation_parser
+from pylib.utils import apk_helper
+from pylib.utils import md5sum
+from pylib.utils import proguard
+
+sys.path.append(
+ os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common'))
+import unittest_util
+
+# Ref: http://developer.android.com/reference/android/app/Activity.html
+_ACTIVITY_RESULT_CANCELED = 0
+_ACTIVITY_RESULT_OK = -1
+
+_DEFAULT_ANNOTATIONS = [
+ 'Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
+ 'EnormousTest', 'IntegrationTest']
+_EXTRA_ENABLE_HTTP_SERVER = (
+ 'org.chromium.chrome.test.ChromeInstrumentationTestRunner.'
+ + 'EnableTestHttpServer')
+_EXTRA_DRIVER_TEST_LIST = (
+ 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList')
+_EXTRA_DRIVER_TEST_LIST_FILE = (
+ 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile')
+_EXTRA_DRIVER_TARGET_PACKAGE = (
+ 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage')
+_EXTRA_DRIVER_TARGET_CLASS = (
+ 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass')
+_NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE)
+_PICKLE_FORMAT_VERSION = 10
+
+
+# TODO(jbudorick): Make these private class methods of
+# InstrumentationTestInstance once the instrumentation test_runner is
+# deprecated.
+def ParseAmInstrumentRawOutput(raw_output):
+ """Parses the output of an |am instrument -r| call.
+
+ Args:
+ raw_output: the output of an |am instrument -r| call as a list of lines
+ Returns:
+ A 3-tuple containing:
+ - the instrumentation code as an integer
+ - the instrumentation result as a list of lines
+ - the instrumentation statuses received as a list of 2-tuples
+ containing:
+ - the status code as an integer
+ - the bundle dump as a dict mapping string keys to a list of
+ strings, one for each line.
+ """
+ parser = instrumentation_parser.InstrumentationParser(raw_output)
+ statuses = list(parser.IterStatus())
+ code, bundle = parser.GetResult()
+ return (code, bundle, statuses)
+
+
+def GenerateTestResults(
+ result_code, result_bundle, statuses, start_ms, duration_ms):
+ """Generate test results from |statuses|.
+
+ Args:
+ result_code: The overall status code as an integer.
+ result_bundle: The summary bundle dump as a dict.
+ statuses: A list of 2-tuples containing:
+ - the status code as an integer
+ - the bundle dump as a dict mapping string keys to string values
+ Note that this is the same as the third item in the 3-tuple returned by
+ |_ParseAmInstrumentRawOutput|.
+ start_ms: The start time of the test in milliseconds.
+ duration_ms: The duration of the test in milliseconds.
+
+ Returns:
+ A list containing an instance of InstrumentationTestResult for each test
+ parsed.
+ """
+
+ results = []
+
+ current_result = None
+
+ for status_code, bundle in statuses:
+ test_class = bundle.get('class', '')
+ test_method = bundle.get('test', '')
+ if test_class and test_method:
+ test_name = '%s#%s' % (test_class, test_method)
+ else:
+ continue
+
+ if status_code == instrumentation_parser.STATUS_CODE_START:
+ if current_result:
+ results.append(current_result)
+ current_result = test_result.InstrumentationTestResult(
+ test_name, base_test_result.ResultType.UNKNOWN, start_ms, duration_ms)
+ else:
+ if status_code == instrumentation_parser.STATUS_CODE_OK:
+ if bundle.get('test_skipped', '').lower() in ('true', '1', 'yes'):
+ current_result.SetType(base_test_result.ResultType.SKIP)
+ elif current_result.GetType() == base_test_result.ResultType.UNKNOWN:
+ current_result.SetType(base_test_result.ResultType.PASS)
+ else:
+ if status_code not in (instrumentation_parser.STATUS_CODE_ERROR,
+ instrumentation_parser.STATUS_CODE_FAILURE):
+ logging.error('Unrecognized status code %d. Handling as an error.',
+ status_code)
+ current_result.SetType(base_test_result.ResultType.FAIL)
+ if 'stack' in bundle:
+ current_result.SetLog(bundle['stack'])
+
+ if current_result:
+ if current_result.GetType() == base_test_result.ResultType.UNKNOWN:
+ crashed = (result_code == _ACTIVITY_RESULT_CANCELED
+ and any(_NATIVE_CRASH_RE.search(l)
+ for l in result_bundle.itervalues()))
+ if crashed:
+ current_result.SetType(base_test_result.ResultType.CRASH)
+
+ results.append(current_result)
+
+ return results
+
+
+class InstrumentationTestInstance(test_instance.TestInstance):
+
+ def __init__(self, args, isolate_delegate, error_func):
+ super(InstrumentationTestInstance, self).__init__()
+
+ self._apk_under_test = None
+ self._package_info = None
+ self._suite = None
+ self._test_apk = None
+ self._test_jar = None
+ self._test_package = None
+ self._test_runner = None
+ self._test_support_apk = None
+ self._initializeApkAttributes(args, error_func)
+
+ self._data_deps = None
+ self._isolate_abs_path = None
+ self._isolate_delegate = None
+ self._isolated_abs_path = None
+ self._test_data = None
+ self._initializeDataDependencyAttributes(args, isolate_delegate)
+
+ self._annotations = None
+ self._excluded_annotations = None
+ self._test_filter = None
+ self._initializeTestFilterAttributes(args)
+
+ self._flags = None
+ self._initializeFlagAttributes(args)
+
+ self._driver_apk = None
+ self._driver_package = None
+ self._driver_name = None
+ self._initializeDriverAttributes()
+
+ def _initializeApkAttributes(self, args, error_func):
+ if args.apk_under_test.endswith('.apk'):
+ self._apk_under_test = args.apk_under_test
+ else:
+ self._apk_under_test = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
+ '%s.apk' % args.apk_under_test)
+
+ if not os.path.exists(self._apk_under_test):
+ error_func('Unable to find APK under test: %s' % self._apk_under_test)
+
+ if args.test_apk.endswith('.apk'):
+ self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
+ self._test_apk = args.test_apk
+ else:
+ self._suite = args.test_apk
+ self._test_apk = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
+ '%s.apk' % args.test_apk)
+
+ self._test_jar = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
+ '%s.jar' % self._suite)
+ self._test_support_apk = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
+ '%sSupport.apk' % self._suite)
+
+ if not os.path.exists(self._test_apk):
+ error_func('Unable to find test APK: %s' % self._test_apk)
+ if not os.path.exists(self._test_jar):
+ error_func('Unable to find test JAR: %s' % self._test_jar)
+
+ apk = apk_helper.ApkHelper(self.test_apk)
+ self._test_package = apk.GetPackageName()
+ self._test_runner = apk.GetInstrumentationName()
+
+ self._package_info = None
+ for package_info in constants.PACKAGE_INFO.itervalues():
+ if self._test_package == package_info.test_package:
+ self._package_info = package_info
+ if not self._package_info:
+ logging.warning('Unable to find package info for %s', self._test_package)
+
+ def _initializeDataDependencyAttributes(self, args, isolate_delegate):
+ self._data_deps = []
+ if args.isolate_file_path:
+ self._isolate_abs_path = os.path.abspath(args.isolate_file_path)
+ self._isolate_delegate = isolate_delegate
+ self._isolated_abs_path = os.path.join(
+ constants.GetOutDirectory(), '%s.isolated' % self._test_package)
+ else:
+ self._isolate_delegate = None
+
+ # TODO(jbudorick): Deprecate and remove --test-data once data dependencies
+ # are fully converted to isolate.
+ if args.test_data:
+ logging.info('Data dependencies specified via --test-data')
+ self._test_data = args.test_data
+ else:
+ self._test_data = None
+
+ if not self._isolate_delegate and not self._test_data:
+ logging.warning('No data dependencies will be pushed.')
+
+ def _initializeTestFilterAttributes(self, args):
+ self._test_filter = args.test_filter
+
+ def annotation_dict_element(a):
+ a = a.split('=')
+ return (a[0], a[1] if len(a) == 2 else None)
+
+ if args.annotation_str:
+ self._annotations = dict(
+ annotation_dict_element(a)
+ for a in args.annotation_str.split(','))
+ elif not self._test_filter:
+ self._annotations = dict(
+ annotation_dict_element(a)
+ for a in _DEFAULT_ANNOTATIONS)
+ else:
+ self._annotations = {}
+
+ if args.exclude_annotation_str:
+ self._excluded_annotations = dict(
+ annotation_dict_element(a)
+ for a in args.exclude_annotation_str.split(','))
+ else:
+ self._excluded_annotations = {}
+
+ def _initializeFlagAttributes(self, args):
+ self._flags = ['--disable-fre', '--enable-test-intents']
+ # TODO(jbudorick): Transition "--device-flags" to "--device-flags-file"
+ if hasattr(args, 'device_flags') and args.device_flags:
+ with open(args.device_flags) as device_flags_file:
+ stripped_lines = (l.strip() for l in device_flags_file)
+ self._flags.extend([flag for flag in stripped_lines if flag])
+ if hasattr(args, 'device_flags_file') and args.device_flags_file:
+ with open(args.device_flags_file) as device_flags_file:
+ stripped_lines = (l.strip() for l in device_flags_file)
+ self._flags.extend([flag for flag in stripped_lines if flag])
+
+ def _initializeDriverAttributes(self):
+ self._driver_apk = os.path.join(
+ constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
+ 'OnDeviceInstrumentationDriver.apk')
+ if os.path.exists(self._driver_apk):
+ driver_apk = apk_helper.ApkHelper(self._driver_apk)
+ self._driver_package = driver_apk.GetPackageName()
+ self._driver_name = driver_apk.GetInstrumentationName()
+ else:
+ self._driver_apk = None
+
+ @property
+ def apk_under_test(self):
+ return self._apk_under_test
+
+ @property
+ def flags(self):
+ return self._flags
+
+ @property
+ def driver_apk(self):
+ return self._driver_apk
+
+ @property
+ def driver_package(self):
+ return self._driver_package
+
+ @property
+ def driver_name(self):
+ return self._driver_name
+
+ @property
+ def package_info(self):
+ return self._package_info
+
+ @property
+ def suite(self):
+ return self._suite
+
+ @property
+ def test_apk(self):
+ return self._test_apk
+
+ @property
+ def test_jar(self):
+ return self._test_jar
+
+ @property
+ def test_support_apk(self):
+ return self._test_support_apk
+
+ @property
+ def test_package(self):
+ return self._test_package
+
+ @property
+ def test_runner(self):
+ return self._test_runner
+
+ #override
+ def TestType(self):
+ return 'instrumentation'
+
+ #override
+ def SetUp(self):
+ if self._isolate_delegate:
+ self._isolate_delegate.Remap(
+ self._isolate_abs_path, self._isolated_abs_path)
+ self._isolate_delegate.MoveOutputDeps()
+ self._data_deps.extend([(constants.ISOLATE_DEPS_DIR, None)])
+
+ # TODO(jbudorick): Convert existing tests that depend on the --test-data
+ # mechanism to isolate, then remove this.
+ if self._test_data:
+ for t in self._test_data:
+ device_rel_path, host_rel_path = t.split(':')
+ host_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, host_rel_path)
+ self._data_deps.extend(
+ [(host_abs_path,
+ [None, 'chrome', 'test', 'data', device_rel_path])])
+
+ def GetDataDependencies(self):
+ return self._data_deps
+
+ def GetTests(self):
+ pickle_path = '%s-proguard.pickle' % self.test_jar
+ try:
+ tests = self._GetTestsFromPickle(pickle_path, self.test_jar)
+ except self.ProguardPickleException as e:
+ logging.info('Getting tests from JAR via proguard. (%s)' % str(e))
+ tests = self._GetTestsFromProguard(self.test_jar)
+ self._SaveTestsToPickle(pickle_path, self.test_jar, tests)
+ return self._InflateTests(self._FilterTests(tests))
+
+ class ProguardPickleException(Exception):
+ pass
+
+ def _GetTestsFromPickle(self, pickle_path, jar_path):
+ if not os.path.exists(pickle_path):
+ raise self.ProguardPickleException('%s does not exist.' % pickle_path)
+ if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
+ raise self.ProguardPickleException(
+ '%s newer than %s.' % (jar_path, pickle_path))
+
+ with open(pickle_path, 'r') as pickle_file:
+ pickle_data = pickle.loads(pickle_file.read())
+ jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
+
+ try:
+ if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
+ raise self.ProguardPickleException('PICKLE_FORMAT_VERSION has changed.')
+ if pickle_data['JAR_MD5SUM'] != jar_md5:
+ raise self.ProguardPickleException('JAR file MD5 sum differs.')
+ return pickle_data['TEST_METHODS']
+ except TypeError as e:
+ logging.error(pickle_data)
+ raise self.ProguardPickleException(str(e))
+
+ def _GetTestsFromProguard(self, jar_path):
+ p = proguard.Dump(jar_path)
+
+ def is_test_class(c):
+ return c['class'].endswith('Test')
+
+ def is_test_method(m):
+ return m['method'].startswith('test')
+
+ class_lookup = dict((c['class'], c) for c in p['classes'])
+ def recursive_get_class_annotations(c):
+ s = c['superclass']
+ if s in class_lookup:
+ a = recursive_get_class_annotations(class_lookup[s])
+ else:
+ a = {}
+ a.update(c['annotations'])
+ return a
+
+ def stripped_test_class(c):
+ return {
+ 'class': c['class'],
+ 'annotations': recursive_get_class_annotations(c),
+ 'methods': [m for m in c['methods'] if is_test_method(m)],
+ }
+
+ return [stripped_test_class(c) for c in p['classes']
+ if is_test_class(c)]
+
+ def _SaveTestsToPickle(self, pickle_path, jar_path, tests):
+ jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
+ pickle_data = {
+ 'VERSION': _PICKLE_FORMAT_VERSION,
+ 'JAR_MD5SUM': jar_md5,
+ 'TEST_METHODS': tests,
+ }
+ with open(pickle_path, 'w') as pickle_file:
+ pickle.dump(pickle_data, pickle_file)
+
+ def _FilterTests(self, tests):
+
+ def gtest_filter(c, m):
+ t = ['%s.%s' % (c['class'].split('.')[-1], m['method'])]
+ return (not self._test_filter
+ or unittest_util.FilterTestNames(t, self._test_filter))
+
+ def annotation_filter(all_annotations):
+ if not self._annotations:
+ return True
+ return any_annotation_matches(self._annotations, all_annotations)
+
+ def excluded_annotation_filter(all_annotations):
+ if not self._excluded_annotations:
+ return True
+ return not any_annotation_matches(self._excluded_annotations,
+ all_annotations)
+
+ def any_annotation_matches(annotations, all_annotations):
+ return any(
+ ak in all_annotations and (av is None or av == all_annotations[ak])
+ for ak, av in annotations.iteritems())
+
+ filtered_classes = []
+ for c in tests:
+ filtered_methods = []
+ for m in c['methods']:
+ # Gtest filtering
+ if not gtest_filter(c, m):
+ continue
+
+ all_annotations = dict(c['annotations'])
+ all_annotations.update(m['annotations'])
+ if (not annotation_filter(all_annotations)
+ or not excluded_annotation_filter(all_annotations)):
+ continue
+
+ filtered_methods.append(m)
+
+ if filtered_methods:
+ filtered_class = dict(c)
+ filtered_class['methods'] = filtered_methods
+ filtered_classes.append(filtered_class)
+
+ return filtered_classes
+
+ def _InflateTests(self, tests):
+ inflated_tests = []
+ for c in tests:
+ for m in c['methods']:
+ a = dict(c['annotations'])
+ a.update(m['annotations'])
+ inflated_tests.append({
+ 'class': c['class'],
+ 'method': m['method'],
+ 'annotations': a,
+ })
+ return inflated_tests
+
+ @staticmethod
+ def GetHttpServerEnvironmentVars():
+ return {
+ _EXTRA_ENABLE_HTTP_SERVER: None,
+ }
+
+ def GetDriverEnvironmentVars(
+ self, test_list=None, test_list_file_path=None):
+ env = {
+ _EXTRA_DRIVER_TARGET_PACKAGE: self.test_package,
+ _EXTRA_DRIVER_TARGET_CLASS: self.test_runner,
+ }
+
+ if test_list:
+ env[_EXTRA_DRIVER_TEST_LIST] = ','.join(test_list)
+
+ if test_list_file_path:
+ env[_EXTRA_DRIVER_TEST_LIST_FILE] = (
+ os.path.basename(test_list_file_path))
+
+ return env
+
+ @staticmethod
+ def ParseAmInstrumentRawOutput(raw_output):
+ return ParseAmInstrumentRawOutput(raw_output)
+
+ @staticmethod
+ def GenerateTestResults(
+ result_code, result_bundle, statuses, start_ms, duration_ms):
+ return GenerateTestResults(result_code, result_bundle, statuses,
+ start_ms, duration_ms)
+
+ #override
+ def TearDown(self):
+ if self._isolate_delegate:
+ self._isolate_delegate.Clear()
+
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance_test.py b/build/android/pylib/instrumentation/instrumentation_test_instance_test.py
new file mode 100755
index 00000000000..752e4d3d0e4
--- /dev/null
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance_test.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+
+"""Unit tests for instrumentation.TestRunner."""
+
+# pylint: disable=W0212
+
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.instrumentation import instrumentation_test_instance
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class InstrumentationTestInstanceTest(unittest.TestCase):
+
+ def setUp(self):
+ options = mock.Mock()
+ options.tool = ''
+
+ def testGenerateTestResults_noStatus(self):
+ results = instrumentation_test_instance.GenerateTestResults(
+ None, None, [], 0, 1000)
+ self.assertEqual([], results)
+
+ def testGenerateTestResults_testPassed(self):
+ statuses = [
+ (1, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ (0, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ ]
+ results = instrumentation_test_instance.GenerateTestResults(
+ None, None, statuses, 0, 1000)
+ self.assertEqual(1, len(results))
+ self.assertEqual(base_test_result.ResultType.PASS, results[0].GetType())
+
+ def testGenerateTestResults_testSkipped_true(self):
+ statuses = [
+ (1, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ (0, {
+ 'test_skipped': 'true',
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ (0, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ ]
+ results = instrumentation_test_instance.GenerateTestResults(
+ None, None, statuses, 0, 1000)
+ self.assertEqual(1, len(results))
+ self.assertEqual(base_test_result.ResultType.SKIP, results[0].GetType())
+
+ def testGenerateTestResults_testSkipped_false(self):
+ statuses = [
+ (1, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ (0, {
+ 'test_skipped': 'false',
+ }),
+ (0, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ ]
+ results = instrumentation_test_instance.GenerateTestResults(
+ None, None, statuses, 0, 1000)
+ self.assertEqual(1, len(results))
+ self.assertEqual(base_test_result.ResultType.PASS, results[0].GetType())
+
+ def testGenerateTestResults_testFailed(self):
+ statuses = [
+ (1, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ (-2, {
+ 'class': 'test.package.TestClass',
+ 'test': 'testMethod',
+ }),
+ ]
+ results = instrumentation_test_instance.GenerateTestResults(
+ None, None, statuses, 0, 1000)
+ self.assertEqual(1, len(results))
+ self.assertEqual(base_test_result.ResultType.FAIL, results[0].GetType())
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/build/android/pylib/instrumentation/json_perf_parser.py b/build/android/pylib/instrumentation/json_perf_parser.py
new file mode 100644
index 00000000000..ffdfbe7c548
--- /dev/null
+++ b/build/android/pylib/instrumentation/json_perf_parser.py
@@ -0,0 +1,161 @@
+# Copyright 2013 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.
+
+
+"""A helper module for parsing JSON objects from perf tests results."""
+
+import json
+
+
+def GetAverageRunInfo(json_data, name):
+ """Summarizes TraceEvent JSON data for performance metrics.
+
+ Example JSON Inputs (More tags can be added but these are required):
+ Measuring Duration:
+ [
+ { "cat": "Java",
+ "ts": 10000000000,
+ "ph": "S",
+ "name": "TestTrace"
+ },
+ { "cat": "Java",
+ "ts": 10000004000,
+ "ph": "F",
+ "name": "TestTrace"
+ },
+ ...
+ ]
+
+ Measuring Call Frequency (FPS):
+ [
+ { "cat": "Java",
+ "ts": 10000000000,
+ "ph": "I",
+ "name": "TestTraceFPS"
+ },
+ { "cat": "Java",
+ "ts": 10000004000,
+ "ph": "I",
+ "name": "TestTraceFPS"
+ },
+ ...
+ ]
+
+ Args:
+ json_data: A list of dictonaries each representing a JSON object.
+ name: The 'name' tag to filter on in the JSON file.
+
+ Returns:
+ A dictionary of result data with the following tags:
+ min: The minimum value tracked.
+ max: The maximum value tracked.
+ average: The average of all the values tracked.
+ count: The number of times the category/name pair was tracked.
+ type: The type of tracking ('Instant' for instant tags and 'Span' for
+ begin/end tags.
+ category: The passed in category filter.
+ name: The passed in name filter.
+ data_points: A list of all of the times used to generate this data.
+ units: The units for the values being reported.
+
+ Raises:
+ Exception: if entry contains invalid data.
+ """
+
+ def EntryFilter(entry):
+ return entry['cat'] == 'Java' and entry['name'] == name
+ filtered_entries = filter(EntryFilter, json_data)
+
+ result = {}
+
+ result['min'] = -1
+ result['max'] = -1
+ result['average'] = 0
+ result['count'] = 0
+ result['type'] = 'Unknown'
+ result['category'] = 'Java'
+ result['name'] = name
+ result['data_points'] = []
+ result['units'] = ''
+
+ total_sum = 0
+
+ last_val = 0
+ val_type = None
+ for entry in filtered_entries:
+ if not val_type:
+ if 'mem' in entry:
+ val_type = 'mem'
+
+ def GetVal(entry):
+ return entry['mem']
+
+ result['units'] = 'kb'
+ elif 'ts' in entry:
+ val_type = 'ts'
+
+ def GetVal(entry):
+ return float(entry['ts']) / 1000.0
+
+ result['units'] = 'ms'
+ else:
+ raise Exception('Entry did not contain valid value info: %s' % entry)
+
+ if not val_type in entry:
+ raise Exception('Entry did not contain expected value type "%s" '
+ 'information: %s' % (val_type, entry))
+ val = GetVal(entry)
+ if (entry['ph'] == 'S' and
+ (result['type'] == 'Unknown' or result['type'] == 'Span')):
+ result['type'] = 'Span'
+ last_val = val
+ elif ((entry['ph'] == 'F' and result['type'] == 'Span') or
+ (entry['ph'] == 'I' and (result['type'] == 'Unknown' or
+ result['type'] == 'Instant'))):
+ if last_val > 0:
+ delta = val - last_val
+ if result['min'] == -1 or result['min'] > delta:
+ result['min'] = delta
+ if result['max'] == -1 or result['max'] < delta:
+ result['max'] = delta
+ total_sum += delta
+ result['count'] += 1
+ result['data_points'].append(delta)
+ if entry['ph'] == 'I':
+ result['type'] = 'Instant'
+ last_val = val
+ if result['count'] > 0:
+ result['average'] = total_sum / result['count']
+
+ return result
+
+
+def GetAverageRunInfoFromJSONString(json_string, name):
+ """Returns the results from GetAverageRunInfo using a JSON string.
+
+ Args:
+ json_string: The string containing JSON.
+ name: The 'name' tag to filter on in the JSON file.
+
+ Returns:
+ See GetAverageRunInfo Returns section.
+ """
+ return GetAverageRunInfo(json.loads(json_string), name)
+
+
+def GetAverageRunInfoFromFile(json_file, name):
+ """Returns the results from GetAverageRunInfo using a JSON file.
+
+ Args:
+ json_file: The path to a JSON file.
+ name: The 'name' tag to filter on in the JSON file.
+
+ Returns:
+ See GetAverageRunInfo Returns section.
+ """
+ with open(json_file, 'r') as f:
+ data = f.read()
+ perf = json.loads(data)
+
+ return GetAverageRunInfo(perf, name)
diff --git a/build/android/pylib/instrumentation/setup.py b/build/android/pylib/instrumentation/setup.py
new file mode 100644
index 00000000000..7a0501e5b5c
--- /dev/null
+++ b/build/android/pylib/instrumentation/setup.py
@@ -0,0 +1,113 @@
+# Copyright 2013 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.
+
+"""Generates test runner factory and tests for instrumentation tests."""
+
+import logging
+import os
+
+from pylib import constants
+from pylib import valgrind_tools
+
+from pylib.base import base_setup
+from pylib.device import device_utils
+from pylib.instrumentation import test_package
+from pylib.instrumentation import test_runner
+
+DEVICE_DATA_DIR = 'chrome/test/data'
+
+ISOLATE_FILE_PATHS = {
+ 'AndroidWebViewTest': 'android_webview/android_webview_test_apk.isolate',
+ 'ChromeShellTest': 'chrome/chrome_shell_test_apk.isolate',
+ 'ContentShellTest': 'content/content_shell_test_apk.isolate',
+}
+
+DEPS_EXCLUSION_LIST = []
+
+# TODO(mikecase): Remove this function and the constant DEVICE_DATA_DIR
+# once all data deps are pushed to the same location on the device.
+def _PushExtraSuiteDataDeps(device, test_apk):
+ """Pushes some extra data files/dirs needed by some test suite.
+
+ Args:
+ test_apk: The test suite basename for which to return file paths.
+ """
+ if test_apk in ['ChromeTest', 'ContentShellTest']:
+ test_files = 'net/data/ssl/certificates'
+ host_device_file_tuple = [
+ (os.path.join(constants.DIR_SOURCE_ROOT, test_files),
+ os.path.join(device.GetExternalStoragePath(), test_files))]
+ device.PushChangedFiles(host_device_file_tuple)
+
+
+# TODO(mikecase): Remove this function once everything uses
+# base_setup.PushDataDeps to push data deps to the device.
+def _PushDataDeps(device, test_options):
+ valgrind_tools.PushFilesForTool(test_options.tool, device)
+
+ host_device_file_tuples = []
+ for dest_host_pair in test_options.test_data:
+ dst_src = dest_host_pair.split(':', 1)
+ dst_layer = dst_src[0]
+ host_src = dst_src[1]
+ host_test_files_path = os.path.join(constants.DIR_SOURCE_ROOT, host_src)
+ if os.path.exists(host_test_files_path):
+ host_device_file_tuples += [(
+ host_test_files_path,
+ '%s/%s/%s' % (
+ device.GetExternalStoragePath(),
+ DEVICE_DATA_DIR,
+ dst_layer))]
+ if host_device_file_tuples:
+ device.PushChangedFiles(host_device_file_tuples)
+
+
+def Setup(test_options, devices):
+ """Create and return the test runner factory and tests.
+
+ Args:
+ test_options: An InstrumentationOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+ if (test_options.coverage_dir and not
+ os.path.exists(test_options.coverage_dir)):
+ os.makedirs(test_options.coverage_dir)
+
+ test_pkg = test_package.TestPackage(test_options.test_apk_path,
+ test_options.test_apk_jar_path,
+ test_options.test_support_apk_path)
+ tests = test_pkg.GetAllMatchingTests(
+ test_options.annotations,
+ test_options.exclude_annotations,
+ test_options.test_filter)
+ if not tests:
+ logging.error('No instrumentation tests to run with current args.')
+
+ if test_options.test_data:
+ device_utils.DeviceUtils.parallel(devices).pMap(
+ _PushDataDeps, test_options)
+
+ if test_options.isolate_file_path:
+ i = base_setup.GenerateDepsDirUsingIsolate(test_options.test_apk,
+ test_options.isolate_file_path,
+ ISOLATE_FILE_PATHS,
+ DEPS_EXCLUSION_LIST)
+ def push_data_deps_to_device_dir(device):
+ base_setup.PushDataDeps(device, device.GetExternalStoragePath(),
+ test_options)
+ device_utils.DeviceUtils.parallel(devices).pMap(
+ push_data_deps_to_device_dir)
+ if i:
+ i.Clear()
+
+ device_utils.DeviceUtils.parallel(devices).pMap(
+ _PushExtraSuiteDataDeps, test_options.test_apk)
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.TestRunner(test_options, device, shard_index,
+ test_pkg)
+
+ return (TestRunnerFactory, tests)
diff --git a/build/android/pylib/instrumentation/test_jar.py b/build/android/pylib/instrumentation/test_jar.py
new file mode 100644
index 00000000000..7ad8997755c
--- /dev/null
+++ b/build/android/pylib/instrumentation/test_jar.py
@@ -0,0 +1,230 @@
+# Copyright (c) 2013 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.
+
+"""Helper class for instrumenation test jar."""
+# pylint: disable=W0702
+
+import logging
+import os
+import pickle
+import re
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.device import device_utils
+from pylib.utils import md5sum
+from pylib.utils import proguard
+
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT,
+ 'build', 'util', 'lib', 'common'))
+
+import unittest_util # pylint: disable=F0401
+
+# If you change the cached output of proguard, increment this number
+PICKLE_FORMAT_VERSION = 4
+
+
+class TestJar(object):
+ _ANNOTATIONS = frozenset(
+ ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest',
+ 'FlakyTest', 'DisabledTest', 'Manual', 'PerfTest', 'HostDrivenTest',
+ 'IntegrationTest'])
+ _DEFAULT_ANNOTATION = 'SmallTest'
+ _PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$')
+ _PROGUARD_SUPERCLASS_RE = re.compile(r'\s*? Superclass:\s*([\S]+)$')
+ _PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$')
+ _PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$')
+ _PROGUARD_ANNOTATION_CONST_RE = (
+ re.compile(r'\s*?- Constant element value.*$'))
+ _PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
+
+ def __init__(self, jar_path):
+ if not os.path.exists(jar_path):
+ raise Exception('%s not found, please build it' % jar_path)
+
+ self._PROGUARD_PATH = os.path.join(constants.ANDROID_SDK_ROOT,
+ 'tools/proguard/lib/proguard.jar')
+ if not os.path.exists(self._PROGUARD_PATH):
+ self._PROGUARD_PATH = os.path.join(os.environ['ANDROID_BUILD_TOP'],
+ 'external/proguard/lib/proguard.jar')
+ self._jar_path = jar_path
+ self._pickled_proguard_name = self._jar_path + '-proguard.pickle'
+ self._test_methods = {}
+ if not self._GetCachedProguardData():
+ self._GetProguardData()
+
+ def _GetCachedProguardData(self):
+ if (os.path.exists(self._pickled_proguard_name) and
+ (os.path.getmtime(self._pickled_proguard_name) >
+ os.path.getmtime(self._jar_path))):
+ logging.info('Loading cached proguard output from %s',
+ self._pickled_proguard_name)
+ try:
+ with open(self._pickled_proguard_name, 'r') as r:
+ d = pickle.loads(r.read())
+ jar_md5 = md5sum.CalculateHostMd5Sums(
+ self._jar_path)[os.path.realpath(self._jar_path)]
+ if (d['JAR_MD5SUM'] == jar_md5 and
+ d['VERSION'] == PICKLE_FORMAT_VERSION):
+ self._test_methods = d['TEST_METHODS']
+ return True
+ except:
+ logging.warning('PICKLE_FORMAT_VERSION has changed, ignoring cache')
+ return False
+
+ def _GetProguardData(self):
+ logging.info('Retrieving test methods via proguard.')
+
+ p = proguard.Dump(self._jar_path)
+
+ class_lookup = dict((c['class'], c) for c in p['classes'])
+ def recursive_get_annotations(c):
+ s = c['superclass']
+ if s in class_lookup:
+ a = recursive_get_annotations(class_lookup[s])
+ else:
+ a = {}
+ a.update(c['annotations'])
+ return a
+
+ test_classes = (c for c in p['classes']
+ if c['class'].endswith('Test'))
+ for c in test_classes:
+ class_annotations = recursive_get_annotations(c)
+ test_methods = (m for m in c['methods']
+ if m['method'].startswith('test'))
+ for m in test_methods:
+ qualified_method = '%s#%s' % (c['class'], m['method'])
+ annotations = dict(class_annotations)
+ annotations.update(m['annotations'])
+ self._test_methods[qualified_method] = m
+ self._test_methods[qualified_method]['annotations'] = annotations
+
+ logging.info('Storing proguard output to %s', self._pickled_proguard_name)
+ d = {'VERSION': PICKLE_FORMAT_VERSION,
+ 'TEST_METHODS': self._test_methods,
+ 'JAR_MD5SUM':
+ md5sum.CalculateHostMd5Sums(
+ self._jar_path)[os.path.realpath(self._jar_path)]}
+ with open(self._pickled_proguard_name, 'w') as f:
+ f.write(pickle.dumps(d))
+
+ @staticmethod
+ def _IsTestMethod(test):
+ class_name, method = test.split('#')
+ return class_name.endswith('Test') and method.startswith('test')
+
+ def GetTestAnnotations(self, test):
+ """Returns a list of all annotations for the given |test|. May be empty."""
+ if not self._IsTestMethod(test) or not test in self._test_methods:
+ return []
+ return self._test_methods[test]['annotations']
+
+ @staticmethod
+ def _AnnotationsMatchFilters(annotation_filter_list, annotations):
+ """Checks if annotations match any of the filters."""
+ if not annotation_filter_list:
+ return True
+ for annotation_filter in annotation_filter_list:
+ filters = annotation_filter.split('=')
+ if len(filters) == 2:
+ key = filters[0]
+ value_list = filters[1].split(',')
+ for value in value_list:
+ if key in annotations and value == annotations[key]:
+ return True
+ elif annotation_filter in annotations:
+ return True
+ return False
+
+ def GetAnnotatedTests(self, annotation_filter_list):
+ """Returns a list of all tests that match the given annotation filters."""
+ return [test for test in self.GetTestMethods()
+ if self._IsTestMethod(test) and self._AnnotationsMatchFilters(
+ annotation_filter_list, self.GetTestAnnotations(test))]
+
+ def GetTestMethods(self):
+ """Returns a dict of all test methods and relevant attributes.
+
+ Test methods are retrieved as Class#testMethod.
+ """
+ return self._test_methods
+
+ def _GetTestsMissingAnnotation(self):
+ """Get a list of test methods with no known annotations."""
+ tests_missing_annotations = []
+ for test_method in self.GetTestMethods().iterkeys():
+ annotations_ = frozenset(self.GetTestAnnotations(test_method).iterkeys())
+ if (annotations_.isdisjoint(self._ANNOTATIONS) and
+ not self.IsHostDrivenTest(test_method)):
+ tests_missing_annotations.append(test_method)
+ return sorted(tests_missing_annotations)
+
+ def _IsTestValidForSdkRange(self, test_name, attached_min_sdk_level):
+ required_min_sdk_level = int(
+ self.GetTestAnnotations(test_name).get('MinAndroidSdkLevel', 0))
+ return (required_min_sdk_level is None or
+ attached_min_sdk_level >= required_min_sdk_level)
+
+ def GetAllMatchingTests(self, annotation_filter_list,
+ exclude_annotation_list, test_filter):
+ """Get a list of tests matching any of the annotations and the filter.
+
+ Args:
+ annotation_filter_list: List of test annotations. A test must have at
+ least one of these annotations. A test without any annotations is
+ considered to be SmallTest.
+ exclude_annotation_list: List of test annotations. A test must not have
+ any of these annotations.
+ test_filter: Filter used for partial matching on the test method names.
+
+ Returns:
+ List of all matching tests.
+ """
+ if annotation_filter_list:
+ available_tests = self.GetAnnotatedTests(annotation_filter_list)
+ # Include un-annotated tests in SmallTest.
+ if annotation_filter_list.count(self._DEFAULT_ANNOTATION) > 0:
+ for test in self._GetTestsMissingAnnotation():
+ logging.warning(
+ '%s has no annotations. Assuming "%s".', test,
+ self._DEFAULT_ANNOTATION)
+ available_tests.append(test)
+ else:
+ available_tests = [m for m in self.GetTestMethods()
+ if not self.IsHostDrivenTest(m)]
+
+ if exclude_annotation_list:
+ excluded_tests = self.GetAnnotatedTests(exclude_annotation_list)
+ available_tests = list(set(available_tests) - set(excluded_tests))
+
+ tests = []
+ if test_filter:
+ # |available_tests| are in adb instrument format: package.path.class#test.
+
+ # Maps a 'class.test' name to each 'package.path.class#test' name.
+ sanitized_test_names = dict([
+ (t.split('.')[-1].replace('#', '.'), t) for t in available_tests])
+ # Filters 'class.test' names and populates |tests| with the corresponding
+ # 'package.path.class#test' names.
+ tests = [
+ sanitized_test_names[t] for t in unittest_util.FilterTestNames(
+ sanitized_test_names.keys(), test_filter.replace('#', '.'))]
+ else:
+ tests = available_tests
+
+ # Filter out any tests with SDK level requirements that don't match the set
+ # of attached devices.
+ devices = device_utils.DeviceUtils.parallel()
+ min_sdk_version = min(devices.build_version_sdk.pGet(None))
+ tests = [t for t in tests
+ if self._IsTestValidForSdkRange(t, min_sdk_version)]
+
+ return tests
+
+ @staticmethod
+ def IsHostDrivenTest(test):
+ return 'pythonDrivenTests' in test
diff --git a/build/android/pylib/instrumentation/test_options.py b/build/android/pylib/instrumentation/test_options.py
new file mode 100644
index 00000000000..e7b7a9f9b63
--- /dev/null
+++ b/build/android/pylib/instrumentation/test_options.py
@@ -0,0 +1,27 @@
+# Copyright 2013 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.
+
+"""Defines the InstrumentationOptions named tuple."""
+
+import collections
+
+InstrumentationOptions = collections.namedtuple('InstrumentationOptions', [
+ 'tool',
+ 'annotations',
+ 'exclude_annotations',
+ 'test_filter',
+ 'test_data',
+ 'save_perf_json',
+ 'screenshot_failures',
+ 'wait_for_debugger',
+ 'coverage_dir',
+ 'test_apk',
+ 'test_apk_path',
+ 'test_apk_jar_path',
+ 'test_runner',
+ 'test_support_apk_path',
+ 'device_flags',
+ 'isolate_file_path',
+ 'set_asserts',
+ 'delete_stale_data'])
diff --git a/build/android/pylib/instrumentation/test_package.py b/build/android/pylib/instrumentation/test_package.py
new file mode 100644
index 00000000000..5be061d4af8
--- /dev/null
+++ b/build/android/pylib/instrumentation/test_package.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2013 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.
+
+"""Class representing instrumentation test apk and jar."""
+
+import os
+
+from pylib.instrumentation import test_jar
+from pylib.utils import apk_helper
+
+
+class TestPackage(test_jar.TestJar):
+ def __init__(self, apk_path, jar_path, test_support_apk_path):
+ test_jar.TestJar.__init__(self, jar_path)
+
+ if not os.path.exists(apk_path):
+ raise Exception('%s not found, please build it' % apk_path)
+ if test_support_apk_path and not os.path.exists(test_support_apk_path):
+ raise Exception('%s not found, please build it' % test_support_apk_path)
+ self._apk_path = apk_path
+ self._apk_name = os.path.splitext(os.path.basename(apk_path))[0]
+ self._package_name = apk_helper.GetPackageName(self._apk_path)
+ self._test_support_apk_path = test_support_apk_path
+
+ def GetApkPath(self):
+ """Returns the absolute path to the APK."""
+ return self._apk_path
+
+ def GetApkName(self):
+ """Returns the name of the apk without the suffix."""
+ return self._apk_name
+
+ def GetPackageName(self):
+ """Returns the package name of this APK."""
+ return self._package_name
+
+ # Override.
+ def Install(self, device):
+ device.Install(self.GetApkPath())
+ if (self._test_support_apk_path and
+ os.path.exists(self._test_support_apk_path)):
+ device.Install(self._test_support_apk_path)
+
diff --git a/build/android/pylib/instrumentation/test_result.py b/build/android/pylib/instrumentation/test_result.py
new file mode 100644
index 00000000000..24e80a8e5fb
--- /dev/null
+++ b/build/android/pylib/instrumentation/test_result.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2012 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.
+
+from pylib.base import base_test_result
+
+
+class InstrumentationTestResult(base_test_result.BaseTestResult):
+ """Result information for a single instrumentation test."""
+
+ def __init__(self, full_name, test_type, start_date, dur, log=''):
+ """Construct an InstrumentationTestResult object.
+
+ Args:
+ full_name: Full name of the test.
+ test_type: Type of the test result as defined in ResultType.
+ start_date: Date in milliseconds when the test began running.
+ dur: Duration of the test run in milliseconds.
+ log: A string listing any errors.
+ """
+ super(InstrumentationTestResult, self).__init__(
+ full_name, test_type, dur, log)
+ name_pieces = full_name.rsplit('#')
+ if len(name_pieces) > 1:
+ self._test_name = name_pieces[1]
+ self._class_name = name_pieces[0]
+ else:
+ self._class_name = full_name
+ self._test_name = full_name
+ self._start_date = start_date
diff --git a/build/android/pylib/instrumentation/test_runner.py b/build/android/pylib/instrumentation/test_runner.py
new file mode 100644
index 00000000000..0f2e53fea73
--- /dev/null
+++ b/build/android/pylib/instrumentation/test_runner.py
@@ -0,0 +1,374 @@
+# Copyright (c) 2012 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.
+
+"""Class for running instrumentation tests on a single device."""
+
+import logging
+import os
+import re
+import sys
+import time
+
+from pylib import constants
+from pylib import flag_changer
+from pylib import valgrind_tools
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.device import device_errors
+from pylib.instrumentation import instrumentation_test_instance
+from pylib.instrumentation import json_perf_parser
+from pylib.instrumentation import test_result
+from pylib.local.device import local_device_instrumentation_test_run
+
+sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
+ 'common'))
+import perf_tests_results_helper # pylint: disable=F0401
+
+
+_PERF_TEST_ANNOTATION = 'PerfTest'
+
+
+class TestRunner(base_test_runner.BaseTestRunner):
+ """Responsible for running a series of tests connected to a single device."""
+
+ _DEVICE_COVERAGE_DIR = 'chrome/test/coverage'
+ _HOSTMACHINE_PERF_OUTPUT_FILE = '/tmp/chrome-profile'
+ _DEVICE_PERF_OUTPUT_SEARCH_PREFIX = (constants.DEVICE_PERF_OUTPUT_DIR +
+ '/chrome-profile*')
+
+ def __init__(self, test_options, device, shard_index, test_pkg,
+ additional_flags=None):
+ """Create a new TestRunner.
+
+ Args:
+ test_options: An InstrumentationOptions object.
+ device: Attached android device.
+ shard_index: Shard index.
+ test_pkg: A TestPackage object.
+ additional_flags: A list of additional flags to add to the command line.
+ """
+ super(TestRunner, self).__init__(device, test_options.tool)
+ self._lighttp_port = constants.LIGHTTPD_RANDOM_PORT_FIRST + shard_index
+ self._logcat_monitor = None
+
+ self.coverage_device_file = None
+ self.coverage_dir = test_options.coverage_dir
+ self.coverage_host_file = None
+ self.options = test_options
+ self.test_pkg = test_pkg
+ # Use the correct command line file for the package under test.
+ cmdline_file = [a.cmdline_file for a in constants.PACKAGE_INFO.itervalues()
+ if a.test_package == self.test_pkg.GetPackageName()]
+ assert len(cmdline_file) < 2, 'Multiple packages have the same test package'
+ if len(cmdline_file) and cmdline_file[0]:
+ self.flags = flag_changer.FlagChanger(self.device, cmdline_file[0])
+ if additional_flags:
+ self.flags.AddFlags(additional_flags)
+ else:
+ self.flags = None
+
+ #override
+ def InstallTestPackage(self):
+ self.test_pkg.Install(self.device)
+
+ def _GetInstrumentationArgs(self):
+ ret = {}
+ if self.options.wait_for_debugger:
+ ret['debug'] = 'true'
+ if self.coverage_dir:
+ ret['coverage'] = 'true'
+ ret['coverageFile'] = self.coverage_device_file
+
+ return ret
+
+ def _TakeScreenshot(self, test):
+ """Takes a screenshot from the device."""
+ screenshot_name = os.path.join(constants.SCREENSHOTS_DIR, '%s.png' % test)
+ logging.info('Taking screenshot named %s', screenshot_name)
+ self.device.TakeScreenshot(screenshot_name)
+
+ def SetUp(self):
+ """Sets up the test harness and device before all tests are run."""
+ super(TestRunner, self).SetUp()
+ if not self.device.HasRoot():
+ logging.warning('Unable to enable java asserts for %s, non rooted device',
+ str(self.device))
+ else:
+ if self.device.SetJavaAsserts(self.options.set_asserts):
+ # TODO(jbudorick) How to best do shell restart after the
+ # android_commands refactor?
+ self.device.RunShellCommand('stop')
+ self.device.RunShellCommand('start')
+ self.device.WaitUntilFullyBooted()
+
+ # We give different default value to launch HTTP server based on shard index
+ # because it may have race condition when multiple processes are trying to
+ # launch lighttpd with same port at same time.
+ self.LaunchTestHttpServer(
+ os.path.join(constants.DIR_SOURCE_ROOT), self._lighttp_port)
+ if self.flags:
+ self.flags.AddFlags(['--disable-fre', '--enable-test-intents'])
+ if self.options.device_flags:
+ with open(self.options.device_flags) as device_flags_file:
+ stripped_flags = (l.strip() for l in device_flags_file)
+ self.flags.AddFlags([flag for flag in stripped_flags if flag])
+
+ def TearDown(self):
+ """Cleans up the test harness and saves outstanding data from test run."""
+ if self.flags:
+ self.flags.Restore()
+ super(TestRunner, self).TearDown()
+
+ def TestSetup(self, test):
+ """Sets up the test harness for running a particular test.
+
+ Args:
+ test: The name of the test that will be run.
+ """
+ self.SetupPerfMonitoringIfNeeded(test)
+ self._SetupIndividualTestTimeoutScale(test)
+ self.tool.SetupEnvironment()
+
+ if self.flags and self._IsFreTest(test):
+ self.flags.RemoveFlags(['--disable-fre'])
+
+ # Make sure the forwarder is still running.
+ self._RestartHttpServerForwarderIfNecessary()
+
+ if self.coverage_dir:
+ coverage_basename = '%s.ec' % test
+ self.coverage_device_file = '%s/%s/%s' % (
+ self.device.GetExternalStoragePath(),
+ TestRunner._DEVICE_COVERAGE_DIR, coverage_basename)
+ self.coverage_host_file = os.path.join(
+ self.coverage_dir, coverage_basename)
+
+ def _IsFreTest(self, test):
+ """Determines whether a test is a first run experience test.
+
+ Args:
+ test: The name of the test to be checked.
+
+ Returns:
+ Whether the feature being tested is FirstRunExperience.
+ """
+ annotations = self.test_pkg.GetTestAnnotations(test)
+ return 'FirstRunExperience' == annotations.get('Feature', None)
+
+ def _IsPerfTest(self, test):
+ """Determines whether a test is a performance test.
+
+ Args:
+ test: The name of the test to be checked.
+
+ Returns:
+ Whether the test is annotated as a performance test.
+ """
+ return _PERF_TEST_ANNOTATION in self.test_pkg.GetTestAnnotations(test)
+
+ def SetupPerfMonitoringIfNeeded(self, test):
+ """Sets up performance monitoring if the specified test requires it.
+
+ Args:
+ test: The name of the test to be run.
+ """
+ if not self._IsPerfTest(test):
+ return
+ self.device.RunShellCommand(
+ ['rm', TestRunner._DEVICE_PERF_OUTPUT_SEARCH_PREFIX])
+ self._logcat_monitor = self.device.GetLogcatMonitor()
+ self._logcat_monitor.Start()
+
+ def TestTeardown(self, test, result):
+ """Cleans up the test harness after running a particular test.
+
+ Depending on the options of this TestRunner this might handle performance
+ tracking. This method will only be called if the test passed.
+
+ Args:
+ test: The name of the test that was just run.
+ result: result for this test.
+ """
+
+ self.tool.CleanUpEnvironment()
+
+ # The logic below relies on the test passing.
+ if not result or not result.DidRunPass():
+ return
+
+ self.TearDownPerfMonitoring(test)
+
+ if self.flags and self._IsFreTest(test):
+ self.flags.AddFlags(['--disable-fre'])
+
+ if self.coverage_dir:
+ self.device.PullFile(
+ self.coverage_device_file, self.coverage_host_file)
+ self.device.RunShellCommand(
+ 'rm -f %s' % self.coverage_device_file)
+
+ def TearDownPerfMonitoring(self, test):
+ """Cleans up performance monitoring if the specified test required it.
+
+ Args:
+ test: The name of the test that was just run.
+ Raises:
+ Exception: if there's anything wrong with the perf data.
+ """
+ if not self._IsPerfTest(test):
+ return
+ raw_test_name = test.split('#')[1]
+
+ # Wait and grab annotation data so we can figure out which traces to parse
+ regex = self._logcat_monitor.WaitFor(
+ re.compile(r'\*\*PERFANNOTATION\(' + raw_test_name + r'\)\:(.*)'))
+
+ # If the test is set to run on a specific device type only (IE: only
+ # tablet or phone) and it is being run on the wrong device, the test
+ # just quits and does not do anything. The java test harness will still
+ # print the appropriate annotation for us, but will add --NORUN-- for
+ # us so we know to ignore the results.
+ # The --NORUN-- tag is managed by ChromeTabbedActivityTestBase.java
+ if regex.group(1) != '--NORUN--':
+
+ # Obtain the relevant perf data. The data is dumped to a
+ # JSON formatted file.
+ json_string = self.device.ReadFile(
+ '/data/data/com.google.android.apps.chrome/files/PerfTestData.txt',
+ as_root=True)
+
+ if not json_string:
+ raise Exception('Perf file is empty')
+
+ if self.options.save_perf_json:
+ json_local_file = '/tmp/chromium-android-perf-json-' + raw_test_name
+ with open(json_local_file, 'w') as f:
+ f.write(json_string)
+ logging.info('Saving Perf UI JSON from test ' +
+ test + ' to ' + json_local_file)
+
+ raw_perf_data = regex.group(1).split(';')
+
+ for raw_perf_set in raw_perf_data:
+ if raw_perf_set:
+ perf_set = raw_perf_set.split(',')
+ if len(perf_set) != 3:
+ raise Exception('Unexpected number of tokens in perf annotation '
+ 'string: ' + raw_perf_set)
+
+ # Process the performance data
+ result = json_perf_parser.GetAverageRunInfoFromJSONString(json_string,
+ perf_set[0])
+ perf_tests_results_helper.PrintPerfResult(perf_set[1], perf_set[2],
+ [result['average']],
+ result['units'])
+
+ def _SetupIndividualTestTimeoutScale(self, test):
+ timeout_scale = self._GetIndividualTestTimeoutScale(test)
+ valgrind_tools.SetChromeTimeoutScale(self.device, timeout_scale)
+
+ def _GetIndividualTestTimeoutScale(self, test):
+ """Returns the timeout scale for the given |test|."""
+ annotations = self.test_pkg.GetTestAnnotations(test)
+ timeout_scale = 1
+ if 'TimeoutScale' in annotations:
+ try:
+ timeout_scale = int(annotations['TimeoutScale'])
+ except ValueError:
+ logging.warning('Non-integer value of TimeoutScale ignored. (%s)'
+ % annotations['TimeoutScale'])
+ if self.options.wait_for_debugger:
+ timeout_scale *= 100
+ return timeout_scale
+
+ def _GetIndividualTestTimeoutSecs(self, test):
+ """Returns the timeout in seconds for the given |test|."""
+ annotations = self.test_pkg.GetTestAnnotations(test)
+ if 'Manual' in annotations:
+ return 10 * 60 * 60
+ if 'IntegrationTest' in annotations:
+ return 30 * 60
+ if 'External' in annotations:
+ return 10 * 60
+ if 'EnormousTest' in annotations:
+ return 10 * 60
+ if 'LargeTest' in annotations or _PERF_TEST_ANNOTATION in annotations:
+ return 5 * 60
+ if 'MediumTest' in annotations:
+ return 3 * 60
+ if 'SmallTest' in annotations:
+ return 1 * 60
+
+ logging.warn(("Test size not found in annotations for test '%s', using " +
+ "1 minute for timeout.") % test)
+ return 1 * 60
+
+ def _RunTest(self, test, timeout):
+ """Runs a single instrumentation test.
+
+ Args:
+ test: Test class/method.
+ timeout: Timeout time in seconds.
+
+ Returns:
+ The raw output of am instrument as a list of lines.
+ """
+ extras = self._GetInstrumentationArgs()
+ extras['class'] = test
+ return self.device.StartInstrumentation(
+ '%s/%s' % (self.test_pkg.GetPackageName(), self.options.test_runner),
+ raw=True, extras=extras, timeout=timeout, retries=3)
+
+ def _GenerateTestResult(self, test, instr_result_code, instr_result_bundle,
+ statuses, start_ms, duration_ms):
+ results = instrumentation_test_instance.GenerateTestResults(
+ instr_result_code, instr_result_bundle, statuses, start_ms, duration_ms)
+ for r in results:
+ if r.GetName() == test:
+ return r
+ logging.error('Could not find result for test: %s', test)
+ return test_result.InstrumentationTestResult(
+ test, base_test_result.ResultType.UNKNOWN, start_ms, duration_ms)
+
+ #override
+ def RunTest(self, test):
+ results = base_test_result.TestRunResults()
+ timeout = (self._GetIndividualTestTimeoutSecs(test) *
+ self._GetIndividualTestTimeoutScale(test) *
+ self.tool.GetTimeoutScale())
+
+ start_ms = 0
+ duration_ms = 0
+ try:
+ self.TestSetup(test)
+
+ try:
+ self.device.GoHome()
+ except device_errors.CommandTimeoutError:
+ logging.exception('Failed to focus the launcher.')
+
+ time_ms = lambda: int(time.time() * 1000)
+ start_ms = time_ms()
+ raw_output = self._RunTest(test, timeout)
+ duration_ms = time_ms() - start_ms
+
+ # Parse the test output
+ result_code, result_bundle, statuses = (
+ instrumentation_test_instance.ParseAmInstrumentRawOutput(raw_output))
+ result = self._GenerateTestResult(
+ test, result_code, result_bundle, statuses, start_ms, duration_ms)
+ if local_device_instrumentation_test_run.DidPackageCrashOnDevice(
+ self.test_pkg.GetPackageName(), self.device):
+ result.SetType(base_test_result.ResultType.CRASH)
+ results.AddResult(result)
+ except device_errors.CommandTimeoutError as e:
+ results.AddResult(test_result.InstrumentationTestResult(
+ test, base_test_result.ResultType.TIMEOUT, start_ms, duration_ms,
+ log=str(e) or 'No information'))
+ except device_errors.DeviceUnreachableError as e:
+ results.AddResult(test_result.InstrumentationTestResult(
+ test, base_test_result.ResultType.CRASH, start_ms, duration_ms,
+ log=str(e) or 'No information'))
+ self.TestTeardown(test, results)
+ return (results, None if results.DidRunPass() else test)
diff --git a/build/android/pylib/junit/__init__.py b/build/android/pylib/junit/__init__.py
new file mode 100644
index 00000000000..5cac026153c
--- /dev/null
+++ b/build/android/pylib/junit/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2014 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.
+
diff --git a/build/android/pylib/junit/setup.py b/build/android/pylib/junit/setup.py
new file mode 100644
index 00000000000..94d4277d8e6
--- /dev/null
+++ b/build/android/pylib/junit/setup.py
@@ -0,0 +1,20 @@
+# Copyright 2014 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.
+
+from pylib.junit import test_runner
+
+def Setup(args):
+ """Creates a test runner factory for junit tests.
+
+ Args:
+ args: an argparse.Namespace object.
+ Return:
+ A (runner_factory, tests) tuple.
+ """
+
+ def TestRunnerFactory(_unused_device, _unused_shard_index):
+ return test_runner.JavaTestRunner(args)
+
+ return (TestRunnerFactory, ['JUnit tests'])
+
diff --git a/build/android/pylib/junit/test_dispatcher.py b/build/android/pylib/junit/test_dispatcher.py
new file mode 100644
index 00000000000..6e0d865c3f5
--- /dev/null
+++ b/build/android/pylib/junit/test_dispatcher.py
@@ -0,0 +1,28 @@
+# Copyright 2014 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.
+
+from pylib import constants
+from pylib.base import base_test_result
+
+def RunTests(tests, runner_factory):
+ """Runs a set of java tests on the host.
+
+ Return:
+ A tuple containing the results & the exit code.
+ """
+ def run(t):
+ runner = runner_factory(None, None)
+ runner.SetUp()
+ results_list, return_code = runner.RunTest(t)
+ runner.TearDown()
+ return (results_list, return_code == 0)
+
+ test_run_results = base_test_result.TestRunResults()
+ exit_code = 0
+ for t in tests:
+ results_list, passed = run(t)
+ test_run_results.AddResults(results_list)
+ if not passed:
+ exit_code = constants.ERROR_EXIT_CODE
+ return (test_run_results, exit_code)
\ No newline at end of file
diff --git a/build/android/pylib/junit/test_runner.py b/build/android/pylib/junit/test_runner.py
new file mode 100644
index 00000000000..a6d3bf9c92e
--- /dev/null
+++ b/build/android/pylib/junit/test_runner.py
@@ -0,0 +1,50 @@
+# Copyright 2014 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.
+
+import json
+import os
+import tempfile
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.results import json_results
+
+class JavaTestRunner(object):
+ """Runs java tests on the host."""
+
+ def __init__(self, args):
+ self._package_filter = args.package_filter
+ self._runner_filter = args.runner_filter
+ self._sdk_version = args.sdk_version
+ self._test_filter = args.test_filter
+ self._test_suite = args.test_suite
+
+ def SetUp(self):
+ pass
+
+ def RunTest(self, _test):
+ """Runs junit tests from |self._test_suite|."""
+ with tempfile.NamedTemporaryFile() as json_file:
+ java_script = os.path.join(
+ constants.GetOutDirectory(), 'bin', self._test_suite)
+ command = [java_script,
+ '-test-jars', self._test_suite + '.jar',
+ '-json-results-file', json_file.name]
+ if self._test_filter:
+ command.extend(['-gtest-filter', self._test_filter])
+ if self._package_filter:
+ command.extend(['-package-filter', self._package_filter])
+ if self._runner_filter:
+ command.extend(['-runner-filter', self._runner_filter])
+ if self._sdk_version:
+ command.extend(['-sdk-version', self._sdk_version])
+ return_code = cmd_helper.RunCmd(command)
+ results_list = json_results.ParseResultsFromJson(
+ json.loads(json_file.read()))
+ return (results_list, return_code)
+
+ def TearDown(self):
+ pass
+
diff --git a/build/android/pylib/linker/__init__.py b/build/android/pylib/linker/__init__.py
new file mode 100644
index 00000000000..af994371233
--- /dev/null
+++ b/build/android/pylib/linker/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2013 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.
+
diff --git a/build/android/pylib/linker/setup.py b/build/android/pylib/linker/setup.py
new file mode 100644
index 00000000000..5776f5af335
--- /dev/null
+++ b/build/android/pylib/linker/setup.py
@@ -0,0 +1,45 @@
+# Copyright 2013 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.
+
+"""Setup for linker tests."""
+
+import os
+import sys
+
+from pylib import constants
+from pylib.linker import test_case
+from pylib.linker import test_runner
+
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
+ 'common'))
+import unittest_util # pylint: disable=F0401
+
+def Setup(args, _devices):
+ """Creates a list of test cases and a runner factory.
+
+ Args:
+ args: an argparse.Namespace object.
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+ test_cases = [
+ test_case.LinkerLibraryAddressTest,
+ test_case.LinkerSharedRelroTest,
+ test_case.LinkerRandomizationTest]
+
+ low_memory_modes = [False, True]
+ all_tests = [t(is_low_memory=m) for t in test_cases for m in low_memory_modes]
+
+ if args.test_filter:
+ all_test_names = [test.qualified_name for test in all_tests]
+ filtered_test_names = unittest_util.FilterTestNames(all_test_names,
+ args.test_filter)
+ all_tests = [t for t in all_tests \
+ if t.qualified_name in filtered_test_names]
+
+ def TestRunnerFactory(device, _shard_index):
+ return test_runner.LinkerTestRunner(device, args.tool)
+
+ return (TestRunnerFactory, all_tests)
diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
new file mode 100644
index 00000000000..c7b0f50b59c
--- /dev/null
+++ b/build/android/pylib/linker/test_case.py
@@ -0,0 +1,496 @@
+# Copyright 2013 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.
+
+"""Base class for linker-specific test cases.
+
+ The custom dynamic linker can only be tested through a custom test case
+ for various technical reasons:
+
+ - It's an 'invisible feature', i.e. it doesn't expose a new API or
+ behaviour, all it does is save RAM when loading native libraries.
+
+ - Checking that it works correctly requires several things that do not
+ fit the existing GTest-based and instrumentation-based tests:
+
+ - Native test code needs to be run in both the browser and renderer
+ process at the same time just after loading native libraries, in
+ a completely asynchronous way.
+
+ - Each test case requires restarting a whole new application process
+ with a different command-line.
+
+ - Enabling test support in the Linker code requires building a special
+ APK with a flag to activate special test-only support code in the
+ Linker code itself.
+
+ Host-driven tests have also been tried, but since they're really
+ sub-classes of instrumentation tests, they didn't work well either.
+
+ To build and run the linker tests, do the following:
+
+ ninja -C out/Debug chromium_linker_test_apk
+ build/android/test_runner.py linker
+
+"""
+# pylint: disable=R0201
+
+import logging
+import os
+import re
+import time
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.device import device_errors
+from pylib.device import intent
+
+
+ResultType = base_test_result.ResultType
+
+_PACKAGE_NAME = 'org.chromium.chromium_linker_test_apk'
+_ACTIVITY_NAME = '.ChromiumLinkerTestActivity'
+_COMMAND_LINE_FILE = '/data/local/tmp/chromium-linker-test-command-line'
+
+# Path to the Linker.java source file.
+_LINKER_JAVA_SOURCE_PATH = (
+ 'base/android/java/src/org/chromium/base/library_loader/Linker.java')
+
+# A regular expression used to extract the browser shared RELRO configuration
+# from the Java source file above.
+_RE_LINKER_BROWSER_CONFIG = re.compile(
+ r'.*BROWSER_SHARED_RELRO_CONFIG\s+=\s+' +
+ r'BROWSER_SHARED_RELRO_CONFIG_(\S+)\s*;.*',
+ re.MULTILINE | re.DOTALL)
+
+# Logcat filters used during each test. Only the 'chromium' one is really
+# needed, but the logs are added to the TestResult in case of error, and
+# it is handy to have the 'chromium_android_linker' ones as well when
+# troubleshooting.
+_LOGCAT_FILTERS = ['*:s', 'chromium:v', 'chromium_android_linker:v']
+#_LOGCAT_FILTERS = ['*:v'] ## DEBUG
+
+# Regular expression used to match status lines in logcat.
+_RE_BROWSER_STATUS_LINE = re.compile(r' BROWSER_LINKER_TEST: (FAIL|SUCCESS)$')
+_RE_RENDERER_STATUS_LINE = re.compile(r' RENDERER_LINKER_TEST: (FAIL|SUCCESS)$')
+
+# Regular expression used to mach library load addresses in logcat.
+_RE_LIBRARY_ADDRESS = re.compile(
+ r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)')
+
+
+def _GetBrowserSharedRelroConfig():
+ """Returns a string corresponding to the Linker's configuration of shared
+ RELRO sections in the browser process. This parses the Java linker source
+ file to get the appropriate information.
+ Return:
+ None in case of error (e.g. could not locate the source file).
+ 'NEVER' if the browser process shall never use shared RELROs.
+ 'LOW_RAM_ONLY' if if uses it only on low-end devices.
+ 'ALWAYS' if it always uses a shared RELRO.
+ """
+ source_path = \
+ os.path.join(constants.DIR_SOURCE_ROOT, _LINKER_JAVA_SOURCE_PATH)
+ if not os.path.exists(source_path):
+ logging.error('Could not find linker source file: ' + source_path)
+ return None
+
+ with open(source_path) as f:
+ configs = _RE_LINKER_BROWSER_CONFIG.findall(f.read())
+ if not configs:
+ logging.error(
+ 'Can\'t find browser shared RELRO configuration value in ' + \
+ source_path)
+ return None
+
+ if configs[0] not in ['NEVER', 'LOW_RAM_ONLY', 'ALWAYS']:
+ logging.error('Unexpected browser config value: ' + configs[0])
+ return None
+
+ logging.info('Found linker browser shared RELRO config: ' + configs[0])
+ return configs[0]
+
+
+def _StartActivityAndWaitForLinkerTestStatus(device, timeout):
+ """Force-start an activity and wait up to |timeout| seconds until the full
+ linker test status lines appear in the logcat, recorded through |device|.
+ Args:
+ device: A DeviceUtils instance.
+ timeout: Timeout in seconds
+ Returns:
+ A (status, logs) tuple, where status is a ResultType constant, and logs
+ if the final logcat output as a string.
+ """
+
+ # 1. Start recording logcat with appropriate filters.
+ with device.GetLogcatMonitor(filter_specs=_LOGCAT_FILTERS) as logmon:
+
+ # 2. Force-start activity.
+ device.StartActivity(
+ intent.Intent(package=_PACKAGE_NAME, activity=_ACTIVITY_NAME),
+ force_stop=True)
+
+ # 3. Wait up to |timeout| seconds until the test status is in the logcat.
+ result = ResultType.PASS
+ try:
+ browser_match = logmon.WaitFor(_RE_BROWSER_STATUS_LINE, timeout=timeout)
+ logging.debug('Found browser match: %s', browser_match.group(0))
+ renderer_match = logmon.WaitFor(_RE_RENDERER_STATUS_LINE,
+ timeout=timeout)
+ logging.debug('Found renderer match: %s', renderer_match.group(0))
+ if (browser_match.group(1) != 'SUCCESS'
+ or renderer_match.group(1) != 'SUCCESS'):
+ result = ResultType.FAIL
+ except device_errors.CommandTimeoutError:
+ result = ResultType.TIMEOUT
+
+ return result, '\n'.join(device.adb.Logcat(dump=True))
+
+
+class LibraryLoadMap(dict):
+ """A helper class to pretty-print a map of library names to load addresses."""
+ def __str__(self):
+ items = ['\'%s\': 0x%x' % (name, address) for \
+ (name, address) in self.iteritems()]
+ return '{%s}' % (', '.join(items))
+
+ def __repr__(self):
+ return 'LibraryLoadMap(%s)' % self.__str__()
+
+
+class AddressList(list):
+ """A helper class to pretty-print a list of load addresses."""
+ def __str__(self):
+ items = ['0x%x' % address for address in self]
+ return '[%s]' % (', '.join(items))
+
+ def __repr__(self):
+ return 'AddressList(%s)' % self.__str__()
+
+
+def _ExtractLibraryLoadAddressesFromLogcat(logs):
+ """Extract the names and addresses of shared libraries loaded in the
+ browser and renderer processes.
+ Args:
+ logs: A string containing logcat output.
+ Returns:
+ A tuple (browser_libs, renderer_libs), where each item is a map of
+ library names (strings) to library load addresses (ints), for the
+ browser and renderer processes, respectively.
+ """
+ browser_libs = LibraryLoadMap()
+ renderer_libs = LibraryLoadMap()
+ for m in _RE_LIBRARY_ADDRESS.finditer(logs):
+ process_type, lib_name, lib_address = m.groups()
+ lib_address = int(lib_address, 16)
+ if process_type == 'BROWSER':
+ browser_libs[lib_name] = lib_address
+ elif process_type == 'RENDERER':
+ renderer_libs[lib_name] = lib_address
+ else:
+ assert False, 'Invalid process type'
+
+ return browser_libs, renderer_libs
+
+
+def _CheckLoadAddressRandomization(lib_map_list, process_type):
+ """Check that a map of library load addresses is random enough.
+ Args:
+ lib_map_list: a list of dictionaries that map library names (string)
+ to load addresses (int). Each item in the list corresponds to a
+ different run / process start.
+ process_type: a string describing the process type.
+ Returns:
+ (status, logs) tuple, where is True iff the load addresses are
+ randomized, False otherwise, and is a string containing an error
+ message detailing the libraries that are not randomized properly.
+ """
+ # Collect, for each library, its list of load addresses.
+ lib_addr_map = {}
+ for lib_map in lib_map_list:
+ for lib_name, lib_address in lib_map.iteritems():
+ if lib_name not in lib_addr_map:
+ lib_addr_map[lib_name] = AddressList()
+ lib_addr_map[lib_name].append(lib_address)
+
+ logging.info('%s library load map: %s', process_type, lib_addr_map)
+
+ # For each library, check the randomness of its load addresses.
+ bad_libs = {}
+ for lib_name, lib_address_list in lib_addr_map.iteritems():
+ # If all addresses are different, skip to next item.
+ lib_address_set = set(lib_address_list)
+ # Consider that if there is more than one pair of identical addresses in
+ # the list, then randomization is broken.
+ if len(lib_address_set) < len(lib_address_list) - 1:
+ bad_libs[lib_name] = lib_address_list
+
+
+ if bad_libs:
+ return False, '%s libraries failed randomization: %s' % \
+ (process_type, bad_libs)
+
+ return True, '%s libraries properly randomized: %s' % \
+ (process_type, lib_addr_map)
+
+
+class LinkerTestCaseBase(object):
+ """Base class for linker test cases."""
+
+ def __init__(self, is_low_memory=False):
+ """Create a test case.
+ Args:
+ is_low_memory: True to simulate a low-memory device, False otherwise.
+ """
+ self.is_low_memory = is_low_memory
+ if is_low_memory:
+ test_suffix = 'ForLowMemoryDevice'
+ else:
+ test_suffix = 'ForRegularDevice'
+ class_name = self.__class__.__name__
+ self.qualified_name = '%s.%s' % (class_name, test_suffix)
+ self.tagged_name = self.qualified_name
+
+ def _RunTest(self, _device):
+ """Run the test, must be overriden.
+ Args:
+ _device: A DeviceUtils interface.
+ Returns:
+ A (status, log) tuple, where is a ResultType constant, and
+ is the logcat output captured during the test in case of error, or None
+ in case of success.
+ """
+ return ResultType.FAIL, 'Unimplemented _RunTest() method!'
+
+ def Run(self, device):
+ """Run the test on a given device.
+ Args:
+ device: Name of target device where to run the test.
+ Returns:
+ A base_test_result.TestRunResult() instance.
+ """
+ margin = 8
+ print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name)
+ logging.info('Running linker test: %s', self.tagged_name)
+
+ # Create command-line file on device.
+ command_line_flags = ''
+ if self.is_low_memory:
+ command_line_flags = '--low-memory-device'
+ device.WriteFile(_COMMAND_LINE_FILE, command_line_flags)
+
+ # Run the test.
+ status, logs = self._RunTest(device)
+
+ result_text = 'OK'
+ if status == ResultType.FAIL:
+ result_text = 'FAILED'
+ elif status == ResultType.TIMEOUT:
+ result_text = 'TIMEOUT'
+ print '[ %*s ] %s' % (margin, result_text, self.tagged_name)
+
+ results = base_test_result.TestRunResults()
+ results.AddResult(
+ base_test_result.BaseTestResult(
+ self.tagged_name,
+ status,
+ log=logs))
+
+ return results
+
+ def __str__(self):
+ return self.tagged_name
+
+ def __repr__(self):
+ return self.tagged_name
+
+
+class LinkerSharedRelroTest(LinkerTestCaseBase):
+ """A linker test case to check the status of shared RELRO sections.
+
+ The core of the checks performed here are pretty simple:
+
+ - Clear the logcat and start recording with an appropriate set of filters.
+ - Create the command-line appropriate for the test-case.
+ - Start the activity (always forcing a cold start).
+ - Every second, look at the current content of the filtered logcat lines
+ and look for instances of the following:
+
+ BROWSER_LINKER_TEST:
+ RENDERER_LINKER_TEST:
+
+ where can be either FAIL or SUCCESS. These lines can appear
+ in any order in the logcat. Once both browser and renderer status are
+ found, stop the loop. Otherwise timeout after 30 seconds.
+
+ Note that there can be other lines beginning with BROWSER_LINKER_TEST:
+ and RENDERER_LINKER_TEST:, but are not followed by a code.
+
+ - The test case passes if the for both the browser and renderer
+ process are SUCCESS. Otherwise its a fail.
+ """
+ def _RunTest(self, device):
+ # Wait up to 30 seconds until the linker test status is in the logcat.
+ return _StartActivityAndWaitForLinkerTestStatus(device, timeout=30)
+
+
+class LinkerLibraryAddressTest(LinkerTestCaseBase):
+ """A test case that verifies library load addresses.
+
+ The point of this check is to ensure that the libraries are loaded
+ according to the following rules:
+
+ - For low-memory devices, they should always be loaded at the same address
+ in both browser and renderer processes, both below 0x4000_0000.
+
+ - For regular devices, the browser process should load libraries above
+ 0x4000_0000, and renderer ones below it.
+ """
+ def _RunTest(self, device):
+ result, logs = _StartActivityAndWaitForLinkerTestStatus(device, timeout=30)
+
+ # Return immediately in case of timeout.
+ if result == ResultType.TIMEOUT:
+ return result, logs
+
+ # Collect the library load addresses in the browser and renderer processes.
+ browser_libs, renderer_libs = _ExtractLibraryLoadAddressesFromLogcat(logs)
+
+ logging.info('Browser libraries: %s', browser_libs)
+ logging.info('Renderer libraries: %s', renderer_libs)
+
+ # Check that the same libraries are loaded into both processes:
+ browser_set = set(browser_libs.keys())
+ renderer_set = set(renderer_libs.keys())
+ if browser_set != renderer_set:
+ logging.error('Library set mistmach browser=%s renderer=%s',
+ browser_libs.keys(), renderer_libs.keys())
+ return ResultType.FAIL, logs
+
+ # And that there are not empty.
+ if not browser_set:
+ logging.error('No libraries loaded in any process!')
+ return ResultType.FAIL, logs
+
+ # Check that the renderer libraries are loaded at 'low-addresses'. i.e.
+ # below 0x4000_0000, for every kind of device.
+ memory_boundary = 0x40000000
+ bad_libs = []
+ for lib_name, lib_address in renderer_libs.iteritems():
+ if lib_address >= memory_boundary:
+ bad_libs.append((lib_name, lib_address))
+
+ if bad_libs:
+ logging.error('Renderer libraries loaded at high addresses: %s', bad_libs)
+ return ResultType.FAIL, logs
+
+ browser_config = _GetBrowserSharedRelroConfig()
+ if not browser_config:
+ return ResultType.FAIL, 'Bad linker source configuration'
+
+ if browser_config == 'ALWAYS' or \
+ (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory):
+ # The libraries must all be loaded at the same addresses. This also
+ # implicitly checks that the browser libraries are at low addresses.
+ addr_mismatches = []
+ for lib_name, lib_address in browser_libs.iteritems():
+ lib_address2 = renderer_libs[lib_name]
+ if lib_address != lib_address2:
+ addr_mismatches.append((lib_name, lib_address, lib_address2))
+
+ if addr_mismatches:
+ logging.error('Library load address mismatches: %s',
+ addr_mismatches)
+ return ResultType.FAIL, logs
+
+ # Otherwise, check that libraries are loaded at 'high-addresses'.
+ # Note that for low-memory devices, the previous checks ensure that they
+ # were loaded at low-addresses.
+ else:
+ bad_libs = []
+ for lib_name, lib_address in browser_libs.iteritems():
+ if lib_address < memory_boundary:
+ bad_libs.append((lib_name, lib_address))
+
+ if bad_libs:
+ logging.error('Browser libraries loaded at low addresses: %s', bad_libs)
+ return ResultType.FAIL, logs
+
+ # Everything's ok.
+ return ResultType.PASS, logs
+
+
+class LinkerRandomizationTest(LinkerTestCaseBase):
+ """A linker test case to check that library load address randomization works
+ properly between successive starts of the test program/activity.
+
+ This starts the activity several time (each time forcing a new process
+ creation) and compares the load addresses of the libraries in them to
+ detect that they have changed.
+
+ In theory, two successive runs could (very rarely) use the same load
+ address, so loop 5 times and compare the values there. It is assumed
+ that if there are more than one pair of identical addresses, then the
+ load addresses are not random enough for this test.
+ """
+ def _RunTest(self, device):
+ max_loops = 5
+ browser_lib_map_list = []
+ renderer_lib_map_list = []
+ logs_list = []
+ for _ in range(max_loops):
+ # Start the activity.
+ result, logs = _StartActivityAndWaitForLinkerTestStatus(
+ device, timeout=30)
+ if result == ResultType.TIMEOUT:
+ # Something bad happened. Return immediately.
+ return result, logs
+
+ # Collect library addresses.
+ browser_libs, renderer_libs = _ExtractLibraryLoadAddressesFromLogcat(logs)
+ browser_lib_map_list.append(browser_libs)
+ renderer_lib_map_list.append(renderer_libs)
+ logs_list.append(logs)
+
+ # Check randomization in the browser libraries.
+ logs = '\n'.join(logs_list)
+
+ browser_status, browser_logs = _CheckLoadAddressRandomization(
+ browser_lib_map_list, 'Browser')
+
+ renderer_status, renderer_logs = _CheckLoadAddressRandomization(
+ renderer_lib_map_list, 'Renderer')
+
+ browser_config = _GetBrowserSharedRelroConfig()
+ if not browser_config:
+ return ResultType.FAIL, 'Bad linker source configuration'
+
+ if not browser_status:
+ if browser_config == 'ALWAYS' or \
+ (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory):
+ return ResultType.FAIL, browser_logs
+
+ # IMPORTANT NOTE: The system's ASLR implementation seems to be very poor
+ # when starting an activity process in a loop with "adb shell am start".
+ #
+ # When simulating a regular device, loading libraries in the browser
+ # process uses a simple mmap(NULL, ...) to let the kernel device where to
+ # load the file (this is similar to what System.loadLibrary() does).
+ #
+ # Unfortunately, at least in the context of this test, doing so while
+ # restarting the activity with the activity manager very, very, often
+ # results in the system using the same load address for all 5 runs, or
+ # sometimes only 4 out of 5.
+ #
+ # This has been tested experimentally on both Android 4.1.2 and 4.3.
+ #
+ # Note that this behaviour doesn't seem to happen when starting an
+ # application 'normally', i.e. when using the application launcher to
+ # start the activity.
+ logging.info('Ignoring system\'s low randomization of browser libraries' +
+ ' for regular devices')
+
+ if not renderer_status:
+ return ResultType.FAIL, renderer_logs
+
+ return ResultType.PASS, logs
diff --git a/build/android/pylib/linker/test_runner.py b/build/android/pylib/linker/test_runner.py
new file mode 100644
index 00000000000..b6803e45d9d
--- /dev/null
+++ b/build/android/pylib/linker/test_runner.py
@@ -0,0 +1,98 @@
+# Copyright 2013 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.
+
+"""Runs linker tests on a particular device."""
+
+import logging
+import os.path
+import sys
+import traceback
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.linker import test_case
+from pylib.utils import apk_helper
+
+
+# Name of the Android package to install for this to work.
+_PACKAGE_NAME = 'ChromiumLinkerTest'
+
+
+class LinkerExceptionTestResult(base_test_result.BaseTestResult):
+ """Test result corresponding to a python exception in a host-custom test."""
+
+ def __init__(self, test_name, exc_info):
+ """Constructs a LinkerExceptionTestResult object.
+
+ Args:
+ test_name: name of the test which raised an exception.
+ exc_info: exception info, ostensibly from sys.exc_info().
+ """
+ exc_type, exc_value, exc_traceback = exc_info
+ trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
+ exc_traceback))
+ log_msg = 'Exception:\n' + trace_info
+
+ super(LinkerExceptionTestResult, self).__init__(
+ test_name,
+ base_test_result.ResultType.FAIL,
+ log="%s %s" % (exc_type, log_msg))
+
+
+class LinkerTestRunner(base_test_runner.BaseTestRunner):
+ """Orchestrates running a set of linker tests.
+
+ Any Python exceptions in the tests are caught and translated into a failed
+ result, rather than being re-raised on the main thread.
+ """
+
+ #override
+ def __init__(self, device, tool):
+ """Creates a new LinkerTestRunner.
+
+ Args:
+ device: Attached android device.
+ tool: Name of the Valgrind tool.
+ """
+ super(LinkerTestRunner, self).__init__(device, tool)
+
+ #override
+ def InstallTestPackage(self):
+ apk_path = os.path.join(
+ constants.GetOutDirectory(), 'apks', '%s.apk' % _PACKAGE_NAME)
+
+ if not os.path.exists(apk_path):
+ raise Exception('%s not found, please build it' % apk_path)
+
+ self.device.Install(apk_path)
+
+ #override
+ def RunTest(self, test):
+ """Sets up and runs a test case.
+
+ Args:
+ test: An object which is ostensibly a subclass of LinkerTestCaseBase.
+
+ Returns:
+ A TestRunResults object which contains the result produced by the test
+ and, in the case of a failure, the test that should be retried.
+ """
+
+ assert isinstance(test, test_case.LinkerTestCaseBase)
+
+ try:
+ results = test.Run(self.device)
+ except Exception:
+ logging.exception('Caught exception while trying to run test: ' +
+ test.tagged_name)
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(LinkerExceptionTestResult(
+ test.tagged_name, exc_info))
+
+ if not results.DidRunPass():
+ return results, test
+ else:
+ return results, None
diff --git a/build/android/pylib/local/__init__.py b/build/android/pylib/local/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/local/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/local/device/__init__.py b/build/android/pylib/local/device/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/local/device/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py
new file mode 100644
index 00000000000..04f9ab7d2fe
--- /dev/null
+++ b/build/android/pylib/local/device/local_device_environment.py
@@ -0,0 +1,54 @@
+# Copyright 2014 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.
+
+from pylib.base import environment
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import parallelizer
+
+
+class LocalDeviceEnvironment(environment.Environment):
+
+ def __init__(self, args, _error_func):
+ super(LocalDeviceEnvironment, self).__init__()
+ self._device_serial = args.test_device
+ self._devices = []
+ self._max_tries = 1 + args.num_retries
+ self._tool_name = args.tool
+
+ #override
+ def SetUp(self):
+ available_devices = device_utils.DeviceUtils.HealthyDevices()
+ if not available_devices:
+ raise device_errors.NoDevicesError
+ if self._device_serial:
+ self._devices = [d for d in available_devices
+ if d.adb.GetDeviceSerial() == self._device_serial]
+ if not self._devices:
+ raise device_errors.DeviceUnreachableError(
+ 'Could not find device %r' % self._device_serial)
+ else:
+ self._devices = available_devices
+
+ @property
+ def devices(self):
+ return self._devices
+
+ @property
+ def parallel_devices(self):
+ return parallelizer.SyncParallelizer(self._devices)
+
+ @property
+ def max_tries(self):
+ return self._max_tries
+
+ @property
+ def tool(self):
+ return self._tool_name
+
+ #override
+ def TearDown(self):
+ pass
+
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
new file mode 100644
index 00000000000..e388fce9efc
--- /dev/null
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -0,0 +1,207 @@
+# Copyright 2015 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.
+
+import logging
+import re
+import time
+
+from pylib import flag_changer
+from pylib.base import base_test_result
+from pylib.base import test_run
+from pylib.constants import keyevent
+from pylib.device import device_errors
+from pylib.local.device import local_device_test_run
+
+
+TIMEOUT_ANNOTATIONS = [
+ ('Manual', 10 * 60 * 60),
+ ('IntegrationTest', 30 * 60),
+ ('External', 10 * 60),
+ ('EnormousTest', 10 * 60),
+ ('LargeTest', 5 * 60),
+ ('MediumTest', 3 * 60),
+ ('SmallTest', 1 * 60),
+]
+
+
+# TODO(jbudorick): Make this private once the instrumentation test_runner is
+# deprecated.
+def DidPackageCrashOnDevice(package_name, device):
+ # Dismiss any error dialogs. Limit the number in case we have an error
+ # loop or we are failing to dismiss.
+ try:
+ for _ in xrange(10):
+ package = _DismissCrashDialog(device)
+ if not package:
+ return False
+ # Assume test package convention of ".test" suffix
+ if package in package_name:
+ return True
+ except device_errors.CommandFailedError:
+ logging.exception('Error while attempting to dismiss crash dialog.')
+ return False
+
+
+_CURRENT_FOCUS_CRASH_RE = re.compile(
+ r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
+
+
+def _DismissCrashDialog(device):
+ # TODO(jbudorick): Try to grep the output on the device instead of using
+ # large_output if/when DeviceUtils exposes a public interface for piped
+ # shell command handling.
+ for l in device.RunShellCommand(
+ ['dumpsys', 'window', 'windows'], check_return=True, large_output=True):
+ m = re.match(_CURRENT_FOCUS_CRASH_RE, l)
+ if m:
+ device.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
+ device.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
+ device.SendKeyEvent(keyevent.KEYCODE_ENTER)
+ return m.group(2)
+
+ return None
+
+
+class LocalDeviceInstrumentationTestRun(
+ local_device_test_run.LocalDeviceTestRun):
+ def __init__(self, env, test_instance):
+ super(LocalDeviceInstrumentationTestRun, self).__init__(env, test_instance)
+ self._flag_changers = {}
+
+ def TestPackage(self):
+ return None
+
+ def SetUp(self):
+ def substitute_external_storage(d, external_storage):
+ if not d:
+ return external_storage
+ elif isinstance(d, list):
+ return '/'.join(p if p else external_storage for p in d)
+ else:
+ return d
+
+ def individual_device_set_up(dev, host_device_tuples):
+ dev.Install(self._test_instance.apk_under_test)
+ dev.Install(self._test_instance.test_apk)
+
+ external_storage = dev.GetExternalStoragePath()
+ host_device_tuples = [
+ (h, substitute_external_storage(d, external_storage))
+ for h, d in host_device_tuples]
+ logging.info('instrumentation data deps:')
+ for h, d in host_device_tuples:
+ logging.info('%r -> %r', h, d)
+ dev.PushChangedFiles(host_device_tuples)
+ if self._test_instance.flags:
+ if not self._test_instance.package_info:
+ logging.error("Couldn't set flags: no package info")
+ elif not self._test_instance.package_info.cmdline_file:
+ logging.error("Couldn't set flags: no cmdline_file")
+ else:
+ self._flag_changers[str(dev)] = flag_changer.FlagChanger(
+ dev, self._test_instance.package_info.cmdline_file)
+ logging.debug('Attempting to set flags: %r',
+ self._test_instance.flags)
+ self._flag_changers[str(dev)].AddFlags(self._test_instance.flags)
+
+ self._env.parallel_devices.pMap(
+ individual_device_set_up,
+ self._test_instance.GetDataDependencies())
+
+ def TearDown(self):
+ def individual_device_tear_down(dev):
+ if str(dev) in self._flag_changers:
+ self._flag_changers[str(dev)].Restore()
+
+ self._env.parallel_devices.pMap(individual_device_tear_down)
+
+ #override
+ def _CreateShards(self, tests):
+ return tests
+
+ #override
+ def _GetTests(self):
+ return self._test_instance.GetTests()
+
+ #override
+ def _GetTestName(self, test):
+ return '%s#%s' % (test['class'], test['method'])
+
+ #override
+ def _RunTest(self, device, test):
+ extras = self._test_instance.GetHttpServerEnvironmentVars()
+
+ if isinstance(test, list):
+ if not self._test_instance.driver_apk:
+ raise Exception('driver_apk does not exist. '
+ 'Please build it and try again.')
+
+ def name_and_timeout(t):
+ n = self._GetTestName(t)
+ i = self._GetTimeoutFromAnnotations(t['annotations'], n)
+ return (n, i)
+
+ test_names, timeouts = zip(*(name_and_timeout(t) for t in test))
+
+ test_name = ','.join(test_names)
+ target = '%s/%s' % (
+ self._test_instance.driver_package,
+ self._test_instance.driver_name)
+ extras.update(
+ self._test_instance.GetDriverEnvironmentVars(
+ test_list=test_names))
+ timeout = sum(timeouts)
+ else:
+ test_name = self._GetTestName(test)
+ target = '%s/%s' % (
+ self._test_instance.test_package, self._test_instance.test_runner)
+ extras['class'] = test_name
+ timeout = self._GetTimeoutFromAnnotations(test['annotations'], test_name)
+
+ logging.info('preparing to run %s: %s' % (test_name, test))
+
+ time_ms = lambda: int(time.time() * 1e3)
+ start_ms = time_ms()
+ output = device.StartInstrumentation(
+ target, raw=True, extras=extras, timeout=timeout, retries=0)
+ duration_ms = time_ms() - start_ms
+
+ # TODO(jbudorick): Make instrumentation tests output a JSON so this
+ # doesn't have to parse the output.
+ logging.debug('output from %s:', test_name)
+ for l in output:
+ logging.debug(' %s', l)
+
+ result_code, result_bundle, statuses = (
+ self._test_instance.ParseAmInstrumentRawOutput(output))
+ results = self._test_instance.GenerateTestResults(
+ result_code, result_bundle, statuses, start_ms, duration_ms)
+ if DidPackageCrashOnDevice(self._test_instance.test_package, device):
+ for r in results:
+ if r.GetType() == base_test_result.ResultType.UNKNOWN:
+ r.SetType(base_test_result.ResultType.CRASH)
+ return results
+
+ #override
+ def _ShouldShard(self):
+ return True
+
+ @staticmethod
+ def _GetTimeoutFromAnnotations(annotations, test_name):
+ for k, v in TIMEOUT_ANNOTATIONS:
+ if k in annotations:
+ timeout = v
+ else:
+ logging.warning('Using default 1 minute timeout for %s' % test_name)
+ timeout = 60
+
+ try:
+ scale = int(annotations.get('TimeoutScale', 1))
+ except ValueError as e:
+ logging.warning("Non-integer value of TimeoutScale ignored. (%s)", str(e))
+ scale = 1
+ timeout *= scale
+
+ return timeout
+
diff --git a/build/android/pylib/local/device/local_device_test_run.py b/build/android/pylib/local/device/local_device_test_run.py
new file mode 100644
index 00000000000..fa24eb10e26
--- /dev/null
+++ b/build/android/pylib/local/device/local_device_test_run.py
@@ -0,0 +1,99 @@
+# Copyright 2014 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.
+
+import logging
+
+from pylib import valgrind_tools
+from pylib.base import base_test_result
+from pylib.base import test_run
+from pylib.base import test_collection
+
+
+class LocalDeviceTestRun(test_run.TestRun):
+
+ def __init__(self, env, test_instance):
+ super(LocalDeviceTestRun, self).__init__(env, test_instance)
+ self._tools = {}
+
+ #override
+ def RunTests(self):
+ tests = self._GetTests()
+
+ def run_tests_on_device(dev, tests):
+ r = base_test_result.TestRunResults()
+ for test in tests:
+ result = self._RunTest(dev, test)
+ if isinstance(result, base_test_result.BaseTestResult):
+ r.AddResult(result)
+ elif isinstance(result, list):
+ r.AddResults(result)
+ else:
+ raise Exception('Unexpected result type: %s' % type(result).__name__)
+ if isinstance(tests, test_collection.TestCollection):
+ tests.test_completed()
+ return r
+
+ tries = 0
+ results = base_test_result.TestRunResults()
+ all_fail_results = {}
+ while tries < self._env.max_tries and tests:
+ logging.debug('try %d, will run %d tests:', tries, len(tests))
+ for t in tests:
+ logging.debug(' %s', t)
+
+ if self._ShouldShard():
+ tc = test_collection.TestCollection(self._CreateShards(tests))
+ try_results = self._env.parallel_devices.pMap(
+ run_tests_on_device, tc).pGet(None)
+ else:
+ try_results = self._env.parallel_devices.pMap(
+ run_tests_on_device, tests).pGet(None)
+ for try_result in try_results:
+ for result in try_result.GetAll():
+ if result.GetType() in (base_test_result.ResultType.PASS,
+ base_test_result.ResultType.SKIP):
+ results.AddResult(result)
+ else:
+ all_fail_results[result.GetName()] = result
+
+ results_names = set(r.GetName() for r in results.GetAll())
+ tests = [t for t in tests if self._GetTestName(t) not in results_names]
+ tries += 1
+
+ all_unknown_test_names = set(self._GetTestName(t) for t in tests)
+ all_failed_test_names = set(all_fail_results.iterkeys())
+
+ unknown_tests = all_unknown_test_names.difference(all_failed_test_names)
+ failed_tests = all_failed_test_names.intersection(all_unknown_test_names)
+
+ if unknown_tests:
+ results.AddResults(
+ base_test_result.BaseTestResult(
+ u, base_test_result.ResultType.UNKNOWN)
+ for u in unknown_tests)
+ if failed_tests:
+ results.AddResults(all_fail_results[f] for f in failed_tests)
+
+ return results
+
+ def GetTool(self, device):
+ if not str(device) in self._tools:
+ self._tools[str(device)] = valgrind_tools.CreateTool(
+ self._env.tool, device)
+ return self._tools[str(device)]
+
+ def _CreateShards(self, tests):
+ raise NotImplementedError
+
+ def _GetTestName(self, test):
+ return test
+
+ def _GetTests(self):
+ raise NotImplementedError
+
+ def _RunTest(self, device, test):
+ raise NotImplementedError
+
+ def _ShouldShard(self):
+ raise NotImplementedError
diff --git a/build/android/pylib/local/local_test_server_spawner.py b/build/android/pylib/local/local_test_server_spawner.py
new file mode 100644
index 00000000000..77f552e8d5c
--- /dev/null
+++ b/build/android/pylib/local/local_test_server_spawner.py
@@ -0,0 +1,45 @@
+# Copyright 2014 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.
+
+from pylib import chrome_test_server_spawner
+from pylib import forwarder
+from pylib.base import test_server
+
+
+class LocalTestServerSpawner(test_server.TestServer):
+
+ def __init__(self, port, device, tool):
+ super(LocalTestServerSpawner, self).__init__()
+ self._device = device
+ self._spawning_server = chrome_test_server_spawner.SpawningServer(
+ port, device, tool)
+ self._tool = tool
+
+ @property
+ def server_address(self):
+ return self._spawning_server.server.server_address
+
+ @property
+ def port(self):
+ return self.server_address[1]
+
+ #override
+ def SetUp(self):
+ self._device.WriteFile(
+ '%s/net-test-server-ports' % self._device.GetExternalStoragePath(),
+ '%s:0' % str(self.port))
+ forwarder.Forwarder.Map(
+ [(self.port, self.port)], self._device, self._tool)
+ self._spawning_server.Start()
+
+ #override
+ def Reset(self):
+ self._spawning_server.CleanupState()
+
+ #override
+ def TearDown(self):
+ self.Reset()
+ self._spawning_server.Stop()
+ forwarder.Forwarder.UnmapDevicePort(self.port, self._device)
+
diff --git a/build/android/pylib/monkey/__init__.py b/build/android/pylib/monkey/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/build/android/pylib/monkey/setup.py b/build/android/pylib/monkey/setup.py
new file mode 100644
index 00000000000..fe690a505e9
--- /dev/null
+++ b/build/android/pylib/monkey/setup.py
@@ -0,0 +1,27 @@
+# Copyright 2013 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.
+
+"""Generates test runner factory and tests for monkey tests."""
+
+from pylib.monkey import test_runner
+
+
+def Setup(test_options):
+ """Create and return the test runner factory and tests.
+
+ Args:
+ test_options: A MonkeyOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+ # Token to replicate across devices as the "test". The TestRunner does all of
+ # the work to run the test.
+ tests = ['MonkeyTest']
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.TestRunner(
+ test_options, device, shard_index)
+
+ return (TestRunnerFactory, tests)
diff --git a/build/android/pylib/monkey/test_options.py b/build/android/pylib/monkey/test_options.py
new file mode 100644
index 00000000000..54d3d0840e1
--- /dev/null
+++ b/build/android/pylib/monkey/test_options.py
@@ -0,0 +1,16 @@
+# Copyright 2013 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.
+
+"""Defines the MonkeyOptions named tuple."""
+
+import collections
+
+MonkeyOptions = collections.namedtuple('MonkeyOptions', [
+ 'verbose_count',
+ 'package',
+ 'event_count',
+ 'category',
+ 'throttle',
+ 'seed',
+ 'extra_args'])
diff --git a/build/android/pylib/monkey/test_runner.py b/build/android/pylib/monkey/test_runner.py
new file mode 100644
index 00000000000..3fd1797494d
--- /dev/null
+++ b/build/android/pylib/monkey/test_runner.py
@@ -0,0 +1,106 @@
+# Copyright 2013 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.
+
+"""Runs a monkey test on a single device."""
+
+import logging
+import random
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.device import device_errors
+from pylib.device import intent
+
+_CHROME_PACKAGE = constants.PACKAGE_INFO['chrome'].package
+
+class TestRunner(base_test_runner.BaseTestRunner):
+ """A TestRunner instance runs a monkey test on a single device."""
+
+ def __init__(self, test_options, device, _):
+ super(TestRunner, self).__init__(device, None)
+ self._options = test_options
+ self._package = constants.PACKAGE_INFO[self._options.package].package
+ self._activity = constants.PACKAGE_INFO[self._options.package].activity
+
+ def _LaunchMonkeyTest(self):
+ """Runs monkey test for a given package.
+
+ Returns:
+ Output from the monkey command on the device.
+ """
+
+ timeout_ms = self._options.event_count * self._options.throttle * 1.5
+
+ cmd = ['monkey',
+ '-p %s' % self._package,
+ ' '.join(['-c %s' % c for c in self._options.category]),
+ '--throttle %d' % self._options.throttle,
+ '-s %d' % (self._options.seed or random.randint(1, 100)),
+ '-v ' * self._options.verbose_count,
+ '--monitor-native-crashes',
+ '--kill-process-after-error',
+ self._options.extra_args,
+ '%d' % self._options.event_count]
+ return self.device.RunShellCommand(' '.join(cmd), timeout=timeout_ms)
+
+ def RunTest(self, test_name):
+ """Run a Monkey test on the device.
+
+ Args:
+ test_name: String to use for logging the test result.
+
+ Returns:
+ A tuple of (TestRunResults, retry).
+ """
+ self.device.StartActivity(
+ intent.Intent(package=self._package, activity=self._activity,
+ action='android.intent.action.MAIN'),
+ blocking=True, force_stop=True)
+
+ # Chrome crashes are not always caught by Monkey test runner.
+ # Verify Chrome has the same PID before and after the test.
+ before_pids = self.device.GetPids(self._package)
+
+ # Run the test.
+ output = ''
+ if before_pids:
+ output = '\n'.join(self._LaunchMonkeyTest())
+ after_pids = self.device.GetPids(self._package)
+
+ crashed = True
+ if not self._package in before_pids:
+ logging.error('Failed to start the process.')
+ elif not self._package in after_pids:
+ logging.error('Process %s has died.', before_pids[self._package])
+ elif before_pids[self._package] != after_pids[self._package]:
+ logging.error('Detected process restart %s -> %s',
+ before_pids[self._package], after_pids[self._package])
+ else:
+ crashed = False
+
+ results = base_test_result.TestRunResults()
+ success_pattern = 'Events injected: %d' % self._options.event_count
+ if success_pattern in output and not crashed:
+ result = base_test_result.BaseTestResult(
+ test_name, base_test_result.ResultType.PASS, log=output)
+ else:
+ result = base_test_result.BaseTestResult(
+ test_name, base_test_result.ResultType.FAIL, log=output)
+ if 'chrome' in self._options.package:
+ logging.warning('Starting MinidumpUploadService...')
+ # TODO(jbudorick): Update this after upstreaming.
+ minidump_intent = intent.Intent(
+ action='%s.crash.ACTION_FIND_ALL' % _CHROME_PACKAGE,
+ package=self._package,
+ activity='%s.crash.MinidumpUploadService' % _CHROME_PACKAGE)
+ try:
+ self.device.RunShellCommand(
+ ['am', 'startservice'] + minidump_intent.am_args,
+ as_root=True, check_return=True)
+ except device_errors.CommandFailedError:
+ logging.exception('Failed to start MinidumpUploadService')
+
+ results.AddResult(result)
+ return results, False
diff --git a/build/android/pylib/perf/__init__.py b/build/android/pylib/perf/__init__.py
new file mode 100644
index 00000000000..9228df89b0e
--- /dev/null
+++ b/build/android/pylib/perf/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2013 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.
diff --git a/build/android/pylib/perf/cache_control.py b/build/android/pylib/perf/cache_control.py
new file mode 100644
index 00000000000..8065cf9ad79
--- /dev/null
+++ b/build/android/pylib/perf/cache_control.py
@@ -0,0 +1,21 @@
+# Copyright 2013 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.
+
+from pylib import android_commands
+from pylib.device import device_utils
+
+class CacheControl(object):
+ _DROP_CACHES = '/proc/sys/vm/drop_caches'
+
+ def __init__(self, device):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, android_commands.AndroidCommands):
+ device = device_utils.DeviceUtils(device)
+ self._device = device
+
+ def DropRamCaches(self):
+ """Drops the filesystem ram caches for performance testing."""
+ self._device.RunShellCommand('sync', as_root=True)
+ self._device.WriteFile(CacheControl._DROP_CACHES, '3', as_root=True)
+
diff --git a/build/android/pylib/perf/perf_control.py b/build/android/pylib/perf/perf_control.py
new file mode 100644
index 00000000000..f89f397d24e
--- /dev/null
+++ b/build/android/pylib/perf/perf_control.py
@@ -0,0 +1,161 @@
+# Copyright 2013 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.
+
+import atexit
+import logging
+
+from pylib import android_commands
+from pylib.device import device_errors
+from pylib.device import device_utils
+
+
+class PerfControl(object):
+ """Provides methods for setting the performance mode of a device."""
+ _CPU_PATH = '/sys/devices/system/cpu'
+ _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
+
+ def __init__(self, device):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, android_commands.AndroidCommands):
+ device = device_utils.DeviceUtils(device)
+ self._device = device
+ # this will raise an AdbCommandFailedError if no CPU files are found
+ self._cpu_files = self._device.RunShellCommand(
+ 'ls -d cpu[0-9]*', cwd=self._CPU_PATH, check_return=True, as_root=True)
+ assert self._cpu_files, 'Failed to detect CPUs.'
+ self._cpu_file_list = ' '.join(self._cpu_files)
+ logging.info('CPUs found: %s', self._cpu_file_list)
+ self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
+
+ def SetHighPerfMode(self):
+ """Sets the highest stable performance mode for the device."""
+ try:
+ self._device.EnableRoot()
+ except device_errors.CommandFailedError:
+ message = 'Need root for performance mode. Results may be NOISY!!'
+ logging.warning(message)
+ # Add an additional warning at exit, such that it's clear that any results
+ # may be different/noisy (due to the lack of intended performance mode).
+ atexit.register(logging.warning, message)
+ return
+
+ product_model = self._device.product_model
+ # TODO(epenner): Enable on all devices (http://crbug.com/383566)
+ if 'Nexus 4' == product_model:
+ self._ForceAllCpusOnline(True)
+ if not self._AllCpusAreOnline():
+ logging.warning('Failed to force CPUs online. Results may be NOISY!')
+ self._SetScalingGovernorInternal('performance')
+ elif 'Nexus 5' == product_model:
+ self._ForceAllCpusOnline(True)
+ if not self._AllCpusAreOnline():
+ logging.warning('Failed to force CPUs online. Results may be NOISY!')
+ self._SetScalingGovernorInternal('performance')
+ self._SetScalingMaxFreq(1190400)
+ self._SetMaxGpuClock(200000000)
+ else:
+ self._SetScalingGovernorInternal('performance')
+
+ def SetPerfProfilingMode(self):
+ """Enables all cores for reliable perf profiling."""
+ self._ForceAllCpusOnline(True)
+ self._SetScalingGovernorInternal('performance')
+ if not self._AllCpusAreOnline():
+ if not self._device.HasRoot():
+ raise RuntimeError('Need root to force CPUs online.')
+ raise RuntimeError('Failed to force CPUs online.')
+
+ def SetDefaultPerfMode(self):
+ """Sets the performance mode for the device to its default mode."""
+ if not self._device.HasRoot():
+ return
+ product_model = self._device.product_model
+ if 'Nexus 5' == product_model:
+ if self._AllCpusAreOnline():
+ self._SetScalingMaxFreq(2265600)
+ self._SetMaxGpuClock(450000000)
+
+ governor_mode = {
+ 'GT-I9300': 'pegasusq',
+ 'Galaxy Nexus': 'interactive',
+ 'Nexus 4': 'ondemand',
+ 'Nexus 5': 'ondemand',
+ 'Nexus 7': 'interactive',
+ 'Nexus 10': 'interactive'
+ }.get(product_model, 'ondemand')
+ self._SetScalingGovernorInternal(governor_mode)
+ self._ForceAllCpusOnline(False)
+
+ def GetCpuInfo(self):
+ online = (output.rstrip() == '1' and status == 0
+ for (_, output, status) in self._ForEachCpu('cat "$CPU/online"'))
+ governor = (output.rstrip() if status == 0 else None
+ for (_, output, status)
+ in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"'))
+ return zip(self._cpu_files, online, governor)
+
+ def _ForEachCpu(self, cmd):
+ script = '; '.join([
+ 'for CPU in %s' % self._cpu_file_list,
+ 'do %s' % cmd,
+ 'echo -n "%~%$?%~%"',
+ 'done'
+ ])
+ output = self._device.RunShellCommand(
+ script, cwd=self._CPU_PATH, check_return=True, as_root=True)
+ output = '\n'.join(output).split('%~%')
+ return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2]))
+
+ def _WriteEachCpuFile(self, path, value):
+ results = self._ForEachCpu(
+ 'test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"'.format(
+ path=path, value=value))
+ cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0)
+ if cpus:
+ logging.info('Successfully set %s to %r on: %s', path, value, cpus)
+ else:
+ logging.warning('Failed to set %s to %r on any cpus')
+
+ def _SetScalingGovernorInternal(self, value):
+ self._WriteEachCpuFile('cpufreq/scaling_governor', value)
+
+ def _SetScalingMaxFreq(self, value):
+ self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value)
+
+ def _SetMaxGpuClock(self, value):
+ self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
+ str(value),
+ as_root=True)
+
+ def _AllCpusAreOnline(self):
+ results = self._ForEachCpu('cat "$CPU/online"')
+ # TODO(epenner): Investigate why file may be missing
+ # (http://crbug.com/397118)
+ return all(output.rstrip() == '1' and status == 0
+ for (cpu, output, status) in results
+ if cpu != 'cpu0')
+
+ def _ForceAllCpusOnline(self, force_online):
+ """Enable all CPUs on a device.
+
+ Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
+ to measurements:
+ - In perf, samples are only taken for the CPUs that are online when the
+ measurement is started.
+ - The scaling governor can't be set for an offline CPU and frequency scaling
+ on newly enabled CPUs adds noise to both perf and tracing measurements.
+
+ It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
+ this is done by "mpdecision".
+
+ """
+ if self._have_mpdecision:
+ script = 'stop mpdecision' if force_online else 'start mpdecision'
+ self._device.RunShellCommand(script, check_return=True, as_root=True)
+
+ if not self._have_mpdecision and not self._AllCpusAreOnline():
+ logging.warning('Unexpected cpu hot plugging detected.')
+
+ if force_online:
+ self._ForEachCpu('echo 1 > "$CPU/online"')
diff --git a/build/android/pylib/perf/perf_control_unittest.py b/build/android/pylib/perf/perf_control_unittest.py
new file mode 100644
index 00000000000..69b8b469bfc
--- /dev/null
+++ b/build/android/pylib/perf/perf_control_unittest.py
@@ -0,0 +1,37 @@
+# Copyright 2014 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.
+# pylint: disable=W0212
+
+import os
+import sys
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+from pylib.device import device_utils
+from pylib.perf import perf_control
+
+class TestPerfControl(unittest.TestCase):
+ def setUp(self):
+ if not os.getenv('BUILDTYPE'):
+ os.environ['BUILDTYPE'] = 'Debug'
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ self.assertGreater(len(devices), 0, 'No device attached!')
+ self._device = devices[0]
+
+ def testHighPerfMode(self):
+ perf = perf_control.PerfControl(self._device)
+ try:
+ perf.SetPerfProfilingMode()
+ cpu_info = perf.GetCpuInfo()
+ self.assertEquals(len(perf._cpu_files), len(cpu_info))
+ for _, online, governor in cpu_info:
+ self.assertTrue(online)
+ self.assertEquals('performance', governor)
+ finally:
+ perf.SetDefaultPerfMode()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/perf/setup.py b/build/android/pylib/perf/setup.py
new file mode 100644
index 00000000000..8e1fc28a7e6
--- /dev/null
+++ b/build/android/pylib/perf/setup.py
@@ -0,0 +1,97 @@
+# Copyright 2013 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.
+
+"""Generates test runner factory and tests for performance tests."""
+
+import json
+import fnmatch
+import logging
+import os
+import shutil
+
+from pylib import constants
+from pylib import forwarder
+from pylib.device import device_list
+from pylib.device import device_utils
+from pylib.perf import test_runner
+from pylib.utils import test_environment
+
+
+def _GetAllDevices():
+ devices_path = os.path.join(os.environ.get('CHROMIUM_OUT_DIR', 'out'),
+ device_list.LAST_DEVICES_FILENAME)
+ try:
+ devices = [device_utils.DeviceUtils(s)
+ for s in device_list.GetPersistentDeviceList(devices_path)]
+ except IOError as e:
+ logging.error('Unable to find %s [%s]', devices_path, e)
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ return sorted(devices)
+
+
+def _GetStepsDictFromSingleStep(test_options):
+ # Running a single command, build the tests structure.
+ steps_dict = {
+ 'version': 1,
+ 'steps': {
+ 'single_step': {
+ 'device_affinity': 0,
+ 'cmd': test_options.single_step
+ },
+ }
+ }
+ return steps_dict
+
+
+def _GetStepsDict(test_options):
+ if test_options.single_step:
+ return _GetStepsDictFromSingleStep(test_options)
+ if test_options.steps:
+ with file(test_options.steps, 'r') as f:
+ steps = json.load(f)
+
+ # Already using the new format.
+ assert steps['version'] == 1
+ return steps
+
+
+def Setup(test_options):
+ """Create and return the test runner factory and tests.
+
+ Args:
+ test_options: A PerformanceOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests, devices).
+ """
+ # TODO(bulach): remove this once the bot side lands. BUG=318369
+ constants.SetBuildType('Release')
+ if os.path.exists(constants.PERF_OUTPUT_DIR):
+ shutil.rmtree(constants.PERF_OUTPUT_DIR)
+ os.makedirs(constants.PERF_OUTPUT_DIR)
+
+ # Before running the tests, kill any leftover server.
+ test_environment.CleanupLeftoverProcesses()
+
+ # We want to keep device affinity, so return all devices ever seen.
+ all_devices = _GetAllDevices()
+
+ steps_dict = _GetStepsDict(test_options)
+ sorted_step_names = sorted(steps_dict['steps'].keys())
+
+ if test_options.test_filter:
+ sorted_step_names = fnmatch.filter(sorted_step_names,
+ test_options.test_filter)
+
+ flaky_steps = []
+ if test_options.flaky_steps:
+ with file(test_options.flaky_steps, 'r') as f:
+ flaky_steps = json.load(f)
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.TestRunner(
+ test_options, device, shard_index, len(all_devices),
+ steps_dict, flaky_steps)
+
+ return (TestRunnerFactory, sorted_step_names, all_devices)
diff --git a/build/android/pylib/perf/surface_stats_collector.py b/build/android/pylib/perf/surface_stats_collector.py
new file mode 100644
index 00000000000..c7e7527a1e2
--- /dev/null
+++ b/build/android/pylib/perf/surface_stats_collector.py
@@ -0,0 +1,191 @@
+# Copyright 2013 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.
+
+import Queue
+import datetime
+import logging
+import re
+import threading
+from pylib import android_commands
+from pylib.device import device_utils
+
+
+# Log marker containing SurfaceTexture timestamps.
+_SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
+_SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+'
+
+
+class SurfaceStatsCollector(object):
+ """Collects surface stats for a SurfaceView from the output of SurfaceFlinger.
+
+ Args:
+ device: A DeviceUtils instance.
+ """
+
+ def __init__(self, device):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, android_commands.AndroidCommands):
+ device = device_utils.DeviceUtils(device)
+ self._device = device
+ self._collector_thread = None
+ self._surface_before = None
+ self._get_data_event = None
+ self._data_queue = None
+ self._stop_event = None
+ self._warn_about_empty_data = True
+
+ def DisableWarningAboutEmptyData(self):
+ self._warn_about_empty_data = False
+
+ def Start(self):
+ assert not self._collector_thread
+
+ if self._ClearSurfaceFlingerLatencyData():
+ self._get_data_event = threading.Event()
+ self._stop_event = threading.Event()
+ self._data_queue = Queue.Queue()
+ self._collector_thread = threading.Thread(target=self._CollectorThread)
+ self._collector_thread.start()
+ else:
+ raise Exception('SurfaceFlinger not supported on this device.')
+
+ def Stop(self):
+ assert self._collector_thread
+ (refresh_period, timestamps) = self._GetDataFromThread()
+ if self._collector_thread:
+ self._stop_event.set()
+ self._collector_thread.join()
+ self._collector_thread = None
+ return (refresh_period, timestamps)
+
+ def _CollectorThread(self):
+ last_timestamp = 0
+ timestamps = []
+ retries = 0
+
+ while not self._stop_event.is_set():
+ self._get_data_event.wait(1)
+ try:
+ refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
+ if refresh_period is None or timestamps is None:
+ retries += 1
+ if retries < 3:
+ continue
+ if last_timestamp:
+ # Some data has already been collected, but either the app
+ # was closed or there's no new data. Signal the main thread and
+ # wait.
+ self._data_queue.put((None, None))
+ self._stop_event.wait()
+ break
+ raise Exception('Unable to get surface flinger latency data')
+
+ timestamps += [timestamp for timestamp in new_timestamps
+ if timestamp > last_timestamp]
+ if len(timestamps):
+ last_timestamp = timestamps[-1]
+
+ if self._get_data_event.is_set():
+ self._get_data_event.clear()
+ self._data_queue.put((refresh_period, timestamps))
+ timestamps = []
+ except Exception as e:
+ # On any error, before aborting, put the exception into _data_queue to
+ # prevent the main thread from waiting at _data_queue.get() infinitely.
+ self._data_queue.put(e)
+ raise
+
+ def _GetDataFromThread(self):
+ self._get_data_event.set()
+ ret = self._data_queue.get()
+ if isinstance(ret, Exception):
+ raise ret
+ return ret
+
+ def _ClearSurfaceFlingerLatencyData(self):
+ """Clears the SurfaceFlinger latency data.
+
+ Returns:
+ True if SurfaceFlinger latency is supported by the device, otherwise
+ False.
+ """
+ # The command returns nothing if it is supported, otherwise returns many
+ # lines of result just like 'dumpsys SurfaceFlinger'.
+ results = self._device.RunShellCommand(
+ 'dumpsys SurfaceFlinger --latency-clear SurfaceView')
+ return not len(results)
+
+ def GetSurfaceFlingerPid(self):
+ results = self._device.RunShellCommand('ps | grep surfaceflinger')
+ if not results:
+ raise Exception('Unable to get surface flinger process id')
+ pid = results[0].split()[1]
+ return pid
+
+ def _GetSurfaceFlingerFrameData(self):
+ """Returns collected SurfaceFlinger frame timing data.
+
+ Returns:
+ A tuple containing:
+ - The display's nominal refresh period in milliseconds.
+ - A list of timestamps signifying frame presentation times in
+ milliseconds.
+ The return value may be (None, None) if there was no data collected (for
+ example, if the app was closed before the collector thread has finished).
+ """
+ # adb shell dumpsys SurfaceFlinger --latency
+ # prints some information about the last 128 frames displayed in
+ # that window.
+ # The data returned looks like this:
+ # 16954612
+ # 7657467895508 7657482691352 7657493499756
+ # 7657484466553 7657499645964 7657511077881
+ # 7657500793457 7657516600576 7657527404785
+ # (...)
+ #
+ # The first line is the refresh period (here 16.95 ms), it is followed
+ # by 128 lines w/ 3 timestamps in nanosecond each:
+ # A) when the app started to draw
+ # B) the vsync immediately preceding SF submitting the frame to the h/w
+ # C) timestamp immediately after SF submitted that frame to the h/w
+ #
+ # The difference between the 1st and 3rd timestamp is the frame-latency.
+ # An interesting data is when the frame latency crosses a refresh period
+ # boundary, this can be calculated this way:
+ #
+ # ceil((C - A) / refresh-period)
+ #
+ # (each time the number above changes, we have a "jank").
+ # If this happens a lot during an animation, the animation appears
+ # janky, even if it runs at 60 fps in average.
+ #
+ # We use the special "SurfaceView" window name because the statistics for
+ # the activity's main window are not updated when the main web content is
+ # composited into a SurfaceView.
+ results = self._device.RunShellCommand(
+ 'dumpsys SurfaceFlinger --latency SurfaceView')
+ if not len(results):
+ return (None, None)
+
+ timestamps = []
+ nanoseconds_per_millisecond = 1e6
+ refresh_period = long(results[0]) / nanoseconds_per_millisecond
+
+ # If a fence associated with a frame is still pending when we query the
+ # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
+ # Since we only care about completed frames, we will ignore any timestamps
+ # with this value.
+ pending_fence_timestamp = (1 << 63) - 1
+
+ for line in results[1:]:
+ fields = line.split()
+ if len(fields) != 3:
+ continue
+ timestamp = long(fields[1])
+ if timestamp == pending_fence_timestamp:
+ continue
+ timestamp /= nanoseconds_per_millisecond
+ timestamps.append(timestamp)
+
+ return (refresh_period, timestamps)
diff --git a/build/android/pylib/perf/surface_stats_collector_unittest.py b/build/android/pylib/perf/surface_stats_collector_unittest.py
new file mode 100644
index 00000000000..e905d7314e3
--- /dev/null
+++ b/build/android/pylib/perf/surface_stats_collector_unittest.py
@@ -0,0 +1,64 @@
+# Copyright 2013 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.
+
+"""Unittests for SurfaceStatsCollector."""
+# pylint: disable=W0212
+
+import unittest
+
+from pylib.perf.surface_stats_collector import SurfaceStatsCollector
+
+
+class TestSurfaceStatsCollector(unittest.TestCase):
+ @staticmethod
+ def _CreateUniformTimestamps(base, num, delta):
+ return [base + i * delta for i in range(1, num + 1)]
+
+ @staticmethod
+ def _CreateDictionaryFromResults(results):
+ dictionary = {}
+ for result in results:
+ dictionary[result.name] = result
+ return dictionary
+
+ def setUp(self):
+ self.refresh_period = 0.1
+
+ def testOneFrameDelta(self):
+ timestamps = self._CreateUniformTimestamps(0, 10, self.refresh_period)
+ results = self._CreateDictionaryFromResults(
+ SurfaceStatsCollector._CalculateResults(
+ self.refresh_period, timestamps, ''))
+
+ self.assertEquals(results['avg_surface_fps'].value,
+ int(round(1 / self.refresh_period)))
+ self.assertEquals(results['jank_count'].value, 0)
+ self.assertEquals(results['max_frame_delay'].value, 1)
+ self.assertEquals(len(results['frame_lengths'].value), len(timestamps) - 1)
+
+ def testAllFramesTooShort(self):
+ timestamps = self._CreateUniformTimestamps(0, 10, self.refresh_period / 100)
+ self.assertRaises(Exception,
+ SurfaceStatsCollector._CalculateResults,
+ [self.refresh_period, timestamps, ''])
+
+ def testSomeFramesTooShort(self):
+ timestamps = self._CreateUniformTimestamps(0, 5, self.refresh_period)
+ # The following timestamps should be skipped.
+ timestamps += self._CreateUniformTimestamps(timestamps[4],
+ 5,
+ self.refresh_period / 100)
+ timestamps += self._CreateUniformTimestamps(timestamps[4],
+ 5,
+ self.refresh_period)
+
+ results = self._CreateDictionaryFromResults(
+ SurfaceStatsCollector._CalculateResults(
+ self.refresh_period, timestamps, ''))
+
+ self.assertEquals(len(results['frame_lengths'].value), 9)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/perf/test_options.py b/build/android/pylib/perf/test_options.py
new file mode 100644
index 00000000000..eff928e1155
--- /dev/null
+++ b/build/android/pylib/perf/test_options.py
@@ -0,0 +1,22 @@
+# Copyright 2013 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.
+
+"""Defines the PerfOptions named tuple."""
+
+import collections
+
+PerfOptions = collections.namedtuple('PerfOptions', [
+ 'steps',
+ 'flaky_steps',
+ 'output_json_list',
+ 'print_step',
+ 'no_timeout',
+ 'test_filter',
+ 'dry_run',
+ 'single_step',
+ 'collect_chartjson_data',
+ 'output_chartjson_data',
+ 'max_battery_temp',
+ 'min_battery_level',
+])
diff --git a/build/android/pylib/perf/test_runner.py b/build/android/pylib/perf/test_runner.py
new file mode 100644
index 00000000000..d21a9b71b10
--- /dev/null
+++ b/build/android/pylib/perf/test_runner.py
@@ -0,0 +1,374 @@
+# Copyright 2013 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.
+
+"""Runs perf tests.
+
+Our buildbot infrastructure requires each slave to run steps serially.
+This is sub-optimal for android, where these steps can run independently on
+multiple connected devices.
+
+The buildbots will run this script multiple times per cycle:
+- First: all steps listed in --steps in will be executed in parallel using all
+connected devices. Step results will be pickled to disk. Each step has a unique
+name. The result code will be ignored if the step name is listed in
+--flaky-steps.
+The buildbot will treat this step as a regular step, and will not process any
+graph data.
+
+- Then, with -print-step STEP_NAME: at this stage, we'll simply print the file
+with the step results previously saved. The buildbot will then process the graph
+data accordingly.
+
+The JSON steps file contains a dictionary in the format:
+{ "version": int,
+ "steps": {
+ "foo": {
+ "device_affinity": int,
+ "cmd": "script_to_execute foo"
+ },
+ "bar": {
+ "device_affinity": int,
+ "cmd": "script_to_execute bar"
+ }
+ }
+}
+
+The JSON flaky steps file contains a list with step names which results should
+be ignored:
+[
+ "step_name_foo",
+ "step_name_bar"
+]
+
+Note that script_to_execute necessarily have to take at least the following
+option:
+ --device: the serial number to be passed to all adb commands.
+"""
+
+import collections
+import datetime
+import json
+import logging
+import os
+import pickle
+import shutil
+import sys
+import tempfile
+import threading
+import time
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib import forwarder
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.device import battery_utils
+from pylib.device import device_errors
+
+
+def GetPersistedResult(test_name):
+ file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name)
+ if not os.path.exists(file_name):
+ logging.error('File not found %s', file_name)
+ return None
+
+ with file(file_name, 'r') as f:
+ return pickle.loads(f.read())
+
+
+def OutputJsonList(json_input, json_output):
+ with file(json_input, 'r') as i:
+ all_steps = json.load(i)
+
+ step_values = []
+ for k, v in all_steps['steps'].iteritems():
+ data = {'test': k, 'device_affinity': v['device_affinity']}
+
+ persisted_result = GetPersistedResult(k)
+ if persisted_result:
+ data['total_time'] = persisted_result['total_time']
+ step_values.append(data)
+
+ with file(json_output, 'w') as o:
+ o.write(json.dumps(step_values))
+ return 0
+
+
+def PrintTestOutput(test_name, json_file_name=None):
+ """Helper method to print the output of previously executed test_name.
+
+ Args:
+ test_name: name of the test that has been previously executed.
+ json_file_name: name of the file to output chartjson data to.
+
+ Returns:
+ exit code generated by the test step.
+ """
+ persisted_result = GetPersistedResult(test_name)
+ if not persisted_result:
+ return 1
+ logging.info('*' * 80)
+ logging.info('Output from:')
+ logging.info(persisted_result['cmd'])
+ logging.info('*' * 80)
+ print persisted_result['output']
+
+ if json_file_name:
+ with file(json_file_name, 'w') as f:
+ f.write(persisted_result['chartjson'])
+
+ return persisted_result['exit_code']
+
+
+def PrintSummary(test_names):
+ logging.info('*' * 80)
+ logging.info('Sharding summary')
+ device_total_time = collections.defaultdict(int)
+ for test_name in test_names:
+ file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name)
+ if not os.path.exists(file_name):
+ logging.info('%s : No status file found', test_name)
+ continue
+ with file(file_name, 'r') as f:
+ result = pickle.loads(f.read())
+ logging.info('%s : exit_code=%d in %d secs at %s',
+ result['name'], result['exit_code'], result['total_time'],
+ result['device'])
+ device_total_time[result['device']] += result['total_time']
+ for device, device_time in device_total_time.iteritems():
+ logging.info('Total for device %s : %d secs', device, device_time)
+ logging.info('Total steps time: %d secs', sum(device_total_time.values()))
+
+
+class _HeartBeatLogger(object):
+ # How often to print the heartbeat on flush().
+ _PRINT_INTERVAL = 30.0
+
+ def __init__(self):
+ """A file-like class for keeping the buildbot alive."""
+ self._len = 0
+ self._tick = time.time()
+ self._stopped = threading.Event()
+ self._timer = threading.Thread(target=self._runner)
+ self._timer.start()
+
+ def _runner(self):
+ while not self._stopped.is_set():
+ self.flush()
+ self._stopped.wait(_HeartBeatLogger._PRINT_INTERVAL)
+
+ def write(self, data):
+ self._len += len(data)
+
+ def flush(self):
+ now = time.time()
+ if now - self._tick >= _HeartBeatLogger._PRINT_INTERVAL:
+ self._tick = now
+ print '--single-step output length %d' % self._len
+ sys.stdout.flush()
+
+ def stop(self):
+ self._stopped.set()
+
+
+class TestRunner(base_test_runner.BaseTestRunner):
+ def __init__(self, test_options, device, shard_index, max_shard, tests,
+ flaky_tests):
+ """A TestRunner instance runs a perf test on a single device.
+
+ Args:
+ test_options: A PerfOptions object.
+ device: Device to run the tests.
+ shard_index: the index of this device.
+ max_shards: the maximum shard index.
+ tests: a dict mapping test_name to command.
+ flaky_tests: a list of flaky test_name.
+ """
+ super(TestRunner, self).__init__(device, None)
+ self._options = test_options
+ self._shard_index = shard_index
+ self._max_shard = max_shard
+ self._tests = tests
+ self._flaky_tests = flaky_tests
+ self._output_dir = None
+ self._device_battery = battery_utils.BatteryUtils(self.device)
+
+ @staticmethod
+ def _IsBetter(result):
+ if result['actual_exit_code'] == 0:
+ return True
+ pickled = os.path.join(constants.PERF_OUTPUT_DIR,
+ result['name'])
+ if not os.path.exists(pickled):
+ return True
+ with file(pickled, 'r') as f:
+ previous = pickle.loads(f.read())
+ return result['actual_exit_code'] < previous['actual_exit_code']
+
+ @staticmethod
+ def _SaveResult(result):
+ if TestRunner._IsBetter(result):
+ with file(os.path.join(constants.PERF_OUTPUT_DIR,
+ result['name']), 'w') as f:
+ f.write(pickle.dumps(result))
+
+ def _CheckDeviceAffinity(self, test_name):
+ """Returns True if test_name has affinity for this shard."""
+ affinity = (self._tests['steps'][test_name]['device_affinity'] %
+ self._max_shard)
+ if self._shard_index == affinity:
+ return True
+ logging.info('Skipping %s on %s (affinity is %s, device is %s)',
+ test_name, self.device_serial, affinity, self._shard_index)
+ return False
+
+ def _CleanupOutputDirectory(self):
+ if self._output_dir:
+ shutil.rmtree(self._output_dir, ignore_errors=True)
+ self._output_dir = None
+
+ def _ReadChartjsonOutput(self):
+ if not self._output_dir:
+ return ''
+
+ json_output_path = os.path.join(self._output_dir, 'results-chart.json')
+ try:
+ with open(json_output_path) as f:
+ return f.read()
+ except IOError:
+ logging.exception('Exception when reading chartjson.')
+ logging.error('This usually means that telemetry did not run, so it could'
+ ' not generate the file. Please check the device running'
+ ' the test.')
+ return ''
+
+ def _LaunchPerfTest(self, test_name):
+ """Runs a perf test.
+
+ Args:
+ test_name: the name of the test to be executed.
+
+ Returns:
+ A tuple containing (Output, base_test_result.ResultType)
+ """
+ if not self._CheckDeviceAffinity(test_name):
+ return '', base_test_result.ResultType.PASS
+
+ try:
+ logging.warning('Unmapping device ports')
+ forwarder.Forwarder.UnmapAllDevicePorts(self.device)
+ self.device.old_interface.RestartAdbdOnDevice()
+ except Exception as e:
+ logging.error('Exception when tearing down device %s', e)
+
+ cmd = ('%s --device %s' %
+ (self._tests['steps'][test_name]['cmd'],
+ self.device_serial))
+
+ if self._options.collect_chartjson_data:
+ self._output_dir = tempfile.mkdtemp()
+ cmd = cmd + ' --output-dir=%s' % self._output_dir
+
+ logging.info(
+ 'temperature: %s (0.1 C)',
+ str(self._device_battery.GetBatteryInfo().get('temperature')))
+ if self._options.max_battery_temp:
+ self._device_battery.LetBatteryCoolToTemperature(
+ self._options.max_battery_temp)
+
+ logging.info('Charge level: %s%%',
+ str(self._device_battery.GetBatteryInfo().get('level')))
+ if self._options.min_battery_level:
+ self._device_battery.ChargeDeviceToLevel(
+ self._options.min_battery_level)
+
+ logging.info('%s : %s', test_name, cmd)
+ start_time = datetime.datetime.now()
+
+ timeout = self._tests['steps'][test_name].get('timeout', 5400)
+ if self._options.no_timeout:
+ timeout = None
+ logging.info('Timeout for %s test: %s', test_name, timeout)
+ full_cmd = cmd
+ if self._options.dry_run:
+ full_cmd = 'echo %s' % cmd
+
+ logfile = sys.stdout
+ if self._options.single_step:
+ # Just print a heart-beat so that the outer buildbot scripts won't timeout
+ # without response.
+ logfile = _HeartBeatLogger()
+ cwd = os.path.abspath(constants.DIR_SOURCE_ROOT)
+ if full_cmd.startswith('src/'):
+ cwd = os.path.abspath(os.path.join(constants.DIR_SOURCE_ROOT, os.pardir))
+ try:
+ exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
+ full_cmd, timeout, cwd=cwd, shell=True, logfile=logfile)
+ json_output = self._ReadChartjsonOutput()
+ except cmd_helper.TimeoutError as e:
+ exit_code = -1
+ output = str(e)
+ json_output = ''
+ finally:
+ self._CleanupOutputDirectory()
+ if self._options.single_step:
+ logfile.stop()
+ end_time = datetime.datetime.now()
+ if exit_code is None:
+ exit_code = -1
+ logging.info('%s : exit_code=%d in %d secs at %s',
+ test_name, exit_code, (end_time - start_time).seconds,
+ self.device_serial)
+
+ if exit_code == 0:
+ result_type = base_test_result.ResultType.PASS
+ else:
+ result_type = base_test_result.ResultType.FAIL
+ # Since perf tests use device affinity, give the device a chance to
+ # recover if it is offline after a failure. Otherwise, the master sharder
+ # will remove it from the pool and future tests on this device will fail.
+ try:
+ self.device.WaitUntilFullyBooted(timeout=120)
+ except device_errors.CommandTimeoutError as e:
+ logging.error('Device failed to return after %s: %s' % (test_name, e))
+
+ actual_exit_code = exit_code
+ if test_name in self._flaky_tests:
+ # The exit_code is used at the second stage when printing the
+ # test output. If the test is flaky, force to "0" to get that step green
+ # whilst still gathering data to the perf dashboards.
+ # The result_type is used by the test_dispatcher to retry the test.
+ exit_code = 0
+
+ persisted_result = {
+ 'name': test_name,
+ 'output': output,
+ 'chartjson': json_output,
+ 'exit_code': exit_code,
+ 'actual_exit_code': actual_exit_code,
+ 'result_type': result_type,
+ 'total_time': (end_time - start_time).seconds,
+ 'device': self.device_serial,
+ 'cmd': cmd,
+ }
+ self._SaveResult(persisted_result)
+
+ return (output, result_type)
+
+ def RunTest(self, test_name):
+ """Run a perf test on the device.
+
+ Args:
+ test_name: String to use for logging the test result.
+
+ Returns:
+ A tuple of (TestRunResults, retry).
+ """
+ _, result_type = self._LaunchPerfTest(test_name)
+ results = base_test_result.TestRunResults()
+ results.AddResult(base_test_result.BaseTestResult(test_name, result_type))
+ retry = None
+ if not results.DidRunPass():
+ retry = test_name
+ return results, retry
diff --git a/build/android/pylib/perf/thermal_throttle.py b/build/android/pylib/perf/thermal_throttle.py
new file mode 100644
index 00000000000..383b6d55f86
--- /dev/null
+++ b/build/android/pylib/perf/thermal_throttle.py
@@ -0,0 +1,137 @@
+# Copyright 2013 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.
+
+import logging
+from pylib import android_commands
+from pylib.device import device_utils
+
+
+class OmapThrottlingDetector(object):
+ """Class to detect and track thermal throttling on an OMAP 4."""
+ OMAP_TEMP_FILE = ('/sys/devices/platform/omap/omap_temp_sensor.0/'
+ 'temperature')
+
+ @staticmethod
+ def IsSupported(device):
+ return device.FileExists(OmapThrottlingDetector.OMAP_TEMP_FILE)
+
+ def __init__(self, device):
+ self._device = device
+
+ @staticmethod
+ def BecameThrottled(log_line):
+ return 'omap_thermal_throttle' in log_line
+
+ @staticmethod
+ def BecameUnthrottled(log_line):
+ return 'omap_thermal_unthrottle' in log_line
+
+ @staticmethod
+ def GetThrottlingTemperature(log_line):
+ if 'throttle_delayed_work_fn' in log_line:
+ return float([s for s in log_line.split() if s.isdigit()][0]) / 1000.0
+
+ def GetCurrentTemperature(self):
+ tempdata = self._device.ReadFile(OmapThrottlingDetector.OMAP_TEMP_FILE)
+ return float(tempdata) / 1000.0
+
+
+class ExynosThrottlingDetector(object):
+ """Class to detect and track thermal throttling on an Exynos 5."""
+ @staticmethod
+ def IsSupported(device):
+ return device.FileExists('/sys/bus/exynos5-core')
+
+ def __init__(self, device):
+ pass
+
+ @staticmethod
+ def BecameThrottled(log_line):
+ return 'exynos_tmu: Throttling interrupt' in log_line
+
+ @staticmethod
+ def BecameUnthrottled(log_line):
+ return 'exynos_thermal_unthrottle: not throttling' in log_line
+
+ @staticmethod
+ def GetThrottlingTemperature(_log_line):
+ return None
+
+ @staticmethod
+ def GetCurrentTemperature():
+ return None
+
+
+class ThermalThrottle(object):
+ """Class to detect and track thermal throttling.
+
+ Usage:
+ Wait for IsThrottled() to be False before running test
+ After running test call HasBeenThrottled() to find out if the
+ test run was affected by thermal throttling.
+ """
+
+ def __init__(self, device):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, android_commands.AndroidCommands):
+ device = device_utils.DeviceUtils(device)
+ self._device = device
+ self._throttled = False
+ self._detector = None
+ if OmapThrottlingDetector.IsSupported(device):
+ self._detector = OmapThrottlingDetector(device)
+ elif ExynosThrottlingDetector.IsSupported(device):
+ self._detector = ExynosThrottlingDetector(device)
+
+ def HasBeenThrottled(self):
+ """True if there has been any throttling since the last call to
+ HasBeenThrottled or IsThrottled.
+ """
+ return self._ReadLog()
+
+ def IsThrottled(self):
+ """True if currently throttled."""
+ self._ReadLog()
+ return self._throttled
+
+ def _ReadLog(self):
+ if not self._detector:
+ return False
+ has_been_throttled = False
+ serial_number = str(self._device)
+ log = self._device.RunShellCommand('dmesg -c')
+ degree_symbol = unichr(0x00B0)
+ for line in log:
+ if self._detector.BecameThrottled(line):
+ if not self._throttled:
+ logging.warning('>>> Device %s thermally throttled', serial_number)
+ self._throttled = True
+ has_been_throttled = True
+ elif self._detector.BecameUnthrottled(line):
+ if self._throttled:
+ logging.warning('>>> Device %s thermally unthrottled', serial_number)
+ self._throttled = False
+ has_been_throttled = True
+ temperature = self._detector.GetThrottlingTemperature(line)
+ if temperature is not None:
+ logging.info(u'Device %s thermally throttled at %3.1f%sC',
+ serial_number, temperature, degree_symbol)
+
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
+ # Print current temperature of CPU SoC.
+ temperature = self._detector.GetCurrentTemperature()
+ if temperature is not None:
+ logging.debug(u'Current SoC temperature of %s = %3.1f%sC',
+ serial_number, temperature, degree_symbol)
+
+ # Print temperature of battery, to give a system temperature
+ dumpsys_log = self._device.RunShellCommand('dumpsys battery')
+ for line in dumpsys_log:
+ if 'temperature' in line:
+ btemp = float([s for s in line.split() if s.isdigit()][0]) / 10.0
+ logging.debug(u'Current battery temperature of %s = %3.1f%sC',
+ serial_number, btemp, degree_symbol)
+
+ return has_been_throttled
+
diff --git a/build/android/pylib/pexpect.py b/build/android/pylib/pexpect.py
new file mode 100644
index 00000000000..cf59fb0f6d3
--- /dev/null
+++ b/build/android/pylib/pexpect.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2012 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.
+from __future__ import absolute_import
+
+import os
+import sys
+
+_CHROME_SRC = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)), '..', '..', '..')
+
+_PEXPECT_PATH = os.path.join(_CHROME_SRC, 'third_party', 'pexpect')
+if _PEXPECT_PATH not in sys.path:
+ sys.path.append(_PEXPECT_PATH)
+
+# pexpect is not available on all platforms. We allow this file to be imported
+# on platforms without pexpect and only fail when pexpect is actually used.
+try:
+ from pexpect import * # pylint: disable=W0401,W0614
+except ImportError:
+ pass
diff --git a/build/android/pylib/ports.py b/build/android/pylib/ports.py
new file mode 100644
index 00000000000..578152cb0f1
--- /dev/null
+++ b/build/android/pylib/ports.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2012 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.
+
+"""Functions that deal with local and device ports."""
+
+import contextlib
+import fcntl
+import httplib
+import logging
+import os
+import socket
+import traceback
+
+from pylib import constants
+
+
+# The following two methods are used to allocate the port source for various
+# types of test servers. Because some net-related tests can be run on shards at
+# same time, it's important to have a mechanism to allocate the port
+# process-safe. In here, we implement the safe port allocation by leveraging
+# flock.
+def ResetTestServerPortAllocation():
+ """Resets the port allocation to start from TEST_SERVER_PORT_FIRST.
+
+ Returns:
+ Returns True if reset successes. Otherwise returns False.
+ """
+ try:
+ with open(constants.TEST_SERVER_PORT_FILE, 'w') as fp:
+ fp.write('%d' % constants.TEST_SERVER_PORT_FIRST)
+ if os.path.exists(constants.TEST_SERVER_PORT_LOCKFILE):
+ os.unlink(constants.TEST_SERVER_PORT_LOCKFILE)
+ return True
+ except Exception as e:
+ logging.error(e)
+ return False
+
+
+def AllocateTestServerPort():
+ """Allocates a port incrementally.
+
+ Returns:
+ Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and
+ TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used.
+ """
+ port = 0
+ ports_tried = []
+ try:
+ fp_lock = open(constants.TEST_SERVER_PORT_LOCKFILE, 'w')
+ fcntl.flock(fp_lock, fcntl.LOCK_EX)
+ # Get current valid port and calculate next valid port.
+ if not os.path.exists(constants.TEST_SERVER_PORT_FILE):
+ ResetTestServerPortAllocation()
+ with open(constants.TEST_SERVER_PORT_FILE, 'r+') as fp:
+ port = int(fp.read())
+ ports_tried.append(port)
+ while not IsHostPortAvailable(port):
+ port += 1
+ ports_tried.append(port)
+ if (port > constants.TEST_SERVER_PORT_LAST or
+ port < constants.TEST_SERVER_PORT_FIRST):
+ port = 0
+ else:
+ fp.seek(0, os.SEEK_SET)
+ fp.write('%d' % (port + 1))
+ except Exception as e:
+ logging.error(e)
+ finally:
+ if fp_lock:
+ fcntl.flock(fp_lock, fcntl.LOCK_UN)
+ fp_lock.close()
+ if port:
+ logging.info('Allocate port %d for test server.', port)
+ else:
+ logging.error('Could not allocate port for test server. '
+ 'List of ports tried: %s', str(ports_tried))
+ return port
+
+
+def IsHostPortAvailable(host_port):
+ """Checks whether the specified host port is available.
+
+ Args:
+ host_port: Port on host to check.
+
+ Returns:
+ True if the port on host is available, otherwise returns False.
+ """
+ s = socket.socket()
+ try:
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind(('', host_port))
+ s.close()
+ return True
+ except socket.error:
+ return False
+
+
+def IsDevicePortUsed(device, device_port, state=''):
+ """Checks whether the specified device port is used or not.
+
+ Args:
+ device: A DeviceUtils instance.
+ device_port: Port on device we want to check.
+ state: String of the specified state. Default is empty string, which
+ means any state.
+
+ Returns:
+ True if the port on device is already used, otherwise returns False.
+ """
+ base_url = '127.0.0.1:%d' % device_port
+ netstat_results = device.RunShellCommand('netstat')
+ for single_connect in netstat_results:
+ # Column 3 is the local address which we want to check with.
+ connect_results = single_connect.split()
+ if connect_results[0] != 'tcp':
+ continue
+ if len(connect_results) < 6:
+ raise Exception('Unexpected format while parsing netstat line: ' +
+ single_connect)
+ is_state_match = connect_results[5] == state if state else True
+ if connect_results[3] == base_url and is_state_match:
+ return True
+ return False
+
+
+def IsHttpServerConnectable(host, port, tries=3, command='GET', path='/',
+ expected_read='', timeout=2):
+ """Checks whether the specified http server is ready to serve request or not.
+
+ Args:
+ host: Host name of the HTTP server.
+ port: Port number of the HTTP server.
+ tries: How many times we want to test the connection. The default value is
+ 3.
+ command: The http command we use to connect to HTTP server. The default
+ command is 'GET'.
+ path: The path we use when connecting to HTTP server. The default path is
+ '/'.
+ expected_read: The content we expect to read from the response. The default
+ value is ''.
+ timeout: Timeout (in seconds) for each http connection. The default is 2s.
+
+ Returns:
+ Tuple of (connect status, client error). connect status is a boolean value
+ to indicate whether the server is connectable. client_error is the error
+ message the server returns when connect status is false.
+ """
+ assert tries >= 1
+ for i in xrange(0, tries):
+ client_error = None
+ try:
+ with contextlib.closing(httplib.HTTPConnection(
+ host, port, timeout=timeout)) as http:
+ # Output some debug information when we have tried more than 2 times.
+ http.set_debuglevel(i >= 2)
+ http.request(command, path)
+ r = http.getresponse()
+ content = r.read()
+ if r.status == 200 and r.reason == 'OK' and content == expected_read:
+ return (True, '')
+ client_error = ('Bad response: %s %s version %s\n ' %
+ (r.status, r.reason, r.version) +
+ '\n '.join([': '.join(h) for h in r.getheaders()]))
+ except (httplib.HTTPException, socket.error) as e:
+ # Probably too quick connecting: try again.
+ exception_error_msgs = traceback.format_exception_only(type(e), e)
+ if exception_error_msgs:
+ client_error = ''.join(exception_error_msgs)
+ # Only returns last client_error.
+ return (False, client_error or 'Timeout')
diff --git a/build/android/pylib/remote/__init__.py b/build/android/pylib/remote/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/remote/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/remote/device/__init__.py b/build/android/pylib/remote/device/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/remote/device/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/remote/device/appurify_constants.py b/build/android/pylib/remote/device/appurify_constants.py
new file mode 100644
index 00000000000..93431782e05
--- /dev/null
+++ b/build/android/pylib/remote/device/appurify_constants.py
@@ -0,0 +1,57 @@
+# Copyright 2015 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.
+
+"""Defines a set of constants specific to appurify."""
+
+# Appurify network config constants.
+class NETWORK(object):
+ WIFI_1_BAR = 1
+ SPRINT_4G_LTE_4_BARS = 2
+ SPRINT_3G_5_BARS = 3
+ SPRINT_3G_4_BARS = 4
+ SPRINT_3G_3_BARS = 5
+ SPRINT_3G_2_BARS = 6
+ SPRINT_3G_1_BAR = 7
+ SPRING_4G_1_BAR = 8
+ VERIZON_3G_5_BARS = 9
+ VERIZON_3G_4_BARS = 10
+ VERIZON_3G_3_BARS = 11
+ VERIZON_3G_2_BARS = 12
+ VERIZON_3G_1_BAR = 13
+ VERIZON_4G_1_BAR = 14
+ ATANDT_3G_5_BARS = 15
+ ATANDT_3G_4_BARS = 16
+ ATANDT_3G_3_BARS = 17
+ ATANDT_3G_2_BARS = 18
+ ATANDT_3G_1_BAR = 19
+ GENERIC_2G_4_BARS = 20
+ GENERIC_2G_3_BARS = 21
+ GENERIC_EVOLVED_EDGE = 22
+ GENERIC_GPRS = 23
+ GENERIC_ENHANCED_GPRS = 24
+ GENERIC_LTE = 25
+ GENERIC_HIGH_LATENCY_DNS = 26
+ GENERIC_100_PERCENT_PACKET_LOSS = 27
+ ATANDT_HSPA_PLUS = 28
+ ATANDT_4G_LTE_4_BARS = 29
+ VERIZON_4G_LTE_4_BARS = 30
+ GENERIC_DIGITAL_SUBSCRIBE_LINE = 31
+ WIFI_STARBUCKS_3_BARS = 32
+ WIFI_STARBUCKS_4_BARS = 33
+ WIFI_STARBUCKS_HIGH_TRAFFIC = 34
+ WIFI_TARGET_1_BAR = 35
+ WIFI_TARGET_3_BARS = 36
+ WIFI_TARGET_4_BARS = 37
+ PUBLIC_WIFI_MCDONALDS_5_BARS = 38
+ PUBLIC_WIFI_MCDONALDS_4_BARS = 39
+ PUBLIC_WIFI_MCDONALDS_2_BARS = 40
+ PUBLIC_WIFI_MCDONALDS_1_BAR = 41
+ PUBLIC_WIFI_KOHLS_5_BARS = 42
+ PUBLIC_WIFI_KOHLS_4_BARS = 43
+ PUBLIC_WIFI_KOHLS_2_BARS = 44
+ PUBLIC_WIFI_ATANDT_5_BARS = 45
+ PUBLIC_WIFI_ATANDT_4_BARS = 46
+ PUBLIC_WIFI_ATANDT_2_BARS = 47
+ PUBLIC_WIFI_ATANDT_1_BAR = 48
+ BOINGO = 49
\ No newline at end of file
diff --git a/build/android/pylib/remote/device/appurify_sanitized.py b/build/android/pylib/remote/device/appurify_sanitized.py
new file mode 100644
index 00000000000..9f6ab4054f0
--- /dev/null
+++ b/build/android/pylib/remote/device/appurify_sanitized.py
@@ -0,0 +1,40 @@
+# Copyright 2014 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.
+
+import contextlib
+import logging
+import os
+import sys
+
+from pylib import constants
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'requests', 'src'))
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'appurify-python', 'src'))
+handlers_before = list(logging.getLogger().handlers)
+
+import appurify.api
+import appurify.utils
+
+handlers_after = list(logging.getLogger().handlers)
+new_handler = list(set(handlers_after) - set(handlers_before))
+while new_handler:
+ logging.info("Removing logging handler.")
+ logging.getLogger().removeHandler(new_handler.pop())
+
+api = appurify.api
+utils = appurify.utils
+
+# This is not thread safe. If multiple threads are ever supported with appurify
+# this may cause logging messages to go missing.
+@contextlib.contextmanager
+def SanitizeLogging(verbose_count, level):
+ if verbose_count < 2:
+ logging.disable(level)
+ yield True
+ logging.disable(logging.NOTSET)
+ else:
+ yield False
+
diff --git a/build/android/pylib/remote/device/dummy/BUILD.gn b/build/android/pylib/remote/device/dummy/BUILD.gn
new file mode 100644
index 00000000000..54ca275ea34
--- /dev/null
+++ b/build/android/pylib/remote/device/dummy/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2015 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.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+
+# GYP: //build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk
+android_apk("remote_device_dummy_apk") {
+ android_manifest = "//build/android/AndroidManifest.xml"
+ java_files = [ "src/org/chromium/dummy/Dummy.java" ]
+ apk_name = "remote_device_dummy"
+ testonly = true
+}
diff --git a/build/android/pylib/remote/device/dummy/dummy.gyp b/build/android/pylib/remote/device/dummy/dummy.gyp
new file mode 100644
index 00000000000..b003edcd869
--- /dev/null
+++ b/build/android/pylib/remote/device/dummy/dummy.gyp
@@ -0,0 +1,25 @@
+# Copyright 2015 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.
+
+# Running gtests on a remote device via am instrument requires both an "app"
+# APK and a "test" APK with different package names. Our gtests only use one
+# APK, so we build a dummy APK to upload as the app.
+
+{
+ 'targets': [
+ {
+ # GN: //build/android/pylib/remote/device/dummy:remote_device_dummy_apk
+ 'target_name': 'remote_device_dummy_apk',
+ 'type': 'none',
+ 'variables': {
+ 'apk_name': 'remote_device_dummy',
+ 'java_in_dir': '.',
+ 'android_manifest_path': '../../../../../../build/android/AndroidManifest.xml',
+ },
+ 'includes': [
+ '../../../../../../build/java_apk.gypi',
+ ]
+ },
+ ]
+}
diff --git a/build/android/pylib/remote/device/dummy/src/org/chromium/dummy/Dummy.java b/build/android/pylib/remote/device/dummy/src/org/chromium/dummy/Dummy.java
new file mode 100644
index 00000000000..1281b39586e
--- /dev/null
+++ b/build/android/pylib/remote/device/dummy/src/org/chromium/dummy/Dummy.java
@@ -0,0 +1,9 @@
+// Copyright 2015 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.
+
+package org.chromium.dummy;
+
+/** Does nothing. */
+class Dummy {}
+
diff --git a/build/android/pylib/remote/device/remote_device_environment.py b/build/android/pylib/remote/device/remote_device_environment.py
new file mode 100644
index 00000000000..dc118455661
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_environment.py
@@ -0,0 +1,368 @@
+# Copyright 2014 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.
+
+"""Environment setup and teardown for remote devices."""
+
+import distutils.version
+import json
+import logging
+import os
+import random
+import sys
+
+from pylib import constants
+from pylib.base import environment
+from pylib.remote.device import appurify_sanitized
+from pylib.remote.device import remote_device_helper
+from pylib.utils import timeout_retry
+from pylib.utils import reraiser_thread
+
+class RemoteDeviceEnvironment(environment.Environment):
+ """An environment for running on remote devices."""
+
+ _ENV_KEY = 'env'
+ _DEVICE_KEY = 'device'
+ _DEFAULT_RETRIES = 0
+
+ def __init__(self, args, error_func):
+ """Constructor.
+
+ Args:
+ args: Command line arguments.
+ error_func: error to show when using bad command line arguments.
+ """
+ super(RemoteDeviceEnvironment, self).__init__()
+ self._access_token = None
+ self._device = None
+ self._device_type = args.device_type
+ self._verbose_count = args.verbose_count
+ self._timeouts = {
+ 'queueing': 60 * 10,
+ 'installing': 60 * 10,
+ 'in-progress': 60 * 30,
+ 'unknown': 60 * 5
+ }
+ # Example config file:
+ # {
+ # "remote_device": ["Galaxy S4", "Galaxy S3"],
+ # "remote_device_os": ["4.4.2", "4.4.4"],
+ # "remote_device_minimum_os": "4.4.2",
+ # "api_address": "www.example.com",
+ # "api_port": "80",
+ # "api_protocol": "http",
+ # "api_secret": "apisecret",
+ # "api_key": "apikey",
+ # "timeouts": {
+ # "queueing": 600,
+ # "installing": 600,
+ # "in-progress": 1800,
+ # "unknown": 300
+ # }
+ # }
+ if args.remote_device_file:
+ with open(args.remote_device_file) as device_file:
+ device_json = json.load(device_file)
+ else:
+ device_json = {}
+
+ self._api_address = device_json.get('api_address', None)
+ self._api_key = device_json.get('api_key', None)
+ self._api_port = device_json.get('api_port', None)
+ self._api_protocol = device_json.get('api_protocol', None)
+ self._api_secret = device_json.get('api_secret', None)
+ self._device_oem = device_json.get('device_oem', None)
+ self._device_type = device_json.get('device_type', 'Android')
+ self._network_config = device_json.get('network_config', None)
+ self._remote_device = device_json.get('remote_device', None)
+ self._remote_device_minimum_os = device_json.get(
+ 'remote_device_minimum_os', None)
+ self._remote_device_os = device_json.get('remote_device_os', None)
+ self._remote_device_timeout = device_json.get(
+ 'remote_device_timeout', None)
+ self._results_path = device_json.get('results_path', None)
+ self._runner_package = device_json.get('runner_package', None)
+ self._runner_type = device_json.get('runner_type', None)
+ self._timeouts.update(device_json.get('timeouts', {}))
+
+ def command_line_override(
+ file_value, cmd_line_value, desc, print_value=True):
+ if cmd_line_value:
+ if file_value and file_value != cmd_line_value:
+ if print_value:
+ logging.info('Overriding %s from %s to %s',
+ desc, file_value, cmd_line_value)
+ else:
+ logging.info('overriding %s', desc)
+ return cmd_line_value
+ return file_value
+
+ self._api_address = command_line_override(
+ self._api_address, args.api_address, 'api_address')
+ self._api_port = command_line_override(
+ self._api_port, args.api_port, 'api_port')
+ self._api_protocol = command_line_override(
+ self._api_protocol, args.api_protocol, 'api_protocol')
+ self._device_oem = command_line_override(
+ self._device_oem, args.device_oem, 'device_oem')
+ self._device_type = command_line_override(
+ self._device_type, args.device_type, 'device_type')
+ self._network_config = command_line_override(
+ self._network_config, args.network_config, 'network_config')
+ self._remote_device = command_line_override(
+ self._remote_device, args.remote_device, 'remote_device')
+ self._remote_device_minimum_os = command_line_override(
+ self._remote_device_minimum_os, args.remote_device_minimum_os,
+ 'remote_device_minimum_os')
+ self._remote_device_os = command_line_override(
+ self._remote_device_os, args.remote_device_os, 'remote_device_os')
+ self._remote_device_timeout = command_line_override(
+ self._remote_device_timeout, args.remote_device_timeout,
+ 'remote_device_timeout')
+ self._results_path = command_line_override(
+ self._results_path, args.results_path, 'results_path')
+ self._runner_package = command_line_override(
+ self._runner_package, args.runner_package, 'runner_package')
+ self._runner_type = command_line_override(
+ self._runner_type, args.runner_type, 'runner_type')
+
+ if args.api_key_file:
+ with open(args.api_key_file) as api_key_file:
+ temp_key = api_key_file.read().strip()
+ self._api_key = command_line_override(
+ self._api_key, temp_key, 'api_key', print_value=False)
+ self._api_key = command_line_override(
+ self._api_key, args.api_key, 'api_key', print_value=False)
+
+ if args.api_secret_file:
+ with open(args.api_secret_file) as api_secret_file:
+ temp_secret = api_secret_file.read().strip()
+ self._api_secret = command_line_override(
+ self._api_secret, temp_secret, 'api_secret', print_value=False)
+ self._api_secret = command_line_override(
+ self._api_secret, args.api_secret, 'api_secret', print_value=False)
+
+ if not self._api_address:
+ error_func('Must set api address with --api-address'
+ ' or in --remote-device-file.')
+ if not self._api_key:
+ error_func('Must set api key with --api-key, --api-key-file'
+ ' or in --remote-device-file')
+ if not self._api_port:
+ error_func('Must set api port with --api-port'
+ ' or in --remote-device-file')
+ if not self._api_protocol:
+ error_func('Must set api protocol with --api-protocol'
+ ' or in --remote-device-file. Example: http')
+ if not self._api_secret:
+ error_func('Must set api secret with --api-secret, --api-secret-file'
+ ' or in --remote-device-file')
+
+ logging.info('Api address: %s', self._api_address)
+ logging.info('Api port: %s', self._api_port)
+ logging.info('Api protocol: %s', self._api_protocol)
+ logging.info('Remote device: %s', self._remote_device)
+ logging.info('Remote device minimum OS: %s',
+ self._remote_device_minimum_os)
+ logging.info('Remote device OS: %s', self._remote_device_os)
+ logging.info('Remote device OEM: %s', self._device_oem)
+ logging.info('Remote device type: %s', self._device_type)
+ logging.info('Remote device timout: %s', self._remote_device_timeout)
+ logging.info('Results Path: %s', self._results_path)
+ logging.info('Runner package: %s', self._runner_package)
+ logging.info('Runner type: %s', self._runner_type)
+ logging.info('Timeouts: %s', self._timeouts)
+
+ if not args.trigger and not args.collect:
+ self._trigger = True
+ self._collect = True
+ else:
+ self._trigger = args.trigger
+ self._collect = args.collect
+
+ def SetUp(self):
+ """Set up the test environment."""
+ os.environ['APPURIFY_API_PROTO'] = self._api_protocol
+ os.environ['APPURIFY_API_HOST'] = self._api_address
+ os.environ['APPURIFY_API_PORT'] = self._api_port
+ os.environ['APPURIFY_STATUS_BASE_URL'] = 'none'
+ self._GetAccessToken()
+ if self._trigger:
+ self._SelectDevice()
+
+ def TearDown(self):
+ """Teardown the test environment."""
+ self._RevokeAccessToken()
+
+ def __enter__(self):
+ """Set up the test run when used as a context manager."""
+ try:
+ self.SetUp()
+ return self
+ except:
+ self.__exit__(*sys.exc_info())
+ raise
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Tears down the test run when used as a context manager."""
+ self.TearDown()
+
+ def DumpTo(self, persisted_data):
+ env_data = {
+ self._DEVICE_KEY: self._device,
+ }
+ persisted_data[self._ENV_KEY] = env_data
+
+ def LoadFrom(self, persisted_data):
+ env_data = persisted_data[self._ENV_KEY]
+ self._device = env_data[self._DEVICE_KEY]
+
+ def _GetAccessToken(self):
+ """Generates access token for remote device service."""
+ logging.info('Generating remote service access token')
+ with appurify_sanitized.SanitizeLogging(self._verbose_count,
+ logging.WARNING):
+ access_token_results = appurify_sanitized.api.access_token_generate(
+ self._api_key, self._api_secret)
+ remote_device_helper.TestHttpResponse(access_token_results,
+ 'Unable to generate access token.')
+ self._access_token = access_token_results.json()['response']['access_token']
+
+ def _RevokeAccessToken(self):
+ """Destroys access token for remote device service."""
+ logging.info('Revoking remote service access token')
+ with appurify_sanitized.SanitizeLogging(self._verbose_count,
+ logging.WARNING):
+ revoke_token_results = appurify_sanitized.api.access_token_revoke(
+ self._access_token)
+ remote_device_helper.TestHttpResponse(revoke_token_results,
+ 'Unable to revoke access token.')
+
+ def _SelectDevice(self):
+ if self._remote_device_timeout:
+ try:
+ timeout_retry.Run(self._FindDeviceWithTimeout,
+ self._remote_device_timeout, self._DEFAULT_RETRIES)
+ except reraiser_thread.TimeoutError:
+ self._NoDeviceFound()
+ else:
+ if not self._FindDevice():
+ self._NoDeviceFound()
+
+ def _FindDevice(self):
+ """Find which device to use."""
+ logging.info('Finding device to run tests on.')
+ device_list = self._GetDeviceList()
+ random.shuffle(device_list)
+ for device in device_list:
+ if device['os_name'] != self._device_type:
+ continue
+ if self._remote_device and device['name'] not in self._remote_device:
+ continue
+ if (self._remote_device_os
+ and device['os_version'] not in self._remote_device_os):
+ continue
+ if self._device_oem and device['brand'] not in self._device_oem:
+ continue
+ if (self._remote_device_minimum_os
+ and distutils.version.LooseVersion(device['os_version'])
+ < distutils.version.LooseVersion(self._remote_device_minimum_os)):
+ continue
+ if device['has_available_device']:
+ logging.info('Found device: %s %s',
+ device['name'], device['os_version'])
+ self._device = device
+ return True
+ return False
+
+ def _FindDeviceWithTimeout(self):
+ """Find which device to use with timeout."""
+ timeout_retry.WaitFor(self._FindDevice, wait_period=1)
+
+ def _PrintAvailableDevices(self, device_list):
+ def compare_devices(a,b):
+ for key in ('os_version', 'name'):
+ c = cmp(a[key], b[key])
+ if c:
+ return c
+ return 0
+
+ logging.critical('Available %s Devices:', self._device_type)
+ logging.critical(
+ ' %s %s %s %s %s',
+ 'OS'.ljust(10),
+ 'Device Name'.ljust(30),
+ 'Available'.ljust(10),
+ 'Busy'.ljust(10),
+ 'All'.ljust(10))
+ devices = (d for d in device_list if d['os_name'] == self._device_type)
+ for d in sorted(devices, compare_devices):
+ logging.critical(
+ ' %s %s %s %s %s',
+ d['os_version'].ljust(10),
+ d['name'].ljust(30),
+ str(d['available_devices_count']).ljust(10),
+ str(d['busy_devices_count']).ljust(10),
+ str(d['all_devices_count']).ljust(10))
+
+ def _GetDeviceList(self):
+ with appurify_sanitized.SanitizeLogging(self._verbose_count,
+ logging.WARNING):
+ dev_list_res = appurify_sanitized.api.devices_list(self._access_token)
+ remote_device_helper.TestHttpResponse(dev_list_res,
+ 'Unable to generate access token.')
+ return dev_list_res.json()['response']
+
+ def _NoDeviceFound(self):
+ self._PrintAvailableDevices(self._GetDeviceList())
+ raise remote_device_helper.RemoteDeviceError(
+ 'No device found.', is_infra_error=True)
+
+ @property
+ def collect(self):
+ return self._collect
+
+ @property
+ def device_type_id(self):
+ return self._device['device_type_id']
+
+ @property
+ def network_config(self):
+ return self._network_config
+
+ @property
+ def only_output_failures(self):
+ # TODO(jbudorick): Remove this once b/18981674 is fixed.
+ return True
+
+ @property
+ def results_path(self):
+ return self._results_path
+
+ @property
+ def runner_package(self):
+ return self._runner_package
+
+ @property
+ def runner_type(self):
+ return self._runner_type
+
+ @property
+ def timeouts(self):
+ return self._timeouts
+
+ @property
+ def token(self):
+ return self._access_token
+
+ @property
+ def trigger(self):
+ return self._trigger
+
+ @property
+ def verbose_count(self):
+ return self._verbose_count
+
+ @property
+ def device_type(self):
+ return self._device_type
diff --git a/build/android/pylib/remote/device/remote_device_gtest_run.py b/build/android/pylib/remote/device/remote_device_gtest_run.py
new file mode 100644
index 00000000000..98d41e43021
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_gtest_run.py
@@ -0,0 +1,81 @@
+# Copyright 2014 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.
+
+"""Run specific test on specific environment."""
+
+import logging
+import os
+import sys
+import tempfile
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.remote.device import appurify_sanitized
+from pylib.remote.device import remote_device_test_run
+from pylib.remote.device import remote_device_helper
+
+
+_EXTRA_COMMAND_LINE_FILE = (
+ 'org.chromium.native_test.NativeTestActivity.CommandLineFile')
+
+
+class RemoteDeviceGtestTestRun(remote_device_test_run.RemoteDeviceTestRun):
+ """Run gtests and uirobot tests on a remote device."""
+
+ DEFAULT_RUNNER_PACKAGE = (
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner')
+
+ #override
+ def TestPackage(self):
+ return self._test_instance.suite
+
+ #override
+ def _TriggerSetUp(self):
+ """Set up the triggering of a test run."""
+ logging.info('Triggering test run.')
+
+ if self._env.runner_type:
+ logging.warning('Ignoring configured runner_type "%s"',
+ self._env.runner_type)
+
+ if not self._env.runner_package:
+ runner_package = self.DEFAULT_RUNNER_PACKAGE
+ logging.info('Using default runner package: %s',
+ self.DEFAULT_RUNNER_PACKAGE)
+ else:
+ runner_package = self._env.runner_package
+
+ dummy_app_path = os.path.join(
+ constants.GetOutDirectory(), 'apks', 'remote_device_dummy.apk')
+ with tempfile.NamedTemporaryFile(suffix='.flags.txt') as flag_file:
+ env_vars = {}
+ filter_string = self._test_instance._GenerateDisabledFilterString(None)
+ if filter_string:
+ flag_file.write('_ --gtest_filter=%s' % filter_string)
+ flag_file.flush()
+ env_vars[_EXTRA_COMMAND_LINE_FILE] = os.path.basename(flag_file.name)
+ self._test_instance._data_deps.append(
+ (os.path.abspath(flag_file.name), None))
+ self._AmInstrumentTestSetup(
+ dummy_app_path, self._test_instance.apk, runner_package,
+ environment_variables=env_vars)
+
+ _INSTRUMENTATION_STREAM_LEADER = 'INSTRUMENTATION_STATUS: stream='
+
+ #override
+ def _ParseTestResults(self):
+ logging.info('Parsing results from stdout.')
+ results = base_test_result.TestRunResults()
+ output = self._results['results']['output'].splitlines()
+ output = (l[len(self._INSTRUMENTATION_STREAM_LEADER):] for l in output
+ if l.startswith(self._INSTRUMENTATION_STREAM_LEADER))
+ results_list = self._test_instance.ParseGTestOutput(output)
+ results.AddResults(results_list)
+ if self._env.only_output_failures:
+ logging.info('See logcat for more results information.')
+ if not self._results['results']['pass']:
+ results.AddResult(base_test_result.BaseTestResult(
+ 'Remote Service detected error.',
+ base_test_result.ResultType.FAIL))
+ return results
diff --git a/build/android/pylib/remote/device/remote_device_helper.py b/build/android/pylib/remote/device/remote_device_helper.py
new file mode 100644
index 00000000000..896ae99fab9
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_helper.py
@@ -0,0 +1,24 @@
+# Copyright 2014 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.
+
+"""Common functions and Exceptions for remote_device_*"""
+
+from pylib.utils import base_error
+
+
+class RemoteDeviceError(base_error.BaseError):
+ """Exception to throw when problems occur with remote device service."""
+ pass
+
+
+def TestHttpResponse(response, error_msg):
+ """Checks the Http response from remote device service.
+
+ Args:
+ response: response dict from the remote device service.
+ error_msg: Error message to display if bad response is seen.
+ """
+ if response.status_code != 200:
+ raise RemoteDeviceError(
+ '%s (%d: %s)' % (error_msg, response.status_code, response.reason))
diff --git a/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py b/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py
new file mode 100644
index 00000000000..bcdb90c82f3
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py
@@ -0,0 +1,74 @@
+# Copyright 2015 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.
+
+"""Run specific test on specific environment."""
+
+import logging
+import os
+import tempfile
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.remote.device import remote_device_test_run
+from pylib.utils import apk_helper
+
+
+class RemoteDeviceInstrumentationTestRun(
+ remote_device_test_run.RemoteDeviceTestRun):
+ """Run instrumentation tests on a remote device."""
+
+ #override
+ def TestPackage(self):
+ return self._test_instance.test_package
+
+ #override
+ def _TriggerSetUp(self):
+ """Set up the triggering of a test run."""
+ logging.info('Triggering test run.')
+
+ with tempfile.NamedTemporaryFile(suffix='.txt') as test_list_file:
+ tests = self._test_instance.GetTests()
+ logging.debug('preparing to run %d instrumentation tests remotely:',
+ len(tests))
+ for t in tests:
+ test_name = '%s#%s' % (t['class'], t['method'])
+ logging.debug(' %s', test_name)
+ test_list_file.write('%s\n' % test_name)
+ test_list_file.flush()
+ self._test_instance._data_deps.append(
+ (os.path.abspath(test_list_file.name), None))
+
+ env_vars = self._test_instance.GetDriverEnvironmentVars(
+ test_list_file_path=test_list_file.name)
+ env_vars.update(self._test_instance.GetHttpServerEnvironmentVars())
+
+ logging.debug('extras:')
+ for k, v in env_vars.iteritems():
+ logging.debug(' %s: %s', k, v)
+
+ self._AmInstrumentTestSetup(
+ self._test_instance.apk_under_test,
+ self._test_instance.driver_apk,
+ self._test_instance.driver_name,
+ environment_variables=env_vars,
+ extra_apks=[self._test_instance.test_apk])
+
+ #override
+ def _ParseTestResults(self):
+ logging.info('Parsing results from stdout.')
+ r = base_test_result.TestRunResults()
+ result_code, result_bundle, statuses = (
+ self._test_instance.ParseAmInstrumentRawOutput(
+ self._results['results']['output'].splitlines()))
+ result = self._test_instance.GenerateTestResults(
+ result_code, result_bundle, statuses, 0, 0)
+
+ if isinstance(result, base_test_result.BaseTestResult):
+ r.AddResult(result)
+ elif isinstance(result, list):
+ r.AddResults(result)
+ else:
+ raise Exception('Unexpected result type: %s' % type(result).__name__)
+
+ return r
diff --git a/build/android/pylib/remote/device/remote_device_test_run.py b/build/android/pylib/remote/device/remote_device_test_run.py
new file mode 100644
index 00000000000..60cc73567b8
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_test_run.py
@@ -0,0 +1,308 @@
+# Copyright 2014 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.
+
+"""Run specific test on specific environment."""
+
+import json
+import logging
+import os
+import sys
+import tempfile
+import time
+import zipfile
+
+from pylib import constants
+from pylib.base import test_run
+from pylib.remote.device import appurify_constants
+from pylib.remote.device import appurify_sanitized
+from pylib.remote.device import remote_device_helper
+from pylib.utils import zip_utils
+
+class RemoteDeviceTestRun(test_run.TestRun):
+ """Run tests on a remote device."""
+
+ _TEST_RUN_KEY = 'test_run'
+ _TEST_RUN_ID_KEY = 'test_run_id'
+
+ WAIT_TIME = 5
+ COMPLETE = 'complete'
+ HEARTBEAT_INTERVAL = 300
+
+ def __init__(self, env, test_instance):
+ """Constructor.
+
+ Args:
+ env: Environment the tests will run in.
+ test_instance: The test that will be run.
+ """
+ super(RemoteDeviceTestRun, self).__init__(env, test_instance)
+ self._env = env
+ self._test_instance = test_instance
+ self._app_id = ''
+ self._test_id = ''
+ self._results = ''
+ self._test_run_id = ''
+
+ #override
+ def SetUp(self):
+ """Set up a test run."""
+ if self._env.trigger:
+ self._TriggerSetUp()
+ elif self._env.collect:
+ assert isinstance(self._env.collect, basestring), (
+ 'File for storing test_run_id must be a string.')
+ with open(self._env.collect, 'r') as persisted_data_file:
+ persisted_data = json.loads(persisted_data_file.read())
+ self._env.LoadFrom(persisted_data)
+ self.LoadFrom(persisted_data)
+
+ def _TriggerSetUp(self):
+ """Set up the triggering of a test run."""
+ raise NotImplementedError
+
+ #override
+ def RunTests(self):
+ """Run the test."""
+ if self._env.trigger:
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ test_start_res = appurify_sanitized.api.tests_run(
+ self._env.token, self._env.device_type_id, self._app_id,
+ self._test_id)
+ remote_device_helper.TestHttpResponse(
+ test_start_res, 'Unable to run test.')
+ self._test_run_id = test_start_res.json()['response']['test_run_id']
+ logging.info('Test run id: %s' % self._test_run_id)
+
+ if self._env.collect:
+ current_status = ''
+ timeout_counter = 0
+ heartbeat_counter = 0
+ while self._GetTestStatus(self._test_run_id) != self.COMPLETE:
+ if self._results['detailed_status'] != current_status:
+ logging.info('Test status: %s', self._results['detailed_status'])
+ current_status = self._results['detailed_status']
+ timeout_counter = 0
+ heartbeat_counter = 0
+ if heartbeat_counter > self.HEARTBEAT_INTERVAL:
+ logging.info('Test status: %s', self._results['detailed_status'])
+ heartbeat_counter = 0
+
+ timeout = self._env.timeouts.get(
+ current_status, self._env.timeouts['unknown'])
+ if timeout_counter > timeout:
+ raise remote_device_helper.RemoteDeviceError(
+ 'Timeout while in %s state for %s seconds'
+ % (current_status, timeout),
+ is_infra_error=True)
+ time.sleep(self.WAIT_TIME)
+ timeout_counter += self.WAIT_TIME
+ heartbeat_counter += self.WAIT_TIME
+ self._DownloadTestResults(self._env.results_path)
+
+ if self._results['results']['exception']:
+ raise remote_device_helper.RemoteDeviceError(
+ self._results['results']['exception'], is_infra_error=True)
+
+ return self._ParseTestResults()
+
+ #override
+ def TearDown(self):
+ """Tear down the test run."""
+ if self._env.collect:
+ self._CollectTearDown()
+ elif self._env.trigger:
+ assert isinstance(self._env.trigger, basestring), (
+ 'File for storing test_run_id must be a string.')
+ with open(self._env.trigger, 'w') as persisted_data_file:
+ persisted_data = {}
+ self.DumpTo(persisted_data)
+ self._env.DumpTo(persisted_data)
+ persisted_data_file.write(json.dumps(persisted_data))
+
+ def _CollectTearDown(self):
+ if self._GetTestStatus(self._test_run_id) != self.COMPLETE:
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ test_abort_res = appurify_sanitized.api.tests_abort(
+ self._env.token, self._test_run_id, reason='Test runner exiting.')
+ remote_device_helper.TestHttpResponse(test_abort_res,
+ 'Unable to abort test.')
+
+ def __enter__(self):
+ """Set up the test run when used as a context manager."""
+ self.SetUp()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Tear down the test run when used as a context manager."""
+ self.TearDown()
+
+ def DumpTo(self, persisted_data):
+ test_run_data = {
+ self._TEST_RUN_ID_KEY: self._test_run_id,
+ }
+ persisted_data[self._TEST_RUN_KEY] = test_run_data
+
+ def LoadFrom(self, persisted_data):
+ test_run_data = persisted_data[self._TEST_RUN_KEY]
+ self._test_run_id = test_run_data[self._TEST_RUN_ID_KEY]
+
+ def _ParseTestResults(self):
+ raise NotImplementedError
+
+ def _GetTestByName(self, test_name):
+ """Gets test_id for specific test.
+
+ Args:
+ test_name: Test to find the ID of.
+ """
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ test_list_res = appurify_sanitized.api.tests_list(self._env.token)
+ remote_device_helper.TestHttpResponse(test_list_res,
+ 'Unable to get tests list.')
+ for test in test_list_res.json()['response']:
+ if test['test_type'] == test_name:
+ return test['test_id']
+ raise remote_device_helper.RemoteDeviceError(
+ 'No test found with name %s' % (test_name))
+
+ def _DownloadTestResults(self, results_path):
+ """Download the test results from remote device service.
+
+ Args:
+ results_path: Path to download appurify results zipfile.
+ """
+ if results_path:
+ logging.info('Downloading results to %s.' % results_path)
+ if not os.path.exists(os.path.dirname(results_path)):
+ os.makedirs(os.path.dirname(results_path))
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ appurify_sanitized.utils.wget(self._results['results']['url'],
+ results_path)
+
+ def _GetTestStatus(self, test_run_id):
+ """Checks the state of the test, and sets self._results
+
+ Args:
+ test_run_id: Id of test on on remote service.
+ """
+
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ test_check_res = appurify_sanitized.api.tests_check_result(
+ self._env.token, test_run_id)
+ remote_device_helper.TestHttpResponse(test_check_res,
+ 'Unable to get test status.')
+ self._results = test_check_res.json()['response']
+ return self._results['status']
+
+ def _AmInstrumentTestSetup(self, app_path, test_path, runner_package,
+ environment_variables, extra_apks=None):
+ config = {'runner': runner_package}
+ if environment_variables:
+ config['environment_vars'] = ','.join(
+ '%s=%s' % (k, v) for k, v in environment_variables.iteritems())
+
+ self._app_id = self._UploadAppToDevice(app_path)
+
+ data_deps = self._test_instance.GetDataDependencies()
+ if data_deps:
+ with tempfile.NamedTemporaryFile(suffix='.zip') as test_with_deps:
+ sdcard_files = []
+ additional_apks = []
+ host_test = os.path.basename(test_path)
+ with zipfile.ZipFile(test_with_deps.name, 'w') as zip_file:
+ zip_file.write(test_path, host_test, zipfile.ZIP_DEFLATED)
+ for h, _ in data_deps:
+ if os.path.isdir(h):
+ zip_utils.WriteToZipFile(zip_file, h, '.')
+ sdcard_files.extend(os.listdir(h))
+ else:
+ zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h))
+ sdcard_files.append(os.path.basename(h))
+ for a in extra_apks or ():
+ zip_utils.WriteToZipFile(zip_file, a, os.path.basename(a));
+ additional_apks.append(os.path.basename(a))
+
+ config['sdcard_files'] = ','.join(sdcard_files)
+ config['host_test'] = host_test
+ if additional_apks:
+ config['additional_apks'] = ','.join(additional_apks)
+ self._test_id = self._UploadTestToDevice(
+ 'robotium', test_with_deps.name, app_id=self._app_id)
+ else:
+ self._test_id = self._UploadTestToDevice('robotium', test_path)
+
+ logging.info('Setting config: %s' % config)
+ appurify_configs = {}
+ if self._env.network_config:
+ appurify_configs['network'] = self._env.network_config
+ self._SetTestConfig('robotium', config, **appurify_configs)
+
+ def _UploadAppToDevice(self, app_path):
+ """Upload app to device."""
+ logging.info('Uploading %s to remote service as %s.', app_path,
+ self._test_instance.suite)
+ with open(app_path, 'rb') as apk_src:
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ upload_results = appurify_sanitized.api.apps_upload(
+ self._env.token, apk_src, 'raw', name=self._test_instance.suite)
+ remote_device_helper.TestHttpResponse(
+ upload_results, 'Unable to upload %s.' % app_path)
+ return upload_results.json()['response']['app_id']
+
+ def _UploadTestToDevice(self, test_type, test_path, app_id=None):
+ """Upload test to device
+ Args:
+ test_type: Type of test that is being uploaded. Ex. uirobot, gtest..
+ """
+ logging.info('Uploading %s to remote service.' % test_path)
+ with open(test_path, 'rb') as test_src:
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ upload_results = appurify_sanitized.api.tests_upload(
+ self._env.token, test_src, 'raw', test_type, app_id=app_id)
+ remote_device_helper.TestHttpResponse(upload_results,
+ 'Unable to upload %s.' % test_path)
+ return upload_results.json()['response']['test_id']
+
+ def _SetTestConfig(self, runner_type, runner_configs,
+ network=appurify_constants.NETWORK.WIFI_1_BAR,
+ pcap=0, profiler=0, videocapture=0):
+ """Generates and uploads config file for test.
+ Args:
+ runner_configs: Configs specific to the runner you are using.
+ network: Config to specify the network environment the devices running
+ the tests will be in.
+ pcap: Option to set the recording the of network traffic from the device.
+ profiler: Option to set the recording of CPU, memory, and network
+ transfer usage in the tests.
+ videocapture: Option to set video capture during the tests.
+
+ """
+ logging.info('Generating config file for test.')
+ with tempfile.TemporaryFile() as config:
+ config_data = [
+ '[appurify]',
+ 'network=%s' % network,
+ 'pcap=%s' % pcap,
+ 'profiler=%s' % profiler,
+ 'videocapture=%s' % videocapture,
+ '[%s]' % runner_type
+ ]
+ config_data.extend(
+ '%s=%s' % (k, v) for k, v in runner_configs.iteritems())
+ config.write(''.join('%s\n' % l for l in config_data))
+ config.flush()
+ config.seek(0)
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ config_response = appurify_sanitized.api.config_upload(
+ self._env.token, config, self._test_id)
+ remote_device_helper.TestHttpResponse(
+ config_response, 'Unable to upload test config.')
diff --git a/build/android/pylib/remote/device/remote_device_uirobot_test_run.py b/build/android/pylib/remote/device/remote_device_uirobot_test_run.py
new file mode 100644
index 00000000000..f818c989abb
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_uirobot_test_run.py
@@ -0,0 +1,88 @@
+# Copyright 2014 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.
+
+"""Run specific test on specific environment."""
+
+import logging
+import os
+import sys
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.remote.device import appurify_sanitized
+from pylib.remote.device import remote_device_test_run
+from pylib.remote.device import remote_device_helper
+
+
+class RemoteDeviceUirobotTestRun(remote_device_test_run.RemoteDeviceTestRun):
+ """Run uirobot tests on a remote device."""
+
+
+ def __init__(self, env, test_instance):
+ """Constructor.
+
+ Args:
+ env: Environment the tests will run in.
+ test_instance: The test that will be run.
+ """
+ super(RemoteDeviceUirobotTestRun, self).__init__(env, test_instance)
+
+ #override
+ def TestPackage(self):
+ return self._test_instance.package_name
+
+ #override
+ def _TriggerSetUp(self):
+ """Set up the triggering of a test run."""
+ logging.info('Triggering test run.')
+
+ if self._env.device_type == 'Android':
+ default_runner_type = 'android_robot'
+ elif self._env.device_type == 'iOS':
+ default_runner_type = 'ios_robot'
+ else:
+ raise remote_device_helper.RemoteDeviceError(
+ 'Unknown device type: %s' % self._env.device_type)
+
+ self._app_id = self._UploadAppToDevice(self._test_instance.app_under_test)
+ if not self._env.runner_type:
+ runner_type = default_runner_type
+ logging.info('Using default runner type: %s', default_runner_type)
+ else:
+ runner_type = self._env.runner_type
+
+ self._test_id = self._UploadTestToDevice(
+ 'android_robot', None, app_id=self._app_id)
+ config_body = {'duration': self._test_instance.minutes}
+ self._SetTestConfig(runner_type, config_body)
+
+
+ # TODO(rnephew): Switch to base class implementation when supported.
+ #override
+ def _UploadTestToDevice(self, test_type, test_path, app_id=None):
+ if test_path:
+ logging.info("Ignoring test path.")
+ data = {
+ 'access_token':self._env.token,
+ 'test_type':test_type,
+ 'app_id':app_id,
+ }
+ with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
+ logging.WARNING):
+ test_upload_res = appurify_sanitized.utils.post('tests/upload',
+ data, None)
+ remote_device_helper.TestHttpResponse(
+ test_upload_res, 'Unable to get UiRobot test id.')
+ return test_upload_res.json()['response']['test_id']
+
+ #override
+ def _ParseTestResults(self):
+ logging.info('Parsing results from remote service.')
+ results = base_test_result.TestRunResults()
+ if self._results['results']['pass']:
+ result_type = base_test_result.ResultType.PASS
+ else:
+ result_type = base_test_result.ResultType.FAIL
+ results.AddResult(base_test_result.BaseTestResult('uirobot', result_type))
+ return results
diff --git a/build/android/pylib/restart_adbd.sh b/build/android/pylib/restart_adbd.sh
new file mode 100755
index 00000000000..393b2ebac04
--- /dev/null
+++ b/build/android/pylib/restart_adbd.sh
@@ -0,0 +1,20 @@
+#!/system/bin/sh
+
+# Copyright 2014 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.
+
+# Android shell script to restart adbd on the device. This has to be run
+# atomically as a shell script because stopping adbd prevents further commands
+# from running (even if called in the same adb shell).
+
+trap '' HUP
+trap '' TERM
+trap '' PIPE
+
+function restart() {
+ stop adbd
+ start adbd
+}
+
+restart &
diff --git a/build/android/pylib/results/__init__.py b/build/android/pylib/results/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/results/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/results/flakiness_dashboard/__init__.py b/build/android/pylib/results/flakiness_dashboard/__init__.py
new file mode 100644
index 00000000000..4d6aabb953d
--- /dev/null
+++ b/build/android/pylib/results/flakiness_dashboard/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 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.
diff --git a/build/android/pylib/results/flakiness_dashboard/json_results_generator.py b/build/android/pylib/results/flakiness_dashboard/json_results_generator.py
new file mode 100644
index 00000000000..e5c433dac5e
--- /dev/null
+++ b/build/android/pylib/results/flakiness_dashboard/json_results_generator.py
@@ -0,0 +1,697 @@
+# Copyright 2014 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.
+
+#
+# Most of this file was ported over from Blink's
+# Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py
+# Tools/Scripts/webkitpy/common/net/file_uploader.py
+#
+
+import json
+import logging
+import mimetypes
+import os
+import time
+import urllib2
+
+_log = logging.getLogger(__name__)
+
+_JSON_PREFIX = 'ADD_RESULTS('
+_JSON_SUFFIX = ');'
+
+
+def HasJSONWrapper(string):
+ return string.startswith(_JSON_PREFIX) and string.endswith(_JSON_SUFFIX)
+
+
+def StripJSONWrapper(json_content):
+ # FIXME: Kill this code once the server returns json instead of jsonp.
+ if HasJSONWrapper(json_content):
+ return json_content[len(_JSON_PREFIX):len(json_content) - len(_JSON_SUFFIX)]
+ return json_content
+
+
+def WriteJSON(json_object, file_path, callback=None):
+ # Specify separators in order to get compact encoding.
+ json_string = json.dumps(json_object, separators=(',', ':'))
+ if callback:
+ json_string = callback + '(' + json_string + ');'
+ with open(file_path, 'w') as fp:
+ fp.write(json_string)
+
+
+def ConvertTrieToFlatPaths(trie, prefix=None):
+ """Flattens the trie of paths, prepending a prefix to each."""
+ result = {}
+ for name, data in trie.iteritems():
+ if prefix:
+ name = prefix + '/' + name
+
+ if len(data) and not 'results' in data:
+ result.update(ConvertTrieToFlatPaths(data, name))
+ else:
+ result[name] = data
+
+ return result
+
+
+def AddPathToTrie(path, value, trie):
+ """Inserts a single path and value into a directory trie structure."""
+ if not '/' in path:
+ trie[path] = value
+ return
+
+ directory, _slash, rest = path.partition('/')
+ if not directory in trie:
+ trie[directory] = {}
+ AddPathToTrie(rest, value, trie[directory])
+
+
+def TestTimingsTrie(individual_test_timings):
+ """Breaks a test name into dicts by directory
+
+ foo/bar/baz.html: 1ms
+ foo/bar/baz1.html: 3ms
+
+ becomes
+ foo: {
+ bar: {
+ baz.html: 1,
+ baz1.html: 3
+ }
+ }
+ """
+ trie = {}
+ for test_result in individual_test_timings:
+ test = test_result.test_name
+
+ AddPathToTrie(test, int(1000 * test_result.test_run_time), trie)
+
+ return trie
+
+
+class TestResult(object):
+ """A simple class that represents a single test result."""
+
+ # Test modifier constants.
+ (NONE, FAILS, FLAKY, DISABLED) = range(4)
+
+ def __init__(self, test, failed=False, elapsed_time=0):
+ self.test_name = test
+ self.failed = failed
+ self.test_run_time = elapsed_time
+
+ test_name = test
+ try:
+ test_name = test.split('.')[1]
+ except IndexError:
+ _log.warn('Invalid test name: %s.', test)
+
+ if test_name.startswith('FAILS_'):
+ self.modifier = self.FAILS
+ elif test_name.startswith('FLAKY_'):
+ self.modifier = self.FLAKY
+ elif test_name.startswith('DISABLED_'):
+ self.modifier = self.DISABLED
+ else:
+ self.modifier = self.NONE
+
+ def Fixable(self):
+ return self.failed or self.modifier == self.DISABLED
+
+
+class JSONResultsGeneratorBase(object):
+ """A JSON results generator for generic tests."""
+
+ MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750
+ # Min time (seconds) that will be added to the JSON.
+ MIN_TIME = 1
+
+ # Note that in non-chromium tests those chars are used to indicate
+ # test modifiers (FAILS, FLAKY, etc) but not actual test results.
+ PASS_RESULT = 'P'
+ SKIP_RESULT = 'X'
+ FAIL_RESULT = 'F'
+ FLAKY_RESULT = 'L'
+ NO_DATA_RESULT = 'N'
+
+ MODIFIER_TO_CHAR = {TestResult.NONE: PASS_RESULT,
+ TestResult.DISABLED: SKIP_RESULT,
+ TestResult.FAILS: FAIL_RESULT,
+ TestResult.FLAKY: FLAKY_RESULT}
+
+ VERSION = 4
+ VERSION_KEY = 'version'
+ RESULTS = 'results'
+ TIMES = 'times'
+ BUILD_NUMBERS = 'buildNumbers'
+ TIME = 'secondsSinceEpoch'
+ TESTS = 'tests'
+
+ FIXABLE_COUNT = 'fixableCount'
+ FIXABLE = 'fixableCounts'
+ ALL_FIXABLE_COUNT = 'allFixableCount'
+
+ RESULTS_FILENAME = 'results.json'
+ TIMES_MS_FILENAME = 'times_ms.json'
+ INCREMENTAL_RESULTS_FILENAME = 'incremental_results.json'
+
+ # line too long pylint: disable=line-too-long
+ URL_FOR_TEST_LIST_JSON = (
+ 'http://%s/testfile?builder=%s&name=%s&testlistjson=1&testtype=%s&master=%s')
+ # pylint: enable=line-too-long
+
+ def __init__(self, builder_name, build_name, build_number,
+ results_file_base_path, builder_base_url,
+ test_results_map, svn_repositories=None,
+ test_results_server=None,
+ test_type='',
+ master_name=''):
+ """Modifies the results.json file. Grabs it off the archive directory
+ if it is not found locally.
+
+ Args
+ builder_name: the builder name (e.g. Webkit).
+ build_name: the build name (e.g. webkit-rel).
+ build_number: the build number.
+ results_file_base_path: Absolute path to the directory containing the
+ results json file.
+ builder_base_url: the URL where we have the archived test results.
+ If this is None no archived results will be retrieved.
+ test_results_map: A dictionary that maps test_name to TestResult.
+ svn_repositories: A (json_field_name, svn_path) pair for SVN
+ repositories that tests rely on. The SVN revision will be
+ included in the JSON with the given json_field_name.
+ test_results_server: server that hosts test results json.
+ test_type: test type string (e.g. 'layout-tests').
+ master_name: the name of the buildbot master.
+ """
+ self._builder_name = builder_name
+ self._build_name = build_name
+ self._build_number = build_number
+ self._builder_base_url = builder_base_url
+ self._results_directory = results_file_base_path
+
+ self._test_results_map = test_results_map
+ self._test_results = test_results_map.values()
+
+ self._svn_repositories = svn_repositories
+ if not self._svn_repositories:
+ self._svn_repositories = {}
+
+ self._test_results_server = test_results_server
+ self._test_type = test_type
+ self._master_name = master_name
+
+ self._archived_results = None
+
+ def GenerateJSONOutput(self):
+ json_object = self.GetJSON()
+ if json_object:
+ file_path = (
+ os.path.join(
+ self._results_directory,
+ self.INCREMENTAL_RESULTS_FILENAME))
+ WriteJSON(json_object, file_path)
+
+ def GenerateTimesMSFile(self):
+ times = TestTimingsTrie(self._test_results_map.values())
+ file_path = os.path.join(self._results_directory, self.TIMES_MS_FILENAME)
+ WriteJSON(times, file_path)
+
+ def GetJSON(self):
+ """Gets the results for the results.json file."""
+ results_json = {}
+
+ if not results_json:
+ results_json, error = self._GetArchivedJSONResults()
+ if error:
+ # If there was an error don't write a results.json
+ # file at all as it would lose all the information on the
+ # bot.
+ _log.error('Archive directory is inaccessible. Not '
+ 'modifying or clobbering the results.json '
+ 'file: ' + str(error))
+ return None
+
+ builder_name = self._builder_name
+ if results_json and builder_name not in results_json:
+ _log.debug('Builder name (%s) is not in the results.json file.'
+ % builder_name)
+
+ self._ConvertJSONToCurrentVersion(results_json)
+
+ if builder_name not in results_json:
+ results_json[builder_name] = (
+ self._CreateResultsForBuilderJSON())
+
+ results_for_builder = results_json[builder_name]
+
+ if builder_name:
+ self._InsertGenericMetaData(results_for_builder)
+
+ self._InsertFailureSummaries(results_for_builder)
+
+ # Update the all failing tests with result type and time.
+ tests = results_for_builder[self.TESTS]
+ all_failing_tests = self._GetFailedTestNames()
+ all_failing_tests.update(ConvertTrieToFlatPaths(tests))
+
+ for test in all_failing_tests:
+ self._InsertTestTimeAndResult(test, tests)
+
+ return results_json
+
+ def SetArchivedResults(self, archived_results):
+ self._archived_results = archived_results
+
+ def UploadJSONFiles(self, json_files):
+ """Uploads the given json_files to the test_results_server (if the
+ test_results_server is given)."""
+ if not self._test_results_server:
+ return
+
+ if not self._master_name:
+ _log.error(
+ '--test-results-server was set, but --master-name was not. Not '
+ 'uploading JSON files.')
+ return
+
+ _log.info('Uploading JSON files for builder: %s', self._builder_name)
+ attrs = [('builder', self._builder_name),
+ ('testtype', self._test_type),
+ ('master', self._master_name)]
+
+ files = [(json_file, os.path.join(self._results_directory, json_file))
+ for json_file in json_files]
+
+ url = 'http://%s/testfile/upload' % self._test_results_server
+ # Set uploading timeout in case appengine server is having problems.
+ # 120 seconds are more than enough to upload test results.
+ uploader = _FileUploader(url, 120)
+ try:
+ response = uploader.UploadAsMultipartFormData(files, attrs)
+ if response:
+ if response.code == 200:
+ _log.info('JSON uploaded.')
+ else:
+ _log.debug(
+ "JSON upload failed, %d: '%s'" %
+ (response.code, response.read()))
+ else:
+ _log.error('JSON upload failed; no response returned')
+ except Exception, err:
+ _log.error('Upload failed: %s' % err)
+ return
+
+ def _GetTestTiming(self, test_name):
+ """Returns test timing data (elapsed time) in second
+ for the given test_name."""
+ if test_name in self._test_results_map:
+ # Floor for now to get time in seconds.
+ return int(self._test_results_map[test_name].test_run_time)
+ return 0
+
+ def _GetFailedTestNames(self):
+ """Returns a set of failed test names."""
+ return set([r.test_name for r in self._test_results if r.failed])
+
+ def _GetModifierChar(self, test_name):
+ """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT,
+ PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test modifier
+ for the given test_name.
+ """
+ if test_name not in self._test_results_map:
+ return self.__class__.NO_DATA_RESULT
+
+ test_result = self._test_results_map[test_name]
+ if test_result.modifier in self.MODIFIER_TO_CHAR.keys():
+ return self.MODIFIER_TO_CHAR[test_result.modifier]
+
+ return self.__class__.PASS_RESULT
+
+ def _get_result_char(self, test_name):
+ """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT,
+ PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test result
+ for the given test_name.
+ """
+ if test_name not in self._test_results_map:
+ return self.__class__.NO_DATA_RESULT
+
+ test_result = self._test_results_map[test_name]
+ if test_result.modifier == TestResult.DISABLED:
+ return self.__class__.SKIP_RESULT
+
+ if test_result.failed:
+ return self.__class__.FAIL_RESULT
+
+ return self.__class__.PASS_RESULT
+
+ def _GetSVNRevision(self, in_directory):
+ """Returns the svn revision for the given directory.
+
+ Args:
+ in_directory: The directory where svn is to be run.
+ """
+ # This is overridden in flakiness_dashboard_results_uploader.py.
+ raise NotImplementedError()
+
+ def _GetArchivedJSONResults(self):
+ """Download JSON file that only contains test
+ name list from test-results server. This is for generating incremental
+ JSON so the file generated has info for tests that failed before but
+ pass or are skipped from current run.
+
+ Returns (archived_results, error) tuple where error is None if results
+ were successfully read.
+ """
+ results_json = {}
+ old_results = None
+ error = None
+
+ if not self._test_results_server:
+ return {}, None
+
+ results_file_url = (self.URL_FOR_TEST_LIST_JSON %
+ (urllib2.quote(self._test_results_server),
+ urllib2.quote(self._builder_name),
+ self.RESULTS_FILENAME,
+ urllib2.quote(self._test_type),
+ urllib2.quote(self._master_name)))
+
+ try:
+ # FIXME: We should talk to the network via a Host object.
+ results_file = urllib2.urlopen(results_file_url)
+ old_results = results_file.read()
+ except urllib2.HTTPError, http_error:
+ # A non-4xx status code means the bot is hosed for some reason
+ # and we can't grab the results.json file off of it.
+ if http_error.code < 400 and http_error.code >= 500:
+ error = http_error
+ except urllib2.URLError, url_error:
+ error = url_error
+
+ if old_results:
+ # Strip the prefix and suffix so we can get the actual JSON object.
+ old_results = StripJSONWrapper(old_results)
+
+ try:
+ results_json = json.loads(old_results)
+ except Exception:
+ _log.debug('results.json was not valid JSON. Clobbering.')
+ # The JSON file is not valid JSON. Just clobber the results.
+ results_json = {}
+ else:
+ _log.debug('Old JSON results do not exist. Starting fresh.')
+ results_json = {}
+
+ return results_json, error
+
+ def _InsertFailureSummaries(self, results_for_builder):
+ """Inserts aggregate pass/failure statistics into the JSON.
+ This method reads self._test_results and generates
+ FIXABLE, FIXABLE_COUNT and ALL_FIXABLE_COUNT entries.
+
+ Args:
+ results_for_builder: Dictionary containing the test results for a
+ single builder.
+ """
+ # Insert the number of tests that failed or skipped.
+ fixable_count = len([r for r in self._test_results if r.Fixable()])
+ self._InsertItemIntoRawList(results_for_builder,
+ fixable_count, self.FIXABLE_COUNT)
+
+ # Create a test modifiers (FAILS, FLAKY etc) summary dictionary.
+ entry = {}
+ for test_name in self._test_results_map.iterkeys():
+ result_char = self._GetModifierChar(test_name)
+ entry[result_char] = entry.get(result_char, 0) + 1
+
+ # Insert the pass/skip/failure summary dictionary.
+ self._InsertItemIntoRawList(results_for_builder, entry,
+ self.FIXABLE)
+
+ # Insert the number of all the tests that are supposed to pass.
+ all_test_count = len(self._test_results)
+ self._InsertItemIntoRawList(results_for_builder,
+ all_test_count, self.ALL_FIXABLE_COUNT)
+
+ def _InsertItemIntoRawList(self, results_for_builder, item, key):
+ """Inserts the item into the list with the given key in the results for
+ this builder. Creates the list if no such list exists.
+
+ Args:
+ results_for_builder: Dictionary containing the test results for a
+ single builder.
+ item: Number or string to insert into the list.
+ key: Key in results_for_builder for the list to insert into.
+ """
+ if key in results_for_builder:
+ raw_list = results_for_builder[key]
+ else:
+ raw_list = []
+
+ raw_list.insert(0, item)
+ raw_list = raw_list[:self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG]
+ results_for_builder[key] = raw_list
+
+ def _InsertItemRunLengthEncoded(self, item, encoded_results):
+ """Inserts the item into the run-length encoded results.
+
+ Args:
+ item: String or number to insert.
+ encoded_results: run-length encoded results. An array of arrays, e.g.
+ [[3,'A'],[1,'Q']] encodes AAAQ.
+ """
+ if len(encoded_results) and item == encoded_results[0][1]:
+ num_results = encoded_results[0][0]
+ if num_results <= self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG:
+ encoded_results[0][0] = num_results + 1
+ else:
+ # Use a list instead of a class for the run-length encoding since
+ # we want the serialized form to be concise.
+ encoded_results.insert(0, [1, item])
+
+ def _InsertGenericMetaData(self, results_for_builder):
+ """ Inserts generic metadata (such as version number, current time etc)
+ into the JSON.
+
+ Args:
+ results_for_builder: Dictionary containing the test results for
+ a single builder.
+ """
+ self._InsertItemIntoRawList(results_for_builder,
+ self._build_number, self.BUILD_NUMBERS)
+
+ # Include SVN revisions for the given repositories.
+ for (name, path) in self._svn_repositories:
+ # Note: for JSON file's backward-compatibility we use 'chrome' rather
+ # than 'chromium' here.
+ lowercase_name = name.lower()
+ if lowercase_name == 'chromium':
+ lowercase_name = 'chrome'
+ self._InsertItemIntoRawList(results_for_builder,
+ self._GetSVNRevision(path),
+ lowercase_name + 'Revision')
+
+ self._InsertItemIntoRawList(results_for_builder,
+ int(time.time()),
+ self.TIME)
+
+ def _InsertTestTimeAndResult(self, test_name, tests):
+ """ Insert a test item with its results to the given tests dictionary.
+
+ Args:
+ tests: Dictionary containing test result entries.
+ """
+
+ result = self._get_result_char(test_name)
+ test_time = self._GetTestTiming(test_name)
+
+ this_test = tests
+ for segment in test_name.split('/'):
+ if segment not in this_test:
+ this_test[segment] = {}
+ this_test = this_test[segment]
+
+ if not len(this_test):
+ self._PopulateResultsAndTimesJSON(this_test)
+
+ if self.RESULTS in this_test:
+ self._InsertItemRunLengthEncoded(result, this_test[self.RESULTS])
+ else:
+ this_test[self.RESULTS] = [[1, result]]
+
+ if self.TIMES in this_test:
+ self._InsertItemRunLengthEncoded(test_time, this_test[self.TIMES])
+ else:
+ this_test[self.TIMES] = [[1, test_time]]
+
+ def _ConvertJSONToCurrentVersion(self, results_json):
+ """If the JSON does not match the current version, converts it to the
+ current version and adds in the new version number.
+ """
+ if self.VERSION_KEY in results_json:
+ archive_version = results_json[self.VERSION_KEY]
+ if archive_version == self.VERSION:
+ return
+ else:
+ archive_version = 3
+
+ # version 3->4
+ if archive_version == 3:
+ for results in results_json.values():
+ self._ConvertTestsToTrie(results)
+
+ results_json[self.VERSION_KEY] = self.VERSION
+
+ def _ConvertTestsToTrie(self, results):
+ if not self.TESTS in results:
+ return
+
+ test_results = results[self.TESTS]
+ test_results_trie = {}
+ for test in test_results.iterkeys():
+ single_test_result = test_results[test]
+ AddPathToTrie(test, single_test_result, test_results_trie)
+
+ results[self.TESTS] = test_results_trie
+
+ def _PopulateResultsAndTimesJSON(self, results_and_times):
+ results_and_times[self.RESULTS] = []
+ results_and_times[self.TIMES] = []
+ return results_and_times
+
+ def _CreateResultsForBuilderJSON(self):
+ results_for_builder = {}
+ results_for_builder[self.TESTS] = {}
+ return results_for_builder
+
+ def _RemoveItemsOverMaxNumberOfBuilds(self, encoded_list):
+ """Removes items from the run-length encoded list after the final
+ item that exceeds the max number of builds to track.
+
+ Args:
+ encoded_results: run-length encoded results. An array of arrays, e.g.
+ [[3,'A'],[1,'Q']] encodes AAAQ.
+ """
+ num_builds = 0
+ index = 0
+ for result in encoded_list:
+ num_builds = num_builds + result[0]
+ index = index + 1
+ if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG:
+ return encoded_list[:index]
+ return encoded_list
+
+ def _NormalizeResultsJSON(self, test, test_name, tests):
+ """ Prune tests where all runs pass or tests that no longer exist and
+ truncate all results to maxNumberOfBuilds.
+
+ Args:
+ test: ResultsAndTimes object for this test.
+ test_name: Name of the test.
+ tests: The JSON object with all the test results for this builder.
+ """
+ test[self.RESULTS] = self._RemoveItemsOverMaxNumberOfBuilds(
+ test[self.RESULTS])
+ test[self.TIMES] = self._RemoveItemsOverMaxNumberOfBuilds(
+ test[self.TIMES])
+
+ is_all_pass = self._IsResultsAllOfType(test[self.RESULTS],
+ self.PASS_RESULT)
+ is_all_no_data = self._IsResultsAllOfType(test[self.RESULTS],
+ self.NO_DATA_RESULT)
+ max_time = max([test_time[1] for test_time in test[self.TIMES]])
+
+ # Remove all passes/no-data from the results to reduce noise and
+ # filesize. If a test passes every run, but takes > MIN_TIME to run,
+ # don't throw away the data.
+ if is_all_no_data or (is_all_pass and max_time <= self.MIN_TIME):
+ del tests[test_name]
+
+ # method could be a function pylint: disable=R0201
+ def _IsResultsAllOfType(self, results, result_type):
+ """Returns whether all the results are of the given type
+ (e.g. all passes)."""
+ return len(results) == 1 and results[0][1] == result_type
+
+
+class _FileUploader(object):
+
+ def __init__(self, url, timeout_seconds):
+ self._url = url
+ self._timeout_seconds = timeout_seconds
+
+ def UploadAsMultipartFormData(self, files, attrs):
+ file_objs = []
+ for filename, path in files:
+ with file(path, 'rb') as fp:
+ file_objs.append(('file', filename, fp.read()))
+
+ # FIXME: We should use the same variable names for the formal and actual
+ # parameters.
+ content_type, data = _EncodeMultipartFormData(attrs, file_objs)
+ return self._UploadData(content_type, data)
+
+ def _UploadData(self, content_type, data):
+ start = time.time()
+ end = start + self._timeout_seconds
+ while time.time() < end:
+ try:
+ request = urllib2.Request(self._url, data,
+ {'Content-Type': content_type})
+ return urllib2.urlopen(request)
+ except urllib2.HTTPError as e:
+ _log.warn("Received HTTP status %s loading \"%s\". "
+ 'Retrying in 10 seconds...' % (e.code, e.filename))
+ time.sleep(10)
+
+
+def _GetMIMEType(filename):
+ return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+
+
+# FIXME: Rather than taking tuples, this function should take more
+# structured data.
+def _EncodeMultipartFormData(fields, files):
+ """Encode form fields for multipart/form-data.
+
+ Args:
+ fields: A sequence of (name, value) elements for regular form fields.
+ files: A sequence of (name, filename, value) elements for data to be
+ uploaded as files.
+ Returns:
+ (content_type, body) ready for httplib.HTTP instance.
+
+ Source:
+ http://code.google.com/p/rietveld/source/browse/trunk/upload.py
+ """
+ BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
+ CRLF = '\r\n'
+ lines = []
+
+ for key, value in fields:
+ lines.append('--' + BOUNDARY)
+ lines.append('Content-Disposition: form-data; name="%s"' % key)
+ lines.append('')
+ if isinstance(value, unicode):
+ value = value.encode('utf-8')
+ lines.append(value)
+
+ for key, filename, value in files:
+ lines.append('--' + BOUNDARY)
+ lines.append('Content-Disposition: form-data; name="%s"; '
+ 'filename="%s"' % (key, filename))
+ lines.append('Content-Type: %s' % _GetMIMEType(filename))
+ lines.append('')
+ if isinstance(value, unicode):
+ value = value.encode('utf-8')
+ lines.append(value)
+
+ lines.append('--' + BOUNDARY + '--')
+ lines.append('')
+ body = CRLF.join(lines)
+ content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
+ return content_type, body
diff --git a/build/android/pylib/results/flakiness_dashboard/results_uploader.py b/build/android/pylib/results/flakiness_dashboard/results_uploader.py
new file mode 100644
index 00000000000..b86d7acd70a
--- /dev/null
+++ b/build/android/pylib/results/flakiness_dashboard/results_uploader.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2012 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.
+
+"""Uploads the results to the flakiness dashboard server."""
+# pylint: disable=E1002,R0201
+
+import logging
+import os
+import shutil
+import tempfile
+import xml
+
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.results.flakiness_dashboard import json_results_generator
+from pylib.utils import repo_utils
+
+
+
+class JSONResultsGenerator(json_results_generator.JSONResultsGeneratorBase):
+ """Writes test results to a JSON file and handles uploading that file to
+ the test results server.
+ """
+ def __init__(self, builder_name, build_name, build_number, tmp_folder,
+ test_results_map, test_results_server, test_type, master_name):
+ super(JSONResultsGenerator, self).__init__(
+ builder_name=builder_name,
+ build_name=build_name,
+ build_number=build_number,
+ results_file_base_path=tmp_folder,
+ builder_base_url=None,
+ test_results_map=test_results_map,
+ svn_repositories=(('webkit', 'third_party/WebKit'),
+ ('chrome', '.')),
+ test_results_server=test_results_server,
+ test_type=test_type,
+ master_name=master_name)
+
+ #override
+ def _GetModifierChar(self, test_name):
+ if test_name not in self._test_results_map:
+ return self.__class__.NO_DATA_RESULT
+
+ return self._test_results_map[test_name].modifier
+
+ #override
+ def _GetSVNRevision(self, in_directory):
+ """Returns the git/svn revision for the given directory.
+
+ Args:
+ in_directory: The directory relative to src.
+ """
+ def _is_git_directory(in_directory):
+ """Returns true if the given directory is in a git repository.
+
+ Args:
+ in_directory: The directory path to be tested.
+ """
+ if os.path.exists(os.path.join(in_directory, '.git')):
+ return True
+ parent = os.path.dirname(in_directory)
+ if parent == constants.DIR_SOURCE_ROOT or parent == in_directory:
+ return False
+ return _is_git_directory(parent)
+
+ in_directory = os.path.join(constants.DIR_SOURCE_ROOT, in_directory)
+
+ if not os.path.exists(os.path.join(in_directory, '.svn')):
+ if _is_git_directory(in_directory):
+ return repo_utils.GetGitHeadSHA1(in_directory)
+ else:
+ return ''
+
+ output = cmd_helper.GetCmdOutput(['svn', 'info', '--xml'], cwd=in_directory)
+ try:
+ dom = xml.dom.minidom.parseString(output)
+ return dom.getElementsByTagName('entry')[0].getAttribute('revision')
+ except xml.parsers.expat.ExpatError:
+ return ''
+ return ''
+
+
+class ResultsUploader(object):
+ """Handles uploading buildbot tests results to the flakiness dashboard."""
+ def __init__(self, tests_type):
+ self._build_number = os.environ.get('BUILDBOT_BUILDNUMBER')
+ self._builder_name = os.environ.get('BUILDBOT_BUILDERNAME')
+ self._tests_type = tests_type
+
+ if not self._build_number or not self._builder_name:
+ raise Exception('You should not be uploading tests results to the server'
+ 'from your local machine.')
+
+ upstream = (tests_type != 'Chromium_Android_Instrumentation')
+ if upstream:
+ # TODO(frankf): Use factory properties (see buildbot/bb_device_steps.py)
+ # This requires passing the actual master name (e.g. 'ChromiumFYI' not
+ # 'chromium.fyi').
+ from slave import slave_utils # pylint: disable=F0401
+ self._build_name = slave_utils.SlaveBuildName(constants.DIR_SOURCE_ROOT)
+ self._master_name = slave_utils.GetActiveMaster()
+ else:
+ self._build_name = 'chromium-android'
+ buildbot_branch = os.environ.get('BUILDBOT_BRANCH')
+ if not buildbot_branch:
+ buildbot_branch = 'master'
+ else:
+ # Ensure there's no leading "origin/"
+ buildbot_branch = buildbot_branch[buildbot_branch.find('/') + 1:]
+ self._master_name = '%s-%s' % (self._build_name, buildbot_branch)
+
+ self._test_results_map = {}
+
+ def AddResults(self, test_results):
+ # TODO(frankf): Differentiate between fail/crash/timeouts.
+ conversion_map = [
+ (test_results.GetPass(), False,
+ json_results_generator.JSONResultsGeneratorBase.PASS_RESULT),
+ (test_results.GetFail(), True,
+ json_results_generator.JSONResultsGeneratorBase.FAIL_RESULT),
+ (test_results.GetCrash(), True,
+ json_results_generator.JSONResultsGeneratorBase.FAIL_RESULT),
+ (test_results.GetTimeout(), True,
+ json_results_generator.JSONResultsGeneratorBase.FAIL_RESULT),
+ (test_results.GetUnknown(), True,
+ json_results_generator.JSONResultsGeneratorBase.NO_DATA_RESULT),
+ ]
+
+ for results_list, failed, modifier in conversion_map:
+ for single_test_result in results_list:
+ test_result = json_results_generator.TestResult(
+ test=single_test_result.GetName(),
+ failed=failed,
+ elapsed_time=single_test_result.GetDuration() / 1000)
+ # The WebKit TestResult object sets the modifier it based on test name.
+ # Since we don't use the same test naming convention as WebKit the
+ # modifier will be wrong, so we need to overwrite it.
+ test_result.modifier = modifier
+
+ self._test_results_map[single_test_result.GetName()] = test_result
+
+ def Upload(self, test_results_server):
+ if not self._test_results_map:
+ return
+
+ tmp_folder = tempfile.mkdtemp()
+
+ try:
+ results_generator = JSONResultsGenerator(
+ builder_name=self._builder_name,
+ build_name=self._build_name,
+ build_number=self._build_number,
+ tmp_folder=tmp_folder,
+ test_results_map=self._test_results_map,
+ test_results_server=test_results_server,
+ test_type=self._tests_type,
+ master_name=self._master_name)
+
+ json_files = ["incremental_results.json", "times_ms.json"]
+ results_generator.GenerateJSONOutput()
+ results_generator.GenerateTimesMSFile()
+ results_generator.UploadJSONFiles(json_files)
+ except Exception as e:
+ logging.error("Uploading results to test server failed: %s." % e)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+
+def Upload(results, flakiness_dashboard_server, test_type):
+ """Reports test results to the flakiness dashboard for Chrome for Android.
+
+ Args:
+ results: test results.
+ flakiness_dashboard_server: the server to upload the results to.
+ test_type: the type of the tests (as displayed by the flakiness dashboard).
+ """
+ uploader = ResultsUploader(test_type)
+ uploader.AddResults(results)
+ uploader.Upload(flakiness_dashboard_server)
diff --git a/build/android/pylib/results/json_results.py b/build/android/pylib/results/json_results.py
new file mode 100644
index 00000000000..65664e3da09
--- /dev/null
+++ b/build/android/pylib/results/json_results.py
@@ -0,0 +1,139 @@
+# Copyright 2014 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.
+
+import json
+import logging
+
+from pylib.base import base_test_result
+
+
+def GenerateResultsDict(test_run_result):
+ """Create a results dict from |test_run_result| suitable for writing to JSON.
+ Args:
+ test_run_result: a base_test_result.TestRunResults object.
+ Returns:
+ A results dict that mirrors the one generated by
+ base/test/launcher/test_results_tracker.cc:SaveSummaryAsJSON.
+ """
+ # Example json output.
+ # {
+ # "global_tags": [],
+ # "all_tests": [
+ # "test1",
+ # "test2",
+ # ],
+ # "disabled_tests": [],
+ # "per_iteration_data": [
+ # {
+ # "test1": [
+ # {
+ # "status": "SUCCESS",
+ # "elapsed_time_ms": 1,
+ # "output_snippet": "",
+ # "output_snippet_base64": "",
+ # "losless_snippet": "",
+ # },
+ # ],
+ # "test2": [
+ # {
+ # "status": "FAILURE",
+ # "elapsed_time_ms": 12,
+ # "output_snippet": "",
+ # "output_snippet_base64": "",
+ # "losless_snippet": "",
+ # },
+ # ],
+ # },
+ # ],
+ # }
+
+ assert isinstance(test_run_result, base_test_result.TestRunResults)
+
+ def status_as_string(s):
+ if s == base_test_result.ResultType.PASS:
+ return 'SUCCESS'
+ elif s == base_test_result.ResultType.SKIP:
+ return 'SKIPPED'
+ elif s == base_test_result.ResultType.FAIL:
+ return 'FAILURE'
+ elif s == base_test_result.ResultType.CRASH:
+ return 'CRASH'
+ elif s == base_test_result.ResultType.TIMEOUT:
+ return 'TIMEOUT'
+ elif s == base_test_result.ResultType.UNKNOWN:
+ return 'UNKNOWN'
+
+ def generate_iteration_data(t):
+ return {
+ t.GetName(): [
+ {
+ 'status': status_as_string(t.GetType()),
+ 'elapsed_time_ms': t.GetDuration(),
+ 'output_snippet': '',
+ 'losless_snippet': '',
+ 'output_snippet_base64:': '',
+ }
+ ]
+ }
+
+ all_tests_tuple, per_iteration_data_tuple = zip(
+ *[(t.GetName(), generate_iteration_data(t))
+ for t in test_run_result.GetAll()])
+
+ return {
+ 'global_tags': [],
+ 'all_tests': list(all_tests_tuple),
+ # TODO(jbudorick): Add support for disabled tests within base_test_result.
+ 'disabled_tests': [],
+ 'per_iteration_data': list(per_iteration_data_tuple),
+ }
+
+
+def GenerateJsonResultsFile(test_run_result, file_path):
+ """Write |test_run_result| to JSON.
+
+ This emulates the format of the JSON emitted by
+ base/test/launcher/test_results_tracker.cc:SaveSummaryAsJSON.
+
+ Args:
+ test_run_result: a base_test_result.TestRunResults object.
+ file_path: The path to the JSON file to write.
+ """
+ with open(file_path, 'w') as json_result_file:
+ json_result_file.write(json.dumps(GenerateResultsDict(test_run_result)))
+
+
+def ParseResultsFromJson(json_results):
+ """Creates a list of BaseTestResult objects from JSON.
+
+ Args:
+ json_results: A JSON dict in the format created by
+ GenerateJsonResultsFile.
+ """
+
+ def string_as_status(s):
+ if s == 'SUCCESS':
+ return base_test_result.ResultType.PASS
+ elif s == 'SKIPPED':
+ return base_test_result.ResultType.SKIP
+ elif s == 'FAILURE':
+ return base_test_result.ResultType.FAIL
+ elif s == 'CRASH':
+ return base_test_result.ResultType.CRASH
+ elif s == 'TIMEOUT':
+ return base_test_result.ResultType.TIMEOUT
+ else:
+ return base_test_result.ResultType.UNKNOWN
+
+ results_list = []
+ testsuite_runs = json_results['per_iteration_data']
+ for testsuite_run in testsuite_runs:
+ for test, test_runs in testsuite_run.iteritems():
+ results_list.extend(
+ [base_test_result.BaseTestResult(test,
+ string_as_status(tr['status']),
+ duration=tr['elapsed_time_ms'])
+ for tr in test_runs])
+ return results_list
+
diff --git a/build/android/pylib/results/json_results_test.py b/build/android/pylib/results/json_results_test.py
new file mode 100755
index 00000000000..1bc730d4cf5
--- /dev/null
+++ b/build/android/pylib/results/json_results_test.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import unittest
+
+from pylib.base import base_test_result
+from pylib.results import json_results
+
+
+class JsonResultsTest(unittest.TestCase):
+
+ def testGenerateResultsDict_passedResult(self):
+ result = base_test_result.BaseTestResult(
+ 'test.package.TestName', base_test_result.ResultType.PASS)
+
+ all_results = base_test_result.TestRunResults()
+ all_results.AddResult(result)
+
+ results_dict = json_results.GenerateResultsDict(all_results)
+ self.assertEquals(
+ ['test.package.TestName'],
+ results_dict['all_tests'])
+ self.assertEquals(1, len(results_dict['per_iteration_data']))
+
+ iteration_result = results_dict['per_iteration_data'][0]
+ self.assertTrue('test.package.TestName' in iteration_result)
+ self.assertEquals(1, len(iteration_result['test.package.TestName']))
+
+ test_iteration_result = iteration_result['test.package.TestName'][0]
+ self.assertTrue('status' in test_iteration_result)
+ self.assertEquals('SUCCESS', test_iteration_result['status'])
+
+ def testGenerateResultsDict_skippedResult(self):
+ result = base_test_result.BaseTestResult(
+ 'test.package.TestName', base_test_result.ResultType.SKIP)
+
+ all_results = base_test_result.TestRunResults()
+ all_results.AddResult(result)
+
+ results_dict = json_results.GenerateResultsDict(all_results)
+ self.assertEquals(
+ ['test.package.TestName'],
+ results_dict['all_tests'])
+ self.assertEquals(1, len(results_dict['per_iteration_data']))
+
+ iteration_result = results_dict['per_iteration_data'][0]
+ self.assertTrue('test.package.TestName' in iteration_result)
+ self.assertEquals(1, len(iteration_result['test.package.TestName']))
+
+ test_iteration_result = iteration_result['test.package.TestName'][0]
+ self.assertTrue('status' in test_iteration_result)
+ self.assertEquals('SKIPPED', test_iteration_result['status'])
+
+ def testGenerateResultsDict_failedResult(self):
+ result = base_test_result.BaseTestResult(
+ 'test.package.TestName', base_test_result.ResultType.FAIL)
+
+ all_results = base_test_result.TestRunResults()
+ all_results.AddResult(result)
+
+ results_dict = json_results.GenerateResultsDict(all_results)
+ self.assertEquals(
+ ['test.package.TestName'],
+ results_dict['all_tests'])
+ self.assertEquals(1, len(results_dict['per_iteration_data']))
+
+ iteration_result = results_dict['per_iteration_data'][0]
+ self.assertTrue('test.package.TestName' in iteration_result)
+ self.assertEquals(1, len(iteration_result['test.package.TestName']))
+
+ test_iteration_result = iteration_result['test.package.TestName'][0]
+ self.assertTrue('status' in test_iteration_result)
+ self.assertEquals('FAILURE', test_iteration_result['status'])
+
+ def testGenerateResultsDict_duration(self):
+ result = base_test_result.BaseTestResult(
+ 'test.package.TestName', base_test_result.ResultType.PASS, duration=123)
+
+ all_results = base_test_result.TestRunResults()
+ all_results.AddResult(result)
+
+ results_dict = json_results.GenerateResultsDict(all_results)
+ self.assertEquals(
+ ['test.package.TestName'],
+ results_dict['all_tests'])
+ self.assertEquals(1, len(results_dict['per_iteration_data']))
+
+ iteration_result = results_dict['per_iteration_data'][0]
+ self.assertTrue('test.package.TestName' in iteration_result)
+ self.assertEquals(1, len(iteration_result['test.package.TestName']))
+
+ test_iteration_result = iteration_result['test.package.TestName'][0]
+ self.assertTrue('elapsed_time_ms' in test_iteration_result)
+ self.assertEquals(123, test_iteration_result['elapsed_time_ms'])
+
+ def testGenerateResultsDict_multipleResults(self):
+ result1 = base_test_result.BaseTestResult(
+ 'test.package.TestName1', base_test_result.ResultType.PASS)
+ result2 = base_test_result.BaseTestResult(
+ 'test.package.TestName2', base_test_result.ResultType.PASS)
+
+ all_results = base_test_result.TestRunResults()
+ all_results.AddResult(result1)
+ all_results.AddResult(result2)
+
+ results_dict = json_results.GenerateResultsDict(all_results)
+ self.assertEquals(
+ ['test.package.TestName1', 'test.package.TestName2'],
+ results_dict['all_tests'])
+ self.assertEquals(2, len(results_dict['per_iteration_data']))
+
+ expected_tests = set([
+ 'test.package.TestName1',
+ 'test.package.TestName2',
+ ])
+
+ for iteration_result in results_dict['per_iteration_data']:
+ self.assertEquals(1, len(iteration_result))
+ name = iteration_result.keys()[0]
+ self.assertTrue(name in expected_tests)
+ expected_tests.remove(name)
+ self.assertEquals(1, len(iteration_result[name]))
+
+ test_iteration_result = iteration_result[name][0]
+ self.assertTrue('status' in test_iteration_result)
+ self.assertEquals('SUCCESS', test_iteration_result['status'])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/results/report_results.py b/build/android/pylib/results/report_results.py
new file mode 100644
index 00000000000..4fc6aa0b5cf
--- /dev/null
+++ b/build/android/pylib/results/report_results.py
@@ -0,0 +1,114 @@
+# Copyright (c) 2013 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.
+
+"""Module containing utility functions for reporting results."""
+
+import logging
+import os
+import re
+
+from pylib import constants
+from pylib.results.flakiness_dashboard import results_uploader
+
+
+def _LogToFile(results, test_type, suite_name):
+ """Log results to local files which can be used for aggregation later."""
+ log_file_path = os.path.join(constants.GetOutDirectory(), 'test_logs')
+ if not os.path.exists(log_file_path):
+ os.mkdir(log_file_path)
+ full_file_name = os.path.join(
+ log_file_path, re.sub(r'\W', '_', test_type).lower() + '.log')
+ if not os.path.exists(full_file_name):
+ with open(full_file_name, 'w') as log_file:
+ print >> log_file, '\n%s results for %s build %s:' % (
+ test_type, os.environ.get('BUILDBOT_BUILDERNAME'),
+ os.environ.get('BUILDBOT_BUILDNUMBER'))
+ logging.info('Writing results to %s.' % full_file_name)
+
+ logging.info('Writing results to %s.' % full_file_name)
+ with open(full_file_name, 'a') as log_file:
+ shortened_suite_name = suite_name[:25] + (suite_name[25:] and '...')
+ print >> log_file, '%s%s' % (shortened_suite_name.ljust(30),
+ results.GetShortForm())
+
+
+def _LogToFlakinessDashboard(results, test_type, test_package,
+ flakiness_server):
+ """Upload results to the flakiness dashboard"""
+ logging.info('Upload results for test type "%s", test package "%s" to %s' %
+ (test_type, test_package, flakiness_server))
+
+ try:
+ if test_type == 'Instrumentation':
+ if flakiness_server == constants.UPSTREAM_FLAKINESS_SERVER:
+ assert test_package in ['ContentShellTest',
+ 'ChromePublicTest',
+ 'ChromeShellTest',
+ 'ChromeSyncShellTest',
+ 'AndroidWebViewTest']
+ dashboard_test_type = ('%s_instrumentation_tests' %
+ test_package.lower().rstrip('test'))
+ # Downstream server.
+ else:
+ dashboard_test_type = 'Chromium_Android_Instrumentation'
+
+ elif test_type == 'Unit test':
+ dashboard_test_type = test_package
+
+ else:
+ logging.warning('Invalid test type')
+ return
+
+ results_uploader.Upload(
+ results, flakiness_server, dashboard_test_type)
+
+ except Exception as e:
+ logging.error(e)
+
+
+def LogFull(results, test_type, test_package, annotation=None,
+ flakiness_server=None):
+ """Log the tests results for the test suite.
+
+ The results will be logged three different ways:
+ 1. Log to stdout.
+ 2. Log to local files for aggregating multiple test steps
+ (on buildbots only).
+ 3. Log to flakiness dashboard (on buildbots only).
+
+ Args:
+ results: An instance of TestRunResults object.
+ test_type: Type of the test (e.g. 'Instrumentation', 'Unit test', etc.).
+ test_package: Test package name (e.g. 'ipc_tests' for gtests,
+ 'ContentShellTest' for instrumentation tests)
+ annotation: If instrumenation test type, this is a list of annotations
+ (e.g. ['Smoke', 'SmallTest']).
+ flakiness_server: If provider, upload the results to flakiness dashboard
+ with this URL.
+ """
+ if not results.DidRunPass():
+ logging.critical('*' * 80)
+ logging.critical('Detailed Logs')
+ logging.critical('*' * 80)
+ for line in results.GetLogs().splitlines():
+ logging.critical(line)
+ logging.critical('*' * 80)
+ logging.critical('Summary')
+ logging.critical('*' * 80)
+ for line in results.GetGtestForm().splitlines():
+ logging.critical(line)
+ logging.critical('*' * 80)
+
+ if os.environ.get('BUILDBOT_BUILDERNAME'):
+ # It is possible to have multiple buildbot steps for the same
+ # instrumenation test package using different annotations.
+ if annotation and len(annotation) == 1:
+ suite_name = annotation[0]
+ else:
+ suite_name = test_package
+ _LogToFile(results, test_type, suite_name)
+
+ if flakiness_server:
+ _LogToFlakinessDashboard(results, test_type, test_package,
+ flakiness_server)
diff --git a/build/android/pylib/screenshot.py b/build/android/pylib/screenshot.py
new file mode 100644
index 00000000000..0fcc59048ec
--- /dev/null
+++ b/build/android/pylib/screenshot.py
@@ -0,0 +1,99 @@
+# Copyright 2013 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.
+
+import logging
+import os
+import tempfile
+import time
+
+from pylib import cmd_helper
+from pylib import device_signal
+from pylib.device import device_errors
+
+# TODO(jbudorick) Remove once telemetry gets switched over.
+import pylib.android_commands
+import pylib.device.device_utils
+
+
+class VideoRecorder(object):
+ """Records a screen capture video from an Android Device (KitKat or newer).
+
+ Args:
+ device: DeviceUtils instance.
+ host_file: Path to the video file to store on the host.
+ megabits_per_second: Video bitrate in megabits per second. Allowed range
+ from 0.1 to 100 mbps.
+ size: Video frame size tuple (width, height) or None to use the device
+ default.
+ rotate: If True, the video will be rotated 90 degrees.
+ """
+ def __init__(self, device, megabits_per_second=4, size=None,
+ rotate=False):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, pylib.android_commands.AndroidCommands):
+ device = pylib.device.device_utils.DeviceUtils(device)
+ self._device = device
+ self._device_file = (
+ '%s/screen-recording.mp4' % device.GetExternalStoragePath())
+ self._recorder = None
+ self._recorder_stdout = None
+ self._is_started = False
+
+ self._args = ['adb']
+ if str(self._device):
+ self._args += ['-s', str(self._device)]
+ self._args += ['shell', 'screenrecord', '--verbose']
+ self._args += ['--bit-rate', str(megabits_per_second * 1000 * 1000)]
+ if size:
+ self._args += ['--size', '%dx%d' % size]
+ if rotate:
+ self._args += ['--rotate']
+ self._args += [self._device_file]
+
+ def Start(self):
+ """Start recording video."""
+ self._recorder_stdout = tempfile.mkstemp()[1]
+ self._recorder = cmd_helper.Popen(
+ self._args, stdout=open(self._recorder_stdout, 'w'))
+ if not self._device.GetPids('screenrecord'):
+ raise RuntimeError('Recording failed. Is your device running Android '
+ 'KitKat or later?')
+
+ def IsStarted(self):
+ if not self._is_started:
+ for line in open(self._recorder_stdout):
+ self._is_started = line.startswith('Content area is ')
+ if self._is_started:
+ break
+ return self._is_started
+
+ def Stop(self):
+ """Stop recording video."""
+ os.remove(self._recorder_stdout)
+ self._is_started = False
+ if not self._recorder:
+ return
+ if not self._device.KillAll('screenrecord', signum=device_signal.SIGINT,
+ quiet=True):
+ logging.warning('Nothing to kill: screenrecord was not running')
+ self._recorder.wait()
+
+ def Pull(self, host_file=None):
+ """Pull resulting video file from the device.
+
+ Args:
+ host_file: Path to the video file to store on the host.
+ Returns:
+ Output video file name on the host.
+ """
+ # TODO(jbudorick): Merge filename generation with the logic for doing so in
+ # DeviceUtils.
+ host_file_name = (
+ host_file
+ or 'screen-recording-%s.mp4' % time.strftime('%Y%m%dT%H%M%S',
+ time.localtime()))
+ host_file_name = os.path.abspath(host_file_name)
+ self._device.PullFile(self._device_file, host_file_name)
+ self._device.RunShellCommand('rm -f "%s"' % self._device_file)
+ return host_file_name
diff --git a/build/android/pylib/sdk/__init__.py b/build/android/pylib/sdk/__init__.py
new file mode 100644
index 00000000000..50b23dff631
--- /dev/null
+++ b/build/android/pylib/sdk/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2015 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.
diff --git a/build/android/pylib/sdk/aapt.py b/build/android/pylib/sdk/aapt.py
new file mode 100644
index 00000000000..3d317ff2c59
--- /dev/null
+++ b/build/android/pylib/sdk/aapt.py
@@ -0,0 +1,42 @@
+# Copyright 2015 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 module wraps the Android Asset Packaging Tool."""
+
+import os
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.utils import timeout_retry
+
+_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
+
+def _RunAaptCmd(args):
+ """Runs an aapt command.
+
+ Args:
+ args: A list of arguments for aapt.
+
+ Returns:
+ The output of the command.
+ """
+ cmd = [_AAPT_PATH] + args
+ status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
+ if status != 0:
+ raise Exception('Failed running aapt command: "%s" with output "%s".' %
+ (' '.join(cmd), output))
+ return output
+
+def Dump(what, apk, assets=None):
+ """Returns the output of the aapt dump command.
+
+ Args:
+ what: What you want to dump.
+ apk: Path to apk you want to dump information for.
+ assets: List of assets in apk you want to dump information for.
+ """
+ assets = assets or []
+ if isinstance(assets, basestring):
+ assets = [assets]
+ return _RunAaptCmd(['dump', what, apk] + assets).splitlines()
\ No newline at end of file
diff --git a/build/android/pylib/sdk/dexdump.py b/build/android/pylib/sdk/dexdump.py
new file mode 100644
index 00000000000..ec10aba0775
--- /dev/null
+++ b/build/android/pylib/sdk/dexdump.py
@@ -0,0 +1,30 @@
+# Copyright 2015 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.
+
+import os
+
+from pylib import cmd_helper
+from pylib import constants
+
+_DEXDUMP_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'dexdump')
+
+def DexDump(dexfiles, file_summary=False):
+ """A wrapper around the Android SDK's dexdump tool.
+
+ Args:
+ dexfiles: The dexfile or list of dex files to dump.
+ file_summary: Display summary information from the file header. (-f)
+
+ Returns:
+ An iterable over the output lines.
+ """
+ # TODO(jbudorick): Add support for more options as necessary.
+ if isinstance(dexfiles, basestring):
+ dexfiles = [dexfiles]
+ args = [_DEXDUMP_PATH] + dexfiles
+ if file_summary:
+ args.append('-f')
+
+ return cmd_helper.IterCmdOutputLines(args)
+
diff --git a/build/android/pylib/sdk/split_select.py b/build/android/pylib/sdk/split_select.py
new file mode 100644
index 00000000000..e2046621423
--- /dev/null
+++ b/build/android/pylib/sdk/split_select.py
@@ -0,0 +1,58 @@
+# Copyright 2015 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 module wraps Android's split-select tool."""
+
+import os
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.utils import timeout_retry
+
+_SPLIT_SELECT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'split-select')
+
+def _RunSplitSelectCmd(args):
+ """Runs a split-select command.
+
+ Args:
+ args: A list of arguments for split-select.
+
+ Returns:
+ The output of the command.
+ """
+ cmd = [_SPLIT_SELECT_PATH] + args
+ status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
+ if status != 0:
+ raise Exception('Failed running command "%s" with output "%s".' %
+ (' '.join(cmd), output))
+ return output
+
+def _SplitConfig(device):
+ """Returns a config specifying which APK splits are required by the device.
+
+ Args:
+ device: A DeviceUtils object.
+ """
+ return ('%s-r%s-%s:%s' %
+ (device.language,
+ device.country,
+ device.screen_density,
+ device.product_cpu_abi))
+
+def SelectSplits(device, base_apk, split_apks):
+ """Determines which APK splits the device requires.
+
+ Args:
+ device: A DeviceUtils object.
+ base_apk: The path of the base APK.
+ split_apks: A list of paths of APK splits.
+
+ Returns:
+ The list of APK splits that the device requires.
+ """
+ config = _SplitConfig(device)
+ args = ['--target', config, '--base', base_apk]
+ for split in split_apks:
+ args.extend(['--split', split])
+ return _RunSplitSelectCmd(args).splitlines()
\ No newline at end of file
diff --git a/build/android/pylib/symbols/PRESUBMIT.py b/build/android/pylib/symbols/PRESUBMIT.py
new file mode 100644
index 00000000000..b4d94ae225b
--- /dev/null
+++ b/build/android/pylib/symbols/PRESUBMIT.py
@@ -0,0 +1,21 @@
+# Copyright 2014 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.
+
+def CommonChecks(input_api, output_api):
+ output = []
+ output.extend(input_api.canned_checks.RunPylint(input_api, output_api))
+ output.extend(input_api.canned_checks.RunUnitTestsInDirectory(
+ input_api,
+ output_api,
+ input_api.PresubmitLocalPath(),
+ whitelist=[r'^.+_unittest\.py$']))
+ return output
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
\ No newline at end of file
diff --git a/build/android/pylib/symbols/__init__.py b/build/android/pylib/symbols/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/build/android/pylib/symbols/elf_symbolizer.py b/build/android/pylib/symbols/elf_symbolizer.py
new file mode 100644
index 00000000000..374063a57f8
--- /dev/null
+++ b/build/android/pylib/symbols/elf_symbolizer.py
@@ -0,0 +1,467 @@
+# Copyright 2014 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.
+
+import collections
+import datetime
+import logging
+import multiprocessing
+import os
+import posixpath
+import Queue
+import re
+import subprocess
+import sys
+import threading
+import time
+
+
+# addr2line builds a possibly infinite memory cache that can exhaust
+# the computer's memory if allowed to grow for too long. This constant
+# controls how many lookups we do before restarting the process. 4000
+# gives near peak performance without extreme memory usage.
+ADDR2LINE_RECYCLE_LIMIT = 4000
+
+
+class ELFSymbolizer(object):
+ """An uber-fast (multiprocessing, pipelined and asynchronous) ELF symbolizer.
+
+ This class is a frontend for addr2line (part of GNU binutils), designed to
+ symbolize batches of large numbers of symbols for a given ELF file. It
+ supports sharding symbolization against many addr2line instances and
+ pipelining of multiple requests per each instance (in order to hide addr2line
+ internals and OS pipe latencies).
+
+ The interface exhibited by this class is a very simple asynchronous interface,
+ which is based on the following three methods:
+ - SymbolizeAsync(): used to request (enqueue) resolution of a given address.
+ - The |callback| method: used to communicated back the symbol information.
+ - Join(): called to conclude the batch to gather the last outstanding results.
+ In essence, before the Join method returns, this class will have issued as
+ many callbacks as the number of SymbolizeAsync() calls. In this regard, note
+ that due to multiprocess sharding, callbacks can be delivered out of order.
+
+ Some background about addr2line:
+ - it is invoked passing the elf path in the cmdline, piping the addresses in
+ its stdin and getting results on its stdout.
+ - it has pretty large response times for the first requests, but it
+ works very well in streaming mode once it has been warmed up.
+ - it doesn't scale by itself (on more cores). However, spawning multiple
+ instances at the same time on the same file is pretty efficient as they
+ keep hitting the pagecache and become mostly CPU bound.
+ - it might hang or crash, mostly for OOM. This class deals with both of these
+ problems.
+
+ Despite the "scary" imports and the multi* words above, (almost) no multi-
+ threading/processing is involved from the python viewpoint. Concurrency
+ here is achieved by spawning several addr2line subprocesses and handling their
+ output pipes asynchronously. Therefore, all the code here (with the exception
+ of the Queue instance in Addr2Line) should be free from mind-blowing
+ thread-safety concerns.
+
+ The multiprocess sharding works as follows:
+ The symbolizer tries to use the lowest number of addr2line instances as
+ possible (with respect of |max_concurrent_jobs|) and enqueue all the requests
+ in a single addr2line instance. For few symbols (i.e. dozens) sharding isn't
+ worth the startup cost.
+ The multiprocess logic kicks in as soon as the queues for the existing
+ instances grow. Specifically, once all the existing instances reach the
+ |max_queue_size| bound, a new addr2line instance is kicked in.
+ In the case of a very eager producer (i.e. all |max_concurrent_jobs| instances
+ have a backlog of |max_queue_size|), back-pressure is applied on the caller by
+ blocking the SymbolizeAsync method.
+
+ This module has been deliberately designed to be dependency free (w.r.t. of
+ other modules in this project), to allow easy reuse in external projects.
+ """
+
+ def __init__(self, elf_file_path, addr2line_path, callback, inlines=False,
+ max_concurrent_jobs=None, addr2line_timeout=30, max_queue_size=50,
+ source_root_path=None, strip_base_path=None):
+ """Args:
+ elf_file_path: path of the elf file to be symbolized.
+ addr2line_path: path of the toolchain's addr2line binary.
+ callback: a callback which will be invoked for each resolved symbol with
+ the two args (sym_info, callback_arg). The former is an instance of
+ |ELFSymbolInfo| and contains the symbol information. The latter is an
+ embedder-provided argument which is passed to SymbolizeAsync().
+ inlines: when True, the ELFSymbolInfo will contain also the details about
+ the outer inlining functions. When False, only the innermost function
+ will be provided.
+ max_concurrent_jobs: Max number of addr2line instances spawned.
+ Parallelize responsibly, addr2line is a memory and I/O monster.
+ max_queue_size: Max number of outstanding requests per addr2line instance.
+ addr2line_timeout: Max time (in seconds) to wait for a addr2line response.
+ After the timeout, the instance will be considered hung and respawned.
+ source_root_path: In some toolchains only the name of the source file is
+ is output, without any path information; disambiguation searches
+ through the source directory specified by |source_root_path| argument
+ for files whose name matches, adding the full path information to the
+ output. For example, if the toolchain outputs "unicode.cc" and there
+ is a file called "unicode.cc" located under |source_root_path|/foo,
+ the tool will replace "unicode.cc" with
+ "|source_root_path|/foo/unicode.cc". If there are multiple files with
+ the same name, disambiguation will fail because the tool cannot
+ determine which of the files was the source of the symbol.
+ strip_base_path: Rebases the symbols source paths onto |source_root_path|
+ (i.e replace |strip_base_path| with |source_root_path).
+ """
+ assert(os.path.isfile(addr2line_path)), 'Cannot find ' + addr2line_path
+ self.elf_file_path = elf_file_path
+ self.addr2line_path = addr2line_path
+ self.callback = callback
+ self.inlines = inlines
+ self.max_concurrent_jobs = (max_concurrent_jobs or
+ min(multiprocessing.cpu_count(), 4))
+ self.max_queue_size = max_queue_size
+ self.addr2line_timeout = addr2line_timeout
+ self.requests_counter = 0 # For generating monotonic request IDs.
+ self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst.
+
+ # If necessary, create disambiguation lookup table
+ self.disambiguate = source_root_path is not None
+ self.disambiguation_table = {}
+ self.strip_base_path = strip_base_path
+ if(self.disambiguate):
+ self.source_root_path = os.path.abspath(source_root_path)
+ self._CreateDisambiguationTable()
+
+ # Create one addr2line instance. More instances will be created on demand
+ # (up to |max_concurrent_jobs|) depending on the rate of the requests.
+ self._CreateNewA2LInstance()
+
+ def SymbolizeAsync(self, addr, callback_arg=None):
+ """Requests symbolization of a given address.
+
+ This method is not guaranteed to return immediately. It generally does, but
+ in some scenarios (e.g. all addr2line instances have full queues) it can
+ block to create back-pressure.
+
+ Args:
+ addr: address to symbolize.
+ callback_arg: optional argument which will be passed to the |callback|."""
+ assert(isinstance(addr, int))
+
+ # Process all the symbols that have been resolved in the meanwhile.
+ # Essentially, this drains all the addr2line(s) out queues.
+ for a2l_to_purge in self._a2l_instances:
+ a2l_to_purge.ProcessAllResolvedSymbolsInQueue()
+ a2l_to_purge.RecycleIfNecessary()
+
+ # Find the best instance according to this logic:
+ # 1. Find an existing instance with the shortest queue.
+ # 2. If all of instances' queues are full, but there is room in the pool,
+ # (i.e. < |max_concurrent_jobs|) create a new instance.
+ # 3. If there were already |max_concurrent_jobs| instances and all of them
+ # had full queues, make back-pressure.
+
+ # 1.
+ def _SortByQueueSizeAndReqID(a2l):
+ return (a2l.queue_size, a2l.first_request_id)
+ a2l = min(self._a2l_instances, key=_SortByQueueSizeAndReqID)
+
+ # 2.
+ if (a2l.queue_size >= self.max_queue_size and
+ len(self._a2l_instances) < self.max_concurrent_jobs):
+ a2l = self._CreateNewA2LInstance()
+
+ # 3.
+ if a2l.queue_size >= self.max_queue_size:
+ a2l.WaitForNextSymbolInQueue()
+
+ a2l.EnqueueRequest(addr, callback_arg)
+
+ def Join(self):
+ """Waits for all the outstanding requests to complete and terminates."""
+ for a2l in self._a2l_instances:
+ a2l.WaitForIdle()
+ a2l.Terminate()
+
+ def _CreateNewA2LInstance(self):
+ assert(len(self._a2l_instances) < self.max_concurrent_jobs)
+ a2l = ELFSymbolizer.Addr2Line(self)
+ self._a2l_instances.append(a2l)
+ return a2l
+
+ def _CreateDisambiguationTable(self):
+ """ Non-unique file names will result in None entries"""
+ start_time = time.time()
+ logging.info('Collecting information about available source files...')
+ self.disambiguation_table = {}
+
+ for root, _, filenames in os.walk(self.source_root_path):
+ for f in filenames:
+ self.disambiguation_table[f] = os.path.join(root, f) if (f not in
+ self.disambiguation_table) else None
+ logging.info('Finished collecting information about '
+ 'possible files (took %.1f s).',
+ (time.time() - start_time))
+
+
+ class Addr2Line(object):
+ """A python wrapper around an addr2line instance.
+
+ The communication with the addr2line process looks as follows:
+ [STDIN] [STDOUT] (from addr2line's viewpoint)
+ > f001111
+ > f002222
+ < Symbol::Name(foo, bar) for f001111
+ < /path/to/source/file.c:line_number
+ > f003333
+ < Symbol::Name2() for f002222
+ < /path/to/source/file.c:line_number
+ < Symbol::Name3() for f003333
+ < /path/to/source/file.c:line_number
+ """
+
+ SYM_ADDR_RE = re.compile(r'([^:]+):(\?|\d+).*')
+
+ def __init__(self, symbolizer):
+ self._symbolizer = symbolizer
+ self._lib_file_name = posixpath.basename(symbolizer.elf_file_path)
+
+ # The request queue (i.e. addresses pushed to addr2line's stdin and not
+ # yet retrieved on stdout)
+ self._request_queue = collections.deque()
+
+ # This is essentially len(self._request_queue). It has been optimized to a
+ # separate field because turned out to be a perf hot-spot.
+ self.queue_size = 0
+
+ # Keep track of the number of symbols a process has processed to
+ # avoid a single process growing too big and using all the memory.
+ self._processed_symbols_count = 0
+
+ # Objects required to handle the addr2line subprocess.
+ self._proc = None # Subprocess.Popen(...) instance.
+ self._thread = None # Threading.thread instance.
+ self._out_queue = None # Queue.Queue instance (for buffering a2l stdout).
+ self._RestartAddr2LineProcess()
+
+ def EnqueueRequest(self, addr, callback_arg):
+ """Pushes an address to addr2line's stdin (and keeps track of it)."""
+ self._symbolizer.requests_counter += 1 # For global "age" of requests.
+ req_idx = self._symbolizer.requests_counter
+ self._request_queue.append((addr, callback_arg, req_idx))
+ self.queue_size += 1
+ self._WriteToA2lStdin(addr)
+
+ def WaitForIdle(self):
+ """Waits until all the pending requests have been symbolized."""
+ while self.queue_size > 0:
+ self.WaitForNextSymbolInQueue()
+
+ def WaitForNextSymbolInQueue(self):
+ """Waits for the next pending request to be symbolized."""
+ if not self.queue_size:
+ return
+
+ # This outer loop guards against a2l hanging (detecting stdout timeout).
+ while True:
+ start_time = datetime.datetime.now()
+ timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout)
+
+ # The inner loop guards against a2l crashing (checking if it exited).
+ while (datetime.datetime.now() - start_time < timeout):
+ # poll() returns !None if the process exited. a2l should never exit.
+ if self._proc.poll():
+ logging.warning('addr2line crashed, respawning (lib: %s).' %
+ self._lib_file_name)
+ self._RestartAddr2LineProcess()
+ # TODO(primiano): the best thing to do in this case would be
+ # shrinking the pool size as, very likely, addr2line is crashed
+ # due to low memory (and the respawned one will die again soon).
+
+ try:
+ lines = self._out_queue.get(block=True, timeout=0.25)
+ except Queue.Empty:
+ # On timeout (1/4 s.) repeat the inner loop and check if either the
+ # addr2line process did crash or we waited its output for too long.
+ continue
+
+ # In nominal conditions, we get straight to this point.
+ self._ProcessSymbolOutput(lines)
+ return
+
+ # If this point is reached, we waited more than |addr2line_timeout|.
+ logging.warning('Hung addr2line process, respawning (lib: %s).' %
+ self._lib_file_name)
+ self._RestartAddr2LineProcess()
+
+ def ProcessAllResolvedSymbolsInQueue(self):
+ """Consumes all the addr2line output lines produced (without blocking)."""
+ if not self.queue_size:
+ return
+ while True:
+ try:
+ lines = self._out_queue.get_nowait()
+ except Queue.Empty:
+ break
+ self._ProcessSymbolOutput(lines)
+
+ def RecycleIfNecessary(self):
+ """Restarts the process if it has been used for too long.
+
+ A long running addr2line process will consume excessive amounts
+ of memory without any gain in performance."""
+ if self._processed_symbols_count >= ADDR2LINE_RECYCLE_LIMIT:
+ self._RestartAddr2LineProcess()
+
+
+ def Terminate(self):
+ """Kills the underlying addr2line process.
+
+ The poller |_thread| will terminate as well due to the broken pipe."""
+ try:
+ self._proc.kill()
+ self._proc.communicate() # Essentially wait() without risking deadlock.
+ except Exception: # An exception while terminating? How interesting.
+ pass
+ self._proc = None
+
+ def _WriteToA2lStdin(self, addr):
+ self._proc.stdin.write('%s\n' % hex(addr))
+ if self._symbolizer.inlines:
+ # In the case of inlines we output an extra blank line, which causes
+ # addr2line to emit a (??,??:0) tuple that we use as a boundary marker.
+ self._proc.stdin.write('\n')
+ self._proc.stdin.flush()
+
+ def _ProcessSymbolOutput(self, lines):
+ """Parses an addr2line symbol output and triggers the client callback."""
+ (_, callback_arg, _) = self._request_queue.popleft()
+ self.queue_size -= 1
+
+ innermost_sym_info = None
+ sym_info = None
+ for (line1, line2) in lines:
+ prev_sym_info = sym_info
+ name = line1 if not line1.startswith('?') else None
+ source_path = None
+ source_line = None
+ m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2)
+ if m:
+ if not m.group(1).startswith('?'):
+ source_path = m.group(1)
+ if not m.group(2).startswith('?'):
+ source_line = int(m.group(2))
+ else:
+ logging.warning('Got invalid symbol path from addr2line: %s' % line2)
+
+ # In case disambiguation is on, and needed
+ was_ambiguous = False
+ disambiguated = False
+ if self._symbolizer.disambiguate:
+ if source_path and not posixpath.isabs(source_path):
+ path = self._symbolizer.disambiguation_table.get(source_path)
+ was_ambiguous = True
+ disambiguated = path is not None
+ source_path = path if disambiguated else source_path
+
+ # Use absolute paths (so that paths are consistent, as disambiguation
+ # uses absolute paths)
+ if source_path and not was_ambiguous:
+ source_path = os.path.abspath(source_path)
+
+ if source_path and self._symbolizer.strip_base_path:
+ # Strip the base path
+ source_path = re.sub('^' + self._symbolizer.strip_base_path,
+ self._symbolizer.source_root_path or '', source_path)
+
+ sym_info = ELFSymbolInfo(name, source_path, source_line, was_ambiguous,
+ disambiguated)
+ if prev_sym_info:
+ prev_sym_info.inlined_by = sym_info
+ if not innermost_sym_info:
+ innermost_sym_info = sym_info
+
+ self._processed_symbols_count += 1
+ self._symbolizer.callback(innermost_sym_info, callback_arg)
+
+ def _RestartAddr2LineProcess(self):
+ if self._proc:
+ self.Terminate()
+
+ # The only reason of existence of this Queue (and the corresponding
+ # Thread below) is the lack of a subprocess.stdout.poll_avail_lines().
+ # Essentially this is a pipe able to extract a couple of lines atomically.
+ self._out_queue = Queue.Queue()
+
+ # Start the underlying addr2line process in line buffered mode.
+
+ cmd = [self._symbolizer.addr2line_path, '--functions', '--demangle',
+ '--exe=' + self._symbolizer.elf_file_path]
+ if self._symbolizer.inlines:
+ cmd += ['--inlines']
+ self._proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE, stderr=sys.stderr, close_fds=True)
+
+ # Start the poller thread, which simply moves atomically the lines read
+ # from the addr2line's stdout to the |_out_queue|.
+ self._thread = threading.Thread(
+ target=ELFSymbolizer.Addr2Line.StdoutReaderThread,
+ args=(self._proc.stdout, self._out_queue, self._symbolizer.inlines))
+ self._thread.daemon = True # Don't prevent early process exit.
+ self._thread.start()
+
+ self._processed_symbols_count = 0
+
+ # Replay the pending requests on the new process (only for the case
+ # of a hung addr2line timing out during the game).
+ for (addr, _, _) in self._request_queue:
+ self._WriteToA2lStdin(addr)
+
+ @staticmethod
+ def StdoutReaderThread(process_pipe, queue, inlines):
+ """The poller thread fn, which moves the addr2line stdout to the |queue|.
+
+ This is the only piece of code not running on the main thread. It merely
+ writes to a Queue, which is thread-safe. In the case of inlines, it
+ detects the ??,??:0 marker and sends the lines atomically, such that the
+ main thread always receives all the lines corresponding to one symbol in
+ one shot."""
+ try:
+ lines_for_one_symbol = []
+ while True:
+ line1 = process_pipe.readline().rstrip('\r\n')
+ line2 = process_pipe.readline().rstrip('\r\n')
+ if not line1 or not line2:
+ break
+ inline_has_more_lines = inlines and (len(lines_for_one_symbol) == 0 or
+ (line1 != '??' and line2 != '??:0'))
+ if not inlines or inline_has_more_lines:
+ lines_for_one_symbol += [(line1, line2)]
+ if inline_has_more_lines:
+ continue
+ queue.put(lines_for_one_symbol)
+ lines_for_one_symbol = []
+ process_pipe.close()
+
+ # Every addr2line processes will die at some point, please die silently.
+ except (IOError, OSError):
+ pass
+
+ @property
+ def first_request_id(self):
+ """Returns the request_id of the oldest pending request in the queue."""
+ return self._request_queue[0][2] if self._request_queue else 0
+
+
+class ELFSymbolInfo(object):
+ """The result of the symbolization passed as first arg. of each callback."""
+
+ def __init__(self, name, source_path, source_line, was_ambiguous=False,
+ disambiguated=False):
+ """All the fields here can be None (if addr2line replies with '??')."""
+ self.name = name
+ self.source_path = source_path
+ self.source_line = source_line
+ # In the case of |inlines|=True, the |inlined_by| points to the outer
+ # function inlining the current one (and so on, to form a chain).
+ self.inlined_by = None
+ self.disambiguated = disambiguated
+ self.was_ambiguous = was_ambiguous
+
+ def __str__(self):
+ return '%s [%s:%d]' % (
+ self.name or '??', self.source_path or '??', self.source_line or 0)
diff --git a/build/android/pylib/symbols/elf_symbolizer_unittest.py b/build/android/pylib/symbols/elf_symbolizer_unittest.py
new file mode 100755
index 00000000000..e963a343df4
--- /dev/null
+++ b/build/android/pylib/symbols/elf_symbolizer_unittest.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import functools
+import logging
+import os
+import sys
+import unittest
+
+sys.path.insert(0, os.path.dirname(__file__))
+import elf_symbolizer
+import mock_addr2line
+
+
+_MOCK_A2L_PATH = os.path.join(os.path.dirname(mock_addr2line.__file__),
+ 'mock_addr2line')
+_INCOMPLETE_MOCK_ADDR = 1024 * 1024
+_UNKNOWN_MOCK_ADDR = 2 * 1024 * 1024
+_INLINE_MOCK_ADDR = 3 * 1024 * 1024
+
+
+class ELFSymbolizerTest(unittest.TestCase):
+ def setUp(self):
+ self._callback = functools.partial(
+ ELFSymbolizerTest._SymbolizeCallback, self)
+ self._resolved_addresses = set()
+ # Mute warnings, we expect them due to the crash/hang tests.
+ logging.getLogger().setLevel(logging.ERROR)
+
+ def testParallelism1(self):
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+
+ def testParallelism4(self):
+ self._RunTest(max_concurrent_jobs=4, num_symbols=100)
+
+ def testParallelism8(self):
+ self._RunTest(max_concurrent_jobs=8, num_symbols=100)
+
+ def testCrash(self):
+ os.environ['MOCK_A2L_CRASH_EVERY'] = '99'
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+ os.environ['MOCK_A2L_CRASH_EVERY'] = '0'
+
+ def testHang(self):
+ os.environ['MOCK_A2L_HANG_EVERY'] = '99'
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+ os.environ['MOCK_A2L_HANG_EVERY'] = '0'
+
+ def testInlines(self):
+ """Stimulate the inline processing logic."""
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ inlines=True,
+ max_concurrent_jobs=4)
+
+ for addr in xrange(1000):
+ exp_inline = False
+ exp_unknown = False
+
+ # First 100 addresses with inlines.
+ if addr < 100:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+
+ # Followed by 100 without inlines.
+ elif addr < 200:
+ pass
+
+ # Followed by 100 interleaved inlines and not inlines.
+ elif addr < 300:
+ if addr & 1:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+
+ # Followed by 100 interleaved inlines and unknonwn.
+ elif addr < 400:
+ if addr & 1:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+ else:
+ addr += _UNKNOWN_MOCK_ADDR
+ exp_unknown = True
+
+ exp_name = 'mock_sym_for_addr_%d' % addr if not exp_unknown else None
+ exp_source_path = 'mock_src/mock_lib1.so.c' if not exp_unknown else None
+ exp_source_line = addr if not exp_unknown else None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, exp_inline)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ def testIncompleteSyminfo(self):
+ """Stimulate the symbol-not-resolved logic."""
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ max_concurrent_jobs=1)
+
+ # Test symbols with valid name but incomplete path.
+ addr = _INCOMPLETE_MOCK_ADDR
+ exp_name = 'mock_sym_for_addr_%d' % addr
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ # Test symbols with no name or sym info.
+ addr = _UNKNOWN_MOCK_ADDR
+ exp_name = None
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ def _RunTest(self, max_concurrent_jobs, num_symbols):
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ max_concurrent_jobs=max_concurrent_jobs,
+ addr2line_timeout=0.5)
+
+ for addr in xrange(num_symbols):
+ exp_name = 'mock_sym_for_addr_%d' % addr
+ exp_source_path = 'mock_src/mock_lib1.so.c'
+ exp_source_line = addr
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ # Check that all the expected callbacks have been received.
+ for addr in xrange(num_symbols):
+ self.assertIn(addr, self._resolved_addresses)
+ self._resolved_addresses.remove(addr)
+
+ # Check for unexpected callbacks.
+ self.assertEqual(len(self._resolved_addresses), 0)
+
+ def _SymbolizeCallback(self, sym_info, cb_arg):
+ self.assertTrue(isinstance(sym_info, elf_symbolizer.ELFSymbolInfo))
+ self.assertTrue(isinstance(cb_arg, tuple))
+ self.assertEqual(len(cb_arg), 5)
+
+ # Unpack expectations from the callback extra argument.
+ (addr, exp_name, exp_source_path, exp_source_line, exp_inlines) = cb_arg
+ if exp_name is None:
+ self.assertIsNone(sym_info.name)
+ else:
+ self.assertTrue(sym_info.name.startswith(exp_name))
+ self.assertEqual(sym_info.source_path, exp_source_path)
+ self.assertEqual(sym_info.source_line, exp_source_line)
+
+ if exp_inlines:
+ self.assertEqual(sym_info.name, exp_name + '_inner')
+ self.assertEqual(sym_info.inlined_by.name, exp_name + '_middle')
+ self.assertEqual(sym_info.inlined_by.inlined_by.name,
+ exp_name + '_outer')
+
+ # Check against duplicate callbacks.
+ self.assertNotIn(addr, self._resolved_addresses)
+ self._resolved_addresses.add(addr)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/symbols/mock_addr2line/__init__.py b/build/android/pylib/symbols/mock_addr2line/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/build/android/pylib/symbols/mock_addr2line/mock_addr2line b/build/android/pylib/symbols/mock_addr2line/mock_addr2line
new file mode 100755
index 00000000000..cd58f56d576
--- /dev/null
+++ b/build/android/pylib/symbols/mock_addr2line/mock_addr2line
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Simple mock for addr2line.
+
+Outputs mock symbol information, with each symbol being a function of the
+original address (so it is easy to double-check consistency in unittests).
+"""
+
+import optparse
+import os
+import posixpath
+import sys
+import time
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-e', '--exe', dest='exe') # Path of the debug-library.so.
+ # Silently swallow the other unnecessary arguments.
+ parser.add_option('-C', '--demangle', action='store_true')
+ parser.add_option('-f', '--functions', action='store_true')
+ parser.add_option('-i', '--inlines', action='store_true')
+ options, _ = parser.parse_args(argv[1:])
+ lib_file_name = posixpath.basename(options.exe)
+ processed_sym_count = 0
+ crash_every = int(os.environ.get('MOCK_A2L_CRASH_EVERY', 0))
+ hang_every = int(os.environ.get('MOCK_A2L_HANG_EVERY', 0))
+
+ while(True):
+ line = sys.stdin.readline().rstrip('\r')
+ if not line:
+ break
+
+ # An empty line should generate '??,??:0' (is used as marker for inlines).
+ if line == '\n':
+ print '??'
+ print '??:0'
+ sys.stdout.flush()
+ continue
+
+ addr = int(line, 16)
+ processed_sym_count += 1
+ if crash_every and processed_sym_count % crash_every == 0:
+ sys.exit(1)
+ if hang_every and processed_sym_count % hang_every == 0:
+ time.sleep(1)
+
+ # Addresses < 1M will return good mock symbol information.
+ if addr < 1024 * 1024:
+ print 'mock_sym_for_addr_%d' % addr
+ print 'mock_src/%s.c:%d' % (lib_file_name, addr)
+
+ # Addresses 1M <= x < 2M will return symbols with a name but a missing path.
+ elif addr < 2 * 1024 * 1024:
+ print 'mock_sym_for_addr_%d' % addr
+ print '??:0'
+
+ # Addresses 2M <= x < 3M will return unknown symbol information.
+ elif addr < 3 * 1024 * 1024:
+ print '??'
+ print '??'
+
+ # Addresses 3M <= x < 4M will return inlines.
+ elif addr < 4 * 1024 * 1024:
+ print 'mock_sym_for_addr_%d_inner' % addr
+ print 'mock_src/%s.c:%d' % (lib_file_name, addr)
+ print 'mock_sym_for_addr_%d_middle' % addr
+ print 'mock_src/%s.c:%d' % (lib_file_name, addr)
+ print 'mock_sym_for_addr_%d_outer' % addr
+ print 'mock_src/%s.c:%d' % (lib_file_name, addr)
+
+ sys.stdout.flush()
+
+
+if __name__ == '__main__':
+ main(sys.argv)
\ No newline at end of file
diff --git a/build/android/pylib/system_properties.py b/build/android/pylib/system_properties.py
new file mode 100644
index 00000000000..3f16f86e77b
--- /dev/null
+++ b/build/android/pylib/system_properties.py
@@ -0,0 +1,40 @@
+# Copyright 2013 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.
+
+
+class SystemProperties(dict):
+
+ """A dict interface to interact with device system properties.
+
+ System properties are key/value pairs as exposed by adb shell getprop/setprop.
+
+ This implementation minimizes interaction with the physical device. It is
+ valid for the lifetime of a boot.
+ """
+
+ def __init__(self, android_commands):
+ super(SystemProperties, self).__init__()
+ self._adb = android_commands
+ self._cached_static_properties = {}
+
+ def __getitem__(self, key):
+ if self._IsStatic(key):
+ if key not in self._cached_static_properties:
+ self._cached_static_properties[key] = self._GetProperty(key)
+ return self._cached_static_properties[key]
+ return self._GetProperty(key)
+
+ def __setitem__(self, key, value):
+ # TODO(tonyg): This can fail with no root. Verify that it succeeds.
+ self._adb.SendShellCommand('setprop %s "%s"' % (key, value), retry_count=3)
+
+ @staticmethod
+ def _IsStatic(key):
+ # TODO(tonyg): This list is conservative and could be expanded as needed.
+ return (key.startswith('ro.boot.') or
+ key.startswith('ro.build.') or
+ key.startswith('ro.product.'))
+
+ def _GetProperty(self, key):
+ return self._adb.SendShellCommand('getprop %s' % key, retry_count=3).strip()
diff --git a/build/android/pylib/uiautomator/__init__.py b/build/android/pylib/uiautomator/__init__.py
new file mode 100644
index 00000000000..cda7672a8d0
--- /dev/null
+++ b/build/android/pylib/uiautomator/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2013 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.
+
diff --git a/build/android/pylib/uiautomator/setup.py b/build/android/pylib/uiautomator/setup.py
new file mode 100644
index 00000000000..bd8ffc732d9
--- /dev/null
+++ b/build/android/pylib/uiautomator/setup.py
@@ -0,0 +1,35 @@
+# Copyright 2013 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.
+
+"""Generates test runner factory and tests for uiautomator tests."""
+
+import logging
+
+from pylib.uiautomator import test_package
+from pylib.uiautomator import test_runner
+
+
+def Setup(test_options):
+ """Runs uiautomator tests on connected device(s).
+
+ Args:
+ test_options: A UIAutomatorOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+ test_pkg = test_package.TestPackage(test_options.uiautomator_jar,
+ test_options.uiautomator_info_jar)
+ tests = test_pkg.GetAllMatchingTests(test_options.annotations,
+ test_options.exclude_annotations,
+ test_options.test_filter)
+
+ if not tests:
+ logging.error('No uiautomator tests to run with current args.')
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.TestRunner(
+ test_options, device, shard_index, test_pkg)
+
+ return (TestRunnerFactory, tests)
diff --git a/build/android/pylib/uiautomator/test_options.py b/build/android/pylib/uiautomator/test_options.py
new file mode 100644
index 00000000000..3f5f9504e13
--- /dev/null
+++ b/build/android/pylib/uiautomator/test_options.py
@@ -0,0 +1,20 @@
+# Copyright 2013 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.
+
+"""Defines the UIAutomatorOptions named tuple."""
+
+import collections
+
+UIAutomatorOptions = collections.namedtuple('UIAutomatorOptions', [
+ 'tool',
+ 'annotations',
+ 'exclude_annotations',
+ 'test_filter',
+ 'test_data',
+ 'save_perf_json',
+ 'screenshot_failures',
+ 'uiautomator_jar',
+ 'uiautomator_info_jar',
+ 'package',
+ 'set_asserts'])
diff --git a/build/android/pylib/uiautomator/test_package.py b/build/android/pylib/uiautomator/test_package.py
new file mode 100644
index 00000000000..cb51fdfd65a
--- /dev/null
+++ b/build/android/pylib/uiautomator/test_package.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2013 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.
+
+"""Class representing uiautomator test package."""
+
+import os
+
+from pylib import constants
+from pylib.instrumentation import test_jar
+
+
+class TestPackage(test_jar.TestJar):
+
+ UIAUTOMATOR_PATH = 'uiautomator/'
+ UIAUTOMATOR_DEVICE_DIR = os.path.join(constants.TEST_EXECUTABLE_DIR,
+ UIAUTOMATOR_PATH)
+
+ def __init__(self, jar_path, jar_info_path):
+ test_jar.TestJar.__init__(self, jar_info_path)
+
+ if not os.path.exists(jar_path):
+ raise Exception('%s not found, please build it' % jar_path)
+ self._jar_path = jar_path
+
+ def GetPackageName(self):
+ """Returns the JAR named that is installed on the device."""
+ return os.path.basename(self._jar_path)
+
+ # Override.
+ def Install(self, device):
+ device.PushChangedFiles([(self._jar_path, self.UIAUTOMATOR_DEVICE_DIR +
+ self.GetPackageName())])
diff --git a/build/android/pylib/uiautomator/test_runner.py b/build/android/pylib/uiautomator/test_runner.py
new file mode 100644
index 00000000000..bda66877889
--- /dev/null
+++ b/build/android/pylib/uiautomator/test_runner.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2013 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.
+
+"""Class for running uiautomator tests on a single device."""
+
+from pylib import constants
+from pylib import flag_changer
+from pylib.device import intent
+from pylib.instrumentation import test_options as instr_test_options
+from pylib.instrumentation import test_runner as instr_test_runner
+
+
+class TestRunner(instr_test_runner.TestRunner):
+ """Responsible for running a series of tests connected to a single device."""
+
+ def __init__(self, test_options, device, shard_index, test_pkg):
+ """Create a new TestRunner.
+
+ Args:
+ test_options: A UIAutomatorOptions object.
+ device: Attached android device.
+ shard_index: Shard index.
+ test_pkg: A TestPackage object.
+ """
+ # Create an InstrumentationOptions object to pass to the super class
+ instrumentation_options = instr_test_options.InstrumentationOptions(
+ test_options.tool,
+ test_options.annotations,
+ test_options.exclude_annotations,
+ test_options.test_filter,
+ test_options.test_data,
+ test_options.save_perf_json,
+ test_options.screenshot_failures,
+ wait_for_debugger=False,
+ coverage_dir=None,
+ test_apk=None,
+ test_apk_path=None,
+ test_apk_jar_path=None,
+ test_runner=None,
+ test_support_apk_path=None,
+ device_flags=None,
+ isolate_file_path=None,
+ set_asserts=test_options.set_asserts,
+ delete_stale_data=False)
+ super(TestRunner, self).__init__(instrumentation_options, device,
+ shard_index, test_pkg)
+
+ cmdline_file = constants.PACKAGE_INFO[test_options.package].cmdline_file
+ self.flags = None
+ if cmdline_file:
+ self.flags = flag_changer.FlagChanger(self.device, cmdline_file)
+ self._package = constants.PACKAGE_INFO[test_options.package].package
+ self._activity = constants.PACKAGE_INFO[test_options.package].activity
+
+ #override
+ def InstallTestPackage(self):
+ self.test_pkg.Install(self.device)
+
+ #override
+ def _RunTest(self, test, timeout):
+ self.device.ClearApplicationState(self._package)
+ if self.flags:
+ annotations = self.test_pkg.GetTestAnnotations(test)
+ if 'FirstRunExperience' == annotations.get('Feature', None):
+ self.flags.RemoveFlags(['--disable-fre'])
+ else:
+ self.flags.AddFlags(['--disable-fre'])
+ self.device.StartActivity(
+ intent.Intent(action='android.intent.action.MAIN',
+ activity=self._activity,
+ package=self._package),
+ blocking=True,
+ force_stop=True)
+ cmd = ['uiautomator', 'runtest',
+ self.test_pkg.UIAUTOMATOR_PATH + self.test_pkg.GetPackageName(),
+ '-e', 'class', test,
+ '-e', 'test_package', self._package]
+ return self.device.RunShellCommand(cmd, timeout=timeout, retries=0)
+
+ #override
+ def _GenerateTestResult(self, test, _result_code, _result_bundle, statuses,
+ start_ms, duration_ms):
+ # uiautomator emits its summary status with INSTRUMENTATION_STATUS_CODE,
+ # not INSTRUMENTATION_CODE, so we have to drop if off the list of statuses.
+ summary_code, summary_bundle = statuses[-1]
+ return super(TestRunner, self)._GenerateTestResult(
+ test, summary_code, summary_bundle, statuses[:-1], start_ms,
+ duration_ms)
diff --git a/build/android/pylib/uirobot/__init__.py b/build/android/pylib/uirobot/__init__.py
new file mode 100644
index 00000000000..5cac026153c
--- /dev/null
+++ b/build/android/pylib/uirobot/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2014 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.
+
diff --git a/build/android/pylib/uirobot/uirobot_test_instance.py b/build/android/pylib/uirobot/uirobot_test_instance.py
new file mode 100644
index 00000000000..e3f6eb70589
--- /dev/null
+++ b/build/android/pylib/uirobot/uirobot_test_instance.py
@@ -0,0 +1,79 @@
+# Copyright 2014 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.
+
+import os
+import json
+import logging
+
+from pylib import constants
+from pylib.base import test_instance
+from pylib.utils import apk_helper
+
+class UirobotTestInstance(test_instance.TestInstance):
+
+ def __init__(self, args, error_func):
+ """Constructor.
+
+ Args:
+ args: Command line arguments.
+ """
+ super(UirobotTestInstance, self).__init__()
+ if not args.app_under_test:
+ error_func('Must set --app-under-test.')
+ self._app_under_test = args.app_under_test
+ self._minutes = args.minutes
+
+ if args.remote_device_file:
+ with open(args.remote_device_file) as remote_device_file:
+ device_json = json.load(remote_device_file)
+ else:
+ device_json = {}
+ device_type = device_json.get('device_type', 'Android')
+ if args.device_type:
+ if device_type and device_type != args.device_type:
+ logging.info('Overriding device_type from %s to %s',
+ device_type, args.device_type)
+ device_type = args.device_type
+
+ if device_type == 'Android':
+ self._suite = 'Android Uirobot'
+ self._package_name = apk_helper.GetPackageName(self._app_under_test)
+ elif device_type == 'iOS':
+ self._suite = 'iOS Uirobot'
+ self._package_name = self._app_under_test
+
+
+ #override
+ def TestType(self):
+ """Returns type of test."""
+ return 'uirobot'
+
+ #override
+ def SetUp(self):
+ """Setup for test."""
+ pass
+
+ #override
+ def TearDown(self):
+ """Teardown for test."""
+ pass
+
+ @property
+ def app_under_test(self):
+ """Returns the app to run the test on."""
+ return self._app_under_test
+
+ @property
+ def minutes(self):
+ """Returns the number of minutes to run the uirobot for."""
+ return self._minutes
+
+ @property
+ def package_name(self):
+ """Returns the name of the package in the APK."""
+ return self._package_name
+
+ @property
+ def suite(self):
+ return self._suite
diff --git a/build/android/pylib/utils/__init__.py b/build/android/pylib/utils/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/build/android/pylib/utils/apk_helper.py b/build/android/pylib/utils/apk_helper.py
new file mode 100644
index 00000000000..a556e7b31e6
--- /dev/null
+++ b/build/android/pylib/utils/apk_helper.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2013 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.
+
+"""Module containing utilities for apk packages."""
+
+import os.path
+import re
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.sdk import aapt
+
+
+_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
+_MANIFEST_ATTRIBUTE_RE = re.compile(
+ r'\s*A: ([^\(\)= ]*)\([^\(\)= ]*\)="(.*)" \(Raw: .*\)$')
+_MANIFEST_ELEMENT_RE = re.compile(r'\s*(?:E|N): (\S*) .*$')
+_PACKAGE_NAME_RE = re.compile(r'package: .*name=\'(\S*)\'')
+_SPLIT_NAME_RE = re.compile(r'package: .*split=\'(\S*)\'')
+
+
+def GetPackageName(apk_path):
+ """Returns the package name of the apk."""
+ return ApkHelper(apk_path).GetPackageName()
+
+
+# TODO(jbudorick): Deprecate and remove this function once callers have been
+# converted to ApkHelper.GetInstrumentationName
+def GetInstrumentationName(apk_path):
+ """Returns the name of the Instrumentation in the apk."""
+ return ApkHelper(apk_path).GetInstrumentationName()
+
+
+def _ParseManifestFromApk(apk_path):
+ aapt_output = aapt.Dump('xmltree', apk_path, 'AndroidManifest.xml')
+
+ parsed_manifest = {}
+ node_stack = [parsed_manifest]
+ indent = ' '
+
+ for line in aapt_output[1:]:
+ if len(line) == 0:
+ continue
+
+ indent_depth = 0
+ while line[(len(indent) * indent_depth):].startswith(indent):
+ indent_depth += 1
+
+ node_stack = node_stack[:indent_depth]
+ node = node_stack[-1]
+
+ m = _MANIFEST_ELEMENT_RE.match(line[len(indent) * indent_depth:])
+ if m:
+ if not m.group(1) in node:
+ node[m.group(1)] = {}
+ node_stack += [node[m.group(1)]]
+ continue
+
+ m = _MANIFEST_ATTRIBUTE_RE.match(line[len(indent) * indent_depth:])
+ if m:
+ if not m.group(1) in node:
+ node[m.group(1)] = []
+ node[m.group(1)].append(m.group(2))
+ continue
+
+ return parsed_manifest
+
+
+class ApkHelper(object):
+ def __init__(self, apk_path):
+ self._apk_path = apk_path
+ self._manifest = None
+ self._package_name = None
+ self._split_name = None
+
+ def GetActivityName(self):
+ """Returns the name of the Activity in the apk."""
+ manifest_info = self._GetManifest()
+ try:
+ activity = (
+ manifest_info['manifest']['application']['activity']
+ ['android:name'][0])
+ except KeyError:
+ return None
+ if '.' not in activity:
+ activity = '%s.%s' % (self.GetPackageName(), activity)
+ elif activity.startswith('.'):
+ activity = '%s%s' % (self.GetPackageName(), activity)
+ return activity
+
+ def GetInstrumentationName(
+ self, default='android.test.InstrumentationTestRunner'):
+ """Returns the name of the Instrumentation in the apk."""
+ manifest_info = self._GetManifest()
+ try:
+ return manifest_info['manifest']['instrumentation']['android:name'][0]
+ except KeyError:
+ return default
+
+ def GetPackageName(self):
+ """Returns the package name of the apk."""
+ if self._package_name:
+ return self._package_name
+
+ aapt_output = aapt.Dump('badging', self._apk_path)
+ for line in aapt_output:
+ m = _PACKAGE_NAME_RE.match(line)
+ if m:
+ self._package_name = m.group(1)
+ return self._package_name
+ raise Exception('Failed to determine package name of %s' % self._apk_path)
+
+ def GetSplitName(self):
+ """Returns the name of the split of the apk."""
+ if self._split_name:
+ return self._split_name
+
+ aapt_output = aapt.Dump('badging', self._apk_path)
+ for line in aapt_output:
+ m = _SPLIT_NAME_RE.match(line)
+ if m:
+ self._split_name = m.group(1)
+ return self._split_name
+ return None
+
+ def _GetManifest(self):
+ if not self._manifest:
+ self._manifest = _ParseManifestFromApk(self._apk_path)
+ return self._manifest
+
diff --git a/build/android/pylib/utils/base_error.py b/build/android/pylib/utils/base_error.py
new file mode 100644
index 00000000000..31eaa54c446
--- /dev/null
+++ b/build/android/pylib/utils/base_error.py
@@ -0,0 +1,16 @@
+# Copyright 2015 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.
+
+
+class BaseError(Exception):
+ """Base error for all test runner errors."""
+
+ def __init__(self, message, is_infra_error=False):
+ super(BaseError, self).__init__(message)
+ self._is_infra_error = is_infra_error
+
+ @property
+ def is_infra_error(self):
+ """Property to indicate if error was caused by an infrastructure issue."""
+ return self._is_infra_error
\ No newline at end of file
diff --git a/build/android/pylib/utils/command_option_parser.py b/build/android/pylib/utils/command_option_parser.py
new file mode 100644
index 00000000000..cf501d09df5
--- /dev/null
+++ b/build/android/pylib/utils/command_option_parser.py
@@ -0,0 +1,75 @@
+# Copyright 2013 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.
+
+"""An option parser which handles the first arg as a command.
+
+Add other nice functionality such as printing a list of commands
+and an example in usage.
+"""
+
+import optparse
+import sys
+
+
+class CommandOptionParser(optparse.OptionParser):
+ """Wrapper class for OptionParser to help with listing commands."""
+
+ def __init__(self, *args, **kwargs):
+ """Creates a CommandOptionParser.
+
+ Args:
+ commands_dict: A dictionary mapping command strings to an object defining
+ - add_options_func: Adds options to the option parser
+ - run_command_func: Runs the command itself.
+ example: An example command.
+ everything else: Passed to optparse.OptionParser contructor.
+ """
+ self.commands_dict = kwargs.pop('commands_dict', {})
+ self.example = kwargs.pop('example', '')
+ if not 'usage' in kwargs:
+ kwargs['usage'] = 'Usage: %prog [options]'
+ optparse.OptionParser.__init__(self, *args, **kwargs)
+
+ #override
+ def get_usage(self):
+ normal_usage = optparse.OptionParser.get_usage(self)
+ command_list = self.get_command_list()
+ example = self.get_example()
+ return self.expand_prog_name(normal_usage + example + command_list)
+
+ #override
+ def get_command_list(self):
+ if self.commands_dict.keys():
+ return '\nCommands:\n %s\n' % '\n '.join(
+ sorted(self.commands_dict.keys()))
+ return ''
+
+ def get_example(self):
+ if self.example:
+ return '\nExample:\n %s\n' % self.example
+ return ''
+
+
+def ParseAndExecute(option_parser, argv=None):
+ """Parses options/args from argv and runs the specified command.
+
+ Args:
+ option_parser: A CommandOptionParser object.
+ argv: Command line arguments. If None, automatically draw from sys.argv.
+
+ Returns:
+ An exit code.
+ """
+ if not argv:
+ argv = sys.argv
+
+ if len(argv) < 2 or argv[1] not in option_parser.commands_dict:
+ # Parse args first, if this is '--help', optparse will print help and exit
+ option_parser.parse_args(argv)
+ option_parser.error('Invalid command.')
+
+ cmd = option_parser.commands_dict[argv[1]]
+ cmd.add_options_func(option_parser)
+ options, args = option_parser.parse_args(argv)
+ return cmd.run_command_func(argv[1], options, args, option_parser)
diff --git a/build/android/pylib/utils/device_temp_file.py b/build/android/pylib/utils/device_temp_file.py
new file mode 100644
index 00000000000..7d3b95b1046
--- /dev/null
+++ b/build/android/pylib/utils/device_temp_file.py
@@ -0,0 +1,57 @@
+# Copyright 2013 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.
+
+"""A temp file that automatically gets pushed and deleted from a device."""
+
+# pylint: disable=W0622
+
+import random
+import time
+
+from pylib import cmd_helper
+from pylib.device import device_errors
+
+
+class DeviceTempFile(object):
+ def __init__(self, adb, suffix='', prefix='temp_file', dir='/data/local/tmp'):
+ """Find an unused temporary file path in the devices external directory.
+
+ When this object is closed, the file will be deleted on the device.
+
+ Args:
+ adb: An instance of AdbWrapper
+ suffix: The suffix of the name of the temp file.
+ prefix: The prefix of the name of the temp file.
+ dir: The directory on the device where to place the temp file.
+ """
+ self._adb = adb
+ # make sure that the temp dir is writable
+ self._adb.Shell('test -d %s' % cmd_helper.SingleQuote(dir))
+ while True:
+ self.name = '{dir}/{prefix}-{time:d}-{nonce:d}{suffix}'.format(
+ dir=dir, prefix=prefix, time=int(time.time()),
+ nonce=random.randint(0, 1000000), suffix=suffix)
+ self.name_quoted = cmd_helper.SingleQuote(self.name)
+ try:
+ self._adb.Shell('test -e %s' % self.name_quoted)
+ except device_errors.AdbCommandFailedError:
+ break # file does not exist
+
+ # Immediately touch the file, so other temp files can't get the same name.
+ self._adb.Shell('touch %s' % self.name_quoted)
+
+ def close(self):
+ """Deletes the temporary file from the device."""
+ # ignore exception if the file is already gone.
+ try:
+ self._adb.Shell('rm -f %s' % self.name_quoted)
+ except device_errors.AdbCommandFailedError:
+ # file does not exist on Android version without 'rm -f' support (ICS)
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
diff --git a/build/android/pylib/utils/device_temp_file_test.py b/build/android/pylib/utils/device_temp_file_test.py
new file mode 100755
index 00000000000..f839ce093ca
--- /dev/null
+++ b/build/android/pylib/utils/device_temp_file_test.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""
+Unit tests for the contents of device_temp_file.py.
+"""
+
+import logging
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+from pylib.utils import device_temp_file
+from pylib.utils import mock_calls
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+class DeviceTempFileTest(mock_calls.TestCase):
+
+ def setUp(self):
+ test_serial = '0123456789abcdef'
+ self.adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+ self.adb.__str__ = mock.Mock(return_value=test_serial)
+ self.watchMethodCalls(self.call.adb)
+
+ def mockShellCall(self, cmd_prefix, action=''):
+ """Expect an adb.Shell(cmd) call with cmd_prefix and do some action
+
+ Args:
+ cmd_prefix: A string, the cmd of the received call is expected to have
+ this as a prefix.
+ action: If callable, an action to perform when the expected call is
+ received, otherwise a return value.
+ Returns:
+ An (expected_call, action) pair suitable for use in assertCalls.
+ """
+ def check_and_return(cmd):
+ self.assertTrue(
+ cmd.startswith(cmd_prefix),
+ 'command %r does not start with prefix %r' % (cmd, cmd_prefix))
+ if callable(action):
+ return action(cmd)
+ else:
+ return action
+ return (self.call.adb.Shell(mock.ANY), check_and_return)
+
+ def mockExistsTest(self, exists_result):
+ def action(cmd):
+ if exists_result:
+ return ''
+ else:
+ raise device_errors.AdbCommandFailedError(
+ cmd, 'File not found', 1, str(self.adb))
+ return self.mockShellCall('test -e ', action)
+
+ def testTempFileNameAlreadyExists(self):
+ with self.assertCalls(
+ self.mockShellCall('test -d /data/local/tmp'),
+ self.mockExistsTest(True),
+ self.mockExistsTest(True),
+ self.mockExistsTest(True),
+ self.mockExistsTest(False),
+ self.mockShellCall('touch '),
+ self.mockShellCall('rm -f ')):
+ with device_temp_file.DeviceTempFile(self.adb) as tmpfile:
+ logging.debug('Temp file name: %s' % tmpfile.name)
+
+ def testTempFileLifecycle(self):
+ with self.assertCalls(
+ self.mockShellCall('test -d /data/local/tmp'),
+ self.mockExistsTest(False),
+ self.mockShellCall('touch ')):
+ tempFileContextManager = device_temp_file.DeviceTempFile(self.adb)
+ with mock.patch.object(self.adb, 'Shell'):
+ with tempFileContextManager as tmpfile:
+ logging.debug('Temp file name: %s' % tmpfile.name)
+ self.assertEquals(0, self.adb.Shell.call_count)
+ self.assertEquals(1, self.adb.Shell.call_count)
+ args, _ = self.adb.Shell.call_args
+ self.assertTrue(args[0].startswith('rm -f '))
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/build/android/pylib/utils/emulator.py b/build/android/pylib/utils/emulator.py
new file mode 100644
index 00000000000..cc07e6139d7
--- /dev/null
+++ b/build/android/pylib/utils/emulator.py
@@ -0,0 +1,444 @@
+# Copyright (c) 2012 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.
+
+"""Provides an interface to start and stop Android emulator.
+
+ Emulator: The class provides the methods to launch/shutdown the emulator with
+ the android virtual device named 'avd_armeabi' .
+"""
+
+import logging
+import os
+import signal
+import subprocess
+import time
+
+# TODO(craigdh): Move these pylib dependencies to pylib/utils/.
+from pylib import cmd_helper
+from pylib import constants
+from pylib import pexpect
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import time_profile
+
+import errors
+import run_command
+
+# SD card size
+SDCARD_SIZE = '512M'
+
+# Template used to generate config.ini files for the emulator
+CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1
+hw.dPad=no
+hw.lcd.density=320
+sdcard.size=512M
+hw.cpu.arch={hw.cpu.arch}
+hw.device.hash=-708107041
+hw.camera.back=none
+disk.dataPartition.size=800M
+hw.gpu.enabled=yes
+skin.path=720x1280
+skin.dynamic=yes
+hw.keyboard=yes
+hw.ramSize=1024
+hw.device.manufacturer=Google
+hw.sdCard=yes
+hw.mainKeys=no
+hw.accelerometer=yes
+skin.name=720x1280
+abi.type={abi.type}
+hw.trackBall=no
+hw.device.name=Galaxy Nexus
+hw.battery=yes
+hw.sensors.proximity=yes
+image.sysdir.1=system-images/android-{api.level}/{abi.type}/
+hw.sensors.orientation=yes
+hw.audioInput=yes
+hw.camera.front=none
+hw.gps=yes
+vm.heapSize=128
+{extras}"""
+
+CONFIG_REPLACEMENTS = {
+ 'x86': {
+ '{hw.cpu.arch}': 'x86',
+ '{abi.type}': 'x86',
+ '{extras}': ''
+ },
+ 'arm': {
+ '{hw.cpu.arch}': 'arm',
+ '{abi.type}': 'armeabi-v7a',
+ '{extras}': 'hw.cpu.model=cortex-a8\n'
+ },
+ 'mips': {
+ '{hw.cpu.arch}': 'mips',
+ '{abi.type}': 'mips',
+ '{extras}': ''
+ }
+}
+
+class EmulatorLaunchException(Exception):
+ """Emulator failed to launch."""
+ pass
+
+def _KillAllEmulators():
+ """Kill all running emulators that look like ones we started.
+
+ There are odd 'sticky' cases where there can be no emulator process
+ running but a device slot is taken. A little bot trouble and we're out of
+ room forever.
+ """
+ emulators = [d for d in device_utils.DeviceUtils.HealthyDevices()
+ if d.adb.is_emulator]
+ if not emulators:
+ return
+ for e in emulators:
+ e.adb.Emu(['kill'])
+ logging.info('Emulator killing is async; give a few seconds for all to die.')
+ for _ in range(5):
+ if not any(d.adb.is_emulator for d
+ in device_utils.DeviceUtils.HealthyDevices()):
+ return
+ time.sleep(1)
+
+
+def DeleteAllTempAVDs():
+ """Delete all temporary AVDs which are created for tests.
+
+ If the test exits abnormally and some temporary AVDs created when testing may
+ be left in the system. Clean these AVDs.
+ """
+ avds = device_utils.GetAVDs()
+ if not avds:
+ return
+ for avd_name in avds:
+ if 'run_tests_avd' in avd_name:
+ cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name]
+ cmd_helper.RunCmd(cmd)
+ logging.info('Delete AVD %s' % avd_name)
+
+
+class PortPool(object):
+ """Pool for emulator port starting position that changes over time."""
+ _port_min = 5554
+ _port_max = 5585
+ _port_current_index = 0
+
+ @classmethod
+ def port_range(cls):
+ """Return a range of valid ports for emulator use.
+
+ The port must be an even number between 5554 and 5584. Sometimes
+ a killed emulator "hangs on" to a port long enough to prevent
+ relaunch. This is especially true on slow machines (like a bot).
+ Cycling through a port start position helps make us resilient."""
+ ports = range(cls._port_min, cls._port_max, 2)
+ n = cls._port_current_index
+ cls._port_current_index = (n + 1) % len(ports)
+ return ports[n:] + ports[:n]
+
+
+def _GetAvailablePort():
+ """Returns an available TCP port for the console."""
+ used_ports = []
+ emulators = [d for d in device_utils.DeviceUtils.HealthyDevices()
+ if d.adb.is_emulator]
+ for emulator in emulators:
+ used_ports.append(emulator.adb.GetDeviceSerial().split('-')[1])
+ for port in PortPool.port_range():
+ if str(port) not in used_ports:
+ return port
+
+
+def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True):
+ """Create and launch temporary emulators and wait for them to boot.
+
+ Args:
+ emulator_count: number of emulators to launch.
+ abi: the emulator target platform
+ api_level: the api level (e.g., 19 for Android v4.4 - KitKat release)
+ wait_for_boot: whether or not to wait for emulators to boot up
+
+ Returns:
+ List of emulators.
+ """
+ emulators = []
+ for n in xrange(emulator_count):
+ t = time_profile.TimeProfile('Emulator launch %d' % n)
+ # Creates a temporary AVD.
+ avd_name = 'run_tests_avd_%d' % n
+ logging.info('Emulator launch %d with avd_name=%s and api=%d',
+ n, avd_name, api_level)
+ emulator = Emulator(avd_name, abi)
+ emulator.CreateAVD(api_level)
+ emulator.Launch(kill_all_emulators=n == 0)
+ t.Stop()
+ emulators.append(emulator)
+ # Wait for all emulators to boot completed.
+ if wait_for_boot:
+ for emulator in emulators:
+ emulator.ConfirmLaunch(True)
+ return emulators
+
+
+def LaunchEmulator(avd_name, abi):
+ """Launch an existing emulator with name avd_name.
+
+ Args:
+ avd_name: name of existing emulator
+ abi: the emulator target platform
+
+ Returns:
+ emulator object.
+ """
+ logging.info('Specified emulator named avd_name=%s launched', avd_name)
+ emulator = Emulator(avd_name, abi)
+ emulator.Launch(kill_all_emulators=True)
+ emulator.ConfirmLaunch(True)
+ return emulator
+
+
+class Emulator(object):
+ """Provides the methods to launch/shutdown the emulator.
+
+ The emulator has the android virtual device named 'avd_armeabi'.
+
+ The emulator could use any even TCP port between 5554 and 5584 for the
+ console communication, and this port will be part of the device name like
+ 'emulator-5554'. Assume it is always True, as the device name is the id of
+ emulator managed in this class.
+
+ Attributes:
+ emulator: Path of Android's emulator tool.
+ popen: Popen object of the running emulator process.
+ device: Device name of this emulator.
+ """
+
+ # Signals we listen for to kill the emulator on
+ _SIGNALS = (signal.SIGINT, signal.SIGHUP)
+
+ # Time to wait for an emulator launch, in seconds. This includes
+ # the time to launch the emulator and a wait-for-device command.
+ _LAUNCH_TIMEOUT = 120
+
+ # Timeout interval of wait-for-device command before bouncing to a a
+ # process life check.
+ _WAITFORDEVICE_TIMEOUT = 5
+
+ # Time to wait for a "wait for boot complete" (property set on device).
+ _WAITFORBOOT_TIMEOUT = 300
+
+ def __init__(self, avd_name, abi):
+ """Init an Emulator.
+
+ Args:
+ avd_name: name of the AVD to create
+ abi: target platform for emulator being created, defaults to x86
+ """
+ android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk')
+ self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
+ self.android = os.path.join(android_sdk_root, 'tools', 'android')
+ self.popen = None
+ self.device_serial = None
+ self.abi = abi
+ self.avd_name = avd_name
+
+ @staticmethod
+ def _DeviceName():
+ """Return our device name."""
+ port = _GetAvailablePort()
+ return ('emulator-%d' % port, port)
+
+ def CreateAVD(self, api_level):
+ """Creates an AVD with the given name.
+
+ Args:
+ api_level: the api level of the image
+
+ Return avd_name.
+ """
+
+ if self.abi == 'arm':
+ abi_option = 'armeabi-v7a'
+ elif self.abi == 'mips':
+ abi_option = 'mips'
+ else:
+ abi_option = 'x86'
+
+ api_target = 'android-%s' % api_level
+
+ avd_command = [
+ self.android,
+ '--silent',
+ 'create', 'avd',
+ '--name', self.avd_name,
+ '--abi', abi_option,
+ '--target', api_target,
+ '--sdcard', SDCARD_SIZE,
+ '--force',
+ ]
+ avd_cmd_str = ' '.join(avd_command)
+ logging.info('Create AVD command: %s', avd_cmd_str)
+ avd_process = pexpect.spawn(avd_cmd_str)
+
+ # Instead of creating a custom profile, we overwrite config files.
+ avd_process.expect('Do you wish to create a custom hardware profile')
+ avd_process.sendline('no\n')
+ avd_process.expect('Created AVD \'%s\'' % self.avd_name)
+
+ # Replace current configuration with default Galaxy Nexus config.
+ avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd')
+ ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name)
+ new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name,
+ 'config.ini')
+
+ # Remove config files with defaults to replace with Google's GN settings.
+ os.unlink(ini_file)
+ os.unlink(new_config_ini)
+
+ # Create new configuration files with Galaxy Nexus by Google settings.
+ with open(ini_file, 'w') as new_ini:
+ new_ini.write('avd.ini.encoding=ISO-8859-1\n')
+ new_ini.write('target=%s\n' % api_target)
+ new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name))
+ new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name)
+
+ custom_config = CONFIG_TEMPLATE
+ replacements = CONFIG_REPLACEMENTS[self.abi]
+ for key in replacements:
+ custom_config = custom_config.replace(key, replacements[key])
+ custom_config = custom_config.replace('{api.level}', str(api_level))
+
+ with open(new_config_ini, 'w') as new_config_ini:
+ new_config_ini.write(custom_config)
+
+ return self.avd_name
+
+
+ def _DeleteAVD(self):
+ """Delete the AVD of this emulator."""
+ avd_command = [
+ self.android,
+ '--silent',
+ 'delete',
+ 'avd',
+ '--name', self.avd_name,
+ ]
+ logging.info('Delete AVD command: %s', ' '.join(avd_command))
+ cmd_helper.RunCmd(avd_command)
+
+
+ def Launch(self, kill_all_emulators):
+ """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
+ emulator is ready for use.
+
+ If fails, an exception will be raised.
+ """
+ if kill_all_emulators:
+ _KillAllEmulators() # just to be sure
+ self._AggressiveImageCleanup()
+ (self.device_serial, port) = self._DeviceName()
+ emulator_command = [
+ self.emulator,
+ # Speed up emulator launch by 40%. Really.
+ '-no-boot-anim',
+ # The default /data size is 64M.
+ # That's not enough for 8 unit test bundles and their data.
+ '-partition-size', '512',
+ # Use a familiar name and port.
+ '-avd', self.avd_name,
+ '-port', str(port),
+ # Wipe the data. We've seen cases where an emulator gets 'stuck' if we
+ # don't do this (every thousand runs or so).
+ '-wipe-data',
+ # Enable GPU by default.
+ '-gpu', 'on',
+ '-qemu', '-m', '1024',
+ ]
+ if self.abi == 'x86':
+ emulator_command.extend([
+ # For x86 emulator --enable-kvm will fail early, avoiding accidental
+ # runs in a slow mode (i.e. without hardware virtualization support).
+ '--enable-kvm',
+ ])
+
+ logging.info('Emulator launch command: %s', ' '.join(emulator_command))
+ self.popen = subprocess.Popen(args=emulator_command,
+ stderr=subprocess.STDOUT)
+ self._InstallKillHandler()
+
+ @staticmethod
+ def _AggressiveImageCleanup():
+ """Aggressive cleanup of emulator images.
+
+ Experimentally it looks like our current emulator use on the bot
+ leaves image files around in /tmp/android-$USER. If a "random"
+ name gets reused, we choke with a 'File exists' error.
+ TODO(jrg): is there a less hacky way to accomplish the same goal?
+ """
+ logging.info('Aggressive Image Cleanup')
+ emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
+ if not os.path.exists(emulator_imagedir):
+ return
+ for image in os.listdir(emulator_imagedir):
+ full_name = os.path.join(emulator_imagedir, image)
+ if 'emulator' in full_name:
+ logging.info('Deleting emulator image %s', full_name)
+ os.unlink(full_name)
+
+ def ConfirmLaunch(self, wait_for_boot=False):
+ """Confirm the emulator launched properly.
+
+ Loop on a wait-for-device with a very small timeout. On each
+ timeout, check the emulator process is still alive.
+ After confirming a wait-for-device can be successful, make sure
+ it returns the right answer.
+ """
+ seconds_waited = 0
+ number_of_waits = 2 # Make sure we can wfd twice
+
+ device = device_utils.DeviceUtils(self.device_serial)
+ while seconds_waited < self._LAUNCH_TIMEOUT:
+ try:
+ device.adb.WaitForDevice(
+ timeout=self._WAITFORDEVICE_TIMEOUT, retries=1)
+ number_of_waits -= 1
+ if not number_of_waits:
+ break
+ except device_errors.CommandTimeoutError:
+ seconds_waited += self._WAITFORDEVICE_TIMEOUT
+ device.adb.KillServer()
+ self.popen.poll()
+ if self.popen.returncode != None:
+ raise EmulatorLaunchException('EMULATOR DIED')
+
+ if seconds_waited >= self._LAUNCH_TIMEOUT:
+ raise EmulatorLaunchException('TIMEOUT with wait-for-device')
+
+ logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
+ if wait_for_boot:
+ # Now that we checked for obvious problems, wait for a boot complete.
+ # Waiting for the package manager is sometimes problematic.
+ device.WaitUntilFullyBooted(timeout=self._WAITFORBOOT_TIMEOUT)
+
+ def Shutdown(self):
+ """Shuts down the process started by launch."""
+ self._DeleteAVD()
+ if self.popen:
+ self.popen.poll()
+ if self.popen.returncode == None:
+ self.popen.kill()
+ self.popen = None
+
+ def _ShutdownOnSignal(self, _signum, _frame):
+ logging.critical('emulator _ShutdownOnSignal')
+ for sig in self._SIGNALS:
+ signal.signal(sig, signal.SIG_DFL)
+ self.Shutdown()
+ raise KeyboardInterrupt # print a stack
+
+ def _InstallKillHandler(self):
+ """Install a handler to kill the emulator when we exit unexpectedly."""
+ for sig in self._SIGNALS:
+ signal.signal(sig, self._ShutdownOnSignal)
diff --git a/build/android/pylib/utils/findbugs.py b/build/android/pylib/utils/findbugs.py
new file mode 100644
index 00000000000..8deb0fe274d
--- /dev/null
+++ b/build/android/pylib/utils/findbugs.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2012 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.
+
+import argparse
+import logging
+import os
+import re
+import shlex
+import sys
+import xml.dom.minidom
+
+from pylib import cmd_helper
+from pylib import constants
+
+
+_FINDBUGS_HOME = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party',
+ 'findbugs')
+_FINDBUGS_JAR = os.path.join(_FINDBUGS_HOME, 'lib', 'findbugs.jar')
+_FINDBUGS_MAX_HEAP = 768
+_FINDBUGS_PLUGIN_PATH = os.path.join(
+ constants.DIR_SOURCE_ROOT, 'tools', 'android', 'findbugs_plugin', 'lib',
+ 'chromiumPlugin.jar')
+
+
+def _ParseXmlResults(results_doc):
+ warnings = set()
+ for en in (n for n in results_doc.documentElement.childNodes
+ if n.nodeType == xml.dom.Node.ELEMENT_NODE):
+ if en.tagName == 'BugInstance':
+ warnings.add(_ParseBugInstance(en))
+ return warnings
+
+
+def _GetMessage(node):
+ for c in (n for n in node.childNodes
+ if n.nodeType == xml.dom.Node.ELEMENT_NODE):
+ if c.tagName == 'Message':
+ if (len(c.childNodes) == 1
+ and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE):
+ return c.childNodes[0].data
+ return None
+
+
+def _ParseBugInstance(node):
+ bug = FindBugsWarning(node.getAttribute('type'))
+ msg_parts = []
+ for c in (n for n in node.childNodes
+ if n.nodeType == xml.dom.Node.ELEMENT_NODE):
+ if c.tagName == 'Class':
+ msg_parts.append(_GetMessage(c))
+ elif c.tagName == 'Method':
+ msg_parts.append(_GetMessage(c))
+ elif c.tagName == 'Field':
+ msg_parts.append(_GetMessage(c))
+ elif c.tagName == 'SourceLine':
+ bug.file_name = c.getAttribute('sourcefile')
+ if c.hasAttribute('start'):
+ bug.start_line = int(c.getAttribute('start'))
+ if c.hasAttribute('end'):
+ bug.end_line = int(c.getAttribute('end'))
+ msg_parts.append(_GetMessage(c))
+ elif (c.tagName == 'ShortMessage' and len(c.childNodes) == 1
+ and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE):
+ msg_parts.append(c.childNodes[0].data)
+ bug.message = tuple(m for m in msg_parts if m)
+ return bug
+
+
+class FindBugsWarning(object):
+
+ def __init__(self, bug_type='', end_line=0, file_name='', message=None,
+ start_line=0):
+ self.bug_type = bug_type
+ self.end_line = end_line
+ self.file_name = file_name
+ if message is None:
+ self.message = tuple()
+ else:
+ self.message = message
+ self.start_line = start_line
+
+ def __cmp__(self, other):
+ return (cmp(self.file_name, other.file_name)
+ or cmp(self.start_line, other.start_line)
+ or cmp(self.end_line, other.end_line)
+ or cmp(self.bug_type, other.bug_type)
+ or cmp(self.message, other.message))
+
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
+
+ def __hash__(self):
+ return hash((self.bug_type, self.end_line, self.file_name, self.message,
+ self.start_line))
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __str__(self):
+ return '%s: %s' % (self.bug_type, '\n '.join(self.message))
+
+
+def Run(exclude, classes_to_analyze, auxiliary_classes, output_file,
+ findbug_args, jars):
+ """Run FindBugs.
+
+ Args:
+ exclude: the exclude xml file, refer to FindBugs's -exclude command option.
+ classes_to_analyze: the list of classes need to analyze, refer to FindBug's
+ -onlyAnalyze command line option.
+ auxiliary_classes: the classes help to analyze, refer to FindBug's
+ -auxclasspath command line option.
+ output_file: An optional path to dump XML results to.
+ findbug_args: A list of addtional command line options to pass to Findbugs.
+ """
+ # TODO(jbudorick): Get this from the build system.
+ system_classes = [
+ os.path.join(constants.ANDROID_SDK_ROOT, 'platforms',
+ 'android-%s' % constants.ANDROID_SDK_VERSION, 'android.jar')
+ ]
+ system_classes.extend(os.path.abspath(classes)
+ for classes in auxiliary_classes or [])
+
+ cmd = ['java',
+ '-classpath', '%s:' % _FINDBUGS_JAR,
+ '-Xmx%dm' % _FINDBUGS_MAX_HEAP,
+ '-Dfindbugs.home="%s"' % _FINDBUGS_HOME,
+ '-jar', _FINDBUGS_JAR,
+ '-textui', '-sortByClass',
+ '-pluginList', _FINDBUGS_PLUGIN_PATH, '-xml:withMessages']
+ if system_classes:
+ cmd.extend(['-auxclasspath', ':'.join(system_classes)])
+ if classes_to_analyze:
+ cmd.extend(['-onlyAnalyze', classes_to_analyze])
+ if exclude:
+ cmd.extend(['-exclude', os.path.abspath(exclude)])
+ if output_file:
+ cmd.extend(['-output', output_file])
+ if findbug_args:
+ cmd.extend(findbug_args)
+ cmd.extend(os.path.abspath(j) for j in jars or [])
+
+ if output_file:
+ cmd_helper.RunCmd(cmd)
+ results_doc = xml.dom.minidom.parse(output_file)
+ else:
+ raw_out = cmd_helper.GetCmdOutput(cmd)
+ results_doc = xml.dom.minidom.parseString(raw_out)
+
+ current_warnings_set = _ParseXmlResults(results_doc)
+
+ return (' '.join(cmd), current_warnings_set)
+
diff --git a/build/android/pylib/utils/host_path_finder.py b/build/android/pylib/utils/host_path_finder.py
new file mode 100644
index 00000000000..389ac4371bb
--- /dev/null
+++ b/build/android/pylib/utils/host_path_finder.py
@@ -0,0 +1,22 @@
+# Copyright 2013 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.
+
+import os
+
+from pylib import constants
+
+
+def GetMostRecentHostPath(file_name):
+ """Returns the most recent existing full path for the given file name.
+
+ Returns: An empty string if no path could be found.
+ """
+ out_dir = os.path.join(
+ constants.DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'))
+ candidate_paths = [os.path.join(out_dir, build_type, file_name)
+ for build_type in ['Debug', 'Release']]
+ candidate_paths = filter(os.path.exists, candidate_paths)
+ candidate_paths = sorted(candidate_paths, key=os.path.getmtime, reverse=True)
+ candidate_paths.append('')
+ return candidate_paths[0]
diff --git a/build/android/pylib/utils/host_utils.py b/build/android/pylib/utils/host_utils.py
new file mode 100644
index 00000000000..580721f1275
--- /dev/null
+++ b/build/android/pylib/utils/host_utils.py
@@ -0,0 +1,16 @@
+# Copyright 2014 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.
+
+import os
+
+
+def GetRecursiveDiskUsage(path):
+ """Returns the disk usage in bytes of |path|. Similar to `du -sb |path|`."""
+ running_size = os.path.getsize(path)
+ if os.path.isdir(path):
+ for root, dirs, files in os.walk(path):
+ running_size += sum([os.path.getsize(os.path.join(root, f))
+ for f in files + dirs])
+ return running_size
+
diff --git a/build/android/pylib/utils/isolator.py b/build/android/pylib/utils/isolator.py
new file mode 100644
index 00000000000..cac39d81369
--- /dev/null
+++ b/build/android/pylib/utils/isolator.py
@@ -0,0 +1,173 @@
+# Copyright 2014 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.
+
+import fnmatch
+import glob
+import os
+import shutil
+import sys
+
+from pylib import cmd_helper
+from pylib import constants
+
+
+_ISOLATE_SCRIPT = os.path.join(
+ constants.DIR_SOURCE_ROOT, 'tools', 'swarming_client', 'isolate.py')
+
+
+def DefaultPathVariables():
+ return {
+ 'DEPTH': constants.DIR_SOURCE_ROOT,
+ 'PRODUCT_DIR': constants.GetOutDirectory(),
+ }
+
+
+def DefaultConfigVariables():
+ # Note: This list must match the --config-vars in build/isolate.gypi
+ return {
+ 'CONFIGURATION_NAME': constants.GetBuildType(),
+ 'OS': 'android',
+ 'asan': '0',
+ 'branding': 'Chromium',
+ 'chromeos': '0',
+ 'component': 'static_library',
+ 'enable_pepper_cdms': '0',
+ 'enable_plugins': '0',
+ 'fastbuild': '0',
+ 'icu_use_data_file_flag': '1',
+ 'kasko': '0',
+ 'lsan': '0',
+ 'msan': '0',
+ # TODO(maruel): This may not always be true.
+ 'target_arch': 'arm',
+ 'tsan': '0',
+ 'use_custom_libcxx': '0',
+ 'use_instrumented_libraries': '0',
+ 'use_prebuilt_instrumented_libraries': '0',
+ 'use_openssl': '0',
+ 'use_ozone': '0',
+ 'use_x11': '0',
+ 'v8_use_external_startup_data': '1',
+ }
+
+
+class Isolator(object):
+ """Manages calls to isolate.py for the android test runner scripts."""
+
+ def __init__(self, isolate_deps_dir):
+ """
+ Args:
+ isolate_deps_dir: The directory in which dependencies specified by
+ isolate are or should be stored.
+ """
+ self._isolate_deps_dir = isolate_deps_dir
+
+ def Clear(self):
+ """Deletes the isolate dependency directory."""
+ if os.path.exists(self._isolate_deps_dir):
+ shutil.rmtree(self._isolate_deps_dir)
+
+ def Remap(self, isolate_abs_path, isolated_abs_path,
+ path_variables=None, config_variables=None):
+ """Remaps data dependencies into |self._isolate_deps_dir|.
+
+ Args:
+ isolate_abs_path: The absolute path to the .isolate file, which specifies
+ data dependencies in the source tree.
+ isolated_abs_path: The absolute path to the .isolated file, which is
+ generated by isolate.py and specifies data dependencies in
+ |self._isolate_deps_dir| and their digests.
+ path_variables: A dict containing everything that should be passed
+ as a |--path-variable| to the isolate script. Defaults to the return
+ value of |DefaultPathVariables()|.
+ config_variables: A dict containing everything that should be passed
+ as a |--config-variable| to the isolate script. Defaults to the return
+ value of |DefaultConfigVariables()|.
+ Raises:
+ Exception if the isolate command fails for some reason.
+ """
+ if not path_variables:
+ path_variables = DefaultPathVariables()
+ if not config_variables:
+ config_variables = DefaultConfigVariables()
+
+ isolate_cmd = [
+ sys.executable, _ISOLATE_SCRIPT, 'remap',
+ '--isolate', isolate_abs_path,
+ '--isolated', isolated_abs_path,
+ '--outdir', self._isolate_deps_dir,
+ ]
+ for k, v in path_variables.iteritems():
+ isolate_cmd.extend(['--path-variable', k, v])
+ for k, v in config_variables.iteritems():
+ isolate_cmd.extend(['--config-variable', k, v])
+
+ if cmd_helper.RunCmd(isolate_cmd):
+ raise Exception('isolate command failed: %s' % ' '.join(isolate_cmd))
+
+ def VerifyHardlinks(self):
+ """Checks |isolate_deps_dir| for a hardlink.
+
+ Returns:
+ True if a hardlink is found.
+ False if nothing is found.
+ Raises:
+ Exception if a non-hardlink is found.
+ """
+ for root, _, filenames in os.walk(self._isolate_deps_dir):
+ if filenames:
+ linked_file = os.path.join(root, filenames[0])
+ orig_file = os.path.join(
+ self._isolate_deps_dir,
+ os.path.relpath(linked_file, self._isolate_deps_dir))
+ if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
+ return True
+ else:
+ raise Exception('isolate remap command did not use hardlinks.')
+ return False
+
+ def PurgeExcluded(self, deps_exclusion_list):
+ """Deletes anything on |deps_exclusion_list| from |self._isolate_deps_dir|.
+
+ Args:
+ deps_exclusion_list: A list of globs to exclude from the isolate
+ dependency directory.
+ """
+ excluded_paths = (
+ x for y in deps_exclusion_list
+ for x in glob.glob(
+ os.path.abspath(os.path.join(self._isolate_deps_dir, y))))
+ for p in excluded_paths:
+ if os.path.isdir(p):
+ shutil.rmtree(p)
+ else:
+ os.remove(p)
+
+ def MoveOutputDeps(self):
+ """Moves files from the output directory to the top level of
+ |self._isolate_deps_dir|.
+
+ Moves pak files from the output directory to to /paks
+ Moves files from the product directory to
+ """
+ # On Android, all pak files need to be in the top-level 'paks' directory.
+ paks_dir = os.path.join(self._isolate_deps_dir, 'paks')
+ os.mkdir(paks_dir)
+
+ deps_out_dir = os.path.join(
+ self._isolate_deps_dir,
+ os.path.relpath(os.path.join(constants.GetOutDirectory(), os.pardir),
+ constants.DIR_SOURCE_ROOT))
+ for root, _, filenames in os.walk(deps_out_dir):
+ for filename in fnmatch.filter(filenames, '*.pak'):
+ shutil.move(os.path.join(root, filename), paks_dir)
+
+ # Move everything in PRODUCT_DIR to top level.
+ deps_product_dir = os.path.join(deps_out_dir, constants.GetBuildType())
+ if os.path.isdir(deps_product_dir):
+ for p in os.listdir(deps_product_dir):
+ shutil.move(os.path.join(deps_product_dir, p), self._isolate_deps_dir)
+ os.rmdir(deps_product_dir)
+ os.rmdir(deps_out_dir)
+
diff --git a/build/android/pylib/utils/json_results_generator_unittest.py b/build/android/pylib/utils/json_results_generator_unittest.py
new file mode 100644
index 00000000000..41ab77b81f7
--- /dev/null
+++ b/build/android/pylib/utils/json_results_generator_unittest.py
@@ -0,0 +1,213 @@
+# Copyright 2014 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.
+
+#
+# Most of this file was ported over from Blink's
+# webkitpy/layout_tests/layout_package/json_results_generator_unittest.py
+#
+
+import unittest
+import json
+
+from pylib.utils import json_results_generator
+
+
+class JSONGeneratorTest(unittest.TestCase):
+
+ def setUp(self):
+ self.builder_name = 'DUMMY_BUILDER_NAME'
+ self.build_name = 'DUMMY_BUILD_NAME'
+ self.build_number = 'DUMMY_BUILDER_NUMBER'
+
+ # For archived results.
+ self._json = None
+ self._num_runs = 0
+ self._tests_set = set([])
+ self._test_timings = {}
+ self._failed_count_map = {}
+
+ self._PASS_count = 0
+ self._DISABLED_count = 0
+ self._FLAKY_count = 0
+ self._FAILS_count = 0
+ self._fixable_count = 0
+
+ self._orig_write_json = json_results_generator.WriteJSON
+
+ # unused arguments ... pylint: disable=W0613
+ def _WriteJSONStub(json_object, file_path, callback=None):
+ pass
+
+ json_results_generator.WriteJSON = _WriteJSONStub
+
+ def tearDown(self):
+ json_results_generator.WriteJSON = self._orig_write_json
+
+ def _TestJSONGeneration(self, passed_tests_list, failed_tests_list):
+ tests_set = set(passed_tests_list) | set(failed_tests_list)
+
+ DISABLED_tests = set([t for t in tests_set
+ if t.startswith('DISABLED_')])
+ FLAKY_tests = set([t for t in tests_set
+ if t.startswith('FLAKY_')])
+ FAILS_tests = set([t for t in tests_set
+ if t.startswith('FAILS_')])
+ PASS_tests = tests_set - (DISABLED_tests | FLAKY_tests | FAILS_tests)
+
+ failed_tests = set(failed_tests_list) - DISABLED_tests
+ failed_count_map = dict([(t, 1) for t in failed_tests])
+
+ test_timings = {}
+ i = 0
+ for test in tests_set:
+ test_timings[test] = float(self._num_runs * 100 + i)
+ i += 1
+
+ test_results_map = dict()
+ for test in tests_set:
+ test_results_map[test] = json_results_generator.TestResult(
+ test, failed=(test in failed_tests),
+ elapsed_time=test_timings[test])
+
+ generator = json_results_generator.JSONResultsGeneratorBase(
+ self.builder_name, self.build_name, self.build_number,
+ '',
+ None, # don't fetch past json results archive
+ test_results_map)
+
+ failed_count_map = dict([(t, 1) for t in failed_tests])
+
+ # Test incremental json results
+ incremental_json = generator.GetJSON()
+ self._VerifyJSONResults(
+ tests_set,
+ test_timings,
+ failed_count_map,
+ len(PASS_tests),
+ len(DISABLED_tests),
+ len(FLAKY_tests),
+ len(DISABLED_tests | failed_tests),
+ incremental_json,
+ 1)
+
+ # We don't verify the results here, but at least we make sure the code
+ # runs without errors.
+ generator.GenerateJSONOutput()
+ generator.GenerateTimesMSFile()
+
+ def _VerifyJSONResults(self, tests_set, test_timings, failed_count_map,
+ PASS_count, DISABLED_count, FLAKY_count,
+ fixable_count, json_obj, num_runs):
+ # Aliasing to a short name for better access to its constants.
+ JRG = json_results_generator.JSONResultsGeneratorBase
+
+ self.assertIn(JRG.VERSION_KEY, json_obj)
+ self.assertIn(self.builder_name, json_obj)
+
+ buildinfo = json_obj[self.builder_name]
+ self.assertIn(JRG.FIXABLE, buildinfo)
+ self.assertIn(JRG.TESTS, buildinfo)
+ self.assertEqual(len(buildinfo[JRG.BUILD_NUMBERS]), num_runs)
+ self.assertEqual(buildinfo[JRG.BUILD_NUMBERS][0], self.build_number)
+
+ if tests_set or DISABLED_count:
+ fixable = {}
+ for fixable_items in buildinfo[JRG.FIXABLE]:
+ for (result_type, count) in fixable_items.iteritems():
+ if result_type in fixable:
+ fixable[result_type] = fixable[result_type] + count
+ else:
+ fixable[result_type] = count
+
+ if PASS_count:
+ self.assertEqual(fixable[JRG.PASS_RESULT], PASS_count)
+ else:
+ self.assertTrue(JRG.PASS_RESULT not in fixable or
+ fixable[JRG.PASS_RESULT] == 0)
+ if DISABLED_count:
+ self.assertEqual(fixable[JRG.SKIP_RESULT], DISABLED_count)
+ else:
+ self.assertTrue(JRG.SKIP_RESULT not in fixable or
+ fixable[JRG.SKIP_RESULT] == 0)
+ if FLAKY_count:
+ self.assertEqual(fixable[JRG.FLAKY_RESULT], FLAKY_count)
+ else:
+ self.assertTrue(JRG.FLAKY_RESULT not in fixable or
+ fixable[JRG.FLAKY_RESULT] == 0)
+
+ if failed_count_map:
+ tests = buildinfo[JRG.TESTS]
+ for test_name in failed_count_map.iterkeys():
+ test = self._FindTestInTrie(test_name, tests)
+
+ failed = 0
+ for result in test[JRG.RESULTS]:
+ if result[1] == JRG.FAIL_RESULT:
+ failed += result[0]
+ self.assertEqual(failed_count_map[test_name], failed)
+
+ timing_count = 0
+ for timings in test[JRG.TIMES]:
+ if timings[1] == test_timings[test_name]:
+ timing_count = timings[0]
+ self.assertEqual(1, timing_count)
+
+ if fixable_count:
+ self.assertEqual(sum(buildinfo[JRG.FIXABLE_COUNT]), fixable_count)
+
+ def _FindTestInTrie(self, path, trie):
+ nodes = path.split('/')
+ sub_trie = trie
+ for node in nodes:
+ self.assertIn(node, sub_trie)
+ sub_trie = sub_trie[node]
+ return sub_trie
+
+ def testJSONGeneration(self):
+ self._TestJSONGeneration([], [])
+ self._TestJSONGeneration(['A1', 'B1'], [])
+ self._TestJSONGeneration([], ['FAILS_A2', 'FAILS_B2'])
+ self._TestJSONGeneration(['DISABLED_A3', 'DISABLED_B3'], [])
+ self._TestJSONGeneration(['A4'], ['B4', 'FAILS_C4'])
+ self._TestJSONGeneration(['DISABLED_C5', 'DISABLED_D5'], ['A5', 'B5'])
+ self._TestJSONGeneration(
+ ['A6', 'B6', 'FAILS_C6', 'DISABLED_E6', 'DISABLED_F6'],
+ ['FAILS_D6'])
+
+ # Generate JSON with the same test sets. (Both incremental results and
+ # archived results must be updated appropriately.)
+ self._TestJSONGeneration(
+ ['A', 'FLAKY_B', 'DISABLED_C'],
+ ['FAILS_D', 'FLAKY_E'])
+ self._TestJSONGeneration(
+ ['A', 'DISABLED_C', 'FLAKY_E'],
+ ['FLAKY_B', 'FAILS_D'])
+ self._TestJSONGeneration(
+ ['FLAKY_B', 'DISABLED_C', 'FAILS_D'],
+ ['A', 'FLAKY_E'])
+
+ def testHierarchicalJSNGeneration(self):
+ # FIXME: Re-work tests to be more comprehensible and comprehensive.
+ self._TestJSONGeneration(['foo/A'], ['foo/B', 'bar/C'])
+
+ def testTestTimingsTrie(self):
+ individual_test_timings = []
+ individual_test_timings.append(
+ json_results_generator.TestResult(
+ 'foo/bar/baz.html',
+ elapsed_time=1.2))
+ individual_test_timings.append(
+ json_results_generator.TestResult('bar.html', elapsed_time=0.0001))
+ trie = json_results_generator.TestTimingsTrie(individual_test_timings)
+
+ expected_trie = {
+ 'bar.html': 0,
+ 'foo': {
+ 'bar': {
+ 'baz.html': 1200,
+ }
+ }
+ }
+
+ self.assertEqual(json.dumps(trie), json.dumps(expected_trie))
diff --git a/build/android/pylib/utils/logging_utils.py b/build/android/pylib/utils/logging_utils.py
new file mode 100644
index 00000000000..1e46fa82080
--- /dev/null
+++ b/build/android/pylib/utils/logging_utils.py
@@ -0,0 +1,27 @@
+# Copyright 2014 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.
+
+import contextlib
+import logging
+
+@contextlib.contextmanager
+def SuppressLogging(level=logging.ERROR):
+ """Momentarilly suppress logging events from all loggers.
+
+ TODO(jbudorick): This is not thread safe. Log events from other threads might
+ also inadvertently dissapear.
+
+ Example:
+
+ with logging_utils.SuppressLogging():
+ # all but CRITICAL logging messages are suppressed
+ logging.info('just doing some thing') # not shown
+ logging.critical('something really bad happened') # still shown
+
+ Args:
+ level: logging events with this or lower levels are suppressed.
+ """
+ logging.disable(level)
+ yield
+ logging.disable(logging.NOTSET)
diff --git a/build/android/pylib/utils/md5sum.py b/build/android/pylib/utils/md5sum.py
new file mode 100644
index 00000000000..3e61c8facb1
--- /dev/null
+++ b/build/android/pylib/utils/md5sum.py
@@ -0,0 +1,91 @@
+# Copyright 2014 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.
+
+import collections
+import logging
+import os
+import re
+import tempfile
+import types
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.utils import device_temp_file
+
+MD5SUM_DEVICE_LIB_PATH = '/data/local/tmp/md5sum/'
+MD5SUM_DEVICE_BIN_PATH = MD5SUM_DEVICE_LIB_PATH + 'md5sum_bin'
+
+MD5SUM_DEVICE_SCRIPT_FORMAT = (
+ 'test -f {path} -o -d {path} '
+ '&& LD_LIBRARY_PATH={md5sum_lib} {md5sum_bin} {path}')
+
+_STARTS_WITH_CHECKSUM_RE = re.compile(r'^\s*[0-9a-fA-F]{32}\s+')
+
+
+def CalculateHostMd5Sums(paths):
+ """Calculates the MD5 sum value for all items in |paths|.
+
+ Directories are traversed recursively and the MD5 sum of each file found is
+ reported in the result.
+
+ Args:
+ paths: A list of host paths to md5sum.
+ Returns:
+ A dict mapping file paths to their respective md5sum checksums.
+ """
+ if isinstance(paths, basestring):
+ paths = [paths]
+
+ md5sum_bin_host_path = os.path.join(
+ constants.GetOutDirectory(), 'md5sum_bin_host')
+ if not os.path.exists(md5sum_bin_host_path):
+ raise IOError('File not built: %s' % md5sum_bin_host_path)
+ out = cmd_helper.GetCmdOutput([md5sum_bin_host_path] + [p for p in paths])
+
+ return _ParseMd5SumOutput(out.splitlines())
+
+
+def CalculateDeviceMd5Sums(paths, device):
+ """Calculates the MD5 sum value for all items in |paths|.
+
+ Directories are traversed recursively and the MD5 sum of each file found is
+ reported in the result.
+
+ Args:
+ paths: A list of device paths to md5sum.
+ Returns:
+ A dict mapping file paths to their respective md5sum checksums.
+ """
+ if isinstance(paths, basestring):
+ paths = [paths]
+
+ if not device.FileExists(MD5SUM_DEVICE_BIN_PATH):
+ md5sum_dist_path = os.path.join(constants.GetOutDirectory(), 'md5sum_dist')
+ if not os.path.exists(md5sum_dist_path):
+ raise IOError('File not built: %s' % md5sum_dist_path)
+ device.adb.Push(md5sum_dist_path, MD5SUM_DEVICE_LIB_PATH)
+
+ out = []
+
+ with tempfile.NamedTemporaryFile() as md5sum_script_file:
+ with device_temp_file.DeviceTempFile(
+ device.adb) as md5sum_device_script_file:
+ md5sum_script = (
+ MD5SUM_DEVICE_SCRIPT_FORMAT.format(
+ path=p, md5sum_lib=MD5SUM_DEVICE_LIB_PATH,
+ md5sum_bin=MD5SUM_DEVICE_BIN_PATH)
+ for p in paths)
+ md5sum_script_file.write('; '.join(md5sum_script))
+ md5sum_script_file.flush()
+ device.adb.Push(md5sum_script_file.name, md5sum_device_script_file.name)
+ out = device.RunShellCommand(['sh', md5sum_device_script_file.name])
+
+ return _ParseMd5SumOutput(out)
+
+
+def _ParseMd5SumOutput(out):
+ hash_and_path = (l.split(None, 1) for l in out
+ if l and _STARTS_WITH_CHECKSUM_RE.match(l))
+ return dict((p, h) for h, p in hash_and_path)
+
diff --git a/build/android/pylib/utils/md5sum_test.py b/build/android/pylib/utils/md5sum_test.py
new file mode 100755
index 00000000000..c94c19d9476
--- /dev/null
+++ b/build/android/pylib/utils/md5sum_test.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import os
+import sys
+import unittest
+
+from pylib import cmd_helper
+from pylib import constants
+from pylib.utils import md5sum
+
+sys.path.append(
+ os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock
+
+TEST_OUT_DIR = os.path.join('test', 'out', 'directory')
+HOST_MD5_EXECUTABLE = os.path.join(TEST_OUT_DIR, 'md5sum_bin_host')
+
+class Md5SumTest(unittest.TestCase):
+
+ def setUp(self):
+ self._patchers = [
+ mock.patch('pylib.constants.GetOutDirectory',
+ new=mock.Mock(return_value=TEST_OUT_DIR)),
+ mock.patch('os.path.exists',
+ new=mock.Mock(return_value=True)),
+ ]
+ for p in self._patchers:
+ p.start()
+
+ def tearDown(self):
+ for p in self._patchers:
+ p.stop()
+
+ def testCalculateHostMd5Sums_singlePath(self):
+ test_path = '/test/host/file.dat'
+ mock_get_cmd_output = mock.Mock(
+ return_value='0123456789abcdeffedcba9876543210 /test/host/file.dat')
+ with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output):
+ out = md5sum.CalculateHostMd5Sums(test_path)
+ self.assertEquals(1, len(out))
+ self.assertTrue('/test/host/file.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/test/host/file.dat'])
+ mock_get_cmd_output.assert_called_once_with(
+ [HOST_MD5_EXECUTABLE, '/test/host/file.dat'])
+
+ def testCalculateHostMd5Sums_list(self):
+ test_paths = ['/test/host/file0.dat', '/test/host/file1.dat']
+ mock_get_cmd_output = mock.Mock(
+ return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
+ '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
+ with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output):
+ out = md5sum.CalculateHostMd5Sums(test_paths)
+ self.assertEquals(2, len(out))
+ self.assertTrue('/test/host/file0.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/test/host/file0.dat'])
+ self.assertTrue('/test/host/file1.dat' in out)
+ self.assertEquals('123456789abcdef00fedcba987654321',
+ out['/test/host/file1.dat'])
+ mock_get_cmd_output.assert_called_once_with(
+ [HOST_MD5_EXECUTABLE, '/test/host/file0.dat',
+ '/test/host/file1.dat'])
+
+ def testCalculateHostMd5Sums_generator(self):
+ test_paths = ('/test/host/' + p for p in ['file0.dat', 'file1.dat'])
+ mock_get_cmd_output = mock.Mock(
+ return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
+ '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
+ with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output):
+ out = md5sum.CalculateHostMd5Sums(test_paths)
+ self.assertEquals(2, len(out))
+ self.assertTrue('/test/host/file0.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/test/host/file0.dat'])
+ self.assertTrue('/test/host/file1.dat' in out)
+ self.assertEquals('123456789abcdef00fedcba987654321',
+ out['/test/host/file1.dat'])
+ mock_get_cmd_output.assert_called_once_with(
+ [HOST_MD5_EXECUTABLE, '/test/host/file0.dat', '/test/host/file1.dat'])
+
+ def testCalculateDeviceMd5Sums_singlePath(self):
+ test_path = '/storage/emulated/legacy/test/file.dat'
+
+ device = mock.NonCallableMock()
+ device.adb = mock.NonCallableMock()
+ device.adb.Push = mock.Mock()
+ device_md5sum_output = [
+ '0123456789abcdeffedcba9876543210 '
+ '/storage/emulated/legacy/test/file.dat',
+ ]
+ device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
+
+ mock_temp_file = mock.mock_open()
+ mock_temp_file.return_value.name = '/tmp/test/script/file.sh'
+
+ mock_device_temp_file = mock.mock_open()
+ mock_device_temp_file.return_value.name = (
+ '/data/local/tmp/test/script/file.sh')
+
+ with mock.patch('tempfile.NamedTemporaryFile', new=mock_temp_file), (
+ mock.patch('pylib.utils.device_temp_file.DeviceTempFile',
+ new=mock_device_temp_file)):
+ out = md5sum.CalculateDeviceMd5Sums(test_path, device)
+ self.assertEquals(1, len(out))
+ self.assertTrue('/storage/emulated/legacy/test/file.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/storage/emulated/legacy/test/file.dat'])
+ device.adb.Push.assert_called_once_with(
+ '/tmp/test/script/file.sh', '/data/local/tmp/test/script/file.sh')
+ device.RunShellCommand.assert_called_once_with(
+ ['sh', '/data/local/tmp/test/script/file.sh'])
+
+ def testCalculateDeviceMd5Sums_list(self):
+ test_path = ['/storage/emulated/legacy/test/file0.dat',
+ '/storage/emulated/legacy/test/file1.dat']
+ device = mock.NonCallableMock()
+ device.adb = mock.NonCallableMock()
+ device.adb.Push = mock.Mock()
+ device_md5sum_output = [
+ '0123456789abcdeffedcba9876543210 '
+ '/storage/emulated/legacy/test/file0.dat',
+ '123456789abcdef00fedcba987654321 '
+ '/storage/emulated/legacy/test/file1.dat',
+ ]
+ device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
+
+ mock_temp_file = mock.mock_open()
+ mock_temp_file.return_value.name = '/tmp/test/script/file.sh'
+
+ mock_device_temp_file = mock.mock_open()
+ mock_device_temp_file.return_value.name = (
+ '/data/local/tmp/test/script/file.sh')
+
+ with mock.patch('tempfile.NamedTemporaryFile', new=mock_temp_file), (
+ mock.patch('pylib.utils.device_temp_file.DeviceTempFile',
+ new=mock_device_temp_file)):
+ out = md5sum.CalculateDeviceMd5Sums(test_path, device)
+ self.assertEquals(2, len(out))
+ self.assertTrue('/storage/emulated/legacy/test/file0.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/storage/emulated/legacy/test/file0.dat'])
+ self.assertTrue('/storage/emulated/legacy/test/file1.dat' in out)
+ self.assertEquals('123456789abcdef00fedcba987654321',
+ out['/storage/emulated/legacy/test/file1.dat'])
+ device.adb.Push.assert_called_once_with(
+ '/tmp/test/script/file.sh', '/data/local/tmp/test/script/file.sh')
+ device.RunShellCommand.assert_called_once_with(
+ ['sh', '/data/local/tmp/test/script/file.sh'])
+
+ def testCalculateDeviceMd5Sums_generator(self):
+ test_path = ('/storage/emulated/legacy/test/file%d.dat' % n
+ for n in xrange(0, 2))
+
+ device = mock.NonCallableMock()
+ device.adb = mock.NonCallableMock()
+ device.adb.Push = mock.Mock()
+ device_md5sum_output = [
+ '0123456789abcdeffedcba9876543210 '
+ '/storage/emulated/legacy/test/file0.dat',
+ '123456789abcdef00fedcba987654321 '
+ '/storage/emulated/legacy/test/file1.dat',
+ ]
+ device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
+
+ mock_temp_file = mock.mock_open()
+ mock_temp_file.return_value.name = '/tmp/test/script/file.sh'
+
+ mock_device_temp_file = mock.mock_open()
+ mock_device_temp_file.return_value.name = (
+ '/data/local/tmp/test/script/file.sh')
+
+ with mock.patch('tempfile.NamedTemporaryFile', new=mock_temp_file), (
+ mock.patch('pylib.utils.device_temp_file.DeviceTempFile',
+ new=mock_device_temp_file)):
+ out = md5sum.CalculateDeviceMd5Sums(test_path, device)
+ self.assertEquals(2, len(out))
+ self.assertTrue('/storage/emulated/legacy/test/file0.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/storage/emulated/legacy/test/file0.dat'])
+ self.assertTrue('/storage/emulated/legacy/test/file1.dat' in out)
+ self.assertEquals('123456789abcdef00fedcba987654321',
+ out['/storage/emulated/legacy/test/file1.dat'])
+ device.adb.Push.assert_called_once_with(
+ '/tmp/test/script/file.sh', '/data/local/tmp/test/script/file.sh')
+ device.RunShellCommand.assert_called_once_with(
+ ['sh', '/data/local/tmp/test/script/file.sh'])
+
+ def testCalculateDeviceMd5Sums_singlePath_linkerWarning(self):
+ # See crbug/479966
+ test_path = '/storage/emulated/legacy/test/file.dat'
+
+ device = mock.NonCallableMock()
+ device.adb = mock.NonCallableMock()
+ device.adb.Push = mock.Mock()
+ device_md5sum_output = [
+ 'WARNING: linker: /data/local/tmp/md5sum/md5sum_bin: '
+ 'unused DT entry: type 0x1d arg 0x15db',
+ 'THIS_IS_NOT_A_VALID_CHECKSUM_ZZZ some random text',
+ '0123456789abcdeffedcba9876543210 '
+ '/storage/emulated/legacy/test/file.dat',
+ ]
+ device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
+
+ mock_temp_file = mock.mock_open()
+ mock_temp_file.return_value.name = '/tmp/test/script/file.sh'
+
+ mock_device_temp_file = mock.mock_open()
+ mock_device_temp_file.return_value.name = (
+ '/data/local/tmp/test/script/file.sh')
+
+ with mock.patch('tempfile.NamedTemporaryFile', new=mock_temp_file), (
+ mock.patch('pylib.utils.device_temp_file.DeviceTempFile',
+ new=mock_device_temp_file)):
+ out = md5sum.CalculateDeviceMd5Sums(test_path, device)
+ self.assertEquals(1, len(out))
+ self.assertTrue('/storage/emulated/legacy/test/file.dat' in out)
+ self.assertEquals('0123456789abcdeffedcba9876543210',
+ out['/storage/emulated/legacy/test/file.dat'])
+ device.adb.Push.assert_called_once_with(
+ '/tmp/test/script/file.sh', '/data/local/tmp/test/script/file.sh')
+ device.RunShellCommand.assert_called_once_with(
+ ['sh', '/data/local/tmp/test/script/file.sh'])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/utils/mock_calls.py b/build/android/pylib/utils/mock_calls.py
new file mode 100644
index 00000000000..59167bacbf1
--- /dev/null
+++ b/build/android/pylib/utils/mock_calls.py
@@ -0,0 +1,182 @@
+# Copyright 2014 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.
+
+"""
+A test facility to assert call sequences while mocking their behavior.
+"""
+
+import os
+import sys
+import unittest
+
+from pylib import constants
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class TestCase(unittest.TestCase):
+ """Adds assertCalls to TestCase objects."""
+ class _AssertCalls(object):
+ def __init__(self, test_case, expected_calls, watched):
+ def call_action(pair):
+ if isinstance(pair, type(mock.call)):
+ return (pair, None)
+ else:
+ return pair
+
+ def do_check(call):
+ def side_effect(*args, **kwargs):
+ received_call = call(*args, **kwargs)
+ self._test_case.assertTrue(
+ self._expected_calls,
+ msg=('Unexpected call: %s' % str(received_call)))
+ expected_call, action = self._expected_calls.pop(0)
+ self._test_case.assertTrue(
+ received_call == expected_call,
+ msg=('Expected call mismatch:\n'
+ ' expected: %s\n'
+ ' received: %s\n'
+ % (str(expected_call), str(received_call))))
+ if callable(action):
+ return action(*args, **kwargs)
+ else:
+ return action
+ return side_effect
+
+ self._test_case = test_case
+ self._expected_calls = [call_action(pair) for pair in expected_calls]
+ watched = watched.copy() # do not pollute the caller's dict
+ watched.update((call.parent.name, call.parent)
+ for call, _ in self._expected_calls)
+ self._patched = [test_case.patch_call(call, side_effect=do_check(call))
+ for call in watched.itervalues()]
+
+ def __enter__(self):
+ for patch in self._patched:
+ patch.__enter__()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ for patch in self._patched:
+ patch.__exit__(exc_type, exc_val, exc_tb)
+ if exc_type is None:
+ missing = ''.join(' expected: %s\n' % str(call)
+ for call, _ in self._expected_calls)
+ self._test_case.assertFalse(
+ missing,
+ msg='Expected calls not found:\n' + missing)
+
+ def __init__(self, *args, **kwargs):
+ super(TestCase, self).__init__(*args, **kwargs)
+ self.call = mock.call.self
+ self._watched = {}
+
+ def call_target(self, call):
+ """Resolve a self.call instance to the target it represents.
+
+ Args:
+ call: a self.call instance, e.g. self.call.adb.Shell
+
+ Returns:
+ The target object represented by the call, e.g. self.adb.Shell
+
+ Raises:
+ ValueError if the path of the call does not start with "self", i.e. the
+ target of the call is external to the self object.
+ AttributeError if the path of the call does not specify a valid
+ chain of attributes (without any calls) starting from "self".
+ """
+ path = call.name.split('.')
+ if path.pop(0) != 'self':
+ raise ValueError("Target %r outside of 'self' object" % call.name)
+ target = self
+ for attr in path:
+ target = getattr(target, attr)
+ return target
+
+ def patch_call(self, call, **kwargs):
+ """Patch the target of a mock.call instance.
+
+ Args:
+ call: a mock.call instance identifying a target to patch
+ Extra keyword arguments are processed by mock.patch
+
+ Returns:
+ A context manager to mock/unmock the target of the call
+ """
+ if call.name.startswith('self.'):
+ target = self.call_target(call.parent)
+ _, attribute = call.name.rsplit('.', 1)
+ if (hasattr(type(target), attribute)
+ and isinstance(getattr(type(target), attribute), property)):
+ return mock.patch.object(
+ type(target), attribute, new_callable=mock.PropertyMock, **kwargs)
+ else:
+ return mock.patch.object(target, attribute, **kwargs)
+ else:
+ return mock.patch(call.name, **kwargs)
+
+ def watchCalls(self, calls):
+ """Add calls to the set of watched calls.
+
+ Args:
+ calls: a sequence of mock.call instances identifying targets to watch
+ """
+ self._watched.update((call.name, call) for call in calls)
+
+ def watchMethodCalls(self, call, ignore=None):
+ """Watch all public methods of the target identified by a self.call.
+
+ Args:
+ call: a self.call instance indetifying an object
+ ignore: a list of public methods to ignore when watching for calls
+ """
+ target = self.call_target(call)
+ if ignore is None:
+ ignore = []
+ self.watchCalls(getattr(call, method)
+ for method in dir(target.__class__)
+ if not method.startswith('_') and not method in ignore)
+
+ def clearWatched(self):
+ """Clear the set of watched calls."""
+ self._watched = {}
+
+ def assertCalls(self, *calls):
+ """A context manager to assert that a sequence of calls is made.
+
+ During the assertion, a number of functions and methods will be "watched",
+ and any calls made to them is expected to appear---in the exact same order,
+ and with the exact same arguments---as specified by the argument |calls|.
+
+ By default, the targets of all expected calls are watched. Further targets
+ to watch may be added using watchCalls and watchMethodCalls.
+
+ Optionaly, each call may be accompanied by an action. If the action is a
+ (non-callable) value, this value will be used as the return value given to
+ the caller when the matching call is found. Alternatively, if the action is
+ a callable, the action will be then called with the same arguments as the
+ intercepted call, so that it can provide a return value or perform other
+ side effects. If the action is missing, a return value of None is assumed.
+
+ Note that mock.Mock objects are often convenient to use as a callable
+ action, e.g. to raise exceptions or return other objects which are
+ themselves callable.
+
+ Args:
+ calls: each argument is either a pair (expected_call, action) or just an
+ expected_call, where expected_call is a mock.call instance.
+
+ Raises:
+ AssertionError if the watched targets do not receive the exact sequence
+ of calls specified. Missing calls, extra calls, and calls with
+ mismatching arguments, all cause the assertion to fail.
+ """
+ return self._AssertCalls(self, calls, self._watched)
+
+ def assertCall(self, call, action=None):
+ return self.assertCalls((call, action))
+
diff --git a/build/android/pylib/utils/mock_calls_test.py b/build/android/pylib/utils/mock_calls_test.py
new file mode 100755
index 00000000000..4dbafd4715d
--- /dev/null
+++ b/build/android/pylib/utils/mock_calls_test.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""
+Unit tests for the contents of mock_calls.py.
+"""
+
+import logging
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.utils import mock_calls
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class _DummyAdb(object):
+ def __str__(self):
+ return '0123456789abcdef'
+
+ def Push(self, host_path, device_path):
+ logging.debug('(device %s) pushing %r to %r', self, host_path, device_path)
+
+ def IsOnline(self):
+ logging.debug('(device %s) checking device online', self)
+ return True
+
+ def Shell(self, cmd):
+ logging.debug('(device %s) running command %r', self, cmd)
+ return "nice output\n"
+
+ def Reboot(self):
+ logging.debug('(device %s) rebooted!', self)
+
+ @property
+ def build_version_sdk(self):
+ logging.debug('(device %s) getting build_version_sdk', self)
+ return constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP
+
+
+class TestCaseWithAssertCallsTest(mock_calls.TestCase):
+ def setUp(self):
+ self.adb = _DummyAdb()
+
+ def ShellError(self):
+ def action(cmd):
+ raise ValueError('(device %s) command %r is not nice' % (self.adb, cmd))
+ return action
+
+ def get_answer(self):
+ logging.debug("called 'get_answer' of %r object", self)
+ return 42
+
+ def echo(self, thing):
+ logging.debug("called 'echo' of %r object", self)
+ return thing
+
+ def testCallTarget_succeds(self):
+ self.assertEquals(self.adb.Shell,
+ self.call_target(self.call.adb.Shell))
+
+ def testCallTarget_failsExternal(self):
+ with self.assertRaises(ValueError):
+ self.call_target(mock.call.sys.getcwd)
+
+ def testCallTarget_failsUnknownAttribute(self):
+ with self.assertRaises(AttributeError):
+ self.call_target(self.call.adb.Run)
+
+ def testCallTarget_failsIntermediateCalls(self):
+ with self.assertRaises(AttributeError):
+ self.call_target(self.call.adb.RunShell('cmd').append)
+
+ def testPatchCall_method(self):
+ self.assertEquals(42, self.get_answer())
+ with self.patch_call(self.call.get_answer, return_value=123):
+ self.assertEquals(123, self.get_answer())
+ self.assertEquals(42, self.get_answer())
+
+ def testPatchCall_attribute_method(self):
+ with self.patch_call(self.call.adb.Shell, return_value='hello'):
+ self.assertEquals('hello', self.adb.Shell('echo hello'))
+
+ def testPatchCall_global(self):
+ with self.patch_call(mock.call.os.getcwd, return_value='/some/path'):
+ self.assertEquals('/some/path', os.getcwd())
+
+ def testPatchCall_withSideEffect(self):
+ with self.patch_call(self.call.adb.Shell, side_effect=ValueError):
+ with self.assertRaises(ValueError):
+ self.adb.Shell('echo hello')
+
+ def testPatchCall_property(self):
+ self.assertEquals(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP,
+ self.adb.build_version_sdk)
+ with self.patch_call(
+ self.call.adb.build_version_sdk,
+ return_value=constants.ANDROID_SDK_VERSION_CODES.KITKAT):
+ self.assertEquals(constants.ANDROID_SDK_VERSION_CODES.KITKAT,
+ self.adb.build_version_sdk)
+ self.assertEquals(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP,
+ self.adb.build_version_sdk)
+
+ def testAssertCalls_succeeds_simple(self):
+ self.assertEquals(42, self.get_answer())
+ with self.assertCall(self.call.get_answer(), 123):
+ self.assertEquals(123, self.get_answer())
+ self.assertEquals(42, self.get_answer())
+
+ def testAssertCalls_succeeds_multiple(self):
+ with self.assertCalls(
+ (mock.call.os.getcwd(), '/some/path'),
+ (self.call.echo('hello'), 'hello'),
+ (self.call.get_answer(), 11),
+ self.call.adb.Push('this_file', 'that_file'),
+ (self.call.get_answer(), 12)):
+ self.assertEquals(os.getcwd(), '/some/path')
+ self.assertEquals('hello', self.echo('hello'))
+ self.assertEquals(11, self.get_answer())
+ self.adb.Push('this_file', 'that_file')
+ self.assertEquals(12, self.get_answer())
+
+ def testAsserCalls_succeeds_withAction(self):
+ with self.assertCall(
+ self.call.adb.Shell('echo hello'), self.ShellError()):
+ with self.assertRaises(ValueError):
+ self.adb.Shell('echo hello')
+
+ def testAssertCalls_fails_tooManyCalls(self):
+ with self.assertRaises(AssertionError):
+ with self.assertCalls(self.call.adb.IsOnline()):
+ self.adb.IsOnline()
+ self.adb.IsOnline()
+
+ def testAssertCalls_fails_tooFewCalls(self):
+ with self.assertRaises(AssertionError):
+ with self.assertCalls(self.call.adb.IsOnline()):
+ pass
+
+ def testAssertCalls_succeeds_extraCalls(self):
+ # we are not watching Reboot, so the assertion succeeds
+ with self.assertCalls(self.call.adb.IsOnline()):
+ self.adb.IsOnline()
+ self.adb.Reboot()
+
+ def testAssertCalls_fails_extraCalls(self):
+ self.watchCalls([self.call.adb.Reboot])
+ # this time we are also watching Reboot, so the assertion fails
+ with self.assertRaises(AssertionError):
+ with self.assertCalls(self.call.adb.IsOnline()):
+ self.adb.IsOnline()
+ self.adb.Reboot()
+
+ def testAssertCalls_succeeds_NoCalls(self):
+ self.watchMethodCalls(self.call.adb) # we are watching all adb methods
+ with self.assertCalls():
+ pass
+
+ def testAssertCalls_fails_NoCalls(self):
+ self.watchMethodCalls(self.call.adb)
+ with self.assertRaises(AssertionError):
+ with self.assertCalls():
+ self.adb.IsOnline()
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/utils/parallelizer.py b/build/android/pylib/utils/parallelizer.py
new file mode 100644
index 00000000000..9a85b54a5e8
--- /dev/null
+++ b/build/android/pylib/utils/parallelizer.py
@@ -0,0 +1,242 @@
+# Copyright 2014 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.
+
+""" Wrapper that allows method execution in parallel.
+
+This class wraps a list of objects of the same type, emulates their
+interface, and executes any functions called on the objects in parallel
+in ReraiserThreads.
+
+This means that, given a list of objects:
+
+ class Foo:
+ def __init__(self):
+ self.baz = Baz()
+
+ def bar(self, my_param):
+ // do something
+
+ list_of_foos = [Foo(1), Foo(2), Foo(3)]
+
+we can take a sequential operation on that list of objects:
+
+ for f in list_of_foos:
+ f.bar('Hello')
+
+and run it in parallel across all of the objects:
+
+ Parallelizer(list_of_foos).bar('Hello')
+
+It can also handle (non-method) attributes of objects, so that this:
+
+ for f in list_of_foos:
+ f.baz.myBazMethod()
+
+can be run in parallel with:
+
+ Parallelizer(list_of_foos).baz.myBazMethod()
+
+Because it emulates the interface of the wrapped objects, a Parallelizer
+can be passed to a method or function that takes objects of that type:
+
+ def DoesSomethingWithFoo(the_foo):
+ the_foo.bar('Hello')
+ the_foo.bar('world')
+ the_foo.baz.myBazMethod
+
+ DoesSomethingWithFoo(Parallelizer(list_of_foos))
+
+Note that this class spins up a thread for each object. Using this class
+to parallelize operations that are already fast will incur a net performance
+penalty.
+
+"""
+# pylint: disable=protected-access
+
+from pylib.utils import reraiser_thread
+from pylib.utils import watchdog_timer
+
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 3
+
+
+class Parallelizer(object):
+ """Allows parallel execution of method calls across a group of objects."""
+
+ def __init__(self, objs):
+ assert (objs is not None and len(objs) > 0), (
+ "Passed empty list to 'Parallelizer'")
+ self._orig_objs = objs
+ self._objs = objs
+
+ def __getattr__(self, name):
+ """Emulate getting the |name| attribute of |self|.
+
+ Args:
+ name: The name of the attribute to retrieve.
+ Returns:
+ A Parallelizer emulating the |name| attribute of |self|.
+ """
+ self.pGet(None)
+
+ r = type(self)(self._orig_objs)
+ r._objs = [getattr(o, name) for o in self._objs]
+ return r
+
+ def __getitem__(self, index):
+ """Emulate getting the value of |self| at |index|.
+
+ Returns:
+ A Parallelizer emulating the value of |self| at |index|.
+ """
+ self.pGet(None)
+
+ r = type(self)(self._orig_objs)
+ r._objs = [o[index] for o in self._objs]
+ return r
+
+ def __call__(self, *args, **kwargs):
+ """Emulate calling |self| with |args| and |kwargs|.
+
+ Note that this call is asynchronous. Call pFinish on the return value to
+ block until the call finishes.
+
+ Returns:
+ A Parallelizer wrapping the ReraiserThreadGroup running the call in
+ parallel.
+ Raises:
+ AttributeError if the wrapped objects aren't callable.
+ """
+ self.pGet(None)
+
+ if not self._objs:
+ raise AttributeError('Nothing to call.')
+ for o in self._objs:
+ if not callable(o):
+ raise AttributeError("'%s' is not callable" % o.__name__)
+
+ r = type(self)(self._orig_objs)
+ r._objs = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(
+ o, args=args, kwargs=kwargs,
+ name='%s.%s' % (str(d), o.__name__))
+ for d, o in zip(self._orig_objs, self._objs)])
+ r._objs.StartAll() # pylint: disable=W0212
+ return r
+
+ def pFinish(self, timeout):
+ """Finish any outstanding asynchronous operations.
+
+ Args:
+ timeout: The maximum number of seconds to wait for an individual
+ result to return, or None to wait forever.
+ Returns:
+ self, now emulating the return values.
+ """
+ self._assertNoShadow('pFinish')
+ if isinstance(self._objs, reraiser_thread.ReraiserThreadGroup):
+ self._objs.JoinAll()
+ self._objs = self._objs.GetAllReturnValues(
+ watchdog_timer.WatchdogTimer(timeout))
+ return self
+
+ def pGet(self, timeout):
+ """Get the current wrapped objects.
+
+ Args:
+ timeout: Same as |pFinish|.
+ Returns:
+ A list of the results, in order of the provided devices.
+ Raises:
+ Any exception raised by any of the called functions.
+ """
+ self._assertNoShadow('pGet')
+ self.pFinish(timeout)
+ return self._objs
+
+ def pMap(self, f, *args, **kwargs):
+ """Map a function across the current wrapped objects in parallel.
+
+ This calls f(o, *args, **kwargs) for each o in the set of wrapped objects.
+
+ Note that this call is asynchronous. Call pFinish on the return value to
+ block until the call finishes.
+
+ Args:
+ f: The function to call.
+ args: The positional args to pass to f.
+ kwargs: The keyword args to pass to f.
+ Returns:
+ A Parallelizer wrapping the ReraiserThreadGroup running the map in
+ parallel.
+ """
+ self._assertNoShadow('pMap')
+ r = type(self)(self._orig_objs)
+ r._objs = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(
+ f, args=tuple([o] + list(args)), kwargs=kwargs,
+ name='%s(%s)' % (f.__name__, d))
+ for d, o in zip(self._orig_objs, self._objs)])
+ r._objs.StartAll() # pylint: disable=W0212
+ return r
+
+ def _assertNoShadow(self, attr_name):
+ """Ensures that |attr_name| isn't shadowing part of the wrapped obejcts.
+
+ If the wrapped objects _do_ have an |attr_name| attribute, it will be
+ inaccessible to clients.
+
+ Args:
+ attr_name: The attribute to check.
+ Raises:
+ AssertionError if the wrapped objects have an attribute named 'attr_name'
+ or '_assertNoShadow'.
+ """
+ if isinstance(self._objs, reraiser_thread.ReraiserThreadGroup):
+ assert not hasattr(self._objs, '_assertNoShadow')
+ assert not hasattr(self._objs, attr_name)
+ else:
+ assert not any(hasattr(o, '_assertNoShadow') for o in self._objs)
+ assert not any(hasattr(o, attr_name) for o in self._objs)
+
+
+class SyncParallelizer(Parallelizer):
+ """A Parallelizer that blocks on function calls."""
+
+ #override
+ def __call__(self, *args, **kwargs):
+ """Emulate calling |self| with |args| and |kwargs|.
+
+ Note that this call is synchronous.
+
+ Returns:
+ A Parallelizer emulating the value returned from calling |self| with
+ |args| and |kwargs|.
+ Raises:
+ AttributeError if the wrapped objects aren't callable.
+ """
+ r = super(SyncParallelizer, self).__call__(*args, **kwargs)
+ r.pFinish(None)
+ return r
+
+ #override
+ def pMap(self, f, *args, **kwargs):
+ """Map a function across the current wrapped objects in parallel.
+
+ This calls f(o, *args, **kwargs) for each o in the set of wrapped objects.
+
+ Note that this call is synchronous.
+
+ Args:
+ f: The function to call.
+ args: The positional args to pass to f.
+ kwargs: The keyword args to pass to f.
+ Returns:
+ A Parallelizer wrapping the ReraiserThreadGroup running the map in
+ parallel.
+ """
+ r = super(SyncParallelizer, self).pMap(f, *args, **kwargs)
+ r.pFinish(None)
+ return r
+
diff --git a/build/android/pylib/utils/parallelizer_test.py b/build/android/pylib/utils/parallelizer_test.py
new file mode 100644
index 00000000000..6e0c7e79a58
--- /dev/null
+++ b/build/android/pylib/utils/parallelizer_test.py
@@ -0,0 +1,166 @@
+# Copyright 2014 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.
+
+"""Unit tests for the contents of parallelizer.py."""
+
+# pylint: disable=W0212
+# pylint: disable=W0613
+
+import os
+import tempfile
+import time
+import unittest
+
+from pylib.utils import parallelizer
+
+
+class ParallelizerTestObject(object):
+ """Class used to test parallelizer.Parallelizer."""
+
+ parallel = parallelizer.Parallelizer
+
+ def __init__(self, thing, completion_file_name=None):
+ self._thing = thing
+ self._completion_file_name = completion_file_name
+ self.helper = ParallelizerTestObjectHelper(thing)
+
+ @staticmethod
+ def doReturn(what):
+ return what
+
+ @classmethod
+ def doRaise(cls, what):
+ raise what
+
+ def doSetTheThing(self, new_thing):
+ self._thing = new_thing
+
+ def doReturnTheThing(self):
+ return self._thing
+
+ def doRaiseTheThing(self):
+ raise self._thing
+
+ def doRaiseIfExceptionElseSleepFor(self, sleep_duration):
+ if isinstance(self._thing, Exception):
+ raise self._thing
+ time.sleep(sleep_duration)
+ self._write_completion_file()
+ return self._thing
+
+ def _write_completion_file(self):
+ if self._completion_file_name and len(self._completion_file_name):
+ with open(self._completion_file_name, 'w+b') as completion_file:
+ completion_file.write('complete')
+
+ def __getitem__(self, index):
+ return self._thing[index]
+
+ def __str__(self):
+ return type(self).__name__
+
+
+class ParallelizerTestObjectHelper(object):
+
+ def __init__(self, thing):
+ self._thing = thing
+
+ def doReturnStringThing(self):
+ return str(self._thing)
+
+
+class ParallelizerTest(unittest.TestCase):
+
+ def testInitWithNone(self):
+ with self.assertRaises(AssertionError):
+ parallelizer.Parallelizer(None)
+
+ def testInitEmptyList(self):
+ with self.assertRaises(AssertionError):
+ parallelizer.Parallelizer([])
+
+ def testMethodCall(self):
+ test_data = ['abc_foo', 'def_foo', 'ghi_foo']
+ expected = ['abc_bar', 'def_bar', 'ghi_bar']
+ r = parallelizer.Parallelizer(test_data).replace('_foo', '_bar').pGet(0.1)
+ self.assertEquals(expected, r)
+
+ def testMutate(self):
+ devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
+ self.assertTrue(all(d.doReturnTheThing() for d in devices))
+ ParallelizerTestObject.parallel(devices).doSetTheThing(False).pFinish(1)
+ self.assertTrue(not any(d.doReturnTheThing() for d in devices))
+
+ def testAllReturn(self):
+ devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
+ results = ParallelizerTestObject.parallel(
+ devices).doReturnTheThing().pGet(1)
+ self.assertTrue(isinstance(results, list))
+ self.assertEquals(10, len(results))
+ self.assertTrue(all(results))
+
+ def testAllRaise(self):
+ devices = [ParallelizerTestObject(Exception('thing %d' % i))
+ for i in xrange(0, 10)]
+ p = ParallelizerTestObject.parallel(devices).doRaiseTheThing()
+ with self.assertRaises(Exception):
+ p.pGet(1)
+
+ def testOneFailOthersComplete(self):
+ parallel_device_count = 10
+ exception_index = 7
+ exception_msg = 'thing %d' % exception_index
+
+ try:
+ completion_files = [tempfile.NamedTemporaryFile(delete=False)
+ for _ in xrange(0, parallel_device_count)]
+ devices = [
+ ParallelizerTestObject(
+ i if i != exception_index else Exception(exception_msg),
+ completion_files[i].name)
+ for i in xrange(0, parallel_device_count)]
+ for f in completion_files:
+ f.close()
+ p = ParallelizerTestObject.parallel(devices)
+ with self.assertRaises(Exception) as e:
+ p.doRaiseIfExceptionElseSleepFor(2).pGet(3)
+ self.assertTrue(exception_msg in str(e.exception))
+ for i in xrange(0, parallel_device_count):
+ with open(completion_files[i].name) as f:
+ if i == exception_index:
+ self.assertEquals('', f.read())
+ else:
+ self.assertEquals('complete', f.read())
+ finally:
+ for f in completion_files:
+ os.remove(f.name)
+
+ def testReusable(self):
+ devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
+ p = ParallelizerTestObject.parallel(devices)
+ results = p.doReturn(True).pGet(1)
+ self.assertTrue(all(results))
+ results = p.doReturn(True).pGet(1)
+ self.assertTrue(all(results))
+ with self.assertRaises(Exception):
+ results = p.doRaise(Exception('reusableTest')).pGet(1)
+
+ def testContained(self):
+ devices = [ParallelizerTestObject(i) for i in xrange(0, 10)]
+ results = (ParallelizerTestObject.parallel(devices).helper
+ .doReturnStringThing().pGet(1))
+ self.assertTrue(isinstance(results, list))
+ self.assertEquals(10, len(results))
+ for i in xrange(0, 10):
+ self.assertEquals(str(i), results[i])
+
+ def testGetItem(self):
+ devices = [ParallelizerTestObject(range(i, i+10)) for i in xrange(0, 10)]
+ results = ParallelizerTestObject.parallel(devices)[9].pGet(1)
+ self.assertEquals(range(9, 19), results)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
+
diff --git a/build/android/pylib/utils/proguard.py b/build/android/pylib/utils/proguard.py
new file mode 100644
index 00000000000..34ad5c36316
--- /dev/null
+++ b/build/android/pylib/utils/proguard.py
@@ -0,0 +1,148 @@
+# Copyright 2014 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.
+
+import os
+import re
+import tempfile
+
+from pylib import constants
+from pylib import cmd_helper
+
+
+_PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$')
+_PROGUARD_SUPERCLASS_RE = re.compile(r'\s*? Superclass:\s*([\S]+)$')
+_PROGUARD_SECTION_RE = re.compile(
+ r'^(?:Interfaces|Constant Pool|Fields|Methods|Class file attributes) '
+ r'\(count = \d+\):$')
+_PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$')
+_PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$')
+_PROGUARD_ANNOTATION_CONST_RE = (
+ re.compile(r'\s*?- Constant element value.*$'))
+_PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
+
+_PROGUARD_PATH_SDK = os.path.join(
+ constants.ANDROID_SDK_ROOT, 'tools', 'proguard', 'lib', 'proguard.jar')
+_PROGUARD_PATH_BUILT = (
+ os.path.join(os.environ['ANDROID_BUILD_TOP'], 'external', 'proguard',
+ 'lib', 'proguard.jar')
+ if 'ANDROID_BUILD_TOP' in os.environ else None)
+_PROGUARD_PATH = (
+ _PROGUARD_PATH_SDK if os.path.exists(_PROGUARD_PATH_SDK)
+ else _PROGUARD_PATH_BUILT)
+
+
+def Dump(jar_path):
+ """Dumps class and method information from a JAR into a dict via proguard.
+
+ Args:
+ jar_path: An absolute path to the JAR file to dump.
+ Returns:
+ A dict in the following format:
+ {
+ 'classes': [
+ {
+ 'class': '',
+ 'superclass': '',
+ 'annotations': {},
+ 'methods': [
+ {
+ 'method': '',
+ 'annotations': {},
+ },
+ ...
+ ],
+ },
+ ...
+ ],
+ }
+ """
+
+ with tempfile.NamedTemporaryFile() as proguard_output:
+ cmd_helper.RunCmd(['java', '-jar',
+ _PROGUARD_PATH,
+ '-injars', jar_path,
+ '-dontshrink',
+ '-dontoptimize',
+ '-dontobfuscate',
+ '-dontpreverify',
+ '-dump', proguard_output.name])
+
+
+ results = {
+ 'classes': [],
+ }
+
+ annotation = None
+ annotation_has_value = False
+ class_result = None
+ method_result = None
+
+ for line in proguard_output:
+ line = line.strip('\r\n')
+
+ m = _PROGUARD_CLASS_RE.match(line)
+ if m:
+ class_result = {
+ 'class': m.group(1).replace('/', '.'),
+ 'superclass': '',
+ 'annotations': {},
+ 'methods': [],
+ }
+ results['classes'].append(class_result)
+ annotation = None
+ annotation_has_value = False
+ method_result = None
+ continue
+
+ if not class_result:
+ continue
+
+ m = _PROGUARD_SUPERCLASS_RE.match(line)
+ if m:
+ class_result['superclass'] = m.group(1).replace('/', '.')
+ continue
+
+ m = _PROGUARD_SECTION_RE.match(line)
+ if m:
+ annotation = None
+ annotation_has_value = False
+ method_result = None
+ continue
+
+ m = _PROGUARD_METHOD_RE.match(line)
+ if m:
+ method_result = {
+ 'method': m.group(1),
+ 'annotations': {},
+ }
+ class_result['methods'].append(method_result)
+ annotation = None
+ annotation_has_value = False
+ continue
+
+ m = _PROGUARD_ANNOTATION_RE.match(line)
+ if m:
+ # Ignore the annotation package.
+ annotation = m.group(1).split('/')[-1]
+ if method_result:
+ method_result['annotations'][annotation] = None
+ else:
+ class_result['annotations'][annotation] = None
+ continue
+
+ if annotation:
+ if not annotation_has_value:
+ m = _PROGUARD_ANNOTATION_CONST_RE.match(line)
+ annotation_has_value = bool(m)
+ else:
+ m = _PROGUARD_ANNOTATION_VALUE_RE.match(line)
+ if m:
+ if method_result:
+ method_result['annotations'][annotation] = m.group(1)
+ else:
+ class_result['annotations'][annotation] = m.group(1)
+ annotation_has_value = None
+
+ return results
+
diff --git a/build/android/pylib/utils/repo_utils.py b/build/android/pylib/utils/repo_utils.py
new file mode 100644
index 00000000000..e0c7d2c2552
--- /dev/null
+++ b/build/android/pylib/utils/repo_utils.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2013 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.
+
+from pylib import cmd_helper
+
+
+def GetGitHeadSHA1(in_directory):
+ """Returns the git hash tag for the given directory.
+
+ Args:
+ in_directory: The directory where git is to be run.
+ """
+ command_line = ['git', 'log', '-1', '--pretty=format:%H']
+ output = cmd_helper.GetCmdOutput(command_line, cwd=in_directory)
+ return output[0:40]
diff --git a/build/android/pylib/utils/reraiser_thread.py b/build/android/pylib/utils/reraiser_thread.py
new file mode 100644
index 00000000000..0ec16b1908c
--- /dev/null
+++ b/build/android/pylib/utils/reraiser_thread.py
@@ -0,0 +1,158 @@
+# Copyright 2013 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.
+
+"""Thread and ThreadGroup that reraise exceptions on the main thread."""
+# pylint: disable=W0212
+
+import logging
+import sys
+import threading
+import traceback
+
+from pylib.utils import watchdog_timer
+
+
+class TimeoutError(Exception):
+ """Module-specific timeout exception."""
+ pass
+
+
+def LogThreadStack(thread):
+ """Log the stack for the given thread.
+
+ Args:
+ thread: a threading.Thread instance.
+ """
+ stack = sys._current_frames()[thread.ident]
+ logging.critical('*' * 80)
+ logging.critical('Stack dump for thread %r', thread.name)
+ logging.critical('*' * 80)
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ logging.critical('File: "%s", line %d, in %s', filename, lineno, name)
+ if line:
+ logging.critical(' %s', line.strip())
+ logging.critical('*' * 80)
+
+
+class ReraiserThread(threading.Thread):
+ """Thread class that can reraise exceptions."""
+
+ def __init__(self, func, args=None, kwargs=None, name=None):
+ """Initialize thread.
+
+ Args:
+ func: callable to call on a new thread.
+ args: list of positional arguments for callable, defaults to empty.
+ kwargs: dictionary of keyword arguments for callable, defaults to empty.
+ name: thread name, defaults to Thread-N.
+ """
+ super(ReraiserThread, self).__init__(name=name)
+ if not args:
+ args = []
+ if not kwargs:
+ kwargs = {}
+ self.daemon = True
+ self._func = func
+ self._args = args
+ self._kwargs = kwargs
+ self._ret = None
+ self._exc_info = None
+
+ def ReraiseIfException(self):
+ """Reraise exception if an exception was raised in the thread."""
+ if self._exc_info:
+ raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
+
+ def GetReturnValue(self):
+ """Reraise exception if present, otherwise get the return value."""
+ self.ReraiseIfException()
+ return self._ret
+
+ #override
+ def run(self):
+ """Overrides Thread.run() to add support for reraising exceptions."""
+ try:
+ self._ret = self._func(*self._args, **self._kwargs)
+ except: # pylint: disable=W0702
+ self._exc_info = sys.exc_info()
+
+
+class ReraiserThreadGroup(object):
+ """A group of ReraiserThread objects."""
+
+ def __init__(self, threads=None):
+ """Initialize thread group.
+
+ Args:
+ threads: a list of ReraiserThread objects; defaults to empty.
+ """
+ if not threads:
+ threads = []
+ self._threads = threads
+
+ def Add(self, thread):
+ """Add a thread to the group.
+
+ Args:
+ thread: a ReraiserThread object.
+ """
+ self._threads.append(thread)
+
+ def StartAll(self):
+ """Start all threads."""
+ for thread in self._threads:
+ thread.start()
+
+ def _JoinAll(self, watcher=None):
+ """Join all threads without stack dumps.
+
+ Reraises exceptions raised by the child threads and supports breaking
+ immediately on exceptions raised on the main thread.
+
+ Args:
+ watcher: Watchdog object providing timeout, by default waits forever.
+ """
+ if watcher is None:
+ watcher = watchdog_timer.WatchdogTimer(None)
+ alive_threads = self._threads[:]
+ while alive_threads:
+ for thread in alive_threads[:]:
+ if watcher.IsTimedOut():
+ raise TimeoutError('Timed out waiting for %d of %d threads.' %
+ (len(alive_threads), len(self._threads)))
+ # Allow the main thread to periodically check for interrupts.
+ thread.join(0.1)
+ if not thread.isAlive():
+ alive_threads.remove(thread)
+ # All threads are allowed to complete before reraising exceptions.
+ for thread in self._threads:
+ thread.ReraiseIfException()
+
+ def JoinAll(self, watcher=None):
+ """Join all threads.
+
+ Reraises exceptions raised by the child threads and supports breaking
+ immediately on exceptions raised on the main thread. Unfinished threads'
+ stacks will be logged on watchdog timeout.
+
+ Args:
+ watcher: Watchdog object providing timeout, by default waits forever.
+ """
+ try:
+ self._JoinAll(watcher)
+ except TimeoutError:
+ for thread in (t for t in self._threads if t.isAlive()):
+ LogThreadStack(thread)
+ raise
+
+ def GetAllReturnValues(self, watcher=None):
+ """Get all return values, joining all threads if necessary.
+
+ Args:
+ watcher: same as in |JoinAll|. Only used if threads are alive.
+ """
+ if any([t.isAlive() for t in self._threads]):
+ self.JoinAll(watcher)
+ return [t.GetReturnValue() for t in self._threads]
+
diff --git a/build/android/pylib/utils/reraiser_thread_unittest.py b/build/android/pylib/utils/reraiser_thread_unittest.py
new file mode 100644
index 00000000000..2392d0e95a2
--- /dev/null
+++ b/build/android/pylib/utils/reraiser_thread_unittest.py
@@ -0,0 +1,96 @@
+# Copyright 2013 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.
+
+"""Unittests for reraiser_thread.py."""
+
+import threading
+import unittest
+
+from pylib.utils import reraiser_thread
+from pylib.utils import watchdog_timer
+
+
+class TestException(Exception):
+ pass
+
+
+class TestReraiserThread(unittest.TestCase):
+ """Tests for reraiser_thread.ReraiserThread."""
+ def testNominal(self):
+ result = [None, None]
+
+ def f(a, b=None):
+ result[0] = a
+ result[1] = b
+
+ thread = reraiser_thread.ReraiserThread(f, [1], {'b': 2})
+ thread.start()
+ thread.join()
+ self.assertEqual(result[0], 1)
+ self.assertEqual(result[1], 2)
+
+ def testRaise(self):
+ def f():
+ raise TestException
+
+ thread = reraiser_thread.ReraiserThread(f)
+ thread.start()
+ thread.join()
+ with self.assertRaises(TestException):
+ thread.ReraiseIfException()
+
+
+class TestReraiserThreadGroup(unittest.TestCase):
+ """Tests for reraiser_thread.ReraiserThreadGroup."""
+ def testInit(self):
+ ran = [False] * 5
+ def f(i):
+ ran[i] = True
+
+ group = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(f, args=[i]) for i in range(5)])
+ group.StartAll()
+ group.JoinAll()
+ for v in ran:
+ self.assertTrue(v)
+
+ def testAdd(self):
+ ran = [False] * 5
+ def f(i):
+ ran[i] = True
+
+ group = reraiser_thread.ReraiserThreadGroup()
+ for i in xrange(5):
+ group.Add(reraiser_thread.ReraiserThread(f, args=[i]))
+ group.StartAll()
+ group.JoinAll()
+ for v in ran:
+ self.assertTrue(v)
+
+ def testJoinRaise(self):
+ def f():
+ raise TestException
+ group = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(f) for _ in xrange(5)])
+ group.StartAll()
+ with self.assertRaises(TestException):
+ group.JoinAll()
+
+ def testJoinTimeout(self):
+ def f():
+ pass
+ event = threading.Event()
+ def g():
+ event.wait()
+ group = reraiser_thread.ReraiserThreadGroup(
+ [reraiser_thread.ReraiserThread(g),
+ reraiser_thread.ReraiserThread(f)])
+ group.StartAll()
+ with self.assertRaises(reraiser_thread.TimeoutError):
+ group.JoinAll(watchdog_timer.WatchdogTimer(0.01))
+ event.set()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/utils/run_tests_helper.py b/build/android/pylib/utils/run_tests_helper.py
new file mode 100644
index 00000000000..43f654ddcff
--- /dev/null
+++ b/build/android/pylib/utils/run_tests_helper.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2012 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.
+
+"""Helper functions common to native, java and host-driven test runners."""
+
+import logging
+import sys
+import time
+
+
+class CustomFormatter(logging.Formatter):
+ """Custom log formatter."""
+
+ #override
+ def __init__(self, fmt='%(threadName)-4s %(message)s'):
+ # Can't use super() because in older Python versions logging.Formatter does
+ # not inherit from object.
+ logging.Formatter.__init__(self, fmt=fmt)
+ self._creation_time = time.time()
+
+ #override
+ def format(self, record):
+ # Can't use super() because in older Python versions logging.Formatter does
+ # not inherit from object.
+ msg = logging.Formatter.format(self, record)
+ if 'MainThread' in msg[:19]:
+ msg = msg.replace('MainThread', 'Main', 1)
+ timediff = time.time() - self._creation_time
+ return '%s %8.3fs %s' % (record.levelname[0], timediff, msg)
+
+
+def SetLogLevel(verbose_count):
+ """Sets log level as |verbose_count|."""
+ log_level = logging.WARNING # Default.
+ if verbose_count == 1:
+ log_level = logging.INFO
+ elif verbose_count >= 2:
+ log_level = logging.DEBUG
+ logger = logging.getLogger()
+ logger.setLevel(log_level)
+ custom_handler = logging.StreamHandler(sys.stdout)
+ custom_handler.setFormatter(CustomFormatter())
+ logging.getLogger().addHandler(custom_handler)
diff --git a/build/android/pylib/utils/test_environment.py b/build/android/pylib/utils/test_environment.py
new file mode 100644
index 00000000000..e78eb5ce801
--- /dev/null
+++ b/build/android/pylib/utils/test_environment.py
@@ -0,0 +1,47 @@
+# Copyright 2013 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.
+
+import logging
+import psutil
+import signal
+
+from pylib.device import device_errors
+from pylib.device import device_utils
+
+
+def _KillWebServers():
+ for s in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT, signal.SIGKILL]:
+ signalled = []
+ for server in ['lighttpd', 'webpagereplay']:
+ for p in psutil.process_iter():
+ try:
+ if not server in ' '.join(p.cmdline):
+ continue
+ logging.info('Killing %s %s %s', s, server, p.pid)
+ p.send_signal(s)
+ signalled.append(p)
+ except Exception as e:
+ logging.warning('Failed killing %s %s %s', server, p.pid, e)
+ for p in signalled:
+ try:
+ p.wait(1)
+ except Exception as e:
+ logging.warning('Failed waiting for %s to die. %s', p.pid, e)
+
+
+def CleanupLeftoverProcesses():
+ """Clean up the test environment, restarting fresh adb and HTTP daemons."""
+ _KillWebServers()
+ device_utils.RestartServer()
+
+ def cleanup_device(d):
+ d.old_interface.RestartAdbdOnDevice()
+ try:
+ d.EnableRoot()
+ except device_errors.CommandFailedError as e:
+ logging.error(str(e))
+ d.WaitUntilFullyBooted()
+
+ device_utils.DeviceUtils.parallel().pMap(cleanup_device)
+
diff --git a/build/android/pylib/utils/time_profile.py b/build/android/pylib/utils/time_profile.py
new file mode 100644
index 00000000000..45da7ff3af7
--- /dev/null
+++ b/build/android/pylib/utils/time_profile.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2013 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.
+
+import logging
+import time
+
+
+class TimeProfile(object):
+ """Class for simple profiling of action, with logging of cost."""
+
+ def __init__(self, description):
+ self._starttime = None
+ self._description = description
+ self.Start()
+
+ def Start(self):
+ self._starttime = time.time()
+
+ def Stop(self):
+ """Stop profiling and dump a log."""
+ if self._starttime:
+ stoptime = time.time()
+ logging.info('%fsec to perform %s',
+ stoptime - self._starttime, self._description)
+ self._starttime = None
diff --git a/build/android/pylib/utils/timeout_retry.py b/build/android/pylib/utils/timeout_retry.py
new file mode 100644
index 00000000000..61f7c70baa9
--- /dev/null
+++ b/build/android/pylib/utils/timeout_retry.py
@@ -0,0 +1,167 @@
+# Copyright 2013 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.
+
+"""A utility to run functions with timeouts and retries."""
+# pylint: disable=W0702
+
+import logging
+import threading
+import time
+import traceback
+
+from pylib.utils import reraiser_thread
+from pylib.utils import watchdog_timer
+
+
+class TimeoutRetryThread(reraiser_thread.ReraiserThread):
+ def __init__(self, func, timeout, name):
+ super(TimeoutRetryThread, self).__init__(func, name=name)
+ self._watcher = watchdog_timer.WatchdogTimer(timeout)
+ self._expired = False
+
+ def GetWatcher(self):
+ """Returns the watchdog keeping track of this thread's time."""
+ return self._watcher
+
+ def GetElapsedTime(self):
+ return self._watcher.GetElapsed()
+
+ def GetRemainingTime(self, required=0, msg=None):
+ """Get the remaining time before the thread times out.
+
+ Useful to send as the |timeout| parameter of async IO operations.
+
+ Args:
+ required: minimum amount of time that will be required to complete, e.g.,
+ some sleep or IO operation.
+ msg: error message to show if timing out.
+
+ Returns:
+ The number of seconds remaining before the thread times out, or None
+ if the thread never times out.
+
+ Raises:
+ reraiser_thread.TimeoutError if the remaining time is less than the
+ required time.
+ """
+ remaining = self._watcher.GetRemaining()
+ if remaining is not None and remaining < required:
+ if msg is None:
+ msg = 'Timeout expired'
+ if remaining > 0:
+ msg += (', wait of %.1f secs required but only %.1f secs left'
+ % (required, remaining))
+ self._expired = True
+ raise reraiser_thread.TimeoutError(msg)
+ return remaining
+
+ def LogTimeoutException(self):
+ """Log the exception that terminated this thread."""
+ if not self._expired:
+ return
+ logging.critical('*' * 80)
+ logging.critical('%s on thread %r', self._exc_info[0].__name__, self.name)
+ logging.critical('*' * 80)
+ fmt_exc = ''.join(traceback.format_exception(*self._exc_info))
+ for line in fmt_exc.splitlines():
+ logging.critical(line.rstrip())
+ logging.critical('*' * 80)
+
+
+def CurrentTimeoutThread():
+ """Get the current thread if it is a TimeoutRetryThread.
+
+ Returns:
+ The current thread if it is a TimeoutRetryThread, otherwise None.
+ """
+ current_thread = threading.current_thread()
+ if isinstance(current_thread, TimeoutRetryThread):
+ return current_thread
+ else:
+ return None
+
+
+def WaitFor(condition, wait_period=5, max_tries=None):
+ """Wait for a condition to become true.
+
+ Repeadly call the function condition(), with no arguments, until it returns
+ a true value.
+
+ If called within a TimeoutRetryThread, it cooperates nicely with it.
+
+ Args:
+ condition: function with the condition to check
+ wait_period: number of seconds to wait before retrying to check the
+ condition
+ max_tries: maximum number of checks to make, the default tries forever
+ or until the TimeoutRetryThread expires.
+
+ Returns:
+ The true value returned by the condition, or None if the condition was
+ not met after max_tries.
+
+ Raises:
+ reraiser_thread.TimeoutError if the current thread is a TimeoutRetryThread
+ and the timeout expires.
+ """
+ condition_name = condition.__name__
+ timeout_thread = CurrentTimeoutThread()
+ while max_tries is None or max_tries > 0:
+ result = condition()
+ if max_tries is not None:
+ max_tries -= 1
+ msg = ['condition', repr(condition_name), 'met' if result else 'not met']
+ if timeout_thread:
+ msg.append('(%.1fs)' % timeout_thread.GetElapsedTime())
+ logging.info(' '.join(msg))
+ if result:
+ return result
+ if timeout_thread:
+ timeout_thread.GetRemainingTime(wait_period,
+ msg='Timed out waiting for %r' % condition_name)
+ time.sleep(wait_period)
+ return None
+
+
+def Run(func, timeout, retries, args=None, kwargs=None):
+ """Runs the passed function in a separate thread with timeouts and retries.
+
+ Args:
+ func: the function to be wrapped.
+ timeout: the timeout in seconds for each try.
+ retries: the number of retries.
+ args: list of positional args to pass to |func|.
+ kwargs: dictionary of keyword args to pass to |func|.
+
+ Returns:
+ The return value of func(*args, **kwargs).
+ """
+ if not args:
+ args = []
+ if not kwargs:
+ kwargs = {}
+
+ # The return value uses a list because Python variables are references, not
+ # values. Closures make a copy of the reference, so updating the closure's
+ # reference wouldn't update where the original reference pointed.
+ ret = [None]
+ def RunOnTimeoutThread():
+ ret[0] = func(*args, **kwargs)
+
+ num_try = 1
+ while True:
+ child_thread = TimeoutRetryThread(
+ RunOnTimeoutThread, timeout,
+ name='TimeoutThread-%d-for-%s' % (num_try,
+ threading.current_thread().name))
+ try:
+ thread_group = reraiser_thread.ReraiserThreadGroup([child_thread])
+ thread_group.StartAll()
+ thread_group.JoinAll(child_thread.GetWatcher())
+ return ret[0]
+ except:
+ child_thread.LogTimeoutException()
+ if num_try > retries:
+ raise
+ num_try += 1
diff --git a/build/android/pylib/utils/timeout_retry_unittest.py b/build/android/pylib/utils/timeout_retry_unittest.py
new file mode 100644
index 00000000000..dc36c42ba5c
--- /dev/null
+++ b/build/android/pylib/utils/timeout_retry_unittest.py
@@ -0,0 +1,52 @@
+# Copyright 2013 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.
+
+"""Unittests for timeout_and_retry.py."""
+
+import unittest
+
+from pylib.utils import reraiser_thread
+from pylib.utils import timeout_retry
+
+
+class TestException(Exception):
+ pass
+
+
+def _NeverEnding(tries):
+ tries[0] += 1
+ while True:
+ pass
+
+
+def _CountTries(tries):
+ tries[0] += 1
+ raise TestException
+
+
+class TestRun(unittest.TestCase):
+ """Tests for timeout_retry.Run."""
+
+ def testRun(self):
+ self.assertTrue(timeout_retry.Run(
+ lambda x: x, 30, 3, [True], {}))
+
+ def testTimeout(self):
+ tries = [0]
+ self.assertRaises(reraiser_thread.TimeoutError,
+ timeout_retry.Run, lambda: _NeverEnding(tries), 0, 3)
+ self.assertEqual(tries[0], 4)
+
+ def testRetries(self):
+ tries = [0]
+ self.assertRaises(TestException,
+ timeout_retry.Run, lambda: _CountTries(tries), 30, 3)
+ self.assertEqual(tries[0], 4)
+
+ def testReturnValue(self):
+ self.assertTrue(timeout_retry.Run(lambda: True, 30, 3))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/utils/watchdog_timer.py b/build/android/pylib/utils/watchdog_timer.py
new file mode 100644
index 00000000000..2f4c46455be
--- /dev/null
+++ b/build/android/pylib/utils/watchdog_timer.py
@@ -0,0 +1,47 @@
+# Copyright 2013 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.
+
+"""WatchdogTimer timeout objects."""
+
+import time
+
+
+class WatchdogTimer(object):
+ """A resetable timeout-based watchdog.
+
+ This object is threadsafe.
+ """
+
+ def __init__(self, timeout):
+ """Initializes the watchdog.
+
+ Args:
+ timeout: The timeout in seconds. If timeout is None it will never timeout.
+ """
+ self._start_time = time.time()
+ self._timeout = timeout
+
+ def Reset(self):
+ """Resets the timeout countdown."""
+ self._start_time = time.time()
+
+ def GetElapsed(self):
+ """Returns the elapsed time of the watchdog."""
+ return time.time() - self._start_time
+
+ def GetRemaining(self):
+ """Returns the remaining time of the watchdog."""
+ if self._timeout:
+ return self._timeout - self.GetElapsed()
+ else:
+ return None
+
+ def IsTimedOut(self):
+ """Whether the watchdog has timed out.
+
+ Returns:
+ True if the watchdog has timed out, False otherwise.
+ """
+ remaining = self.GetRemaining()
+ return remaining is not None and remaining < 0
diff --git a/build/android/pylib/utils/xvfb.py b/build/android/pylib/utils/xvfb.py
new file mode 100644
index 00000000000..cb9d50e8fd9
--- /dev/null
+++ b/build/android/pylib/utils/xvfb.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2013 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.
+
+# pylint: disable=W0702
+
+import os
+import signal
+import subprocess
+import sys
+import time
+
+
+def _IsLinux():
+ """Return True if on Linux; else False."""
+ return sys.platform.startswith('linux')
+
+
+class Xvfb(object):
+ """Class to start and stop Xvfb if relevant. Nop if not Linux."""
+
+ def __init__(self):
+ self._pid = 0
+
+ def Start(self):
+ """Start Xvfb and set an appropriate DISPLAY environment. Linux only.
+
+ Copied from tools/code_coverage/coverage_posix.py
+ """
+ if not _IsLinux():
+ return
+ proc = subprocess.Popen(['Xvfb', ':9', '-screen', '0', '1024x768x24',
+ '-ac'],
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ self._pid = proc.pid
+ if not self._pid:
+ raise Exception('Could not start Xvfb')
+ os.environ['DISPLAY'] = ':9'
+
+ # Now confirm, giving a chance for it to start if needed.
+ for _ in range(10):
+ proc = subprocess.Popen('xdpyinfo >/dev/null', shell=True)
+ _, retcode = os.waitpid(proc.pid, 0)
+ if retcode == 0:
+ break
+ time.sleep(0.25)
+ if retcode != 0:
+ raise Exception('Could not confirm Xvfb happiness')
+
+ def Stop(self):
+ """Stop Xvfb if needed. Linux only."""
+ if self._pid:
+ try:
+ os.kill(self._pid, signal.SIGKILL)
+ except:
+ pass
+ del os.environ['DISPLAY']
+ self._pid = 0
diff --git a/build/android/pylib/utils/zip_utils.py b/build/android/pylib/utils/zip_utils.py
new file mode 100644
index 00000000000..d799463f904
--- /dev/null
+++ b/build/android/pylib/utils/zip_utils.py
@@ -0,0 +1,31 @@
+# Copyright 2015 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.
+
+import logging
+import os
+import zipfile
+
+
+def WriteToZipFile(zip_file, path, arc_path):
+ """Recursively write |path| to |zip_file| as |arc_path|.
+
+ zip_file: An open instance of zipfile.ZipFile.
+ path: An absolute path to the file or directory to be zipped.
+ arc_path: A relative path within the zip file to which the file or directory
+ located at |path| should be written.
+ """
+ if os.path.isdir(path):
+ for dir_path, _, file_names in os.walk(path):
+ dir_arc_path = os.path.join(arc_path, os.path.relpath(dir_path, path))
+ logging.debug('dir: %s -> %s', dir_path, dir_arc_path)
+ zip_file.write(dir_path, dir_arc_path, zipfile.ZIP_STORED)
+ for f in file_names:
+ file_path = os.path.join(dir_path, f)
+ file_arc_path = os.path.join(dir_arc_path, f)
+ logging.debug('file: %s -> %s', file_path, file_arc_path)
+ zip_file.write(file_path, file_arc_path, zipfile.ZIP_DEFLATED)
+ else:
+ logging.debug('file: %s -> %s', path, arc_path)
+ zip_file.write(path, arc_path, zipfile.ZIP_DEFLATED)
+
diff --git a/build/android/pylib/valgrind_tools.py b/build/android/pylib/valgrind_tools.py
new file mode 100644
index 00000000000..99719d08114
--- /dev/null
+++ b/build/android/pylib/valgrind_tools.py
@@ -0,0 +1,304 @@
+# Copyright (c) 2012 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.
+
+"""
+Classes in this file define additional actions that need to be taken to run a
+test under some kind of runtime error detection tool.
+
+The interface is intended to be used as follows.
+
+1. For tests that simply run a native process (i.e. no activity is spawned):
+
+Call tool.CopyFiles(device).
+Prepend test command line with tool.GetTestWrapper().
+
+2. For tests that spawn an activity:
+
+Call tool.CopyFiles(device).
+Call tool.SetupEnvironment().
+Run the test as usual.
+Call tool.CleanUpEnvironment().
+"""
+# pylint: disable=R0201
+
+import glob
+import logging
+import os.path
+import subprocess
+import sys
+
+from pylib.constants import DIR_SOURCE_ROOT
+from pylib.device import device_errors
+
+
+def SetChromeTimeoutScale(device, scale):
+ """Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
+ path = '/data/local/tmp/chrome_timeout_scale'
+ if not scale or scale == 1.0:
+ # Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
+ device.RunShellCommand('rm %s' % path)
+ else:
+ device.WriteFile(path, '%f' % scale, as_root=True)
+
+
+class BaseTool(object):
+ """A tool that does nothing."""
+
+ def __init__(self):
+ """Does nothing."""
+ pass
+
+ def GetTestWrapper(self):
+ """Returns a string that is to be prepended to the test command line."""
+ return ''
+
+ def GetUtilWrapper(self):
+ """Returns the wrapper name for the utilities.
+
+ Returns:
+ A string that is to be prepended to the command line of utility
+ processes (forwarder, etc.).
+ """
+ return ''
+
+ @classmethod
+ def CopyFiles(cls, device):
+ """Copies tool-specific files to the device, create directories, etc."""
+ pass
+
+ def SetupEnvironment(self):
+ """Sets up the system environment for a test.
+
+ This is a good place to set system properties.
+ """
+ pass
+
+ def CleanUpEnvironment(self):
+ """Cleans up environment."""
+ pass
+
+ def GetTimeoutScale(self):
+ """Returns a multiplier that should be applied to timeout values."""
+ return 1.0
+
+ def NeedsDebugInfo(self):
+ """Whether this tool requires debug info.
+
+ Returns:
+ True if this tool can not work with stripped binaries.
+ """
+ return False
+
+
+class AddressSanitizerTool(BaseTool):
+ """AddressSanitizer tool."""
+
+ WRAPPER_NAME = '/system/bin/asanwrapper'
+ # Disable memcmp overlap check.There are blobs (gl drivers)
+ # on some android devices that use memcmp on overlapping regions,
+ # nothing we can do about that.
+ EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
+
+ def __init__(self, device):
+ super(AddressSanitizerTool, self).__init__()
+ self._device = device
+ # Configure AndroidCommands to run utils (such as md5sum_bin) under ASan.
+ # This is required because ASan is a compiler-based tool, and md5sum
+ # includes instrumented code from base.
+ device.old_interface.SetUtilWrapper(self.GetUtilWrapper())
+
+ @classmethod
+ def CopyFiles(cls, device):
+ """Copies ASan tools to the device."""
+ libs = glob.glob(os.path.join(DIR_SOURCE_ROOT,
+ 'third_party/llvm-build/Release+Asserts/',
+ 'lib/clang/*/lib/linux/',
+ 'libclang_rt.asan-arm-android.so'))
+ assert len(libs) == 1
+ subprocess.call(
+ [os.path.join(
+ DIR_SOURCE_ROOT,
+ 'tools/android/asan/third_party/asan_device_setup.sh'),
+ '--device', str(device),
+ '--lib', libs[0],
+ '--extra-options', AddressSanitizerTool.EXTRA_OPTIONS])
+ device.WaitUntilFullyBooted()
+
+ def GetTestWrapper(self):
+ return AddressSanitizerTool.WRAPPER_NAME
+
+ def GetUtilWrapper(self):
+ """Returns the wrapper for utilities, such as forwarder.
+
+ AddressSanitizer wrapper must be added to all instrumented binaries,
+ including forwarder and the like. This can be removed if such binaries
+ were built without instrumentation. """
+ return self.GetTestWrapper()
+
+ def SetupEnvironment(self):
+ try:
+ self._device.EnableRoot()
+ except device_errors.CommandFailedError as e:
+ # Try to set the timeout scale anyway.
+ # TODO(jbudorick) Handle this exception appropriately after interface
+ # conversions are finished.
+ logging.error(str(e))
+ SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
+
+ def CleanUpEnvironment(self):
+ SetChromeTimeoutScale(self._device, None)
+
+ def GetTimeoutScale(self):
+ # Very slow startup.
+ return 20.0
+
+
+class ValgrindTool(BaseTool):
+ """Base abstract class for Valgrind tools."""
+
+ VG_DIR = '/data/local/tmp/valgrind'
+ VGLOGS_DIR = '/data/local/tmp/vglogs'
+
+ def __init__(self, device):
+ super(ValgrindTool, self).__init__()
+ self._device = device
+ # exactly 31 chars, SystemProperties::PROP_NAME_MAX
+ self._wrap_properties = ['wrap.com.google.android.apps.ch',
+ 'wrap.org.chromium.native_test']
+
+ @classmethod
+ def CopyFiles(cls, device):
+ """Copies Valgrind tools to the device."""
+ device.RunShellCommand(
+ 'rm -r %s; mkdir %s' % (ValgrindTool.VG_DIR, ValgrindTool.VG_DIR))
+ device.RunShellCommand(
+ 'rm -r %s; mkdir %s' % (ValgrindTool.VGLOGS_DIR,
+ ValgrindTool.VGLOGS_DIR))
+ files = cls.GetFilesForTool()
+ device.PushChangedFiles(
+ [((os.path.join(DIR_SOURCE_ROOT, f),
+ os.path.join(ValgrindTool.VG_DIR, os.path.basename(f)))
+ for f in files)])
+
+ def SetupEnvironment(self):
+ """Sets up device environment."""
+ self._device.RunShellCommand('chmod 777 /data/local/tmp')
+ self._device.RunShellCommand('setenforce 0')
+ for prop in self._wrap_properties:
+ self._device.RunShellCommand(
+ 'setprop %s "logwrapper %s"' % (prop, self.GetTestWrapper()))
+ SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
+
+ def CleanUpEnvironment(self):
+ """Cleans up device environment."""
+ for prop in self._wrap_properties:
+ self._device.RunShellCommand('setprop %s ""' % (prop,))
+ SetChromeTimeoutScale(self._device, None)
+
+ @staticmethod
+ def GetFilesForTool():
+ """Returns a list of file names for the tool."""
+ raise NotImplementedError()
+
+ def NeedsDebugInfo(self):
+ """Whether this tool requires debug info.
+
+ Returns:
+ True if this tool can not work with stripped binaries.
+ """
+ return True
+
+
+class MemcheckTool(ValgrindTool):
+ """Memcheck tool."""
+
+ def __init__(self, device):
+ super(MemcheckTool, self).__init__(device)
+
+ @staticmethod
+ def GetFilesForTool():
+ """Returns a list of file names for the tool."""
+ return ['tools/valgrind/android/vg-chrome-wrapper.sh',
+ 'tools/valgrind/memcheck/suppressions.txt',
+ 'tools/valgrind/memcheck/suppressions_android.txt']
+
+ def GetTestWrapper(self):
+ """Returns a string that is to be prepended to the test command line."""
+ return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper.sh'
+
+ def GetTimeoutScale(self):
+ """Returns a multiplier that should be applied to timeout values."""
+ return 30
+
+
+class TSanTool(ValgrindTool):
+ """ThreadSanitizer tool. See http://code.google.com/p/data-race-test ."""
+
+ def __init__(self, device):
+ super(TSanTool, self).__init__(device)
+
+ @staticmethod
+ def GetFilesForTool():
+ """Returns a list of file names for the tool."""
+ return ['tools/valgrind/android/vg-chrome-wrapper-tsan.sh',
+ 'tools/valgrind/tsan/suppressions.txt',
+ 'tools/valgrind/tsan/suppressions_android.txt',
+ 'tools/valgrind/tsan/ignores.txt']
+
+ def GetTestWrapper(self):
+ """Returns a string that is to be prepended to the test command line."""
+ return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper-tsan.sh'
+
+ def GetTimeoutScale(self):
+ """Returns a multiplier that should be applied to timeout values."""
+ return 30.0
+
+
+TOOL_REGISTRY = {
+ 'memcheck': MemcheckTool,
+ 'memcheck-renderer': MemcheckTool,
+ 'tsan': TSanTool,
+ 'tsan-renderer': TSanTool,
+ 'asan': AddressSanitizerTool,
+}
+
+
+def CreateTool(tool_name, device):
+ """Creates a tool with the specified tool name.
+
+ Args:
+ tool_name: Name of the tool to create.
+ device: A DeviceUtils instance.
+ Returns:
+ A tool for the specified tool_name.
+ """
+ if not tool_name:
+ return BaseTool()
+
+ ctor = TOOL_REGISTRY.get(tool_name)
+ if ctor:
+ return ctor(device)
+ else:
+ print 'Unknown tool %s, available tools: %s' % (
+ tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
+ sys.exit(1)
+
+def PushFilesForTool(tool_name, device):
+ """Pushes the files required for |tool_name| to |device|.
+
+ Args:
+ tool_name: Name of the tool to create.
+ device: A DeviceUtils instance.
+ """
+ if not tool_name:
+ return
+
+ clazz = TOOL_REGISTRY.get(tool_name)
+ if clazz:
+ clazz.CopyFiles(device)
+ else:
+ print 'Unknown tool %s, available tools: %s' % (
+ tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
+ sys.exit(1)
+
diff --git a/build/android/rezip.gyp b/build/android/rezip.gyp
new file mode 100644
index 00000000000..111517763ab
--- /dev/null
+++ b/build/android/rezip.gyp
@@ -0,0 +1,45 @@
+# Copyright 2014 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.
+
+# Build the rezip build tool.
+{
+ 'targets': [
+ {
+ # GN: //build/android/rezip:rezip
+ 'target_name': 'rezip_apk_jar',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': 'rezip',
+ 'compile_stamp': '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/compile.stamp',
+ 'javac_jar_path': '<(PRODUCT_DIR)/lib.java/rezip_apk.jar',
+ },
+ 'actions': [
+ {
+ 'action_name': 'javac_<(_target_name)',
+ 'message': 'Compiling <(_target_name) java sources',
+ 'variables': {
+ 'java_sources': ['>!@(find >(java_in_dir) -name "*.java")'],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/javac.py',
+ '>@(java_sources)',
+ ],
+ 'outputs': [
+ '<(compile_stamp)',
+ '<(javac_jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/javac.py',
+ '--classpath=',
+ '--classes-dir=<(SHARED_INTERMEDIATE_DIR)/<(_target_name)',
+ '--jar-path=<(javac_jar_path)',
+ '--stamp=<(compile_stamp)',
+ '>@(java_sources)',
+ ]
+ },
+ ],
+ }
+ ],
+}
diff --git a/build/android/rezip/BUILD.gn b/build/android/rezip/BUILD.gn
new file mode 100644
index 00000000000..8b8f78e8751
--- /dev/null
+++ b/build/android/rezip/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2014 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //build/android/rezip.gyp:rezip_apk_jar
+java_library("rezip") {
+ jar_path = "$root_build_dir/lib.java/rezip_apk.jar"
+ DEPRECATED_java_in_dir = "."
+}
diff --git a/build/android/rezip/RezipApk.java b/build/android/rezip/RezipApk.java
new file mode 100644
index 00000000000..43d75447c8f
--- /dev/null
+++ b/build/android/rezip/RezipApk.java
@@ -0,0 +1,448 @@
+// Copyright 2014 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.
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+
+/**
+ * Command line tool used to build APKs which support loading the native code library
+ * directly from the APK file. To construct the APK we rename the native library by
+ * adding the prefix "crazy." to the filename. This is done to prevent the Android
+ * Package Manager from extracting the library. The native code must be page aligned
+ * and uncompressed. The page alignment is implemented by adding a zero filled file
+ * in front of the the native code library. This tool is designed so that running
+ * SignApk and/or zipalign on the resulting APK does not break the page alignment.
+ * This is achieved by outputing the filenames in the same canonical order used
+ * by SignApk and adding the same alignment fields added by zipalign.
+ */
+class RezipApk {
+ // Alignment to use for non-compressed files (must match zipalign).
+ private static final int ALIGNMENT = 4;
+
+ // Alignment to use for non-compressed *.so files
+ private static final int LIBRARY_ALIGNMENT = 4096;
+
+ // Files matching this pattern are not copied to the output when adding alignment.
+ // When reordering and verifying the APK they are copied to the end of the file.
+ private static Pattern sMetaFilePattern =
+ Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|("
+ + Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
+
+ // Pattern for matching a shared library in the APK
+ private static Pattern sLibraryPattern = Pattern.compile("^lib/[^/]*/lib.*[.]so$");
+ // Pattern for match the crazy linker in the APK
+ private static Pattern sCrazyLinkerPattern =
+ Pattern.compile("^lib/[^/]*/libchromium_android_linker.so$");
+ // Pattern for matching a crazy loaded shared library in the APK
+ private static Pattern sCrazyLibraryPattern = Pattern.compile("^lib/[^/]*/crazy.lib.*[.]so$");
+
+ private static boolean isLibraryFilename(String filename) {
+ return sLibraryPattern.matcher(filename).matches()
+ && !sCrazyLinkerPattern.matcher(filename).matches();
+ }
+
+ private static boolean isCrazyLibraryFilename(String filename) {
+ return sCrazyLibraryPattern.matcher(filename).matches();
+ }
+
+ private static String renameLibraryForCrazyLinker(String filename) {
+ int lastSlash = filename.lastIndexOf('/');
+ // We rename the library, so that the Android Package Manager
+ // no longer extracts the library.
+ return filename.substring(0, lastSlash + 1) + "crazy." + filename.substring(lastSlash + 1);
+ }
+
+ /**
+ * Wraps another output stream, counting the number of bytes written.
+ */
+ private static class CountingOutputStream extends OutputStream {
+ private long mCount = 0;
+ private OutputStream mOut;
+
+ public CountingOutputStream(OutputStream out) {
+ this.mOut = out;
+ }
+
+ /** Returns the number of bytes written. */
+ public long getCount() {
+ return mCount;
+ }
+
+ @Override public void write(byte[] b, int off, int len) throws IOException {
+ mOut.write(b, off, len);
+ mCount += len;
+ }
+
+ @Override public void write(int b) throws IOException {
+ mOut.write(b);
+ mCount++;
+ }
+
+ @Override public void close() throws IOException {
+ mOut.close();
+ }
+
+ @Override public void flush() throws IOException {
+ mOut.flush();
+ }
+ }
+
+ private static String outputName(JarEntry entry, boolean rename) {
+ String inName = entry.getName();
+ if (rename && entry.getSize() > 0 && isLibraryFilename(inName)) {
+ return renameLibraryForCrazyLinker(inName);
+ }
+ return inName;
+ }
+
+ /**
+ * Comparator used to sort jar entries from the input file.
+ * Sorting is done based on the output filename (which maybe renamed).
+ * Filenames are in natural string order, except that filenames matching
+ * the meta-file pattern are always after other files. This is so the manifest
+ * and signature are at the end of the file after any alignment file.
+ */
+ private static class EntryComparator implements Comparator {
+ private boolean mRename;
+
+ public EntryComparator(boolean rename) {
+ mRename = rename;
+ }
+
+ @Override
+ public int compare(JarEntry j1, JarEntry j2) {
+ String o1 = outputName(j1, mRename);
+ String o2 = outputName(j2, mRename);
+ boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
+ boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
+ if (o1Matches != o2Matches) {
+ return o1Matches ? 1 : -1;
+ } else {
+ return o1.compareTo(o2);
+ }
+ }
+ }
+
+ // Build an ordered list of jar entries. The jar entries from the input are
+ // sorted based on the output filenames (which maybe renamed). If |omitMetaFiles|
+ // is true do not include the jar entries for the META-INF files.
+ // Entries are ordered in the deterministic order used by SignApk.
+ private static List getOutputFileOrderEntries(
+ JarFile jar, boolean omitMetaFiles, boolean rename) {
+ List entries = new ArrayList();
+ for (Enumeration e = jar.entries(); e.hasMoreElements(); ) {
+ JarEntry entry = e.nextElement();
+ if (entry.isDirectory()) {
+ continue;
+ }
+ if (omitMetaFiles && sMetaFilePattern.matcher(entry.getName()).matches()) {
+ continue;
+ }
+ entries.add(entry);
+ }
+
+ // We sort the input entries by name. When present META-INF files
+ // are sorted to the end.
+ Collections.sort(entries, new EntryComparator(rename));
+ return entries;
+ }
+
+ /**
+ * Add a zero filled alignment file at this point in the zip file,
+ * The added file will be added before |name| and after |prevName|.
+ * The size of the alignment file is such that the location of the
+ * file |name| will be on a LIBRARY_ALIGNMENT boundary.
+ *
+ * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
+ * file will not alter the alignment.
+ *
+ * @param offset number of bytes into the output file at this point.
+ * @param timestamp time in millis since the epoch to include in the header.
+ * @param name the name of the library filename.
+ * @param prevName the name of the previous file in the archive (or null).
+ * @param out jar output stream to write the alignment file to.
+ *
+ * @throws IOException if the output file can not be written.
+ */
+ private static void addAlignmentFile(
+ long offset, long timestamp, String name, String prevName,
+ JarOutputStream out) throws IOException {
+
+ // Compute the start and alignment of the library, as if it was next.
+ int headerSize = JarFile.LOCHDR + name.length();
+ long libOffset = offset + headerSize;
+ int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT);
+ if (libNeeded == LIBRARY_ALIGNMENT) {
+ // Already aligned, no need to added alignment file.
+ return;
+ }
+
+ // Check that there is not another file between the library and the
+ // alignment file.
+ String alignName = name.substring(0, name.length() - 2) + "align";
+ if (prevName != null && prevName.compareTo(alignName) >= 0) {
+ throw new UnsupportedOperationException(
+ "Unable to insert alignment file, because there is "
+ + "another file in front of the file to be aligned. "
+ + "Other file: " + prevName + " Alignment file: " + alignName
+ + " file: " + name);
+ }
+
+ // Compute the size of the alignment file header.
+ headerSize = JarFile.LOCHDR + alignName.length();
+ // We are going to add an alignment file of type STORED. This file
+ // will itself induce a zipalign alignment adjustment.
+ int extraNeeded =
+ (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNMENT;
+ headerSize += extraNeeded;
+
+ if (libNeeded < headerSize + 1) {
+ // The header was bigger than the alignment that we need, add another page.
+ libNeeded += LIBRARY_ALIGNMENT;
+ }
+ // Compute the size of the alignment file.
+ libNeeded -= headerSize;
+
+ // Build the header for the alignment file.
+ byte[] zeroBuffer = new byte[libNeeded];
+ JarEntry alignEntry = new JarEntry(alignName);
+ alignEntry.setMethod(JarEntry.STORED);
+ alignEntry.setSize(libNeeded);
+ alignEntry.setTime(timestamp);
+ CRC32 crc = new CRC32();
+ crc.update(zeroBuffer);
+ alignEntry.setCrc(crc.getValue());
+
+ if (extraNeeded != 0) {
+ alignEntry.setExtra(new byte[extraNeeded]);
+ }
+
+ // Output the alignment file.
+ out.putNextEntry(alignEntry);
+ out.write(zeroBuffer);
+ out.closeEntry();
+ out.flush();
+ }
+
+ // Make a JarEntry for the output file which corresponds to the input
+ // file. The output file will be called |name|. The output file will always
+ // be uncompressed (STORED). If the input is not STORED it is necessary to inflate
+ // it to compute the CRC and size of the output entry.
+ private static JarEntry makeStoredEntry(String name, JarEntry inEntry, JarFile in)
+ throws IOException {
+ JarEntry outEntry = new JarEntry(name);
+ outEntry.setMethod(JarEntry.STORED);
+
+ if (inEntry.getMethod() == JarEntry.STORED) {
+ outEntry.setCrc(inEntry.getCrc());
+ outEntry.setSize(inEntry.getSize());
+ } else {
+ // We are inflating the file. We need to compute the CRC and size.
+ byte[] buffer = new byte[4096];
+ CRC32 crc = new CRC32();
+ int size = 0;
+ int num;
+ InputStream data = in.getInputStream(inEntry);
+ while ((num = data.read(buffer)) > 0) {
+ crc.update(buffer, 0, num);
+ size += num;
+ }
+ data.close();
+ outEntry.setCrc(crc.getValue());
+ outEntry.setSize(size);
+ }
+ return outEntry;
+ }
+
+ /**
+ * Copy the contents of the input APK file to the output APK file. If |rename| is
+ * true then non-empty libraries (*.so) in the input will be renamed by prefixing
+ * "crazy.". This is done to prevent the Android Package Manager extracting the
+ * library. Note the crazy linker itself is not renamed, for bootstrapping reasons.
+ * Empty libraries are not renamed (they are in the APK to workaround a bug where
+ * the Android Package Manager fails to delete old versions when upgrading).
+ * There must be exactly one "crazy" library in the output stream. The "crazy"
+ * library will be uncompressed and page aligned in the output stream. Page
+ * alignment is implemented by adding a zero filled file, regular alignment is
+ * implemented by adding a zero filled extra field to the zip file header. If
+ * |addAlignment| is true a page alignment file is added, otherwise the "crazy"
+ * library must already be page aligned. Care is taken so that the output is generated
+ * in the same way as SignApk. This is important so that running SignApk and
+ * zipalign on the output does not break the page alignment. The archive may not
+ * contain a "*.apk" as SignApk has special nested signing logic that we do not
+ * support.
+ *
+ * @param in The input APK File.
+ * @param out The output APK stream.
+ * @param countOut Counting output stream (to measure the current offset).
+ * @param addAlignment Whether to add the alignment file or just check.
+ * @param rename Whether to rename libraries to be "crazy".
+ *
+ * @throws IOException if the output file can not be written.
+ */
+ private static void rezip(
+ JarFile in, JarOutputStream out, CountingOutputStream countOut,
+ boolean addAlignment, boolean rename) throws IOException {
+
+ List entries = getOutputFileOrderEntries(in, addAlignment, rename);
+ long timestamp = System.currentTimeMillis();
+ byte[] buffer = new byte[4096];
+ boolean firstEntry = true;
+ String prevName = null;
+ int numCrazy = 0;
+ for (JarEntry inEntry : entries) {
+ // Rename files, if specied.
+ String name = outputName(inEntry, rename);
+ if (name.endsWith(".apk")) {
+ throw new UnsupportedOperationException(
+ "Nested APKs are not supported: " + name);
+ }
+
+ // Build the header.
+ JarEntry outEntry = null;
+ boolean isCrazy = isCrazyLibraryFilename(name);
+ if (isCrazy) {
+ // "crazy" libraries are alway output uncompressed (STORED).
+ outEntry = makeStoredEntry(name, inEntry, in);
+ numCrazy++;
+ if (numCrazy > 1) {
+ throw new UnsupportedOperationException(
+ "Found more than one library\n"
+ + "Multiple libraries are not supported for APKs that use "
+ + "'load_library_from_zip'.\n"
+ + "See crbug/388223.\n"
+ + "Note, check that your build is clean.\n"
+ + "An unclean build can incorrectly incorporate old "
+ + "libraries in the APK.");
+ }
+ } else if (inEntry.getMethod() == JarEntry.STORED) {
+ // Preserve the STORED method of the input entry.
+ outEntry = new JarEntry(inEntry);
+ outEntry.setExtra(null);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ outEntry = new JarEntry(name);
+ }
+ outEntry.setTime(timestamp);
+
+ // Compute and add alignment
+ long offset = countOut.getCount();
+ if (firstEntry) {
+ // The first entry in a jar file has an extra field of
+ // four bytes that you can't get rid of; any extra
+ // data you specify in the JarEntry is appended to
+ // these forced four bytes. This is JAR_MAGIC in
+ // JarOutputStream; the bytes are 0xfeca0000.
+ firstEntry = false;
+ offset += 4;
+ }
+ if (outEntry.getMethod() == JarEntry.STORED) {
+ if (isCrazy) {
+ if (addAlignment) {
+ addAlignmentFile(offset, timestamp, name, prevName, out);
+ }
+ // We check that we did indeed get to a page boundary.
+ offset = countOut.getCount() + JarFile.LOCHDR + name.length();
+ if ((offset % LIBRARY_ALIGNMENT) != 0) {
+ throw new AssertionError(
+ "Library was not page aligned when verifying page alignment. "
+ + "Library name: " + name + " Expected alignment: "
+ + LIBRARY_ALIGNMENT + "Offset: " + offset + " Error: "
+ + (offset % LIBRARY_ALIGNMENT));
+ }
+ } else {
+ // This is equivalent to zipalign.
+ offset += JarFile.LOCHDR + name.length();
+ int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIGNMENT;
+ if (needed != 0) {
+ outEntry.setExtra(new byte[needed]);
+ }
+ }
+ }
+ out.putNextEntry(outEntry);
+
+ // Copy the data from the input to the output
+ int num;
+ InputStream data = in.getInputStream(inEntry);
+ while ((num = data.read(buffer)) > 0) {
+ out.write(buffer, 0, num);
+ }
+ data.close();
+ out.closeEntry();
+ out.flush();
+ prevName = name;
+ }
+ if (numCrazy == 0) {
+ throw new AssertionError("There was no crazy library in the archive");
+ }
+ }
+
+ private static void usage() {
+ System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
+ System.err.println("\"crazy\" libraries are always inflated in the output");
+ System.err.println(
+ " renamealign - rename libraries with \"crazy.\" prefix and add alignment file");
+ System.err.println(" align - add alignment file");
+ System.err.println(" reorder - re-creates canonical ordering and checks alignment");
+ System.exit(2);
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 3) usage();
+
+ boolean addAlignment = false;
+ boolean rename = false;
+ if (args[0].equals("renamealign")) {
+ // Normal case. Before signing we rename the library and add an alignment file.
+ addAlignment = true;
+ rename = true;
+ } else if (args[0].equals("align")) {
+ // LGPL compliance case. Before signing, we add an alignment file to a
+ // reconstructed APK which already contains the "crazy" library.
+ addAlignment = true;
+ rename = false;
+ } else if (args[0].equals("reorder")) {
+ // Normal case. After jarsigning we write the file in the canonical order and check.
+ addAlignment = false;
+ } else {
+ usage();
+ }
+
+ String inputFilename = args[1];
+ String outputFilename = args[2];
+
+ JarFile inputJar = null;
+ FileOutputStream outputFile = null;
+
+ try {
+ inputJar = new JarFile(new File(inputFilename), true);
+ outputFile = new FileOutputStream(outputFilename);
+
+ CountingOutputStream outCount = new CountingOutputStream(outputFile);
+ JarOutputStream outputJar = new JarOutputStream(outCount);
+
+ // Match the compression level used by SignApk.
+ outputJar.setLevel(9);
+
+ rezip(inputJar, outputJar, outCount, addAlignment, rename);
+ outputJar.close();
+ } finally {
+ if (inputJar != null) inputJar.close();
+ if (outputFile != null) outputFile.close();
+ }
+ }
+}
diff --git a/build/android/screenshot.py b/build/android/screenshot.py
new file mode 100755
index 00000000000..097739fb62d
--- /dev/null
+++ b/build/android/screenshot.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012 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.
+
+"""Takes a screenshot or a screen video capture from an Android device."""
+
+import logging
+import optparse
+import os
+import sys
+
+from pylib import screenshot
+from pylib.device import device_errors
+from pylib.device import device_utils
+
+def _PrintMessage(heading, eol='\n'):
+ sys.stdout.write('%s%s' % (heading, eol))
+ sys.stdout.flush()
+
+
+def _CaptureScreenshot(device, host_file):
+ host_file = device.TakeScreenshot(host_file)
+ _PrintMessage('Screenshot written to %s' % os.path.abspath(host_file))
+
+
+def _CaptureVideo(device, host_file, options):
+ size = tuple(map(int, options.size.split('x'))) if options.size else None
+ recorder = screenshot.VideoRecorder(device,
+ megabits_per_second=options.bitrate,
+ size=size,
+ rotate=options.rotate)
+ try:
+ recorder.Start()
+ _PrintMessage('Recording. Press Enter to stop...', eol='')
+ raw_input()
+ finally:
+ recorder.Stop()
+ host_file = recorder.Pull(host_file)
+ _PrintMessage('Video written to %s' % os.path.abspath(host_file))
+
+
+def main():
+ # Parse options.
+ parser = optparse.OptionParser(description=__doc__,
+ usage='screenshot.py [options] [filename]')
+ parser.add_option('-d', '--device', metavar='ANDROID_DEVICE', help='Serial '
+ 'number of Android device to use.', default=None)
+ parser.add_option('-f', '--file', help='Save result to file instead of '
+ 'generating a timestamped file name.', metavar='FILE')
+ parser.add_option('-v', '--verbose', help='Verbose logging.',
+ action='store_true')
+ video_options = optparse.OptionGroup(parser, 'Video capture')
+ video_options.add_option('--video', help='Enable video capturing. Requires '
+ 'Android KitKat or later', action='store_true')
+ video_options.add_option('-b', '--bitrate', help='Bitrate in megabits/s, '
+ 'from 0.1 to 100 mbps, %default mbps by default.',
+ default=4, type='float')
+ video_options.add_option('-r', '--rotate', help='Rotate video by 90 degrees.',
+ default=False, action='store_true')
+ video_options.add_option('-s', '--size', metavar='WIDTHxHEIGHT',
+ help='Frame size to use instead of the device '
+ 'screen size.', default=None)
+ parser.add_option_group(video_options)
+
+ (options, args) = parser.parse_args()
+
+ if len(args) > 1:
+ parser.error('Too many positional arguments.')
+ host_file = args[0] if args else options.file
+
+ if options.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if options.device:
+ device = next((d for d in devices if d == options.device), None)
+ if not device:
+ raise device_errors.DeviceUnreachableError(options.device)
+ else:
+ if len(devices) > 1:
+ parser.error('Multiple devices are attached. '
+ 'Please specify device serial number with --device.')
+ elif len(devices) == 1:
+ device = devices[0]
+ else:
+ raise device_errors.NoDevicesError()
+
+ if options.video:
+ _CaptureVideo(device, host_file, options)
+ else:
+ _CaptureScreenshot(device, host_file)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/setup.gyp b/build/android/setup.gyp
new file mode 100644
index 00000000000..0e1c2c4b492
--- /dev/null
+++ b/build/android/setup.gyp
@@ -0,0 +1,111 @@
+# Copyright (c) 2012 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.
+{
+ 'conditions': [
+ ['component == "shared_library"', {
+ 'targets': [
+ {
+ # These libraries from the Android ndk are required to be packaged with
+ # any APK that is built with them. build/java_apk.gypi expects any
+ # libraries that should be packaged with the apk to be in
+ # <(SHARED_LIB_DIR)
+ 'target_name': 'copy_system_libraries',
+ 'type': 'none',
+ 'copies': [
+ {
+ 'destination': '<(SHARED_LIB_DIR)/',
+ 'files': [
+ '<(android_libcpp_libs_dir)/libc++_shared.so',
+ ],
+ },
+ ],
+ },
+ ],
+ }],
+ ],
+ 'targets': [
+ {
+ 'target_name': 'get_build_device_configurations',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'get configurations',
+ 'inputs': [
+ 'gyp/util/build_device.py',
+ 'gyp/get_device_configuration.py',
+ ],
+ 'outputs': [
+ '<(build_device_config_path)',
+ '<(build_device_config_path).fake',
+ ],
+ 'action': [
+ 'python', 'gyp/get_device_configuration.py',
+ '--output=<(build_device_config_path)',
+ ],
+ }
+ ],
+ },
+ {
+ # Target for creating common output build directories. Creating output
+ # dirs beforehand ensures that build scripts can assume these folders to
+ # exist and there are no race conditions resulting from build scripts
+ # trying to create these directories.
+ # The build/java.gypi target depends on this target.
+ 'target_name': 'build_output_dirs',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'create_java_output_dirs',
+ 'variables' : {
+ 'output_dirs' : [
+ '<(PRODUCT_DIR)/apks',
+ '<(PRODUCT_DIR)/lib.java',
+ '<(PRODUCT_DIR)/test.lib.java',
+ ]
+ },
+ 'inputs' : [],
+ # By not specifying any outputs, we ensure that this command isn't
+ # re-run when the output directories are touched (i.e. apks are
+ # written to them).
+ 'outputs': [''],
+ 'action': [
+ 'mkdir',
+ '-p',
+ '<@(output_dirs)',
+ ],
+ },
+ ],
+ }, # build_output_dirs
+ {
+ 'target_name': 'sun_tools_java',
+ 'type': 'none',
+ 'variables': {
+ 'found_jar_path': '<(PRODUCT_DIR)/sun_tools_java/tools.jar',
+ 'jar_path': '<(found_jar_path)',
+ },
+ 'includes': [
+ '../../build/host_prebuilt_jar.gypi',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'find_sun_tools_jar',
+ 'variables' : {
+ },
+ 'inputs' : [
+ 'gyp/find_sun_tools_jar.py',
+ 'gyp/util/build_utils.py',
+ ],
+ 'outputs': [
+ '<(found_jar_path)',
+ ],
+ 'action': [
+ 'python', 'gyp/find_sun_tools_jar.py',
+ '--output', '<(found_jar_path)',
+ ],
+ },
+ ],
+ }, # sun_tools_java
+ ]
+}
+
diff --git a/build/android/strip_native_libraries.gypi b/build/android/strip_native_libraries.gypi
new file mode 100644
index 00000000000..bdffcfd0494
--- /dev/null
+++ b/build/android/strip_native_libraries.gypi
@@ -0,0 +1,54 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule that strips
+# native libraries.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'action_name': 'strip_native_libraries',
+# 'actions': [
+# 'variables': {
+# 'ordered_libraries_file': 'file generated by write_ordered_libraries'
+# 'input_paths': 'files to be added to the list of inputs'
+# 'stamp': 'file to touch when the action is complete'
+# 'stripped_libraries_dir': 'directory to store stripped libraries',
+# },
+# 'includes': [ '../../build/android/strip_native_libraries.gypi' ],
+# ],
+# },
+#
+
+{
+ 'message': 'Stripping libraries for <(_target_name)',
+ 'variables': {
+ 'input_paths': [],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/strip_library_for_device.py',
+ '<(ordered_libraries_file)',
+ '>@(input_paths)',
+ ],
+ 'outputs': [
+ '<(stamp)',
+ ],
+ 'conditions': [
+ ['component == "shared_library"', {
+ # Add a fake output to force the build to always re-run this step. This
+ # is required because the real inputs are not known at gyp-time and
+ # changing base.so may not trigger changes to dependent libraries.
+ 'outputs': [ '<(stamp).fake' ]
+ }],
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/strip_library_for_device.py',
+ '--android-strip=<(android_strip)',
+ '--android-strip-arg=--strip-unneeded',
+ '--stripped-libraries-dir=<(stripped_libraries_dir)',
+ '--libraries-dir=<(SHARED_LIB_DIR),<(PRODUCT_DIR)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--stamp=<(stamp)',
+ ],
+}
diff --git a/build/android/symbolize.py b/build/android/symbolize.py
new file mode 100755
index 00000000000..56d3b19e228
--- /dev/null
+++ b/build/android/symbolize.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Symbolizes stack traces generated by Chromium for Android.
+
+Sample usage:
+ adb logcat chromium:V | symbolize.py
+"""
+
+import os
+import re
+import sys
+
+from pylib import constants
+
+# Uses symbol.py from third_party/android_platform, not python's.
+sys.path.insert(0,
+ os.path.join(constants.DIR_SOURCE_ROOT,
+ 'third_party/android_platform/development/scripts'))
+import symbol
+
+# Sample output from base/debug/stack_trace_android.cc
+#00 0x693cd34f /path/to/some/libfoo.so+0x0007434f
+TRACE_LINE = re.compile(r'(?P \#[0-9]+ 0x[0-9a-f]{8,8}) '
+ r'(?P[^+]+)\+0x(?P[0-9a-f]{8,8})')
+
+class Symbolizer(object):
+ def __init__(self, output):
+ self._output = output
+
+ def write(self, data):
+ while True:
+ match = re.search(TRACE_LINE, data)
+ if not match:
+ self._output.write(data)
+ break
+
+ frame = match.group('frame')
+ lib = match.group('lib')
+ addr = match.group('addr')
+
+ # TODO(scherkus): Doing a single lookup per line is pretty slow,
+ # especially with larger libraries. Consider caching strategies such as:
+ # 1) Have Python load the libraries and do symbol lookups instead of
+ # calling out to addr2line each time.
+ # 2) Have Python keep multiple addr2line instances open as subprocesses,
+ # piping addresses and reading back symbols as we find them
+ # 3) Read ahead the entire stack trace until we find no more, then batch
+ # the symbol lookups.
+ #
+ # TODO(scherkus): These results are memoized, which could result in
+ # incorrect lookups when running this script on long-lived instances
+ # (e.g., adb logcat) when doing incremental development. Consider clearing
+ # the cache when modification timestamp of libraries change.
+ sym = symbol.SymbolInformation(lib, addr, False)[0][0]
+
+ if not sym:
+ post = match.end('addr')
+ self._output.write(data[:post])
+ data = data[post:]
+ continue
+
+ pre = match.start('frame')
+ post = match.end('addr')
+
+ self._output.write(data[:pre])
+ self._output.write(frame)
+ self._output.write(' ')
+ self._output.write(sym)
+
+ data = data[post:]
+
+ def flush(self):
+ self._output.flush()
+
+
+def main():
+ symbolizer = Symbolizer(sys.stdout)
+ for line in sys.stdin:
+ symbolizer.write(line)
+ symbolizer.flush()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/android/symbolize_test.py b/build/android/symbolize_test.py
new file mode 100755
index 00000000000..826d85228e6
--- /dev/null
+++ b/build/android/symbolize_test.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Unittest for symbolize.py.
+
+This test uses test libraries generated by the Android g++ toolchain.
+
+Should things break you can recreate the libraries and get the updated
+addresses and demangled names by running the following:
+ cd test/symbolize/
+ make
+ nm -gC *.so
+"""
+
+import StringIO
+import unittest
+
+import symbolize
+
+LIB_A_PATH = '/build/android/tests/symbolize/liba.so'
+LIB_B_PATH = '/build/android/tests/symbolize/libb.so'
+
+def RunSymbolizer(text):
+ output = StringIO.StringIO()
+ s = symbolize.Symbolizer(output)
+ s.write(text)
+ return output.getvalue()
+
+
+class SymbolizerUnittest(unittest.TestCase):
+ def testSingleLineNoMatch(self):
+ # Leading '#' is required.
+ expected = '00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Whitespace should be exactly one space.
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Decimal stack frame numbers are required.
+ expected = '#0a 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Hexadecimal addresses are required.
+ expected = '#00 0xghijklmn ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+0xghijklmn\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Addresses must be exactly 8 characters.
+ expected = '#00 0x0000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x000000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ expected = '#00 0x0000000 ' + LIB_A_PATH + '+0x0000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x000000000 ' + LIB_A_PATH + '+0x000000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Addresses must be prefixed with '0x'.
+ expected = '#00 00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Library name is required.
+ expected = '#00 0x00000000\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 +0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ # Library name must be followed by offset with no spaces around '+'.
+ expected = '#00 0x00000000 ' + LIB_A_PATH + ' +0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+ 0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + ' 0x00000254\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+ expected = '#00 0x00000000 ' + LIB_A_PATH + '+\n'
+ self.assertEqual(expected, RunSymbolizer(expected))
+
+ def testSingleLine(self):
+ text = '#00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ expected = '#00 0x00000000 A::Bar(char const*)\n'
+ actual = RunSymbolizer(text)
+ self.assertEqual(expected, actual)
+
+ def testSingleLineWithSurroundingText(self):
+ text = 'LEFT #00 0x00000000 ' + LIB_A_PATH + '+0x00000254 RIGHT\n'
+ expected = 'LEFT #00 0x00000000 A::Bar(char const*) RIGHT\n'
+ actual = RunSymbolizer(text)
+ self.assertEqual(expected, actual)
+
+ def testMultipleLinesSameLibrary(self):
+ text = '#00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ text += '#01 0x00000000 ' + LIB_A_PATH + '+0x00000234\n'
+ expected = '#00 0x00000000 A::Bar(char const*)\n'
+ expected += '#01 0x00000000 A::Foo(int)\n'
+ actual = RunSymbolizer(text)
+ self.assertEqual(expected, actual)
+
+ def testMultipleLinesDifferentLibrary(self):
+ text = '#00 0x00000000 ' + LIB_A_PATH + '+0x00000254\n'
+ text += '#01 0x00000000 ' + LIB_B_PATH + '+0x00000234\n'
+ expected = '#00 0x00000000 A::Bar(char const*)\n'
+ expected += '#01 0x00000000 B::Baz(float)\n'
+ actual = RunSymbolizer(text)
+ self.assertEqual(expected, actual)
+
+ def testMultipleLinesWithSurroundingTextEverywhere(self):
+ text = 'TOP\n'
+ text += 'LEFT #00 0x00000000 ' + LIB_A_PATH + '+0x00000254 RIGHT\n'
+ text += 'LEFT #01 0x00000000 ' + LIB_B_PATH + '+0x00000234 RIGHT\n'
+ text += 'BOTTOM\n'
+ expected = 'TOP\n'
+ expected += 'LEFT #00 0x00000000 A::Bar(char const*) RIGHT\n'
+ expected += 'LEFT #01 0x00000000 B::Baz(float) RIGHT\n'
+ expected += 'BOTTOM\n'
+ actual = RunSymbolizer(text)
+ self.assertEqual(expected, actual)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/test_runner.gypi b/build/android/test_runner.gypi
new file mode 100644
index 00000000000..f92b7cedc0e
--- /dev/null
+++ b/build/android/test_runner.gypi
@@ -0,0 +1,81 @@
+# Copyright 2015 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.
+
+# Generates a script in the output bin directory which runs the test
+# target using the test runner script in build/android/pylib/test_runner.py.
+#
+# To use this, include this file in a gtest or instrumentation test target.
+# {
+# 'target_name': 'gtest',
+# 'type': 'none',
+# 'variables': {
+# 'test_type': 'gtest', # string
+# 'test_suite_name': 'gtest_suite' # string
+# 'isolate_file': 'path/to/gtest.isolate' # string
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+# {
+# 'target_name': 'instrumentation_apk',
+# 'type': 'none',
+# 'variables': {
+# 'test_type': 'instrumentation', # string
+# 'apk_name': 'TestApk' # string
+# 'isolate_file': 'path/to/instrumentation_test.isolate' # string
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+
+{
+ 'variables': {
+ 'variables': {
+ 'isolate_file%': '',
+ 'support_apk_path%': '',
+ },
+ 'test_runner_args': ['--output-directory', '<(PRODUCT_DIR)'],
+ 'conditions': [
+ ['test_type == "gtest"', {
+ 'test_runner_args': ['--suite', '<(test_suite_name)'],
+ 'script_name': 'run_<(test_suite_name)',
+ }],
+ ['test_type == "instrumentation"', {
+ 'test_runner_args': ['--test-apk', '<(apk_name)'],
+ 'script_name': 'run_<(_target_name)',
+ 'conditions': [
+ ['support_apk_path != ""', {
+ 'test_runner_args': [
+ '--support-apk',
+ '<(support_apk_path)'
+ ],
+ }],
+ ],
+ }],
+ ['isolate_file != ""', {
+ 'test_runner_args': ['--isolate-file-path', '<(isolate_file)']
+ }],
+ ],
+ },
+ 'actions': [
+ {
+ 'action_name': 'create_test_runner_script_<(script_name)',
+ 'message': 'Creating test runner script <(script_name)',
+ 'variables': {
+ 'script_output_path': '<(PRODUCT_DIR)/bin/<(script_name)',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/create_test_runner_script.py',
+ ],
+ 'outputs': [
+ '<(script_output_path)'
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/create_test_runner_script.py',
+ '--script-output-path=<(script_output_path)',
+ '<(test_type)', '<@(test_runner_args)',
+ ],
+ },
+ ],
+}
\ No newline at end of file
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
new file mode 100755
index 00000000000..1fc48ecf505
--- /dev/null
+++ b/build/android/test_runner.py
@@ -0,0 +1,1067 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Runs all types of tests from one unified interface."""
+
+import argparse
+import collections
+import logging
+import os
+import shutil
+import signal
+import sys
+import threading
+import unittest
+
+from pylib import constants
+from pylib import forwarder
+from pylib import ports
+from pylib.base import base_test_result
+from pylib.base import environment_factory
+from pylib.base import test_dispatcher
+from pylib.base import test_instance_factory
+from pylib.base import test_run_factory
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.gtest import gtest_config
+# TODO(jbudorick): Remove this once we stop selectively enabling platform mode.
+from pylib.gtest import gtest_test_instance
+from pylib.gtest import setup as gtest_setup
+from pylib.gtest import test_options as gtest_test_options
+from pylib.linker import setup as linker_setup
+from pylib.host_driven import setup as host_driven_setup
+from pylib.instrumentation import setup as instrumentation_setup
+from pylib.instrumentation import test_options as instrumentation_test_options
+from pylib.junit import setup as junit_setup
+from pylib.junit import test_dispatcher as junit_dispatcher
+from pylib.monkey import setup as monkey_setup
+from pylib.monkey import test_options as monkey_test_options
+from pylib.perf import setup as perf_setup
+from pylib.perf import test_options as perf_test_options
+from pylib.perf import test_runner as perf_test_runner
+from pylib.results import json_results
+from pylib.results import report_results
+from pylib.uiautomator import setup as uiautomator_setup
+from pylib.uiautomator import test_options as uiautomator_test_options
+from pylib.utils import apk_helper
+from pylib.utils import base_error
+from pylib.utils import reraiser_thread
+from pylib.utils import run_tests_helper
+
+
+def AddCommonOptions(parser):
+ """Adds all common options to |parser|."""
+
+ group = parser.add_argument_group('Common Options')
+
+ default_build_type = os.environ.get('BUILDTYPE', 'Debug')
+
+ debug_or_release_group = group.add_mutually_exclusive_group()
+ debug_or_release_group.add_argument(
+ '--debug', action='store_const', const='Debug', dest='build_type',
+ default=default_build_type,
+ help=('If set, run test suites under out/Debug. '
+ 'Default is env var BUILDTYPE or Debug.'))
+ debug_or_release_group.add_argument(
+ '--release', action='store_const', const='Release', dest='build_type',
+ help=('If set, run test suites under out/Release. '
+ 'Default is env var BUILDTYPE or Debug.'))
+
+ group.add_argument('--build-directory', dest='build_directory',
+ help=('Path to the directory in which build files are'
+ ' located (should not include build type)'))
+ group.add_argument('--output-directory', dest='output_directory',
+ help=('Path to the directory in which build files are'
+ ' located (must include build type). This will take'
+ ' precedence over --debug, --release and'
+ ' --build-directory'))
+ group.add_argument('--num_retries', dest='num_retries', type=int, default=2,
+ help=('Number of retries for a test before '
+ 'giving up (default: %(default)s).'))
+ group.add_argument('-v',
+ '--verbose',
+ dest='verbose_count',
+ default=0,
+ action='count',
+ help='Verbose level (multiple times for more)')
+ group.add_argument('--flakiness-dashboard-server',
+ dest='flakiness_dashboard_server',
+ help=('Address of the server that is hosting the '
+ 'Chrome for Android flakiness dashboard.'))
+ group.add_argument('--enable-platform-mode', action='store_true',
+ help=('Run the test scripts in platform mode, which '
+ 'conceptually separates the test runner from the '
+ '"device" (local or remote, real or emulated) on '
+ 'which the tests are running. [experimental]'))
+ group.add_argument('-e', '--environment', default='local',
+ choices=constants.VALID_ENVIRONMENTS,
+ help='Test environment to run in (default: %(default)s).')
+ group.add_argument('--adb-path',
+ help=('Specify the absolute path of the adb binary that '
+ 'should be used.'))
+ group.add_argument('--json-results-file', dest='json_results_file',
+ help='If set, will dump results in JSON form '
+ 'to specified file.')
+
+def ProcessCommonOptions(args):
+ """Processes and handles all common options."""
+ run_tests_helper.SetLogLevel(args.verbose_count)
+ constants.SetBuildType(args.build_type)
+ if args.build_directory:
+ constants.SetBuildDirectory(args.build_directory)
+ if args.output_directory:
+ constants.SetOutputDirectory(args.output_directory)
+ if args.adb_path:
+ constants.SetAdbPath(args.adb_path)
+ # Some things such as Forwarder require ADB to be in the environment path.
+ adb_dir = os.path.dirname(constants.GetAdbPath())
+ if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
+ os.environ['PATH'] = adb_dir + os.pathsep + os.environ['PATH']
+
+
+def AddRemoteDeviceOptions(parser):
+ group = parser.add_argument_group('Remote Device Options')
+
+ group.add_argument('--trigger',
+ help=('Only triggers the test if set. Stores test_run_id '
+ 'in given file path. '))
+ group.add_argument('--collect',
+ help=('Only collects the test results if set. '
+ 'Gets test_run_id from given file path.'))
+ group.add_argument('--remote-device', action='append',
+ help='Device type to run test on.')
+ group.add_argument('--results-path',
+ help='File path to download results to.')
+ group.add_argument('--api-protocol',
+ help='HTTP protocol to use. (http or https)')
+ group.add_argument('--api-address',
+ help='Address to send HTTP requests.')
+ group.add_argument('--api-port',
+ help='Port to send HTTP requests to.')
+ group.add_argument('--runner-type',
+ help='Type of test to run as.')
+ group.add_argument('--runner-package',
+ help='Package name of test.')
+ group.add_argument('--device-type',
+ choices=constants.VALID_DEVICE_TYPES,
+ help=('Type of device to run on. iOS or android'))
+ group.add_argument('--device-oem', action='append',
+ help='Device OEM to run on.')
+ group.add_argument('--remote-device-file',
+ help=('File with JSON to select remote device. '
+ 'Overrides all other flags.'))
+ group.add_argument('--remote-device-timeout', type=int,
+ help='Times to retry finding remote device')
+ group.add_argument('--network-config', type=int,
+ help='Integer that specifies the network environment '
+ 'that the tests will be run in.')
+
+ device_os_group = group.add_mutually_exclusive_group()
+ device_os_group.add_argument('--remote-device-minimum-os',
+ help='Minimum OS on device.')
+ device_os_group.add_argument('--remote-device-os', action='append',
+ help='OS to have on the device.')
+
+ api_secret_group = group.add_mutually_exclusive_group()
+ api_secret_group.add_argument('--api-secret', default='',
+ help='API secret for remote devices.')
+ api_secret_group.add_argument('--api-secret-file', default='',
+ help='Path to file that contains API secret.')
+
+ api_key_group = group.add_mutually_exclusive_group()
+ api_key_group.add_argument('--api-key', default='',
+ help='API key for remote devices.')
+ api_key_group.add_argument('--api-key-file', default='',
+ help='Path to file that contains API key.')
+
+
+def AddDeviceOptions(parser):
+ """Adds device options to |parser|."""
+ group = parser.add_argument_group(title='Device Options')
+ group.add_argument('--tool',
+ dest='tool',
+ help=('Run the test under a tool '
+ '(use --tool help to list them)'))
+ group.add_argument('-d', '--device', dest='test_device',
+ help=('Target device for the test suite '
+ 'to run on.'))
+
+
+def AddGTestOptions(parser):
+ """Adds gtest options to |parser|."""
+
+ gtest_suites = list(gtest_config.STABLE_TEST_SUITES
+ + gtest_config.EXPERIMENTAL_TEST_SUITES)
+
+ group = parser.add_argument_group('GTest Options')
+ group.add_argument('-s', '--suite', dest='suite_name',
+ nargs='+', metavar='SUITE_NAME', required=True,
+ help=('Executable name of the test suite to run. '
+ 'Available suites include (but are not limited to): '
+ '%s' % ', '.join('"%s"' % s for s in gtest_suites)))
+ group.add_argument('--gtest_also_run_disabled_tests',
+ '--gtest-also-run-disabled-tests',
+ dest='run_disabled', action='store_true',
+ help='Also run disabled tests if applicable.')
+ group.add_argument('-a', '--test-arguments', dest='test_arguments',
+ default='',
+ help='Additional arguments to pass to the test.')
+ group.add_argument('-t', dest='timeout', type=int, default=60,
+ help='Timeout to wait for each test '
+ '(default: %(default)s).')
+ group.add_argument('--isolate_file_path',
+ '--isolate-file-path',
+ dest='isolate_file_path',
+ help='.isolate file path to override the default '
+ 'path')
+ group.add_argument('--app-data-file', action='append', dest='app_data_files',
+ help='A file path relative to the app data directory '
+ 'that should be saved to the host.')
+ group.add_argument('--app-data-file-dir',
+ help='Host directory to which app data files will be'
+ ' saved. Used with --app-data-file.')
+ group.add_argument('--delete-stale-data', dest='delete_stale_data',
+ action='store_true',
+ help='Delete stale test data on the device.')
+
+ filter_group = group.add_mutually_exclusive_group()
+ filter_group.add_argument('-f', '--gtest_filter', '--gtest-filter',
+ dest='test_filter',
+ help='googletest-style filter string.')
+ filter_group.add_argument('--gtest-filter-file', dest='test_filter_file',
+ help='Path to file that contains googletest-style '
+ 'filter strings. (Lines will be joined with '
+ '":" to create a single filter string.)')
+
+ AddDeviceOptions(parser)
+ AddCommonOptions(parser)
+ AddRemoteDeviceOptions(parser)
+
+
+def AddLinkerTestOptions(parser):
+ group = parser.add_argument_group('Linker Test Options')
+ group.add_argument('-f', '--gtest-filter', dest='test_filter',
+ help='googletest-style filter string.')
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+
+
+def AddJavaTestOptions(argument_group):
+ """Adds the Java test options to |option_parser|."""
+
+ argument_group.add_argument(
+ '-f', '--test-filter', dest='test_filter',
+ help=('Test filter (if not fully qualified, will run all matches).'))
+ argument_group.add_argument(
+ '-A', '--annotation', dest='annotation_str',
+ help=('Comma-separated list of annotations. Run only tests with any of '
+ 'the given annotations. An annotation can be either a key or a '
+ 'key-values pair. A test that has no annotation is considered '
+ '"SmallTest".'))
+ argument_group.add_argument(
+ '-E', '--exclude-annotation', dest='exclude_annotation_str',
+ help=('Comma-separated list of annotations. Exclude tests with these '
+ 'annotations.'))
+ argument_group.add_argument(
+ '--screenshot', dest='screenshot_failures', action='store_true',
+ help='Capture screenshots of test failures')
+ argument_group.add_argument(
+ '--save-perf-json', action='store_true',
+ help='Saves the JSON file for each UI Perf test.')
+ argument_group.add_argument(
+ '--official-build', action='store_true', help='Run official build tests.')
+ argument_group.add_argument(
+ '--test_data', '--test-data', action='append', default=[],
+ help=('Each instance defines a directory of test data that should be '
+ 'copied to the target(s) before running the tests. The argument '
+ 'should be of the form :, is relative to '
+ 'the device data directory, and is relative to the '
+ 'chromium build directory.'))
+ argument_group.add_argument(
+ '--disable-dalvik-asserts', dest='set_asserts', action='store_false',
+ default=True, help='Removes the dalvik.vm.enableassertions property')
+
+
+
+def ProcessJavaTestOptions(args):
+ """Processes options/arguments and populates |options| with defaults."""
+
+ # TODO(jbudorick): Handle most of this function in argparse.
+ if args.annotation_str:
+ args.annotations = args.annotation_str.split(',')
+ elif args.test_filter:
+ args.annotations = []
+ else:
+ args.annotations = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
+ 'EnormousTest', 'IntegrationTest']
+
+ if args.exclude_annotation_str:
+ args.exclude_annotations = args.exclude_annotation_str.split(',')
+ else:
+ args.exclude_annotations = []
+
+
+def AddInstrumentationTestOptions(parser):
+ """Adds Instrumentation test options to |parser|."""
+
+ parser.usage = '%(prog)s [options]'
+
+ group = parser.add_argument_group('Instrumentation Test Options')
+ AddJavaTestOptions(group)
+
+ java_or_python_group = group.add_mutually_exclusive_group()
+ java_or_python_group.add_argument(
+ '-j', '--java-only', action='store_false',
+ dest='run_python_tests', default=True, help='Run only the Java tests.')
+ java_or_python_group.add_argument(
+ '-p', '--python-only', action='store_false',
+ dest='run_java_tests', default=True,
+ help='Run only the host-driven tests.')
+
+ group.add_argument('--host-driven-root',
+ help='Root of the host-driven tests.')
+ group.add_argument('-w', '--wait_debugger', dest='wait_for_debugger',
+ action='store_true',
+ help='Wait for debugger.')
+ group.add_argument('--apk-under-test', dest='apk_under_test',
+ help=('the name of the apk under test.'))
+ group.add_argument('--test-apk', dest='test_apk', required=True,
+ help=('The name of the apk containing the tests '
+ '(without the .apk extension; '
+ 'e.g. "ContentShellTest").'))
+ group.add_argument('--support-apk', dest='test_support_apk_path',
+ help=('The path to an optional support apk to be '
+ 'installed alongside the test apk. The '
+ 'path should be relative to the output '
+ 'directory (--output-directory).'))
+ group.add_argument('--coverage-dir',
+ help=('Directory in which to place all generated '
+ 'EMMA coverage files.'))
+ group.add_argument('--device-flags', dest='device_flags', default='',
+ help='The relative filepath to a file containing '
+ 'command-line flags to set on the device')
+ group.add_argument('--device-flags-file', default='',
+ help='The relative filepath to a file containing '
+ 'command-line flags to set on the device')
+ group.add_argument('--isolate_file_path',
+ '--isolate-file-path',
+ dest='isolate_file_path',
+ help='.isolate file path to override the default '
+ 'path')
+ group.add_argument('--delete-stale-data', dest='delete_stale_data',
+ action='store_true',
+ help='Delete stale test data on the device.')
+
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+ AddRemoteDeviceOptions(parser)
+
+
+def ProcessInstrumentationOptions(args):
+ """Processes options/arguments and populate |options| with defaults.
+
+ Args:
+ args: argparse.Namespace object.
+
+ Returns:
+ An InstrumentationOptions named tuple which contains all options relevant to
+ instrumentation tests.
+ """
+
+ ProcessJavaTestOptions(args)
+
+ if not args.host_driven_root:
+ args.run_python_tests = False
+
+ args.test_apk_path = os.path.join(
+ constants.GetOutDirectory(),
+ constants.SDK_BUILD_APKS_DIR,
+ '%s.apk' % args.test_apk)
+ args.test_apk_jar_path = os.path.join(
+ constants.GetOutDirectory(),
+ constants.SDK_BUILD_TEST_JAVALIB_DIR,
+ '%s.jar' % args.test_apk)
+
+ args.test_runner = apk_helper.GetInstrumentationName(args.test_apk_path)
+
+ # TODO(jbudorick): Get rid of InstrumentationOptions.
+ return instrumentation_test_options.InstrumentationOptions(
+ args.tool,
+ args.annotations,
+ args.exclude_annotations,
+ args.test_filter,
+ args.test_data,
+ args.save_perf_json,
+ args.screenshot_failures,
+ args.wait_for_debugger,
+ args.coverage_dir,
+ args.test_apk,
+ args.test_apk_path,
+ args.test_apk_jar_path,
+ args.test_runner,
+ args.test_support_apk_path,
+ args.device_flags,
+ args.isolate_file_path,
+ args.set_asserts,
+ args.delete_stale_data
+ )
+
+
+def AddUIAutomatorTestOptions(parser):
+ """Adds UI Automator test options to |parser|."""
+
+ group = parser.add_argument_group('UIAutomator Test Options')
+ AddJavaTestOptions(group)
+ group.add_argument(
+ '--package', required=True, choices=constants.PACKAGE_INFO.keys(),
+ metavar='PACKAGE', help='Package under test.')
+ group.add_argument(
+ '--test-jar', dest='test_jar', required=True,
+ help=('The name of the dexed jar containing the tests (without the '
+ '.dex.jar extension). Alternatively, this can be a full path '
+ 'to the jar.'))
+
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+
+
+def ProcessUIAutomatorOptions(args):
+ """Processes UIAutomator options/arguments.
+
+ Args:
+ args: argparse.Namespace object.
+
+ Returns:
+ A UIAutomatorOptions named tuple which contains all options relevant to
+ uiautomator tests.
+ """
+
+ ProcessJavaTestOptions(args)
+
+ if os.path.exists(args.test_jar):
+ # The dexed JAR is fully qualified, assume the info JAR lives along side.
+ args.uiautomator_jar = args.test_jar
+ else:
+ args.uiautomator_jar = os.path.join(
+ constants.GetOutDirectory(),
+ constants.SDK_BUILD_JAVALIB_DIR,
+ '%s.dex.jar' % args.test_jar)
+ args.uiautomator_info_jar = (
+ args.uiautomator_jar[:args.uiautomator_jar.find('.dex.jar')] +
+ '_java.jar')
+
+ return uiautomator_test_options.UIAutomatorOptions(
+ args.tool,
+ args.annotations,
+ args.exclude_annotations,
+ args.test_filter,
+ args.test_data,
+ args.save_perf_json,
+ args.screenshot_failures,
+ args.uiautomator_jar,
+ args.uiautomator_info_jar,
+ args.package,
+ args.set_asserts)
+
+
+def AddJUnitTestOptions(parser):
+ """Adds junit test options to |parser|."""
+
+ group = parser.add_argument_group('JUnit Test Options')
+ group.add_argument(
+ '-s', '--test-suite', dest='test_suite', required=True,
+ help=('JUnit test suite to run.'))
+ group.add_argument(
+ '-f', '--test-filter', dest='test_filter',
+ help='Filters tests googletest-style.')
+ group.add_argument(
+ '--package-filter', dest='package_filter',
+ help='Filters tests by package.')
+ group.add_argument(
+ '--runner-filter', dest='runner_filter',
+ help='Filters tests by runner class. Must be fully qualified.')
+ group.add_argument(
+ '--sdk-version', dest='sdk_version', type=int,
+ help='The Android SDK version.')
+ AddCommonOptions(parser)
+
+
+def AddMonkeyTestOptions(parser):
+ """Adds monkey test options to |parser|."""
+
+ group = parser.add_argument_group('Monkey Test Options')
+ group.add_argument(
+ '--package', required=True, choices=constants.PACKAGE_INFO.keys(),
+ metavar='PACKAGE', help='Package under test.')
+ group.add_argument(
+ '--event-count', default=10000, type=int,
+ help='Number of events to generate (default: %(default)s).')
+ group.add_argument(
+ '--category', default='',
+ help='A list of allowed categories.')
+ group.add_argument(
+ '--throttle', default=100, type=int,
+ help='Delay between events (ms) (default: %(default)s). ')
+ group.add_argument(
+ '--seed', type=int,
+ help=('Seed value for pseudo-random generator. Same seed value generates '
+ 'the same sequence of events. Seed is randomized by default.'))
+ group.add_argument(
+ '--extra-args', default='',
+ help=('String of other args to pass to the command verbatim.'))
+
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+
+def ProcessMonkeyTestOptions(args):
+ """Processes all monkey test options.
+
+ Args:
+ args: argparse.Namespace object.
+
+ Returns:
+ A MonkeyOptions named tuple which contains all options relevant to
+ monkey tests.
+ """
+ # TODO(jbudorick): Handle this directly in argparse with nargs='+'
+ category = args.category
+ if category:
+ category = args.category.split(',')
+
+ # TODO(jbudorick): Get rid of MonkeyOptions.
+ return monkey_test_options.MonkeyOptions(
+ args.verbose_count,
+ args.package,
+ args.event_count,
+ category,
+ args.throttle,
+ args.seed,
+ args.extra_args)
+
+def AddUirobotTestOptions(parser):
+ """Adds uirobot test options to |option_parser|."""
+ group = parser.add_argument_group('Uirobot Test Options')
+
+ group.add_argument('--app-under-test', required=True,
+ help='APK to run tests on.')
+ group.add_argument(
+ '--minutes', default=5, type=int,
+ help='Number of minutes to run uirobot test [default: %(default)s].')
+
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+ AddRemoteDeviceOptions(parser)
+
+def AddPerfTestOptions(parser):
+ """Adds perf test options to |parser|."""
+
+ group = parser.add_argument_group('Perf Test Options')
+
+ class SingleStepAction(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ if values and not namespace.single_step:
+ parser.error('single step command provided, '
+ 'but --single-step not specified.')
+ elif namespace.single_step and not values:
+ parser.error('--single-step specified, '
+ 'but no single step command provided.')
+ setattr(namespace, self.dest, values)
+
+ step_group = group.add_mutually_exclusive_group(required=True)
+ # TODO(jbudorick): Revise --single-step to use argparse.REMAINDER.
+ # This requires removing "--" from client calls.
+ step_group.add_argument(
+ '--single-step', action='store_true',
+ help='Execute the given command with retries, but only print the result '
+ 'for the "most successful" round.')
+ step_group.add_argument(
+ '--steps',
+ help='JSON file containing the list of commands to run.')
+ step_group.add_argument(
+ '--print-step',
+ help='The name of a previously executed perf step to print.')
+
+ group.add_argument(
+ '--output-json-list',
+ help='Write a simple list of names from --steps into the given file.')
+ group.add_argument(
+ '--collect-chartjson-data',
+ action='store_true',
+ help='Cache the chartjson output from each step for later use.')
+ group.add_argument(
+ '--output-chartjson-data',
+ default='',
+ help='Write out chartjson into the given file.')
+ group.add_argument(
+ '--flaky-steps',
+ help=('A JSON file containing steps that are flaky '
+ 'and will have its exit code ignored.'))
+ group.add_argument(
+ '--no-timeout', action='store_true',
+ help=('Do not impose a timeout. Each perf step is responsible for '
+ 'implementing the timeout logic.'))
+ group.add_argument(
+ '-f', '--test-filter',
+ help=('Test filter (will match against the names listed in --steps).'))
+ group.add_argument(
+ '--dry-run', action='store_true',
+ help='Just print the steps without executing.')
+ # Uses 0.1 degrees C because that's what Android does.
+ group.add_argument(
+ '--max-battery-temp', type=int,
+ help='Only start tests when the battery is at or below the given '
+ 'temperature (0.1 C)')
+ group.add_argument('single_step_command', nargs='*', action=SingleStepAction,
+ help='If --single-step is specified, the command to run.')
+ group.add_argument('--min-battery-level', type=int,
+ help='Only starts tests when the battery is charged above '
+ 'given level.')
+ AddCommonOptions(parser)
+ AddDeviceOptions(parser)
+
+
+def ProcessPerfTestOptions(args):
+ """Processes all perf test options.
+
+ Args:
+ args: argparse.Namespace object.
+
+ Returns:
+ A PerfOptions named tuple which contains all options relevant to
+ perf tests.
+ """
+ # TODO(jbudorick): Move single_step handling down into the perf tests.
+ if args.single_step:
+ args.single_step = ' '.join(args.single_step_command)
+ # TODO(jbudorick): Get rid of PerfOptions.
+ return perf_test_options.PerfOptions(
+ args.steps, args.flaky_steps, args.output_json_list,
+ args.print_step, args.no_timeout, args.test_filter,
+ args.dry_run, args.single_step, args.collect_chartjson_data,
+ args.output_chartjson_data, args.max_battery_temp, args.min_battery_level)
+
+
+def AddPythonTestOptions(parser):
+ group = parser.add_argument_group('Python Test Options')
+ group.add_argument(
+ '-s', '--suite', dest='suite_name', metavar='SUITE_NAME',
+ choices=constants.PYTHON_UNIT_TEST_SUITES.keys(),
+ help='Name of the test suite to run.')
+ AddCommonOptions(parser)
+
+
+def _RunGTests(args, devices):
+ """Subcommand of RunTestsCommands which runs gtests."""
+ exit_code = 0
+ for suite_name in args.suite_name:
+ # TODO(jbudorick): Either deprecate multi-suite or move its handling down
+ # into the gtest code.
+ gtest_options = gtest_test_options.GTestOptions(
+ args.tool,
+ args.test_filter,
+ args.run_disabled,
+ args.test_arguments,
+ args.timeout,
+ args.isolate_file_path,
+ suite_name,
+ args.app_data_files,
+ args.app_data_file_dir,
+ args.delete_stale_data)
+ runner_factory, tests = gtest_setup.Setup(gtest_options, devices)
+
+ results, test_exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=True, test_timeout=None,
+ num_retries=args.num_retries)
+
+ if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
+ exit_code = test_exit_code
+
+ report_results.LogFull(
+ results=results,
+ test_type='Unit test',
+ test_package=suite_name,
+ flakiness_server=args.flakiness_dashboard_server)
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunLinkerTests(args, devices):
+ """Subcommand of RunTestsCommands which runs linker tests."""
+ runner_factory, tests = linker_setup.Setup(args, devices)
+
+ results, exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=True, test_timeout=60,
+ num_retries=args.num_retries)
+
+ report_results.LogFull(
+ results=results,
+ test_type='Linker test',
+ test_package='ChromiumLinkerTest')
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunInstrumentationTests(args, devices):
+ """Subcommand of RunTestsCommands which runs instrumentation tests."""
+ logging.info('_RunInstrumentationTests(%s, %s)' % (str(args), str(devices)))
+
+ instrumentation_options = ProcessInstrumentationOptions(args)
+
+ if len(devices) > 1 and args.wait_for_debugger:
+ logging.warning('Debugger can not be sharded, using first available device')
+ devices = devices[:1]
+
+ results = base_test_result.TestRunResults()
+ exit_code = 0
+
+ if args.run_java_tests:
+ runner_factory, tests = instrumentation_setup.Setup(
+ instrumentation_options, devices)
+
+ test_results, exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=True, test_timeout=None,
+ num_retries=args.num_retries)
+
+ results.AddTestRunResults(test_results)
+
+ if args.run_python_tests:
+ runner_factory, tests = host_driven_setup.InstrumentationSetup(
+ args.host_driven_root, args.official_build,
+ instrumentation_options)
+
+ if tests:
+ test_results, test_exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=True, test_timeout=None,
+ num_retries=args.num_retries)
+
+ results.AddTestRunResults(test_results)
+
+ # Only allow exit code escalation
+ if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
+ exit_code = test_exit_code
+
+ if args.device_flags:
+ args.device_flags = os.path.join(constants.DIR_SOURCE_ROOT,
+ args.device_flags)
+
+ report_results.LogFull(
+ results=results,
+ test_type='Instrumentation',
+ test_package=os.path.basename(args.test_apk),
+ annotation=args.annotations,
+ flakiness_server=args.flakiness_dashboard_server)
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunUIAutomatorTests(args, devices):
+ """Subcommand of RunTestsCommands which runs uiautomator tests."""
+ uiautomator_options = ProcessUIAutomatorOptions(args)
+
+ runner_factory, tests = uiautomator_setup.Setup(uiautomator_options)
+
+ results, exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=True, test_timeout=None,
+ num_retries=args.num_retries)
+
+ report_results.LogFull(
+ results=results,
+ test_type='UIAutomator',
+ test_package=os.path.basename(args.test_jar),
+ annotation=args.annotations,
+ flakiness_server=args.flakiness_dashboard_server)
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunJUnitTests(args):
+ """Subcommand of RunTestsCommand which runs junit tests."""
+ runner_factory, tests = junit_setup.Setup(args)
+ results, exit_code = junit_dispatcher.RunTests(tests, runner_factory)
+
+ report_results.LogFull(
+ results=results,
+ test_type='JUnit',
+ test_package=args.test_suite)
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunMonkeyTests(args, devices):
+ """Subcommand of RunTestsCommands which runs monkey tests."""
+ monkey_options = ProcessMonkeyTestOptions(args)
+
+ runner_factory, tests = monkey_setup.Setup(monkey_options)
+
+ results, exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=False, test_timeout=None,
+ num_retries=args.num_retries)
+
+ report_results.LogFull(
+ results=results,
+ test_type='Monkey',
+ test_package='Monkey')
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ return exit_code
+
+
+def _RunPerfTests(args):
+ """Subcommand of RunTestsCommands which runs perf tests."""
+ perf_options = ProcessPerfTestOptions(args)
+
+ # Just save a simple json with a list of test names.
+ if perf_options.output_json_list:
+ return perf_test_runner.OutputJsonList(
+ perf_options.steps, perf_options.output_json_list)
+
+ # Just print the results from a single previously executed step.
+ if perf_options.print_step:
+ return perf_test_runner.PrintTestOutput(
+ perf_options.print_step, perf_options.output_chartjson_data)
+
+ runner_factory, tests, devices = perf_setup.Setup(perf_options)
+
+ # shard=False means that each device will get the full list of tests
+ # and then each one will decide their own affinity.
+ # shard=True means each device will pop the next test available from a queue,
+ # which increases throughput but have no affinity.
+ results, _ = test_dispatcher.RunTests(
+ tests, runner_factory, devices, shard=False, test_timeout=None,
+ num_retries=args.num_retries)
+
+ report_results.LogFull(
+ results=results,
+ test_type='Perf',
+ test_package='Perf')
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(results, args.json_results_file)
+
+ if perf_options.single_step:
+ return perf_test_runner.PrintTestOutput('single_step')
+
+ perf_test_runner.PrintSummary(tests)
+
+ # Always return 0 on the sharding stage. Individual tests exit_code
+ # will be returned on the print_step stage.
+ return 0
+
+
+def _RunPythonTests(args):
+ """Subcommand of RunTestsCommand which runs python unit tests."""
+ suite_vars = constants.PYTHON_UNIT_TEST_SUITES[args.suite_name]
+ suite_path = suite_vars['path']
+ suite_test_modules = suite_vars['test_modules']
+
+ sys.path = [suite_path] + sys.path
+ try:
+ suite = unittest.TestSuite()
+ suite.addTests(unittest.defaultTestLoader.loadTestsFromName(m)
+ for m in suite_test_modules)
+ runner = unittest.TextTestRunner(verbosity=1+args.verbose_count)
+ return 0 if runner.run(suite).wasSuccessful() else 1
+ finally:
+ sys.path = sys.path[1:]
+
+
+def _GetAttachedDevices(test_device=None):
+ """Get all attached devices.
+
+ Args:
+ test_device: Name of a specific device to use.
+
+ Returns:
+ A list of attached devices.
+ """
+ attached_devices = device_utils.DeviceUtils.HealthyDevices()
+ if test_device:
+ test_device = [d for d in attached_devices if d == test_device]
+ if not test_device:
+ raise device_errors.DeviceUnreachableError(
+ 'Did not find device %s among attached device. Attached devices: %s'
+ % (test_device, ', '.join(attached_devices)))
+ return test_device
+
+ else:
+ if not attached_devices:
+ raise device_errors.NoDevicesError()
+ return sorted(attached_devices)
+
+
+def RunTestsCommand(args, parser):
+ """Checks test type and dispatches to the appropriate function.
+
+ Args:
+ args: argparse.Namespace object.
+ parser: argparse.ArgumentParser object.
+
+ Returns:
+ Integer indicated exit code.
+
+ Raises:
+ Exception: Unknown command name passed in, or an exception from an
+ individual test runner.
+ """
+ command = args.command
+
+ ProcessCommonOptions(args)
+
+ if args.enable_platform_mode:
+ return RunTestsInPlatformMode(args, parser)
+
+ if command in constants.LOCAL_MACHINE_TESTS:
+ devices = []
+ else:
+ devices = _GetAttachedDevices(args.test_device)
+
+ forwarder.Forwarder.RemoveHostLog()
+ if not ports.ResetTestServerPortAllocation():
+ raise Exception('Failed to reset test server port.')
+
+ if command == 'gtest':
+ if args.suite_name[0] in gtest_test_instance.BROWSER_TEST_SUITES:
+ return RunTestsInPlatformMode(args, parser)
+ return _RunGTests(args, devices)
+ elif command == 'linker':
+ return _RunLinkerTests(args, devices)
+ elif command == 'instrumentation':
+ return _RunInstrumentationTests(args, devices)
+ elif command == 'uiautomator':
+ return _RunUIAutomatorTests(args, devices)
+ elif command == 'junit':
+ return _RunJUnitTests(args)
+ elif command == 'monkey':
+ return _RunMonkeyTests(args, devices)
+ elif command == 'perf':
+ return _RunPerfTests(args)
+ elif command == 'python':
+ return _RunPythonTests(args)
+ else:
+ raise Exception('Unknown test type.')
+
+
+_SUPPORTED_IN_PLATFORM_MODE = [
+ # TODO(jbudorick): Add support for more test types.
+ 'gtest',
+ 'instrumentation',
+ 'uirobot',
+]
+
+
+def RunTestsInPlatformMode(args, parser):
+
+ if args.command not in _SUPPORTED_IN_PLATFORM_MODE:
+ parser.error('%s is not yet supported in platform mode' % args.command)
+
+ with environment_factory.CreateEnvironment(args, parser.error) as env:
+ with test_instance_factory.CreateTestInstance(args, parser.error) as test:
+ with test_run_factory.CreateTestRun(
+ args, env, test, parser.error) as test_run:
+ results = test_run.RunTests()
+
+ if args.environment == 'remote_device' and args.trigger:
+ return 0 # Not returning results, only triggering.
+
+ report_results.LogFull(
+ results=results,
+ test_type=test.TestType(),
+ test_package=test_run.TestPackage(),
+ annotation=getattr(args, 'annotations', None),
+ flakiness_server=getattr(args, 'flakiness_dashboard_server', None))
+
+ if args.json_results_file:
+ json_results.GenerateJsonResultsFile(
+ results, args.json_results_file)
+
+ return 0 if results.DidRunPass() else constants.ERROR_EXIT_CODE
+
+
+CommandConfigTuple = collections.namedtuple(
+ 'CommandConfigTuple',
+ ['add_options_func', 'help_txt'])
+VALID_COMMANDS = {
+ 'gtest': CommandConfigTuple(
+ AddGTestOptions,
+ 'googletest-based C++ tests'),
+ 'instrumentation': CommandConfigTuple(
+ AddInstrumentationTestOptions,
+ 'InstrumentationTestCase-based Java tests'),
+ 'uiautomator': CommandConfigTuple(
+ AddUIAutomatorTestOptions,
+ "Tests that run via Android's uiautomator command"),
+ 'junit': CommandConfigTuple(
+ AddJUnitTestOptions,
+ 'JUnit4-based Java tests'),
+ 'monkey': CommandConfigTuple(
+ AddMonkeyTestOptions,
+ "Tests based on Android's monkey"),
+ 'perf': CommandConfigTuple(
+ AddPerfTestOptions,
+ 'Performance tests'),
+ 'python': CommandConfigTuple(
+ AddPythonTestOptions,
+ 'Python tests based on unittest.TestCase'),
+ 'linker': CommandConfigTuple(
+ AddLinkerTestOptions,
+ 'Linker tests'),
+ 'uirobot': CommandConfigTuple(
+ AddUirobotTestOptions,
+ 'Uirobot test'),
+}
+
+
+def DumpThreadStacks(_signal, _frame):
+ for thread in threading.enumerate():
+ reraiser_thread.LogThreadStack(thread)
+
+
+def main():
+ signal.signal(signal.SIGUSR1, DumpThreadStacks)
+
+ parser = argparse.ArgumentParser()
+ command_parsers = parser.add_subparsers(title='test types',
+ dest='command')
+
+ for test_type, config in sorted(VALID_COMMANDS.iteritems(),
+ key=lambda x: x[0]):
+ subparser = command_parsers.add_parser(
+ test_type, usage='%(prog)s [options]', help=config.help_txt)
+ config.add_options_func(subparser)
+
+ args = parser.parse_args()
+
+ try:
+ return RunTestsCommand(args, parser)
+ except base_error.BaseError as e:
+ logging.exception('Error occurred.')
+ if e.is_infra_error:
+ return constants.INFRA_EXIT_CODE
+ return constants.ERROR_EXIT_CODE
+ except: # pylint: disable=W0702
+ logging.exception('Unrecognized error occurred.')
+ return constants.ERROR_EXIT_CODE
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/tests/symbolize/Makefile b/build/android/tests/symbolize/Makefile
new file mode 100644
index 00000000000..5178a04bb74
--- /dev/null
+++ b/build/android/tests/symbolize/Makefile
@@ -0,0 +1,11 @@
+# Copyright 2013 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.
+
+TOOLCHAIN=../../../../third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/bin/arm-linux-androideabi-
+CXX=$(TOOLCHAIN)g++
+
+lib%.so: %.cc
+ $(CXX) -nostdlib -g -fPIC -shared $< -o $@
+
+all: liba.so libb.so
diff --git a/build/android/tests/symbolize/a.cc b/build/android/tests/symbolize/a.cc
new file mode 100644
index 00000000000..f0c7ca4c67f
--- /dev/null
+++ b/build/android/tests/symbolize/a.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 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.
+
+class A {
+ public:
+ A();
+ void Foo(int i);
+ void Bar(const char* c);
+};
+
+A::A() {}
+void A::Foo(int i) {}
+void A::Bar(const char* c) {}
diff --git a/build/android/tests/symbolize/b.cc b/build/android/tests/symbolize/b.cc
new file mode 100644
index 00000000000..db8752099aa
--- /dev/null
+++ b/build/android/tests/symbolize/b.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 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.
+
+class B {
+ public:
+ B();
+ void Baz(float f);
+ void Qux(double d);
+};
+
+B::B() {}
+void B::Baz(float f) {}
+void B::Qux(double d) {}
diff --git a/build/android/tests/symbolize/liba.so b/build/android/tests/symbolize/liba.so
new file mode 100644
index 00000000000..79cb7391212
Binary files /dev/null and b/build/android/tests/symbolize/liba.so differ
diff --git a/build/android/tests/symbolize/libb.so b/build/android/tests/symbolize/libb.so
new file mode 100644
index 00000000000..7cf01d43c58
Binary files /dev/null and b/build/android/tests/symbolize/libb.so differ
diff --git a/build/android/tombstones.py b/build/android/tombstones.py
new file mode 100755
index 00000000000..dbfe3f76c28
--- /dev/null
+++ b/build/android/tombstones.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+#
+# Find the most recent tombstone file(s) on all connected devices
+# and prints their stacks.
+#
+# Assumes tombstone file was created with current symbols.
+
+import datetime
+import itertools
+import logging
+import multiprocessing
+import os
+import re
+import subprocess
+import sys
+import optparse
+
+from pylib.device import adb_wrapper
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import run_tests_helper
+
+
+_TZ_UTC = {'TZ': 'UTC'}
+
+def _ListTombstones(device):
+ """List the tombstone files on the device.
+
+ Args:
+ device: An instance of DeviceUtils.
+
+ Yields:
+ Tuples of (tombstone filename, date time of file on device).
+ """
+ try:
+ lines = device.RunShellCommand(
+ ['ls', '-a', '-l', '/data/tombstones'],
+ as_root=True, check_return=True, env=_TZ_UTC, timeout=60)
+ for line in lines:
+ if 'tombstone' in line and not 'No such file or directory' in line:
+ details = line.split()
+ t = datetime.datetime.strptime(details[-3] + ' ' + details[-2],
+ '%Y-%m-%d %H:%M')
+ yield details[-1], t
+ except device_errors.CommandFailedError:
+ logging.exception('Could not retrieve tombstones.')
+
+
+def _GetDeviceDateTime(device):
+ """Determine the date time on the device.
+
+ Args:
+ device: An instance of DeviceUtils.
+
+ Returns:
+ A datetime instance.
+ """
+ device_now_string = device.RunShellCommand(
+ ['date'], check_return=True, env=_TZ_UTC)
+ return datetime.datetime.strptime(
+ device_now_string[0], '%a %b %d %H:%M:%S %Z %Y')
+
+
+def _GetTombstoneData(device, tombstone_file):
+ """Retrieve the tombstone data from the device
+
+ Args:
+ device: An instance of DeviceUtils.
+ tombstone_file: the tombstone to retrieve
+
+ Returns:
+ A list of lines
+ """
+ return device.ReadFile(
+ '/data/tombstones/' + tombstone_file, as_root=True).splitlines()
+
+
+def _EraseTombstone(device, tombstone_file):
+ """Deletes a tombstone from the device.
+
+ Args:
+ device: An instance of DeviceUtils.
+ tombstone_file: the tombstone to delete.
+ """
+ return device.RunShellCommand(
+ ['rm', '/data/tombstones/' + tombstone_file],
+ as_root=True, check_return=True)
+
+
+def _DeviceAbiToArch(device_abi):
+ # The order of this list is significant to find the more specific match (e.g.,
+ # arm64) before the less specific (e.g., arm).
+ arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips']
+ for arch in arches:
+ if arch in device_abi:
+ return arch
+ raise RuntimeError('Unknown device ABI: %s' % device_abi)
+
+def _ResolveSymbols(tombstone_data, include_stack, device_abi):
+ """Run the stack tool for given tombstone input.
+
+ Args:
+ tombstone_data: a list of strings of tombstone data.
+ include_stack: boolean whether to include stack data in output.
+ device_abi: the default ABI of the device which generated the tombstone.
+
+ Yields:
+ A string for each line of resolved stack output.
+ """
+ # Check if the tombstone data has an ABI listed, if so use this in preference
+ # to the device's default ABI.
+ for line in tombstone_data:
+ found_abi = re.search('ABI: \'(.+?)\'', line)
+ if found_abi:
+ device_abi = found_abi.group(1)
+ arch = _DeviceAbiToArch(device_abi)
+ if not arch:
+ return
+
+ stack_tool = os.path.join(os.path.dirname(__file__), '..', '..',
+ 'third_party', 'android_platform', 'development',
+ 'scripts', 'stack')
+ proc = subprocess.Popen([stack_tool, '--arch', arch], stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ output = proc.communicate(input='\n'.join(tombstone_data))[0]
+ for line in output.split('\n'):
+ if not include_stack and 'Stack Data:' in line:
+ break
+ yield line
+
+
+def _ResolveTombstone(tombstone):
+ lines = []
+ lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) +
+ ', about this long ago: ' +
+ (str(tombstone['device_now'] - tombstone['time']) +
+ ' Device: ' + tombstone['serial'])]
+ logging.info('\n'.join(lines))
+ logging.info('Resolving...')
+ lines += _ResolveSymbols(tombstone['data'], tombstone['stack'],
+ tombstone['device_abi'])
+ return lines
+
+
+def _ResolveTombstones(jobs, tombstones):
+ """Resolve a list of tombstones.
+
+ Args:
+ jobs: the number of jobs to use with multiprocess.
+ tombstones: a list of tombstones.
+ """
+ if not tombstones:
+ logging.warning('No tombstones to resolve.')
+ return
+ if len(tombstones) == 1:
+ data = [_ResolveTombstone(tombstones[0])]
+ else:
+ pool = multiprocessing.Pool(processes=jobs)
+ data = pool.map(_ResolveTombstone, tombstones)
+ for tombstone in data:
+ for line in tombstone:
+ logging.info(line)
+
+
+def _GetTombstonesForDevice(device, options):
+ """Returns a list of tombstones on a given device.
+
+ Args:
+ device: An instance of DeviceUtils.
+ options: command line arguments from OptParse
+ """
+ ret = []
+ all_tombstones = list(_ListTombstones(device))
+ if not all_tombstones:
+ logging.warning('No tombstones.')
+ return ret
+
+ # Sort the tombstones in date order, descending
+ all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1]))
+
+ # Only resolve the most recent unless --all-tombstones given.
+ tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]]
+
+ device_now = _GetDeviceDateTime(device)
+ try:
+ for tombstone_file, tombstone_time in tombstones:
+ ret += [{'serial': str(device),
+ 'device_abi': device.product_cpu_abi,
+ 'device_now': device_now,
+ 'time': tombstone_time,
+ 'file': tombstone_file,
+ 'stack': options.stack,
+ 'data': _GetTombstoneData(device, tombstone_file)}]
+ except device_errors.CommandFailedError:
+ for line in device.RunShellCommand(
+ ['ls', '-a', '-l', '/data/tombstones'],
+ as_root=True, check_return=True, env=_TZ_UTC, timeout=60):
+ logging.info('%s: %s', str(device), line)
+ raise
+
+ # Erase all the tombstones if desired.
+ if options.wipe_tombstones:
+ for tombstone_file, _ in all_tombstones:
+ _EraseTombstone(device, tombstone_file)
+
+ return ret
+
+
+def main():
+ custom_handler = logging.StreamHandler(sys.stdout)
+ custom_handler.setFormatter(run_tests_helper.CustomFormatter())
+ logging.getLogger().addHandler(custom_handler)
+ logging.getLogger().setLevel(logging.INFO)
+
+ parser = optparse.OptionParser()
+ parser.add_option('--device',
+ help='The serial number of the device. If not specified '
+ 'will use all devices.')
+ parser.add_option('-a', '--all-tombstones', action='store_true',
+ help="""Resolve symbols for all tombstones, rather than just
+ the most recent""")
+ parser.add_option('-s', '--stack', action='store_true',
+ help='Also include symbols for stack data')
+ parser.add_option('-w', '--wipe-tombstones', action='store_true',
+ help='Erase all tombstones from device after processing')
+ parser.add_option('-j', '--jobs', type='int',
+ default=4,
+ help='Number of jobs to use when processing multiple '
+ 'crash stacks.')
+ options, _ = parser.parse_args()
+
+ if options.device:
+ devices = [device_utils.DeviceUtils(options.device)]
+ else:
+ devices = device_utils.DeviceUtils.HealthyDevices()
+
+ # This must be done serially because strptime can hit a race condition if
+ # used for the first time in a multithreaded environment.
+ # http://bugs.python.org/issue7980
+ tombstones = []
+ for device in devices:
+ tombstones += _GetTombstonesForDevice(device, options)
+
+ _ResolveTombstones(options.jobs, tombstones)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/update_verification.py b/build/android/update_verification.py
new file mode 100755
index 00000000000..05d083b32a3
--- /dev/null
+++ b/build/android/update_verification.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 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.
+
+"""Runs semi-automated update testing on a non-rooted device.
+
+This script will help verify that app data is preserved during an update.
+To use this script first run it with the create_app_data option.
+
+./update_verification.py create_app_data --old-apk --app-data
+
+The script will then install the old apk, prompt you to create some app data
+(bookmarks, etc.), and then save the app data in the path you gave it.
+
+Next, once you have some app data saved, run this script with the test_update
+option.
+
+./update_verification.py test_update --old-apk --new-apk
+--app-data
+
+This will install the old apk, load the saved app data, install the new apk,
+and ask the user to verify that all of the app data was preserved.
+"""
+
+import argparse
+import logging
+import os
+import sys
+import time
+
+from pylib import constants
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import apk_helper
+from pylib.utils import run_tests_helper
+
+def CreateAppData(device, old_apk, app_data, package_name):
+ device.Install(old_apk)
+ raw_input('Set the application state. Once ready, press enter and '
+ 'select "Backup my data" on the device.')
+ device.adb.Backup(app_data, packages=[package_name])
+ logging.critical('Application data saved to %s' % app_data)
+
+def TestUpdate(device, old_apk, new_apk, app_data, package_name):
+ device.Install(old_apk)
+ device.adb.Restore(app_data)
+ # Restore command is not synchronous
+ raw_input('Select "Restore my data" on the device. Then press enter to '
+ 'continue.')
+ device_path = device.GetApplicationPaths(package_name)
+ if not device_path:
+ raise Exception('Expected package %s to already be installed. '
+ 'Package name might have changed!' % package_name)
+
+ logging.info('Verifying that %s can be overinstalled.', new_apk)
+ device.adb.Install(new_apk, reinstall=True)
+ logging.critical('Successfully updated to the new apk. Please verify that '
+ 'the application data is preserved.')
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Script to do semi-automated upgrade testing.")
+ parser.add_argument('-v', '--verbose', action='count',
+ help='Print verbose log information.')
+ command_parsers = parser.add_subparsers(dest='command')
+
+ subparser = command_parsers.add_parser('create_app_data')
+ subparser.add_argument('--old-apk', required=True,
+ help='Path to apk to update from.')
+ subparser.add_argument('--app-data', required=True,
+ help='Path to where the app data backup should be '
+ 'saved to.')
+ subparser.add_argument('--package-name',
+ help='Chrome apk package name.')
+
+ subparser = command_parsers.add_parser('test_update')
+ subparser.add_argument('--old-apk', required=True,
+ help='Path to apk to update from.')
+ subparser.add_argument('--new-apk', required=True,
+ help='Path to apk to update to.')
+ subparser.add_argument('--app-data', required=True,
+ help='Path to where the app data backup is saved.')
+ subparser.add_argument('--package-name',
+ help='Chrome apk package name.')
+
+ args = parser.parse_args()
+ run_tests_helper.SetLogLevel(args.verbose)
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if not devices:
+ raise device_errors.NoDevicesError()
+ device = devices[0]
+ logging.info('Using device %s for testing.' % str(device))
+
+ package_name = (args.package_name if args.package_name
+ else apk_helper.GetPackageName(args.old_apk))
+ if args.command == 'create_app_data':
+ CreateAppData(device, args.old_apk, args.app_data, package_name)
+ elif args.command == 'test_update':
+ TestUpdate(
+ device, args.old_apk, args.new_apk, args.app_data, package_name)
+ else:
+ raise Exception('Unknown test command: %s' % args.command)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/android/write_ordered_libraries.gypi b/build/android/write_ordered_libraries.gypi
new file mode 100644
index 00000000000..1b52e71e423
--- /dev/null
+++ b/build/android/write_ordered_libraries.gypi
@@ -0,0 +1,43 @@
+# Copyright 2013 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 file is meant to be included into an action to provide a rule that
+# generates a json file with the list of dependent libraries needed for a given
+# shared library or executable.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'actions': [
+# 'variables': {
+# 'input_libraries': 'shared library or executable to process',
+# 'ordered_libraries_file': 'file to generate'
+# },
+# 'includes': [ '../../build/android/write_ordered_libraries.gypi' ],
+# ],
+# },
+#
+
+{
+ 'action_name': 'ordered_libraries_<(_target_name)<(subtarget)',
+ 'message': 'Writing dependency ordered libraries for <(_target_name)',
+ 'variables': {
+ 'input_libraries%': [],
+ 'subtarget%': '',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/write_ordered_libraries.py',
+ '<@(input_libraries)',
+ ],
+ 'outputs': [
+ '<(ordered_libraries_file)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/write_ordered_libraries.py',
+ '--input-libraries=<(input_libraries)',
+ '--libraries-dir=<(SHARED_LIB_DIR),<(PRODUCT_DIR)',
+ '--readelf=<(android_readelf)',
+ '--output=<(ordered_libraries_file)',
+ ],
+}
diff --git a/build/android_sdk_extras.json b/build/android_sdk_extras.json
new file mode 100644
index 00000000000..25b47c360a0
--- /dev/null
+++ b/build/android_sdk_extras.json
@@ -0,0 +1,9 @@
+[
+ {
+ "dir_name": "google",
+ "version": "21.0.0",
+ "zip": "google_google_play_services_21.0.0.zip",
+ "package": "google_play_services",
+ "package_id": "extra-google-google_play_services"
+ }
+]
diff --git a/build/apk_browsertest.gypi b/build/apk_browsertest.gypi
new file mode 100644
index 00000000000..316f52fb853
--- /dev/null
+++ b/build/apk_browsertest.gypi
@@ -0,0 +1,43 @@
+# Copyright 2015 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 file is meant to be included into a target to provide a rule
+# to build APK-based browser test suites.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'test_suite_name_apk',
+# 'type': 'none',
+# 'variables': {
+# 'test_suite_name': 'test_suite_name', # string
+# 'java_in_dir': 'path/to/java/dir',
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+
+{
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base_java',
+ '<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
+ '<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk',
+ '<(DEPTH)/testing/android/appurify_support.gyp:appurify_support_java',
+ '<(DEPTH)/testing/android/native_test.gyp:native_test_java',
+ '<(DEPTH)/tools/android/android_tools.gyp:android_tools',
+ ],
+ 'conditions': [
+ ['OS == "android"', {
+ 'variables': {
+ # These are used to configure java_apk.gypi included below.
+ 'apk_name': '<(test_suite_name)',
+ 'intermediate_dir': '<(PRODUCT_DIR)/<(test_suite_name)_apk',
+ 'final_apk_path': '<(intermediate_dir)/<(test_suite_name)-debug.apk',
+ 'native_lib_target': 'lib<(test_suite_name)',
+ # TODO(yfriedman, cjhopman): Support managed installs for gtests.
+ 'gyp_managed_install': 0,
+ },
+ 'includes': [ 'java_apk.gypi' ],
+ }], # 'OS == "android"
+ ], # conditions
+}
diff --git a/build/apk_fake_jar.gypi b/build/apk_fake_jar.gypi
new file mode 100644
index 00000000000..128b84cc2fa
--- /dev/null
+++ b/build/apk_fake_jar.gypi
@@ -0,0 +1,15 @@
+# Copyright 2013 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 file is meant to be included into a target to provide a rule
+# to build Java in a consistent manner.
+
+{
+ 'all_dependent_settings': {
+ 'variables': {
+ 'input_jars_paths': ['>(apk_output_jar_path)'],
+ 'library_dexed_jars_paths': ['>(apk_output_jar_path)'],
+ },
+ },
+}
diff --git a/build/apk_test.gypi b/build/apk_test.gypi
new file mode 100644
index 00000000000..e0d323f74a3
--- /dev/null
+++ b/build/apk_test.gypi
@@ -0,0 +1,45 @@
+# Copyright (c) 2012 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 file is meant to be included into a target to provide a rule
+# to build APK based test suites.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'test_suite_name_apk',
+# 'type': 'none',
+# 'variables': {
+# 'test_suite_name': 'test_suite_name', # string
+# 'input_jars_paths': ['/path/to/test_suite.jar', ... ], # list
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+
+{
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base_java',
+ '<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
+ '<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk',
+ '<(DEPTH)/testing/android/appurify_support.gyp:appurify_support_java',
+ '<(DEPTH)/testing/android/on_device_instrumentation.gyp:reporter_java',
+ '<(DEPTH)/tools/android/android_tools.gyp:android_tools',
+ ],
+ 'conditions': [
+ ['OS == "android"', {
+ 'variables': {
+ # These are used to configure java_apk.gypi included below.
+ 'test_type': 'gtest',
+ 'apk_name': '<(test_suite_name)',
+ 'intermediate_dir': '<(PRODUCT_DIR)/<(test_suite_name)_apk',
+ 'final_apk_path': '<(intermediate_dir)/<(test_suite_name)-debug.apk',
+ 'java_in_dir': '<(DEPTH)/testing/android/native_test/java',
+ 'native_lib_target': 'lib<(test_suite_name)',
+ # TODO(yfriedman, cjhopman): Support managed installs for gtests.
+ 'gyp_managed_install': 0,
+ },
+ 'includes': [ 'java_apk.gypi', 'android/test_runner.gypi' ],
+ }], # 'OS == "android"
+ ], # conditions
+}
diff --git a/build/apply_locales.py b/build/apply_locales.py
new file mode 100755
index 00000000000..6af7280fadc
--- /dev/null
+++ b/build/apply_locales.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 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.
+
+# TODO: remove this script when GYP has for loops
+
+import sys
+import optparse
+
+def main(argv):
+
+ parser = optparse.OptionParser()
+ usage = 'usage: %s [options ...] format_string locale_list'
+ parser.set_usage(usage.replace('%s', '%prog'))
+ parser.add_option('-d', dest='dash_to_underscore', action="store_true",
+ default=False,
+ help='map "en-US" to "en" and "-" to "_" in locales')
+
+ (options, arglist) = parser.parse_args(argv)
+
+ if len(arglist) < 3:
+ print 'ERROR: need string and list of locales'
+ return 1
+
+ str_template = arglist[1]
+ locales = arglist[2:]
+
+ results = []
+ for locale in locales:
+ # For Cocoa to find the locale at runtime, it needs to use '_' instead
+ # of '-' (http://crbug.com/20441). Also, 'en-US' should be represented
+ # simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
+ if options.dash_to_underscore:
+ if locale == 'en-US':
+ locale = 'en'
+ locale = locale.replace('-', '_')
+ results.append(str_template.replace('ZZLOCALE', locale))
+
+ # Quote each element so filename spaces don't mess up GYP's attempt to parse
+ # it into a list.
+ print ' '.join(["'%s'" % x for x in results])
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/branding_value.sh b/build/branding_value.sh
new file mode 100755
index 00000000000..9fcb550caa2
--- /dev/null
+++ b/build/branding_value.sh
@@ -0,0 +1,51 @@
+#!/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 wrapper for fetching values from the BRANDING files. Pass the
+# value of GYP's branding variable followed by the key you want and the right
+# file is checked.
+#
+# branding_value.sh Chromium COPYRIGHT
+# branding_value.sh Chromium PRODUCT_FULLNAME
+#
+
+set -e
+
+if [ $# -ne 2 ] ; then
+ echo "error: expect two arguments, branding and key" >&2
+ exit 1
+fi
+
+BUILD_BRANDING=$1
+THE_KEY=$2
+
+pushd $(dirname "${0}") > /dev/null
+BUILD_DIR=$(pwd)
+popd > /dev/null
+
+TOP="${BUILD_DIR}/.."
+
+case ${BUILD_BRANDING} in
+ Chromium)
+ BRANDING_FILE="${TOP}/chrome/app/theme/chromium/BRANDING"
+ ;;
+ Chrome)
+ BRANDING_FILE="${TOP}/chrome/app/theme/google_chrome/BRANDING"
+ ;;
+ *)
+ echo "error: unknown branding: ${BUILD_BRANDING}" >&2
+ exit 1
+ ;;
+esac
+
+BRANDING_VALUE=$(sed -n -e "s/^${THE_KEY}=\(.*\)\$/\1/p" "${BRANDING_FILE}")
+
+if [ -z "${BRANDING_VALUE}" ] ; then
+ echo "error: failed to find key '${THE_KEY}'" >&2
+ exit 1
+fi
+
+echo "${BRANDING_VALUE}"
diff --git a/build/build-ctags.sh b/build/build-ctags.sh
new file mode 100755
index 00000000000..61e017e3298
--- /dev/null
+++ b/build/build-ctags.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# Copyright 2013 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.
+
+if [[ a"`ctags --version | head -1 | grep \"^Exuberant Ctags\"`" == "a" ]]; then
+ cat < /dev/null || fail $1
+ mv -f .tmp_tags tags
+}
+
+# We always build the top level but leave all submodules as optional.
+build_dir --extra-excludes "" "top level"
+
+# Build any other directies that are listed on the command line.
+for dir in $@; do
+ build_dir "$1"
+ shift
+done
diff --git a/build/build_config.h b/build/build_config.h
new file mode 100644
index 00000000000..d8c3db6ed78
--- /dev/null
+++ b/build/build_config.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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 file adds defines about the platform we're currently building on.
+// Operating System:
+// OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) /
+// OS_NACL (NACL_SFI or NACL_NONSFI) / OS_NACL_SFI / OS_NACL_NONSFI
+// Compiler:
+// COMPILER_MSVC / COMPILER_GCC
+// Processor:
+// ARCH_CPU_X86 / ARCH_CPU_X86_64 / ARCH_CPU_X86_FAMILY (X86 or X86_64)
+// ARCH_CPU_32_BITS / ARCH_CPU_64_BITS
+
+#ifndef BUILD_BUILD_CONFIG_H_
+#define BUILD_BUILD_CONFIG_H_
+
+// A set of macros to use for platform detection.
+#if defined(__native_client__)
+// __native_client__ must be first, so that other OS_ defines are not set.
+#define OS_NACL 1
+// OS_NACL comes in two sandboxing technology flavors, SFI or Non-SFI.
+// PNaCl toolchain defines __native_client_nonsfi__ macro in Non-SFI build
+// mode, while it does not in SFI build mode.
+#if defined(__native_client_nonsfi__)
+#define OS_NACL_NONSFI
+#else
+#define OS_NACL_SFI
+#endif
+#elif defined(ANDROID)
+#define OS_ANDROID 1
+#elif defined(__APPLE__)
+// only include TargetConditions after testing ANDROID as some android builds
+// on mac don't have this header available and it's not needed unless the target
+// is really mac/ios.
+#include
+#define OS_MACOSX 1
+#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+#define OS_IOS 1
+#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+#elif defined(__linux__)
+#define OS_LINUX 1
+// include a system header to pull in features.h for glibc/uclibc macros.
+#include
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+// we really are using glibc, not uClibc pretending to be glibc
+#define LIBC_GLIBC 1
+#endif
+#elif defined(_WIN32)
+#define OS_WIN 1
+#define TOOLKIT_VIEWS 1
+#elif defined(__FreeBSD__)
+#define OS_FREEBSD 1
+#elif defined(__OpenBSD__)
+#define OS_OPENBSD 1
+#elif defined(__sun)
+#define OS_SOLARIS 1
+#elif defined(__QNXNTO__)
+#define OS_QNX 1
+#else
+#error Please add support for your platform in build/build_config.h
+#endif
+
+#if defined(USE_OPENSSL_CERTS) && defined(USE_NSS_CERTS)
+#error Cannot use both OpenSSL and NSS for certificates
+#endif
+
+// For access to standard BSD features, use OS_BSD instead of a
+// more specific macro.
+#if defined(OS_FREEBSD) || defined(OS_OPENBSD)
+#define OS_BSD 1
+#endif
+
+// For access to standard POSIXish features, use OS_POSIX instead of a
+// more specific macro.
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_FREEBSD) || \
+ defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_ANDROID) || \
+ defined(OS_NACL) || defined(OS_QNX)
+#define OS_POSIX 1
+#endif
+
+// Use tcmalloc
+#if (defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID)) && \
+ !defined(NO_TCMALLOC)
+#define USE_TCMALLOC 1
+#endif
+
+// Compiler detection.
+#if defined(__GNUC__)
+#define COMPILER_GCC 1
+#elif defined(_MSC_VER)
+#define COMPILER_MSVC 1
+#else
+#error Please add support for your compiler in build/build_config.h
+#endif
+
+// Processor architecture detection. For more info on what's defined, see:
+// http://msdn.microsoft.com/en-us/library/b0084kay.aspx
+// http://www.agner.org/optimize/calling_conventions.pdf
+// or with gcc, run: "echo | gcc -E -dM -"
+#if defined(_M_X64) || defined(__x86_64__)
+#define ARCH_CPU_X86_FAMILY 1
+#define ARCH_CPU_X86_64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(_M_IX86) || defined(__i386__)
+#define ARCH_CPU_X86_FAMILY 1
+#define ARCH_CPU_X86 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__ARMEL__)
+#define ARCH_CPU_ARM_FAMILY 1
+#define ARCH_CPU_ARMEL 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__aarch64__)
+#define ARCH_CPU_ARM_FAMILY 1
+#define ARCH_CPU_ARM64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__pnacl__)
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__MIPSEL__)
+#if defined(__LP64__)
+#define ARCH_CPU_MIPS64_FAMILY 1
+#define ARCH_CPU_MIPS64EL 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#else
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPSEL 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#endif
+#else
+#error Please add support for your architecture in build/build_config.h
+#endif
+
+// Type detection for wchar_t.
+#if defined(OS_WIN)
+#define WCHAR_T_IS_UTF16
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
+ defined(__WCHAR_MAX__) && \
+ (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
+#define WCHAR_T_IS_UTF32
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
+ defined(__WCHAR_MAX__) && \
+ (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
+// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to
+// compile in this mode (in particular, Chrome doesn't). This is intended for
+// other projects using base who manage their own dependencies and make sure
+// short wchar works for them.
+#define WCHAR_T_IS_UTF16
+#else
+#error Please add support for your compiler in build/build_config.h
+#endif
+
+#if defined(OS_ANDROID)
+// The compiler thinks std::string::const_iterator and "const char*" are
+// equivalent types.
+#define STD_STRING_ITERATOR_IS_CHAR_POINTER
+// The compiler thinks base::string16::const_iterator and "char16*" are
+// equivalent types.
+#define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER
+#endif
+
+#endif // BUILD_BUILD_CONFIG_H_
diff --git a/build/check_return_value.py b/build/check_return_value.py
new file mode 100755
index 00000000000..c659d1e9674
--- /dev/null
+++ b/build/check_return_value.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# Copyright 2014 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 program wraps an arbitrary command and prints "1" if the command ran
+successfully."""
+
+import os
+import subprocess
+import sys
+
+devnull = open(os.devnull, 'wb')
+if not subprocess.call(sys.argv[1:], stdout=devnull, stderr=devnull):
+ print 1
+else:
+ print 0
diff --git a/build/check_sdk_extras_version.py b/build/check_sdk_extras_version.py
new file mode 100755
index 00000000000..9b2f10d2abc
--- /dev/null
+++ b/build/check_sdk_extras_version.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+'''Checks the status of an Android SDK package.
+
+Verifies the given package has been installed from the Android SDK Manager and
+that its version is at least the minimum version required by the project
+configuration.
+'''
+
+import argparse
+import json
+import os
+import re
+import sys
+
+
+COLORAMA_ROOT = os.path.join(os.path.dirname(__file__),
+ os.pardir, 'third_party', 'colorama', 'src')
+
+sys.path.append(COLORAMA_ROOT)
+import colorama
+
+
+UDPATE_SCRIPT_PATH = 'build/install-android-sdks.sh'
+
+SDK_EXTRAS_JSON_FILE = os.path.join(os.path.dirname(__file__),
+ 'android_sdk_extras.json')
+
+PACKAGE_VERSION_PATTERN = r'^Pkg\.Revision=(?P\d+).*$'
+
+PKG_NOT_FOUND_MSG = ('Error while checking Android SDK extras versions. '
+ 'Could not find the "{package_id}" package in '
+ '{checked_location}. Please run {script} to download it.')
+UPDATE_NEEDED_MSG = ('Error while checking Android SDK extras versions. '
+ 'Version {minimum_version} or greater is required for the '
+ 'package "{package_id}". Version {actual_version} found. '
+ 'Please run {script} to update it.')
+REQUIRED_VERSION_ERROR_MSG = ('Error while checking Android SDK extras '
+ 'versions. '
+ 'Could not retrieve the required version for '
+ 'package "{package_id}".')
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--package-id',
+ help=('id of the package to check for. The list of '
+ 'available packages and their ids can be obtained '
+ 'by running '
+ 'third_party/android_tools/sdk/tools/android list '
+ 'sdk --extended'))
+ parser.add_argument('--package-location',
+ help='path to the package\'s expected install location.',
+ metavar='DIR')
+ parser.add_argument('--stamp',
+ help=('if specified, a stamp file will be created at the '
+ 'provided location.'),
+ metavar='FILE')
+
+ args = parser.parse_args()
+
+ if not ShouldSkipVersionCheck():
+ minimum_version = GetRequiredMinimumVersion(args.package_id)
+ CheckPackageVersion(args.package_id, args.package_location, minimum_version)
+
+ # Create the stamp file.
+ if args.stamp:
+ with open(args.stamp, 'a'):
+ os.utime(args.stamp, None)
+
+ sys.exit(0)
+
+def ExitError(msg):
+ sys.exit(colorama.Fore.MAGENTA + colorama.Style.BRIGHT + msg +
+ colorama.Style.RESET_ALL)
+
+
+def GetRequiredMinimumVersion(package_id):
+ with open(SDK_EXTRAS_JSON_FILE, 'r') as json_file:
+ packages = json.load(json_file)
+
+ for package in packages:
+ if package['package_id'] == package_id:
+ return int(package['version'].split('.')[0])
+
+ ExitError(REQUIRED_VERSION_ERROR_MSG.format(package_id=package_id))
+
+
+def CheckPackageVersion(pkg_id, location, minimum_version):
+ version_file_path = os.path.join(location, 'source.properties')
+ # Extracts the version of the package described by the property file. We only
+ # care about the major version number here.
+ version_pattern = re.compile(PACKAGE_VERSION_PATTERN, re.MULTILINE)
+
+ if not os.path.isfile(version_file_path):
+ ExitError(PKG_NOT_FOUND_MSG.format(
+ package_id=pkg_id,
+ checked_location=location,
+ script=UDPATE_SCRIPT_PATH))
+
+ with open(version_file_path, 'r') as f:
+ match = version_pattern.search(f.read())
+
+ if not match:
+ ExitError(PKG_NOT_FOUND_MSG.format(
+ package_id=pkg_id,
+ checked_location=location,
+ script=UDPATE_SCRIPT_PATH))
+
+ pkg_version = int(match.group('version'))
+ if pkg_version < minimum_version:
+ ExitError(UPDATE_NEEDED_MSG.format(
+ package_id=pkg_id,
+ minimum_version=minimum_version,
+ actual_version=pkg_version,
+ script=UDPATE_SCRIPT_PATH))
+
+ # Everything looks ok, print nothing.
+
+def ShouldSkipVersionCheck():
+ '''
+ Bots should not run the version check, since they download the sdk extras
+ in a different way.
+ '''
+ return bool(os.environ.get('CHROME_HEADLESS'))
+
+if __name__ == '__main__':
+ main()
diff --git a/build/chrome_settings.gypi b/build/chrome_settings.gypi
new file mode 100644
index 00000000000..e9c753554de
--- /dev/null
+++ b/build/chrome_settings.gypi
@@ -0,0 +1,30 @@
+# Copyright 2013 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 file contains settings for ../chrome/chrome.gyp that other gyp files
+# also use.
+{
+ 'variables': {
+ # TODO: remove this helper when we have loops in GYP
+ 'apply_locales_cmd': ['python', '<(DEPTH)/build/apply_locales.py'],
+
+ 'conditions': [
+ ['OS=="mac"', {
+ 'conditions': [
+ ['branding=="Chrome"', {
+ 'mac_bundle_id': 'com.google.Chrome',
+ 'mac_creator': 'rimZ',
+ # The policy .grd file also needs the bundle id.
+ 'grit_defines': ['-D', 'mac_bundle_id=com.google.Chrome'],
+ }, { # else: branding!="Chrome"
+ 'mac_bundle_id': 'org.chromium.Chromium',
+ 'mac_creator': 'Cr24',
+ # The policy .grd file also needs the bundle id.
+ 'grit_defines': ['-D', 'mac_bundle_id=org.chromium.Chromium'],
+ }], # branding
+ ], # conditions
+ }], # OS=="mac"
+ ], # conditions
+ }, # variables
+}
diff --git a/build/clobber.py b/build/clobber.py
new file mode 100755
index 00000000000..785011a171d
--- /dev/null
+++ b/build/clobber.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# Copyright 2015 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 script provides methods for clobbering build directories."""
+
+import argparse
+import os
+import shutil
+import sys
+
+
+def extract_gn_build_commands(build_ninja_file):
+ """Extracts from a build.ninja the commands to run GN.
+
+ The commands to run GN are the gn rule and build.ninja build step at the
+ top of the build.ninja file. We want to keep these when deleting GN builds
+ since we want to preserve the command-line flags to GN.
+
+ On error, returns the empty string."""
+ result = ""
+ with open(build_ninja_file, 'r') as f:
+ # Read until the second blank line. The first thing GN writes to the file
+ # is the "rule gn" and the second is the section for "build build.ninja",
+ # separated by blank lines.
+ num_blank_lines = 0
+ while num_blank_lines < 2:
+ line = f.readline()
+ if len(line) == 0:
+ return '' # Unexpected EOF.
+ result += line
+ if line[0] == '\n':
+ num_blank_lines = num_blank_lines + 1
+ return result
+
+
+def delete_build_dir(build_dir):
+ # GN writes a build.ninja.d file. Note that not all GN builds have args.gn.
+ build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d')
+ if not os.path.exists(build_ninja_d_file):
+ shutil.rmtree(build_dir)
+ return
+
+ # GN builds aren't automatically regenerated when you sync. To avoid
+ # messing with the GN workflow, erase everything but the args file, and
+ # write a dummy build.ninja file that will automatically rerun GN the next
+ # time Ninja is run.
+ build_ninja_file = os.path.join(build_dir, 'build.ninja')
+ build_commands = extract_gn_build_commands(build_ninja_file)
+
+ try:
+ gn_args_file = os.path.join(build_dir, 'args.gn')
+ with open(gn_args_file, 'r') as f:
+ args_contents = f.read()
+ except IOError:
+ args_contents = ''
+
+ shutil.rmtree(build_dir)
+
+ # Put back the args file (if any).
+ os.mkdir(build_dir)
+ if args_contents != '':
+ with open(gn_args_file, 'w') as f:
+ f.write(args_contents)
+
+ # Write the build.ninja file sufficiently to regenerate itself.
+ with open(os.path.join(build_dir, 'build.ninja'), 'w') as f:
+ if build_commands != '':
+ f.write(build_commands)
+ else:
+ # Couldn't parse the build.ninja file, write a default thing.
+ f.write('''rule gn
+command = gn -q gen //out/%s/
+description = Regenerating ninja files
+
+build build.ninja: gn
+generator = 1
+depfile = build.ninja.d
+''' % (os.path.split(build_dir)[1]))
+
+ # Write a .d file for the build which references a nonexistant file. This
+ # will make Ninja always mark the build as dirty.
+ with open(build_ninja_d_file, 'w') as f:
+ f.write('build.ninja: nonexistant_file.gn\n')
+
+
+def clobber(out_dir):
+ """Clobber contents of build directory.
+
+ Don't delete the directory itself: some checkouts have the build directory
+ mounted."""
+ for f in os.listdir(out_dir):
+ path = os.path.join(out_dir, f)
+ if os.path.isfile(path):
+ os.unlink(path)
+ elif os.path.isdir(path):
+ delete_build_dir(path)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('out_dir', help='The output directory to clobber')
+ args = parser.parse_args()
+ clobber(args.out_dir)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/common.croc b/build/common.croc
new file mode 100644
index 00000000000..fde7a8b2982
--- /dev/null
+++ b/build/common.croc
@@ -0,0 +1,127 @@
+# -*- python -*-
+# Crocodile config file for Chromium - settings common to all platforms
+#
+# This should be speicified before the platform-specific config, for example:
+# croc -c chrome_common.croc -c linux/chrome_linux.croc
+
+{
+ # List of root directories, applied in order
+ 'roots' : [
+ # Sub-paths we specifically care about and want to call out
+ {
+ 'root' : '_/src',
+ 'altname' : 'CHROMIUM',
+ },
+ ],
+
+ # List of rules, applied in order
+ # Note that any 'include':0 rules here will be overridden by the 'include':1
+ # rules in the platform-specific configs.
+ 'rules' : [
+ # Don't scan for executable lines in uninstrumented C++ header files
+ {
+ 'regexp' : '.*\\.(h|hpp)$',
+ 'add_if_missing' : 0,
+ },
+
+ # Groups
+ {
+ 'regexp' : '',
+ 'group' : 'source',
+ },
+ {
+ 'regexp' : '.*_(test|unittest|uitest|browsertest)\\.',
+ 'group' : 'test',
+ },
+
+ # Languages
+ {
+ 'regexp' : '.*\\.(c|h)$',
+ 'language' : 'C',
+ },
+ {
+ 'regexp' : '.*\\.(cc|cpp|hpp)$',
+ 'language' : 'C++',
+ },
+
+ # Files/paths to include. Specify these before the excludes, since rules
+ # are in order.
+ {
+ 'regexp' : '^CHROMIUM/(base|media|net|printing|remoting|chrome|content|webkit/glue|native_client)/',
+ 'include' : 1,
+ },
+ # Don't include subversion or mercurial SCM dirs
+ {
+ 'regexp' : '.*/(\\.svn|\\.hg)/',
+ 'include' : 0,
+ },
+ # Don't include output dirs
+ {
+ 'regexp' : '.*/(Debug|Release|out|xcodebuild)/',
+ 'include' : 0,
+ },
+ # Don't include third-party source
+ {
+ 'regexp' : '.*/third_party/',
+ 'include' : 0,
+ },
+ # We don't run the V8 test suite, so we don't care about V8 coverage.
+ {
+ 'regexp' : '.*/v8/',
+ 'include' : 0,
+ },
+ ],
+
+ # Paths to add source from
+ 'add_files' : [
+ 'CHROMIUM'
+ ],
+
+ # Statistics to print
+ 'print_stats' : [
+ {
+ 'stat' : 'files_executable',
+ 'format' : '*RESULT FilesKnown: files_executable= %d files',
+ },
+ {
+ 'stat' : 'files_instrumented',
+ 'format' : '*RESULT FilesInstrumented: files_instrumented= %d files',
+ },
+ {
+ 'stat' : '100.0 * files_instrumented / files_executable',
+ 'format' : '*RESULT FilesInstrumentedPercent: files_instrumented_percent= %g percent',
+ },
+ {
+ 'stat' : 'lines_executable',
+ 'format' : '*RESULT LinesKnown: lines_known= %d lines',
+ },
+ {
+ 'stat' : 'lines_instrumented',
+ 'format' : '*RESULT LinesInstrumented: lines_instrumented= %d lines',
+ },
+ {
+ 'stat' : 'lines_covered',
+ 'format' : '*RESULT LinesCoveredSource: lines_covered_source= %d lines',
+ 'group' : 'source',
+ },
+ {
+ 'stat' : 'lines_covered',
+ 'format' : '*RESULT LinesCoveredTest: lines_covered_test= %d lines',
+ 'group' : 'test',
+ },
+ {
+ 'stat' : '100.0 * lines_covered / lines_executable',
+ 'format' : '*RESULT PercentCovered: percent_covered= %g percent',
+ },
+ {
+ 'stat' : '100.0 * lines_covered / lines_executable',
+ 'format' : '*RESULT PercentCoveredSource: percent_covered_source= %g percent',
+ 'group' : 'source',
+ },
+ {
+ 'stat' : '100.0 * lines_covered / lines_executable',
+ 'format' : '*RESULT PercentCoveredTest: percent_covered_test= %g percent',
+ 'group' : 'test',
+ },
+ ],
+}
diff --git a/build/common.gypi b/build/common.gypi
new file mode 100644
index 00000000000..3a2df58447c
--- /dev/null
+++ b/build/common.gypi
@@ -0,0 +1,6216 @@
+# Copyright (c) 2012 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.
+
+# IMPORTANT:
+# Please don't directly include this file if you are building via gyp_chromium,
+# since gyp_chromium is automatically forcing its inclusion.
+{
+ # Variables expected to be overriden on the GYP command line (-D) or by
+ # ~/.gyp/include.gypi.
+ 'variables': {
+ # Putting a variables dict inside another variables dict looks kind of
+ # weird. This is done so that 'host_arch', 'chromeos', etc are defined as
+ # variables within the outer variables dict here. This is necessary
+ # to get these variables defined for the conditions within this variables
+ # dict that operate on these variables.
+ 'variables': {
+ 'variables': {
+ 'variables': {
+ 'variables': {
+ # Whether we're building a ChromeOS build.
+ 'chromeos%': 0,
+
+ # Whether we're building the cast (chromecast) shell
+ 'chromecast%': 0,
+
+ # Whether or not we are using the Aura windowing framework.
+ 'use_aura%': 0,
+
+ # Whether or not we are building the Ash shell.
+ 'use_ash%': 0,
+
+ # Whether or not we are using CRAS, the ChromeOS Audio Server.
+ 'use_cras%': 0,
+
+ # Use a raw surface abstraction.
+ 'use_ozone%': 0,
+
+ # Configure the build for small devices. See crbug.com/318413
+ 'embedded%': 0,
+
+ 'conditions': [
+ # Compute the architecture that we're building on.
+ ['OS=="win" or OS=="ios"', {
+ 'host_arch%': 'ia32',
+ }, {
+ 'host_arch%': '= 4.8 or clang.
+ # http://gcc.gnu.org/wiki/DebugFission
+ ['OS=="linux" and target_arch=="x64"', {
+ 'linux_use_debug_fission%': 1,
+ }, {
+ 'linux_use_debug_fission%': 0,
+ }],
+
+ ['OS=="android" or OS=="ios"', {
+ 'enable_captive_portal_detection%': 0,
+ 'enable_media_router%': 0,
+ }, {
+ 'enable_captive_portal_detection%': 1,
+ 'enable_media_router%': 1,
+ }],
+
+ # Enable Skia UI text drawing incrementally on different platforms.
+ # http://crbug.com/105550
+ #
+ # On Aura, this allows per-tile painting to be used in the browser
+ # compositor.
+ ['OS!="android" and OS!="ios"', {
+ 'use_canvas_skia%': 1,
+ }],
+
+ ['chromeos==1', {
+ 'enable_basic_printing%': 0,
+ 'enable_print_preview%': 1,
+ }],
+
+ # Do not enable the Settings App on ChromeOS.
+ ['enable_app_list==1 and chromeos==0', {
+ 'enable_settings_app%': 1,
+ }, {
+ 'enable_settings_app%': 0,
+ }],
+
+ # Whether tests targets should be run, archived or just have the
+ # dependencies verified. All the tests targets have the '_run' suffix,
+ # e.g. base_unittests_run runs the target base_unittests. The test
+ # target always calls tools/swarming_client/isolate.py. See the script's
+ # --help for more information. Meant to be overriden with GYP_DEFINES.
+ # TODO(maruel): Remove the conditions as more configurations are
+ # supported.
+ ['OS!="ios" and OS!="android" and chromeos==0', {
+ 'test_isolation_mode%': 'check',
+ }, {
+ 'test_isolation_mode%': 'noop',
+ }],
+ # Whether Android build uses OpenMAX DL FFT.
+ ['OS=="android" and ((target_arch=="arm" and arm_version >= 7) or target_arch=="ia32" or target_arch=="x64" or target_arch=="arm64" or target_arch=="mipsel")', {
+ # Currently only supported on Android ARMv7+, ARM64, ia32, x64 and mipsel.
+ # When enabled, this will also enable WebAudio support on
+ # Android for these architectures. Default is enabled. Whether
+ # WebAudio is actually available depends on runtime settings
+ # and flags.
+ 'use_openmax_dl_fft%': 1,
+ }, {
+ 'use_openmax_dl_fft%': 0,
+ }],
+ ['OS=="win" or OS=="linux"', {
+ 'enable_mdns%' : 1,
+ }],
+
+ # Disable various features by default on embedded.
+ ['embedded==1', {
+ 'remoting%': 0,
+ 'enable_basic_printing%': 0,
+ 'enable_print_preview%': 0,
+ }],
+
+ ['OS=="win" or OS=="mac"', {
+ 'enable_wifi_bootstrapping%' : 1,
+ }],
+
+ # Path to sas.dll, which provides the SendSAS function.
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/dd979761(v=vs.85).aspx
+ ['target_arch=="x64"', {
+ 'sas_dll_path%': '<(DEPTH)/third_party/platformsdk_win7/files/redist/amd64',
+ }, {
+ 'sas_dll_path%': '<(DEPTH)/third_party/platformsdk_win7/files/redist/x86',
+ }],
+
+ ['sysroot!=""', {
+ 'pkg-config': '<(chroot_cmd) <(DEPTH)/build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)" "<(system_libdir)"',
+ }, {
+ 'pkg-config': 'pkg-config'
+ }],
+ ],
+
+ # WebVR support disabled until platform implementations have been added
+ 'enable_webvr%': 0,
+
+ # Setting this to '0' will cause V8's startup snapshot to be
+ # embedded in the binary instead of being a external files.
+ 'v8_use_external_startup_data%': 1,
+
+ # Set this to 1 to enable use of concatenated impulse responses
+ # for the HRTF panner in WebAudio.
+ 'use_concatenated_impulse_responses': 1,
+
+ # You can set the variable 'use_official_google_api_keys' to 1
+ # to use the Google-internal file containing official API keys
+ # for Google Chrome even in a developer build. Setting this
+ # variable explicitly to 1 will cause your build to fail if the
+ # internal file is missing.
+ #
+ # The variable is documented here, but not handled in this file;
+ # see //google_apis/determine_use_official_keys.gypi for the
+ # implementation.
+ #
+ # Set the variable to 0 to not use the internal file, even when
+ # it exists in your checkout.
+ #
+ # Leave it unset in your include.gypi to have the variable
+ # implicitly set to 1 if you have
+ # src/google_apis/internal/google_chrome_api_keys.h in your
+ # checkout, and implicitly set to 0 if not.
+ #
+ # Note that official builds always behave as if the variable
+ # was explicitly set to 1, i.e. they always use official keys,
+ # and will fail to build if the internal file is missing.
+ #
+ # NOTE: You MUST NOT explicitly set the variable to 2 in your
+ # include.gypi or by other means. Due to subtleties of GYP, this
+ # is not the same as leaving the variable unset, even though its
+ # default value in
+ # //google_apis/determine_use_official_keys.gypi is 2.
+
+ # Set these to bake the specified API keys and OAuth client
+ # IDs/secrets into your build.
+ #
+ # If you create a build without values baked in, you can instead
+ # set environment variables to provide the keys at runtime (see
+ # src/google_apis/google_api_keys.h for details). Features that
+ # require server-side APIs may fail to work if no keys are
+ # provided.
+ #
+ # Note that if you are building an official build or if
+ # use_official_google_api_keys has been set to 1 (explicitly or
+ # implicitly), these values will be ignored and the official
+ # keys will be used instead.
+ 'google_api_key%': '',
+ 'google_default_client_id%': '',
+ 'google_default_client_secret%': '',
+ # Native Client is enabled by default.
+ 'disable_nacl%': '0',
+
+ # Sets the default version name and code for Android app, by default we
+ # do a developer build.
+ 'android_app_version_name%': 'Developer Build',
+ 'android_app_version_code%': 1,
+ },
+
+ # Copy conditionally-set variables out one scope.
+ 'branding%': '<(branding)',
+ 'branding_path_component%': '<(branding_path_component)',
+ 'buildtype%': '<(buildtype)',
+ 'target_arch%': '<(target_arch)',
+ 'target_subarch%': '<(target_subarch)',
+ 'mips_arch_variant%': '<(mips_arch_variant)',
+ 'mips_dsp_rev%': '<(mips_dsp_rev)',
+ 'host_arch%': '<(host_arch)',
+ 'toolkit_views%': '<(toolkit_views)',
+ 'ui_compositor_image_transport%': '<(ui_compositor_image_transport)',
+ 'use_aura%': '<(use_aura)',
+ 'use_ash%': '<(use_ash)',
+ 'use_cras%': '<(use_cras)',
+ 'use_libpci%': '<(use_libpci)',
+ 'use_openssl%': '<(use_openssl)',
+ 'use_openssl_certs%': '<(use_openssl_certs)',
+ 'use_nss_certs%': '<(use_nss_certs)',
+ 'use_udev%': '<(use_udev)',
+ 'os_bsd%': '<(os_bsd)',
+ 'os_posix%': '<(os_posix)',
+ 'use_dbus%': '<(use_dbus)',
+ 'use_glib%': '<(use_glib)',
+ 'use_pango%': '<(use_pango)',
+ 'use_cairo%': '<(use_cairo)',
+ 'use_ozone%': '<(use_ozone)',
+ 'use_ozone_evdev%': '<(use_ozone_evdev)',
+ 'use_xkbcommon%': '<(use_xkbcommon)',
+ 'use_clipboard_aurax11%': '<(use_clipboard_aurax11)',
+ 'desktop_linux%': '<(desktop_linux)',
+ 'use_x11%': '<(use_x11)',
+ 'use_gnome_keyring%': '<(use_gnome_keyring)',
+ 'linux_fpic%': '<(linux_fpic)',
+ 'chromeos%': '<(chromeos)',
+ 'chromecast%': '<(chromecast)',
+ 'enable_viewport%': '<(enable_viewport)',
+ 'enable_hidpi%': '<(enable_hidpi)',
+ 'enable_topchrome_md%': '<(enable_topchrome_md)',
+ 'image_loader_extension%': '<(image_loader_extension)',
+ 'fastbuild%': '<(fastbuild)',
+ 'dont_embed_build_metadata%': '<(dont_embed_build_metadata)',
+ 'win_z7%': '<(win_z7)',
+ 'dcheck_always_on%': '<(dcheck_always_on)',
+ 'tracing_like_official_build%': '<(tracing_like_official_build)',
+ 'arm_version%': '<(arm_version)',
+ 'arm_neon%': '<(arm_neon)',
+ 'arm_neon_optional%': '<(arm_neon_optional)',
+ 'sysroot%': '<(sysroot)',
+ 'pkg-config%': '<(pkg-config)',
+ 'chroot_cmd%': '<(chroot_cmd)',
+ 'system_libdir%': '<(system_libdir)',
+ 'component%': '<(component)',
+ 'win_analyze%': '<(win_analyze)',
+ 'enable_resource_whitelist_generation%': '<(enable_resource_whitelist_generation)',
+ 'use_titlecase_in_grd%': '<(use_titlecase_in_grd)',
+ 'use_third_party_translations%': '<(use_third_party_translations)',
+ 'remoting%': '<(remoting)',
+ 'enable_one_click_signin%': '<(enable_one_click_signin)',
+ 'enable_pre_sync_backup%': '<(enable_pre_sync_backup)',
+ 'enable_media_router%': '<(enable_media_router)',
+ 'enable_webrtc%': '<(enable_webrtc)',
+ 'chromium_win_pch%': '<(chromium_win_pch)',
+ 'configuration_policy%': '<(configuration_policy)',
+ 'safe_browsing%': '<(safe_browsing)',
+ 'enable_web_speech%': '<(enable_web_speech)',
+ 'enable_hotwording%': '<(enable_hotwording)',
+ 'notifications%': '<(notifications)',
+ 'clang_use_chrome_plugins%': '<(clang_use_chrome_plugins)',
+ 'mac_want_real_dsym%': '<(mac_want_real_dsym)',
+ 'asan%': '<(asan)',
+ 'asan_blacklist%': '<(asan_blacklist)',
+ 'asan_coverage%': '<(asan_coverage)',
+ 'sanitizer_coverage%': '<(sanitizer_coverage)',
+ 'asan_field_padding%': '<(asan_field_padding)',
+ 'use_sanitizer_options%': '<(use_sanitizer_options)',
+ 'syzyasan%': '<(syzyasan)',
+ 'kasko%': '<(kasko)',
+ 'syzygy_optimize%': '<(syzygy_optimize)',
+ 'lsan%': '<(lsan)',
+ 'msan%': '<(msan)',
+ 'msan_blacklist%': '<(msan_blacklist)',
+ 'msan_track_origins%': '<(msan_track_origins)',
+ 'tsan%': '<(tsan)',
+ 'tsan_blacklist%': '<(tsan_blacklist)',
+ 'ubsan%': '<(ubsan)',
+ 'ubsan_blacklist%': '<(ubsan_blacklist)',
+ 'ubsan_vptr_blacklist%': '<(ubsan_vptr_blacklist)',
+ 'ubsan_vptr%': '<(ubsan_vptr)',
+ 'use_instrumented_libraries%': '<(use_instrumented_libraries)',
+ 'use_prebuilt_instrumented_libraries%': '<(use_prebuilt_instrumented_libraries)',
+ 'use_custom_libcxx%': '<(use_custom_libcxx)',
+ 'use_system_libcxx%': '<(use_system_libcxx)',
+ 'clang_type_profiler%': '<(clang_type_profiler)',
+ 'order_profiling%': '<(order_profiling)',
+ 'order_text_section%': '<(order_text_section)',
+ 'enable_extensions%': '<(enable_extensions)',
+ 'enable_plugin_installation%': '<(enable_plugin_installation)',
+ 'enable_plugins%': '<(enable_plugins)',
+ 'enable_session_service%': '<(enable_session_service)',
+ 'enable_themes%': '<(enable_themes)',
+ 'enable_autofill_dialog%': '<(enable_autofill_dialog)',
+ 'enable_prod_wallet_service%': '<(enable_prod_wallet_service)',
+ 'enable_background%': '<(enable_background)',
+ 'linux_use_bundled_gold%': '<(linux_use_bundled_gold)',
+ 'linux_use_bundled_binutils%': '<(linux_use_bundled_binutils)',
+ 'linux_use_gold_flags%': '<(linux_use_gold_flags)',
+ 'linux_use_debug_fission%': '<(linux_use_debug_fission)',
+ 'use_canvas_skia%': '<(use_canvas_skia)',
+ 'test_isolation_mode%': '<(test_isolation_mode)',
+ 'enable_basic_printing%': '<(enable_basic_printing)',
+ 'enable_print_preview%': '<(enable_print_preview)',
+ 'enable_spellcheck%': '<(enable_spellcheck)',
+ 'use_platform_spellchecker%': '<(use_platform_spellchecker)',
+ 'enable_google_now%': '<(enable_google_now)',
+ 'cld_version%': '<(cld_version)',
+ 'cld2_table_size%': '<(cld2_table_size)',
+ 'enable_captive_portal_detection%': '<(enable_captive_portal_detection)',
+ 'disable_file_support%': '<(disable_file_support)',
+ 'disable_ftp_support%': '<(disable_ftp_support)',
+ 'use_icu_alternatives_on_android%': '<(use_icu_alternatives_on_android)',
+ 'enable_task_manager%': '<(enable_task_manager)',
+ 'sas_dll_path%': '<(sas_dll_path)',
+ 'wix_path%': '<(wix_path)',
+ 'use_libjpeg_turbo%': '<(use_libjpeg_turbo)',
+ 'use_system_libjpeg%': '<(use_system_libjpeg)',
+ 'android_channel%': '<(android_channel)',
+ 'icu_use_data_file_flag%': '<(icu_use_data_file_flag)',
+ 'gyp_managed_install%': 0,
+ 'create_standalone_apk%': 1,
+ 'enable_app_list%': '<(enable_app_list)',
+ 'use_default_render_theme%': '<(use_default_render_theme)',
+ 'enable_settings_app%': '<(enable_settings_app)',
+ 'google_api_key%': '<(google_api_key)',
+ 'google_default_client_id%': '<(google_default_client_id)',
+ 'google_default_client_secret%': '<(google_default_client_secret)',
+ 'enable_supervised_users%': '<(enable_supervised_users)',
+ 'native_memory_pressure_signals%': '<(native_memory_pressure_signals)',
+ 'enable_mdns%' : '<(enable_mdns)',
+ 'enable_service_discovery%' : '<(enable_service_discovery)',
+ 'enable_wifi_bootstrapping%': '<(enable_wifi_bootstrapping)',
+ 'enable_hangout_services_extension%' : '<(enable_hangout_services_extension)',
+ 'proprietary_codecs%': '<(proprietary_codecs)',
+ 'use_goma%': '<(use_goma)',
+ 'gomadir%': '<(gomadir)',
+ 'use_lto%': '<(use_lto)',
+ 'use_lto_o2%': '<(use_lto_o2)',
+ 'gold_icf_level%': '<(gold_icf_level)',
+ 'video_hole%': '<(video_hole)',
+ 'v8_use_external_startup_data%': '<(v8_use_external_startup_data)',
+ 'cfi_vptr%': '<(cfi_vptr)',
+ 'cfi_blacklist%': '<(cfi_blacklist)',
+ 'mac_views_browser%': '<(mac_views_browser)',
+ 'android_app_version_name%': '<(android_app_version_name)',
+ 'android_app_version_code%': '<(android_app_version_code)',
+ 'enable_webvr%': '<(enable_webvr)',
+
+ # Turns on compiler optimizations in V8 in Debug build.
+ 'v8_optimized_debug%': 1,
+
+ # Use system protobuf instead of bundled one.
+ 'use_system_protobuf%': 0,
+
+ # Use system yasm instead of bundled one.
+ 'use_system_yasm%': 0,
+
+ # Use system ICU instead of bundled one.
+ 'use_system_icu%' : 0,
+
+ # Default to enabled PIE; this is important for ASLR but we may need to be
+ # able to turn it off for various reasons.
+ 'linux_disable_pie%': 0,
+
+ # The release channel that this build targets. This is used to restrict
+ # channel-specific build options, like which installer packages to create.
+ # The default is 'all', which does no channel-specific filtering.
+ 'channel%': 'all',
+
+ # Override chromium_mac_pch and set it to 0 to suppress the use of
+ # precompiled headers on the Mac. Prefix header injection may still be
+ # used, but prefix headers will not be precompiled. This is useful when
+ # using distcc to distribute a build to compile slaves that don't
+ # share the same compiler executable as the system driving the compilation,
+ # because precompiled headers rely on pointers into a specific compiler
+ # executable's image. Setting this to 0 is needed to use an experimental
+ # Linux-Mac cross compiler distcc farm.
+ 'chromium_mac_pch%': 1,
+
+ # The default value for mac_strip in target_defaults. This cannot be
+ # set there, per the comment about variable% in a target_defaults.
+ 'mac_strip_release%': 0,
+
+ # Set to 1 to enable java code coverage. Instruments classes during build
+ # to produce .ec files during runtime.
+ 'emma_coverage%': 0,
+
+ # EMMA filter string consisting of a list of inclusion/exclusion patterns
+ # separated with whitespace and/or comma. Only has effect if
+ # 'emma_coverage=1'.
+ 'emma_filter%': '',
+
+ # Set to 1 to enable running Android lint on java/class files.
+ 'android_lint%': 1,
+
+ # Although base/allocator lets you select a heap library via an
+ # environment variable, the libcmt shim it uses sometimes gets in
+ # the way. To disable it entirely, and switch to normal msvcrt, do e.g.
+ # 'win_use_allocator_shim': 0,
+ # 'win_release_RuntimeLibrary': 2
+ # to ~/.gyp/include.gypi, gclient runhooks --force, and do a release build.
+ 'win_use_allocator_shim%': 1, # 1 = shim allocator via libcmt; 0 = msvcrt
+
+ # TODO(bradnelson): eliminate this when possible.
+ # To allow local gyp files to prevent release.vsprops from being included.
+ # Yes(1) means include release.vsprops.
+ # Once all vsprops settings are migrated into gyp, this can go away.
+ 'msvs_use_common_release%': 1,
+
+ # TODO(bradnelson): eliminate this when possible.
+ # To allow local gyp files to override additional linker options for msvs.
+ # Yes(1) means set use the common linker options.
+ 'msvs_use_common_linker_extras%': 1,
+
+ # TODO(sgk): eliminate this if possible.
+ # It would be nicer to support this via a setting in 'target_defaults'
+ # in chrome/app/locales/locales.gypi overriding the setting in the
+ # 'Debug' configuration in the 'target_defaults' dict below,
+ # but that doesn't work as we'd like.
+ 'msvs_debug_link_incremental%': '2',
+
+ # Needed for some of the largest modules.
+ 'msvs_debug_link_nonincremental%': '1',
+
+ # Turns on Use Library Dependency Inputs for linking chrome.dll on Windows
+ # to get incremental linking to be faster in debug builds.
+ 'incremental_chrome_dll%': '0',
+
+ # Experimental setting to break chrome.dll into multiple pieces based on
+ # process type.
+ 'chrome_multiple_dll%': '0',
+
+ # Experimental setting to optimize Chrome's DLLs with PGO.
+ 'chrome_pgo_phase%': '0',
+
+ # Whether the VS xtree header has been patched to disable warning 4702. If
+ # it has, then we don't need to disable 4702 (unreachable code warning).
+ # The patch is preapplied to the internal toolchain and hence all bots.
+ 'msvs_xtree_patched%': '. Additional
+ # documentation on these macros is available at
+ # http://developer.apple.com/mac/library/technotes/tn2002/tn2064.html#SECTION3
+ # Chrome normally builds with the Mac OS X 10.6 SDK and sets the
+ # deployment target to 10.6. Other projects, such as O3D, may
+ # override these defaults.
+
+ # Normally, mac_sdk_min is used to find an SDK that Xcode knows
+ # about that is at least the specified version. In official builds,
+ # the SDK must match mac_sdk_min exactly. If the SDK is installed
+ # someplace that Xcode doesn't know about, set mac_sdk_path to the
+ # path to the SDK; when set to a non-empty string, SDK detection
+ # based on mac_sdk_min will be bypassed entirely.
+ 'conditions': [
+ ['OS=="ios"', {
+ 'mac_sdk_min%': '10.8',
+ }, { # else OS!="ios"
+ 'mac_sdk_min%': '10.6',
+ }],
+ ],
+ 'mac_sdk_path%': '',
+
+ 'mac_deployment_target%': '10.6',
+ },
+
+ 'mac_sdk_min': '<(mac_sdk_min)',
+ 'mac_sdk_path': '<(mac_sdk_path)',
+ 'mac_deployment_target': '<(mac_deployment_target)',
+
+ # Compile in Breakpad support by default so that it can be
+ # tested, even if it is not enabled by default at runtime.
+ 'mac_breakpad_compiled_in%': 1,
+ 'conditions': [
+ # mac_product_name is set to the name of the .app bundle as it should
+ # appear on disk. This duplicates data from
+ # chrome/app/theme/chromium/BRANDING and
+ # chrome/app/theme/google_chrome/BRANDING, but is necessary to get
+ # these names into the build system.
+ ['branding=="Chrome"', {
+ 'mac_product_name%': 'Google Chrome',
+ }, { # else: branding!="Chrome"
+ 'mac_product_name%': 'Chromium',
+ }],
+ # Official mac builds require a specific OS X SDK, but iOS and
+ # non-official mac builds do not.
+ ['branding=="Chrome" and buildtype=="Official" and OS=="mac"', {
+ 'mac_sdk%': '= 7.0)}\')==1', {
+ 'clang_xcode%': 1,
+ }],
+ ],
+ }],
+ ['enable_extensions==1', {
+ 'grit_defines': ['-D', 'enable_extensions'],
+ }],
+ ['enable_plugins!=0', {
+ 'grit_defines': ['-D', 'enable_plugins'],
+ }],
+ ['enable_basic_printing==1 or enable_print_preview==1', {
+ 'grit_defines': ['-D', 'enable_printing'],
+ }],
+ ['enable_print_preview==1', {
+ 'grit_defines': ['-D', 'enable_print_preview'],
+ }],
+ ['enable_themes==1', {
+ 'grit_defines': ['-D', 'enable_themes'],
+ }],
+ ['enable_app_list==1', {
+ 'grit_defines': ['-D', 'enable_app_list'],
+ }],
+ ['enable_settings_app==1', {
+ 'grit_defines': ['-D', 'enable_settings_app'],
+ }],
+ ['enable_google_now==1', {
+ 'grit_defines': ['-D', 'enable_google_now'],
+ }],
+ ['use_concatenated_impulse_responses==1', {
+ 'grit_defines': ['-D', 'use_concatenated_impulse_responses'],
+ }],
+ ['enable_media_router==1', {
+ 'grit_defines': ['-D', 'enable_media_router'],
+ }],
+ ['enable_webrtc==1', {
+ 'grit_defines': ['-D', 'enable_webrtc'],
+ }],
+ ['enable_hangout_services_extension==1', {
+ 'grit_defines': ['-D', 'enable_hangout_services_extension'],
+ }],
+ ['enable_task_manager==1', {
+ 'grit_defines': ['-D', 'enable_task_manager'],
+ }],
+ ['notifications==1', {
+ 'grit_defines': ['-D', 'enable_notifications'],
+ }],
+ ['enable_wifi_bootstrapping==1', {
+ 'grit_defines': ['-D', 'enable_wifi_bootstrapping'],
+ }],
+ ['mac_views_browser==1', {
+ 'grit_defines': ['-D', 'mac_views_browser'],
+ }],
+ ['enable_resource_whitelist_generation==1 and OS!="win"', {
+ 'grit_rc_header_format': ['-h', '#define {textual_id} _Pragma("whitelisted_resource_{numeric_id}") {numeric_id}'],
+ }],
+ ['enable_resource_whitelist_generation==1 and OS=="win"', {
+ 'grit_rc_header_format': ['-h', '#define {textual_id} __pragma(message("whitelisted_resource_{numeric_id}")) {numeric_id}'],
+ }],
+ ['enable_mdns==1 or OS=="mac"', {
+ 'grit_defines': ['-D', 'enable_service_discovery'],
+ 'enable_service_discovery%': 1
+ }],
+ ['clang_use_chrome_plugins==1', {
+ 'variables': {
+ 'conditions': [
+ ['OS!="win"', {
+ 'variables': {
+ 'conditions': [
+ ['OS=="mac" or OS=="ios"', {
+ 'clang_lib_path%': '(nacl_untrusted_build)==1', {
+ 'defines': [
+ 'USE_OPENSSL=1',
+ 'USE_OPENSSL_CERTS=1',
+ ],
+ }],
+ ['<(use_glib)==1 and >(nacl_untrusted_build)==0', {
+ 'defines': ['USE_GLIB=1'],
+ }],
+ ['<(use_nss_certs)==1 and >(nacl_untrusted_build)==0', {
+ 'defines': ['USE_NSS_CERTS=1'],
+ }],
+ ['<(chromeos)==1 and >(nacl_untrusted_build)==0', {
+ 'defines': ['OS_CHROMEOS=1'],
+ }],
+ ['enable_wexit_time_destructors==1 and OS!="win"', {
+ # TODO: Enable on Windows too, http://crbug.com/404525
+ 'variables': { 'clang_warning_flags': ['-Wexit-time-destructors']},
+ }],
+ ['chromium_code==0', {
+ 'variables': {
+ 'clang_warning_flags': [
+ # TODO(mgiuca): Move this suppression into individual third-party
+ # libraries as required. http://crbug.com/505301.
+ '-Wno-overloaded-virtual',
+ # Lots of third-party libraries have unused variables. Instead of
+ # suppressing them individually, we just blanket suppress them here.
+ '-Wno-unused-variable',
+ ],
+ },
+ 'conditions': [
+ [ 'os_posix==1 and OS!="mac" and OS!="ios"', {
+ # We don't want to get warnings from third-party code,
+ # so remove any existing warning-enabling flags like -Wall.
+ 'cflags!': [
+ '-Wall',
+ '-Wextra',
+ ],
+ 'cflags_cc': [
+ # Don't warn about hash_map in third-party code.
+ '-Wno-deprecated',
+ ],
+ 'cflags': [
+ # Don't warn about printf format problems.
+ # This is off by default in gcc but on in Ubuntu's gcc(!).
+ '-Wno-format',
+ ],
+ 'cflags_cc!': [
+ # Necessary because llvm.org/PR10448 is WONTFIX (crbug.com/90453).
+ '-Wsign-compare',
+ ]
+ }],
+ # TODO: Fix all warnings on chromeos too.
+ [ 'os_posix==1 and OS!="mac" and OS!="ios" and (clang!=1 or chromeos==1)', {
+ 'cflags!': [
+ '-Werror',
+ ],
+ }],
+ [ 'os_posix==1 and os_bsd!=1 and OS!="mac" and OS!="android"', {
+ 'cflags': [
+ # Don't warn about ignoring the return value from e.g. close().
+ # This is off by default in some gccs but on by default in others.
+ # BSD systems do not support this option, since they are usually
+ # using gcc 4.2.1, which does not have this flag yet.
+ '-Wno-unused-result',
+ ],
+ }],
+ [ 'OS=="win"', {
+ 'defines': [
+ '_CRT_SECURE_NO_DEPRECATE',
+ '_CRT_NONSTDC_NO_WARNINGS',
+ '_CRT_NONSTDC_NO_DEPRECATE',
+ '_SCL_SECURE_NO_DEPRECATE',
+ ],
+ 'msvs_disabled_warnings': [
+ 4800,
+ ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'WarningLevel': '3',
+ 'WarnAsError': 'true',
+ 'Detect64BitPortabilityProblems': 'false',
+ },
+ },
+ 'conditions': [
+ ['buildtype=="Official"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': { 'WarnAsError': 'false' },
+ }
+ }],
+ [ 'component=="shared_library"', {
+ # TODO(darin): Unfortunately, some third_party code depends on base.
+ 'msvs_disabled_warnings': [
+ 4251, # class 'std::xx' needs to have dll-interface.
+ ],
+ }],
+ ],
+ }],
+
+ [ 'OS=="mac" or OS=="ios"', {
+ 'xcode_settings': {
+ 'WARNING_CFLAGS!': ['-Wall', '-Wextra'],
+ },
+ 'conditions': [
+ ['buildtype=="Official"', {
+ 'xcode_settings': {
+ 'GCC_TREAT_WARNINGS_AS_ERRORS': 'NO', # -Werror
+ },
+ }],
+ ],
+ }],
+ [ 'OS=="ios"', {
+ 'xcode_settings': {
+ # TODO(ios): Fix remaining warnings in third-party code, then
+ # remove this; the Mac cleanup didn't get everything that's
+ # flagged in an iOS build.
+ 'GCC_TREAT_WARNINGS_AS_ERRORS': 'NO',
+ 'RUN_CLANG_STATIC_ANALYZER': 'NO',
+ # Several internal ios directories generate numerous warnings for
+ # -Wobjc-missing-property-synthesis.
+ 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO',
+ },
+ }],
+ ],
+ }, {
+ 'includes': [
+ # Rules for excluding e.g. foo_win.cc from the build on non-Windows.
+ 'filename_rules.gypi',
+ ],
+ # In Chromium code, we define __STDC_foo_MACROS in order to get the
+ # C99 macros on Mac and Linux.
+ 'defines': [
+ '__STDC_CONSTANT_MACROS',
+ '__STDC_FORMAT_MACROS',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ # turn on warnings for signed/unsigned mismatch on chromium code.
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': ['/we4389'],
+ },
+ },
+ }],
+ ['OS=="win" and component=="shared_library"', {
+ 'msvs_disabled_warnings': [
+ 4251, # class 'std::xx' needs to have dll-interface.
+ ],
+ }],
+ ],
+ }],
+ ], # target_conditions for 'target_defaults'
+ 'default_configuration': 'Debug',
+ 'configurations': {
+ # VCLinkerTool LinkIncremental values below:
+ # 0 == default
+ # 1 == /INCREMENTAL:NO
+ # 2 == /INCREMENTAL
+ # Debug links incremental, Release does not.
+ #
+ # Abstract base configurations to cover common attributes.
+ #
+ 'Common_Base': {
+ 'abstract': 1,
+ 'msvs_configuration_attributes': {
+ 'OutputDirectory': '<(DEPTH)\\build\\<(build_dir_prefix)$(ConfigurationName)',
+ 'IntermediateDirectory': '$(OutDir)\\obj\\$(ProjectName)',
+ 'CharacterSet': '1',
+ },
+ 'msvs_settings':{
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ '/bigobj',
+ ],
+ },
+ 'VCLinkerTool': {
+ # Add the default import libs.
+ 'AdditionalDependencies': [
+ 'kernel32.lib',
+ 'gdi32.lib',
+ 'winspool.lib',
+ 'comdlg32.lib',
+ 'advapi32.lib',
+ 'shell32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'user32.lib',
+ 'uuid.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'delayimp.lib',
+ 'credui.lib',
+ 'netapi32.lib',
+ ],
+ 'AdditionalOptions': [
+ # Suggested by Microsoft Devrel to avoid
+ # LINK : fatal error LNK1248: image size (80000000) exceeds maximum allowable size (80000000)
+ # which started happening more regularly after VS2013 Update 4.
+ # Needs to be a bit lower for VS2015, or else errors out.
+ '/maxilksize:0x7ff00000',
+ ],
+ },
+ },
+ },
+ 'x86_Base': {
+ 'abstract': 1,
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'MinimumRequiredVersion': '5.01', # XP.
+ 'TargetMachine': '1',
+ },
+ 'VCLibrarianTool': {
+ 'TargetMachine': '1',
+ },
+ },
+ 'msvs_configuration_platform': 'Win32',
+ },
+ 'x64_Base': {
+ 'abstract': 1,
+ 'msvs_configuration_platform': 'x64',
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ # Make sure to understand http://crbug.com/361720 if you want to
+ # increase this.
+ 'MinimumRequiredVersion': '5.02', # Server 2003.
+ 'TargetMachine': '17', # x86 - 64
+ 'AdditionalLibraryDirectories!':
+ ['<(windows_sdk_path)/Lib/win8/um/x86'],
+ 'AdditionalLibraryDirectories':
+ ['<(windows_sdk_path)/Lib/win8/um/x64'],
+ # Doesn't exist x64 SDK. Should use oleaut32 in any case.
+ 'IgnoreDefaultLibraryNames': [ 'olepro32.lib' ],
+ },
+ 'VCLibrarianTool': {
+ 'AdditionalLibraryDirectories!':
+ ['<(windows_sdk_path)/Lib/win8/um/x86'],
+ 'AdditionalLibraryDirectories':
+ ['<(windows_sdk_path)/Lib/win8/um/x64'],
+ 'TargetMachine': '17', # x64
+ },
+ },
+ },
+ 'Debug_Base': {
+ 'abstract': 1,
+ 'defines': [
+ 'DYNAMIC_ANNOTATIONS_ENABLED=1',
+ 'WTF_USE_DYNAMIC_ANNOTATIONS=1',
+ ],
+ 'xcode_settings': {
+ 'GCC_OPTIMIZATION_LEVEL': '<(mac_debug_optimization)',
+ 'OTHER_CFLAGS': [
+ '<@(debug_extra_cflags)',
+ ],
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'Optimization': '<(win_debug_Optimization)',
+ 'PreprocessorDefinitions': ['_DEBUG'],
+ 'BasicRuntimeChecks': '<(win_debug_RuntimeChecks)',
+ 'RuntimeLibrary': '<(win_debug_RuntimeLibrary)',
+ 'conditions': [
+ # According to MSVS, InlineFunctionExpansion=0 means
+ # "default inlining", not "/Ob0".
+ # Thus, we have to handle InlineFunctionExpansion==0 separately.
+ ['win_debug_InlineFunctionExpansion==0', {
+ 'AdditionalOptions': ['/Ob0'],
+ }],
+ ['win_debug_InlineFunctionExpansion!=""', {
+ 'InlineFunctionExpansion':
+ '<(win_debug_InlineFunctionExpansion)',
+ }],
+ ['win_debug_disable_iterator_debugging==1', {
+ 'PreprocessorDefinitions': ['_HAS_ITERATOR_DEBUGGING=0'],
+ }],
+
+ # if win_debug_OmitFramePointers is blank, leave as default
+ ['win_debug_OmitFramePointers==1', {
+ 'OmitFramePointers': 'true',
+ }],
+ ['win_debug_OmitFramePointers==0', {
+ 'OmitFramePointers': 'false',
+ # The above is not sufficient (http://crbug.com/106711): it
+ # simply eliminates an explicit "/Oy", but both /O2 and /Ox
+ # perform FPO regardless, so we must explicitly disable.
+ # We still want the false setting above to avoid having
+ # "/Oy /Oy-" and warnings about overriding.
+ 'AdditionalOptions': ['/Oy-'],
+ }],
+ ],
+ 'AdditionalOptions': [ '<@(win_debug_extra_cflags)', ],
+ },
+ 'VCLinkerTool': {
+ 'LinkIncremental': '<(msvs_debug_link_incremental)',
+ # ASLR makes debugging with windbg difficult because Chrome.exe and
+ # Chrome.dll share the same base name. As result, windbg will
+ # name the Chrome.dll module like chrome_ , where
+ # typically changes with each launch. This in turn
+ # means that breakpoints in Chrome.dll don't stick from one launch
+ # to the next. For this reason, we turn ASLR off in debug builds.
+ # Note that this is a three-way bool, where 0 means to pick up
+ # the default setting, 1 is off and 2 is on.
+ 'RandomizedBaseAddress': 1,
+ },
+ 'VCResourceCompilerTool': {
+ 'PreprocessorDefinitions': ['_DEBUG'],
+ },
+ },
+ 'conditions': [
+ ['OS=="linux" or OS=="android"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'cflags': [
+ '<@(debug_extra_cflags)',
+ ],
+ }],
+ ],
+ }],
+ ['OS=="linux" and target_arch!="ia32" and disable_glibcxx_debug==0', {
+ # Enable libstdc++ debugging facilities to help catch problems
+ # early, see http://crbug.com/65151 .
+ # TODO(phajdan.jr): Should we enable this for all of POSIX?
+ 'defines': ['_GLIBCXX_DEBUG=1',],
+ }],
+ ['release_valgrind_build==0', {
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [
+ '-fstack-protector-all', # Implies -fstack-protector
+ ],
+ },
+ }],
+ ['clang==1', {
+ 'cflags': [
+ # Allow comparing the address of references and 'this' against 0
+ # in debug builds. Technically, these can never be null in
+ # well-defined C/C++ and Clang can optimize such checks away in
+ # release builds, but they may be used in asserts in debug builds.
+ '-Wno-undefined-bool-conversion',
+ '-Wno-tautological-undefined-compare',
+ ],
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [
+ '-Wno-undefined-bool-conversion',
+ '-Wno-tautological-undefined-compare',
+ ],
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ '-Wno-undefined-bool-conversion',
+ '-Wno-tautological-undefined-compare',
+ ],
+ },
+ },
+ }],
+ ],
+ },
+ 'Release_Base': {
+ 'abstract': 1,
+ 'defines': [
+ 'NDEBUG',
+ ],
+ 'xcode_settings': {
+ 'DEAD_CODE_STRIPPING': 'YES', # -Wl,-dead_strip
+ 'GCC_OPTIMIZATION_LEVEL': '<(mac_release_optimization)',
+ 'OTHER_CFLAGS': [ '<@(release_extra_cflags)', ],
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': '<(win_release_RuntimeLibrary)',
+ 'conditions': [
+ # In official builds, each target will self-select
+ # an optimization level.
+ ['buildtype!="Official"', {
+ 'Optimization': '<(win_release_Optimization)',
+ },
+ ],
+ # According to MSVS, InlineFunctionExpansion=0 means
+ # "default inlining", not "/Ob0".
+ # Thus, we have to handle InlineFunctionExpansion==0 separately.
+ ['win_release_InlineFunctionExpansion==0', {
+ 'AdditionalOptions': ['/Ob0'],
+ }],
+ ['win_release_InlineFunctionExpansion!=""', {
+ 'InlineFunctionExpansion':
+ '<(win_release_InlineFunctionExpansion)',
+ }],
+
+ # if win_release_OmitFramePointers is blank, leave as default
+ ['win_release_OmitFramePointers==1', {
+ 'OmitFramePointers': 'true',
+ }],
+ ['win_release_OmitFramePointers==0', {
+ 'OmitFramePointers': 'false',
+ # The above is not sufficient (http://crbug.com/106711): it
+ # simply eliminates an explicit "/Oy", but both /O2 and /Ox
+ # perform FPO regardless, so we must explicitly disable.
+ # We still want the false setting above to avoid having
+ # "/Oy /Oy-" and warnings about overriding.
+ 'AdditionalOptions': ['/Oy-'],
+ }],
+ ['asan==0', {
+ # Put data in separate COMDATs. This allows the linker
+ # to put bit-identical constants at the same address even if
+ # they're unrelated constants, which saves binary size.
+ # This optimization can't be used when ASan is enabled because
+ # it is not compatible with the ASan ODR checker.
+ 'AdditionalOptions': ['/Gw'],
+ }],
+ ],
+ 'AdditionalOptions': [
+ '/d2Zi+', # Improve debugging of Release builds.
+ '/Zc:inline', # Remove unreferenced COMDAT (faster links).
+ '<@(win_release_extra_cflags)',
+ ],
+ },
+ 'VCLinkerTool': {
+ # LinkIncremental is a tri-state boolean, where 0 means default
+ # (i.e., inherit from parent solution), 1 means false, and
+ # 2 means true.
+ 'LinkIncremental': '1',
+ # This corresponds to the /PROFILE flag which ensures the PDB
+ # file contains FIXUP information (growing the PDB file by about
+ # 5%) but does not otherwise alter the output binary. This
+ # information is used by the Syzygy optimization tool when
+ # decomposing the release image.
+ 'Profile': 'true',
+ },
+ },
+ 'conditions': [
+ ['msvs_use_common_release', {
+ 'includes': ['release.gypi'],
+ }],
+ ['release_valgrind_build==0 and tsan==0', {
+ 'defines': [
+ 'NVALGRIND',
+ 'DYNAMIC_ANNOTATIONS_ENABLED=0',
+ ],
+ }, {
+ 'defines': [
+ 'MEMORY_TOOL_REPLACES_ALLOCATOR',
+ 'MEMORY_SANITIZER_INITIAL_SIZE',
+ 'DYNAMIC_ANNOTATIONS_ENABLED=1',
+ 'WTF_USE_DYNAMIC_ANNOTATIONS=1',
+ ],
+ }],
+ ['OS=="win"', {
+ 'defines': ['NO_TCMALLOC'],
+ }],
+ # _FORTIFY_SOURCE isn't really supported by Clang now, see
+ # http://llvm.org/bugs/show_bug.cgi?id=16821.
+ # It seems to work fine with Ubuntu 12 headers though, so use it
+ # in official builds.
+ ['os_posix==1 and (asan!=1 and msan!=1 and tsan!=1 and lsan!=1 and ubsan!=1) and (OS!="linux" or clang!=1 or buildtype=="Official")', {
+ 'target_conditions': [
+ ['chromium_code==1', {
+ # Non-chromium code is not guaranteed to compile cleanly
+ # with _FORTIFY_SOURCE. Also, fortified build may fail
+ # when optimizations are disabled, so only do that for Release
+ # build.
+ 'defines': [
+ '_FORTIFY_SOURCE=2',
+ ],
+ }],
+ ],
+ }],
+ ['OS=="linux" or OS=="android"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'cflags': [
+ '<@(release_extra_cflags)',
+ ],
+ 'conditions': [
+ ['enable_resource_whitelist_generation==1', {
+ 'cflags': [
+ '-Wunknown-pragmas -Wno-error=unknown-pragmas',
+ ],
+ }],
+ ],
+ }],
+ ],
+ }],
+ ['OS=="ios"', {
+ 'defines': [
+ 'NS_BLOCK_ASSERTIONS=1',
+ ],
+ }],
+ ],
+ },
+ #
+ # Concrete configurations
+ #
+ 'Debug': {
+ 'inherit_from': ['Common_Base', 'x86_Base', 'Debug_Base'],
+ },
+ 'Release': {
+ 'inherit_from': ['Common_Base', 'x86_Base', 'Release_Base'],
+ },
+ 'conditions': [
+ [ 'OS=="ios"', {
+ 'Profile': {
+ 'inherit_from': ['Common_Base', 'x86_Base', 'Release_Base'],
+ 'target_conditions': [
+ [ '_type=="executable"', {
+ # To get a real .dSYM bundle produced by dsymutil, set the
+ # debug information format to dwarf-with-dsym. Since
+ # strip_from_xcode will not be used, set Xcode to do the
+ # stripping as well.
+ 'xcode_settings': {
+ 'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym',
+ 'DEPLOYMENT_POSTPROCESSING': 'YES',
+ 'STRIP_INSTALLED_PRODUCT': 'YES',
+ },
+ }],
+ ],
+ },
+ }],
+ [ 'OS=="win"', {
+ # TODO(bradnelson): add a gyp mechanism to make this more graceful.
+ 'Debug_x64': {
+ 'inherit_from': ['Common_Base', 'x64_Base', 'Debug_Base'],
+ },
+ 'Release_x64': {
+ 'inherit_from': ['Common_Base', 'x64_Base', 'Release_Base'],
+ },
+ }],
+ ],
+ },
+ },
+ 'conditions': [
+ ['os_posix==1', {
+ 'target_defaults': {
+ 'ldflags': [
+ '-Wl,-z,now',
+ '-Wl,-z,relro',
+ ],
+ # TODO(glider): enable the default options on other systems.
+ 'conditions': [
+ ['use_sanitizer_options==1 and ((OS=="linux" and (chromeos==0 or target_arch!="ia32")) or OS=="mac")', {
+ 'dependencies': [
+ '<(DEPTH)/build/sanitizers/sanitizers.gyp:sanitizer_options',
+ ],
+ }],
+ ],
+ },
+ }],
+ # TODO(jochen): Enable this on chromeos on arm. http://crbug.com/356580
+ ['os_posix==1 and disable_fatal_linker_warnings==0 and use_evdev_gestures==0 and (chromeos==0 or target_arch!="arm")', {
+ 'target_defaults': {
+ 'ldflags': [
+ '-Wl,--fatal-warnings',
+ ],
+ },
+ }],
+ # -Wl,-z,-defs doesn't work with the sanitiziers, http://crbug.com/452065
+ ['(OS=="linux" or OS=="android") and asan==0 and msan==0 and tsan==0 and ubsan==0 and ubsan_vptr==0', {
+ 'target_defaults': {
+ 'ldflags': [
+ '-Wl,-z,defs',
+ ],
+ },
+ }],
+ ['os_posix==1 and chromeos==0', {
+ # Chrome OS enables -fstack-protector-strong via its build wrapper,
+ # and we want to avoid overriding this, so stack-protector is only
+ # enabled when not building on Chrome OS.
+ # TODO(phajdan.jr): Use -fstack-protector-strong when our gcc
+ # supports it.
+ 'target_defaults': {
+ 'cflags': [
+ '-fstack-protector',
+ '--param=ssp-buffer-size=4',
+ ],
+ },
+ }],
+ ['os_posix==1 and OS=="linux"', {
+ 'defines': [
+ '_LARGEFILE_SOURCE',
+ '_LARGEFILE64_SOURCE',
+ '_FILE_OFFSET_BITS=64',
+ ],
+ }],
+ ['os_posix==1 and OS!="mac" and OS!="ios"', {
+ 'target_defaults': {
+ # Enable -Werror by default, but put it in a variable so it can
+ # be disabled in ~/.gyp/include.gypi on the valgrind builders.
+ 'variables': {
+ 'werror%': '-Werror',
+ 'libraries_for_target%': '',
+ },
+ 'defines': [
+ '_FILE_OFFSET_BITS=64',
+ ],
+ 'cflags': [
+ '<(werror)', # See note above about the werror variable.
+ '-pthread',
+ '-fno-strict-aliasing', # See http://crbug.com/32204
+ '-Wall',
+ # Don't warn about unused function params. We use those everywhere.
+ '-Wno-unused-parameter',
+ # Don't warn about the "struct foo f = {0};" initialization pattern.
+ '-Wno-missing-field-initializers',
+ # Don't export any symbols (for example, to plugins we dlopen()).
+ # Note: this is *required* to make some plugins work.
+ '-fvisibility=hidden',
+ '-pipe',
+ ],
+ 'cflags_cc': [
+ '-fno-exceptions',
+ '-fno-rtti',
+ '-fno-threadsafe-statics',
+ # Make inline functions have hidden visiblity by default.
+ # Surprisingly, not covered by -fvisibility=hidden.
+ '-fvisibility-inlines-hidden',
+ # GCC turns on -Wsign-compare for C++ under -Wall, but clang doesn't,
+ # so we specify it explicitly. (llvm.org/PR10448, crbug.com/90453)
+ '-Wsign-compare',
+ ],
+ 'ldflags': [
+ '-pthread', '-Wl,-z,noexecstack',
+ ],
+ 'libraries' : [
+ '<(libraries_for_target)',
+ ],
+ 'configurations': {
+ 'Debug_Base': {
+ 'variables': {
+ 'debug_optimize%': '0',
+ },
+ 'defines': [
+ '_DEBUG',
+ ],
+ 'cflags': [
+ '-O>(debug_optimize)',
+ '-g',
+ ],
+ 'conditions' : [
+ ['OS=="android" and target_arch!="mipsel" and target_arch!="mips64el"', {
+ # TODO(jdduke) Re-enable on mips after resolving linking
+ # issues with libc++ (crbug.com/456380).
+ 'ldflags': [
+ # Warn in case of text relocations.
+ '-Wl,--warn-shared-textrel',
+ ],
+ }],
+ ['OS=="android" and android_full_debug==0', {
+ # Some configurations are copied from Release_Base to reduce
+ # the binary size.
+ 'variables': {
+ 'debug_optimize%': 's',
+ },
+ 'cflags': [
+ '-fdata-sections',
+ '-ffunction-sections',
+ ],
+ 'ldflags': [
+ '-Wl,-O1',
+ '-Wl,--as-needed',
+ ],
+ }],
+ ['OS=="android" and android_full_debug==0 and target_arch!="arm64"', {
+ # We don't omit frame pointers on arm64 since they are required
+ # to correctly unwind stackframes which contain system library
+ # function frames (crbug.com/391706).
+ 'cflags': [
+ '-fomit-frame-pointer',
+ ],
+ }],
+ ['OS=="linux" and target_arch=="ia32"', {
+ 'ldflags': [
+ '-Wl,--no-as-needed',
+ ],
+ }],
+ ['debug_unwind_tables==1', {
+ 'cflags': ['-funwind-tables'],
+ }, {
+ 'cflags': ['-fno-unwind-tables', '-fno-asynchronous-unwind-tables'],
+ 'defines': ['NO_UNWIND_TABLES'],
+ }],
+ # TODO(mostynb): shuffle clang/gcc_version/binutils_version
+ # definitions in to the right scope to use them when setting
+ # linux_use_debug_fission, so it can be used here alone.
+ ['linux_use_debug_fission==1 and linux_use_gold_flags==1 and (clang==1 or gcc_version>=48) and binutils_version>=223', {
+ 'cflags': ['-gsplit-dwarf'],
+ }],
+ ],
+ },
+ 'Release_Base': {
+ 'variables': {
+ 'release_optimize%': '2',
+ # Binaries become big and gold is unable to perform GC
+ # and remove unused sections for some of test targets
+ # on 32 bit platform.
+ # (This is currently observed only in chromeos valgrind bots)
+ # The following flag is to disable --gc-sections linker
+ # option for these bots.
+ 'no_gc_sections%': 0,
+
+ # TODO(bradnelson): reexamine how this is done if we change the
+ # expansion of configurations
+ 'release_valgrind_build%': 0,
+ },
+ 'cflags': [
+ '-O<(release_optimize)',
+ # Don't emit the GCC version ident directives, they just end up
+ # in the .comment section taking up binary size.
+ '-fno-ident',
+ # Put data and code in their own sections, so that unused symbols
+ # can be removed at link time with --gc-sections.
+ '-fdata-sections',
+ '-ffunction-sections',
+ ],
+ 'ldflags': [
+ # Specifically tell the linker to perform optimizations.
+ # See http://lwn.net/Articles/192624/ .
+ '-Wl,-O1',
+ '-Wl,--as-needed',
+ ],
+ 'conditions' : [
+ ['no_gc_sections==0', {
+ 'ldflags': [
+ '-Wl,--gc-sections',
+ ],
+ }],
+ ['OS=="android" and target_arch!="arm64"', {
+ # We don't omit frame pointers on arm64 since they are required
+ # to correctly unwind stackframes which contain system library
+ # function frames (crbug.com/391706).
+ 'cflags': [
+ '-fomit-frame-pointer',
+ ]
+ }],
+ ['OS=="android" and target_arch!="mipsel" and target_arch!="mips64el"', {
+ # TODO(jdduke) Re-enable on mips after resolving linking
+ # issues with libc++ (crbug.com/456380).
+ 'ldflags': [
+ # Warn in case of text relocations.
+ '-Wl,--warn-shared-textrel',
+ ],
+ }],
+ ['OS=="android"', {
+ 'variables': {
+ 'release_optimize%': 's',
+ },
+ }],
+ ['profiling==1', {
+ 'cflags': [
+ '-fno-omit-frame-pointer',
+ '-g',
+ ],
+ 'conditions' : [
+ ['profiling_full_stack_frames==1', {
+ 'cflags': [
+ '-fno-inline',
+ '-fno-optimize-sibling-calls',
+ ],
+ }],
+ ],
+ }],
+ ['release_unwind_tables==1', {
+ 'cflags': ['-funwind-tables'],
+ }, {
+ 'cflags': ['-fno-unwind-tables', '-fno-asynchronous-unwind-tables'],
+ 'defines': ['NO_UNWIND_TABLES'],
+ }],
+ ],
+ },
+ },
+ 'conditions': [
+ ['target_arch=="ia32"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'asflags': [
+ # Needed so that libs with .s files (e.g. libicudata.a)
+ # are compatible with the general 32-bit-ness.
+ '-32',
+ ],
+ # All floating-point computations on x87 happens in 80-bit
+ # precision. Because the C and C++ language standards allow
+ # the compiler to keep the floating-point values in higher
+ # precision than what's specified in the source and doing so
+ # is more efficient than constantly rounding up to 64-bit or
+ # 32-bit precision as specified in the source, the compiler,
+ # especially in the optimized mode, tries very hard to keep
+ # values in x87 floating-point stack (in 80-bit precision)
+ # as long as possible. This has important side effects, that
+ # the real value used in computation may change depending on
+ # how the compiler did the optimization - that is, the value
+ # kept in 80-bit is different than the value rounded down to
+ # 64-bit or 32-bit. There are possible compiler options to
+ # make this behavior consistent (e.g. -ffloat-store would keep
+ # all floating-values in the memory, thus force them to be
+ # rounded to its original precision) but they have significant
+ # runtime performance penalty.
+ #
+ # -mfpmath=sse -msse2 makes the compiler use SSE instructions
+ # which keep floating-point values in SSE registers in its
+ # native precision (32-bit for single precision, and 64-bit
+ # for double precision values). This means the floating-point
+ # value used during computation does not change depending on
+ # how the compiler optimized the code, since the value is
+ # always kept in its specified precision.
+ #
+ # Refer to http://crbug.com/348761 for rationale behind SSE2
+ # being a minimum requirement for 32-bit Linux builds and
+ # http://crbug.com/313032 for an example where this has "bit"
+ # us in the past.
+ 'cflags': [
+ '-msse2',
+ '-mfpmath=sse',
+ '-mmmx', # Allows mmintrin.h for MMX intrinsics.
+ '-m32',
+ ],
+ 'ldflags': [
+ '-m32',
+ ],
+ 'conditions': [
+ # Use gold linker for Android ia32 target.
+ ['OS=="android"', {
+ 'ldflags': [
+ '-fuse-ld=gold',
+ ],
+ }],
+ ],
+ }],
+ ],
+ }],
+ ['target_arch=="x64"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ # Use gold linker for Android x64 target.
+ ['OS=="android"', {
+ 'ldflags': [
+ '-fuse-ld=gold',
+ ],
+ }],
+ ],
+ 'cflags': [
+ '-m64',
+ '-march=x86-64',
+ ],
+ 'ldflags': [
+ '-m64',
+ ],
+ }],
+ ],
+ }],
+ ['target_arch=="arm"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['clang==0', {
+ 'cflags_cc': [
+ # The codesourcery arm-2009q3 toolchain warns at that the ABI
+ # has changed whenever it encounters a varargs function. This
+ # silences those warnings, as they are not helpful and
+ # clutter legitimate warnings.
+ '-Wno-abi',
+ ],
+ }],
+ ['clang==1 and arm_arch!="" and OS!="android"', {
+ 'cflags': [
+ '-target arm-linux-gnueabihf',
+ ],
+ 'ldflags': [
+ '-target arm-linux-gnueabihf',
+ ],
+ }],
+ ['arm_arch!=""', {
+ 'cflags': [
+ '-march=<(arm_arch)',
+ ],
+ 'conditions': [
+ ['use_lto==1 or use_lto_o2==1', {
+ 'ldflags': [
+ '-march=<(arm_arch)',
+ ],
+ }],
+ ],
+ }],
+ ['clang==1 and OS!="android"', {
+ 'cflags': [
+ # We need to disable clang's builtin assembler as it can't
+ # handle several asm files, crbug.com/124610
+ '-no-integrated-as',
+ ],
+ }],
+ ['arm_tune!=""', {
+ 'cflags': [
+ '-mtune=<(arm_tune)',
+ ],
+ 'conditions': [
+ ['use_lto==1 or use_lto_o2==1', {
+ 'ldflags': [
+ '-mtune=<(arm_tune)',
+ ],
+ }],
+ ],
+ }],
+ ['arm_fpu!=""', {
+ 'cflags': [
+ '-mfpu=<(arm_fpu)',
+ ],
+ 'conditions': [
+ ['use_lto==1 or use_lto_o2==1', {
+ 'ldflags': [
+ '-mfpu=<(arm_fpu)',
+ ],
+ }],
+ ],
+ }],
+ ['arm_float_abi!=""', {
+ 'cflags': [
+ '-mfloat-abi=<(arm_float_abi)',
+ ],
+ 'conditions': [
+ ['use_lto==1 or use_lto_o2==1', {
+ 'ldflags': [
+ '-mfloat-abi=<(arm_float_abi)',
+ ],
+ }],
+ ],
+ }],
+ ['arm_thumb==1', {
+ 'cflags': [
+ '-mthumb',
+ ],
+ 'conditions': [
+ ['use_lto==1 or use_lto_o2==1', {
+ 'ldflags': [
+ '-mthumb',
+ ],
+ }],
+ ],
+ }],
+ ['OS=="android"', {
+ # Most of the following flags are derived from what Android
+ # uses by default when building for arm, reference for which
+ # can be found in the following file in the Android NDK:
+ # toolchains/arm-linux-androideabi-4.9/setup.mk
+ 'cflags': [
+ # The tree-sra optimization (scalar replacement for
+ # aggregates enabling subsequent optimizations) leads to
+ # invalid code generation when using the Android NDK's
+ # compiler (r5-r7). This can be verified using
+ # webkit_unit_tests' WTF.Checked_int8_t test.
+ '-fno-tree-sra',
+ # The following option is disabled to improve binary
+ # size and performance in gcc 4.9.
+ '-fno-caller-saves',
+ '-Wno-psabi',
+ ],
+ # Android now supports .relro sections properly.
+ # NOTE: While these flags enable the generation of .relro
+ # sections, the generated libraries can still be loaded on
+ # older Android platform versions.
+ 'ldflags': [
+ '-Wl,-z,relro',
+ '-Wl,-z,now',
+ '-fuse-ld=gold',
+ ],
+ 'conditions': [
+ ['gcc_version==48 and clang==0', {
+ 'cflags': [
+ # The following 5 options are disabled to save on
+ # binary size in GCC 4.8.
+ '-fno-partial-inlining',
+ '-fno-early-inlining',
+ '-fno-tree-copy-prop',
+ '-fno-tree-loop-optimize',
+ '-fno-move-loop-invariants',
+ ],
+ }],
+ ['arm_thumb==1', {
+ 'cflags': [ '-mthumb-interwork' ],
+ }],
+ ['profiling==1', {
+ 'cflags': [
+ # Thumb code with frame pointer makes chrome crash
+ # early.
+ '-marm',
+ '-mapcs-frame', # Required by -fno-omit-frame-pointer.
+ # The perf report sometimes incorrectly attributes
+ # code from tail calls.
+ '-fno-optimize-sibling-calls',
+ ],
+ 'cflags!': [
+ '-fomit-frame-pointer',
+ ],
+ }],
+ ['clang==1', {
+ 'cflags!': [
+ # Clang does not support the following options.
+ '-mapcs-frame',
+ '-mthumb-interwork',
+ '-finline-limit=64',
+ '-fno-tree-sra',
+ '-fno-caller-saves',
+ '-Wno-psabi',
+ ],
+ 'cflags': [
+ # TODO(hans) Enable integrated-as (crbug.com/124610).
+ '-no-integrated-as',
+ '-B<(android_toolchain)', # Else /usr/bin/as gets picked up.
+ ],
+ }],
+ ['clang==1 and linux_use_bundled_gold==0', {
+ 'ldflags': [
+ # Let clang find the ld.gold in the NDK.
+ '--gcc-toolchain=<(android_toolchain)/..',
+ ],
+ }],
+ ['asan==1', {
+ 'cflags': [
+ '-marm', # Required for frame pointer based stack traces.
+ ],
+ }],
+ ],
+ }],
+ ['chromecast==1', {
+ 'cflags': [
+ # We set arm_arch to "" so that -march compiler option
+ # is not set. Otherwise a gcc bug that would complain
+ # about it conflicting with '-mcpu=cortex-a9'. The flag
+ # '-march=armv7-a' is actually redundant anyway because
+ # it is enabled by default when we built the toolchain.
+ # And using '-mcpu=cortex-a9' should be sufficient.
+ '-mcpu=cortex-a9',
+ '-funwind-tables',
+ # Breakpad requires symbols with debugging information
+ '-g',
+ ],
+ 'ldflags': [
+ # We want to statically link libstdc++/libgcc_s.
+ '-static-libstdc++',
+ '-static-libgcc',
+ ],
+ 'cflags!': [
+ # Some components in Chromium (e.g. v8, skia, ffmpeg)
+ # define their own cflags for arm builds that could
+ # conflict with the flags we set here (e.g.
+ # '-mcpu=cortex-a9'). Remove these flags explicitly.
+ '-march=armv7-a',
+ '-mtune=cortex-a8',
+ ],
+ }],
+ ],
+ }],
+ ],
+ }],
+ ['target_arch=="arm64"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['OS=="android"', {
+ 'cflags!': [
+ '-fstack-protector', # stack protector is always enabled on arm64.
+ ],
+ }],
+ ],
+ }],
+ ],
+ }],
+ ['target_arch=="mipsel"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['mips_arch_variant=="r6"', {
+ 'conditions': [
+ ['clang==1', {
+ 'cflags': [ '-target mipsel-linux-gnu', '-march=mips32r6', ],
+ 'ldflags': [ '-target mipsel-linux-gnu', ],
+ }, { # clang==0
+ 'cflags': ['-mips32r6', '-Wa,-mips32r6', ],
+ }],
+ ['clang==0 and OS=="android"', {
+ 'ldflags': ['-mips32r6', '-Wl,-melf32ltsmip',],
+ }],
+ ],
+ }],
+ ['mips_arch_variant=="r2"', {
+ 'conditions': [
+ ['mips_float_abi=="hard" and mips_fpu_mode!=""', {
+ 'cflags': ['-m<(mips_fpu_mode)'],
+ }],
+ ['clang==1', {
+ 'conditions': [
+ ['OS=="android"', {
+ 'cflags': [ '-target mipsel-linux-android', '-march=mipsel', '-mcpu=mips32r2'],
+ 'ldflags': [ '-target mipsel-linux-android', ],
+ }, {
+ 'cflags': [ '-target mipsel-linux-gnu', '-march=mipsel', '-mcpu=mips32r2'],
+ 'ldflags': [ '-target mipsel-linux-gnu', ],
+ }],
+ ],
+ }, { # clang==0
+ 'cflags': ['-mips32r2', '-Wa,-mips32r2', ],
+ }],
+ ],
+ }],
+ ['mips_arch_variant=="r1"', {
+ 'conditions': [
+ ['clang==1', {
+ 'conditions': [
+ ['OS=="android"', {
+ 'cflags': [ '-target mipsel-linux-android', '-march=mipsel', '-mcpu=mips32'],
+ 'ldflags': [ '-target mipsel-linux-android', ],
+ }, {
+ 'cflags': [ '-target mipsel-linux-gnu', '-march=mipsel', '-mcpu=mips32'],
+ 'ldflags': [ '-target mipsel-linux-gnu', ],
+ }],
+ ],
+ }, { # clang==0
+ 'cflags': ['-mips32', '-Wa,-mips32', ],
+ }],
+ ],
+ }],
+ ['clang==1', {
+ 'cflags!': [
+ # Clang does not support the following options.
+ '-finline-limit=64',
+ ],
+ 'cflags': [
+ # TODO(gordanac) Enable integrated-as.
+ '-no-integrated-as',
+ ],
+ }],
+ ['clang==1 and OS=="android"', {
+ 'cflags': [
+ '-B<(android_toolchain)', # Else /usr/bin/as gets picked up.
+ ],
+ 'ldflags': [
+ # Let clang find the ld in the NDK.
+ '--gcc-toolchain=<(android_toolchain)/..',
+ ],
+ }],
+ ['mips_dsp_rev==1', {
+ 'cflags': ['-mdsp'],
+ }],
+ ['mips_dsp_rev==2', {
+ 'cflags': ['-mdspr2'],
+ }],
+ ],
+ 'cflags': [
+ '-m<(mips_float_abi)-float'
+ ],
+ 'ldflags': [
+ '-Wl,--no-keep-memory'
+ ],
+ 'cflags_cc': [
+ '-Wno-uninitialized',
+ ],
+ }],
+ ],
+ }],
+ ['target_arch=="mips64el"', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'conditions': [
+ ['mips_arch_variant=="r6"', {
+ 'cflags': ['-mips64r6', '-Wa,-mips64r6'],
+ 'ldflags': ['-mips64r6'],
+ }],
+ ['mips_arch_variant=="r2"', {
+ 'cflags': ['-mips64r2', '-Wa,-mips64r2'],
+ 'ldflags': ['-mips64r2'],
+ }],
+ ],
+ 'cflags_cc': [
+ '-Wno-uninitialized',
+ ],
+ }],
+ ],
+ }],
+ ['linux_fpic==1', {
+ 'cflags': [
+ '-fPIC',
+ ],
+ 'ldflags': [
+ '-fPIC',
+ ],
+ }],
+ ['sysroot!=""', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'cflags': [
+ '--sysroot=<(sysroot)',
+ ],
+ 'ldflags': [
+ '--sysroot=<(sysroot)',
+ '=223', {
+ # Newer binutils don't set DT_RPATH unless you disable "new" dtags
+ # and the new DT_RUNPATH doesn't work without --no-as-needed flag.
+ # FIXME(mithro): Figure out the --as-needed/--no-as-needed flags
+ # inside this file to allow usage of --no-as-needed and removal of
+ # this flag.
+ 'ldflags': [
+ '-Wl,--disable-new-dtags',
+ ],
+ }],
+ ['gcc_version>=47 and clang==0', {
+ 'target_conditions': [
+ ['_toolset=="target"', {
+ 'cflags_cc': [
+ '-std=gnu++11',
+ # See comment for -Wno-c++11-narrowing.
+ '-Wno-narrowing',
+ # TODO(thakis): Remove, http://crbug.com/263960
+ '-Wno-literal-suffix',
+ ],
+ }],
+ ],
+ }],
+ ['host_gcc_version>=47 and clang==0 and host_clang==0', {
+ 'target_conditions': [
+ ['_toolset=="host"', {
+ 'cflags_cc': [
+ '-std=gnu++11',
+ # See comment for -Wno-c++11-narrowing.
+ '-Wno-narrowing',
+ # TODO(thakis): Remove, http://crbug.com/263960
+ '-Wno-literal-suffix',
+ ],
+ }],
+ ],
+ }],
+ ],
+ },
+ }],
+ # FreeBSD-specific options; note that most FreeBSD options are set above,
+ # with Linux.
+ ['OS=="freebsd"', {
+ 'target_defaults': {
+ 'ldflags': [
+ '-Wl,--no-keep-memory',
+ ],
+ },
+ }],
+ # Android-specific options; note that most are set above with Linux.
+ ['OS=="android"', {
+ 'variables': {
+ # This is a unique identifier for a given build. It's used for
+ # identifying various build artifacts corresponding to a particular
+ # build of chrome (e.g. where to find archived symbols).
+ 'chrome_build_id%': '',
+ 'conditions': [
+ # Figure this out early since it needs symbols from libgcc.a, so it
+ # has to be before that in the set of libraries.
+ ['component=="shared_library"', {
+ 'android_libcpp_library': 'c++_shared',
+ }, {
+ 'android_libcpp_library': 'c++_static',
+ }],
+ ],
+
+ # Placing this variable here prevents from forking libvpx, used
+ # by remoting. Remoting is off, so it needn't built,
+ # so forking it's deps seems like overkill.
+ # But this variable need defined to properly run gyp.
+ # A proper solution is to have an OS==android conditional
+ # in third_party/libvpx/libvpx.gyp to define it.
+ 'libvpx_path': 'lib/linux/arm',
+ },
+ 'target_defaults': {
+ 'variables': {
+ 'release_extra_cflags%': '',
+ 'conditions': [
+ # If we're using the components build, append "cr" to all shared
+ # libraries to avoid naming collisions with android system library
+ # versions with the same name (e.g. skia, icu).
+ ['component=="shared_library"', {
+ 'android_product_extension': 'cr.so',
+ }, {
+ 'android_product_extension': 'so',
+ } ],
+ ],
+ },
+ 'target_conditions': [
+ ['_type=="shared_library"', {
+ 'product_extension': '<(android_product_extension)',
+ }],
+
+ # Settings for building device targets using Android's toolchain.
+ # These are based on the setup.mk file from the Android NDK.
+ #
+ # The NDK Android executable link step looks as follows:
+ # $LDFLAGS
+ # $(TARGET_CRTBEGIN_DYNAMIC_O) <-- crtbegin.o
+ # $(PRIVATE_OBJECTS) <-- The .o that we built
+ # $(PRIVATE_STATIC_LIBRARIES) <-- The .a that we built
+ # $(TARGET_LIBGCC) <-- libgcc.a
+ # $(PRIVATE_SHARED_LIBRARIES) <-- The .so that we built
+ # $(PRIVATE_LDLIBS) <-- System .so
+ # $(TARGET_CRTEND_O) <-- crtend.o
+ #
+ # For now the above are approximated for executables by adding
+ # crtbegin.o to the end of the ldflags and 'crtend.o' to the end
+ # of 'libraries'.
+ #
+ # The NDK Android shared library link step looks as follows:
+ # $LDFLAGS
+ # $(PRIVATE_OBJECTS) <-- The .o that we built
+ # -l,--whole-archive
+ # $(PRIVATE_WHOLE_STATIC_LIBRARIES)
+ # -l,--no-whole-archive
+ # $(PRIVATE_STATIC_LIBRARIES) <-- The .a that we built
+ # $(TARGET_LIBGCC) <-- libgcc.a
+ # $(PRIVATE_SHARED_LIBRARIES) <-- The .so that we built
+ # $(PRIVATE_LDLIBS) <-- System .so
+ #
+ # For now, assume that whole static libraries are not needed.
+ #
+ # For both executables and shared libraries, add the proper
+ # libgcc.a to the start of libraries which puts it in the
+ # proper spot after .o and .a files get linked in.
+ #
+ # TODO: The proper thing to do longer-tem would be proper gyp
+ # support for a custom link command line.
+ ['_toolset=="target"', {
+ 'cflags!': [
+ '-pthread', # Not supported by Android toolchain.
+ ],
+ 'cflags': [
+ '-ffunction-sections',
+ '-funwind-tables',
+ '-g',
+ '-fstack-protector',
+ '-fno-short-enums',
+ '-finline-limit=64',
+ '<@(release_extra_cflags)',
+ '--sysroot=<(android_ndk_sysroot)',
+ # NOTE: The libc++ header include paths below are specified in
+ # cflags rather than include_dirs because they need to come
+ # after include_dirs.
+ # The include ordering here is important; change with caution.
+ '-isystem<(android_libcpp_include)',
+ '-isystem<(android_ndk_root)/sources/cxx-stl/llvm-libc++abi/libcxxabi/include',
+ '-isystem<(android_ndk_root)/sources/android/support/include',
+ ],
+ 'defines': [
+ 'ANDROID',
+ '__GNU_SOURCE=1', # Necessary for clone()
+ 'CHROME_BUILD_ID="<(chrome_build_id)"',
+ # The NDK has these things, but doesn't define the constants
+ # to say that it does. Define them here instead.
+ 'HAVE_SYS_UIO_H',
+ ],
+ 'ldflags!': [
+ '-pthread', # Not supported by Android toolchain.
+ ],
+ 'ldflags': [
+ '-Wl,--build-id=sha1',
+ '-Wl,--no-undefined',
+ '--sysroot=<(android_ndk_sysroot)',
+ '-nostdlib',
+ '-L<(android_libcpp_libs_dir)',
+ # Don't allow visible symbols from libgcc or libc++ to be
+ # re-exported.
+ '-Wl,--exclude-libs=libgcc.a',
+ '-Wl,--exclude-libs=libc++_static.a',
+ # Don't allow visible symbols from libraries that contain
+ # assembly code with symbols that aren't hidden properly.
+ # http://crbug.com/448386
+ '-Wl,--exclude-libs=libcommon_audio.a',
+ '-Wl,--exclude-libs=libcommon_audio_neon.a',
+ '-Wl,--exclude-libs=libcommon_audio_sse2.a',
+ '-Wl,--exclude-libs=libiSACFix.a',
+ '-Wl,--exclude-libs=libisac_neon.a',
+ '-Wl,--exclude-libs=libopus.a',
+ '-Wl,--exclude-libs=libvpx.a',
+ ],
+ 'libraries': [
+ '-l<(android_libcpp_library)',
+ '-latomic',
+ # Manually link the libgcc.a that the cross compiler uses.
+ '(_target_name).map',
+ ],
+ },
+ }],
+ ],
+ }],
+ ['_mac_bundle', {
+ 'xcode_settings': {'OTHER_LDFLAGS': ['-Wl,-ObjC']},
+ 'target_conditions': [
+ ['_type=="executable"', {
+ 'conditions': [
+ ['asan==1', {
+ 'postbuilds': [
+ {
+ 'variables': {
+ # Define copy_asan_dylib_path in a variable ending in
+ # _path so that gyp understands it's a path and
+ # performs proper relativization during dict merging.
+ 'copy_asan_dylib_path':
+ 'mac/copy_asan_runtime_dylib.sh',
+ },
+ 'postbuild_name': 'Copy ASan runtime dylib',
+ 'action': [
+ '<(copy_asan_dylib_path)',
+ ],
+ },
+ ],
+ }],
+ ],
+ }],
+ ],
+ }],
+ ], # target_conditions
+ }, # target_defaults
+ }], # OS=="mac" or OS=="ios"
+ ['OS=="mac"', {
+ 'target_defaults': {
+ 'defines': [
+ # Prevent Mac OS X AssertMacros.h from defining macros that collide
+ # with common names, like 'check', 'require', and 'verify'.
+ # (Included by system header. Also exists on iOS but not included.)
+ # http://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/AssertMacros.h
+ '__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORE=0',
+ ],
+ 'variables': {
+ # These should end with %, but there seems to be a bug with % in
+ # variables that are intended to be set to different values in
+ # different targets, like these.
+ 'mac_pie': 1, # Most executables can be position-independent.
+ # Strip debugging symbols from the target.
+ 'mac_strip': '<(mac_strip_release)',
+ 'conditions': [
+ ['asan==1', {
+ 'conditions': [
+ ['mac_want_real_dsym=="default"', {
+ 'mac_real_dsym': 1,
+ }, {
+ 'mac_real_dsym': '<(mac_want_real_dsym)'
+ }],
+ ],
+ }, {
+ 'conditions': [
+ ['mac_want_real_dsym=="default"', {
+ 'mac_real_dsym': 0, # Fake .dSYMs are fine in most cases.
+ }, {
+ 'mac_real_dsym': '<(mac_want_real_dsym)'
+ }],
+ ],
+ }],
+ ],
+ },
+ 'configurations': {
+ 'Release_Base': {
+ 'conditions': [
+ ['branding=="Chrome" and buildtype=="Official"', {
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [
+ # The Google Chrome Framework dSYM generated by dsymutil has
+ # grown larger than 4GB, which dsymutil can't handle. Reduce
+ # the amount of debug symbols.
+ '-fno-standalone-debug', # See http://crbug.com/479841
+ ]
+ },
+ }],
+ ],
+ }, # configuration "Release"
+ }, # configurations
+ 'xcode_settings': {
+ 'GCC_DYNAMIC_NO_PIC': 'NO', # No -mdynamic-no-pic
+ # (Equivalent to -fPIC)
+ # MACOSX_DEPLOYMENT_TARGET maps to -mmacosx-version-min
+ 'MACOSX_DEPLOYMENT_TARGET': '<(mac_deployment_target)',
+ # Keep pch files below xcodebuild/.
+ 'SHARED_PRECOMPS_DIR': '$(CONFIGURATION_BUILD_DIR)/SharedPrecompiledHeaders',
+ 'OTHER_CFLAGS': [
+ # Someday this can be replaced by an 'GCC_STRICT_ALIASING': 'NO'
+ # xcode_setting, but not until all downstream projects' mac bots are
+ # using xcode >= 4.6, because that's when the default value of the
+ # flag in the compiler switched. Pre-4.6, the value 'NO' for that
+ # setting is a no-op as far as xcode is concerned, but the compiler
+ # behaves differently based on whether -fno-strict-aliasing is
+ # specified or not.
+ '-fno-strict-aliasing', # See http://crbug.com/32204.
+ ],
+ },
+ 'target_conditions': [
+ ['_type=="executable"', {
+ 'postbuilds': [
+ {
+ # Arranges for data (heap) pages to be protected against
+ # code execution when running on Mac OS X 10.7 ("Lion"), and
+ # ensures that the position-independent executable (PIE) bit
+ # is set for ASLR when running on Mac OS X 10.5 ("Leopard").
+ 'variables': {
+ # Define change_mach_o_flags in a variable ending in _path
+ # so that GYP understands it's a path and performs proper
+ # relativization during dict merging.
+ 'change_mach_o_flags_path':
+ 'mac/change_mach_o_flags_from_xcode.sh',
+ 'change_mach_o_flags_options%': [
+ ],
+ 'target_conditions': [
+ ['mac_pie==0 or release_valgrind_build==1', {
+ # Don't enable PIE if it's unwanted. It's unwanted if
+ # the target specifies mac_pie=0 or if building for
+ # Valgrind, because Valgrind doesn't understand slide.
+ # See the similar mac_pie/release_valgrind_build check
+ # below.
+ 'change_mach_o_flags_options': [
+ '--no-pie',
+ ],
+ }],
+ ],
+ },
+ 'postbuild_name': 'Change Mach-O Flags',
+ 'action': [
+ '<(change_mach_o_flags_path)',
+ '>@(change_mach_o_flags_options)',
+ ],
+ },
+ ],
+ 'target_conditions': [
+ ['mac_pie==1 and release_valgrind_build==0', {
+ # Turn on position-independence (ASLR) for executables. When
+ # PIE is on for the Chrome executables, the framework will
+ # also be subject to ASLR.
+ # Don't do this when building for Valgrind, because Valgrind
+ # doesn't understand slide. TODO: Make Valgrind on Mac OS X
+ # understand slide, and get rid of the Valgrind check.
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [
+ '-Wl,-pie', # Position-independent executable (MH_PIE)
+ ],
+ },
+ }],
+ ],
+ }],
+ ['(_type=="executable" or _type=="shared_library" or \
+ _type=="loadable_module") and mac_strip!=0', {
+ 'target_conditions': [
+ ['mac_real_dsym == 1', {
+ # To get a real .dSYM bundle produced by dsymutil, set the
+ # debug information format to dwarf-with-dsym. Since
+ # strip_from_xcode will not be used, set Xcode to do the
+ # stripping as well.
+ 'configurations': {
+ 'Release_Base': {
+ 'xcode_settings': {
+ 'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym',
+ 'DEPLOYMENT_POSTPROCESSING': 'YES',
+ 'STRIP_INSTALLED_PRODUCT': 'YES',
+ 'conditions': [
+ # Only strip non-ASan builds.
+ ['asan==0', {
+ 'target_conditions': [
+ ['_type=="shared_library" or _type=="loadable_module"', {
+ # The Xcode default is to strip debugging symbols
+ # only (-S). Local symbols should be stripped as
+ # well, which will be handled by -x. Xcode will
+ # continue to insert -S when stripping even when
+ # additional flags are added with STRIPFLAGS.
+ 'STRIPFLAGS': '-x',
+ }], # _type=="shared_library" or _type=="loadable_module"
+ ], # target_conditions
+ }, { # asan != 0
+ 'STRIPFLAGS': '-S',
+ }],
+ ],
+ }, # xcode_settings
+ }, # configuration "Release"
+ }, # configurations
+ }, { # mac_real_dsym != 1
+ # To get a fast fake .dSYM bundle, use a post-build step to
+ # produce the .dSYM and strip the executable. strip_from_xcode
+ # only operates in the Release configuration.
+ 'postbuilds': [
+ {
+ 'variables': {
+ # Define strip_from_xcode in a variable ending in _path
+ # so that gyp understands it's a path and performs proper
+ # relativization during dict merging.
+ 'strip_from_xcode_path': 'mac/strip_from_xcode',
+ },
+ 'postbuild_name': 'Strip If Needed',
+ 'action': ['<(strip_from_xcode_path)'],
+ },
+ ], # postbuilds
+ }], # mac_real_dsym
+ ], # target_conditions
+ }], # (_type=="executable" or _type=="shared_library" or
+ # _type=="loadable_module") and mac_strip!=0
+ ], # target_conditions
+ }, # target_defaults
+ }], # OS=="mac"
+ ['OS=="ios"', {
+ 'includes': [
+ 'ios/coverage.gypi',
+ ],
+ 'target_defaults': {
+ 'xcode_settings' : {
+ 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
+
+ 'conditions': [
+ # Older Xcodes do not support -Wno-deprecated-register, so pass an
+ # additional flag to suppress the "unknown compiler option" error.
+ # Restrict this flag to builds that are either compiling with Xcode
+ # or compiling with Xcode's Clang. This will allow Ninja builds to
+ # continue failing on unknown compiler options.
+ # TODO(rohitrao): This flag is temporary and should be removed as
+ # soon as the iOS bots are updated to use Xcode 5.1.
+ ['clang_xcode==1', {
+ 'WARNING_CFLAGS': [
+ '-Wno-unknown-warning-option',
+ # It's not possible to achieve nullability completeness before
+ # all builders are running Xcode 7. crbug.com/499809
+ '-Wno-nullability-completeness',
+ ],
+ }],
+
+ # Limit the valid architectures depending on "target_subarch".
+ # This need to include the "arm" architectures but also the "x86"
+ # ones (they are used when building for the simulator).
+ ['target_subarch=="arm32"', {
+ 'VALID_ARCHS': ['armv7', 'i386'],
+ }],
+ ['target_subarch=="arm64"', {
+ 'VALID_ARCHS': ['arm64', 'x86_64'],
+ }],
+ ['target_subarch=="both"', {
+ 'VALID_ARCHS': ['arm64', 'armv7', 'x86_64', 'i386'],
+ }],
+ ['use_system_libcxx==1', {
+ 'target_conditions': [
+ # Only use libc++ when building target for iOS not when building
+ # tools for the host (OS X) as Mac targets OS X SDK 10.6 which
+ # does not support libc++.
+ ['_toolset=="target"', {
+ 'CLANG_CXX_LIBRARY': 'libc++', # -stdlib=libc++
+ }]
+ ],
+ }, {
+ # The default for deployment target of 7.0+ is libc++, so force
+ # the old behavior unless libc++ is enabled.
+ 'CLANG_CXX_LIBRARY': 'libstdc++', # -stdlib=libstdc++
+ }],
+ ],
+ },
+ 'target_conditions': [
+ ['_toolset=="host"', {
+ 'xcode_settings': {
+ 'SDKROOT': 'macosx<(mac_sdk)', # -isysroot
+ 'MACOSX_DEPLOYMENT_TARGET': '<(mac_deployment_target)',
+ 'VALID_ARCHS': [
+ 'x86_64',
+ ],
+ 'ARCHS': [
+ 'x86_64',
+ ],
+ },
+ }],
+ ['_toolset=="target"', {
+ 'xcode_settings': {
+ # This section should be for overriding host settings. But,
+ # since we can't negate the iphone deployment target above, we
+ # instead set it here for target only.
+ 'IPHONEOS_DEPLOYMENT_TARGET': '<(ios_deployment_target)',
+ 'ARCHS': ['$(ARCHS_STANDARD_INCLUDING_64_BIT)'],
+ },
+ }],
+ ['_type=="executable"', {
+ 'configurations': {
+ 'Release_Base': {
+ 'xcode_settings': {
+ 'DEPLOYMENT_POSTPROCESSING': 'YES',
+ 'STRIP_INSTALLED_PRODUCT': 'YES',
+ },
+ },
+ 'Debug_Base': {
+ 'xcode_settings': {
+ # Remove dSYM to reduce build time.
+ 'DEBUG_INFORMATION_FORMAT': 'dwarf',
+ },
+ },
+ },
+ 'xcode_settings': {
+ 'conditions': [
+ ['chromium_ios_signing', {
+ # iOS SDK wants everything for device signed.
+ 'CODE_SIGN_IDENTITY[sdk=iphoneos*]': 'iPhone Developer',
+ }, {
+ 'CODE_SIGNING_REQUIRED': 'NO',
+ 'CODE_SIGN_IDENTITY[sdk=iphoneos*]': '',
+ }],
+ ],
+ },
+ }],
+ ], # target_conditions
+ }, # target_defaults
+ }], # OS=="ios"
+ ['OS=="win"', {
+ 'target_defaults': {
+ 'defines': [
+ '_WIN32_WINNT=0x0603',
+ 'WINVER=0x0603',
+ 'WIN32',
+ '_WINDOWS',
+ 'NOMINMAX',
+ 'PSAPI_VERSION=1',
+ '_CRT_RAND_S',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ '_ATL_NO_OPENGL',
+ '_SECURE_ATL',
+ # _HAS_EXCEPTIONS must match ExceptionHandling in msvs_settings.
+ '_HAS_EXCEPTIONS=0',
+ # Silence some warnings; we can't switch the the 'recommended'
+ # versions as they're not available on old OSs.
+ '_WINSOCK_DEPRECATED_NO_WARNINGS',
+ ],
+ 'conditions': [
+ ['buildtype=="Official"', {
+ # In official builds, targets can self-select an optimization
+ # level by defining a variable named 'optimize', and setting it
+ # to one of
+ # - "size", optimizes for minimal code size - the default.
+ # - "speed", optimizes for speed over code size.
+ # - "max", whole program optimization and link-time code
+ # generation. This is very expensive and should be used
+ # sparingly.
+ 'variables': {
+ 'optimize%': 'size',
+ },
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ # Set /LTCG for the official builds.
+ 'LinkTimeCodeGeneration': '1',
+ 'AdditionalOptions': [
+ # Set the number of LTCG code-gen threads to eight.
+ # The default is four. This gives a 5-10% link speedup.
+ '/cgthreads:8',
+ ],
+ },
+ },
+ 'target_conditions': [
+ ['optimize=="size"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # 1, optimizeMinSpace, Minimize Size (/O1)
+ 'Optimization': '1',
+ # 2, favorSize - Favor small code (/Os)
+ 'FavorSizeOrSpeed': '2',
+ },
+ },
+ },
+ ],
+ # This config is used to avoid a problem in ffmpeg, see
+ # http://crbug.com/264459.
+ ['optimize=="size_no_ltcg"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # 1, optimizeMinSpace, Minimize Size (/O1)
+ 'Optimization': '1',
+ # 2, favorSize - Favor small code (/Os)
+ 'FavorSizeOrSpeed': '2',
+ },
+ },
+ },
+ ],
+ ['optimize=="speed"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # 2, optimizeMaxSpeed, Maximize Speed (/O2)
+ 'Optimization': '2',
+ # 1, favorSpeed - Favor fast code (/Ot)
+ 'FavorSizeOrSpeed': '1',
+ },
+ },
+ },
+ ],
+ ['optimize=="max"', {
+ # Disable Warning 4702 ("Unreachable code") for the WPO/PGO
+ # builds. Probably anything that this would catch that
+ # wouldn't be caught in a normal build isn't going to
+ # actually be a bug, so the incremental value of C4702 for
+ # PGO builds is likely very small.
+ 'msvs_disabled_warnings': [
+ 4702
+ ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # 2, optimizeMaxSpeed, Maximize Speed (/O2)
+ 'Optimization': '2',
+ # 1, favorSpeed - Favor fast code (/Ot)
+ 'FavorSizeOrSpeed': '1',
+ # This implies link time code generation.
+ 'WholeProgramOptimization': 'true',
+ },
+ },
+ },
+ ],
+ ],
+ },
+ ],
+ ['msvs_xtree_patched!=1', {
+ # If xtree hasn't been patched, then we disable C4702. Otherwise,
+ # it's enabled. This will generally only be true for system-level
+ # installed Express users.
+ 'msvs_disabled_warnings': [
+ 4702,
+ ],
+ }],
+ ],
+ 'msvs_system_include_dirs': [
+ '<(windows_sdk_path)/Include/shared',
+ '<(windows_sdk_path)/Include/um',
+ '<(windows_sdk_path)/Include/winrt',
+ '$(VSInstallDir)/VC/atlmfc/include',
+ ],
+ 'msvs_cygwin_shell': 0,
+ 'msvs_disabled_warnings': [
+ # C4091: 'typedef ': ignored on left of 'X' when no variable is
+ # declared.
+ # This happens in a number of Windows headers. Dumb.
+ 4091,
+
+ # C4127: conditional expression is constant
+ # This warning can in theory catch dead code and other problems, but
+ # triggers in far too many desirable cases where the conditional
+ # expression is either set by macros or corresponds some legitimate
+ # compile-time constant expression (due to constant template args,
+ # conditionals comparing the sizes of different types, etc.). Some of
+ # these can be worked around, but it's not worth it.
+ 4127,
+
+ # C4351: new behavior: elements of array 'array' will be default
+ # initialized
+ # This is a silly "warning" that basically just alerts you that the
+ # compiler is going to actually follow the language spec like it's
+ # supposed to, instead of not following it like old buggy versions
+ # did. There's absolutely no reason to turn this on.
+ 4351,
+
+ # C4355: 'this': used in base member initializer list
+ # It's commonly useful to pass |this| to objects in a class'
+ # initializer list. While this warning can catch real bugs, most of
+ # the time the constructors in question don't attempt to call methods
+ # on the passed-in pointer (until later), and annotating every legit
+ # usage of this is simply more hassle than the warning is worth.
+ 4355,
+
+ # C4503: 'identifier': decorated name length exceeded, name was
+ # truncated
+ # This only means that some long error messages might have truncated
+ # identifiers in the presence of lots of templates. It has no effect
+ # on program correctness and there's no real reason to waste time
+ # trying to prevent it.
+ 4503,
+
+ # Warning C4589 says: "Constructor of abstract class ignores
+ # initializer for virtual base class." Disable this warning because it
+ # is flaky in VS 2015 RTM. It triggers on compiler generated
+ # copy-constructors in some cases.
+ 4589,
+
+ # C4611: interaction between 'function' and C++ object destruction is
+ # non-portable
+ # This warning is unavoidable when using e.g. setjmp/longjmp. MSDN
+ # suggests using exceptions instead of setjmp/longjmp for C++, but
+ # Chromium code compiles without exception support. We therefore have
+ # to use setjmp/longjmp for e.g. JPEG decode error handling, which
+ # means we have to turn off this warning (and be careful about how
+ # object destruction happens in such cases).
+ 4611,
+
+ # TODO(maruel): These warnings are level 4. They will be slowly
+ # removed as code is fixed.
+ 4100, # Unreferenced formal parameter
+ 4121, # Alignment of a member was sensitive to packing
+ 4244, # Conversion from 'type1' to 'type2', possible loss of data
+ 4481, # Nonstandard extension used: override specifier 'keyword'
+ 4505, # Unreferenced local function has been removed
+ 4510, # Default constructor could not be generated
+ 4512, # Assignment operator could not be generated
+ 4610, # Object can never be instantiated
+ 4838, # Narrowing conversion. Doesn't seem to be very useful.
+ 4996, # 'X': was declared deprecated (for GetVersionEx).
+
+ # These are variable shadowing warnings that are new in VS2015. We
+ # should work through these at some point -- they may be removed from
+ # the RTM release in the /W4 set.
+ 4456, 4457, 4458, 4459,
+ ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': ['/MP'],
+ 'MinimalRebuild': 'false',
+ 'BufferSecurityCheck': 'true',
+ 'EnableFunctionLevelLinking': 'true',
+ 'RuntimeTypeInfo': 'false',
+ 'WarningLevel': '4',
+ 'WarnAsError': 'true',
+ 'DebugInformationFormat': '3',
+ # ExceptionHandling must match _HAS_EXCEPTIONS above.
+ 'ExceptionHandling': '0',
+ },
+ 'VCLibrarianTool': {
+ 'AdditionalOptions': ['/ignore:4221'],
+ 'AdditionalLibraryDirectories': [
+ '<(windows_sdk_path)/Lib/win8/um/x86',
+ ],
+ },
+ 'VCLinkerTool': {
+ 'AdditionalDependencies': [
+ 'wininet.lib',
+ 'dnsapi.lib',
+ 'version.lib',
+ 'msimg32.lib',
+ 'ws2_32.lib',
+ 'usp10.lib',
+ 'psapi.lib',
+ 'dbghelp.lib',
+ 'winmm.lib',
+ 'shlwapi.lib',
+ ],
+ 'AdditionalLibraryDirectories': [
+ '<(windows_sdk_path)/Lib/win8/um/x86',
+ ],
+ 'GenerateDebugInformation': 'true',
+ 'MapFileName': '$(OutDir)\\$(TargetName).map',
+ 'ImportLibrary': '$(OutDir)\\lib\\$(TargetName).lib',
+ 'FixedBaseAddress': '1',
+ # SubSystem values:
+ # 0 == not set
+ # 1 == /SUBSYSTEM:CONSOLE
+ # 2 == /SUBSYSTEM:WINDOWS
+ # Most of the executables we'll ever create are tests
+ # and utilities with console output.
+ 'SubSystem': '1',
+ },
+ 'VCMIDLTool': {
+ 'GenerateStublessProxies': 'true',
+ 'TypeLibraryName': '$(InputName).tlb',
+ 'OutputDirectory': '$(IntDir)',
+ 'HeaderFileName': '$(InputName).h',
+ 'DLLDataFileName': '$(InputName).dlldata.c',
+ 'InterfaceIdentifierFileName': '$(InputName)_i.c',
+ 'ProxyFileName': '$(InputName)_p.c',
+ },
+ 'VCResourceCompilerTool': {
+ 'Culture' : '1033',
+ 'AdditionalIncludeDirectories': [
+ '<(DEPTH)',
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ },
+ 'target_conditions': [
+ ['_type=="executable"', {
+ 'VCManifestTool': {
+ 'EmbedManifest': 'true',
+ },
+ }],
+ ['_type=="executable" and ">(win_exe_compatibility_manifest)"!=""', {
+ 'VCManifestTool': {
+ 'AdditionalManifestFiles': [
+ '>(win_exe_compatibility_manifest)',
+ ],
+ },
+ }],
+ ],
+ 'conditions': [
+ # Building with Clang on Windows is a work in progress and very
+ # experimental. See crbug.com/82385.
+ # Keep this in sync with the similar blocks in build/config/compiler/BUILD.gn
+ ['clang==1', {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ # Many files use intrinsics without including this header.
+ # TODO(hans): Fix those files, or move this to sub-GYPs.
+ '/FIIntrin.h',
+
+ # TODO(hans): Make this list shorter eventually, http://crbug.com/504657
+ '-Qunused-arguments', # http://crbug.com/504658
+ '-Wno-microsoft', # http://crbug.com/505296
+ '-Wno-switch', # http://crbug.com/505308
+ '-Wno-unknown-pragmas', # http://crbug.com/505314
+ '-Wno-unused-function', # http://crbug.com/505316
+ '-Wno-unused-value', # http://crbug.com/505318
+ '-Wno-unused-local-typedef', # http://crbug.com/411648
+ ],
+ },
+ }],
+ ['clang==1 and target_arch=="ia32"', {
+ 'VCCLCompilerTool': {
+ 'WarnAsError': 'false',
+ 'AdditionalOptions': [
+ '/fallback',
+ ],
+ },
+ }],
+ ['clang==1 and clang_use_chrome_plugins==1', {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ '<@(clang_chrome_plugins_flags)',
+ ],
+ },
+ }],
+ ['clang==1 and MSVS_VERSION == "2013"', {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ '-fmsc-version=1800',
+ ],
+ },
+ }],
+ ['clang==1 and MSVS_VERSION == "2015"', {
+ 'VCCLCompilerTool': {
+ 'AdditionalOptions': [
+ '-fmsc-version=1900',
+ ],
+ },
+ }],
+ ['clang==1 and "=48 and clang==0', {
+ 'target_defaults': {
+ 'cflags': [
+ '-Wno-unused-local-typedefs',
+ ],
+ },
+ }],
+ ['gcc_version>=48 and clang==0 and host_clang==1', {
+ 'target_defaults': {
+ 'target_conditions': [
+ ['_toolset=="host"', { 'cflags!': [ '-Wno-unused-local-typedefs' ]}],
+ ],
+ },
+ }],
+ ['clang==1 and ((OS!="mac" and OS!="ios") or clang_xcode==0) '
+ 'and OS!="win"', {
+ 'make_global_settings': [
+ ['CC', '<(make_clang_dir)/bin/clang'],
+ ['CXX', '<(make_clang_dir)/bin/clang++'],
+ ['CC.host', '$(CC)'],
+ ['CXX.host', '$(CXX)'],
+ ],
+ }],
+ ['clang==1 and OS=="win"', {
+ 'make_global_settings': [
+ # On Windows, gyp's ninja generator only looks at CC.
+ ['CC', '<(make_clang_dir)/bin/clang-cl'],
+ ],
+ }],
+ ['use_lld==1 and OS=="win"', {
+ 'make_global_settings': [
+ # Limited to Windows because -flavor link2 is the driver that is
+ # compatible with link.exe.
+ ['LD', '<(make_clang_dir)/bin/lld -flavor link2'],
+ ],
+ }],
+ ['OS=="android" and clang==0', {
+ # Hardcode the compiler names in the Makefile so that
+ # it won't depend on the environment at make time.
+ 'make_global_settings': [
+ ['CC', ' version.
+
+
+def Usage(program_name):
+ print '%s MODE TOOL' % os.path.basename(program_name)
+ print 'MODE: host or target.'
+ print 'TOOL: assembler or compiler or linker.'
+ return 1
+
+
+def ParseArgs(args):
+ if len(args) != 2:
+ raise Exception('Invalid number of arguments')
+ mode = args[0]
+ tool = args[1]
+ if mode not in ('host', 'target'):
+ raise Exception('Invalid mode: %s' % mode)
+ if tool not in ('assembler', 'compiler', 'linker'):
+ raise Exception('Invalid tool: %s' % tool)
+ return mode, tool
+
+
+def GetEnvironFallback(var_list, default):
+ """Look up an environment variable from a possible list of variable names."""
+ for var in var_list:
+ if var in os.environ:
+ return os.environ[var]
+ return default
+
+
+def GetVersion(compiler, tool):
+ tool_output = tool_error = None
+ cache_key = (compiler, tool)
+ cached_version = compiler_version_cache.get(cache_key)
+ if cached_version:
+ return cached_version
+ try:
+ # Note that compiler could be something tricky like "distcc g++".
+ if tool == "compiler":
+ compiler = compiler + " -dumpversion"
+ # 4.6
+ version_re = re.compile(r"(\d+)\.(\d+)")
+ elif tool == "assembler":
+ compiler = compiler + " -Xassembler --version -x assembler -c /dev/null"
+ # Unmodified: GNU assembler (GNU Binutils) 2.24
+ # Ubuntu: GNU assembler (GNU Binutils for Ubuntu) 2.22
+ # Fedora: GNU assembler version 2.23.2
+ version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
+ elif tool == "linker":
+ compiler = compiler + " -Xlinker --version"
+ # Using BFD linker
+ # Unmodified: GNU ld (GNU Binutils) 2.24
+ # Ubuntu: GNU ld (GNU Binutils for Ubuntu) 2.22
+ # Fedora: GNU ld version 2.23.2
+ # Using Gold linker
+ # Unmodified: GNU gold (GNU Binutils 2.24) 1.11
+ # Ubuntu: GNU gold (GNU Binutils for Ubuntu 2.22) 1.11
+ # Fedora: GNU gold (version 2.23.2) 1.11
+ version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
+ else:
+ raise Exception("Unknown tool %s" % tool)
+
+ # Force the locale to C otherwise the version string could be localized
+ # making regex matching fail.
+ env = os.environ.copy()
+ env["LC_ALL"] = "C"
+ pipe = subprocess.Popen(compiler, shell=True, env=env,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ tool_output, tool_error = pipe.communicate()
+ if pipe.returncode:
+ raise subprocess.CalledProcessError(pipe.returncode, compiler)
+
+ parsed_output = version_re.match(tool_output)
+ result = parsed_output.group(1) + parsed_output.group(2)
+ compiler_version_cache[cache_key] = result
+ return result
+ except Exception, e:
+ if tool_error:
+ sys.stderr.write(tool_error)
+ print >> sys.stderr, "compiler_version.py failed to execute:", compiler
+ print >> sys.stderr, e
+ return ""
+
+
+def main(args):
+ try:
+ (mode, tool) = ParseArgs(args[1:])
+ except Exception, e:
+ sys.stderr.write(e.message + '\n\n')
+ return Usage(args[0])
+
+ ret_code, result = ExtractVersion(mode, tool)
+ if ret_code == 0:
+ print result
+ return ret_code
+
+
+def DoMain(args):
+ """Hook to be called from gyp without starting a separate python
+ interpreter."""
+ (mode, tool) = ParseArgs(args)
+ ret_code, result = ExtractVersion(mode, tool)
+ if ret_code == 0:
+ return result
+ raise Exception("Failed to extract compiler version for args: %s" % args)
+
+
+def ExtractVersion(mode, tool):
+ # Check if various CXX environment variables exist and use them if they
+ # exist. The preferences and fallback order is a close approximation of
+ # GenerateOutputForConfig() in GYP's ninja generator.
+ # The main difference being not supporting GYP's make_global_settings.
+ environments = ['CXX_target', 'CXX']
+ if mode == 'host':
+ environments = ['CXX_host'] + environments;
+ compiler = GetEnvironFallback(environments, 'c++')
+
+ if compiler:
+ compiler_version = GetVersion(compiler, tool)
+ if compiler_version != "":
+ return (0, compiler_version)
+ return (1, None)
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn
new file mode 100644
index 00000000000..0af90cd06d4
--- /dev/null
+++ b/build/config/BUILD.gn
@@ -0,0 +1,434 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/allocator.gni")
+import("//build/config/chrome_build.gni")
+import("//build/config/crypto.gni")
+import("//build/config/features.gni")
+import("//build/config/ui.gni")
+import("//build/module_args/v8.gni")
+
+declare_args() {
+ # When set, turns off the (normally-on) iterator debugging and related stuff
+ # that is normally turned on for Debug builds. These are generally useful for
+ # catching bugs but in some cases may cause conflicts or excessive slowness.
+ disable_iterator_debugging = false
+
+ # Set to true to not store any build metadata, e.g. ifdef out all __DATE__
+ # and __TIME__. Set to 0 to reenable the use of these macros in the code
+ # base. See http://crbug.com/314403.
+ #
+ # Continue to embed build meta data in Official builds, basically the
+ # time it was built.
+ # TODO(maruel): This decision should be revisited because having an
+ # official deterministic build has high value too but MSVC toolset can't
+ # generate anything deterministic with WPO enabled AFAIK.
+ dont_embed_build_metadata = !is_official_build
+
+ # Set to true to enable dcheck in Release builds.
+ dcheck_always_on = false
+
+ # Set to true to compile with the OpenGL ES 2.0 conformance tests.
+ internal_gles2_conform_tests = false
+}
+
+# TODO(brettw) Most of these should be removed. Instead of global feature
+# flags, we should have more modular flags that apply only to a target and its
+# dependents. For example, depending on the "x11" meta-target should define
+# USE_X11 for all dependents so that everything that could use X11 gets the
+# define, but anything that doesn't depend on X11 doesn't see it.
+#
+# For now we define these globally to match the current GYP build.
+config("feature_flags") {
+ # TODO(brettw) this probably needs to be parameterized.
+ defines = [ "V8_DEPRECATION_WARNINGS" ] # Don't use deprecated V8 APIs anywhere.
+
+ if (cld_version > 0) {
+ defines += [ "CLD_VERSION=$cld_version" ]
+ }
+ if (enable_mdns) {
+ defines += [ "ENABLE_MDNS=1" ]
+ }
+ if (enable_notifications) {
+ defines += [ "ENABLE_NOTIFICATIONS" ]
+ }
+ if (enable_pepper_cdms) {
+ # TODO(brettw) should probably be "=1"
+ defines += [ "ENABLE_PEPPER_CDMS" ]
+ }
+ if (enable_browser_cdms) {
+ # TODO(brettw) should probably be "=1"
+ defines += [ "ENABLE_BROWSER_CDMS" ]
+ }
+ if (enable_plugins) {
+ defines += [ "ENABLE_PLUGINS=1" ]
+ }
+ if (enable_basic_printing || enable_print_preview) {
+ # Convenience define for ENABLE_BASIC_PRINTING || ENABLE_PRINT_PREVIEW.
+ defines += [ "ENABLE_PRINTING=1" ]
+ if (enable_basic_printing) {
+ # Enable basic printing support and UI.
+ defines += [ "ENABLE_BASIC_PRINTING=1" ]
+ }
+ if (enable_print_preview) {
+ # Enable printing with print preview.
+ # Can be defined without ENABLE_BASIC_PRINTING.
+ defines += [ "ENABLE_PRINT_PREVIEW=1" ]
+ }
+ }
+ if (enable_spellcheck) {
+ defines += [ "ENABLE_SPELLCHECK=1" ]
+ }
+ if (use_platform_spellchecker) {
+ defines += [ "USE_PLATFORM_SPELLCHECKER=1" ]
+ }
+ if (dont_embed_build_metadata) {
+ defines += [ "DONT_EMBED_BUILD_METADATA" ]
+ }
+ if (dcheck_always_on) {
+ defines += [ "DCHECK_ALWAYS_ON=1" ]
+ }
+ if (use_udev) {
+ # TODO(brettw) should probably be "=1".
+ defines += [ "USE_UDEV" ]
+ }
+ if (ui_compositor_image_transport) {
+ # TODO(brettw) should probably be "=1".
+ defines += [ "UI_COMPOSITOR_IMAGE_TRANSPORT" ]
+ }
+ if (use_ash) {
+ defines += [ "USE_ASH=1" ]
+ }
+ if (use_aura) {
+ defines += [ "USE_AURA=1" ]
+ }
+ if (use_glfw) {
+ defines += [ "USE_GLFW=1" ]
+ }
+ if (use_pango) {
+ defines += [ "USE_PANGO=1" ]
+ }
+ if (use_cairo) {
+ defines += [ "USE_CAIRO=1" ]
+ }
+ if (use_clipboard_aurax11) {
+ defines += [ "USE_CLIPBOARD_AURAX11=1" ]
+ }
+ if (use_default_render_theme) {
+ defines += [ "USE_DEFAULT_RENDER_THEME=1" ]
+ }
+ if (use_openssl) {
+ defines += [ "USE_OPENSSL=1" ]
+ }
+ if (use_openssl_certs) {
+ defines += [ "USE_OPENSSL_CERTS=1" ]
+ }
+ if (use_nss_certs) {
+ defines += [ "USE_NSS_CERTS=1" ]
+ }
+ if (use_ozone) {
+ defines += [ "USE_OZONE=1" ]
+ }
+ if (use_x11) {
+ defines += [ "USE_X11=1" ]
+ }
+ if (use_allocator != "tcmalloc") {
+ defines += [ "NO_TCMALLOC" ]
+ }
+ if (is_asan || is_lsan || is_tsan || is_msan || is_ios) {
+ defines += [
+ "MEMORY_TOOL_REPLACES_ALLOCATOR",
+ "MEMORY_SANITIZER_INITIAL_SIZE",
+ ]
+ }
+ if (is_asan) {
+ defines += [ "ADDRESS_SANITIZER" ]
+ }
+ if (is_lsan) {
+ defines += [
+ "LEAK_SANITIZER",
+ "WTF_USE_LEAK_SANITIZER=1",
+ ]
+ }
+ if (is_tsan) {
+ defines += [
+ "THREAD_SANITIZER",
+ "DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL=1",
+ "WTF_USE_DYNAMIC_ANNOTATIONS_NOIMPL=1",
+ ]
+ }
+ if (is_msan) {
+ defines += [ "MEMORY_SANITIZER" ]
+ }
+ if (enable_webrtc) {
+ defines += [ "ENABLE_WEBRTC=1" ]
+ }
+ if (disable_ftp_support) {
+ defines += [ "DISABLE_FTP_SUPPORT=1" ]
+ }
+ if (!enable_nacl) {
+ defines += [ "DISABLE_NACL" ]
+ }
+ if (enable_extensions) {
+ defines += [ "ENABLE_EXTENSIONS=1" ]
+ }
+ if (enable_configuration_policy) {
+ defines += [ "ENABLE_CONFIGURATION_POLICY" ]
+ }
+ if (enable_task_manager) {
+ defines += [ "ENABLE_TASK_MANAGER=1" ]
+ }
+ if (enable_themes) {
+ defines += [ "ENABLE_THEMES=1" ]
+ }
+ if (enable_captive_portal_detection) {
+ defines += [ "ENABLE_CAPTIVE_PORTAL_DETECTION=1" ]
+ }
+ if (enable_session_service) {
+ defines += [ "ENABLE_SESSION_SERVICE=1" ]
+ }
+ if (enable_rlz) {
+ defines += [ "ENABLE_RLZ" ]
+ }
+ if (enable_plugin_installation) {
+ defines += [ "ENABLE_PLUGIN_INSTALLATION=1" ]
+ }
+ if (enable_app_list) {
+ defines += [ "ENABLE_APP_LIST=1" ]
+ }
+ if (enable_settings_app) {
+ defines += [ "ENABLE_SETTINGS_APP=1" ]
+ }
+ if (enable_supervised_users) {
+ defines += [ "ENABLE_SUPERVISED_USERS=1" ]
+ }
+ if (enable_service_discovery) {
+ defines += [ "ENABLE_SERVICE_DISCOVERY=1" ]
+ }
+ if (enable_autofill_dialog) {
+ defines += [ "ENABLE_AUTOFILL_DIALOG=1" ]
+ }
+ if (enable_wifi_bootstrapping) {
+ defines += [ "ENABLE_WIFI_BOOTSTRAPPING=1" ]
+ }
+ if (enable_image_loader_extension) {
+ defines += [ "IMAGE_LOADER_EXTENSION=1" ]
+ }
+ if (enable_remoting) {
+ defines += [ "ENABLE_REMOTING=1" ]
+ }
+ if (enable_google_now) {
+ defines += [ "ENABLE_GOOGLE_NOW=1" ]
+ }
+ if (enable_one_click_signin) {
+ defines += [ "ENABLE_ONE_CLICK_SIGNIN" ]
+ }
+ if (enable_hidpi) {
+ defines += [ "ENABLE_HIDPI=1" ]
+ }
+ if (enable_topchrome_md) {
+ defines += [ "ENABLE_TOPCHROME_MD=1" ]
+ }
+ if (proprietary_codecs) {
+ defines += [ "USE_PROPRIETARY_CODECS" ]
+ }
+ if (enable_hangout_services_extension) {
+ defines += [ "ENABLE_HANGOUT_SERVICES_EXTENSION=1" ]
+ }
+ if (v8_use_external_startup_data) {
+ defines += [ "V8_USE_EXTERNAL_STARTUP_DATA" ]
+ }
+ if (enable_background) {
+ defines += [ "ENABLE_BACKGROUND=1" ]
+ }
+ if (enable_pre_sync_backup) {
+ defines += [ "ENABLE_PRE_SYNC_BACKUP" ]
+ }
+ if (enable_video_hole) {
+ defines += [ "VIDEO_HOLE=1" ]
+ }
+ if (safe_browsing_mode == 1) {
+ defines += [ "FULL_SAFE_BROWSING" ]
+ defines += [ "SAFE_BROWSING_CSD" ]
+ defines += [ "SAFE_BROWSING_DB_LOCAL" ]
+ defines += [ "SAFE_BROWSING_SERVICE" ]
+ } else if (safe_browsing_mode == 2) {
+ defines += [ "MOBILE_SAFE_BROWSING" ]
+ defines += [ "SAFE_BROWSING_SERVICE" ]
+ } else if (safe_browsing_mode == 3) {
+ defines += [ "MOBILE_SAFE_BROWSING" ]
+ defines += [ "SAFE_BROWSING_DB_REMOTE" ]
+ defines += [ "SAFE_BROWSING_SERVICE" ]
+ }
+ if (is_official_build) {
+ defines += [ "OFFICIAL_BUILD" ]
+ }
+ if (is_chrome_branded) {
+ defines += [ "GOOGLE_CHROME_BUILD" ]
+ } else {
+ defines += [ "CHROMIUM_BUILD" ]
+ }
+ if (enable_media_router) {
+ defines += [ "ENABLE_MEDIA_ROUTER=1" ]
+ }
+ if (enable_webvr) {
+ defines += [ "ENABLE_WEBVR" ]
+ }
+ if (is_fnl) {
+ defines += [ "HAVE_SYS_QUEUE_H_=0" ]
+ }
+}
+
+# Debug/release ----------------------------------------------------------------
+
+config("debug") {
+ defines = [
+ "_DEBUG",
+ "DYNAMIC_ANNOTATIONS_ENABLED=1",
+ "WTF_USE_DYNAMIC_ANNOTATIONS=1",
+ ]
+
+ if (is_nacl) {
+ defines += [ "DYNAMIC_ANNOTATIONS_PREFIX=NACL_" ]
+ }
+
+ if (is_win) {
+ if (disable_iterator_debugging) {
+ # Iterator debugging is enabled by the compiler on debug builds, and we
+ # have to tell it to turn it off.
+ defines += [ "_HAS_ITERATOR_DEBUGGING=0" ]
+ }
+ } else if (is_linux && !is_android && current_cpu == "x64" &&
+ !disable_iterator_debugging) {
+ # Enable libstdc++ debugging facilities to help catch problems early, see
+ # http://crbug.com/65151 .
+ # TODO(phajdan.jr): Should we enable this for all of POSIX?
+ defines += [ "_GLIBCXX_DEBUG=1" ]
+ }
+}
+
+config("release") {
+ defines = [ "NDEBUG" ]
+
+ # Sanitizers.
+ # TODO(GYP) The GYP build has "release_valgrind_build == 0" for this
+ # condition. When Valgrind is set up, we need to do the same here.
+ if (is_tsan) {
+ defines += [
+ "DYNAMIC_ANNOTATIONS_ENABLED=1",
+ "WTF_USE_DYNAMIC_ANNOTATIONS=1",
+ ]
+ } else {
+ defines += [ "NVALGRIND" ]
+ if (!is_nacl) {
+ # NaCl always enables dynamic annotations. Currently this value is set to
+ # 1 for all .nexes.
+ defines += [ "DYNAMIC_ANNOTATIONS_ENABLED=0" ]
+ }
+ }
+}
+
+# Default libraries ------------------------------------------------------------
+
+# This config defines the default libraries applied to all targets.
+config("default_libs") {
+ if (is_win) {
+ # TODO(brettw) this list of defaults should probably be smaller, and
+ # instead the targets that use the less common ones (e.g. wininet or
+ # winspool) should include those explicitly.
+ libs = [
+ "advapi32.lib",
+ "comdlg32.lib",
+ "dbghelp.lib",
+ "delayimp.lib",
+ "dnsapi.lib",
+ "gdi32.lib",
+ "kernel32.lib",
+ "msimg32.lib",
+ "odbc32.lib",
+ "odbccp32.lib",
+ "ole32.lib",
+ "oleaut32.lib",
+ "psapi.lib",
+ "shell32.lib",
+ "shlwapi.lib",
+ "user32.lib",
+ "usp10.lib",
+ "uuid.lib",
+ "version.lib",
+ "wininet.lib",
+ "winmm.lib",
+ "winspool.lib",
+ "ws2_32.lib",
+
+ # Please don't add more stuff here. We should actually be making this
+ # list smaller, since all common things should be covered. If you need
+ # some extra libraries, please just add a libs = [ "foo.lib" ] to your
+ # target that needs it.
+ ]
+ } else if (is_android) {
+ # Android uses -nostdlib so we need to add even libc here.
+ libs = [
+ # TODO(brettw) write a version of this, hopefully we can express this
+ # without forking out to GCC just to get the library name. The android
+ # toolchain directory should probably be extracted into a .gni file that
+ # this file and the android toolchain .gn file can share.
+ # # Manually link the libgcc.a that the cross compiler uses.
+ # ' res/raw-en/en_gb.pak). Also generates
+# a locale_paks TypedArray so that resource files can be enumerated at runtime.
+#
+# If this target is included in the deps of an android resources/library/apk,
+# the resources will be included with that target.
+#
+# Variables:
+# sources: List of .pak files. Names must be of the form "en.pak" or
+# "en-US.pak".
+# deps: (optional) List of dependencies that might be needed to generate
+# the .pak files.
+#
+# Example
+# locale_pak_resources("locale_paks") {
+# sources = [ "path/en-US.pak", "path/fr.pak", ... ]
+# }
+template("locale_pak_resources") {
+ set_sources_assignment_filter([])
+ assert(defined(invoker.sources))
+
+ _base_path = "$target_gen_dir/$target_name"
+ _resources_zip = _base_path + ".resources.zip"
+ _build_config = _base_path + ".build_config"
+
+ write_build_config("${target_name}__build_config") {
+ build_config = _build_config
+ resources_zip = _resources_zip
+ type = "android_resources"
+ }
+
+ action("${target_name}__create_resources_zip") {
+ sources = invoker.sources
+ script = "//build/android/gyp/locale_pak_resources.py"
+ depfile = "$target_gen_dir/$target_name.d"
+
+ outputs = [
+ depfile,
+ _resources_zip,
+ ]
+
+ _rebased_sources = rebase_path(invoker.sources, root_build_dir)
+ args = [
+ "--locale-paks=${_rebased_sources}",
+ "--resources-zip",
+ rebase_path(_resources_zip, root_build_dir),
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ ]
+
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ }
+
+ group(target_name) {
+ deps = [
+ ":${target_name}__build_config",
+ ":${target_name}__create_resources_zip",
+ ]
+ }
+}
+
+# Declare an Android resources target
+#
+# This creates a resources zip file that will be used when building an Android
+# library or apk and included into a final apk.
+#
+# To include these resources in a library/apk, this target should be listed in
+# the library's deps. A library/apk will also include any resources used by its
+# own dependencies.
+#
+# Variables
+# deps: Specifies the dependencies of this target. Any Android resources
+# listed in deps will be included by libraries/apks that depend on this
+# target.
+# resource_dirs: List of directories containing resources for this target.
+# android_manifest: AndroidManifest.xml for this target. Defaults to
+# //build/android/AndroidManifest.xml.
+# custom_package: java package for generated .java files.
+# v14_skip: If true, don't run v14 resource generator on this. Defaults to
+# false. (see build/android/gyp/generate_v14_compatible_resources.py)
+#
+# shared_resources: If true make a resource package that can be loaded by a
+# different application at runtime to access the package's resources.
+#
+
+# Example
+# android_resources("foo_resources") {
+# deps = [":foo_strings_grd"]
+# resource_dirs = ["res"]
+# custom_package = "org.chromium.foo"
+# }
+template("android_resources") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ assert(defined(invoker.resource_dirs))
+ assert(defined(invoker.android_manifest) || defined(invoker.custom_package))
+
+ base_path = "$target_gen_dir/$target_name"
+ zip_path = base_path + ".resources.zip"
+ srcjar_path = base_path + ".srcjar"
+ r_text_path = base_path + "_R.txt"
+ build_config = base_path + ".build_config"
+
+ build_config_target_name = "${target_name}__build_config"
+ process_resources_target_name = "${target_name}__process_resources"
+ final_target_name = target_name
+
+ write_build_config(build_config_target_name) {
+ visibility = [ ":$process_resources_target_name" ]
+
+ type = "android_resources"
+ resources_zip = zip_path
+ srcjar = srcjar_path
+ r_text = r_text_path
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.android_manifest)) {
+ android_manifest = invoker.android_manifest
+ }
+ if (defined(invoker.custom_package)) {
+ custom_package = invoker.custom_package
+ }
+ }
+
+ android_manifest = "//build/android/AndroidManifest.xml"
+ if (defined(invoker.android_manifest)) {
+ android_manifest = invoker.android_manifest
+ }
+
+ process_resources(process_resources_target_name) {
+ visibility = [ ":$final_target_name" ]
+
+ resource_dirs = invoker.resource_dirs
+ if (defined(invoker.custom_package)) {
+ custom_package = invoker.custom_package
+ }
+
+ if (defined(invoker.v14_skip)) {
+ v14_skip = invoker.v14_skip
+ }
+
+ if (defined(invoker.shared_resources)) {
+ shared_resources = invoker.shared_resources
+ }
+
+ deps = [
+ ":$build_config_target_name",
+ ]
+ if (defined(invoker.deps)) {
+ # Invoker may have added deps that generate the input resources.
+ deps += invoker.deps
+ }
+ }
+
+ group(final_target_name) {
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ deps = [
+ ":${target_name}__process_resources",
+ ]
+ }
+}
+
+# Declare a target that generates localized strings.xml from a .grd file.
+#
+# If this target is included in the deps of an android resources/library/apk,
+# the strings.xml will be included with that target.
+#
+# Variables
+# deps: Specifies the dependencies of this target.
+# grd_file: Path to the .grd file to generate strings.xml from.
+# outputs: Expected grit outputs (see grit rule).
+#
+# Example
+# java_strings_grd("foo_strings_grd") {
+# grd_file = "foo_strings.grd"
+# }
+template("java_strings_grd") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ base_path = "$target_gen_dir/$target_name"
+ resources_zip = base_path + ".resources.zip"
+ build_config = base_path + ".build_config"
+
+ write_build_config("${target_name}__build_config") {
+ type = "android_resources"
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ }
+
+ # Put grit files into this subdirectory of target_gen_dir.
+ extra_output_path = target_name + "_grit_output"
+
+ grit_target_name = "${target_name}__grit"
+ grit_output_dir = "$target_gen_dir/$extra_output_path"
+ grit(grit_target_name) {
+ grit_flags = [
+ "-E",
+ "ANDROID_JAVA_TAGGED_ONLY=false",
+ ]
+ output_dir = grit_output_dir
+ resource_ids = ""
+ source = invoker.grd_file
+ outputs = invoker.outputs
+ }
+
+ # This needs to get outputs from grit's internal target, not the final
+ # source_set.
+ generate_strings_outputs = get_target_outputs(":${grit_target_name}_grit")
+
+ zip("${target_name}__zip") {
+ base_dir = grit_output_dir
+ inputs = generate_strings_outputs
+ output = resources_zip
+ deps = [
+ ":$grit_target_name",
+ ]
+ }
+
+ group(target_name) {
+ deps = [
+ ":${target_name}__build_config",
+ ":${target_name}__zip",
+ ]
+ }
+}
+
+# Declare a target that packages strings.xml generated from a grd file.
+#
+# If this target is included in the deps of an android resources/library/apk,
+# the strings.xml will be included with that target.
+#
+# Variables
+# grit_output_dir: directory containing grit-generated files.
+# generated_files: list of android resource files to package.
+#
+# Example
+# java_strings_grd_prebuilt("foo_strings_grd") {
+# grit_output_dir = "$root_gen_dir/foo/grit"
+# generated_files = [
+# "values/strings.xml"
+# ]
+# }
+template("java_strings_grd_prebuilt") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ base_path = "$target_gen_dir/$target_name"
+ resources_zip = base_path + ".resources.zip"
+ build_config = base_path + ".build_config"
+
+ build_config_target_name = "${target_name}__build_config"
+ zip_target_name = "${target_name}__zip"
+ final_target_name = target_name
+
+ write_build_config(build_config_target_name) {
+ visibility = [ ":$zip_target_name" ]
+ type = "android_resources"
+ }
+
+ zip(zip_target_name) {
+ visibility = [ ":$final_target_name" ]
+
+ base_dir = invoker.grit_output_dir
+ inputs = rebase_path(invoker.generated_files, ".", base_dir)
+ output = resources_zip
+ deps = [
+ ":$build_config_target_name",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+
+ group(final_target_name) {
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ deps = [
+ ":$zip_target_name",
+ ]
+ }
+}
+
+# Declare a Java executable target
+#
+# This target creates an executable from java code and libraries. The executable
+# will be in the output folder's /bin/ directory.
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be included in the executable (and the javac classpath).
+#
+# java_files: List of .java files included in this library.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this library.
+# srcjars: List of srcjars to be included in this library, together with the
+# ones obtained from srcjar_deps.
+#
+# bypass_platform_checks: Disables checks about cross-platform (Java/Android)
+# dependencies for this target. This will allow depending on an
+# android_library target, for example.
+#
+# chromium_code: If true, extra analysis warning/errors will be enabled.
+# enable_errorprone: If true, enables the errorprone compiler.
+#
+# data_deps, testonly
+#
+# Example
+# java_binary("foo") {
+# java_files = [ "org/chromium/foo/FooMain.java" ]
+# deps = [ ":bar_java" ]
+# main_class = "org.chromium.foo.FooMain"
+# }
+template("java_binary") {
+ set_sources_assignment_filter([])
+
+ # TODO(cjhopman): This should not act like a java_library for dependents (i.e.
+ # dependents shouldn't get the jar in their classpath, etc.).
+ java_library_impl(target_name) {
+ if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ }
+ if (defined(invoker.chromium_code)) {
+ chromium_code = invoker.chromium_code
+ }
+ if (defined(invoker.data_deps)) {
+ deps = invoker.data_deps
+ }
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.enable_errorprone)) {
+ enable_errorprone = invoker.enable_errorprone
+ }
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ }
+ if (defined(invoker.srcjar_deps)) {
+ srcjar_deps = invoker.srcjar_deps
+ }
+ if (defined(invoker.srcjars)) {
+ srcjars = invoker.srcjars
+ }
+ if (defined(invoker.bypass_platform_checks)) {
+ bypass_platform_checks = invoker.bypass_platform_checks
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ supports_android = false
+ main_class = invoker.main_class
+ }
+}
+
+# Declare a Junit executable target
+#
+# This target creates an executable from java code for running as a junit test
+# suite. The executable will be in the output folder's /bin/ directory.
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be included in the executable (and the javac classpath).
+#
+# java_files: List of .java files included in this library.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this library.
+# srcjars: List of srcjars to be included in this library, together with the
+# ones obtained from srcjar_deps.
+#
+# chromium_code: If true, extra analysis warning/errors will be enabled.
+#
+# Example
+# junit_binary("foo") {
+# java_files = [ "org/chromium/foo/FooTest.java" ]
+# deps = [ ":bar_java" ]
+# }
+template("junit_binary") {
+ set_sources_assignment_filter([])
+
+ java_binary(target_name) {
+ bypass_platform_checks = true
+ main_class = "org.chromium.testing.local.JunitTestMain"
+ testonly = true
+
+ if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ }
+ if (defined(invoker.chromium_code)) {
+ chromium_code = invoker.chromium_code
+ }
+ deps = [
+ "//testing/android/junit:junit_test_support",
+ "//third_party/junit",
+ "//third_party/mockito:mockito_java",
+ "//third_party/robolectric:robolectric_java",
+ "//third_party/robolectric:android-all-4.3_r2-robolectric-0",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ }
+ if (defined(invoker.srcjar_deps)) {
+ srcjar_deps = invoker.srcjar_deps
+ }
+ if (defined(invoker.srcjars)) {
+ srcjars = invoker.srcjars
+ }
+ }
+}
+
+# Declare a java library target
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be added to the javac classpath.
+#
+# java_files: List of .java files included in this library.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this library.
+# srcjars: List of srcjars to be included in this library, together with the
+# ones obtained from srcjar_deps.
+# DEPRECATED_java_in_dir: Directory containing java files. All .java files in
+# this directory will be included in the library. This is only supported to
+# ease the gyp->gn conversion and will be removed in the future.
+#
+# chromium_code: If true, extra analysis warning/errors will be enabled.
+# enable_errorprone: If true, enables the errorprone compiler.
+#
+# jar_excluded_patterns: List of patterns of .class files to exclude from the
+# final jar.
+#
+# proguard_preprocess: If true, proguard preprocessing will be run. This can
+# be used to remove unwanted parts of the library.
+# proguard_config: Path to the proguard config for preprocessing.
+#
+# supports_android: If true, Android targets (android_library, android_apk)
+# may depend on this target. Note: if true, this target must only use the
+# subset of Java available on Android.
+# bypass_platform_checks: Disables checks about cross-platform (Java/Android)
+# dependencies for this target. This will allow depending on an
+# android_library target, for example.
+#
+# data_deps, testonly
+#
+# Example
+# java_library("foo_java") {
+# java_files = [
+# "org/chromium/foo/Foo.java",
+# "org/chromium/foo/FooInterface.java",
+# "org/chromium/foo/FooService.java",
+# ]
+# deps = [
+# ":bar_java"
+# ]
+# srcjar_deps = [
+# ":foo_generated_enum"
+# ]
+# jar_excluded_patterns = [
+# "*/FooService.class", "*/FooService##*.class"
+# ]
+# }
+template("java_library") {
+ set_sources_assignment_filter([])
+ java_library_impl(target_name) {
+ if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ }
+ if (defined(invoker.chromium_code)) {
+ chromium_code = invoker.chromium_code
+ }
+ if (defined(invoker.data_deps)) {
+ deps = invoker.data_deps
+ }
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.enable_errorprone)) {
+ enable_errorprone = invoker.enable_errorprone
+ }
+ if (defined(invoker.jar_excluded_patterns)) {
+ jar_excluded_patterns = invoker.jar_excluded_patterns
+ }
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ }
+ if (defined(invoker.proguard_config)) {
+ proguard_config = invoker.proguard_config
+ }
+ if (defined(invoker.proguard_preprocess)) {
+ proguard_preprocess = invoker.proguard_preprocess
+ }
+ if (defined(invoker.srcjar_deps)) {
+ srcjar_deps = invoker.srcjar_deps
+ }
+ if (defined(invoker.srcjars)) {
+ srcjars = invoker.srcjars
+ }
+ if (defined(invoker.bypass_platform_checks)) {
+ bypass_platform_checks = invoker.bypass_platform_checks
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.jar_path)) {
+ jar_path = invoker.jar_path
+ }
+
+ if (defined(invoker.supports_android) && invoker.supports_android) {
+ supports_android = true
+ }
+ }
+}
+
+# Declare a java library target for a prebuilt jar
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be added to the javac classpath.
+# jar_path: Path to the prebuilt jar.
+# jar_dep: Target that builds jar_path (optional).
+# proguard_preprocess: If true, proguard preprocessing will be run. This can
+# be used to remove unwanted parts of the library.
+# proguard_config: Path to the proguard config for preprocessing.
+#
+# Example
+# java_prebuilt("foo_java") {
+# jar_path = "foo.jar"
+# deps = [
+# ":foo_resources",
+# ":bar_java"
+# ]
+# }
+template("java_prebuilt") {
+ set_sources_assignment_filter([])
+ java_prebuilt_impl(target_name) {
+ jar_path = invoker.jar_path
+ if (defined(invoker.jar_dep)) {
+ jar_dep = invoker.jar_dep
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+ if (defined(invoker.proguard_config)) {
+ proguard_config = invoker.proguard_config
+ }
+ if (defined(invoker.proguard_preprocess)) {
+ proguard_preprocess = invoker.proguard_preprocess
+ }
+ }
+}
+
+# Declare an Android library target
+#
+# This target creates an Android library containing java code and Android
+# resources.
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be added to the javac classpath. Android resources in dependencies
+# will be used when building this library.
+#
+# java_files: List of .java files included in this library.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this library.
+# srcjars: List of srcjars to be included in this library, together with the
+# ones obtained from srcjar_deps.
+# DEPRECATED_java_in_dir: Directory containing java files. All .java files in
+# this directory will be included in the library. This is only supported to
+# ease the gyp->gn conversion and will be removed in the future.
+#
+# chromium_code: If true, extra analysis warning/errors will be enabled.
+# enable_errorprone: If true, enables the errorprone compiler.
+#
+# jar_excluded_patterns: List of patterns of .class files to exclude from the
+# final jar.
+#
+# proguard_preprocess: If true, proguard preprocessing will be run. This can
+# be used to remove unwanted parts of the library.
+# proguard_config: Path to the proguard config for preprocessing.
+#
+# dex_path: If set, the resulting .dex.jar file will be placed under this
+# path.
+#
+#
+# Example
+# android_library("foo_java") {
+# java_files = [
+# "android/org/chromium/foo/Foo.java",
+# "android/org/chromium/foo/FooInterface.java",
+# "android/org/chromium/foo/FooService.java",
+# ]
+# deps = [
+# ":bar_java"
+# ]
+# srcjar_deps = [
+# ":foo_generated_enum"
+# ]
+# jar_excluded_patterns = [
+# "*/FooService.class", "*/FooService##*.class"
+# ]
+# }
+template("android_library") {
+ set_sources_assignment_filter([])
+ assert(!defined(invoker.jar_path),
+ "android_library does not support a custom jar path")
+ java_library_impl(target_name) {
+ if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ }
+ if (defined(invoker.chromium_code)) {
+ chromium_code = invoker.chromium_code
+ }
+ if (defined(invoker.data_deps)) {
+ deps = invoker.data_deps
+ }
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.enable_errorprone)) {
+ enable_errorprone = invoker.enable_errorprone
+ }
+ if (defined(invoker.jar_excluded_patterns)) {
+ jar_excluded_patterns = invoker.jar_excluded_patterns
+ }
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ }
+ if (defined(invoker.proguard_config)) {
+ proguard_config = invoker.proguard_config
+ }
+ if (defined(invoker.proguard_preprocess)) {
+ proguard_preprocess = invoker.proguard_preprocess
+ }
+ if (defined(invoker.srcjar_deps)) {
+ srcjar_deps = invoker.srcjar_deps
+ }
+ if (defined(invoker.srcjars)) {
+ srcjars = invoker.srcjars
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ if (defined(invoker.dex_path)) {
+ dex_path = invoker.dex_path
+ }
+ if (defined(invoker.manifest_entries)) {
+ manifest_entries = invoker.manifest_entries
+ }
+
+ supports_android = true
+ requires_android = true
+
+ if (!defined(jar_excluded_patterns)) {
+ jar_excluded_patterns = []
+ }
+ jar_excluded_patterns += [
+ "*/R.class",
+ "*/R##*.class",
+ "*/Manifest.class",
+ "*/Manifest##*.class",
+ ]
+ }
+}
+
+# Declare a target that packages a set of Java dependencies into a standalone
+# .dex.jar.
+#
+# Variables
+# deps: specifies the dependencies of this target. Android libraries in deps
+# will be packaged into the resulting .dex.jar file.
+# dex_path: location at which the output file will be put
+template("android_standalone_library") {
+ set_sources_assignment_filter([])
+ deps_dex(target_name) {
+ deps = invoker.deps
+ dex_path = invoker.dex_path
+ if (defined(invoker.excluded_jars)) {
+ excluded_jars = invoker.excluded_jars
+ }
+ }
+}
+
+# Declare an Android library target for a prebuilt jar
+#
+# This target creates an Android library containing java code and Android
+# resources.
+#
+# Variables
+# deps: Specifies the dependencies of this target. Java targets in this list
+# will be added to the javac classpath. Android resources in dependencies
+# will be used when building this library.
+# jar_path: Path to the prebuilt jar.
+# proguard_preprocess: If true, proguard preprocessing will be run. This can
+# be used to remove unwanted parts of the library.
+# proguard_config: Path to the proguard config for preprocessing.
+#
+# Example
+# android_java_prebuilt("foo_java") {
+# jar_path = "foo.jar"
+# deps = [
+# ":foo_resources",
+# ":bar_java"
+# ]
+# }
+template("android_java_prebuilt") {
+ set_sources_assignment_filter([])
+ java_prebuilt_impl(target_name) {
+ jar_path = invoker.jar_path
+ supports_android = true
+ requires_android = true
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+ if (defined(invoker.proguard_config)) {
+ proguard_config = invoker.proguard_config
+ }
+ if (defined(invoker.proguard_preprocess)) {
+ proguard_preprocess = invoker.proguard_preprocess
+ }
+ }
+}
+
+# Declare an Android apk target
+#
+# This target creates an Android APK containing java code, resources, assets,
+# and (possibly) native libraries.
+#
+# Variables
+# android_manifest: Path to AndroidManifest.xml.
+# android_manifest_dep: Target that generates AndroidManifest (if applicable)
+# data_deps: List of dependencies needed at runtime. These will be built but
+# won't change the generated .apk in any way (in fact they may be built
+# after the .apk is).
+# deps: List of dependencies. All Android java resources and libraries in the
+# "transitive closure" of these dependencies will be included in the apk.
+# Note: this "transitive closure" actually only includes such targets if
+# they are depended on through android_library or android_resources targets
+# (and so not through builtin targets like 'action', 'group', etc).
+# java_files: List of .java files to include in the apk.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this apk.
+# apk_name: Name for final apk.
+# final_apk_path: Path to final built apk. Default is
+# $root_out_dir/apks/$apk_name.apk. Setting this will override apk_name.
+# native_libs: List paths of native libraries to include in this apk. If these
+# libraries depend on other shared_library targets, those dependencies will
+# also be included in the apk.
+# apk_under_test: For an instrumentation test apk, this is the target of the
+# tested apk.
+# include_all_resources - If true include all resource IDs in all generated
+# R.java files.
+# testonly: Marks this target as "test-only".
+#
+# DEPRECATED_java_in_dir: Directory containing java files. All .java files in
+# this directory will be included in the library. This is only supported to
+# ease the gyp->gn conversion and will be removed in the future.
+#
+# Example
+# android_apk("foo_apk") {
+# android_manifest = "AndroidManifest.xml"
+# java_files = [
+# "android/org/chromium/foo/FooApplication.java",
+# "android/org/chromium/foo/FooActivity.java",
+# ]
+# deps = [
+# ":foo_support_java"
+# ":foo_resources"
+# ]
+# srcjar_deps = [
+# ":foo_generated_enum"
+# ]
+# native_libs = [
+# native_lib_path
+# ]
+# }
+template("android_apk") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ assert(defined(invoker.final_apk_path) || defined(invoker.apk_name))
+ assert(defined(invoker.android_manifest))
+ gen_dir = "$target_gen_dir/$target_name"
+ base_path = "$gen_dir/$target_name"
+ _build_config = "$target_gen_dir/$target_name.build_config"
+ resources_zip_path = "$base_path.resources.zip"
+ _all_resources_zip_path = "$base_path.resources.all.zip"
+ jar_path = "$base_path.jar"
+ _template_name = target_name
+
+ final_dex_path = "$gen_dir/classes.dex"
+ final_dex_target_name = "${_template_name}__final_dex"
+
+ _final_apk_path = ""
+ if (defined(invoker.final_apk_path)) {
+ _final_apk_path = invoker.final_apk_path
+ } else if (defined(invoker.apk_name)) {
+ _final_apk_path = "$root_build_dir/apks/" + invoker.apk_name + ".apk"
+ }
+ _dist_jar_path_list =
+ process_file_template(
+ [ _final_apk_path ],
+ "$root_build_dir/test.lib.java/{{source_name_part}}.jar")
+ _dist_jar_path = _dist_jar_path_list[0]
+
+ _native_libs = []
+
+ _version_code = "1"
+ if (defined(invoker.version_code)) {
+ _version_code = invoker.version_code
+ }
+
+ _version_name = "Developer Build"
+ if (defined(invoker.version_name)) {
+ _version_name = invoker.version_name
+ }
+ _keystore_path = android_default_keystore_path
+ _keystore_name = android_default_keystore_name
+ _keystore_password = android_default_keystore_password
+
+ if (defined(invoker.keystore_path)) {
+ _keystore_path = invoker.keystore_path
+ _keystore_name = invoker.keystore_name
+ _keystore_password = invoker.keystore_password
+ }
+
+ _srcjar_deps = []
+ if (defined(invoker.srcjar_deps)) {
+ _srcjar_deps += invoker.srcjar_deps
+ }
+
+ _load_library_from_apk = false
+
+ # The dependency that makes the chromium linker, if any is needed.
+ _chromium_linker_dep = []
+
+ if (defined(invoker.native_libs)) {
+ _use_chromium_linker = false
+ if (defined(invoker.use_chromium_linker)) {
+ _use_chromium_linker =
+ invoker.use_chromium_linker && chromium_linker_supported
+ _chromium_linker_dep = [ "//base/android/linker:chromium_android_linker" ]
+ }
+
+ if (defined(invoker.load_library_from_apk) &&
+ invoker.load_library_from_apk) {
+ _load_library_from_apk = true
+ assert(_use_chromium_linker,
+ "Loading library from the apk requires use" +
+ " of the Chromium linker.")
+ }
+
+ _enable_relocation_packing = false
+ if (defined(invoker.enable_relocation_packing) &&
+ invoker.enable_relocation_packing) {
+ _enable_relocation_packing = relocation_packing_supported
+ assert(_use_chromium_linker,
+ "Relocation packing requires use of the" + " Chromium linker.")
+ }
+
+ if (is_component_build) {
+ _native_libs += [ "$root_out_dir/lib.stripped/libc++_shared.so" ]
+ _chromium_linker_dep += [ "//build/android:cpplib_stripped" ]
+ }
+
+ # Allow native_libs to be in the form "foo.so" or "foo.cr.so"
+ _first_ext_removed =
+ process_file_template(invoker.native_libs, "{{source_name_part}}")
+ _native_libs += process_file_template(
+ _first_ext_removed,
+ "$root_build_dir/lib.stripped/{{source_name_part}}$android_product_extension")
+
+ _native_libs_dir = base_path + "/libs"
+
+ if (_use_chromium_linker) {
+ _native_libs += [ "$root_build_dir/lib.stripped/libchromium_android_linker$android_product_extension" ]
+ }
+
+ _enable_relocation_packing = false
+ if (_use_chromium_linker && defined(invoker.enable_relocation_packing) &&
+ invoker.enable_relocation_packing) {
+ _enable_relocation_packing = true
+ }
+
+ _native_lib_version_rule = ""
+ if (defined(invoker.native_lib_version_rule)) {
+ _native_lib_version_rule = invoker.native_lib_version_rule
+ }
+ _native_lib_version_arg = "\"\""
+ if (defined(invoker.native_lib_version_arg)) {
+ _native_lib_version_arg = invoker.native_lib_version_arg
+ }
+ }
+
+ _android_manifest_deps = []
+ if (defined(invoker.android_manifest_dep)) {
+ _android_manifest_deps = [ invoker.android_manifest_dep ]
+ }
+ _android_manifest = invoker.android_manifest
+
+ _rebased_build_config = rebase_path(_build_config, root_build_dir)
+ _create_abi_split =
+ defined(invoker.create_abi_split) && invoker.create_abi_split
+ _create_density_splits =
+ defined(invoker.create_density_splits) && invoker.create_density_splits
+
+ # Help GN understand that _create_abi_split is not unused (bug in GN).
+ assert(_create_abi_split || true)
+
+ build_config_target = "${_template_name}__build_config"
+ write_build_config(build_config_target) {
+ type = "android_apk"
+ dex_path = final_dex_path
+ resources_zip = resources_zip_path
+ build_config = _build_config
+ android_manifest = _android_manifest
+
+ deps = _chromium_linker_dep + _android_manifest_deps
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+
+ if (defined(invoker.apk_under_test)) {
+ apk_under_test = invoker.apk_under_test
+ }
+
+ native_libs = _native_libs
+ }
+
+ final_deps = []
+
+ process_resources_target = "${_template_name}__process_resources"
+ final_deps += [ ":$process_resources_target" ]
+ process_resources(process_resources_target) {
+ srcjar_path = "${target_gen_dir}/${target_name}.srcjar"
+ r_text_path = "${target_gen_dir}/${target_name}_R.txt"
+ android_manifest = _android_manifest
+ resource_dirs = [ "//build/android/ant/empty/res" ]
+ zip_path = resources_zip_path
+ all_resources_zip_path = _all_resources_zip_path
+ generate_constant_ids = true
+
+ if (defined(invoker.include_all_resources)) {
+ include_all_resources = invoker.include_all_resources
+ }
+
+ build_config = _build_config
+ deps = _android_manifest_deps + [ ":$build_config_target" ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+ _srcjar_deps += [ ":$process_resources_target" ]
+
+ if (_native_libs != []) {
+ _enable_chromium_linker_tests = false
+ if (defined(invoker.enable_chromium_linker_tests)) {
+ _enable_chromium_linker_tests = invoker.enable_chromium_linker_tests
+ }
+
+ java_cpp_template("${_template_name}__native_libraries_java") {
+ package_name = "org/chromium/base/library_loader"
+ sources = [
+ "//base/android/java/templates/NativeLibraries.template",
+ ]
+ inputs = [
+ _build_config,
+ ]
+ deps = [
+ ":$build_config_target",
+ ]
+ if (_native_lib_version_rule != "") {
+ deps += [ _native_lib_version_rule ]
+ }
+
+ defines = [
+ "NATIVE_LIBRARIES_LIST=" +
+ "@FileArg($_rebased_build_config:native:java_libraries_list)",
+ "NATIVE_LIBRARIES_VERSION_NUMBER=$_native_lib_version_arg",
+ ]
+ if (_use_chromium_linker) {
+ defines += [ "ENABLE_CHROMIUM_LINKER" ]
+ }
+ if (_load_library_from_apk) {
+ defines += [ "ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE" ]
+ }
+ if (_enable_chromium_linker_tests) {
+ defines += [ "ENABLE_CHROMIUM_LINKER_TESTS" ]
+ }
+ }
+ _srcjar_deps += [ ":${_template_name}__native_libraries_java" ]
+ }
+
+ java_target = "${_template_name}__java"
+ final_deps += [ ":$java_target" ]
+ java_library_impl(java_target) {
+ supports_android = true
+ requires_android = true
+ override_build_config = _build_config
+ deps = _android_manifest_deps + [ ":$build_config_target" ]
+
+ android_manifest = _android_manifest
+ chromium_code = true
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ } else if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ } else {
+ java_files = []
+ }
+ srcjar_deps = _srcjar_deps
+ dex_path = base_path + ".dex.jar"
+
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+
+ if (_dist_jar_path != "") {
+ create_dist_target = "${_template_name}__create_dist_jar"
+ final_deps += [ ":$create_dist_target" ]
+
+ # TODO(cjhopman): This is only ever needed to calculate the list of tests to
+ # run. See build/android/pylib/instrumentation/test_jar.py. We should be
+ # able to just do that calculation at build time instead.
+ action(create_dist_target) {
+ script = "//build/android/gyp/create_dist_jar.py"
+ depfile = "$target_gen_dir/$target_name.d"
+ inputs = [
+ _build_config,
+ ]
+ outputs = [
+ depfile,
+ _dist_jar_path,
+ ]
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--output",
+ rebase_path(_dist_jar_path, root_build_dir),
+ "--inputs=@FileArg($_rebased_build_config:dist_jar:dependency_jars)",
+ ]
+ inputs += [ jar_path ]
+ _rebased_jar_path = rebase_path([ jar_path ], root_build_dir)
+ args += [ "--inputs=$_rebased_jar_path" ]
+ deps = [
+ ":$build_config_target", # Generates the build config file.
+ ":$java_target", # Generates the jar file.
+ ]
+ }
+ }
+
+ final_deps += [ ":$final_dex_target_name" ]
+ dex("${final_dex_target_name}_jar") {
+ deps = [
+ ":$build_config_target",
+ ":$java_target",
+ ]
+ sources = [
+ jar_path,
+ ]
+ inputs = [
+ _build_config,
+ ]
+ output = "${final_dex_path}.jar"
+ dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
+ args = [ "--inputs=@FileArg($dex_arg_key)" ]
+ }
+
+ dex("$final_dex_target_name") {
+ deps = [
+ ":${final_dex_target_name}_jar",
+ ]
+ sources = [
+ "${final_dex_path}.jar",
+ ]
+ output = final_dex_path
+ }
+
+ if (_native_libs != []) {
+ action("${_template_name}__prepare_native") {
+ script = "//build/android/gyp/pack_relocations.py"
+ packed_libraries_dir = "$_native_libs_dir/$android_app_abi"
+ depfile = "$target_gen_dir/$target_name.d"
+ outputs = [
+ depfile,
+ ]
+
+ inputs = _native_libs
+ deps = _chromium_linker_dep
+
+ inputs += [ _build_config ]
+ deps += [ ":$build_config_target" ]
+
+ skip_packing_list = [
+ "gdbserver",
+ "libchromium_android_linker$android_product_extension",
+ ]
+
+ enable_packing_arg = 0
+ if (_enable_relocation_packing) {
+ enable_packing_arg = 1
+ deps += [ relocation_packer_target ]
+ }
+
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--enable-packing=$enable_packing_arg",
+ "--exclude-packing-list=$skip_packing_list",
+ "--android-pack-relocations",
+ rebase_path(relocation_packer_exe, root_build_dir),
+ "--stripped-libraries-dir",
+ rebase_path(root_build_dir, root_build_dir),
+ "--packed-libraries-dir",
+ rebase_path(packed_libraries_dir, root_build_dir),
+ "--libraries=@FileArg(${_rebased_build_config}:native:libraries)",
+ "--clear-dir",
+ ]
+
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.public_deps)) {
+ public_deps = invoker.public_deps
+ }
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+
+ if (is_debug) {
+ rebased_gdbserver = rebase_path([ android_gdbserver ], root_build_dir)
+ inputs += [ android_gdbserver ]
+ args += [ "--libraries=$rebased_gdbserver" ]
+ }
+ }
+ }
+
+ final_deps += [ ":${_template_name}__create" ]
+ create_apk("${_template_name}__create") {
+ apk_path = _final_apk_path
+ android_manifest = _android_manifest
+ resources_zip = _all_resources_zip_path
+ dex_path = final_dex_path
+ load_library_from_apk = _load_library_from_apk
+ create_density_splits = _create_density_splits
+ if (defined(invoker.language_splits)) {
+ language_splits = invoker.language_splits
+ }
+ if (defined(invoker.extensions_to_not_compress)) {
+ extensions_to_not_compress = invoker.extensions_to_not_compress
+ } else {
+ # Allow icu data, v8 snapshots, and pak files to be loaded directly from
+ # the .apk.
+ # Note: These are actually suffix matches, not necessarily extensions.
+ extensions_to_not_compress = ".dat,.bin,.pak"
+ }
+
+ version_code = _version_code
+ version_name = _version_name
+
+ keystore_name = _keystore_name
+ keystore_path = _keystore_path
+ keystore_password = _keystore_password
+
+ # This target generates the input file _all_resources_zip_path.
+ deps = _android_manifest_deps + [
+ ":$process_resources_target",
+ ":$final_dex_target_name",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+
+ if (defined(invoker.asset_location)) {
+ asset_location = invoker.asset_location
+
+ # We don't know the exact dependencies that create the assets in
+ # |asset_location|; we depend on all caller deps until a better solution
+ # is figured out (http://crbug.com/433330).
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+
+ if (_native_libs != [] && !_create_abi_split) {
+ native_libs_dir = _native_libs_dir
+ deps += [ ":${_template_name}__prepare_native" ]
+ }
+ }
+
+ if (_native_libs != [] && _create_abi_split) {
+ _manifest_rule = "${_template_name}__split_manifest_abi_${android_app_abi}"
+ generate_split_manifest(_manifest_rule) {
+ main_manifest = _android_manifest
+ out_manifest =
+ "$gen_dir/split-manifests/${android_app_abi}/AndroidManifest.xml"
+ split_name = "abi_${android_app_abi}"
+ deps = _android_manifest_deps
+ }
+
+ _apk_rule = "${_template_name}__split_apk_abi_${android_app_abi}"
+ final_deps += [ ":$_apk_rule" ]
+ create_apk(_apk_rule) {
+ _split_paths = process_file_template(
+ [ _final_apk_path ],
+ "{{source_dir}}/{{source_name_part}}-abi-${android_app_abi}.apk")
+ apk_path = _split_paths[0]
+ base_path = "$gen_dir/$_apk_rule"
+
+ manifest_outputs = get_target_outputs(":${_manifest_rule}")
+ android_manifest = manifest_outputs[1]
+ load_library_from_apk = _load_library_from_apk
+
+ version_code = _version_code
+ version_name = _version_name
+
+ keystore_name = _keystore_name
+ keystore_path = _keystore_path
+ keystore_password = _keystore_password
+
+ native_libs_dir = _native_libs_dir
+ deps = [
+ ":${_template_name}__prepare_native",
+ ":${_manifest_rule}",
+ ]
+ }
+ }
+
+ if (defined(invoker.flutter_dist_jar)) {
+ flutter_jar_target = "${_template_name}__create_flutter_jar"
+ final_deps += [ ":$flutter_jar_target" ]
+
+ action(flutter_jar_target) {
+ script = "//build/android/gyp/create_flutter_jar.py"
+ depfile = "$target_gen_dir/$target_name.d"
+ inputs = [
+ _dist_jar_path,
+ ]
+ outputs = [
+ invoker.flutter_dist_jar,
+ ]
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--output",
+ rebase_path(invoker.flutter_dist_jar, root_build_dir),
+ "--dist_jar",
+ rebase_path(_dist_jar_path, root_build_dir),
+ "--android_abi",
+ "$android_app_abi",
+ "--asset_dir",
+ rebase_path(invoker.asset_location, root_build_dir),
+ ]
+ foreach(native_lib, rebase_path(_native_libs, root_build_dir)) {
+ args += [
+ "--native_lib",
+ native_lib,
+ ]
+ }
+ deps = [
+ ":$create_dist_target",
+ ":${_template_name}__prepare_native"
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+ }
+
+ group(target_name) {
+ deps = final_deps
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+ }
+}
+
+# Declare an Android instrumentation test apk
+#
+# This target creates an Android instrumentation test apk.
+#
+# Variables
+# android_manifest: Path to AndroidManifest.xml.
+# data_deps: List of dependencies needed at runtime. These will be built but
+# won't change the generated .apk in any way (in fact they may be built
+# after the .apk is).
+# deps: List of dependencies. All Android java resources and libraries in the
+# "transitive closure" of these dependencies will be included in the apk.
+# Note: this "transitive closure" actually only includes such targets if
+# they are depended on through android_library or android_resources targets
+# (and so not through builtin targets like 'action', 'group', etc).
+# java_files: List of .java files to include in the apk.
+# srcjar_deps: List of srcjar dependencies. The .java files in the srcjars
+# will be added to java_files and be included in this apk.
+# apk_name: Name for final apk.
+# support_apk_path: Path to a support apk. If present, the test runner script
+# will install it on the device before running the instrumentation tests.
+# Should be a path relative to the src root.
+# final_apk_path: Path to final built apk. Default is
+# $root_out_dir/apks/$apk_name.apk. Setting this will override apk_name.
+# native_libs: List paths of native libraries to include in this apk. If these
+# libraries depend on other shared_library targets, those dependencies will
+# also be included in the apk.
+# apk_under_test: The apk being tested.
+# isolate_file: Isolate file containing the list of test data dependencies.
+#
+# DEPRECATED_java_in_dir: Directory containing java files. All .java files in
+# this directory will be included in the library. This is only supported to
+# ease the gyp->gn conversion and will be removed in the future.
+#
+# Example
+# instrumentation_test_apk("foo_test_apk") {
+# android_manifest = "AndroidManifest.xml"
+# apk_name = "FooTest"
+# apk_under_test = "Foo"
+# java_files = [
+# "android/org/chromium/foo/FooTestCase.java",
+# "android/org/chromium/foo/FooExampleTest.java",
+# ]
+# deps = [
+# ":foo_test_support_java"
+# ]
+# }
+template("instrumentation_test_apk") {
+ set_sources_assignment_filter([])
+ testonly = true
+ _template_name = target_name
+
+ if (defined(invoker.apk_name)) {
+ test_runner_data_dep = [ ":${_template_name}__test_runner_script" ]
+ test_runner_script("${_template_name}__test_runner_script") {
+ test_name = invoker.target_name
+ test_type = "instrumentation"
+ test_apk = invoker.apk_name
+ if (defined(invoker.isolate_file)) {
+ isolate_file = invoker.isolate_file
+ }
+ if (defined(invoker.support_apk_path)) {
+ support_apk_path = invoker.support_apk_path
+ }
+ }
+ }
+
+ android_apk(target_name) {
+ if (defined(invoker.android_manifest)) {
+ android_manifest = invoker.android_manifest
+ }
+ data_deps = [
+ "//testing/android/driver:driver_apk",
+ "//tools/android/forwarder2",
+ "//tools/android/md5sum",
+ ]
+ if (defined(test_runner_data_dep)) {
+ data_deps += test_runner_data_dep
+ }
+ if (defined(invoker.data_deps)) {
+ data_deps += invoker.data_deps
+ }
+ deps = [
+ "//testing/android/broker:broker_java",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.java_files)) {
+ java_files = invoker.java_files
+ }
+ if (defined(invoker.srcjar_deps)) {
+ srcjar_deps = invoker.srcjar_deps
+ }
+ if (defined(invoker.apk_name)) {
+ apk_name = invoker.apk_name
+ }
+ if (defined(invoker.final_apk_path)) {
+ final_apk_path = invoker.final_apk_path
+ }
+ if (defined(invoker.native_libs)) {
+ native_libs = invoker.native_libs
+ }
+ if (defined(invoker.apk_under_test)) {
+ apk_under_test = invoker.apk_under_test
+ }
+ if (defined(invoker.DEPRECATED_java_in_dir)) {
+ DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
+ }
+ }
+}
+
+# Declare an Android gtest apk
+#
+# This target creates an Android apk for running gtest-based unittests.
+#
+# Variables
+# deps: Specifies the dependencies of this target. These will be passed to
+# the underlying android_apk invocation and should include the java and
+# resource dependencies of the apk.
+# unittests_dep: This should be the label of the gtest native target. This
+# target must be defined previously in the same file.
+# unittests_binary: The basename of the library produced by the unittests_dep
+# target. If unspecified, it assumes the name of the unittests_dep target
+# (which will be correct unless that target specifies an "output_name".
+# TODO(brettw) make this automatic by allowing get_target_outputs to
+# support executables.
+# apk_name: The name of the produced apk. If unspecified, it uses the name
+# of the unittests_dep target postfixed with "_apk"
+#
+# Example
+# unittest_apk("foo_unittests_apk") {
+# deps = [ ":foo_java", ":foo_resources" ]
+# unittests_dep = ":foo_unittests"
+# }
+template("unittest_apk") {
+ set_sources_assignment_filter([])
+ testonly = true
+
+ assert(defined(invoker.unittests_dep), "Need unittests_dep for $target_name")
+
+ test_suite_name = get_label_info(invoker.unittests_dep, "name")
+
+ # This trivial assert is needed in case both unittests_binary and apk_name
+ # are defined, as otherwise test_suite_name would not be used.
+ assert(test_suite_name != "")
+
+ if (defined(invoker.unittests_binary)) {
+ unittests_binary = invoker.unittests_binary
+ } else {
+ unittests_binary = "lib${test_suite_name}${android_product_extension}"
+ }
+
+ if (defined(invoker.apk_name)) {
+ apk_name = invoker.apk_name
+ } else {
+ apk_name = test_suite_name
+ }
+
+ android_apk(target_name) {
+ final_apk_path = "$root_build_dir/${apk_name}_apk/${apk_name}-debug.apk"
+ java_files = [
+ "//testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java",
+ "//testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java",
+ "//testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java",
+ "//testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java",
+ ]
+ android_manifest = "//testing/android/native_test/java/AndroidManifest.xml"
+ native_libs = [ unittests_binary ]
+ if (defined(invoker.asset_location)) {
+ asset_location = invoker.asset_location
+ }
+ deps = [
+ "//base:base_java",
+ "//build/android/pylib/remote/device/dummy:remote_device_dummy_apk",
+ "//testing/android/appurify_support:appurify_support_java",
+ "//testing/android/reporter:reporter_java",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ data_deps = [ "//tools/android/md5sum" ]
+ if (host_os == "linux") {
+ data_deps += [ "//tools/android/forwarder2" ]
+ }
+ if (defined(invoker.data_deps)) {
+ data_deps += invoker.data_deps
+ }
+ }
+}
+
+# Generate .java files from .aidl files.
+#
+# This target will store the .java files in a srcjar and should be included in
+# an android_library or android_apk's srcjar_deps.
+#
+# Variables
+# sources: Paths to .aidl files to compile.
+# import_include: Path to directory containing .java files imported by the
+# .aidl files.
+# interface_file: Preprocessed aidl file to import.
+#
+# Example
+# android_aidl("foo_aidl") {
+# import_include = "java/src"
+# sources = [
+# "java/src/com/foo/bar/FooBarService.aidl",
+# "java/src/com/foo/bar/FooBarServiceCallback.aidl",
+# ]
+# }
+template("android_aidl") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ srcjar_path = "${target_gen_dir}/${target_name}.srcjar"
+ aidl_path = "${android_sdk_build_tools}/aidl"
+ framework_aidl = "$android_sdk/framework.aidl"
+
+ action(target_name) {
+ script = "//build/android/gyp/aidl.py"
+ sources = invoker.sources
+
+ imports = [ framework_aidl ]
+ if (defined(invoker.interface_file)) {
+ assert(invoker.interface_file != "")
+ imports += [ invoker.interface_file ]
+ }
+
+ inputs = [ aidl_path ] + imports
+
+ depfile = "${target_gen_dir}/${target_name}.d"
+ outputs = [
+ depfile,
+ srcjar_path,
+ ]
+ rebased_imports = rebase_path(imports, root_build_dir)
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--aidl-path",
+ rebase_path(aidl_path, root_build_dir),
+ "--imports=$rebased_imports",
+ "--srcjar",
+ rebase_path(srcjar_path, root_build_dir),
+ ]
+ if (defined(invoker.import_include) && invoker.import_include != "") {
+ # TODO(cjhopman): aidl supports creating a depfile. We should be able to
+ # switch to constructing a depfile for the overall action from that
+ # instead of having all the .java files in the include paths as inputs.
+ rebased_import_includes =
+ rebase_path([ invoker.import_include ], root_build_dir)
+ args += [ "--includes=$rebased_import_includes" ]
+
+ _java_files_build_rel =
+ exec_script("//build/android/gyp/find.py",
+ rebase_path([ invoker.import_include ], root_build_dir),
+ "list lines")
+ _java_files = rebase_path(_java_files_build_rel, ".", root_build_dir)
+ inputs += _java_files
+ }
+ args += rebase_path(sources, root_build_dir)
+ }
+}
+
+# Creates a dist directory for a native executable.
+#
+# Running a native executable on a device requires all the shared library
+# dependencies of that executable. To make it easier to install and run such an
+# executable, this will create a directory containing the native exe and all
+# it's library dependencies.
+#
+# Note: It's usually better to package things as an APK than as a native
+# executable.
+#
+# Variables
+# dist_dir: Directory for the exe and libraries. Everything in this directory
+# will be deleted before copying in the exe and libraries.
+# binary: Path to (stripped) executable.
+#
+# Example
+# create_native_executable_dist("foo_dist") {
+# dist_dir = "$root_build_dir/foo_dist"
+# binary = "$root_build_dir/exe.stripped/foo"
+# deps = [ ":the_thing_that_makes_foo" ]
+# }
+template("create_native_executable_dist") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ dist_dir = invoker.dist_dir
+ binary = invoker.binary
+ template_name = target_name
+
+ libraries_list =
+ "${target_gen_dir}/${template_name}_library_dependencies.list"
+
+ find_deps_target_name = "${template_name}__find_library_dependencies"
+ copy_target_name = "${template_name}__copy_libraries_and_exe"
+
+ stripped_libraries_dir = "$root_build_dir/lib.stripped"
+ action(find_deps_target_name) {
+ visibility = [ ":$copy_target_name" ]
+
+ script = "//build/android/gyp/write_ordered_libraries.py"
+ depfile = "$target_gen_dir/$target_name.d"
+ inputs = [
+ binary,
+ android_readelf,
+ ]
+ outputs = [
+ depfile,
+ libraries_list,
+ ]
+ rebased_binaries = rebase_path([ binary ], root_build_dir)
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--input-libraries=$rebased_binaries",
+ "--libraries-dir",
+ rebase_path(stripped_libraries_dir, root_build_dir),
+ "--output",
+ rebase_path(libraries_list, root_build_dir),
+ "--readelf",
+ rebase_path(android_readelf, root_build_dir),
+ ]
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ }
+
+ copy_ex(copy_target_name) {
+ visibility = [ ":$template_name" ]
+
+ clear_dir = true
+ inputs = [
+ binary,
+ libraries_list,
+ ]
+ dest = dist_dir
+ rebased_binaries_list = rebase_path([ binary ], root_build_dir)
+ rebased_libraries_list = rebase_path(libraries_list, root_build_dir)
+ args = [
+ "--files=$rebased_binaries_list",
+ "--files=@FileArg($rebased_libraries_list:lib_paths)",
+ ]
+
+ deps = [
+ ":$find_deps_target_name",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ }
+
+ group(template_name) {
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ deps = [
+ ":$copy_target_name",
+ ]
+ }
+}
+
+# Compile a protocol buffer to java.
+#
+# This generates java files from protocol buffers and creates an Android library
+# containing the classes.
+#
+# Variables
+# sources: Paths to .proto files to compile.
+# proto_path: Root directory of .proto files.
+#
+# Example:
+# proto_java_library("foo_proto_java") {
+# proto_path = [ "src/foo" ]
+# sources = [ "$proto_path/foo.proto" ]
+# }
+template("proto_java_library") {
+ set_sources_assignment_filter([])
+ _protoc_dep = "//third_party/android_protobuf:android_protoc($host_toolchain)"
+ _protoc_out_dir = get_label_info(_protoc_dep, "root_out_dir")
+ _protoc_bin = "$_protoc_out_dir/android_protoc"
+ _proto_path = invoker.proto_path
+
+ _template_name = target_name
+
+ action("${_template_name}__protoc_java") {
+ srcjar_path = "$target_gen_dir/$target_name.srcjar"
+ script = "//build/protoc_java.py"
+ deps = [
+ _protoc_dep,
+ ]
+ sources = invoker.sources
+ depfile = "$target_gen_dir/$target_name.d"
+ outputs = [
+ depfile,
+ srcjar_path,
+ ]
+ args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--protoc",
+ rebase_path(_protoc_bin, root_build_dir),
+ "--proto-path",
+ rebase_path(_proto_path, root_build_dir),
+ "--srcjar",
+ rebase_path(srcjar_path, root_build_dir),
+ ] + rebase_path(sources, root_build_dir)
+ }
+
+ android_library(target_name) {
+ java_files = []
+ srcjar_deps = [ ":${_template_name}__protoc_java" ]
+ deps = [
+ "//third_party/android_protobuf:protobuf_nano_javalib",
+ ]
+ }
+}
+
+# TODO(GYP): implement this.
+template("uiautomator_test") {
+ set_sources_assignment_filter([])
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ assert(target_name != "")
+ assert(invoker.deps != [] || true)
+ group(target_name) {
+ }
+}
diff --git a/build/config/arm.gni b/build/config/arm.gni
new file mode 100644
index 00000000000..429a250b21d
--- /dev/null
+++ b/build/config/arm.gni
@@ -0,0 +1,78 @@
+# Copyright 2014 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.
+
+if (current_cpu == "arm" || current_cpu == "arm64") {
+ declare_args() {
+ # Version of the ARM processor when compiling on ARM. Ignored on non-ARM
+ # platforms.
+ if (current_cpu == "arm") {
+ arm_version = 7
+ } else if(current_cpu == "arm64") {
+ arm_version = 8
+ } else {
+ assert(false, "Unconfigured arm version")
+ }
+
+ # The ARM floating point mode. This is either the string "hard", "soft", or
+ # "softfp". An empty string means to use the default one for the
+ # arm_version.
+ arm_float_abi = ""
+
+ # The ARM variant-specific tuning mode. This will be a string like "armv6"
+ # or "cortex-a15". An empty string means to use the default for the
+ # arm_version.
+ arm_tune = ""
+
+ # Whether to use the neon FPU instruction set or not.
+ arm_use_neon = true
+
+ # Whether to enable optional NEON code paths.
+ arm_optionally_use_neon = false
+
+ if (is_android) {
+ arm_use_neon = false
+ arm_optionally_use_neon = true
+ }
+
+ if (is_ios) {
+ arm_use_neon = false
+ arm_optionally_use_neon = false
+ }
+ }
+
+ assert(arm_float_abi == "" || arm_float_abi == "hard" ||
+ arm_float_abi == "soft" || arm_float_abi == "softfp")
+
+ if (arm_version == 6) {
+ arm_arch = "armv6"
+ if (arm_tune != "") {
+ arm_tune = ""
+ }
+ if (arm_float_abi == "") {
+ arm_float_abi = "softfp"
+ }
+ arm_fpu = "vfp"
+
+ # Thumb is a reduced instruction set available on some ARM processors that
+ # has increased code density.
+ arm_use_thumb = false
+ } else if (arm_version == 7) {
+ arm_arch = "armv7-a"
+ if (arm_tune == "") {
+ arm_tune = "generic-armv7-a"
+ }
+
+ if (arm_float_abi == "") {
+ arm_float_abi = "softfp"
+ }
+
+ arm_use_thumb = true
+
+ if (arm_use_neon) {
+ arm_fpu = "neon"
+ } else {
+ arm_fpu = "vfpv3-d16"
+ }
+ }
+}
diff --git a/build/config/chrome_build.gni b/build/config/chrome_build.gni
new file mode 100644
index 00000000000..c2132c4a162
--- /dev/null
+++ b/build/config/chrome_build.gni
@@ -0,0 +1,19 @@
+# Copyright 2015 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.
+
+declare_args() {
+ # Selects the desired build flavor. Official builds get additional
+ # processing to prepare for release. Normally you will want to develop and
+ # test with this flag off.
+ is_official_build = false
+
+ # Select the desired branding flavor. False means normal Chromium branding,
+ # true means official Google Chrome branding (requires extra Google-internal
+ # resources).
+ is_chrome_branded = false
+
+ # Break chrome.dll into multple pieces based on process type. Only available
+ # on Windows.
+ is_multi_dll_chrome = is_win && !is_component_build
+}
diff --git a/build/config/clang/BUILD.gn b/build/config/clang/BUILD.gn
new file mode 100644
index 00000000000..6dff4863a5e
--- /dev/null
+++ b/build/config/clang/BUILD.gn
@@ -0,0 +1,62 @@
+# Copyright (c) 2013 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.
+
+import("clang.gni")
+
+config("find_bad_constructs") {
+ if (clang_use_chrome_plugins) {
+ cflags = []
+
+ # On Windows, the plugin is built directly into clang, so there's
+ # no need to load it dynamically.
+
+ if (is_mac || is_ios) {
+ cflags += [
+ "-Xclang",
+ "-load",
+ "-Xclang",
+ rebase_path(
+ "//third_party/llvm-build/Release+Asserts/lib/libFindBadConstructs.dylib",
+ root_build_dir),
+ ]
+ } else if (is_linux) {
+ cflags += [
+ "-Xclang",
+ "-load",
+ "-Xclang",
+ rebase_path(
+ "//third_party/llvm-build/Release+Asserts/lib/libFindBadConstructs.so",
+ root_build_dir),
+ ]
+ }
+
+ # https://crbug.com/441916
+ if (is_android || is_linux || is_mac) {
+ cflags += [
+ "-Xclang",
+ "-plugin-arg-find-bad-constructs",
+ "-Xclang",
+ "check-templates",
+ ]
+ }
+
+ cflags += [
+ "-Xclang",
+ "-add-plugin",
+ "-Xclang",
+ "find-bad-constructs",
+ ]
+ }
+}
+
+# Enables some extra Clang-specific warnings. Some third-party code won't
+# compile with these so may want to remove this config.
+config("extra_warnings") {
+ cflags = [
+ "-Wheader-hygiene",
+
+ # Warns when a const char[] is converted to bool.
+ "-Wstring-conversion",
+ ]
+}
diff --git a/build/config/clang/clang.gni b/build/config/clang/clang.gni
new file mode 100644
index 00000000000..cb84879466f
--- /dev/null
+++ b/build/config/clang/clang.gni
@@ -0,0 +1,9 @@
+# Copyright 2014 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.
+
+declare_args() {
+ # Indicates if the build should use the Chrome-specific plugins for enforcing
+ # coding guidelines, etc. Only used when compiling with Clang.
+ clang_use_chrome_plugins = is_clang && !is_nacl
+}
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
new file mode 100644
index 00000000000..04d4bd9c5f5
--- /dev/null
+++ b/build/config/compiler/BUILD.gn
@@ -0,0 +1,1243 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/android/config.gni")
+import("//build/config/chrome_build.gni")
+if (current_cpu == "arm") {
+ import("//build/config/arm.gni")
+}
+if (current_cpu == "mipsel" || current_cpu == "mips64el") {
+ import("//build/config/mips.gni")
+}
+if (is_posix) {
+ import("//build/config/gcc/gcc_version.gni")
+}
+if (is_win) {
+ import("//build/config/win/visual_studio_version.gni")
+}
+
+import("//build/toolchain/ccache.gni")
+import("//build/config/sanitizers/sanitizers.gni")
+
+declare_args() {
+ # Normally, Android builds are lightly optimized, even for debug builds, to
+ # keep binary size down. Setting this flag to true disables such optimization
+ android_full_debug = false
+
+ # Whether to use the binary binutils checked into third_party/binutils.
+ # These are not multi-arch so cannot be used except on x86 and x86-64 (the
+ # only two architectures that are currently checked in). Turn this off when
+ # you are using a custom toolchain and need to control -B in cflags.
+ linux_use_bundled_binutils = is_linux && current_cpu == "x64"
+
+ # Compile in such a way as to enable profiling of the generated code. For
+ # example, don't omit the frame pointer and leave in symbols.
+ enable_profiling = false
+
+ # Compile in such a way as to make it possible for the profiler to unwind full
+ # stack frames. Setting this flag has a large effect on the performance of the
+ # generated code than just setting profiling, but gives the profiler more
+ # information to analyze.
+ # Requires profiling to be set to true.
+ enable_full_stack_frames_for_profiling = false
+
+ # Use gold for linking on 64-bit Linux only (on 32-bit it runs out of
+ # address space, and it doesn't support cross-compiling).
+ use_gold = is_linux && current_cpu == "x64"
+
+ # use_debug_fission: whether to use split DWARF debug info
+ # files. This can reduce link time significantly, but is incompatible
+ # with some utilities such as icecc and ccache. Requires gold and
+ # gcc >= 4.8 or clang.
+ # http://gcc.gnu.org/wiki/DebugFission
+ use_debug_fission = is_debug && !is_win && use_gold &&
+ linux_use_bundled_binutils && !use_ccache
+
+ if (is_win) {
+ # Whether the VS xtree header has been patched to disable warning 4702. If
+ # it has, then we don't need to disable 4702 (unreachable code warning).
+ # The patch is preapplied to the internal toolchain and hence all bots.
+ msvs_xtree_patched = false
+ }
+}
+
+# default_include_dirs ---------------------------------------------------------
+#
+# This is a separate config so that third_party code (which would not use the
+# source root and might have conflicting versions of some headers) can remove
+# this and specify their own include paths.
+config("default_include_dirs") {
+ include_dirs = [
+ "//",
+ root_gen_dir,
+ ]
+}
+
+# TODO(GYP): is_ubsan, is_ubsan_vptr
+if (!is_win) {
+ using_sanitizer = is_asan || is_lsan || is_tsan || is_msan
+}
+
+# compiler ---------------------------------------------------------------------
+#
+# Base compiler configuration.
+#
+# See also "runtime_library" below for related stuff and a discussion about
+# where stuff should go. Put warning related stuff in the "warnings" config.
+
+config("compiler") {
+ cflags = []
+ cflags_c = []
+ cflags_cc = []
+ cflags_objcc = []
+ ldflags = []
+ defines = []
+
+ # In general, Windows is totally different, but all the other builds share
+ # some common GCC configuration. This section sets up Windows and the common
+ # GCC flags, and then we handle the other non-Windows platforms specifically
+ # below.
+ if (is_win) {
+ # Windows compiler flags setup.
+ # -----------------------------
+ cflags += [
+ "/Gy", # Enable function-level linking.
+ "/GS", # Enable buffer security checking.
+ "/FS", # Preserve previous PDB behavior.
+ ]
+
+ # Building with Clang on Windows is a work in progress and very
+ # experimental. See crbug.com/82385.
+ # Keep this in sync with the similar block in build/common.gypi
+ if (is_clang) {
+ cflags += [
+ # Many files use intrinsics without including this header.
+ # TODO(hans): Fix those files, or move this to sub-GYPs.
+ "/FIIntrin.h",
+ ]
+
+ if (visual_studio_version == "2013") {
+ cflags += [ "-fmsc-version=1800" ]
+ } else if (visual_studio_version == "2015") {
+ cflags += [ "-fmsc-version=1900" ]
+ }
+
+ if (current_cpu == "x86") {
+ cflags += [
+ "/fallback",
+ "-m32",
+ ]
+ } else {
+ cflags += [ "-m64" ]
+ }
+ if (exec_script("//build/win/use_ansi_codes.py", [], "trim string") ==
+ "True") {
+ cflags += [
+ # cmd.exe doesn't understand ANSI escape codes by default,
+ # so only enable them if something emulating them is around.
+ "-fansi-escape-codes",
+ ]
+ }
+ }
+ } else {
+ # Common GCC compiler flags setup.
+ # --------------------------------
+ cflags += [ "-fno-strict-aliasing" ] # See http://crbug.com/32204
+ common_flags = [
+ # Not exporting C++ inline functions can generally be applied anywhere
+ # so we do so here. Normal function visibility is controlled by
+ # //build/config/gcc:symbol_visibility_hidden.
+ "-fvisibility-inlines-hidden",
+ ]
+ cflags_cc += common_flags
+ cflags_objcc += common_flags
+
+ # Stack protection.
+ if (is_mac) {
+ cflags += [ "-fstack-protector-all" ]
+ } else if (is_linux) {
+ cflags += [
+ "-fstack-protector",
+ "--param=ssp-buffer-size=4",
+ ]
+ }
+
+ # Linker warnings.
+ if (!(is_chromeos && current_cpu == "arm") && !is_mac && !is_ios) {
+ # TODO(jochen): Enable this on chromeos on arm. http://crbug.com/356580
+ ldflags += [ "-Wl,--fatal-warnings" ]
+ }
+
+ # Common options for AddressSanitizer, LeakSanitizer, ThreadSanitizer and
+ # MemorySanitizer
+ if (using_sanitizer) {
+ cflags += [
+ "-fno-omit-frame-pointer",
+ "-gline-tables-only",
+ ]
+ }
+ if (is_asan) {
+ asan_blacklist_path =
+ rebase_path("//tools/memory/asan/blacklist.txt", root_build_dir)
+ cflags += [
+ "-fsanitize=address",
+ "-fsanitize-blacklist=$asan_blacklist_path",
+ ]
+ if (is_mac) {
+ cflags += [ "-mllvm -asan-globals=0" ] # http://crbug.com/352073
+ # TODO(GYP): deal with mac_bundles.
+ }
+ }
+ if (is_lsan) {
+ cflags += [ "-fsanitize=leak" ]
+ }
+ if (is_tsan) {
+ tsan_blacklist_path =
+ rebase_path("//tools/memory/tsan_v2/ignores.txt", root_build_dir)
+ cflags += [
+ "-fsanitize=thread",
+ "-fsanitize-blacklist=$tsan_blacklist_path",
+ ]
+ }
+ if (is_msan) {
+ msan_blacklist_path =
+ rebase_path("//tools/msan/blacklist.txt", root_build_dir)
+ cflags += [
+ "-fsanitize=memory",
+ "-fsanitize-memory-track-origins=$msan_track_origins",
+ "-fsanitize-blacklist=$msan_blacklist_path",
+ ]
+ }
+
+ if (use_custom_libcxx) {
+ cflags_cc += [ "-nostdinc++" ]
+ include_dirs = [
+ "//buildtools/third_party/libc++/trunk/include",
+ "//buildtools/third_party/libc++abi/trunk/include",
+ ]
+ }
+
+ if (is_fnl) {
+ # TODO(kulakowski) remove when fnl no longer uses gcc
+ cflags += [ "-Wno-maybe-uninitialized" ]
+ }
+ }
+
+ if (is_clang && is_debug) {
+ # Allow comparing the address of references and 'this' against 0
+ # in debug builds. Technically, these can never be null in
+ # well-defined C/C++ and Clang can optimize such checks away in
+ # release builds, but they may be used in asserts in debug builds.
+ extra_flags = [
+ "-Wno-undefined-bool-conversion",
+ "-Wno-tautological-undefined-compare",
+ ]
+ cflags_cc += extra_flags
+ cflags_objcc += extra_flags
+ }
+
+ if (is_clang && !is_nacl) {
+ # This is here so that all files get recompiled after a clang roll and
+ # when turning clang on or off. (defines are passed via the command line,
+ # and build system rebuild things when their commandline changes). Nothing
+ # should ever read this define.
+ defines +=
+ [ "CR_CLANG_REVISION=" + exec_script("//tools/clang/scripts/update.py",
+ [ "--print-revision" ],
+ "trim string") ]
+ }
+
+ # Mac-specific compiler flags setup.
+ # ----------------------------------
+ if (is_mac || is_ios) {
+ # These flags are shared between the C compiler and linker.
+ common_mac_flags = []
+
+ # CPU architecture.
+ if (current_cpu == "x64") {
+ common_mac_flags += [
+ "-arch",
+ "x86_64",
+ ]
+ } else if (current_cpu == "x86") {
+ common_mac_flags += [
+ "-arch",
+ "i386",
+ ]
+ } else if (current_cpu == "arm") {
+ common_mac_flags += [
+ "-arch",
+ "armv7",
+ ]
+ } else if (current_cpu == "arm64") {
+ common_mac_flags += [
+ "-arch",
+ "arm64",
+ ]
+ }
+
+ cflags += common_mac_flags
+
+ # Without this, the constructors and destructors of a C++ object inside
+ # an Objective C struct won't be called, which is very bad.
+ cflags_objcc += [ "-fobjc-call-cxx-cdtors" ]
+
+ cflags_c += [ "-std=c99" ]
+
+ ldflags += common_mac_flags
+ } else if (is_posix) {
+ # CPU architecture. We may or may not be doing a cross compile now, so for
+ # simplicity we always explicitly set the architecture.
+ if (current_cpu == "x64") {
+ cflags += [
+ "-m64",
+ "-march=x86-64",
+ ]
+ ldflags += [ "-m64" ]
+ } else if (current_cpu == "x86") {
+ cflags += [ "-m32" ]
+ ldflags += [ "-m32" ]
+ if (is_clang) {
+ cflags += [
+ # Else building libyuv gives clang's register allocator issues,
+ # see llvm.org/PR15798 / crbug.com/233709
+ "-momit-leaf-frame-pointer",
+
+ # Align the stack on 16-byte boundaries, http://crbug.com/418554.
+ "-mstack-alignment=16",
+ "-mstackrealign",
+ ]
+ }
+ } else if (current_cpu == "arm") {
+ cflags += [
+ "-march=$arm_arch",
+ "-mfloat-abi=$arm_float_abi",
+ ]
+ if (arm_tune != "") {
+ cflags += [ "-mtune=$arm_tune" ]
+ }
+ if (arm_use_thumb) {
+ cflags += [ "-mthumb" ]
+ if (is_android && !is_clang) { # Clang doesn't support this option.
+ cflags += [ "-mthumb-interwork" ]
+ }
+ }
+ if (!is_clang) {
+ # Clang doesn't support these flags.
+ cflags += [
+ # The tree-sra optimization (scalar replacement for
+ # aggregates enabling subsequent optimizations) leads to
+ # invalid code generation when using the Android NDK's
+ # compiler (r5-r7). This can be verified using
+ # webkit_unit_tests' WTF.Checked_int8_t test.
+ "-fno-tree-sra",
+
+ # The following option is disabled to improve binary
+ # size and performance in gcc 4.9.
+ "-fno-caller-saves",
+ ]
+ }
+ } else if (current_cpu == "mipsel") {
+ if (mips_arch_variant == "r6") {
+ cflags += [
+ "-mips32r6",
+ "-Wa,-mips32r6",
+ ]
+ if (is_android) {
+ ldflags += [
+ "-mips32r6",
+ "-Wl,-melf32ltsmip",
+ ]
+ }
+ } else if (mips_arch_variant == "r2") {
+ cflags += [
+ "-mips32r2",
+ "-Wa,-mips32r2",
+ ]
+ if (mips_float_abi == "hard" && mips_fpu_mode != "") {
+ cflags += [ "-m$mips_fpu_mode" ]
+ }
+ } else if (mips_arch_variant == "r1") {
+ cflags += [
+ "-mips32",
+ "-Wa,-mips32",
+ ]
+ }
+
+ if (mips_dsp_rev == 1) {
+ cflags += [ "-mdsp" ]
+ } else if (mips_dsp_rev == 2) {
+ cflags += [ "-mdspr2" ]
+ }
+
+ cflags += [ "-m${mips_float_abi}-float" ]
+ } else if (current_cpu == "mips64el") {
+ if (mips_arch_variant == "r6") {
+ cflags += [
+ "-mips64r6",
+ "-Wa,-mips64r6",
+ ]
+ ldflags += [ "-mips64r6" ]
+ } else if (mips_arch_variant == "r2") {
+ cflags += [
+ "-mips64r2",
+ "-Wa,-mips64r2",
+ ]
+ ldflags += [ "-mips64r2" ]
+ }
+ }
+
+ defines += [ "_FILE_OFFSET_BITS=64" ]
+
+ if (!is_android) {
+ defines += [
+ "_LARGEFILE_SOURCE",
+ "_LARGEFILE64_SOURCE",
+ ]
+ }
+
+ # Omit unwind support in official builds to save space. We can use breakpad
+ # for these builds.
+ if (is_chrome_branded && is_official_build) {
+ cflags += [
+ "-fno-unwind-tables",
+ "-fno-asynchronous-unwind-tables",
+ ]
+ defines += [ "NO_UNWIND_TABLES" ]
+ } else {
+ cflags += [ "-funwind-tables" ]
+ }
+ }
+
+ if (enable_profiling && !is_debug) {
+ # The GYP build spams this define into every compilation unit, as we do
+ # here, but it only appears to be used in base and a couple other places.
+ # TODO(abarth): Should we move this define closer to where it's used?
+ defines += [ "ENABLE_PROFILING" ]
+
+ cflags += [
+ "-fno-omit-frame-pointer",
+ "-g",
+ ]
+
+ if (enable_full_stack_frames_for_profiling) {
+ cflags += [
+ "-fno-inline",
+ "-fno-optimize-sibling-calls",
+ ]
+ }
+ }
+
+ # Linux/Android common flags setup.
+ # ---------------------------------
+ if (is_linux || is_android) {
+ cflags += [
+ "-fPIC",
+ "-pipe", # Use pipes for communicating between sub-processes. Faster.
+ ]
+
+ ldflags += [
+ "-fPIC",
+ "-Wl,-z,noexecstack",
+ "-Wl,-z,now",
+ "-Wl,-z,relro",
+ ]
+ if (!using_sanitizer) {
+ ldflags += [ "-Wl,-z,defs" ]
+ }
+ }
+
+ # Linux-specific compiler flags setup.
+ # ------------------------------------
+ if (is_linux) {
+ cflags += [ "-pthread" ]
+ ldflags += [ "-pthread" ]
+ }
+ if (use_gold) {
+ gold_path = rebase_path("//third_party/binutils/Linux_x64/Release/bin",
+ root_build_dir)
+ ldflags += [
+ "-B$gold_path",
+
+ # Newer gccs and clangs support -fuse-ld, use the flag to force gold
+ # selection.
+ # gcc -- http://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Optimize-Options.html
+ "-fuse-ld=gold",
+
+ # Experimentation found that using four linking threads
+ # saved ~20% of link time.
+ # https://groups.google.com/a/chromium.org/group/chromium-dev/browse_thread/thread/281527606915bb36
+ # Only apply this to the target linker, since the host
+ # linker might not be gold, but isn't used much anyway.
+ # TODO(raymes): Disable threading because gold is frequently
+ # crashing on the bots: crbug.com/161942.
+ #"-Wl,--threads",
+ #"-Wl,--thread-count=4",
+ ]
+
+ if (!is_asan && !is_msan && !is_lsan && !is_tsan) {
+ # TODO(brettw) common.gypi has this only for target toolset.
+ ldflags += [ "-Wl,--icf=all" ]
+ }
+
+ # TODO(thestig): Make this flag work with GN.
+ #if (!is_official_build && !is_chromeos && !(is_asan || is_lsan || is_tsan || is_msan)) {
+ # ldflags += [
+ # "-Wl,--detect-odr-violations",
+ # ]
+ #}
+ }
+
+ if (linux_use_bundled_binutils) {
+ binutils_path = rebase_path("//third_party/binutils/Linux_x64/Release/bin",
+ root_build_dir)
+ cflags += [ "-B$binutils_path" ]
+ }
+
+ # Clang-specific compiler flags setup.
+ # ------------------------------------
+ if (is_clang) {
+ cflags += [ "-fcolor-diagnostics" ]
+ }
+
+ # C++11 compiler flags setup.
+ # ---------------------------
+ if (is_linux || is_android || is_nacl) {
+ # gnu++11 instead of c++11 is needed because some code uses typeof() (a
+ # GNU extension).
+ # TODO(thakis): Eventually switch this to c++11 instead,
+ # http://crbug.com/427584
+ cflags_cc += [ "-std=gnu++11" ]
+ } else if (!is_win) {
+ cc_std = [ "-std=c++11" ]
+ cflags_cc += cc_std
+ cflags_objcc += cc_std
+ }
+
+ # Android-specific flags setup.
+ # -----------------------------
+ if (is_android) {
+ cflags += [
+ "-ffunction-sections",
+ "-funwind-tables",
+ "-fno-short-enums",
+ ]
+ if (!is_clang) {
+ # Clang doesn't support these flags.
+ cflags += [ "-finline-limit=64" ]
+ }
+ if (is_asan) {
+ # Android build relies on -Wl,--gc-sections removing unreachable code.
+ # ASan instrumentation for globals inhibits this and results in a library
+ # with unresolvable relocations.
+ # TODO(eugenis): find a way to reenable this.
+ cflags += [ "-mllvm -asan-globals=0" ]
+ }
+
+ defines += [ "ANDROID" ]
+
+ # The NDK has these things, but doesn't define the constants
+ # to say that it does. Define them here instead.
+ defines += [ "HAVE_SYS_UIO_H" ]
+
+ # Use gold for Android for most CPU architectures.
+ if (current_cpu == "x86" || current_cpu == "x64" || current_cpu == "arm") {
+ ldflags += [ "-fuse-ld=gold" ]
+ if (is_clang) {
+ # Let clang find the ld.gold in the NDK.
+ ldflags += [ "--gcc-toolchain=" +
+ rebase_path(android_toolchain_root, root_build_dir) ]
+ }
+ }
+
+ ldflags += [
+ "-Wl,--no-undefined",
+
+ # Don't allow visible symbols from libgcc or libc++ to be
+ # re-exported.
+ "-Wl,--exclude-libs=libgcc.a",
+ "-Wl,--exclude-libs=libc++_static.a",
+
+ # Don't allow visible symbols from libraries that contain
+ # assembly code with symbols that aren't hidden properly.
+ # http://crbug.com/448386
+ "-Wl,--exclude-libs=libvpx_assembly_arm.a",
+ ]
+ if (current_cpu == "arm") {
+ ldflags += [
+ # Enable identical code folding to reduce size.
+ "-Wl,--icf=safe",
+ ]
+ }
+
+ if (is_clang) {
+ if (current_cpu == "arm") {
+ cflags += [ "--target=arm-linux-androideabi" ]
+ ldflags += [ "--target=arm-linux-androideabi" ]
+ } else if (current_cpu == "x86") {
+ cflags += [ "--target=x86-linux-androideabi" ]
+ ldflags += [ "--target=x86-linux-androideabi" ]
+ }
+ }
+ }
+}
+
+config("compiler_arm_fpu") {
+ if (current_cpu == "arm" && !is_ios) {
+ cflags = [ "-mfpu=$arm_fpu" ]
+ }
+}
+
+# runtime_library -------------------------------------------------------------
+#
+# Sets the runtime library and associated options.
+#
+# How do you determine what should go in here vs. "compiler" above? Consider if
+# a target might choose to use a different runtime library (ignore for a moment
+# if this is possible or reasonable on your system). If such a target would want
+# to change or remove your option, put it in the runtime_library config. If a
+# target wants the option regardless, put it in the compiler config.
+
+config("runtime_library") {
+ cflags = []
+ defines = []
+ ldflags = []
+ lib_dirs = []
+ libs = []
+
+ if (is_component_build) {
+ # Component mode: dynamic CRT.
+ defines += [ "COMPONENT_BUILD" ]
+ if (is_win) {
+ # Since the library is shared, it requires exceptions or will give errors
+ # about things not matching, so keep exceptions on.
+ if (is_debug) {
+ cflags += [ "/MDd" ]
+ } else {
+ cflags += [ "/MD" ]
+ }
+ }
+ } else {
+ # Static CRT.
+ if (is_win) {
+ if (is_debug) {
+ cflags += [ "/MTd" ]
+ } else {
+ cflags += [ "/MT" ]
+ }
+ }
+ }
+
+ if (is_win) {
+ defines += [
+ "__STD_C",
+ "_CRT_RAND_S",
+ "_CRT_SECURE_NO_DEPRECATE",
+ "_HAS_EXCEPTIONS=0",
+ "_SCL_SECURE_NO_DEPRECATE",
+ ]
+ }
+
+ # Android standard library setup.
+ if (is_android) {
+ if (is_clang) {
+ # Work around incompatibilities between bionic and clang headers.
+ defines += [
+ "__compiler_offsetof=__builtin_offsetof",
+ "nan=__builtin_nan",
+ ]
+ }
+
+ defines += [ "__GNU_SOURCE=1" ] # Necessary for clone().
+
+ # TODO(jdduke) Re-enable on mips after resolving linking
+ # issues with libc++ (crbug.com/456380).
+ if (current_cpu != "mipsel" && current_cpu != "mips64el") {
+ ldflags += [ "-Wl,--warn-shared-textrel" ]
+ }
+ ldflags += [ "-nostdlib" ]
+
+ # NOTE: The libc++ header include paths below are specified in cflags
+ # rather than include_dirs because they need to come after include_dirs.
+ # Think of them like system headers, but don't use '-isystem' because the
+ # arm-linux-androideabi-4.4.3 toolchain (circa Gingerbread) will exhibit
+ # strange errors. The include ordering here is important; change with
+ # caution.
+ cflags += [
+ "-isystem" +
+ rebase_path("$android_libcpp_root/libcxx/include", root_build_dir),
+ "-isystem" + rebase_path(
+ "$android_ndk_root/sources/cxx-stl/llvm-libc++abi/libcxxabi/include",
+ root_build_dir),
+ "-isystem" +
+ rebase_path("$android_ndk_root/sources/android/support/include",
+ root_build_dir),
+ ]
+
+ lib_dirs += [ "$android_libcpp_root/libs/$android_app_abi" ]
+ libs += [ "$android_libcpp_library" ]
+
+ if (current_cpu == "mipsel") {
+ libs += [
+ # ld linker is used for mips Android, and ld does not accept library
+ # absolute path prefixed by "-l"; Since libgcc does not exist in mips
+ # sysroot the proper library will be linked.
+ # TODO(gordanac): Remove once gold linker is used for mips Android.
+ "gcc",
+ ]
+ } else {
+ libs += [
+ # Manually link the libgcc.a that the cross compiler uses. This is
+ # absolute because the linker will look inside the sysroot if it's not.
+ rebase_path(android_libgcc_file),
+ ]
+ }
+
+ libs += [
+ "c",
+ "dl",
+ "m",
+ ]
+
+ # Clang with libc++ does not require an explicit atomic library reference.
+ if (!is_clang) {
+ libs += [ "atomic" ]
+ }
+ }
+}
+
+# default_warning_flags collects all warning flags that are used by default.
+# This is in a variable instead of a config so that it can be used in
+# both chromium_code and no_chromium_code. This way these flags are guaranteed
+# to appear on the compile command line after -Wall.
+
+default_warning_flags = []
+default_warning_flags_cc = []
+if (is_win) {
+ if (!is_clang || current_cpu != "x86") {
+ default_warning_flags += [ "/WX" ] # Treat warnings as errors.
+ }
+
+ default_warning_flags += [
+ # Warnings permanently disabled:
+
+ # TODO(GYP) The GYP build doesn't have this globally enabled but disabled
+ # for a bunch of individual targets. Re-enable this globally when those
+ # targets are fixed.
+ "/wd4018", # Comparing signed and unsigned values.
+
+ # C4127: conditional expression is constant
+ # This warning can in theory catch dead code and other problems, but
+ # triggers in far too many desirable cases where the conditional
+ # expression is either set by macros or corresponds some legitimate
+ # compile-time constant expression (due to constant template args,
+ # conditionals comparing the sizes of different types, etc.). Some of
+ # these can be worked around, but it's not worth it.
+ "/wd4127",
+
+ # C4251: 'identifier' : class 'type' needs to have dll-interface to be
+ # used by clients of class 'type2'
+ # This is necessary for the shared library build.
+ "/wd4251",
+
+ # C4351: new behavior: elements of array 'array' will be default
+ # initialized
+ # This is a silly "warning" that basically just alerts you that the
+ # compiler is going to actually follow the language spec like it's
+ # supposed to, instead of not following it like old buggy versions did.
+ # There's absolutely no reason to turn this on.
+ "/wd4351",
+
+ # C4355: 'this': used in base member initializer list
+ # It's commonly useful to pass |this| to objects in a class' initializer
+ # list. While this warning can catch real bugs, most of the time the
+ # constructors in question don't attempt to call methods on the passed-in
+ # pointer (until later), and annotating every legit usage of this is
+ # simply more hassle than the warning is worth.
+ "/wd4355",
+
+ # C4503: 'identifier': decorated name length exceeded, name was
+ # truncated
+ # This only means that some long error messages might have truncated
+ # identifiers in the presence of lots of templates. It has no effect on
+ # program correctness and there's no real reason to waste time trying to
+ # prevent it.
+ "/wd4503",
+
+ # Warning C4589 says: "Constructor of abstract class ignores
+ # initializer for virtual base class." Disable this warning because it
+ # is flaky in VS 2015 RTM. It triggers on compiler generated
+ # copy-constructors in some cases.
+ "/wd4589",
+
+ # C4611: interaction between 'function' and C++ object destruction is
+ # non-portable
+ # This warning is unavoidable when using e.g. setjmp/longjmp. MSDN
+ # suggests using exceptions instead of setjmp/longjmp for C++, but
+ # Chromium code compiles without exception support. We therefore have to
+ # use setjmp/longjmp for e.g. JPEG decode error handling, which means we
+ # have to turn off this warning (and be careful about how object
+ # destruction happens in such cases).
+ "/wd4611",
+
+ # Warnings to evaluate and possibly fix/reenable later:
+
+ "/wd4100", # Unreferenced formal function parameter.
+ "/wd4121", # Alignment of a member was sensitive to packing.
+ "/wd4244", # Conversion: possible loss of data.
+ "/wd4481", # Nonstandard extension: override specifier.
+ "/wd4505", # Unreferenced local function has been removed.
+ "/wd4510", # Default constructor could not be generated.
+ "/wd4512", # Assignment operator could not be generated.
+ "/wd4610", # Class can never be instantiated, constructor required.
+ "/wd4996", # Deprecated function warning.
+ ]
+
+ # VS xtree header file needs to be patched or 4702 (unreachable code
+ # warning) is reported if _HAS_EXCEPTIONS=0. Disable the warning if xtree is
+ # not patched.
+ if (!msvs_xtree_patched &&
+ exec_script("../../win_is_xtree_patched.py", [], "value") == 0) {
+ default_warning_flags += [ "/wd4702" ] # Unreachable code.
+ }
+
+ # Building with Clang on Windows is a work in progress and very
+ # experimental. See crbug.com/82385.
+ # Keep this in sync with the similar block in build/common.gypi
+ if (is_clang) {
+ default_warning_flags += [
+ # TODO(hans): Make this list shorter eventually, http://crbug.com/504657
+ "-Qunused-arguments", # http://crbug.com/504658
+ "-Wno-microsoft", # http://crbug.com/505296
+ "-Wno-switch", # http://crbug.com/505308
+ "-Wno-unknown-pragmas", # http://crbug.com/505314
+ "-Wno-unused-function", # http://crbug.com/505316
+ "-Wno-unused-value", # http://crbug.com/505318
+ "-Wno-unused-local-typedef", # http://crbug.com/411648
+ ]
+ }
+} else {
+ # Common GCC warning setup.
+ default_warning_flags += [
+ # Enables.
+ "-Wendif-labels", # Weird old-style text after an #endif.
+ "-Werror", # Warnings as errors.
+
+ # Disables.
+ "-Wno-missing-field-initializers", # "struct foo f = {0};"
+ "-Wno-unused-parameter", # Unused function parameters.
+ ]
+
+ if (is_mac) {
+ # TODO(abarth): Re-enable once https://github.com/domokit/mojo/issues/728
+ # is fixed.
+ # default_warning_flags += [ "-Wnewline-eof" ]
+ if (!is_nacl) {
+ # When compiling Objective-C, warns if a method is used whose
+ # availability is newer than the deployment target. This is not
+ # required when compiling Chrome for iOS.
+ default_warning_flags += [ "-Wpartial-availability" ]
+ }
+ }
+
+ if (gcc_version >= 48) {
+ default_warning_flags_cc += [
+ # See comment for -Wno-c++11-narrowing.
+ "-Wno-narrowing",
+ ]
+ }
+
+ # Suppress warnings about ABI changes on ARM (Clang doesn't give this
+ # warning).
+ if (current_cpu == "arm" && !is_clang) {
+ default_warning_flags += [ "-Wno-psabi" ]
+ }
+
+ if (is_android) {
+ # Disable any additional warnings enabled by the Android build system but
+ # which chromium does not build cleanly with (when treating warning as
+ # errors).
+ default_warning_flags += [
+ "-Wno-extra",
+ "-Wno-ignored-qualifiers",
+ "-Wno-type-limits",
+ ]
+ default_warning_flags_cc += [
+ # Disabling c++0x-compat should be handled in WebKit, but
+ # this currently doesn't work because gcc_version is not set
+ # correctly when building with the Android build system.
+ # TODO(torne): Fix this in WebKit.
+ "-Wno-error=c++0x-compat",
+
+ # Other things unrelated to -Wextra:
+ "-Wno-non-virtual-dtor",
+ "-Wno-sign-promo",
+ ]
+ }
+
+ if (gcc_version >= 48) {
+ # Don't warn about the "typedef 'foo' locally defined but not used"
+ # for gcc 4.8.
+ # TODO: remove this flag once all builds work. See crbug.com/227506
+ default_warning_flags += [ "-Wno-unused-local-typedefs" ]
+ }
+}
+if (is_clang) {
+ default_warning_flags += [
+ # This warns on using ints as initializers for floats in
+ # initializer lists (e.g. |int a = f(); CGSize s = { a, a };|),
+ # which happens in several places in chrome code. Not sure if
+ # this is worth fixing.
+ "-Wno-c++11-narrowing",
+
+ # Don't die on dtoa code that uses a char as an array index.
+ # This is required solely for base/third_party/dmg_fp/dtoa.cc.
+ # TODO(brettw) move this to that project then!
+ "-Wno-char-subscripts",
+
+ # Warns on switches on enums that cover all enum values but
+ # also contain a default: branch. Chrome is full of that.
+ "-Wno-covered-switch-default",
+
+ # Clang considers the `register` keyword as deprecated, but e.g.
+ # code generated by flex (used in angle) contains that keyword.
+ # http://crbug.com/255186
+ "-Wno-deprecated-register",
+ ]
+
+ # NaCl's Clang compiler and Chrome's hermetic Clang compiler will almost
+ # always have different versions. Certain flags may not be recognized by
+ # one version or the other.
+ if (!is_nacl) {
+ # Flags NaCl does not recognize.
+ default_warning_flags += [
+ # TODO(hans): Get this cleaned up, http://crbug.com/428099
+ "-Wno-inconsistent-missing-override",
+
+ # TODO(thakis): Enable this, crbug.com/507717
+ "-Wno-shift-negative-value",
+ ]
+ }
+}
+
+# chromium_code ---------------------------------------------------------------
+#
+# Toggles between higher and lower warnings for code that is (or isn't)
+# part of Chromium.
+
+config("chromium_code") {
+ if (is_win) {
+ cflags = [ "/W4" ] # Warning level 4.
+ } else {
+ cflags = [
+ "-Wall",
+ "-Wextra",
+ ]
+
+ # In Chromium code, we define __STDC_foo_MACROS in order to get the
+ # C99 macros on Mac and Linux.
+ defines = [
+ "__STDC_CONSTANT_MACROS",
+ "__STDC_FORMAT_MACROS",
+ ]
+
+ if (!using_sanitizer && (!is_linux || !is_clang || is_official_build)) {
+ # _FORTIFY_SOURCE isn't really supported by Clang now, see
+ # http://llvm.org/bugs/show_bug.cgi?id=16821.
+ # It seems to work fine with Ubuntu 12 headers though, so use it in
+ # official builds.
+ #
+ # Non-chromium code is not guaranteed to compile cleanly with
+ # _FORTIFY_SOURCE. Also, fortified build may fail when optimizations are
+ # disabled, so only do that for Release build.
+ defines += [ "_FORTIFY_SOURCE=2" ]
+ }
+ }
+ cflags += default_warning_flags
+ cflags_cc = default_warning_flags_cc
+}
+config("no_chromium_code") {
+ cflags = []
+ cflags_cc = []
+ defines = []
+
+ if (is_win) {
+ cflags += [
+ "/W3", # Warning level 3.
+ "/wd4800", # Disable warning when forcing value to bool.
+ "/wd4267", # TODO(jschuh): size_t to int.
+ "/wd4996", # Deprecated function warning.
+ ]
+ defines += [
+ "_CRT_NONSTDC_NO_WARNINGS",
+ "_CRT_NONSTDC_NO_DEPRECATE",
+ ]
+ }
+
+ if (is_linux) {
+ # Don't warn about ignoring the return value from e.g. close(). This is
+ # off by default in some gccs but on by default in others. BSD systems do
+ # not support this option, since they are usually using gcc 4.2.1, which
+ # does not have this flag yet.
+ cflags += [ "-Wno-unused-result" ]
+ }
+
+ if (is_clang) {
+ cflags += [
+ # TODO(mgiuca): Move this suppression into individual third-party
+ # libraries as required. http://crbug.com/505301.
+ "-Wno-overloaded-virtual",
+
+ # Lots of third-party libraries have unused variables. Instead of
+ # suppressing them individually, we just blanket suppress them here.
+ "-Wno-unused-variable",
+ ]
+ }
+
+ if (is_linux || is_android) {
+ cflags += [
+ # Don't warn about printf format problems. This is off by default in gcc
+ # but on in Ubuntu's gcc(!).
+ "-Wno-format",
+ ]
+ cflags_cc += [
+ # Don't warn about hash_map in third-party code.
+ "-Wno-deprecated",
+ ]
+ }
+ cflags += default_warning_flags
+ cflags_cc += default_warning_flags_cc
+}
+
+# rtti ------------------------------------------------------------------------
+#
+# Allows turning Run-Time Type Identification on or off.
+
+config("rtti") {
+ if (is_win) {
+ cflags_cc = [ "/GR" ]
+ }
+}
+config("no_rtti") {
+ if (is_win) {
+ cflags_cc = [ "/GR-" ]
+ } else {
+ rtti_flags = [ "-fno-rtti" ]
+ cflags_cc = rtti_flags
+ cflags_objcc = rtti_flags
+ }
+}
+
+# Warnings ---------------------------------------------------------------------
+
+# This will generate warnings when using Clang if code generates exit-time
+# destructors, which will slow down closing the program.
+# TODO(thakis): Make this a blacklist instead, http://crbug.com/101600
+config("wexit_time_destructors") {
+ # TODO: Enable on Windows too, http://crbug.com/404525
+ if (is_clang && !is_win) {
+ cflags = [ "-Wexit-time-destructors" ]
+ }
+}
+
+# On Windows compiling on x64, VC will issue a warning when converting
+# size_t to int because it will truncate the value. Our code should not have
+# these warnings and one should use a static_cast or a checked_cast for the
+# conversion depending on the case. However, a lot of code still needs to be
+# fixed. Apply this config to such targets to disable the warning.
+#
+# Note that this can be applied regardless of platform and architecture to
+# clean up the call sites. This will only apply the flag when necessary.
+#
+# TODO(jschuh): crbug.com/167187 fix this and delete this config.
+config("no_size_t_to_int_warning") {
+ if (is_win && current_cpu == "x64") {
+ cflags = [ "/wd4267" ]
+ }
+}
+
+# Optimization -----------------------------------------------------------------
+#
+# Note that BUILDCONFIG.gn sets up a variable "default_optimization_config"
+# which it will assign to the config it implicitly applies to every target. If
+# you want to override the optimization level for your target, remove this
+# config (which will expand differently for debug or release builds), and then
+# add back the one you want to override it with:
+#
+# configs -= default_optimization_config
+# configs += [ "//build/config/compiler/optimize_max" ]
+
+# Shared settings for both "optimize" and "optimize_max" configs.
+# IMPORTANT: On Windows "/O1" and "/O2" must go before the common flags.
+if (is_win) {
+ common_optimize_on_cflags = [
+ "/Ob2", # Both explicit and auto inlining.
+ "/Oy-", # Disable omitting frame pointers, must be after /O2.
+ ]
+ if (!is_asan) {
+ common_optimize_on_cflags += [
+ # Put data in separate COMDATs. This allows the linker
+ # to put bit-identical constants at the same address even if
+ # they're unrelated constants, which saves binary size.
+ # This optimization can't be used when ASan is enabled because
+ # it is not compatible with the ASan ODR checker.
+ "/Gw",
+ ]
+ }
+ common_optimize_on_ldflags = [ "/OPT:REF" ]
+} else {
+ common_optimize_on_cflags = [
+ # Don't emit the GCC version ident directives, they just end up in the
+ # .comment section taking up binary size.
+ "-fno-ident",
+
+ # Put data and code in their own sections, so that unused symbols
+ # can be removed at link time with --gc-sections.
+ "-fdata-sections",
+ "-ffunction-sections",
+ ]
+ common_optimize_on_ldflags = []
+
+ if (is_android) {
+ if (!using_sanitizer) {
+ common_optimize_on_cflags += [ "-fomit-frame-pointer" ]
+ }
+
+ # TODO(jdduke) Re-enable on mips after resolving linking
+ # issues with libc++ (crbug.com/456380).
+ if (current_cpu != "mipsel" && current_cpu != "mips64el") {
+ common_optimize_on_ldflags += [
+ # Warn in case of text relocations.
+ "-Wl,--warn-shared-textrel",
+ ]
+ }
+ }
+
+ if (is_mac || is_ios) {
+ if (symbol_level == 2) {
+ # Mac dead code stripping requires symbols.
+ common_optimize_on_ldflags += [ "-Wl,-dead_strip" ]
+ }
+ } else {
+ # Non-Mac Posix linker flags.
+ common_optimize_on_ldflags += [
+ # Specifically tell the linker to perform optimizations.
+ # See http://lwn.net/Articles/192624/ .
+ "-Wl,-O1",
+ "-Wl,--gc-sections",
+ ]
+
+ if (!using_sanitizer) {
+ # Functions interposed by the sanitizers can make ld think
+ # that some libraries aren't needed when they actually are,
+ # http://crbug.com/234010. As workaround, disable --as-needed.
+ common_optimize_on_ldflags += [ "-Wl,--as-needed" ]
+ }
+ }
+}
+
+# Default "optimization on" config. On Windows, this favors size over speed.
+config("optimize") {
+ if (is_win) {
+ # Favor size over speed, /O1 must be before the common flags. The GYP
+ # build also specifies /Os and /GF but these are implied by /O1.
+ cflags = [ "/O1" ] + common_optimize_on_cflags + [ "/Oi" ]
+ } else if (is_android || is_ios) {
+ cflags = [ "-Os" ] + common_optimize_on_cflags # Favor size over speed.
+ } else {
+ cflags = [ "-O2" ] + common_optimize_on_cflags
+ }
+ ldflags = common_optimize_on_ldflags
+}
+
+# Turn off optimizations.
+config("no_optimize") {
+ if (is_win) {
+ cflags = [
+ "/Od", # Disable optimization.
+ "/Ob0", # Disable all inlining (on by default).
+ "/RTC1", # Runtime checks for stack frame and uninitialized variables.
+ ]
+ } else if (is_android && !android_full_debug) {
+ # On Android we kind of optimize some things that don't affect debugging
+ # much even when optimization is disabled to get the binary size down.
+ cflags = [
+ "-Os",
+ "-fdata-sections",
+ "-ffunction-sections",
+ ]
+ if (!using_sanitizer) {
+ cflags += [ "-fomit-frame-pointer" ]
+ }
+ ldflags = common_optimize_on_ldflags
+ } else {
+ cflags = [ "-O0" ]
+ }
+}
+
+# Turns up the optimization level. On Windows, this implies whole program
+# optimization and link-time code generation which is very expensive and should
+# be used sparingly.
+config("optimize_max") {
+ ldflags = common_optimize_on_ldflags
+ if (is_win) {
+ # Favor speed over size, /O2 must be before the common flags. The GYP
+ # build also specifies /Ot, /Oi, and /GF, but these are implied by /O2.
+ cflags = [ "/O2" ] + common_optimize_on_cflags
+ if (is_official_build) {
+ # TODO(GYP): TODO(dpranke): Should these only be on in an official
+ # build, or on all the time? For now we'll require official build so
+ # that the compile is clean.
+ cflags += [
+ "/GL", # Whole program optimization.
+
+ # Disable Warning 4702 ("Unreachable code") for the WPO/PGO builds.
+ # Probably anything that this would catch that wouldn't be caught in a
+ # normal build isn't going to actually be a bug, so the incremental
+ # value of C4702 for PGO builds is likely very small.
+ "/wd4702",
+ ]
+ ldflags += [ "/LTCG" ]
+ }
+ } else {
+ cflags = [ "-O2" ] + common_optimize_on_cflags
+ }
+}
+
+# Symbols ----------------------------------------------------------------------
+
+config("symbols") {
+ if (is_win) {
+ import("//build/toolchain/goma.gni")
+ if (use_goma) {
+ cflags = [ "/Z7" ] # No PDB file
+ } else {
+ cflags = [ "/Zi" ] # Produce PDB file, no edit and continue.
+ }
+ ldflags = [ "/DEBUG" ]
+ } else {
+ cflags = [ "-g2" ]
+ if (use_debug_fission) {
+ cflags += [ "-gsplit-dwarf" ]
+ }
+ }
+}
+
+config("minimal_symbols") {
+ if (is_win) {
+ # Linker symbols for backtraces only.
+ ldflags = [ "/DEBUG" ]
+ } else {
+ cflags = [ "-g1" ]
+ if (use_debug_fission) {
+ cflags += [ "-gsplit-dwarf" ]
+ }
+ }
+}
+
+config("no_symbols") {
+ if (!is_win) {
+ cflags = [ "-g0" ]
+ }
+}
diff --git a/build/config/crypto.gni b/build/config/crypto.gni
new file mode 100644
index 00000000000..2cd72d338ca
--- /dev/null
+++ b/build/config/crypto.gni
@@ -0,0 +1,29 @@
+# Copyright (c) 2013 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 file declares build flags for the SSL library configuration.
+#
+# TODO(brettw) this should probably be moved to src/crypto or somewhere, and
+# the global build dependency on it should be removed.
+#
+# PLEASE TRY TO AVOID ADDING FLAGS TO THIS FILE in cases where grit isn't
+# required. See the declare_args block of BUILDCONFIG.gn for advice on how
+# to set up feature flags.
+
+declare_args() {
+ # Use OpenSSL instead of NSS. This is used for all platforms but iOS. (See
+ # http://crbug.com/338886).
+ use_openssl = !is_ios
+}
+
+# True when we're using OpenSSL for representing certificates. When targeting
+# Android, the platform certificate library is used for certificate
+# verification. On other targets, this flag also enables OpenSSL for certificate
+# verification, but this configuration is unsupported.
+use_openssl_certs = is_android
+
+# True if NSS is used for certificate verification. Note that this is
+# independent from use_openssl. It is possible to use OpenSSL for the crypto
+# library, but NSS for the platform certificate library.
+use_nss_certs = false
diff --git a/build/config/features.gni b/build/config/features.gni
new file mode 100644
index 00000000000..93b19dd1431
--- /dev/null
+++ b/build/config/features.gni
@@ -0,0 +1,204 @@
+# Copyright 2014 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 file contains Chrome-feature-related build flags (see ui.gni for
+# UI-related ones). These should theoretically be moved to the build files of
+# the features themselves.
+#
+# However, today we have many "bad" dependencies on some of these flags from,
+# e.g. base, so they need to be global to match the GYP configuration. Also,
+# anything that needs a grit define must be in either this file or ui.gni.
+#
+# PLEASE TRY TO AVOID ADDING FLAGS TO THIS FILE in cases where grit isn't
+# required. See the declare_args block of BUILDCONFIG.gn for advice on how
+# to set up feature flags.
+
+import("//build/config/chrome_build.gni")
+if (is_android) {
+ import("//build/config/android/config.gni")
+}
+
+declare_args() {
+ # Multicast DNS.
+ enable_mdns = is_win || is_linux
+
+ enable_plugins = !is_android && !is_ios
+
+ # Enables Native Client support.
+ # TODO(GYP): Get NaCl linking on other platforms.
+ # Also, see if we can always get rid of enable_nacl_untrusted and
+ # enable_pnacl and always build them if enable_nacl is true.
+ # The "is_nacl" part of the condition is needed to ensure that
+ # the untrusted code is built properly; arguably it should be
+ # guarded by "is_nacl" directly rather than enable_nacl_untrusted, but
+ # this will go away when Mac and Win are working and we can just use
+ # the commented out logic.
+ # Eventually we want this to be:
+ # enable_nacl = !is_ios && !is_android
+ enable_nacl = (is_linux && !is_chromeos && current_cpu == "x64") || is_nacl
+ enable_nacl_untrusted = enable_nacl
+ enable_pnacl = enable_nacl_untrusted
+
+ # If debug_devtools is set to true, JavaScript files for DevTools are stored
+ # as is and loaded from disk. Otherwise, a concatenated file is stored in
+ # resources.pak. It is still possible to load JS files from disk by passing
+ # --debug-devtools cmdline switch.
+ debug_devtools = false
+
+ # Enables WebRTC.
+ # TODO(GYP) make mac and android work.
+ enable_webrtc = !is_ios && !is_mac && !is_android
+
+ # Enables the Media Router.
+ enable_media_router = !is_ios && !is_android
+
+ # Enables proprietary codecs and demuxers; e.g. H264, MOV, AAC, and MP3.
+ # Android OS includes support for proprietary codecs regardless of building
+ # Chromium or Google Chrome. We also ship Google Chrome and Chromecast with
+ # proprietary codecs.
+ # TODO(GYP) The GYP build has || chromecast==1 for this:
+ proprietary_codecs = is_android || is_chrome_branded
+
+ enable_configuration_policy = true
+
+ # Enables support for background apps.
+ enable_background = !is_ios && !is_android
+
+ enable_captive_portal_detection = !is_android && !is_ios
+
+ # Enables use of the session service, which is enabled by default.
+ # Android stores them separately on the Java side.
+ enable_session_service = !is_android && !is_ios
+
+ enable_plugin_installation = is_win || is_mac
+
+ enable_app_list = !is_ios && !is_android
+
+ enable_supervised_users = !is_ios
+
+ enable_autofill_dialog = !is_ios
+
+ enable_google_now = !is_ios && !is_android
+
+ enable_one_click_signin = is_win || is_mac || (is_linux && !is_chromeos)
+
+ enable_remoting = !is_ios && !is_android
+
+ # Enable hole punching for the protected video.
+ enable_video_hole = is_android
+
+ # Enables browser side Content Decryption Modules. Required for embedders
+ # (e.g. Android and ChromeCast) that use a browser side CDM.
+ enable_browser_cdms = is_android
+
+ # Variable safe_browsing is used to control the build time configuration
+ # for safe browsing feature. Safe browsing can be compiled in 4 different
+ # levels: 0 disables it, 1 enables it fully, and 2 enables only UI and
+ # reporting features for use with Data Saver on Mobile, and 3 enables
+ # extended mobile protection via an external API. When 3 is fully deployed,
+ # it will replace 2.
+ if (is_android) {
+ safe_browsing_mode = 2
+ } else if (is_ios) {
+ safe_browsing_mode = 0
+ } else {
+ safe_browsing_mode = 1
+ }
+}
+
+# Additional dependent variables -----------------------------------------------
+
+# Set the version of CLD.
+# 0: Don't specify the version. This option is for the Finch testing.
+# 1: Use only CLD1.
+# 2: Use only CLD2.
+if (is_android) {
+ cld_version = 1
+} else {
+ cld_version = 2
+}
+
+# libudev usage. This currently only affects the content layer.
+use_udev = is_linux
+
+# Enable the spell checker.
+enable_spellcheck = !is_android
+
+# Use the operating system's spellchecker rather than hunspell.
+use_platform_spellchecker = is_android || is_mac
+
+enable_pepper_cdms = enable_plugins && (is_linux || is_mac || is_win)
+
+# Enable basic printing support and UI.
+enable_basic_printing = !is_chromeos
+
+# Enable printing with print preview. It does not imply
+# enable_basic_printing. It's possible to build Chrome with preview only.
+enable_print_preview = !is_android
+
+# The seccomp-bpf sandbox is only supported on three architectures
+# currently.
+# Do not disable seccomp_bpf anywhere without talking to
+# security@chromium.org!
+use_seccomp_bpf = (is_linux || is_android) &&
+ (current_cpu == "x86" || current_cpu == "x64" ||
+ current_cpu == "arm" || current_cpu == "mipsel")
+
+# Enable notifications everywhere except iOS.
+enable_notifications = !is_ios
+
+# TODO(brettw) this should be moved to net and only dependents get this define.
+disable_ftp_support = is_ios
+
+enable_web_speech = !is_android && !is_ios
+
+use_dbus = is_linux
+
+enable_extensions = !is_android && !is_ios
+
+enable_task_manager = !is_ios && !is_android
+
+use_cups = is_desktop_linux || is_mac
+
+enable_themes = !is_android && !is_ios
+
+# TODO(scottmg) remove this when we've fixed printing.
+win_pdf_metafile_for_printing = true
+
+# Whether we are using the rlz library or not. Platforms like Android send
+# rlz codes for searches but do not use the library.
+enable_rlz_support = is_win || is_mac || is_ios || is_chromeos
+enable_rlz = is_chrome_branded && enable_rlz_support
+
+enable_settings_app = enable_app_list && !is_chromeos
+
+enable_service_discovery = enable_mdns || is_mac
+
+enable_wifi_bootstrapping = is_win || is_mac
+
+# Image loader extension is enabled on ChromeOS only.
+enable_image_loader_extension = is_chromeos
+
+# Chrome OS: whether to also build the upcoming version of
+# ChromeVox, which can then be enabled via a command-line switch.
+enable_chromevox_next = false
+
+# Use brlapi from brltty for braille display support.
+use_brlapi = is_chromeos
+
+# Option controlling the use of GConf (the classic GNOME configuration
+# system).
+# TODO(GYP) also require !embedded to enable.
+use_gconf = is_linux && !is_chromeos
+
+# Hangout services is an extension that adds extra features to Hangouts.
+# For official GYP builds, this flag is set, it will likely need to be
+# parameterized in the future for a similar use.
+enable_hangout_services_extension = false
+
+# Whether to back up data before sync.
+enable_pre_sync_backup = is_win || is_mac || (is_linux && !is_chromeos)
+
+# WebVR support disabled until platform implementations have been added
+enable_webvr = false
diff --git a/build/config/fnl/BUILD.gn b/build/config/fnl/BUILD.gn
new file mode 100644
index 00000000000..0eb35e3499b
--- /dev/null
+++ b/build/config/fnl/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright 2015 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.
+
+import("//build/config/linux/pkg_config.gni")
+import("//build/config/features.gni")
+import("//build/config/sysroot.gni")
+import("//build/config/ui.gni")
+
+config("sdk") {
+ if (sysroot != "") {
+ cflags = [ "--sysroot=" + sysroot ]
+ ldflags = [ "--sysroot=" + sysroot ]
+
+ # Need to get some linker flags out of the sysroot.
+ ldflags += [ exec_script("../linux/sysroot_ld_path.py",
+ [
+ rebase_path("//build/linux/sysroot_ld_path.sh",
+ root_build_dir),
+ sysroot,
+ ],
+ "value") ]
+ }
+
+ defines = [ "FNL_MUSL" ]
+
+ if (use_ozone) {
+ defines += [ "MESA_EGL_NO_X11_HEADERS" ]
+ }
+}
+
+config("fontconfig") {
+ libs = [ "fontconfig" ]
+}
+
+pkg_config("freetype2") {
+ packages = [ "freetype2" ]
+}
+
+if (use_evdev_gestures) {
+ pkg_config("libevdev-cros") {
+ packages = [ "libevdev-cros" ]
+ }
+
+ pkg_config("libgestures") {
+ packages = [ "libgestures" ]
+ }
+}
diff --git a/build/config/gcc/BUILD.gn b/build/config/gcc/BUILD.gn
new file mode 100644
index 00000000000..47bcc0b6124
--- /dev/null
+++ b/build/config/gcc/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2014 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 config causes functions not to be automatically exported from shared
+# libraries. By default, all symbols are exported but this means there are
+# lots of exports that slow everything down. In general we explicitly mark
+# which functiosn we want to export from components.
+#
+# Some third_party code assumes all functions are exported so this is separated
+# into its own config so such libraries can remove this config to make symbols
+# public again.
+#
+# See http://gcc.gnu.org/wiki/Visibility
+config("symbol_visibility_hidden") {
+ # Note that -fvisibility-inlines-hidden is set globally in the compiler
+ # config since that can almost always be applied.
+ cflags = [ "-fvisibility=hidden" ]
+}
+
+# Settings for executables and shared libraries.
+config("executable_ldconfig") {
+ if (is_android) {
+ ldflags = [
+ "-Bdynamic",
+ "-Wl,-z,nocopyreloc",
+ ]
+ } else {
+ # Android doesn't support rpath.
+ ldflags = [
+ # Want to pass "\$". GN will re-escape as required for ninja.
+ "-Wl,-rpath=\$ORIGIN/",
+ "-Wl,-rpath-link=",
+
+ # Newer binutils don't set DT_RPATH unless you disable "new" dtags
+ # and the new DT_RUNPATH doesn't work without --no-as-needed flag.
+ "-Wl,--disable-new-dtags",
+ ]
+ }
+}
+
+config("no_exceptions") {
+ no_exceptions_flags = [ "-fno-exceptions" ]
+ cflags_cc = no_exceptions_flags
+ cflags_objcc = no_exceptions_flags
+}
diff --git a/build/config/gcc/gcc_version.gni b/build/config/gcc/gcc_version.gni
new file mode 100644
index 00000000000..6741e45bf39
--- /dev/null
+++ b/build/config/gcc/gcc_version.gni
@@ -0,0 +1,26 @@
+# Copyright 2014 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.
+
+if (is_android) {
+ gcc_version = 49
+} else if (current_toolchain == "//build/toolchain/cros:target" ||
+ current_toolchain == "//build/toolchain/linux:mipsel") {
+ gcc_version = exec_script("../../compiler_version.py",
+ [
+ "target",
+ "compiler",
+ ],
+ "value")
+} else if (current_toolchain == "//build/toolchain/linux:x64" ||
+ current_toolchain == "//build/toolchain/linux:x86") {
+ # These are both the same and just use the default gcc on the system.
+ gcc_version = exec_script("../../compiler_version.py",
+ [
+ "host",
+ "compiler",
+ ],
+ "value")
+} else {
+ gcc_version = 0
+}
diff --git a/build/config/ios/BUILD.gn b/build/config/ios/BUILD.gn
new file mode 100644
index 00000000000..02923155891
--- /dev/null
+++ b/build/config/ios/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2014 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.
+
+import("//build/config/sysroot.gni")
+import("//build/config/ios/ios_sdk.gni")
+
+config("sdk") {
+ common_flags = [
+ "-stdlib=libc++",
+ ]
+
+ cflags = common_flags
+ ldflags = common_flags
+}
diff --git a/build/config/ios/XcodeHarness/.gitignore b/build/config/ios/XcodeHarness/.gitignore
new file mode 100644
index 00000000000..89c499e451e
--- /dev/null
+++ b/build/config/ios/XcodeHarness/.gitignore
@@ -0,0 +1,18 @@
+# Xcode
+.DS_Store
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+*.xcworkspace
+!default.xcworkspace
+xcuserdata
+profile
+*.moved-aside
+DerivedData
+.idea/
diff --git a/build/config/ios/XcodeHarness/FakeMain.m b/build/config/ios/XcodeHarness/FakeMain.m
new file mode 100644
index 00000000000..5ddbd34dc18
--- /dev/null
+++ b/build/config/ios/XcodeHarness/FakeMain.m
@@ -0,0 +1,10 @@
+// Copyright 2015 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.
+
+#import
+
+int main(int argc, char* argv[]) {
+ NSCAssert(NO, @"Placeholder for Xcode. Should never be run");
+ return EXIT_FAILURE;
+}
diff --git a/build/config/ios/find_signing_identity.py b/build/config/ios/find_signing_identity.py
new file mode 100644
index 00000000000..2fe67f98e77
--- /dev/null
+++ b/build/config/ios/find_signing_identity.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+# Copyright (c) 2015 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.
+
+import subprocess
+import sys
+import re
+
+def ListIdentities():
+ return subprocess.check_output([
+ '/usr/bin/env',
+ 'xcrun',
+ 'security',
+ 'find-identity',
+ '-v',
+ '-p',
+ 'codesigning',
+ ]).strip()
+
+
+def FindValidIdentity():
+ lines = ListIdentities().splitlines()
+ # Look for something like "2) XYZ "iPhone Developer: Name (ABC)""
+ exp = re.compile('.*\) ([A-F|0-9]*)(.*)')
+ for line in lines:
+ res = exp.match(line)
+ if res is None:
+ continue
+ if "iPhone Developer: Google Development" in res.group(2):
+ return res.group(1)
+ return ""
+
+
+if __name__ == '__main__':
+ print FindValidIdentity()
diff --git a/build/config/ios/ios_app.py b/build/config/ios/ios_app.py
new file mode 100644
index 00000000000..b8bf5443ae0
--- /dev/null
+++ b/build/config/ios/ios_app.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# Copyright (c) 2015 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.
+
+import argparse
+import os
+import errno
+import subprocess
+import sys
+
+PLUTIL = [
+ '/usr/bin/env',
+ 'xcrun',
+ 'plutil'
+]
+
+def MakeDirectories(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(path):
+ return 0
+ else:
+ return -1
+
+ return 0
+
+
+def ProcessInfoPlist(args):
+ output_plist_file = os.path.abspath(os.path.join(args.output, 'Info.plist'))
+
+ if MakeDirectories(os.path.dirname(output_plist_file)) == -1:
+ return -1
+
+ return subprocess.check_call( PLUTIL + [
+ '-convert',
+ 'binary1',
+ '-o',
+ output_plist_file,
+ '--',
+ args.input,
+ ])
+
+
+def PerformCodeSigning(args):
+ return subprocess.check_call([
+ '/usr/bin/env',
+ 'xcrun',
+ 'codesign',
+ '--entitlements',
+ args.entitlements_path,
+ '--sign',
+ args.identity,
+ '-f',
+ args.application_path,
+ ])
+
+def GenerateDSYM(args):
+ return subprocess.check_call([
+ '/usr/bin/env',
+ 'xcrun',
+ 'dsymutil',
+ '-o',
+ args.output,
+ args.executable_path
+ ])
+
+
+def GenerateProjectStructure(args):
+ application_path = os.path.join( args.dir, args.name + ".app" )
+ return MakeDirectories( application_path )
+
+
+def Main():
+ parser = argparse.ArgumentParser(description='A script that aids in '
+ 'the creation of an iOS application')
+
+ subparsers = parser.add_subparsers()
+
+ # Plist Parser
+
+ plist_parser = subparsers.add_parser('plist',
+ help='Process the Info.plist')
+ plist_parser.set_defaults(func=ProcessInfoPlist)
+
+ plist_parser.add_argument('-i', dest='input', help='The input plist path')
+ plist_parser.add_argument('-o', dest='output', help='The output plist dir')
+
+ # Directory Structure Parser
+
+ dir_struct_parser = subparsers.add_parser('structure',
+ help='Creates the directory of an iOS application')
+
+ dir_struct_parser.set_defaults(func=GenerateProjectStructure)
+
+ dir_struct_parser.add_argument('-d', dest='dir', help='Out directory')
+ dir_struct_parser.add_argument('-n', dest='name', help='App name')
+
+ # Code Signing
+
+ code_signing_parser = subparsers.add_parser('codesign',
+ help='Code sign the specified application')
+
+ code_signing_parser.set_defaults(func=PerformCodeSigning)
+
+ code_signing_parser.add_argument('-p', dest='application_path', required=True,
+ help='The application path')
+ code_signing_parser.add_argument('-i', dest='identity', required=True,
+ help='The code signing identity to use')
+ code_signing_parser.add_argument('-e', dest='entitlements_path',
+ required=True,
+ help='The path to the entitlements .xcent')
+
+ # dSYM Generation
+
+ dsym_generation_parser = subparsers.add_parser('dsym',
+ help='Generate a .dSYM file for an executable')
+
+ dsym_generation_parser.set_defaults(func=GenerateDSYM)
+
+ dsym_generation_parser.add_argument('-e', dest='executable_path',
+ required=True,
+ help='The executable path')
+ dsym_generation_parser.add_argument('-o', dest='output',
+ required=True,
+ help='The output file name')
+
+ # Engage!
+
+ args = parser.parse_args()
+
+ return args.func(args)
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/build/config/ios/ios_sdk.gni b/build/config/ios/ios_sdk.gni
new file mode 100644
index 00000000000..65654a57166
--- /dev/null
+++ b/build/config/ios/ios_sdk.gni
@@ -0,0 +1,58 @@
+# Copyright 2015 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.
+
+declare_args() {
+ # SDK path to use. When empty this will use the default SDK based on the
+ # value of use_ios_simulator.
+ ios_sdk_path = ""
+
+ # Set to true when targeting a simulator build on iOS. False means that the
+ # target is for running on the device. The default value is to use the
+ # Simulator except when targeting GYP's Xcode builds (for compat with the
+ # existing GYP build).
+ use_ios_simulator = true
+
+ # Version of iOS that we're targeting.
+ ios_deployment_target = "6.0"
+
+ # The iOS Code signing identity to use
+ ios_code_signing_identity = ""
+
+ # The path to the iOS device SDK.
+ ios_device_sdk_path = ""
+
+ # The path to the iOS simulator SDK.
+ ios_simulator_sdk_path = ""
+}
+
+if (ios_device_sdk_path == "") {
+ _ios_device_sdk_result =
+ exec_script("ios_sdk.py", [ "iphoneos" ], "list lines")
+ ios_device_sdk_path = _ios_device_sdk_result[0]
+}
+
+if (ios_simulator_sdk_path == "") {
+ _ios_sim_sdk_result =
+ exec_script("ios_sdk.py", [ "iphonesimulator" ], "list lines")
+ ios_simulator_sdk_path = _ios_sim_sdk_result[0]
+}
+
+# Compute default target.
+if (use_ios_simulator) {
+ ios_sdk_path = ios_simulator_sdk_path
+} else {
+ ios_sdk_path = ios_device_sdk_path
+}
+
+if (use_ios_simulator) {
+ # Always disable code signing on the simulator
+ ios_code_signing_identity = ""
+} else {
+ # If an identity is not provided, look for one on the host
+ if (ios_code_signing_identity == "") {
+ _ios_identities = exec_script("find_signing_identity.py",
+ [], "list lines")
+ ios_code_signing_identity = _ios_identities[0]
+ }
+}
diff --git a/build/config/ios/ios_sdk.py b/build/config/ios/ios_sdk.py
new file mode 100644
index 00000000000..dfec4dbada9
--- /dev/null
+++ b/build/config/ios/ios_sdk.py
@@ -0,0 +1,19 @@
+# Copyright 2014 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.
+
+import subprocess
+import sys
+
+# This script returns the path to the SDK of the given type. Pass the type of
+# SDK you want, which is typically "iphone" or "iphonesimulator".
+#
+# In the GYP build, this is done inside GYP itself based on the SDKROOT
+# variable.
+
+if len(sys.argv) != 2:
+ print "Takes one arg (SDK to find)"
+ sys.exit(1)
+
+print subprocess.check_output(['xcodebuild', '-version', '-sdk',
+ sys.argv[1], 'Path']).strip()
diff --git a/build/config/ios/lldb_start_commands.txt b/build/config/ios/lldb_start_commands.txt
new file mode 100644
index 00000000000..42e0b14c747
--- /dev/null
+++ b/build/config/ios/lldb_start_commands.txt
@@ -0,0 +1,4 @@
+breakpoint set --name UIApplicationMain
+breakpoint set --name objc_exception_throw
+continue
+script print "........ Debugger break on main() ........"
diff --git a/build/config/ios/rules.gni b/build/config/ios/rules.gni
new file mode 100644
index 00000000000..429c153e35b
--- /dev/null
+++ b/build/config/ios/rules.gni
@@ -0,0 +1,234 @@
+# Copyright 2015 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.
+
+ios_app_script = "//build/config/ios/ios_app.py"
+
+template("code_sign_ios") {
+ assert(defined(invoker.entitlements_path),
+ "The path to the entitlements .xcent file")
+ assert(defined(invoker.identity),
+ "The code signing identity")
+ assert(defined(invoker.application_path),
+ "The application to code sign")
+ assert(defined(invoker.deps))
+
+ action(target_name) {
+ sources = [
+ invoker.entitlements_path,
+ ]
+
+ _application_path = invoker.application_path
+
+ script = ios_app_script
+
+ outputs = [
+ "$_application_path/_CodeSignature/CodeResources"
+ ]
+
+ args = [
+ "codesign",
+ "-p",
+ rebase_path(invoker.application_path, root_build_dir),
+ "-i",
+ invoker.identity,
+ "-e",
+ rebase_path(invoker.entitlements_path, root_build_dir),
+ ]
+
+ deps = invoker.deps
+ }
+}
+
+template("xcode_harness_ios") {
+ assert(defined(invoker.deps),
+ "The dependencies must be specified")
+ assert(defined(invoker.app_bundle),
+ "The app bundle must be defined")
+ assert(defined(invoker.app_name),
+ "The application name must be defined")
+ app_name = invoker.app_name
+
+ xcode_project_gen_target_name = app_name + "_xcode"
+ copy(xcode_project_gen_target_name) {
+ sources = [
+ "//build/config/ios/XcodeHarness/FakeMain.m",
+ "//build/config/ios/XcodeHarness/Harness.xcodeproj",
+ ]
+ outputs = [
+ "$root_build_dir/$xcode_project_gen_target_name/{{source_file_part}}",
+ ]
+ }
+
+ bundle_copy_gen_target_name = app_name + "_bundle_copy"
+ copy(bundle_copy_gen_target_name) {
+ sources = [
+ invoker.app_bundle
+ ]
+ outputs = [
+ "$root_build_dir/$xcode_project_gen_target_name/Application",
+ ]
+
+ deps = invoker.deps
+ }
+
+ group(target_name) {
+ deps = [
+ ":$xcode_project_gen_target_name",
+ ":$bundle_copy_gen_target_name",
+ ]
+ }
+}
+
+template("resource_copy_ios") {
+ assert(defined(invoker.resources),
+ "The source list of resources to copy over")
+ assert(defined(invoker.bundle_directory),
+ "The directory within the bundle to place the sources in")
+ assert(defined(invoker.app_name),
+ "The name of the application")
+
+ _bundle_directory = invoker.bundle_directory
+ _app_name = invoker.app_name
+ _resources = invoker.resources
+
+ copy(target_name) {
+ set_sources_assignment_filter([])
+ sources = _resources
+ outputs = [ "$root_build_dir/$_app_name.app/$_bundle_directory/{{source_file_part}}" ]
+
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ }
+}
+
+template("ios_app") {
+
+ assert(defined(invoker.deps),
+ "Dependencies must be specified for $target_name")
+ assert(defined(invoker.info_plist),
+ "The application plist file must be specified for $target_name")
+ assert(defined(invoker.app_name),
+ "The name of iOS application for $target_name")
+ assert(defined(invoker.entitlements_path),
+ "The entitlements path must be specified for $target_name")
+ assert(defined(invoker.code_signing_identity),
+ "The entitlements path must be specified for $target_name")
+
+ # We just create a variable so we can use the same in interpolation
+ app_name = invoker.app_name
+
+ # Generate the project structure
+
+ struct_gen_target_name = target_name + "_struct"
+
+ action(struct_gen_target_name) {
+
+ script = ios_app_script
+
+ sources = []
+ outputs = [ "$root_build_dir/$app_name.app" ]
+
+ args = [
+ "structure",
+ "-d",
+ rebase_path(root_build_dir),
+ "-n",
+ app_name
+ ]
+
+ }
+
+ # Generate the executable
+
+ bin_gen_target_name = target_name + "_bin"
+
+ executable(bin_gen_target_name) {
+ libs = [
+ "AudioToolbox.framework",
+ "AVFoundation.framework",
+ "OpenGLES.framework",
+ "QuartzCore.framework",
+ "UIKit.framework",
+ ]
+ deps = invoker.deps
+ output_name = app_name
+ }
+
+ # Process the Info.plist
+
+ plist_gen_target_name = target_name + "_plist"
+
+ action(plist_gen_target_name) {
+
+ script = ios_app_script
+
+ sources = [ invoker.info_plist ]
+ outputs = [ "$root_build_dir/plist/$app_name/Info.plist" ]
+
+ args = [
+ "plist",
+ "-i",
+ rebase_path(invoker.info_plist, root_build_dir),
+ "-o",
+ rebase_path("$root_build_dir/plist/$app_name"),
+ ]
+ }
+
+ # Copy the generated binaries and assets to their appropriate locations
+
+ copy_gen_target_name = target_name + "_copy"
+ copy(copy_gen_target_name) {
+ sources = [
+ "$root_build_dir/plist/$app_name/Info.plist",
+ "$root_build_dir/$app_name",
+ ]
+
+ outputs = [
+ "$root_build_dir/$app_name.app/{{source_file_part}}"
+ ]
+
+ deps = [
+ ":$struct_gen_target_name",
+ ":$bin_gen_target_name",
+ ":$plist_gen_target_name",
+ ]
+ }
+
+ # Generate the Xcode Harness for Profiling
+
+ xcode_harness_gen_target_name = app_name + "_harness"
+ xcode_harness_ios(xcode_harness_gen_target_name) {
+ app_bundle = "$root_build_dir/$app_name.app"
+ deps = [
+ ":$bin_gen_target_name",
+ ":$struct_gen_target_name",
+ ":$copy_gen_target_name",
+ ]
+ }
+
+ # Perform Code Signing
+
+ code_sign_gen_target_name = target_name + "_codesign"
+ code_sign_ios(code_sign_gen_target_name) {
+ entitlements_path = invoker.entitlements_path
+ identity = invoker.code_signing_identity
+ application_path = "$root_build_dir/$app_name.app"
+ deps = [ ":$copy_gen_target_name" ]
+ }
+
+ # Top level group
+
+ group(target_name) {
+ # Skip code signing if no identity is provided. This is useful for simulator
+ # builds
+ deps = [ ":$xcode_harness_gen_target_name" ]
+ if (invoker.code_signing_identity == "") {
+ deps += [ ":$copy_gen_target_name" ]
+ } else {
+ deps += [ ":$code_sign_gen_target_name" ]
+ }
+ }
+
+}
diff --git a/build/config/linux/BUILD.gn b/build/config/linux/BUILD.gn
new file mode 100644
index 00000000000..71a73b2d625
--- /dev/null
+++ b/build/config/linux/BUILD.gn
@@ -0,0 +1,62 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/linux/pkg_config.gni")
+import("//build/config/features.gni")
+import("//build/config/sysroot.gni")
+import("//build/config/ui.gni")
+
+config("sdk") {
+ if (sysroot != "") {
+ cflags = [ "--sysroot=" + sysroot ]
+ ldflags = [ "--sysroot=" + sysroot ]
+
+ # Need to get some linker flags out of the sysroot.
+ ldflags += [ exec_script("sysroot_ld_path.py",
+ [
+ rebase_path("//build/linux/sysroot_ld_path.sh",
+ root_build_dir),
+ sysroot,
+ ],
+ "value") ]
+ }
+}
+
+config("fontconfig") {
+ libs = [ "fontconfig" ]
+}
+
+pkg_config("freetype2") {
+ packages = [ "freetype2" ]
+}
+
+config("x11") {
+ libs = [
+ "X11",
+ "Xcomposite",
+ "Xcursor",
+ "Xdamage",
+ "Xext",
+ "Xfixes",
+ "Xi",
+ "Xrender",
+ "Xtst",
+ ]
+}
+
+config("xrandr") {
+ libs = [ "Xrandr" ]
+}
+
+config("xinerama") {
+ libs = [ "Xinerama" ]
+}
+
+config("xcomposite") {
+ libs = [ "Xcomposite" ]
+}
+
+config("xext") {
+ libs = [ "Xext" ]
+}
diff --git a/build/config/linux/gtk/BUILD.gn b/build/config/linux/gtk/BUILD.gn
new file mode 100644
index 00000000000..9c9c696fcf3
--- /dev/null
+++ b/build/config/linux/gtk/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2015 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.
+
+import("//build/config/linux/pkg_config.gni")
+
+assert(is_linux, "This file should only be referenced on Linux")
+
+# Depend on //build/config/linux/gtk to use GTK.
+#
+# GN doesn't check visibility for configs so we give this an obviously internal
+# name to discourage random targets from accidentally depending on this and
+# bypassing the GTK target's visibility.
+pkg_config("gtk_internal_config") {
+ # Gtk requires gmodule, but it does not list it as a dependency in some
+ # misconfigured systems.
+ packages = [
+ "gmodule-2.0",
+ "gtk+-2.0",
+ "gthread-2.0",
+ ]
+}
+
+# Basically no parts of Chrome should depend on GTK. To prevent accidents, the
+# parts that explicitly need GTK are whitelisted on this target.
+group("gtk") {
+ visibility = [
+ "//chrome/browser/ui/libgtk2ui",
+ "//gpu/gles2_conform_support:gles2_conform_test_windowless",
+ "//remoting/host",
+ "//remoting/host/it2me:remote_assistance_host",
+ "//remoting/host:remoting_me2me_host_static",
+ ]
+ public_configs = [ ":gtk_internal_config" ]
+}
+
+# Depend on "gtkprint" to get this.
+pkg_config("gtkprint_internal_config") {
+ packages = [ "gtk+-unix-print-2.0" ]
+}
+
+group("gtkprint") {
+ visibility = [ "//chrome/browser/ui/libgtk2ui" ]
+ public_configs = [ ":gtkprint_internal_config" ]
+}
diff --git a/build/config/linux/pkg-config.py b/build/config/linux/pkg-config.py
new file mode 100644
index 00000000000..fadcc0bbcf9
--- /dev/null
+++ b/build/config/linux/pkg-config.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2013 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.
+
+import json
+import os
+import subprocess
+import sys
+import re
+from optparse import OptionParser
+
+# This script runs pkg-config, optionally filtering out some results, and
+# returns the result.
+#
+# The result will be [ , , , , ]
+# where each member is itself a list of strings.
+#
+# You can filter out matches using "-v " where all results from
+# pkgconfig matching the given regular expression will be ignored. You can
+# specify more than one regular expression my specifying "-v" more than once.
+#
+# You can specify a sysroot using "-s " where sysroot is the absolute
+# system path to the sysroot used for compiling. This script will attempt to
+# generate correct paths for the sysroot.
+#
+# When using a sysroot, you must also specify the architecture via
+# "-a " where arch is either "x86" or "x64".
+#
+# Additionally, you can specify the option --atleast-version. This will skip
+# the normal outputting of a dictionary and instead print true or false,
+# depending on the return value of pkg-config for the given package.
+
+# If this is run on non-Linux platforms, just return nothing and indicate
+# success. This allows us to "kind of emulate" a Linux build from other
+# platforms.
+if sys.platform.find("linux") == -1:
+ print "[[],[],[],[],[]]"
+ sys.exit(0)
+
+
+def SetConfigPath(options):
+ """Set the PKG_CONFIG_PATH environment variable.
+ This takes into account any sysroot and architecture specification from the
+ options on the given command line."""
+
+ sysroot = options.sysroot
+ if not sysroot:
+ sysroot = ""
+
+ # Compute the library path name based on the architecture.
+ arch = options.arch
+ if sysroot and not arch:
+ print "You must specify an architecture via -a if using a sysroot."
+ sys.exit(1)
+ if arch == 'x64':
+ libpath = 'lib64'
+ else:
+ libpath = 'lib'
+
+ # Add the sysroot path to the environment's PKG_CONFIG_PATH
+ config_path = sysroot + '/usr/' + libpath + '/pkgconfig'
+ config_path += ':' + sysroot + '/usr/share/pkgconfig'
+ if 'PKG_CONFIG_PATH' in os.environ:
+ os.environ['PKG_CONFIG_PATH'] += ':' + config_path
+ else:
+ os.environ['PKG_CONFIG_PATH'] = config_path
+
+
+def GetPkgConfigPrefixToStrip(args):
+ """Returns the prefix from pkg-config where packages are installed.
+ This returned prefix is the one that should be stripped from the beginning of
+ directory names to take into account sysroots."""
+ # Some sysroots, like the Chromium OS ones, may generate paths that are not
+ # relative to the sysroot. For example,
+ # /path/to/chroot/build/x86-generic/usr/lib/pkgconfig/pkg.pc may have all
+ # paths relative to /path/to/chroot (i.e. prefix=/build/x86-generic/usr)
+ # instead of relative to /path/to/chroot/build/x86-generic (i.e prefix=/usr).
+ # To support this correctly, it's necessary to extract the prefix to strip
+ # from pkg-config's |prefix| variable.
+ prefix = subprocess.check_output(["pkg-config", "--variable=prefix"] + args,
+ env=os.environ)
+ if prefix[-4] == '/usr':
+ return prefix[4:]
+ return prefix
+
+
+def MatchesAnyRegexp(flag, list_of_regexps):
+ """Returns true if the first argument matches any regular expression in the
+ given list."""
+ for regexp in list_of_regexps:
+ if regexp.search(flag) != None:
+ return True
+ return False
+
+
+def RewritePath(path, strip_prefix, sysroot):
+ """Rewrites a path by stripping the prefix and prepending the sysroot."""
+ if os.path.isabs(path) and not path.startswith(sysroot):
+ if path.startswith(strip_prefix):
+ path = path[len(strip_prefix):]
+ path = path.lstrip('/')
+ return os.path.join(sysroot, path)
+ else:
+ return path
+
+
+parser = OptionParser()
+parser.add_option('-p', action='store', dest='pkg_config', type='string',
+ default='pkg-config')
+parser.add_option('-v', action='append', dest='strip_out', type='string')
+parser.add_option('-s', action='store', dest='sysroot', type='string')
+parser.add_option('-a', action='store', dest='arch', type='string')
+parser.add_option('--atleast-version', action='store',
+ dest='atleast_version', type='string')
+parser.add_option('--libdir', action='store_true', dest='libdir')
+(options, args) = parser.parse_args()
+
+# Make a list of regular expressions to strip out.
+strip_out = []
+if options.strip_out != None:
+ for regexp in options.strip_out:
+ strip_out.append(re.compile(regexp))
+
+SetConfigPath(options)
+if options.sysroot:
+ prefix = GetPkgConfigPrefixToStrip(args)
+else:
+ prefix = ''
+
+if options.atleast_version:
+ # When asking for the return value, just run pkg-config and print the return
+ # value, no need to do other work.
+ if not subprocess.call([options.pkg_config,
+ "--atleast-version=" + options.atleast_version] +
+ args,
+ env=os.environ):
+ print "true"
+ else:
+ print "false"
+ sys.exit(0)
+
+if options.libdir:
+ try:
+ libdir = subprocess.check_output([options.pkg_config,
+ "--variable=libdir"] +
+ args,
+ env=os.environ)
+ except:
+ print "Error from pkg-config."
+ sys.exit(1)
+ sys.stdout.write(libdir.strip())
+ sys.exit(0)
+
+try:
+ flag_string = subprocess.check_output(
+ [ options.pkg_config, "--cflags", "--libs-only-l", "--libs-only-L" ] +
+ args, env=os.environ)
+ # For now just split on spaces to get the args out. This will break if
+ # pkgconfig returns quoted things with spaces in them, but that doesn't seem
+ # to happen in practice.
+ all_flags = flag_string.strip().split(' ')
+except:
+ print "Could not run pkg-config."
+ sys.exit(1)
+
+
+sysroot = options.sysroot
+if not sysroot:
+ sysroot = ''
+
+includes = []
+cflags = []
+libs = []
+lib_dirs = []
+ldflags = []
+
+for flag in all_flags[:]:
+ if len(flag) == 0 or MatchesAnyRegexp(flag, strip_out):
+ continue;
+
+ if flag[:2] == '-l':
+ libs.append(RewritePath(flag[2:], prefix, sysroot))
+ elif flag[:2] == '-L':
+ lib_dirs.append(RewritePath(flag[2:], prefix, sysroot))
+ elif flag[:2] == '-I':
+ includes.append(RewritePath(flag[2:], prefix, sysroot))
+ elif flag[:3] == '-Wl':
+ ldflags.append(flag)
+ elif flag == '-pthread':
+ # Many libs specify "-pthread" which we don't need since we always include
+ # this anyway. Removing it here prevents a bunch of duplicate inclusions on
+ # the command line.
+ pass
+ else:
+ cflags.append(flag)
+
+# Output a GN array, the first one is the cflags, the second are the libs. The
+# JSON formatter prints GN compatible lists when everything is a list of
+# strings.
+print json.dumps([includes, cflags, libs, lib_dirs, ldflags])
diff --git a/build/config/linux/pkg_config.gni b/build/config/linux/pkg_config.gni
new file mode 100644
index 00000000000..34ed1afa17c
--- /dev/null
+++ b/build/config/linux/pkg_config.gni
@@ -0,0 +1,83 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/sysroot.gni")
+
+# Defines a config specifying the result of running pkg-config for the given
+# packages. Put the package names you want to query in the "packages" variable
+# inside the template invocation.
+#
+# You can also add defines via the "defines" variable. This can be useful to
+# add this to the config to pass defines that the library expects to get by
+# users of its headers.
+#
+# Example:
+# pkg_config("mything") {
+# packages = [ "mything1", "mything2" ]
+# defines = [ "ENABLE_AWESOME" ]
+# }
+#
+# You can also use "extra args" to filter out results (see pkg-config.py):
+# extra_args = [ "-v, "foo" ]
+# To ignore libs and ldflags (only cflags/defines will be set, which is useful
+# when doing manual dynamic linking), set:
+# ignore_libs = true
+
+declare_args() {
+ # A pkg-config wrapper to call instead of trying to find and call the right
+ # pkg-config directly. Wrappers like this are common in cross-compilation
+ # environments.
+ # Leaving it blank defaults to searching PATH for 'pkg-config' and relying on
+ # the sysroot mechanism to find the right .pc files.
+ pkg_config = ""
+}
+
+pkg_config_script = "//build/config/linux/pkg-config.py"
+
+# Define the args we pass to the pkg-config script for other build files that
+# need to invoke it manually.
+if (sysroot != "") {
+ # Pass the sysroot if we're using one (it requires the CPU arch also).
+ pkg_config_args = [
+ "-s",
+ sysroot,
+ "-a",
+ current_cpu,
+ ]
+} else if (pkg_config != "") {
+ pkg_config_args = [
+ "-p",
+ pkg_config,
+ ]
+} else {
+ pkg_config_args = []
+}
+
+template("pkg_config") {
+ assert(defined(invoker.packages),
+ "Variable |packages| must be defined to be a list in pkg_config.")
+ config(target_name) {
+ args = pkg_config_args + invoker.packages
+ if (defined(invoker.extra_args)) {
+ args += invoker.extra_args
+ }
+
+ pkgresult = exec_script(pkg_config_script, args, "value")
+ include_dirs = pkgresult[0]
+ cflags = pkgresult[1]
+
+ if (!defined(invoker.ignore_libs) || !invoker.ignore_libs) {
+ libs = pkgresult[2]
+ lib_dirs = pkgresult[3]
+ ldflags = pkgresult[4]
+ }
+
+ if (defined(invoker.defines)) {
+ defines = invoker.defines
+ }
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ }
+}
diff --git a/build/config/linux/sysroot_ld_path.py b/build/config/linux/sysroot_ld_path.py
new file mode 100644
index 00000000000..4bce7ee3e2b
--- /dev/null
+++ b/build/config/linux/sysroot_ld_path.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2013 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 file takes two arguments, the relative location of the shell script that
+# does the checking, and the name of the sysroot.
+
+# TODO(brettw) the build/linux/sysroot_ld_path.sh script should be rewritten in
+# Python in this file.
+
+import subprocess
+import sys
+
+if len(sys.argv) != 3:
+ print "Need two arguments"
+ sys.exit(1)
+
+result = subprocess.check_output([sys.argv[1], sys.argv[2]]).strip()
+
+print '"' + result + '"'
diff --git a/build/config/locales.gni b/build/config/locales.gni
new file mode 100644
index 00000000000..a628007506c
--- /dev/null
+++ b/build/config/locales.gni
@@ -0,0 +1,118 @@
+# Copyright 2014 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.
+
+# Note: keep in sync with below.
+locales = [
+ "am",
+ "ar",
+ "bg",
+ "bn",
+ "ca",
+ "cs",
+ "da",
+ "de",
+ "el",
+ "en-GB",
+ "en-US",
+ "es-419",
+ "es",
+ "et",
+ "fa",
+ "fi",
+ "fil",
+ "fr",
+ "gu",
+ "he",
+ "hi",
+ "hr",
+ "hu",
+ "id",
+ "it",
+ "ja",
+ "kn",
+ "ko",
+ "lt",
+ "lv",
+ "ml",
+ "mr",
+ "ms",
+ "nb",
+ "nl",
+ "pl",
+ "pt-BR",
+ "pt-PT",
+ "ro",
+ "ru",
+ "sk",
+ "sl",
+ "sr",
+ "sv",
+ "sw",
+ "ta",
+ "te",
+ "th",
+ "tr",
+ "uk",
+ "vi",
+ "zh-CN",
+ "zh-TW",
+]
+
+# Same as the locales list but in the format Mac expects for output files:
+# it uses underscores instead of hyphens, and "en" instead of "en-US".
+locales_as_mac_outputs = [
+ "am",
+ "ar",
+ "bg",
+ "bn",
+ "ca",
+ "cs",
+ "da",
+ "de",
+ "el",
+ "en_GB",
+ "en",
+ "es_419",
+ "es",
+ "et",
+ "fa",
+ "fi",
+ "fil",
+ "fr",
+ "gu",
+ "he",
+ "hi",
+ "hr",
+ "hu",
+ "id",
+ "it",
+ "ja",
+ "kn",
+ "ko",
+ "lt",
+ "lv",
+ "ml",
+ "mr",
+ "ms",
+ "nb",
+ "nl",
+ "pl",
+ "pt_BR",
+ "pt_PT",
+ "ro",
+ "ru",
+ "sk",
+ "sl",
+ "sr",
+ "sv",
+ "sw",
+ "ta",
+ "te",
+ "th",
+ "tr",
+ "uk",
+ "vi",
+ "zh_CN",
+ "zh_TW",
+]
diff --git a/build/config/mac/BUILD.gn b/build/config/mac/BUILD.gn
new file mode 100644
index 00000000000..132e12ce413
--- /dev/null
+++ b/build/config/mac/BUILD.gn
@@ -0,0 +1,34 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/sysroot.gni")
+
+config("sdk") {
+ common_flags = [
+ "-stdlib=libc++",
+ ]
+
+ cflags = common_flags
+ ldflags = common_flags
+}
+
+# On Mac, this is used for everything except static libraries.
+config("mac_dynamic_flags") {
+ ldflags = [
+ "-Wl,-search_paths_first",
+ "-L.",
+
+ # Path for loading shared libraries for unbundled binaries.
+ "-Wl,-rpath,@loader_path/.",
+
+ # Path for loading shared libraries for bundled binaries. Get back from
+ # Binary.app/Contents/MacOS.
+ "-Wl,-rpath,@loader_path/../../..",
+ ]
+}
+
+# On Mac, this is used only for executables.
+config("mac_executable_flags") {
+ ldflags = [ "-Wl,-pie" ] # Position independent.
+}
diff --git a/build/config/mac/mac_app.py b/build/config/mac/mac_app.py
new file mode 100644
index 00000000000..909fa583e64
--- /dev/null
+++ b/build/config/mac/mac_app.py
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+# Copyright (c) 2015 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.
+
+import argparse
+import os
+import errno
+import subprocess
+import sys
+
+PLUTIL = [
+ '/usr/bin/env',
+ 'xcrun',
+ 'plutil'
+]
+
+IBTOOL = [
+ '/usr/bin/env',
+ 'xcrun',
+ 'ibtool',
+]
+
+
+def MakeDirectories(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(path):
+ return 0
+ else:
+ return -1
+
+ return 0
+
+
+def ProcessInfoPlist(args):
+ output_plist_file = os.path.abspath(os.path.join(args.output, 'Info.plist'))
+ return subprocess.check_call( PLUTIL + [
+ '-convert',
+ 'binary1',
+ '-o',
+ output_plist_file,
+ '--',
+ args.input,
+ ])
+
+
+def ProcessNIB(args):
+ output_nib_file = os.path.join(os.path.abspath(args.output),
+ "%s.nib" % os.path.splitext(os.path.basename(args.input))[0])
+
+ return subprocess.check_call(IBTOOL + [
+ '--module',
+ args.module,
+ '--auto-activate-custom-fonts',
+ '--target-device',
+ 'mac',
+ '--compile',
+ output_nib_file,
+ os.path.abspath(args.input),
+ ])
+
+
+def GenerateProjectStructure(args):
+ application_path = os.path.join( args.dir, args.name + ".app", "Contents" )
+ return MakeDirectories( application_path )
+
+
+def Main():
+ parser = argparse.ArgumentParser(description='A script that aids in '
+ 'the creation of an Mac application')
+
+ subparsers = parser.add_subparsers()
+
+ # Plist Parser
+
+ plist_parser = subparsers.add_parser('plist',
+ help='Process the Info.plist')
+ plist_parser.set_defaults(func=ProcessInfoPlist)
+
+ plist_parser.add_argument('-i', dest='input', help='The input plist path')
+ plist_parser.add_argument('-o', dest='output', help='The output plist dir')
+
+ # NIB Parser
+
+ plist_parser = subparsers.add_parser('nib',
+ help='Process a NIB file')
+ plist_parser.set_defaults(func=ProcessNIB)
+
+ plist_parser.add_argument('-i', dest='input', help='The input nib path')
+ plist_parser.add_argument('-o', dest='output', help='The output nib dir')
+ plist_parser.add_argument('-m', dest='module', help='The module name')
+
+ # Directory Structure Parser
+
+ dir_struct_parser = subparsers.add_parser('structure',
+ help='Creates the directory of an Mac application')
+
+ dir_struct_parser.set_defaults(func=GenerateProjectStructure)
+
+ dir_struct_parser.add_argument('-d', dest='dir', help='Out directory')
+ dir_struct_parser.add_argument('-n', dest='name', help='App name')
+
+ # Engage!
+
+ args = parser.parse_args()
+
+ return args.func(args)
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/build/config/mac/mac_sdk.gni b/build/config/mac/mac_sdk.gni
new file mode 100644
index 00000000000..54b68c40cac
--- /dev/null
+++ b/build/config/mac/mac_sdk.gni
@@ -0,0 +1,41 @@
+# Copyright 2014 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.
+
+import("//build/config/chrome_build.gni")
+
+declare_args() {
+ # Minimum supported version of the Mac SDK.
+ mac_sdk_min = "10.8"
+
+ # Path to a specific version of the Mac SDKJ, not including a backslash at
+ # the end. If empty, the path to the lowest version greater than or equal to
+ # mac_sdk_min is used.
+ mac_sdk_path = ""
+}
+
+find_sdk_args = [ "--print_sdk_path" ]
+if (is_chrome_branded && is_official_build) {
+ find_sdk_args += [
+ "--verify",
+ mac_sdk_min,
+ "--sdk_path=" + mac_sdk_path,
+ ]
+} else {
+ find_sdk_args += [ mac_sdk_min ]
+}
+
+# The tool will print the SDK path on the first line, and the version on the
+# second line.
+find_sdk_lines =
+ exec_script("//build/mac/find_sdk.py", find_sdk_args, "list lines")
+mac_sdk_version = find_sdk_lines[1]
+if (mac_sdk_path == "") {
+ # TODO(brettw) http://crbug.com/335325 when everybody moves to XCode 5 we
+ # can remove the --print_sdk_path argument to find_sdk and instead just use
+ # the following two lines to get the path. Although it looks longer here, it
+ # saves forking a process in find_sdk.py so will be faster.
+ #mac_sdk_root = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX"
+ #mac_sdk_path = mac_sdk_root + mac_sdk_version + ".sdk"
+ mac_sdk_path = find_sdk_lines[0]
+}
diff --git a/build/config/mac/rules.gni b/build/config/mac/rules.gni
new file mode 100644
index 00000000000..66f0de42e00
--- /dev/null
+++ b/build/config/mac/rules.gni
@@ -0,0 +1,220 @@
+# Copyright 2015 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.
+
+mac_app_script = "//build/config/mac/mac_app.py"
+
+template("code_sign_mac") {
+ assert(defined(invoker.entitlements_path),
+ "The path to the entitlements .xcent file")
+ assert(defined(invoker.identity),
+ "The code signing identity")
+ assert(defined(invoker.application_path),
+ "The application to code sign")
+ assert(defined(invoker.deps))
+
+ action(target_name) {
+ sources = [
+ invoker.entitlements_path,
+ ]
+
+ _application_path = invoker.application_path
+
+ script = mac_app_script
+
+ outputs = [
+ "$_application_path/_CodeSignature/CodeResources"
+ ]
+
+ args = [
+ "codesign",
+ "-p",
+ rebase_path(invoker.application_path, root_build_dir),
+ "-i",
+ invoker.identity,
+ "-e",
+ rebase_path(invoker.entitlements_path, root_build_dir),
+ ]
+
+ deps = invoker.deps
+ }
+}
+
+template("process_nibs_mac") {
+ assert(defined(invoker.sources),
+ "The nib sources must be specified")
+ assert(defined(invoker.module),
+ "The nib module must be specified")
+ assert(defined(invoker.output_dir),
+ "The output directory must be specified")
+
+ action_foreach(target_name) {
+ sources = invoker.sources
+
+ script = mac_app_script
+
+ invoker_out_dir = invoker.output_dir
+
+ outputs = [
+ "$root_build_dir/$invoker_out_dir/{{source_name_part}}.nib"
+ ]
+
+ args = [
+ "nib",
+ "-i",
+ "{{source}}",
+ "-o",
+ invoker_out_dir,
+ "-m",
+ invoker.module,
+ ]
+ }
+}
+
+template("resource_copy_mac") {
+ assert(defined(invoker.resources),
+ "The source list of resources to copy over")
+ assert(defined(invoker.bundle_directory),
+ "The directory within the bundle to place the sources in")
+ assert(defined(invoker.app_name),
+ "The name of the application")
+
+ _bundle_directory = invoker.bundle_directory
+ _app_name = invoker.app_name
+ _resources = invoker.resources
+
+ copy(target_name) {
+ set_sources_assignment_filter([])
+ sources = _resources
+ outputs = [ "$root_build_dir/$_app_name.app/$_bundle_directory/Contents/Resources/{{source_file_part}}" ]
+
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ }
+}
+
+template("mac_app") {
+
+ assert(defined(invoker.deps),
+ "Dependencies must be specified for $target_name")
+ assert(defined(invoker.info_plist),
+ "The application plist file must be specified for $target_name")
+ assert(defined(invoker.app_name),
+ "The name of Mac application for $target_name")
+ assert(defined(invoker.xibs),
+ "The list of XIB files must be specified for $target_name")
+ # assert(defined(invoker.entitlements_path),
+ # "The entitlements path must be specified for $target_name")
+ # assert(defined(invoker.code_signing_identity),
+ # "The entitlements path must be specified for $target_name")
+
+ # We just create a variable so we can use the same in interpolation
+ app_name = invoker.app_name
+
+ # Generate the project structure
+
+ struct_gen_target_name = target_name + "_struct"
+
+ action(struct_gen_target_name) {
+
+ script = mac_app_script
+
+ sources = []
+ outputs = [ "$root_build_dir/$app_name.app" ]
+
+ args = [
+ "structure",
+ "-d",
+ rebase_path(root_build_dir),
+ "-n",
+ app_name
+ ]
+
+ }
+
+ # Generate the executable
+
+ bin_gen_target_name = target_name + "_bin"
+
+ executable(bin_gen_target_name) {
+ deps = invoker.deps
+ output_name = app_name
+ }
+
+ # Process the Info.plist
+
+ plist_gen_target_name = target_name + "_plist"
+
+ action(plist_gen_target_name) {
+
+ script = mac_app_script
+
+ sources = [ invoker.info_plist ]
+ outputs = [ "$root_build_dir/plist/$app_name/Info.plist" ]
+
+ args = [
+ "plist",
+ "-i",
+ rebase_path(invoker.info_plist, root_build_dir),
+ "-o",
+ rebase_path("$root_build_dir/plist/$app_name"),
+ ]
+ }
+
+ # Copy the generated binaries and assets to their appropriate locations
+
+ copy_plist_gen_target_name = target_name + "_plist_copy"
+ copy(copy_plist_gen_target_name) {
+ sources = [
+ "$root_build_dir/plist/$app_name/Info.plist",
+ ]
+
+ outputs = [
+ "$root_build_dir/$app_name.app/Contents/{{source_file_part}}"
+ ]
+
+ deps = [
+ ":$plist_gen_target_name",
+ ]
+ }
+
+ copy_bin_target_name = target_name + "_bin_copy"
+ copy(copy_bin_target_name) {
+ sources = [
+ "$root_build_dir/$app_name",
+ ]
+
+ outputs = [
+ "$root_build_dir/$app_name.app/Contents/MacOS/{{source_file_part}}"
+ ]
+
+ deps = [
+ ":$bin_gen_target_name",
+ ]
+ }
+
+ copy_xib_target_name = target_name + "_xib_copy"
+ process_nibs_mac(copy_xib_target_name) {
+ sources = invoker.xibs
+ module = app_name
+ output_dir = "$app_name.app/Contents/Resources"
+ }
+
+ copy_all_target_name = target_name + "_all_copy"
+ group(copy_all_target_name) {
+ deps = [
+ ":$struct_gen_target_name",
+ ":$copy_plist_gen_target_name",
+ ":$copy_bin_target_name",
+ ":$copy_xib_target_name",
+ ]
+ }
+
+ # Top level group
+
+ group(target_name) {
+ deps = [ ":$copy_all_target_name" ]
+ }
+
+}
diff --git a/build/config/mips.gni b/build/config/mips.gni
new file mode 100644
index 00000000000..1b406572c73
--- /dev/null
+++ b/build/config/mips.gni
@@ -0,0 +1,43 @@
+# Copyright 2015 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.
+
+if (current_cpu == "mipsel") {
+ declare_args() {
+ # MIPS arch variant. Possible values are:
+ # "r1"
+ # "r2"
+ # "r6"
+ mips_arch_variant = "r1"
+
+ # MIPS DSP ASE revision. Possible values are:
+ # 0: unavailable
+ # 1: revision 1
+ # 2: revision 2
+ mips_dsp_rev = 0
+
+ # MIPS floating-point ABI. Possible values are:
+ # "hard": sets the GCC -mhard-float option.
+ # "soft": sets the GCC -msoft-float option.
+ mips_float_abi = "hard"
+
+ # MIPS32 floating-point register width. Possible values are:
+ # "fp32": sets the GCC -mfp32 option.
+ # "fp64": sets the GCC -mfp64 option.
+ # "fpxx": sets the GCC -mfpxx option.
+ mips_fpu_mode = "fp32"
+ }
+} else if (current_cpu == "mips64el") {
+ # MIPS arch variant. Possible values are:
+ # "r2"
+ # "r6"
+ if (is_android) {
+ declare_args() {
+ mips_arch_variant = "r6"
+ }
+ } else {
+ declare_args() {
+ mips_arch_variant = "r2"
+ }
+ }
+}
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn
new file mode 100644
index 00000000000..899695127e8
--- /dev/null
+++ b/build/config/sanitizers/BUILD.gn
@@ -0,0 +1,59 @@
+# Copyright 2014 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.
+
+import("//build/config/sanitizers/sanitizers.gni")
+
+# Contains the dependencies needed for sanitizers to link into executables and
+# shared_libraries. Unconditionally depend upon this target as it is empty if
+# |is_asan|, |is_lsan|, |is_tsan|, |is_msan| and |use_custom_libcxx| are false.
+group("deps") {
+ deps = [
+ "//third_party/instrumented_libraries:deps",
+ ]
+ if (is_asan || is_lsan || is_tsan || is_msan) {
+ public_configs = [ ":sanitizer_options_link_helper" ]
+ deps += [ ":options_sources" ]
+ }
+ if (use_custom_libcxx) {
+ deps += [ "//buildtools/third_party/libc++:libcxx_proxy" ]
+ }
+}
+
+config("sanitizer_options_link_helper") {
+ ldflags = [ "-Wl,-u_sanitizer_options_link_helper" ]
+ if (is_asan) {
+ ldflags += [ "-fsanitize=address" ]
+ }
+ if (is_lsan) {
+ ldflags += [ "-fsanitize=leak" ]
+ }
+ if (is_tsan) {
+ ldflags += [ "-fsanitize=thread" ]
+ }
+ if (is_msan) {
+ ldflags += [ "-fsanitize=memory" ]
+ }
+}
+
+source_set("options_sources") {
+ visibility = [
+ ":deps",
+ "//:gn_visibility",
+ ]
+ sources = [
+ "//build/sanitizers/sanitizer_options.cc",
+ ]
+
+ if (is_asan) {
+ sources += [ "//build/sanitizers/asan_suppressions.cc" ]
+ }
+
+ if (is_lsan) {
+ sources += [ "//build/sanitizers/lsan_suppressions.cc" ]
+ }
+
+ if (is_tsan) {
+ sources += [ "//build/sanitizers/tsan_suppressions.cc" ]
+ }
+}
diff --git a/build/config/sanitizers/sanitizers.gni b/build/config/sanitizers/sanitizers.gni
new file mode 100644
index 00000000000..a7b965824a7
--- /dev/null
+++ b/build/config/sanitizers/sanitizers.gni
@@ -0,0 +1,20 @@
+# Copyright 2015 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.
+
+declare_args() {
+ # Use libc++ (buildtools/third_party/libc++ and
+ # buildtools/third_party/libc++abi) instead of stdlibc++ as standard library.
+ # This is intended to be used for instrumented builds.
+ use_custom_libcxx = (is_asan && is_linux) || is_tsan || is_msan
+
+ # Track where uninitialized memory originates from. From fastest to slowest:
+ # 0 - no tracking, 1 - track only the initial allocation site, 2 - track the
+ # chain of stores leading from allocation site to use site.
+ msan_track_origins = 2
+
+ # Use dynamic libraries instrumented by one of the sanitizers instead of the
+ # standard system libraries. Set this flag to download prebuilt binaries from
+ # GCS.
+ use_prebuilt_instrumented_libraries = false
+}
diff --git a/build/config/sysroot.gni b/build/config/sysroot.gni
new file mode 100644
index 00000000000..5bce02e2985
--- /dev/null
+++ b/build/config/sysroot.gni
@@ -0,0 +1,60 @@
+# Copyright (c) 2013 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 header file defines the "sysroot" variable which is the absolute path
+# of the sysroot. If no sysroot applies, the variable will be an empty string.
+
+import("//build/config/chrome_build.gni")
+
+declare_args() {
+ # The absolute path of the sysroot that is applied when compiling using
+ # the target toolchain.
+ target_sysroot = ""
+}
+
+if (current_toolchain == default_toolchain && target_sysroot != "") {
+ sysroot = target_sysroot
+} else if (is_android) {
+ import("//build/config/android/config.gni")
+ if (current_cpu == "x86") {
+ sysroot = rebase_path("$android_ndk_root/$x86_android_sysroot_subdir")
+ } else if (current_cpu == "arm") {
+ sysroot = rebase_path("$android_ndk_root/$arm_android_sysroot_subdir")
+ } else if (current_cpu == "mipsel") {
+ sysroot = rebase_path("$android_ndk_root/$mips_android_sysroot_subdir")
+ } else if (current_cpu == "x64") {
+ sysroot = rebase_path("$android_ndk_root/$x86_64_android_sysroot_subdir")
+ } else if (current_cpu == "arm64") {
+ sysroot = rebase_path("$android_ndk_root/$arm64_android_sysroot_subdir")
+ } else if (current_cpu == "mips64") {
+ sysroot = rebase_path("$android_ndk_root/$mips64_android_sysroot_subdir")
+ } else {
+ sysroot = ""
+ }
+} else if (is_linux && is_chrome_branded && is_official_build && !is_chromeos) {
+ # For official builds, use the sysroot checked into the internal source repo
+ # so that the builds work on older versions of Linux.
+ if (current_cpu == "x64") {
+ sysroot = rebase_path("//build/linux/debian_wheezy_amd64-sysroot")
+ } else if (current_cpu == "x86") {
+ sysroot = rebase_path("//build/linux/debian_wheezy_i386-sysroot")
+ } else {
+ # Any other builds don't use a sysroot.
+ sysroot = ""
+ }
+} else if (is_linux && !is_chromeos) {
+ if (current_cpu == "mipsel") {
+ sysroot = rebase_path("//mipsel-sysroot/sysroot")
+ } else {
+ sysroot = ""
+ }
+} else if (is_mac) {
+ import("//build/config/mac/mac_sdk.gni")
+ sysroot = mac_sdk_path
+} else if (is_ios) {
+ import("//build/config/ios/ios_sdk.gni")
+ sysroot = ios_sdk_path
+} else {
+ sysroot = ""
+}
diff --git a/build/config/templates/templates.gni b/build/config/templates/templates.gni
new file mode 100644
index 00000000000..ae00fca3aee
--- /dev/null
+++ b/build/config/templates/templates.gni
@@ -0,0 +1,56 @@
+# Copyright 2015 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.
+
+# Declare a target for processing a template.
+#
+# Variables
+# input: The template file to be processed.
+# output: Where to save the result.
+# variables: A list of variables to make available to the template
+# processing environment, e.g. ["name=foo", "color=red"].
+#
+# Example
+# file_template("chrome_shell_manifest") {
+# input = "shell/java/AndroidManifest.xml"
+# output = "$target_gen_dir/AndroidManifest.xml"
+# variables = "app_name=chrome_shell app_version=1"
+# }
+template("file_template") {
+ set_sources_assignment_filter([])
+
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ assert(defined(invoker.input),
+ "The input file must be specified")
+ assert(defined(invoker.output),
+ "The output file must be specified")
+ assert(defined(invoker.variables),
+ "The variable used for substitution in templates must be specified")
+
+ variables = invoker.variables
+
+ action(target_name) {
+ if(defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+
+ script = "//build/android/gyp/jinja_template.py"
+ depfile = "$target_gen_dir/$target_name.d"
+
+ sources = [ invoker.input ]
+ outputs = [ invoker.output, depfile ]
+
+ args = [
+ "--inputs",
+ rebase_path(invoker.input, root_build_dir),
+ "--output",
+ rebase_path(invoker.output, root_build_dir),
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
+ "--variables=${variables}"
+ ]
+ }
+}
diff --git a/build/config/ui.gni b/build/config/ui.gni
new file mode 100644
index 00000000000..c2dff4a87df
--- /dev/null
+++ b/build/config/ui.gni
@@ -0,0 +1,68 @@
+# Copyright 2014 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 file contains UI-related build flags. It should theoretically be in the
+# src/ui directory and only things that depend on the ui module should get the
+# definitions.
+#
+# However, today we have many "bad" dependencies on some of these flags from,
+# e.g. base, so they need to be global.
+#
+# See also build/config/features.gni
+
+declare_args() {
+ # Indicates if Ash is enabled. Ash is the Aura Shell which provides a
+ # desktop-like environment for Aura. Requires use_aura = true
+ use_ash = is_win || is_linux
+
+ # Indicates if Ozone is enabled. Ozone is a low-level library layer for Linux
+ # that does not require X11.
+ use_ozone = false
+
+ # Indicates if GLFW is enabled. GLFW is an abstraction layer for the
+ # windowing system and OpenGL rendering, providing cross-platform support
+ # for creating windows and OpenGL surfaces and contexts, and handling
+ # window system events and input.
+ use_glfw = false
+
+ # Support ChromeOS touchpad gestures with ozone.
+ use_evdev_gestures = false
+
+ # Indicates if Aura is enabled. Aura is a low-level windowing library, sort
+ # of a replacement for GDI or GTK.
+ use_aura = is_win || is_linux
+
+ # True means the UI is built using the "views" framework.
+ toolkit_views = is_mac || is_win || is_chromeos || use_aura
+
+ # Whether the entire browser uses toolkit-views on Mac instead of Cocoa.
+ mac_views_browser = false
+
+ # Whether we should use glib, a low level C utility library.
+ use_glib = is_linux && !use_ozone
+}
+
+# Additional dependent variables -----------------------------------------------
+#
+# These variables depend on other variables and can't be set externally.
+
+use_cairo = false
+use_pango = false
+
+# Use GPU accelerated cross process image transport by default on linux builds
+# with the Aura window manager.
+ui_compositor_image_transport = use_aura && is_linux
+
+use_default_render_theme = use_aura || is_linux
+
+# Indicates if the UI toolkit depends on X11.
+use_x11 = is_linux && !use_ozone && !use_glfw
+
+use_ozone_evdev = use_ozone
+
+use_clipboard_aurax11 = is_linux && use_aura && use_x11
+
+enable_hidpi = is_mac || is_chromeos || is_win || is_linux
+
+enable_topchrome_md = false
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
new file mode 100644
index 00000000000..261a1ac4def
--- /dev/null
+++ b/build/config/win/BUILD.gn
@@ -0,0 +1,181 @@
+# Copyright (c) 2013 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.
+
+import("//build/config/win/visual_studio_version.gni")
+
+# Compiler setup for the Windows SDK. Applied to all targets.
+config("sdk") {
+ # The include path is the stuff returned by the script.
+ #include_dirs = msvc_config[0] TODO(brettw) make this work.
+
+ defines = [
+ "_ATL_NO_OPENGL",
+ "_WINDOWS",
+ "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+ "NTDDI_VERSION=0x06030000",
+ "PSAPI_VERSION=1",
+ "WIN32",
+ "_SECURE_ATL",
+
+ # This is required for ATL to use XP-safe versions of its functions.
+ "_USING_V110_SDK71_",
+ ]
+}
+
+# Sets the default Windows build version. This is separated because some
+# targets need to manually override it for their compiles.
+config("winver") {
+ defines = [
+ "_WIN32_WINNT=0x0603",
+ "WINVER=0x0603",
+ ]
+}
+
+# Linker flags for Windows SDK setup, this is applied only to EXEs and DLLs.
+config("sdk_link") {
+ if (current_cpu == "x64") {
+ ldflags = [ "/MACHINE:X64" ]
+ lib_dirs = [
+ "$windows_sdk_path\Lib\winv6.3\um\x64",
+ "$visual_studio_path\VC\lib\amd64",
+ "$visual_studio_path\VC\atlmfc\lib\amd64",
+ ]
+ } else {
+ ldflags = [
+ "/MACHINE:X86",
+ "/SAFESEH", # Not compatible with x64 so use only for x86.
+ ]
+ lib_dirs = [
+ "$windows_sdk_path\Lib\winv6.3\um\x86",
+ "$visual_studio_path\VC\lib",
+ "$visual_studio_path\VC\atlmfc\lib",
+ ]
+ if (!is_asan) {
+ ldflags += [ "/largeaddressaware" ]
+ }
+ }
+}
+
+# This default linker setup is provided separately from the SDK setup so
+# targets who want different library configurations can remove this and specify
+# their own.
+config("common_linker_setup") {
+ ldflags = [
+ "/FIXED:NO",
+ "/ignore:4199",
+ "/ignore:4221",
+ "/NXCOMPAT",
+
+ # Suggested by Microsoft Devrel to avoid
+ # LINK : fatal error LNK1248: image size (80000000)
+ # exceeds maximum allowable size (80000000)
+ # which started happening more regularly after VS2013 Update 4.
+ "/maxilksize:2147483647",
+ ]
+
+ # ASLR makes debugging with windbg difficult because Chrome.exe and
+ # Chrome.dll share the same base name. As result, windbg will name the
+ # Chrome.dll module like chrome_ , where
+ # typically changes with each launch. This in turn means that breakpoints in
+ # Chrome.dll don't stick from one launch to the next. For this reason, we
+ # turn ASLR off in debug builds.
+ if (is_debug) {
+ ldflags += [ "/DYNAMICBASE:NO" ]
+ } else {
+ ldflags += [ "/DYNAMICBASE" ]
+ }
+
+ # Delay loaded DLLs.
+ ldflags += [
+ "/DELAYLOAD:dbghelp.dll",
+ "/DELAYLOAD:dwmapi.dll",
+ "/DELAYLOAD:shell32.dll",
+ "/DELAYLOAD:uxtheme.dll",
+ ]
+}
+
+# Subsystem --------------------------------------------------------------------
+
+# This is appended to the subsystem to specify a minimum version.
+if (current_cpu == "x64") {
+ # The number after the comma is the minimum required OS version.
+ # 5.02 = Windows Server 2003.
+ subsystem_version_suffix = ",5.02"
+} else {
+ # 5.01 = Windows XP.
+ subsystem_version_suffix = ",5.01"
+}
+
+config("console") {
+ ldflags = [ "/SUBSYSTEM:CONSOLE$subsystem_version_suffix" ]
+}
+config("windowed") {
+ ldflags = [ "/SUBSYSTEM:WINDOWS$subsystem_version_suffix" ]
+}
+
+# Incremental linking ----------------------------------------------------------
+
+incremental_linking_on_switch = [ "/INCREMENTAL" ]
+incremental_linking_off_switch = [ "/INCREMENTAL:NO" ]
+if (is_debug) {
+ default_incremental_linking_switch = incremental_linking_on_switch
+} else {
+ default_incremental_linking_switch = incremental_linking_off_switch
+}
+
+# Applies incremental linking or not depending on the current configuration.
+config("default_incremental_linking") {
+ ldflags = default_incremental_linking_switch
+}
+
+# Explicitly on or off incremental linking
+config("incremental_linking") {
+ ldflags = incremental_linking_on_switch
+}
+config("no_incremental_linking") {
+ ldflags = incremental_linking_off_switch
+}
+
+# Some large modules can't handle incremental linking in some situations. This
+# config should be applied to large modules to turn off incremental linking
+# when it won't work.
+config("default_large_module_incremental_linking") {
+ if (symbol_level > 0 && (current_cpu == "x86" || !is_component_build)) {
+ # When symbols are on, things get so large that the tools fail due to the
+ # size of the .ilk files.
+ ldflags = incremental_linking_off_switch
+ } else {
+ # Otherwise just do the default incremental linking for this build type.
+ ldflags = default_incremental_linking_switch
+ }
+}
+
+# Character set ----------------------------------------------------------------
+
+# Not including this config means "ansi" (8-bit system codepage).
+config("unicode") {
+ defines = [
+ "_UNICODE",
+ "UNICODE",
+ ]
+}
+
+# Lean and mean ----------------------------------------------------------------
+
+# Some third party code might not compile with WIN32_LEAN_AND_MEAN so we have
+# to have a separate config for it. Remove this config from your target to
+# get the "bloaty and accomodating" version of windows.h.
+config("lean_and_mean") {
+ defines = [ "WIN32_LEAN_AND_MEAN" ]
+}
+
+# Nominmax --------------------------------------------------------------------
+
+# Some third party code defines NOMINMAX before including windows.h, which
+# then causes warnings when it's been previously defined on the command line.
+# For such targets, this config can be removed.
+
+config("nominmax") {
+ defines = [ "NOMINMAX" ]
+}
diff --git a/build/config/win/visual_studio_version.gni b/build/config/win/visual_studio_version.gni
new file mode 100644
index 00000000000..6a2828c3553
--- /dev/null
+++ b/build/config/win/visual_studio_version.gni
@@ -0,0 +1,39 @@
+# Copyright 2014 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.
+
+declare_args() {
+ # Path to Visual Studio. If empty, the default is used which is to use the
+ # automatic toolchain in depot_tools. If set, you must also set the
+ # visual_studio_version and wdk_path.
+ visual_studio_path = ""
+
+ # Version of Visual Studio pointed to by the visual_studio_path.
+ # Use "2013" for Visual Studio 2013, or "2013e" for the Express version.
+ visual_studio_version = ""
+
+ # Directory of the Windows driver kit. If visual_studio_path is empty, this
+ # will be auto-filled.
+ wdk_path = ""
+
+ # Full path to the Windows SDK, not including a backslash at the end.
+ # This value is the default location, override if you have a different
+ # installation location.
+ windows_sdk_path = "C:\Program Files (x86)\Windows Kits\8.1"
+}
+
+if (visual_studio_path == "") {
+ toolchain_data =
+ exec_script("../../vs_toolchain.py", [ "get_toolchain_dir" ], "scope")
+ visual_studio_path = toolchain_data.vs_path
+ windows_sdk_path = toolchain_data.sdk_path
+ visual_studio_version = toolchain_data.vs_version
+ wdk_path = toolchain_data.wdk_dir
+ visual_studio_runtime_dirs = toolchain_data.runtime_dirs
+} else {
+ assert(visual_studio_version != "",
+ "You must set the visual_studio_version if you set the path")
+ assert(wdk_path != "",
+ "You must set the wdk_path if you set the visual studio path")
+ visual_studio_runtime_dirs = []
+}
diff --git a/build/copy_test_data_ios.gypi b/build/copy_test_data_ios.gypi
new file mode 100644
index 00000000000..576a0f26ecb
--- /dev/null
+++ b/build/copy_test_data_ios.gypi
@@ -0,0 +1,53 @@
+# Copyright (c) 2012 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 file is meant to be included into an action to copy test data files into
+# an iOS app bundle. To use this the following variables need to be defined:
+# test_data_files: list: paths to test data files or directories
+# test_data_prefix: string: a directory prefix that will be prepended to each
+# output path. Generally, this should be the base
+# directory of the gypi file containing the unittest
+# target (e.g. "base" or "chrome").
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my_unittests',
+# 'conditions': [
+# ['OS == "ios"', {
+# 'actions': [
+# {
+# 'action_name': 'copy_test_data',
+# 'variables': {
+# 'test_data_files': [
+# 'path/to/datafile.txt',
+# 'path/to/data/directory/',
+# ]
+# 'test_data_prefix' : 'prefix',
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# },
+# ],
+# }],
+# }
+#
+
+{
+ 'inputs': [
+ # The |-o <(test_data_prefix)| is ignored; it is there to work around a
+ # caching bug in gyp (https://code.google.com/p/gyp/issues/detail?id=112).
+ # It caches command output when the string is the same, so if two copy
+ # steps have the same relative paths, there can be bogus cache hits that
+ # cause compile failures unless something varies.
+ ' [--inputs] [--outputs] '
+ parser.set_usage(usage)
+ parser.add_option('-o', dest='output_dir')
+ parser.add_option('--inputs', action='store_true', dest='list_inputs')
+ parser.add_option('--outputs', action='store_true', dest='list_outputs')
+ options, arglist = parser.parse_args(argv)
+
+ if len(arglist) == 0:
+ raise WrongNumberOfArgumentsException(' required.')
+
+ files_to_copy = CalcInputs(arglist)
+ escaped_files = [EscapePath(x) for x in CalcInputs(arglist)]
+ if options.list_inputs:
+ return '\n'.join(escaped_files)
+
+ if not options.output_dir:
+ raise WrongNumberOfArgumentsException('-o required.')
+
+ if options.list_outputs:
+ outputs = [os.path.join(options.output_dir, x) for x in escaped_files]
+ return '\n'.join(outputs)
+
+ CopyFiles(files_to_copy, options.output_dir)
+ return
+
+def main(argv):
+ try:
+ result = DoMain(argv[1:])
+ except WrongNumberOfArgumentsException, e:
+ print >>sys.stderr, e
+ return 1
+ if result:
+ print result
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/cp.py b/build/cp.py
new file mode 100755
index 00000000000..0f32536b624
--- /dev/null
+++ b/build/cp.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Copy a file.
+
+This module works much like the cp posix command - it takes 2 arguments:
+(src, dst) and copies the file with path |src| to |dst|.
+"""
+
+import os
+import shutil
+import sys
+
+
+def Main(src, dst):
+ # Use copy instead of copyfile to ensure the executable bit is copied.
+ return shutil.copy(src, os.path.normpath(dst))
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1], sys.argv[2]))
diff --git a/build/detect_host_arch.py b/build/detect_host_arch.py
new file mode 100755
index 00000000000..19579ebbc13
--- /dev/null
+++ b/build/detect_host_arch.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Outputs host CPU architecture in format recognized by gyp."""
+
+import platform
+import re
+import sys
+
+
+def HostArch():
+ """Returns the host architecture with a predictable string."""
+ host_arch = platform.machine()
+
+ # Convert machine type to format recognized by gyp.
+ if re.match(r'i.86', host_arch) or host_arch == 'i86pc':
+ host_arch = 'ia32'
+ elif host_arch in ['x86_64', 'amd64']:
+ host_arch = 'x64'
+ elif host_arch.startswith('arm'):
+ host_arch = 'arm'
+
+ # platform.machine is based on running kernel. It's possible to use 64-bit
+ # kernel with 32-bit userland, e.g. to give linker slightly more memory.
+ # Distinguish between different userland bitness by querying
+ # the python binary.
+ if host_arch == 'x64' and platform.architecture()[0] == '32bit':
+ host_arch = 'ia32'
+
+ return host_arch
+
+def DoMain(_):
+ """Hook to be called from gyp without starting a separate python
+ interpreter."""
+ return HostArch()
+
+if __name__ == '__main__':
+ print DoMain([])
diff --git a/build/dir_exists.py b/build/dir_exists.py
new file mode 100755
index 00000000000..70d367ec269
--- /dev/null
+++ b/build/dir_exists.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 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.
+"""Writes True if the argument is a directory."""
+
+import os.path
+import sys
+
+def main():
+ sys.stdout.write(_is_dir(sys.argv[1]))
+ return 0
+
+def _is_dir(dir_name):
+ return str(os.path.isdir(dir_name))
+
+def DoMain(args):
+ """Hook to be called from gyp without starting a separate python
+ interpreter."""
+ return _is_dir(args[0])
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/download_gold_plugin.py b/build/download_gold_plugin.py
new file mode 100755
index 00000000000..cd7ca41ed31
--- /dev/null
+++ b/build/download_gold_plugin.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+"""Script to download LLVM gold plugin from google storage."""
+
+import json
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+CHROME_SRC = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir))
+sys.path.insert(0, os.path.join(CHROME_SRC, 'tools'))
+
+import find_depot_tools
+
+DEPOT_PATH = find_depot_tools.add_depot_tools_to_path()
+GSUTIL_PATH = os.path.join(DEPOT_PATH, 'gsutil.py')
+
+LLVM_BUILD_PATH = os.path.join(CHROME_SRC, 'third_party', 'llvm-build',
+ 'Release+Asserts')
+CLANG_UPDATE_PY = os.path.join(CHROME_SRC, 'tools', 'clang', 'scripts',
+ 'update.py')
+CLANG_REVISION = os.popen(CLANG_UPDATE_PY + ' --print-revision').read().rstrip()
+
+CLANG_BUCKET = 'gs://chromium-browser-clang/Linux_x64'
+
+def main():
+ targz_name = 'llvmgold-%s.tgz' % CLANG_REVISION
+ remote_path = '%s/%s' % (CLANG_BUCKET, targz_name)
+
+ os.chdir(LLVM_BUILD_PATH)
+
+ subprocess.check_call(['python', GSUTIL_PATH,
+ 'cp', remote_path, targz_name])
+ subprocess.check_call(['tar', 'xzf', targz_name])
+ os.remove(targz_name)
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/download_nacl_toolchains.py b/build/download_nacl_toolchains.py
new file mode 100755
index 00000000000..b99b940f9c7
--- /dev/null
+++ b/build/download_nacl_toolchains.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Shim to run nacl toolchain download script only if there is a nacl dir."""
+
+import os
+import shutil
+import sys
+
+
+def Main(args):
+ # Exit early if disable_nacl=1.
+ if 'disable_nacl=1' in os.environ.get('GYP_DEFINES', ''):
+ return 0
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ src_dir = os.path.dirname(script_dir)
+ nacl_dir = os.path.join(src_dir, 'native_client')
+ nacl_build_dir = os.path.join(nacl_dir, 'build')
+ package_version_dir = os.path.join(nacl_build_dir, 'package_version')
+ package_version = os.path.join(package_version_dir, 'package_version.py')
+ if not os.path.exists(package_version):
+ print "Can't find '%s'" % package_version
+ print 'Presumably you are intentionally building without NativeClient.'
+ print 'Skipping NativeClient toolchain download.'
+ sys.exit(0)
+ sys.path.insert(0, package_version_dir)
+ import package_version
+
+ # BUG:
+ # We remove this --optional-pnacl argument, and instead replace it with
+ # --no-pnacl for most cases. However, if the bot name is an sdk
+ # bot then we will go ahead and download it. This prevents increasing the
+ # gclient sync time for developers, or standard Chrome bots.
+ if '--optional-pnacl' in args:
+ args.remove('--optional-pnacl')
+ use_pnacl = False
+ buildbot_name = os.environ.get('BUILDBOT_BUILDERNAME', '')
+ if 'pnacl' in buildbot_name and 'sdk' in buildbot_name:
+ use_pnacl = True
+ if use_pnacl:
+ print '\n*** DOWNLOADING PNACL TOOLCHAIN ***\n'
+ else:
+ args = ['--exclude', 'pnacl_newlib'] + args
+
+ # Only download the ARM gcc toolchain if we are building for ARM
+ # TODO(olonho): we need to invent more reliable way to get build
+ # configuration info, to know if we're building for ARM.
+ if 'target_arch=arm' not in os.environ.get('GYP_DEFINES', ''):
+ args = ['--exclude', 'nacl_arm_newlib'] + args
+
+ package_version.main(args)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/build/download_sdk_extras.py b/build/download_sdk_extras.py
new file mode 100755
index 00000000000..d7c5d6cad6c
--- /dev/null
+++ b/build/download_sdk_extras.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Script to download sdk/extras packages on the bots from google storage.
+
+The script expects arguments that specify zips file in the google storage
+bucket named: __.zip. The file will
+be extracted in the android_tools/sdk/extras directory on the test bots. This
+script will not do anything for developers.
+
+TODO(navabi): Move this script (crbug.com/459819).
+"""
+
+import json
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+CHROME_SRC = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir))
+sys.path.insert(0, os.path.join(SCRIPT_DIR, 'android'))
+sys.path.insert(1, os.path.join(CHROME_SRC, 'tools'))
+
+from pylib import constants
+import find_depot_tools
+
+DEPOT_PATH = find_depot_tools.add_depot_tools_to_path()
+GSUTIL_PATH = os.path.join(DEPOT_PATH, 'gsutil.py')
+SDK_EXTRAS_BUCKET = 'gs://chrome-sdk-extras'
+SDK_EXTRAS_PATH = os.path.join(constants.ANDROID_SDK_ROOT, 'extras')
+SDK_EXTRAS_JSON_FILE = os.path.join(os.path.dirname(__file__),
+ 'android_sdk_extras.json')
+
+
+def clean_and_extract(dir_name, package_name, zip_file):
+ local_dir = '%s/%s/%s' % (SDK_EXTRAS_PATH, dir_name, package_name)
+ if os.path.exists(local_dir):
+ shutil.rmtree(local_dir)
+ local_zip = '%s/%s' % (SDK_EXTRAS_PATH, zip_file)
+ with zipfile.ZipFile(local_zip) as z:
+ z.extractall(path=SDK_EXTRAS_PATH)
+
+
+def main():
+ if not os.environ.get('CHROME_HEADLESS'):
+ # This is not a buildbot checkout.
+ return 0
+ # Update the android_sdk_extras.json file to update downloaded packages.
+ with open(SDK_EXTRAS_JSON_FILE) as json_file:
+ packages = json.load(json_file)
+ for package in packages:
+ local_zip = '%s/%s' % (SDK_EXTRAS_PATH, package['zip'])
+ if not os.path.exists(local_zip):
+ package_zip = '%s/%s' % (SDK_EXTRAS_BUCKET, package['zip'])
+ try:
+ subprocess.check_call(['python', GSUTIL_PATH, '--force-version', '4.7',
+ 'cp', package_zip, local_zip])
+ except subprocess.CalledProcessError:
+ print ('WARNING: Failed to download SDK packages. If this bot compiles '
+ 'for Android, it may have errors.')
+ return 0
+ # Always clean dir and extract zip to ensure correct contents.
+ clean_and_extract(package['dir_name'], package['package'], package['zip'])
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/env_dump.py b/build/env_dump.py
new file mode 100755
index 00000000000..21edfe633c7
--- /dev/null
+++ b/build/env_dump.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+# Copyright 2013 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 script can either source a file and dump the enironment changes done by
+# it, or just simply dump the current environment as JSON into a file.
+
+import json
+import optparse
+import os
+import pipes
+import subprocess
+import sys
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-f', '--output-json',
+ help='File to dump the environment as JSON into.')
+ parser.add_option(
+ '-d', '--dump-mode', action='store_true',
+ help='Dump the environment to sys.stdout and exit immediately.')
+
+ parser.disable_interspersed_args()
+ options, args = parser.parse_args()
+ if options.dump_mode:
+ if args or options.output_json:
+ parser.error('Cannot specify args or --output-json with --dump-mode.')
+ json.dump(dict(os.environ), sys.stdout)
+ else:
+ if not options.output_json:
+ parser.error('Requires --output-json option.')
+
+ envsetup_cmd = ' '.join(map(pipes.quote, args))
+ full_cmd = [
+ 'bash', '-c',
+ '. %s > /dev/null; %s -d' % (envsetup_cmd, os.path.abspath(__file__))
+ ]
+ try:
+ output = subprocess.check_output(full_cmd)
+ except Exception as e:
+ sys.exit('Error running %s and dumping environment.' % envsetup_cmd)
+
+ env_diff = {}
+ new_env = json.loads(output)
+ for k, val in new_env.items():
+ if k == '_' or (k in os.environ and os.environ[k] == val):
+ continue
+ env_diff[k] = val
+ with open(options.output_json, 'w') as f:
+ json.dump(env_diff, f)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/extract_from_cab.py b/build/extract_from_cab.py
new file mode 100755
index 00000000000..080370ca9ad
--- /dev/null
+++ b/build/extract_from_cab.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Extracts a single file from a CAB archive."""
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+def run_quiet(*args):
+ """Run 'expand' suppressing noisy output. Returns returncode from process."""
+ popen = subprocess.Popen(args, stdout=subprocess.PIPE)
+ out, _ = popen.communicate()
+ if popen.returncode:
+ # expand emits errors to stdout, so if we fail, then print that out.
+ print out
+ return popen.returncode
+
+def main():
+ if len(sys.argv) != 4:
+ print 'Usage: extract_from_cab.py cab_path archived_file output_dir'
+ return 1
+
+ [cab_path, archived_file, output_dir] = sys.argv[1:]
+
+ # Expand.exe does its work in a fixed-named temporary directory created within
+ # the given output directory. This is a problem for concurrent extractions, so
+ # create a unique temp dir within the desired output directory to work around
+ # this limitation.
+ temp_dir = tempfile.mkdtemp(dir=output_dir)
+
+ try:
+ # Invoke the Windows expand utility to extract the file.
+ level = run_quiet('expand', cab_path, '-F:' + archived_file, temp_dir)
+ if level == 0:
+ # Move the output file into place, preserving expand.exe's behavior of
+ # paving over any preexisting file.
+ output_file = os.path.join(output_dir, archived_file)
+ try:
+ os.remove(output_file)
+ except OSError:
+ pass
+ os.rename(os.path.join(temp_dir, archived_file), output_file)
+ finally:
+ shutil.rmtree(temp_dir, True)
+
+ if level != 0:
+ return level
+
+ # The expand utility preserves the modification date and time of the archived
+ # file. Touch the extracted file. This helps build systems that compare the
+ # modification times of input and output files to determine whether to do an
+ # action.
+ os.utime(os.path.join(output_dir, archived_file), None)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/filename_rules.gypi b/build/filename_rules.gypi
new file mode 100644
index 00000000000..f67287fa9e9
--- /dev/null
+++ b/build/filename_rules.gypi
@@ -0,0 +1,106 @@
+# Copyright (c) 2012 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 gypi file defines the patterns used for determining whether a
+# file is excluded from the build on a given platform. It is
+# included by common.gypi for chromium_code.
+
+{
+ 'target_conditions': [
+ ['OS!="win" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_win(_browsertest|_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)win/'],
+ ['exclude', '(^|/)win_[^/]*\\.(h|cc)$'] ],
+ }],
+ ['OS!="mac" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_(cocoa|mac)(_unittest)?\\.(h|cc|mm?)$'],
+ ['exclude', '(^|/)(cocoa|mac)/'] ],
+ }],
+ ['OS!="ios" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_ios(_unittest)?\\.(h|cc|mm?)$'],
+ ['exclude', '(^|/)ios/'] ],
+ }],
+ ['(OS!="mac" and OS!="ios") or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '\\.mm?$' ] ],
+ }],
+ # Do not exclude the linux files on *BSD since most of them can be
+ # shared at this point.
+ # In case a file is not needed, it is going to be excluded later on.
+ # TODO(evan): the above is not correct; we shouldn't build _linux
+ # files on non-linux.
+ ['OS!="linux" and OS!="openbsd" and OS!="freebsd" or >(nacl_untrusted_build)==1', {
+ 'sources/': [
+ ['exclude', '_linux(_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)linux/'],
+ ],
+ }],
+ ['OS!="android" or _toolset=="host" or >(nacl_untrusted_build)==1', {
+ 'sources/': [
+ ['exclude', '_android(_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)android/'],
+ ],
+ }],
+ ['OS=="win" and >(nacl_untrusted_build)==0', {
+ 'sources/': [
+ ['exclude', '_posix(_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)posix/'],
+ ],
+ }],
+ ['<(chromeos)!=1 or >(nacl_untrusted_build)==1', {
+ 'sources/': [
+ ['exclude', '_chromeos(_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)chromeos/'],
+ ],
+ }],
+ ['>(nacl_untrusted_build)==0', {
+ 'sources/': [
+ ['exclude', '_nacl(_unittest)?\\.(h|cc)$'],
+ ],
+ }],
+ ['OS!="linux" and OS!="openbsd" and OS!="freebsd" or >(nacl_untrusted_build)==1', {
+ 'sources/': [
+ ['exclude', '_xdg(_unittest)?\\.(h|cc)$'],
+ ],
+ }],
+ ['<(use_x11)!=1 or >(nacl_untrusted_build)==1', {
+ 'sources/': [
+ ['exclude', '_(x|x11)(_interactive_uitest|_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)x11_[^/]*\\.(h|cc)$'],
+ ['exclude', '(^|/)x11/'],
+ ['exclude', '(^|/)x/'],
+ ],
+ }],
+ ['<(toolkit_views)==0 or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_views(_browsertest|_unittest)?\\.(h|cc)$'] ]
+ }],
+ ['<(use_aura)==0 or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_aura(_browsertest|_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)aura/'],
+ ]
+ }],
+ ['<(use_aura)==0 or <(use_x11)==0 or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_aurax11(_browsertest|_unittest)?\\.(h|cc)$'] ]
+ }],
+ ['<(use_aura)==0 or OS!="win" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_aurawin\\.(h|cc)$'] ]
+ }],
+ ['<(use_aura)==0 or OS!="linux" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_auralinux\\.(h|cc)$'] ]
+ }],
+ ['<(use_ash)==0 or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_ash(_browsertest|_unittest)?\\.(h|cc)$'],
+ ['exclude', '(^|/)ash/'],
+ ]
+ }],
+ ['<(use_ash)==0 or OS!="win" or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_ashwin\\.(h|cc)$'] ]
+ }],
+ ['<(use_ozone)==0 or >(nacl_untrusted_build)==1', {
+ 'sources/': [ ['exclude', '_ozone(_browsertest|_unittest)?\\.(h|cc)$'] ]
+ }],
+ ['<(use_pango)==0', {
+ 'sources/': [ ['exclude', '(^|_)pango(_util|_browsertest|_unittest)?\\.(h|cc)$'], ],
+ }],
+ ]
+}
diff --git a/build/find_isolated_tests.py b/build/find_isolated_tests.py
new file mode 100755
index 00000000000..c5b3ab77a90
--- /dev/null
+++ b/build/find_isolated_tests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Scans build output directory for .isolated files, calculates their SHA1
+hashes, stores final list in JSON document and then removes *.isolated files
+found (to ensure no stale *.isolated stay around on the next build).
+
+Used to figure out what tests were build in isolated mode to trigger these
+tests to run on swarming.
+
+For more info see:
+https://sites.google.com/a/chromium.org/dev/developers/testing/isolated-testing
+"""
+
+import glob
+import hashlib
+import json
+import optparse
+import os
+import re
+import sys
+
+
+def hash_file(filepath):
+ """Calculates the hash of a file without reading it all in memory at once."""
+ digest = hashlib.sha1()
+ with open(filepath, 'rb') as f:
+ while True:
+ chunk = f.read(1024*1024)
+ if not chunk:
+ break
+ digest.update(chunk)
+ return digest.hexdigest()
+
+
+def main():
+ parser = optparse.OptionParser(
+ usage='%prog --build-dir --output-json ',
+ description=sys.modules[__name__].__doc__)
+ parser.add_option(
+ '--build-dir',
+ help='Path to a directory to search for *.isolated files.')
+ parser.add_option(
+ '--output-json',
+ help='File to dump JSON results into.')
+
+ options, _ = parser.parse_args()
+ if not options.build_dir:
+ parser.error('--build-dir option is required')
+ if not options.output_json:
+ parser.error('--output-json option is required')
+
+ result = {}
+
+ # Get the file hash values and output the pair.
+ pattern = os.path.join(options.build_dir, '*.isolated')
+ for filepath in sorted(glob.glob(pattern)):
+ test_name = os.path.splitext(os.path.basename(filepath))[0]
+ if re.match(r'^.+?\.\d$', test_name):
+ # It's a split .isolated file, e.g. foo.0.isolated. Ignore these.
+ continue
+
+ # TODO(csharp): Remove deletion once the isolate tracked dependencies are
+ # inputs for the isolated files.
+ sha1_hash = hash_file(filepath)
+ os.remove(filepath)
+ result[test_name] = sha1_hash
+
+ with open(options.output_json, 'wb') as f:
+ json.dump(result, f)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/gdb-add-index b/build/gdb-add-index
new file mode 100755
index 00000000000..992ac161597
--- /dev/null
+++ b/build/gdb-add-index
@@ -0,0 +1,162 @@
+#!/bin/bash
+# Copyright (c) 2012 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.
+#
+# Saves the gdb index for a given binary and its shared library dependencies.
+#
+# This will run gdb index in parallel on a number of binaries using SIGUSR1
+# as the communication mechanism to simulate a semaphore. Because of the
+# nature of this technique, using "set -e" is very difficult. The SIGUSR1
+# terminates a "wait" with an error which we need to interpret.
+#
+# When modifying this code, most of the real logic is in the index_one_file
+# function. The rest is cleanup + sempahore plumbing.
+
+# Cleanup temp directory and ensure all child jobs are dead-dead.
+function on_exit {
+ trap "" EXIT USR1 # Avoid reentrancy.
+
+ local jobs=$(jobs -p)
+ if [ -n "$jobs" ]; then
+ echo -n "Killing outstanding index jobs..."
+ kill -KILL $(jobs -p)
+ wait
+ echo "done"
+ fi
+
+ if [ -f "$DIRECTORY" ]; then
+ echo -n "Removing temp directory $DIRECTORY..."
+ rm -rf $DIRECTORY
+ echo done
+ fi
+}
+
+# Add index to one binary.
+function index_one_file {
+ local file=$1
+ local basename=$(basename "$file")
+ local should_index="${SHOULD_INDEX}"
+
+ local readelf_out=$(${TOOLCHAIN_PREFIX}readelf -S "$file")
+ if [[ $readelf_out =~ "gdb_index" ]]; then
+ if [ "${REMOVE_INDEX}" = 1 ]; then
+ ${TOOLCHAIN_PREFIX}objcopy --remove-section .gdb_index "$file"
+ echo "Removed index from $basename."
+ else
+ echo "Skipped $basename -- already contains index."
+ should_index=0
+ fi
+ fi
+
+ if [ "${should_index}" = 1 ]; then
+ local start=$(date +"%s%N")
+ echo "Adding index to $basename..."
+
+ ${TOOLCHAIN_PREFIX}gdb -batch "$file" -ex "save gdb-index $DIRECTORY" \
+ -ex "quit"
+ local index_file="$DIRECTORY/$basename.gdb-index"
+ if [ -f "$index_file" ]; then
+ ${TOOLCHAIN_PREFIX}objcopy --add-section .gdb_index="$index_file" \
+ --set-section-flags .gdb_index=readonly "$file" "$file"
+ local finish=$(date +"%s%N")
+ local elapsed=$(((finish - start)/1000000))
+ echo " ...$basename indexed. [${elapsed}ms]"
+ else
+ echo " ...$basename unindexable."
+ fi
+ fi
+}
+
+# Functions that when combined, concurrently index all files in FILES_TO_INDEX
+# array. The global FILES_TO_INDEX is declared in the main body of the script.
+function async_index {
+ # Start a background subshell to run the index command.
+ {
+ index_one_file $1
+ kill -SIGUSR1 $$ # $$ resolves to the parent script.
+ exit 129 # See comment above wait loop at bottom.
+ } &
+}
+
+CUR_FILE_NUM=0
+function index_next {
+ if (( CUR_FILE_NUM >= ${#FILES_TO_INDEX[@]} )); then
+ return
+ fi
+
+ async_index "${FILES_TO_INDEX[CUR_FILE_NUM]}"
+ ((CUR_FILE_NUM += 1)) || true
+}
+
+
+########
+### Main body of the script.
+
+REMOVE_INDEX=0
+SHOULD_INDEX=1
+while getopts ":f:r" opt; do
+ case $opt in
+ f)
+ REMOVE_INDEX=1
+ shift
+ ;;
+ r)
+ REMOVE_INDEX=1
+ SHOULD_INDEX=0
+ shift
+ ;;
+ *)
+ echo "Invalid option: -$OPTARG" >&2
+ ;;
+ esac
+done
+
+if [[ ! $# == 1 ]]; then
+ echo "Usage: $0 [-f] [-r] path-to-binary"
+ echo " -f forces replacement of an existing index."
+ echo " -r removes the index section."
+ exit 1
+fi
+
+FILENAME="$1"
+if [[ ! -f "$FILENAME" ]]; then
+ echo "Path $FILENAME does not exist."
+ exit 1
+fi
+
+# Ensure we cleanup on on exit.
+trap on_exit EXIT
+
+# We're good to go! Create temp directory for index files.
+DIRECTORY=$(mktemp -d)
+echo "Made temp directory $DIRECTORY."
+
+# Create array with the filename and all shared libraries that
+# have the same dirname. The dirname is a signal that these
+# shared libraries were part of the same build as the binary.
+declare -a FILES_TO_INDEX=($FILENAME
+ $(ldd "$FILENAME" 2>/dev/null \
+ | grep $(dirname "$FILENAME") \
+ | sed "s/.*[ \t]\(.*\) (.*/\1/")
+)
+
+# Start concurrent indexing.
+trap index_next USR1
+
+# 4 is an arbitrary default. When changing, remember we are likely IO bound
+# so basing this off the number of cores is not sensible.
+INDEX_TASKS=${INDEX_TASKS:-4}
+for ((i=0;i<${INDEX_TASKS};i++)); do
+ index_next
+done
+
+# Do a wait loop. Bash waits that terminate due a trap have an exit
+# code > 128. We also ensure that our subshell's "normal" exit occurs with
+# an exit code > 128. This allows us to do consider a > 128 exit code as
+# an indication that the loop should continue. Unfortunately, it also means
+# we cannot use set -e since technically the "wait" is failing.
+wait
+while (( $? > 128 )); do
+ wait
+done
diff --git a/build/get_landmines.py b/build/get_landmines.py
new file mode 100755
index 00000000000..92f81fe8f03
--- /dev/null
+++ b/build/get_landmines.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# Copyright 2013 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 file emits the list of reasons why a particular build needs to be clobbered
+(or a list of 'landmines').
+"""
+
+import sys
+
+import landmine_utils
+
+
+builder = landmine_utils.builder
+distributor = landmine_utils.distributor
+gyp_defines = landmine_utils.gyp_defines
+gyp_msvs_version = landmine_utils.gyp_msvs_version
+platform = landmine_utils.platform
+
+
+def print_landmines():
+ """
+ ALL LANDMINES ARE EMITTED FROM HERE.
+ """
+ # DO NOT add landmines as part of a regular CL. Landmines are a last-effort
+ # bandaid fix if a CL that got landed has a build dependency bug and all bots
+ # need to be cleaned up. If you're writing a new CL that causes build
+ # dependency problems, fix the dependency problems instead of adding a
+ # landmine.
+
+ print 'Lets start a new landmines file.'
+
+
+def main():
+ print_landmines()
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/get_sdk_extras_packages.py b/build/get_sdk_extras_packages.py
new file mode 100755
index 00000000000..a90b8a8b4a4
--- /dev/null
+++ b/build/get_sdk_extras_packages.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# Copyright (c) 2014 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.
+
+import json
+import os
+import sys
+
+SDK_EXTRAS_JSON_FILE = os.path.join(os.path.dirname(__file__),
+ 'android_sdk_extras.json')
+
+def main():
+ with open(SDK_EXTRAS_JSON_FILE) as json_file:
+ packages = json.load(json_file)
+
+ out = []
+ for package in packages:
+ out.append(package['package_id'])
+
+ print ','.join(out)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/get_syzygy_binaries.py b/build/get_syzygy_binaries.py
new file mode 100755
index 00000000000..1cab3fcf48d
--- /dev/null
+++ b/build/get_syzygy_binaries.py
@@ -0,0 +1,487 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""A utility script for downloading versioned Syzygy binaries."""
+
+import hashlib
+import errno
+import json
+import logging
+import optparse
+import os
+import re
+import shutil
+import stat
+import sys
+import subprocess
+import tempfile
+import time
+import zipfile
+
+
+_LOGGER = logging.getLogger(os.path.basename(__file__))
+
+# The relative path where official builds are archived in their GS bucket.
+_SYZYGY_ARCHIVE_PATH = ('/builds/official/%(revision)s')
+
+# A JSON file containing the state of the download directory. If this file and
+# directory state do not agree, then the binaries will be downloaded and
+# installed again.
+_STATE = '.state'
+
+# This matches an integer (an SVN revision number) or a SHA1 value (a GIT hash).
+# The archive exclusively uses lowercase GIT hashes.
+_REVISION_RE = re.compile('^(?:\d+|[a-f0-9]{40})$')
+
+# This matches an MD5 hash.
+_MD5_RE = re.compile('^[a-f0-9]{32}$')
+
+# List of reources to be downloaded and installed. These are tuples with the
+# following format:
+# (basename, logging name, relative installation path, extraction filter)
+_RESOURCES = [
+ ('benchmark.zip', 'benchmark', '', None),
+ ('binaries.zip', 'binaries', 'exe', None),
+ ('symbols.zip', 'symbols', 'exe',
+ lambda x: x.filename.endswith('.dll.pdb'))]
+
+
+def _LoadState(output_dir):
+ """Loads the contents of the state file for a given |output_dir|, returning
+ None if it doesn't exist.
+ """
+ path = os.path.join(output_dir, _STATE)
+ if not os.path.exists(path):
+ _LOGGER.debug('No state file found.')
+ return None
+ with open(path, 'rb') as f:
+ _LOGGER.debug('Reading state file: %s', path)
+ try:
+ return json.load(f)
+ except ValueError:
+ _LOGGER.debug('Invalid state file.')
+ return None
+
+
+def _SaveState(output_dir, state, dry_run=False):
+ """Saves the |state| dictionary to the given |output_dir| as a JSON file."""
+ path = os.path.join(output_dir, _STATE)
+ _LOGGER.debug('Writing state file: %s', path)
+ if dry_run:
+ return
+ with open(path, 'wb') as f:
+ f.write(json.dumps(state, sort_keys=True, indent=2))
+
+
+def _Md5(path):
+ """Returns the MD5 hash of the file at |path|, which must exist."""
+ return hashlib.md5(open(path, 'rb').read()).hexdigest()
+
+
+def _StateIsValid(state):
+ """Returns true if the given state structure is valid."""
+ if not isinstance(state, dict):
+ _LOGGER.debug('State must be a dict.')
+ return False
+ r = state.get('revision', None)
+ if not isinstance(r, basestring) or not _REVISION_RE.match(r):
+ _LOGGER.debug('State contains an invalid revision.')
+ return False
+ c = state.get('contents', None)
+ if not isinstance(c, dict):
+ _LOGGER.debug('State must contain a contents dict.')
+ return False
+ for (relpath, md5) in c.iteritems():
+ if not isinstance(relpath, basestring) or len(relpath) == 0:
+ _LOGGER.debug('State contents dict contains an invalid path.')
+ return False
+ if not isinstance(md5, basestring) or not _MD5_RE.match(md5):
+ _LOGGER.debug('State contents dict contains an invalid MD5 digest.')
+ return False
+ return True
+
+
+def _BuildActualState(stored, revision, output_dir):
+ """Builds the actual state using the provided |stored| state as a template.
+ Only examines files listed in the stored state, causing the script to ignore
+ files that have been added to the directories locally. |stored| must be a
+ valid state dictionary.
+ """
+ contents = {}
+ state = { 'revision': revision, 'contents': contents }
+ for relpath, md5 in stored['contents'].iteritems():
+ abspath = os.path.abspath(os.path.join(output_dir, relpath))
+ if os.path.isfile(abspath):
+ m = _Md5(abspath)
+ contents[relpath] = m
+
+ return state
+
+
+def _StatesAreConsistent(stored, actual):
+ """Validates whether two state dictionaries are consistent. Both must be valid
+ state dictionaries. Additional entries in |actual| are ignored.
+ """
+ if stored['revision'] != actual['revision']:
+ _LOGGER.debug('Mismatched revision number.')
+ return False
+ cont_stored = stored['contents']
+ cont_actual = actual['contents']
+ for relpath, md5 in cont_stored.iteritems():
+ if relpath not in cont_actual:
+ _LOGGER.debug('Missing content: %s', relpath)
+ return False
+ if md5 != cont_actual[relpath]:
+ _LOGGER.debug('Modified content: %s', relpath)
+ return False
+ return True
+
+
+def _GetCurrentState(revision, output_dir):
+ """Loads the current state and checks to see if it is consistent. Returns
+ a tuple (state, bool). The returned state will always be valid, even if an
+ invalid state is present on disk.
+ """
+ stored = _LoadState(output_dir)
+ if not _StateIsValid(stored):
+ _LOGGER.debug('State is invalid.')
+ # Return a valid but empty state.
+ return ({'revision': '0', 'contents': {}}, False)
+ actual = _BuildActualState(stored, revision, output_dir)
+ # If the script has been modified consider the state invalid.
+ path = os.path.join(output_dir, _STATE)
+ if os.path.getmtime(__file__) > os.path.getmtime(path):
+ return (stored, False)
+ # Otherwise, explicitly validate the state.
+ if not _StatesAreConsistent(stored, actual):
+ return (stored, False)
+ return (stored, True)
+
+
+def _DirIsEmpty(path):
+ """Returns true if the given directory is empty, false otherwise."""
+ for root, dirs, files in os.walk(path):
+ return not dirs and not files
+
+
+def _RmTreeHandleReadOnly(func, path, exc):
+ """An error handling function for use with shutil.rmtree. This will
+ detect failures to remove read-only files, and will change their properties
+ prior to removing them. This is necessary on Windows as os.remove will return
+ an access error for read-only files, and git repos contain read-only
+ pack/index files.
+ """
+ excvalue = exc[1]
+ if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
+ _LOGGER.debug('Removing read-only path: %s', path)
+ os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ func(path)
+ else:
+ raise
+
+
+def _RmTree(path):
+ """A wrapper of shutil.rmtree that handles read-only files."""
+ shutil.rmtree(path, ignore_errors=False, onerror=_RmTreeHandleReadOnly)
+
+
+def _CleanState(output_dir, state, dry_run=False):
+ """Cleans up files/directories in |output_dir| that are referenced by
+ the given |state|. Raises an error if there are local changes. Returns a
+ dictionary of files that were deleted.
+ """
+ _LOGGER.debug('Deleting files from previous installation.')
+ deleted = {}
+
+ # Generate a list of files to delete, relative to |output_dir|.
+ contents = state['contents']
+ files = sorted(contents.keys())
+
+ # Try to delete the files. Keep track of directories to delete as well.
+ dirs = {}
+ for relpath in files:
+ fullpath = os.path.join(output_dir, relpath)
+ fulldir = os.path.dirname(fullpath)
+ dirs[fulldir] = True
+ if os.path.exists(fullpath):
+ # If somehow the file has become a directory complain about it.
+ if os.path.isdir(fullpath):
+ raise Exception('Directory exists where file expected: %s' % fullpath)
+
+ # Double check that the file doesn't have local changes. If it does
+ # then refuse to delete it.
+ if relpath in contents:
+ stored_md5 = contents[relpath]
+ actual_md5 = _Md5(fullpath)
+ if actual_md5 != stored_md5:
+ raise Exception('File has local changes: %s' % fullpath)
+
+ # The file is unchanged so it can safely be deleted.
+ _LOGGER.debug('Deleting file "%s".', fullpath)
+ deleted[relpath] = True
+ if not dry_run:
+ os.unlink(fullpath)
+
+ # Sort directories from longest name to shortest. This lets us remove empty
+ # directories from the most nested paths first.
+ dirs = sorted(dirs.keys(), key=lambda x: len(x), reverse=True)
+ for p in dirs:
+ if os.path.exists(p) and _DirIsEmpty(p):
+ _LOGGER.debug('Deleting empty directory "%s".', p)
+ if not dry_run:
+ _RmTree(p)
+
+ return deleted
+
+
+def _FindGsUtil():
+ """Looks for depot_tools and returns the absolute path to gsutil.py."""
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = os.path.abspath(path)
+ git_cl = os.path.join(path, 'git_cl.py')
+ gs_util = os.path.join(path, 'gsutil.py')
+ if os.path.exists(git_cl) and os.path.exists(gs_util):
+ return gs_util
+ return None
+
+
+def _GsUtil(*cmd):
+ """Runs the given command in gsutil with exponential backoff and retries."""
+ gs_util = _FindGsUtil()
+ cmd = [sys.executable, gs_util] + list(cmd)
+
+ retries = 3
+ timeout = 4 # Seconds.
+ while True:
+ _LOGGER.debug('Running %s', cmd)
+ prog = subprocess.Popen(cmd, shell=False)
+ prog.communicate()
+
+ # Stop retrying on success.
+ if prog.returncode == 0:
+ return
+
+ # Raise a permanent failure if retries have been exhausted.
+ if retries == 0:
+ raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
+
+ _LOGGER.debug('Sleeping %d seconds and trying again.', timeout)
+ time.sleep(timeout)
+ retries -= 1
+ timeout *= 2
+
+
+def _Download(resource):
+ """Downloads the given GS resource to a temporary file, returning its path."""
+ tmp = tempfile.mkstemp(suffix='syzygy_archive')
+ os.close(tmp[0])
+ url = 'gs://syzygy-archive' + resource
+ _GsUtil('cp', url, tmp[1])
+ return tmp[1]
+
+
+def _InstallBinaries(options, deleted={}):
+ """Installs Syzygy binaries. This assumes that the output directory has
+ already been cleaned, as it will refuse to overwrite existing files."""
+ contents = {}
+ state = { 'revision': options.revision, 'contents': contents }
+ archive_path = _SYZYGY_ARCHIVE_PATH % { 'revision': options.revision }
+ if options.resources:
+ resources = [(resource, resource, '', None)
+ for resource in options.resources]
+ else:
+ resources = _RESOURCES
+ for (base, name, subdir, filt) in resources:
+ # Create the output directory if it doesn't exist.
+ fulldir = os.path.join(options.output_dir, subdir)
+ if os.path.isfile(fulldir):
+ raise Exception('File exists where a directory needs to be created: %s' %
+ fulldir)
+ if not os.path.exists(fulldir):
+ _LOGGER.debug('Creating directory: %s', fulldir)
+ if not options.dry_run:
+ os.makedirs(fulldir)
+
+ # Download and read the archive.
+ resource = archive_path + '/' + base
+ _LOGGER.debug('Retrieving %s archive at "%s".', name, resource)
+ path = _Download(resource)
+
+ _LOGGER.debug('Unzipping %s archive.', name)
+ with open(path, 'rb') as data:
+ archive = zipfile.ZipFile(data)
+ for entry in archive.infolist():
+ if not filt or filt(entry):
+ fullpath = os.path.normpath(os.path.join(fulldir, entry.filename))
+ relpath = os.path.relpath(fullpath, options.output_dir)
+ if os.path.exists(fullpath):
+ # If in a dry-run take into account the fact that the file *would*
+ # have been deleted.
+ if options.dry_run and relpath in deleted:
+ pass
+ else:
+ raise Exception('Path already exists: %s' % fullpath)
+
+ # Extract the file and update the state dictionary.
+ _LOGGER.debug('Extracting "%s".', fullpath)
+ if not options.dry_run:
+ archive.extract(entry.filename, fulldir)
+ md5 = _Md5(fullpath)
+ contents[relpath] = md5
+ if sys.platform == 'cygwin':
+ os.chmod(fullpath, os.stat(fullpath).st_mode | stat.S_IXUSR)
+
+ _LOGGER.debug('Removing temporary file "%s".', path)
+ os.remove(path)
+
+ return state
+
+
+def _ParseCommandLine():
+ """Parses the command-line and returns an options structure."""
+ option_parser = optparse.OptionParser()
+ option_parser.add_option('--dry-run', action='store_true', default=False,
+ help='If true then will simply list actions that would be performed.')
+ option_parser.add_option('--force', action='store_true', default=False,
+ help='Force an installation even if the binaries are up to date.')
+ option_parser.add_option('--no-cleanup', action='store_true', default=False,
+ help='Allow installation on non-Windows platforms, and skip the forced '
+ 'cleanup step.')
+ option_parser.add_option('--output-dir', type='string',
+ help='The path where the binaries will be replaced. Existing binaries '
+ 'will only be overwritten if not up to date.')
+ option_parser.add_option('--overwrite', action='store_true', default=False,
+ help='If specified then the installation will happily delete and rewrite '
+ 'the entire output directory, blasting any local changes.')
+ option_parser.add_option('--revision', type='string',
+ help='The SVN revision or GIT hash associated with the required version.')
+ option_parser.add_option('--revision-file', type='string',
+ help='A text file containing an SVN revision or GIT hash.')
+ option_parser.add_option('--resource', type='string', action='append',
+ dest='resources', help='A resource to be downloaded.')
+ option_parser.add_option('--verbose', dest='log_level', action='store_const',
+ default=logging.INFO, const=logging.DEBUG,
+ help='Enables verbose logging.')
+ option_parser.add_option('--quiet', dest='log_level', action='store_const',
+ default=logging.INFO, const=logging.ERROR,
+ help='Disables all output except for errors.')
+ options, args = option_parser.parse_args()
+ if args:
+ option_parser.error('Unexpected arguments: %s' % args)
+ if not options.output_dir:
+ option_parser.error('Must specify --output-dir.')
+ if not options.revision and not options.revision_file:
+ option_parser.error('Must specify one of --revision or --revision-file.')
+ if options.revision and options.revision_file:
+ option_parser.error('Must not specify both --revision and --revision-file.')
+
+ # Configure logging.
+ logging.basicConfig(level=options.log_level)
+
+ # If a revision file has been specified then read it.
+ if options.revision_file:
+ options.revision = open(options.revision_file, 'rb').read().strip()
+ _LOGGER.debug('Parsed revision "%s" from file "%s".',
+ options.revision, options.revision_file)
+
+ # Ensure that the specified SVN revision or GIT hash is valid.
+ if not _REVISION_RE.match(options.revision):
+ option_parser.error('Must specify a valid SVN or GIT revision.')
+
+ # This just makes output prettier to read.
+ options.output_dir = os.path.normpath(options.output_dir)
+
+ return options
+
+
+def _RemoveOrphanedFiles(options):
+ """This is run on non-Windows systems to remove orphaned files that may have
+ been downloaded by a previous version of this script.
+ """
+ # Reconfigure logging to output info messages. This will allow inspection of
+ # cleanup status on non-Windows buildbots.
+ _LOGGER.setLevel(logging.INFO)
+
+ output_dir = os.path.abspath(options.output_dir)
+
+ # We only want to clean up the folder in 'src/third_party/syzygy', and we
+ # expect to be called with that as an output directory. This is an attempt to
+ # not start deleting random things if the script is run from an alternate
+ # location, or not called from the gclient hooks.
+ expected_syzygy_dir = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '..', 'third_party', 'syzygy'))
+ expected_output_dir = os.path.join(expected_syzygy_dir, 'binaries')
+ if expected_output_dir != output_dir:
+ _LOGGER.info('Unexpected output directory, skipping cleanup.')
+ return
+
+ if not os.path.isdir(expected_syzygy_dir):
+ _LOGGER.info('Output directory does not exist, skipping cleanup.')
+ return
+
+ def OnError(function, path, excinfo):
+ """Logs error encountered by shutil.rmtree."""
+ _LOGGER.error('Error when running %s(%s)', function, path, exc_info=excinfo)
+
+ _LOGGER.info('Removing orphaned files from %s', expected_syzygy_dir)
+ if not options.dry_run:
+ shutil.rmtree(expected_syzygy_dir, True, OnError)
+
+
+def main():
+ options = _ParseCommandLine()
+
+ if options.dry_run:
+ _LOGGER.debug('Performing a dry-run.')
+
+ # We only care about Windows platforms, as the Syzygy binaries aren't used
+ # elsewhere. However, there was a short period of time where this script
+ # wasn't gated on OS types, and those OSes downloaded and installed binaries.
+ # This will cleanup orphaned files on those operating systems.
+ if sys.platform not in ('win32', 'cygwin'):
+ if options.no_cleanup:
+ _LOGGER.debug('Skipping usual cleanup for non-Windows platforms.')
+ else:
+ return _RemoveOrphanedFiles(options)
+
+ # Load the current installation state, and validate it against the
+ # requested installation.
+ state, is_consistent = _GetCurrentState(options.revision, options.output_dir)
+
+ # Decide whether or not an install is necessary.
+ if options.force:
+ _LOGGER.debug('Forcing reinstall of binaries.')
+ elif is_consistent:
+ # Avoid doing any work if the contents of the directory are consistent.
+ _LOGGER.debug('State unchanged, no reinstall necessary.')
+ return
+
+ # Under normal logging this is the only only message that will be reported.
+ _LOGGER.info('Installing revision %s Syzygy binaries.',
+ options.revision[0:12])
+
+ # Clean up the old state to begin with.
+ deleted = []
+ if options.overwrite:
+ if os.path.exists(options.output_dir):
+ # If overwrite was specified then take a heavy-handed approach.
+ _LOGGER.debug('Deleting entire installation directory.')
+ if not options.dry_run:
+ _RmTree(options.output_dir)
+ else:
+ # Otherwise only delete things that the previous installation put in place,
+ # and take care to preserve any local changes.
+ deleted = _CleanState(options.output_dir, state, options.dry_run)
+
+ # Install the new binaries. In a dry-run this will actually download the
+ # archives, but it won't write anything to disk.
+ state = _InstallBinaries(options, deleted)
+
+ # Build and save the state for the directory.
+ _SaveState(options.output_dir, state, options.dry_run)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/git-hooks/OWNERS b/build/git-hooks/OWNERS
new file mode 100644
index 00000000000..3e327dc7113
--- /dev/null
+++ b/build/git-hooks/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+szager@chromium.org
+cmp@chromium.org
diff --git a/build/git-hooks/pre-commit b/build/git-hooks/pre-commit
new file mode 100755
index 00000000000..41b596344c4
--- /dev/null
+++ b/build/git-hooks/pre-commit
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+submodule_diff() {
+ if test -n "$2"; then
+ git diff-tree -r --ignore-submodules=dirty "$1" "$2" | grep -e '^:160000' -e '^:...... 160000' | xargs
+ else
+ git diff-index --cached --ignore-submodules=dirty "$1" | grep -e '^:160000' -e '^:...... 160000' | xargs
+ fi
+}
+
+if git rev-parse --verify --quiet --no-revs MERGE_HEAD; then
+ merge_base=$(git merge-base HEAD MERGE_HEAD)
+ if test -z "$(submodule_diff $merge_base HEAD)"; then
+ # Most up-to-date submodules are in MERGE_HEAD.
+ head_ref=MERGE_HEAD
+ else
+ # Most up-to-date submodules are in HEAD.
+ head_ref=HEAD
+ fi
+else
+ # No merge in progress. Submodules must match HEAD.
+ head_ref=HEAD
+fi
+
+submods=$(submodule_diff $head_ref)
+if test "$submods"; then
+ echo "You are trying to commit changes to the following submodules:" 1>&2
+ echo 1>&2
+ echo $submods | cut -d ' ' -f 6 | sed 's/^/ /g' 1>&2
+ cat <&2
+
+Submodule commits are not allowed. Please run:
+
+ git status --ignore-submodules=dirty
+
+and/or:
+
+ git diff-index --cached --ignore-submodules=dirty HEAD
+
+... to see what's in your index.
+
+If you're really and truly trying to roll the version of a submodule, you should
+commit the new version to DEPS, instead.
+EOF
+ exit 1
+fi
+
+gitmodules_diff() {
+ git diff-index --cached "$1" .gitmodules
+}
+
+if [ "$(git ls-files .gitmodules)" ] && [ "$(gitmodules_diff $head_ref)" ]; then
+ cat <&2
+You are trying to commit a change to .gitmodules. That is not allowed.
+To make changes to submodule names/paths, edit DEPS.
+EOF
+ exit 1
+fi
+
+exit 0
diff --git a/build/gn_helpers.py b/build/gn_helpers.py
new file mode 100644
index 00000000000..3b0647d9a5b
--- /dev/null
+++ b/build/gn_helpers.py
@@ -0,0 +1,39 @@
+# Copyright 2014 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.
+
+"""Helper functions useful when writing scripts that are run from GN's
+exec_script function."""
+
+class GNException(Exception):
+ pass
+
+
+def ToGNString(value, allow_dicts = True):
+ """Prints the given value to stdout.
+
+ allow_dicts indicates if this function will allow converting dictionaries
+ to GN scopes. This is only possible at the top level, you can't nest a
+ GN scope in a list, so this should be set to False for recursive calls."""
+ if isinstance(value, str):
+ if value.find('\n') >= 0:
+ raise GNException("Trying to print a string with a newline in it.")
+ return '"' + value.replace('"', '\\"') + '"'
+
+ if isinstance(value, list):
+ return '[ %s ]' % ', '.join(ToGNString(v) for v in value)
+
+ if isinstance(value, dict):
+ if not allow_dicts:
+ raise GNException("Attempting to recursively print a dictionary.")
+ result = ""
+ for key in value:
+ if not isinstance(key, str):
+ raise GNException("Dictionary key is not a string.")
+ result += "%s = %s\n" % (key, ToGNString(value[key], False))
+ return result
+
+ if isinstance(value, int):
+ return str(value)
+
+ raise GNException("Unsupported type when printing to GN.")
diff --git a/build/gn_migration.gypi b/build/gn_migration.gypi
new file mode 100644
index 00000000000..2527b2e52a7
--- /dev/null
+++ b/build/gn_migration.gypi
@@ -0,0 +1,726 @@
+# Copyright (c) 2015 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 file defines five targets that we are using to track the progress of the
+# GYP->GN migration:
+#
+# 'both_gn_and_gyp' lists what GN is currently capable of building and should
+# match the 'both_gn_and_gyp' target in //BUILD.gn.
+#
+# 'gyp_all' Should include everything built when building "all"; i.e., if you
+# type 'ninja gyp_all' and then 'ninja all', the second build should do
+# nothing. 'gyp_all' should just depend on the other four targets.
+#
+# 'gyp_only' lists any targets that are not meant to be ported over to the GN
+# build.
+#
+# 'gyp_remaining' lists all of the targets that still need to be converted,
+# i.e., all of the other (non-empty) targets that a GYP build will build.
+#
+# TODO(GYP): crbug.com/481694. Add a build step to the bot that enforces the
+# above contracts.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'gyp_all',
+ 'type': 'none',
+ 'dependencies': [
+ 'both_gn_and_gyp',
+ 'gyp_only',
+ 'gyp_remaining',
+ ]
+ },
+ {
+ # This target should mirror the structure of //:both_gn_and_gyp
+ # in src/BUILD.gn as closely as possible, for ease of comparison.
+ 'target_name': 'both_gn_and_gyp',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_i18n_perftests',
+ '../base/base.gyp:base_perftests',
+ '../base/base.gyp:base_unittests',
+ '../base/base.gyp:build_utf8_validator_tables#host',
+ '../base/base.gyp:check_example',
+ '../cc/cc_tests.gyp:cc_perftests',
+ '../cc/cc_tests.gyp:cc_unittests',
+ '../cc/blink/cc_blink_tests.gyp:cc_blink_unittests',
+ '../chrome/chrome.gyp:chrome',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:chrome_app_unittests',
+ '../chrome/chrome.gyp:chromedriver',
+ '../chrome/chrome.gyp:chromedriver_tests',
+ '../chrome/chrome.gyp:chromedriver_unittests',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:load_library_perf_tests',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:sync_performance_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../chrome/tools/profile_reset/jtl_compiler.gyp:jtl_compiler',
+ '../cloud_print/cloud_print.gyp:cloud_print_unittests',
+ '../components/components.gyp:network_hints_browser',
+ '../components/components.gyp:policy_templates',
+ '../components/components_tests.gyp:components_browsertests',
+ '../components/components_tests.gyp:components_perftests',
+ '../components/components_tests.gyp:components_unittests',
+ '../content/content.gyp:content_app_browser',
+ '../content/content.gyp:content_app_child',
+ '../content/content_shell_and_tests.gyp:content_browsertests',
+ '../content/content_shell_and_tests.gyp:content_gl_benchmark',
+ '../content/content_shell_and_tests.gyp:content_gl_tests',
+ '../content/content_shell_and_tests.gyp:content_perftests',
+ '../content/content_shell_and_tests.gyp:content_shell',
+ '../content/content_shell_and_tests.gyp:content_unittests',
+ '../courgette/courgette.gyp:courgette',
+ '../courgette/courgette.gyp:courgette_fuzz',
+ '../courgette/courgette.gyp:courgette_minimal_tool',
+ '../courgette/courgette.gyp:courgette_unittests',
+ '../crypto/crypto.gyp:crypto_unittests',
+ '../extensions/extensions_tests.gyp:extensions_browsertests',
+ '../extensions/extensions_tests.gyp:extensions_unittests',
+ '../device/device_tests.gyp:device_unittests',
+ '../gin/gin.gyp:gin_v8_snapshot_fingerprint',
+ '../gin/gin.gyp:gin_shell',
+ '../gin/gin.gyp:gin_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../google_apis/gcm/gcm.gyp:mcs_probe',
+ '../google_apis/google_apis.gyp:google_apis_unittests',
+ '../gpu/gpu.gyp:angle_unittests',
+ '../gpu/gpu.gyp:gl_tests',
+ '../gpu/gpu.gyp:gpu_perftests',
+ '../gpu/gpu.gyp:gpu_unittests',
+ '../gpu/gles2_conform_support/gles2_conform_support.gyp:gles2_conform_support', # TODO(GYP) crbug.com/471920
+ '../gpu/gles2_conform_support/gles2_conform_test.gyp:gles2_conform_test', # TODO(GYP) crbug.com/471920
+ '../gpu/khronos_glcts_support/khronos_glcts_test.gyp:khronos_glcts_test', # TODO(GYP) crbug.com/471903 to make this complete.
+ '../ipc/ipc.gyp:ipc_perftests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../media/media.gyp:ffmpeg_regression_tests', # TODO(GYP) this should be conditional on media_use_ffmpeg
+ '../media/media.gyp:media_perftests',
+ '../media/media.gyp:media_unittests',
+ '../media/midi/midi.gyp:midi_unittests',
+ '../media/cast/cast.gyp:cast_benchmarks',
+ '../media/cast/cast.gyp:cast_unittests',
+ '../media/cast/cast.gyp:generate_barcode_video',
+ '../media/cast/cast.gyp:generate_timecode_audio',
+ '../mojo/mojo.gyp:mojo',
+ '../mojo/mojo_base.gyp:mojo_application_base',
+ '../mojo/mojo_base.gyp:mojo_common_unittests',
+ '../net/net.gyp:crash_cache',
+ '../net/net.gyp:crl_set_dump',
+ '../net/net.gyp:dns_fuzz_stub',
+ '../net/net.gyp:dump_cache',
+ '../net/net.gyp:gdig',
+ '../net/net.gyp:get_server_time',
+ '../net/net.gyp:hpack_example_generator',
+ '../net/net.gyp:hpack_fuzz_mutator',
+ '../net/net.gyp:hpack_fuzz_wrapper',
+ '../net/net.gyp:net_perftests',
+ '../net/net.gyp:net_unittests',
+ '../net/net.gyp:net_watcher', # TODO(GYP): This should be conditional on use_v8_in_net
+ '../net/net.gyp:run_testserver',
+ '../net/net.gyp:stress_cache',
+ '../net/net.gyp:tld_cleanup',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_audio',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_audio_input',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_c_stub',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_cc_stub',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_compositor',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_crxfs',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_enumerate_devices',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_file_chooser',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_flash_topmost',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_gamepad',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_gles2',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_gles2_spinning_cube',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_graphics_2d',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_ime',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_input',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_media_stream_audio',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_media_stream_video',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_mouse_cursor',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_mouse_lock',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_paint_manager',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_post_message',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_printing',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_scaling',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_scroll',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_simple_font',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_threading',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_url_loader',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_url_loader_file',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_vc',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_video_decode',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_video_decode_dev',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_video_effects',
+ '../ppapi/ppapi_internal.gyp:ppapi_example_video_encode',
+ '../ppapi/ppapi_internal.gyp:ppapi_tests',
+ '../ppapi/ppapi_internal.gyp:ppapi_perftests',
+ '../ppapi/ppapi_internal.gyp:ppapi_unittests',
+ '../ppapi/tools/ppapi_tools.gyp:pepper_hash_for_uma',
+ '../printing/printing.gyp:printing_unittests',
+ '../skia/skia_tests.gyp:skia_unittests',
+ '../skia/skia.gyp:filter_fuzz_stub',
+ '../skia/skia.gyp:image_operations_bench',
+ '../sql/sql.gyp:sql_unittests',
+ '../sync/sync.gyp:run_sync_testserver',
+ '../sync/sync.gyp:sync_unit_tests',
+ '../sync/tools/sync_tools.gyp:sync_client',
+ '../sync/tools/sync_tools.gyp:sync_listen_notifications',
+ '../testing/gmock.gyp:gmock_main',
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_heap_unittests',
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_platform_unittests',
+ '../third_party/WebKit/Source/web/web_tests.gyp:webkit_unit_tests',
+ '../third_party/WebKit/Source/wtf/wtf_tests.gyp:wtf_unittests',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests',
+ '../third_party/codesighs/codesighs.gyp:codesighs',
+ '../third_party/codesighs/codesighs.gyp:maptsvdifftool',
+ '../third_party/leveldatabase/leveldatabase.gyp:env_chromium_unittests',
+ '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber_unittests',
+ '../third_party/libaddressinput/libaddressinput.gyp:libaddressinput_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_system_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_bindings_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_environment_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_system_perftests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_system_unittests',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_utility_unittests',
+ '../third_party/pdfium/samples/samples.gyp:pdfium_diff',
+ '../third_party/pdfium/samples/samples.gyp:pdfium_test',
+ '../third_party/smhasher/smhasher.gyp:pmurhash',
+ '../tools/gn/gn.gyp:gn',
+ '../tools/gn/gn.gyp:generate_test_gn_data',
+ '../tools/gn/gn.gyp:gn_unittests',
+ '../tools/imagediff/image_diff.gyp:image_diff',
+ '../tools/perf/clear_system_cache/clear_system_cache.gyp:clear_system_cache',
+ '../tools/telemetry/telemetry.gyp:bitmaptools#host',
+ '../ui/accessibility/accessibility.gyp:accessibility_unittests',
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ '../ui/base/ui_base_tests.gyp:ui_base_unittests',
+ '../ui/compositor/compositor.gyp:compositor_unittests',
+ '../ui/display/display.gyp:display_unittests',
+ '../ui/events/events.gyp:events_unittests',
+ '../ui/gfx/gfx_tests.gyp:gfx_unittests',
+ '../ui/gl/gl_tests.gyp:gl_unittests',
+ '../ui/message_center/message_center.gyp:message_center_unittests',
+ '../ui/snapshot/snapshot.gyp:snapshot_unittests',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests',
+ '../ui/views/examples/examples.gyp:views_examples_with_content_exe',
+ '../url/url.gyp:url_unittests',
+ '../v8/tools/gyp/v8.gyp:v8_snapshot',
+ '../v8/tools/gyp/v8.gyp:postmortem-metadata',
+ ],
+ 'conditions': [
+ ['clang==1', {
+ 'dependencies': [
+ '../build/sanitizers/sanitizers.gyp:llvm-symbolizer',
+ ],
+ }],
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'dependencies': [
+ '../components/nacl.gyp:nacl_loader_unittests',
+ ]
+ }],
+ ['enable_extensions==1 and OS!="mac"', {
+ 'dependencies': [
+ '../extensions/shell/app_shell.gyp:app_shell',
+ '../extensions/shell/app_shell.gyp:app_shell_unittests',
+ ],
+ }],
+ ['enable_mdns==1', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:service_discovery_sniffer',
+ ]
+ }],
+ ['remoting==1', {
+ 'dependencies': [
+ '../remoting/remoting_all.gyp:remoting_all',
+ ],
+ }],
+ ['remoting==1 and chromeos==0 and use_x11==1', {
+ 'dependencies': [
+ '../remoting/remoting.gyp:remoting_me2me_host',
+ '../remoting/remoting.gyp:remoting_me2me_native_messaging_host',
+ ],
+ }],
+ ['toolkit_views==1', {
+ 'dependencies': [
+ '../ui/app_list/app_list.gyp:app_list_demo',
+ '../ui/views/views.gyp:views_unittests',
+ ],
+ }],
+ ['use_ash==1', {
+ 'dependencies': [
+ '../ash/ash.gyp:ash_shell',
+ '../ash/ash.gyp:ash_shell_unittests',
+ '../ash/ash.gyp:ash_unittests',
+ ],
+ }],
+ ['use_ash==1 or chromeos== 1', {
+ 'dependencies': [
+ '../components/components.gyp:session_manager_component',
+ ]
+ }],
+ ['use_aura==1', {
+ 'dependencies': [
+ '../ui/aura/aura.gyp:aura_bench',
+ '../ui/aura/aura.gyp:aura_demo',
+ '../ui/aura/aura.gyp:aura_unittests',
+ '../ui/keyboard/keyboard.gyp:keyboard_unittests',
+ '../ui/wm/wm.gyp:wm_unittests',
+ ],
+ }],
+ ['use_ozone==1', {
+ 'dependencies': [
+ '../ui/ozone/ozone.gyp:ozone',
+ ],
+ }],
+ ['use_x11==1', {
+ 'dependencies': [
+ '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck',
+ ],
+ 'conditions': [
+ ['target_arch!="arm"', {
+ 'dependencies': [
+ '../gpu/tools/tools.gyp:compositor_model_bench',
+ ],
+ }],
+ ],
+ }],
+ ['OS=="android"', {
+ 'dependencies': [
+ '../base/base.gyp:chromium_android_linker',
+ '../breakpad/breakpad.gyp:dump_syms',
+ '../build/android/rezip.gyp:rezip_apk_jar',
+ '../chrome/chrome.gyp:chrome_public_apk',
+ '../chrome/chrome.gyp:chrome_public_test_apk',
+ '../chrome/chrome.gyp:chrome_shell_apk',
+ '../chrome/chrome.gyp:chromedriver_webview_shell_apk',
+ #"//clank" TODO(GYP) - conditional somehow?
+ '../tools/imagediff/image_diff.gyp:image_diff#host',
+ '../tools/telemetry/telemetry.gyp:bitmaptools#host',
+
+ # TODO(GYP): Remove these when the components_unittests work.
+ #"//components/history/core/test:test",
+ #"//components/policy:policy_component_test_support",
+ #"//components/policy:test_support",
+ #"//components/rappor:test_support",
+ #"//components/signin/core/browser:test_support",
+ #"//components/sync_driver:test_support",
+ #"//components/user_manager",
+ #"//components/wallpaper",
+
+ '../content/content_shell_and_tests.gyp:content_shell_apk',
+
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_heap_unittests_apk',
+ '../third_party/WebKit/Source/platform/blink_platform_tests.gyp:blink_platform_unittests_apk',
+ '../third_party/WebKit/Source/web/web_tests.gyp:webkit_unit_tests_apk',
+ '../third_party/WebKit/Source/wtf/wtf_tests.gyp:wtf_unittests_apk',
+ # TODO(GYP): Are these needed, or will they be pulled in automatically?
+ #"//third_party/android_tools:android_gcm_java",
+ #"//third_party/android_tools:uiautomator_java",
+ #"//third_party/android_tools:android_support_v13_java",
+ #"//third_party/android_tools:android_support_v7_appcompat_java",
+ #"//third_party/android_tools:android_support_v7_mediarouter_java",
+ #"//third_party/mesa",
+ #"//third_party/mockito:mockito_java",
+ #"//third_party/openmax_dl/dl",
+ #"//third_party/speex",
+ #"//ui/android:ui_java",
+
+ # TODO(GYP): Are these needed?
+ #"//chrome/test:test_support_unit",
+ #"//third_party/smhasher:murmurhash3",
+ #"//ui/message_center:test_support",
+ ],
+ 'dependencies!': [
+ '../breakpad/breakpad.gyp:symupload',
+ '../chrome/chrome.gyp:browser_tests',
+ '../chrome/chrome.gyp:chromedriver',
+ '../chrome/chrome.gyp:chromedriver_unitests',
+ '../chrome/chrome.gyp:interactive_ui_tests',
+ '../chrome/chrome.gyp:performance_browser_tests',
+ '../chrome/chrome.gyp:sync_integration_tests',
+ '../chrome/chrome.gyp:unit_tests',
+ '../extensions/extensions_tests.gyp:extensions_browsertests',
+ '../extensions/extensions_tests.gyp:extensions_unittests',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests',
+ '../ipc/ipc.gyp:ipc_tests',
+ '../jingle/jingle.gyp:jingle_unittests',
+ '../net/net.gyp:net_unittests',
+ #"//ppapi/examples",
+ '../third_party/pdfium/samples/samples.gyp:pdfium_test',
+ '../tools/gn/gn.gyp:gn',
+ '../tools/gn/gn.gyp:gn_unittests',
+ '../tools/imagediff/image_diff.gyp:image_diff',
+ '../tools/gn/gn.gyp:gn',
+ '../tools/gn/gn.gyp:gn_unittests',
+ '../ui/app_list/app_list.gyp:app_list_unittests',
+ '../url/url.gyp:url_unittests',
+ ],
+ }],
+ ['OS=="android" or OS=="linux"', {
+ 'dependencies': [
+ '../net/net.gyp:disk_cache_memory_test',
+ ],
+ }],
+ ['chromeos==1', {
+ 'dependencies': [
+ '../chromeos/chromeos.gyp:chromeos_unittests',
+ '../ui/chromeos/ui_chromeos.gyp:ui_chromeos_unittests',
+ ]
+ }],
+ ['chromeos==1 or OS=="win" or OS=="mac"', {
+ 'dependencies': [
+ '../rlz/rlz.gyp:rlz_id',
+ '../rlz/rlz.gyp:rlz_lib',
+ '../rlz/rlz.gyp:rlz_unittests',
+ ],
+ }],
+ ['OS=="android" or OS=="linux" or os_bsd==1', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:core-2-minidump',
+ '../breakpad/breakpad.gyp:microdump_stackwalk',
+ '../breakpad/breakpad.gyp:minidump_dump',
+ '../breakpad/breakpad.gyp:minidump_stackwalk',
+ '../breakpad/breakpad.gyp:symupload',
+ '../third_party/codesighs/codesighs.gyp:nm2tsv',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:breakpad_unittests',
+ '../breakpad/breakpad.gyp:dump_syms#host',
+ '../breakpad/breakpad.gyp:generate_test_dump',
+ '../breakpad/breakpad.gyp:minidump-2-core',
+ '../dbus/dbus.gyp:dbus_test_server',
+ '../dbus/dbus.gyp:dbus_unittests',
+ '../media/cast/cast.gyp:tap_proxy',
+ '../net/net.gyp:disk_cache_memory_test',
+ '../net/net.gyp:flip_in_mem_edsm_server',
+ '../net/net.gyp:flip_in_mem_edsm_server_unittests',
+ '../net/net.gyp:epoll_quic_client',
+ '../net/net.gyp:epoll_quic_server',
+ '../net/net.gyp:hpack_example_generator',
+ '../net/net.gyp:hpack_fuzz_mutator',
+ '../net/net.gyp:hpack_fuzz_wrapper',
+ '../net/net.gyp:net_perftests',
+ '../net/net.gyp:quic_client',
+ '../net/net.gyp:quic_server',
+ '../sandbox/sandbox.gyp:chrome_sandbox',
+ '../sandbox/sandbox.gyp:sandbox_linux_unittests',
+ '../sandbox/sandbox.gyp:sandbox_linux_jni_unittests',
+ '../third_party/sqlite/sqlite.gyp:sqlite_shell',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'dependencies': [
+ '../breakpad/breakpad.gyp:crash_inspector',
+ '../breakpad/breakpad.gyp:dump_syms',
+ '../breakpad/breakpad.gyp:symupload',
+ '../third_party/apple_sample_code/apple_sample_code.gyp:apple_sample_code',
+ '../third_party/molokocacao/molokocacao.gyp:molokocacao',
+
+ # TODO(GYP): remove these when the corresponding root targets work.
+ #"//cc/blink",
+ #"//components/ui/zoom:ui_zoom",
+ #"//content",
+ #"//content/test:test_support",
+ #"//device/battery",
+ #"//device/bluetooth",
+ #"//device/nfc",
+ #"//device/usb",
+ #"//device/vibration",
+ #"//media/blink",
+ #"//pdf",
+ #"//storage/browser",
+ #"//third_party/brotli",
+ #"//third_party/flac",
+ #"//third_party/hunspell",
+ #//third_party/iccjpeg",
+ #"//third_party/libphonenumber",
+ #"//third_party/ots",
+ #"//third_party/qcms",
+ #"//third_party/smhasher:murmurhash3",
+ #"//third_party/speex",
+ #"//third_party/webrtc/system_wrappers",
+ #"//ui/native_theme",
+ #"//ui/snapshot",
+ #"//ui/surface",
+ ],
+ 'dependencies!': [
+ #"//chrome", # TODO(GYP)
+ #"//chrome/test:browser_tests", # TODO(GYP)
+ #"//chrome/test:interactive_ui_tests", # TODO(GYP)
+ #"//chrome/test:sync_integration_tests", # TODO(GYP)
+ #"//chrome/test:unit_tests", # TODO(GYP)
+ #"//components:components_unittests", # TODO(GYP)
+ #"//content/test:content_browsertests", # TODO(GYP)
+ #"//content/test:content_perftests", # TODO(GYP)
+ #"//content/test:content_unittests", # TODO(GYP)
+ #"//extensions:extensions_browsertests", # TODO(GYP)
+ #"//extensions:extensions_unittests", # TODO(GYP)
+ #"//net:net_unittests", # TODO(GYP)
+ #"//third_party/usrsctp", # TODO(GYP)
+ #"//ui/app_list:app_list_unittests", # TODO(GYP)
+ #"//ui/gfx:gfx_unittests", # TODO(GYP)
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../base/base.gyp:pe_image_test',
+ '../chrome/chrome.gyp:crash_service',
+ '../chrome/chrome.gyp:setup_unittests',
+ '../chrome_elf/chrome_elf.gyp:chrome_elf_unittests',
+ '../chrome_elf/chrome_elf.gyp:dll_hash_main',
+ '../components/components.gyp:wifi_test',
+ '../net/net.gyp:quic_client',
+ '../net/net.gyp:quic_server',
+ '../sandbox/sandbox.gyp:pocdll',
+ '../sandbox/sandbox.gyp:sandbox_poc',
+ '../sandbox/sandbox.gyp:sbox_integration_tests',
+ '../sandbox/sandbox.gyp:sbox_unittests',
+ '../sandbox/sandbox.gyp:sbox_validation_tests',
+ '../testing/gtest.gyp:gtest_main',
+ '../third_party/codesighs/codesighs.gyp:msdump2symdb',
+ '../third_party/codesighs/codesighs.gyp:msmap2tsv',
+ '../third_party/pdfium/samples/samples.gyp:pdfium_diff',
+ '../win8/win8.gyp:metro_viewer',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'gyp_only',
+ 'type': 'none',
+ 'conditions': [
+ ['OS=="linux" or OS=="win"', {
+ 'conditions': [
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'dependencies': [
+ '../mojo/mojo_nacl.gyp:monacl_shell', # This should not be built in chromium.
+ ]
+ }],
+ ]
+ }],
+ ],
+ },
+ {
+ 'target_name': 'gyp_remaining',
+ 'type': 'none',
+ 'conditions': [
+ ['remoting==1', {
+ 'dependencies': [
+ '../remoting/app_remoting_webapp.gyp:ar_sample_app', # crbug.com/471916
+ ],
+ }],
+ ['test_isolation_mode!="noop"', {
+ 'dependencies': [
+ '../base/base.gyp:base_unittests_run',
+ '../cc/cc_tests.gyp:cc_unittests_run',
+ '../chrome/chrome.gyp:browser_tests_run',
+ '../chrome/chrome.gyp:chrome_run',
+ '../chrome/chrome.gyp:interactive_ui_tests_run',
+ '../chrome/chrome.gyp:sync_integration_tests_run',
+ '../chrome/chrome.gyp:unit_tests_run',
+ '../components/components_tests.gyp:components_browsertests_run',
+ '../components/components_tests.gyp:components_unittests_run',
+ '../content/content_shell_and_tests.gyp:content_browsertests_run',
+ '../content/content_shell_and_tests.gyp:content_unittests_run',
+ '../courgette/courgette.gyp:courgette_unittests_run',
+ '../crypto/crypto.gyp:crypto_unittests_run',
+ '../google_apis/gcm/gcm.gyp:gcm_unit_tests_run',
+ '../gpu/gpu.gyp:gpu_unittests_run',
+ '../ipc/ipc.gyp:ipc_tests_run',
+ '../media/cast/cast.gyp:cast_unittests_run',
+ '../media/media.gyp:media_unittests_run',
+ '../media/midi/midi.gyp:midi_unittests_run',
+ '../net/net.gyp:net_unittests_run',
+ '../printing/printing.gyp:printing_unittests_run',
+ '../remoting/remoting.gyp:remoting_unittests_run',
+ '../skia/skia_tests.gyp:skia_unittests_run',
+ '../sql/sql.gyp:sql_unittests_run',
+ '../sync/sync.gyp:sync_unit_tests_run',
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_unittests_run',
+ '../third_party/mojo/mojo_edk_tests.gyp:mojo_public_bindings_unittests_run',
+ '../tools/gn/gn.gyp:gn_unittests_run',
+ '../ui/accessibility/accessibility.gyp:accessibility_unittests_run',
+ '../ui/app_list/app_list.gyp:app_list_unittests_run',
+ '../ui/compositor/compositor.gyp:compositor_unittests_run',
+ '../ui/events/events.gyp:events_unittests_run',
+ '../ui/gl/gl_tests.gyp:gl_unittests_run',
+ '../ui/message_center/message_center.gyp:message_center_unittests_run',
+ '../ui/touch_selection/ui_touch_selection.gyp:ui_touch_selection_unittests_run',
+ '../url/url.gyp:url_unittests_run',
+ ],
+ 'conditions': [
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../sandbox/sandbox.gyp:sandbox_linux_unittests_run',
+ '../ui/display/display.gyp:display_unittests_run',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'dependencies': [
+ '../sandbox/sandbox.gyp:sandbox_mac_unittests_run',
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../chrome/chrome.gyp:installer_util_unittests_run',
+ '../chrome/chrome.gyp:setup_unittests_run',
+ '../sandbox/sandbox.gyp:sbox_integration_tests',
+ '../sandbox/sandbox.gyp:sbox_unittests',
+ '../sandbox/sandbox.gyp:sbox_validation_tests',
+ ],
+ }],
+ ['use_ash==1', {
+ 'dependencies': [
+ '../ash/ash.gyp:ash_unittests_run',
+ ],
+ }],
+ ['use_aura==1', {
+ 'dependencies': [
+ '../ui/aura/aura.gyp:aura_unittests_run',
+ '../ui/wm/wm.gyp:wm_unittests_run',
+ ],
+ }],
+ ['enable_webrtc==1 or OS!="android"', {
+ 'dependencies': [
+ '../jingle/jingle.gyp:jingle_unittests_run',
+ ],
+ }],
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'dependencies': [
+ '../components/nacl.gyp:nacl_loader_unittests_run',
+ ]
+ }],
+ ],
+ }],
+ ['use_openssl==1', {
+ 'dependencies': [
+ # TODO(GYP): All of these targets still need to be converted.
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_ecdsa_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_bn_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_pqueue_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_digest_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_cipher_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_hkdf_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_constant_time_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_thread_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_base64_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_gcm_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_bytestring_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_evp_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_dsa_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_rsa_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_hmac_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_aead_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_ssl_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_err_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_lhash_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_pbkdf_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_dh_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_pkcs12_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_example_mul',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_ec_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_bio_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_pkcs7_test',
+ '../third_party/boringssl/boringssl_tests.gyp:boringssl_unittests',
+ ],
+ }],
+ ['chromeos==1', {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:jpeg_decode_accelerator_unittest',
+ '../content/content_shell_and_tests.gyp:video_encode_accelerator_unittest',
+ ],
+ }],
+ ['chromeos==1 and target_arch != "arm"', {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:vaapi_jpeg_decoder_unittest',
+ ],
+ }],
+ ['chromeos==1 or OS=="win" or OS=="android"', {
+ 'dependencies': [
+ '../content/content_shell_and_tests.gyp:video_decode_accelerator_unittest',
+ ],
+ }],
+ ['OS=="linux" or OS=="win"', {
+ 'dependencies': [
+ # TODO(GYP): Figure out which of these run on android/mac/win/ios/etc.
+ '../net/net.gyp:net_docs',
+ '../remoting/remoting.gyp:ar_sample_test_driver',
+
+ # TODO(GYP): in progress - see tfarina.
+ '../third_party/webrtc/tools/tools.gyp:frame_analyzer',
+ '../third_party/webrtc/tools/tools.gyp:rgba_to_i420_converter',
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ # TODO(GYP): All of these targets still need to be converted.
+ '../base/base.gyp:debug_message',
+ '../chrome/chrome.gyp:app_shim',
+ '../chrome/chrome.gyp:gcapi_dll',
+ '../chrome/chrome.gyp:gcapi_test',
+ '../chrome/chrome.gyp:installer_util_unittests',
+ '../chrome/chrome.gyp:pack_policy_templates',
+ '../chrome/chrome.gyp:sb_sigutil',
+ '../chrome/chrome.gyp:setup',
+ '../chrome/installer/mini_installer.gyp:mini_installer',
+ '../chrome/tools/crash_service/caps/caps.gyp:caps',
+ '../cloud_print/gcp20/prototype/gcp20_device.gyp:gcp20_device',
+ '../cloud_print/gcp20/prototype/gcp20_device.gyp:gcp20_device_unittests',
+ '../cloud_print/service/win/service.gyp:cloud_print_service',
+ '../cloud_print/service/win/service.gyp:cloud_print_service_config',
+ '../cloud_print/service/win/service.gyp:cloud_print_service_setup',
+ '../cloud_print/virtual_driver/win/install/virtual_driver_install.gyp:virtual_driver_setup',
+ '../cloud_print/virtual_driver/win/virtual_driver.gyp:gcp_portmon',
+ '../components/test_runner/test_runner.gyp:layout_test_helper',
+ '../content/content_shell_and_tests.gyp:content_shell_crash_service',
+ '../gpu/gpu.gyp:angle_end2end_tests',
+ '../gpu/gpu.gyp:angle_perftests',
+ '../net/net.gyp:net_docs',
+ '../ppapi/ppapi_internal.gyp:ppapi_perftests',
+ '../remoting/remoting.gyp:ar_sample_test_driver',
+ '../remoting/remoting.gyp:remoting_breakpad_tester',
+ '../remoting/remoting.gyp:remoting_console',
+ '../remoting/remoting.gyp:remoting_desktop',
+ '../rlz/rlz.gyp:rlz',
+ '../tools/win/static_initializers/static_initializers.gyp:static_initializers',
+ ],
+ }],
+ ['OS=="win" and win_use_allocator_shim==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator_unittests',
+ ]
+ }],
+ ['OS=="win" and target_arch=="ia32"', {
+ 'dependencies': [
+ # TODO(GYP): All of these targets need to be ported over.
+ '../base/base.gyp:base_win64',
+ '../base/base.gyp:base_i18n_nacl_win64',
+ '../chrome/chrome.gyp:crash_service_win64',
+ '../chrome/chrome.gyp:launcher_support64',
+ '../components/components.gyp:breakpad_win64',
+ '../courgette/courgette.gyp:courgette64',
+ '../crypto/crypto.gyp:crypto_nacl_win64',
+ '../ipc/ipc.gyp:ipc_win64',
+ '../sandbox/sandbox.gyp:sandbox_win64',
+ '../cloud_print/virtual_driver/win/virtual_driver64.gyp:gcp_portmon64',
+ '../cloud_print/virtual_driver/win/virtual_driver64.gyp:virtual_driver_lib64',
+ ],
+ }],
+ ['OS=="win" and target_arch=="ia32" and configuration_policy==1', {
+ 'dependencies': [
+ # TODO(GYP): All of these targets need to be ported over.
+ '../components/components.gyp:policy_win64',
+ ]
+ }],
+ ],
+ },
+ ]
+}
+
diff --git a/build/gn_run_binary.py b/build/gn_run_binary.py
new file mode 100644
index 00000000000..7d83f6136fd
--- /dev/null
+++ b/build/gn_run_binary.py
@@ -0,0 +1,22 @@
+# Copyright 2014 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.
+
+"""Helper script for GN to run an arbitrary binary. See compiled_action.gni.
+
+Run with:
+ python gn_run_binary.py [args ...]
+"""
+
+import sys
+import subprocess
+
+# This script is designed to run binaries produced by the current build. We
+# always prefix it with "./" to avoid picking up system versions that might
+# also be on the path.
+path = './' + sys.argv[1]
+
+# The rest of the arguements are passed directly to the executable.
+args = [path] + sys.argv[2:]
+
+sys.exit(subprocess.call(args))
diff --git a/build/grit_action.gypi b/build/grit_action.gypi
new file mode 100644
index 00000000000..b24f0f8d715
--- /dev/null
+++ b/build/grit_action.gypi
@@ -0,0 +1,71 @@
+# Copyright (c) 2011 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 file is meant to be included into an action to invoke grit in a
+# consistent manner. To use this the following variables need to be
+# defined:
+# grit_grd_file: string: grd file path
+# grit_out_dir: string: the output directory path
+
+# It would be really nice to do this with a rule instead of actions, but it
+# would need to determine inputs and outputs via grit_info on a per-file
+# basis. GYP rules don't currently support that. They could be extended to
+# do this, but then every generator would need to be updated to handle this.
+
+{
+ 'variables': {
+ 'grit_cmd': ['python', '<(DEPTH)/tools/grit/grit.py'],
+ 'grit_resource_ids%': '<(DEPTH)/tools/gritsettings/resource_ids',
+ # This makes it possible to add more defines in specific targets,
+ # instead of build/common.gypi .
+ 'grit_additional_defines%': [],
+ 'grit_rc_header_format%': [],
+ 'grit_whitelist%': '',
+
+ 'conditions': [
+ # These scripts can skip writing generated files if they are identical
+ # to the already existing files, which avoids further build steps, like
+ # recompilation. However, a dependency (earlier build step) having a
+ # newer timestamp than an output (later build step) confuses some build
+ # systems, so only use this on ninja, which explicitly supports this use
+ # case (gyp turns all actions into ninja restat rules).
+ ['"<(GENERATOR)"=="ninja"', {
+ 'write_only_new': '1',
+ }, {
+ 'write_only_new': '0',
+ }],
+ ],
+ },
+ 'conditions': [
+ ['"<(grit_whitelist)"==""', {
+ 'variables': {
+ 'grit_whitelist_flag': [],
+ }
+ }, {
+ 'variables': {
+ 'grit_whitelist_flag': ['-w', '<(grit_whitelist)'],
+ }
+ }]
+ ],
+ 'inputs': [
+ ' 2:
+ specified_includes.add(os.path.realpath(arg[2:]))
+
+ result = []
+ def AddInclude(path):
+ if os.path.realpath(path) not in specified_includes:
+ result.append(path)
+
+ if os.environ.get('GYP_INCLUDE_FIRST') != None:
+ AddInclude(os.path.join(chrome_src, os.environ.get('GYP_INCLUDE_FIRST')))
+
+ # Always include common.gypi.
+ AddInclude(os.path.join(script_dir, 'common.gypi'))
+
+ # Optionally add supplemental .gypi files if present.
+ for supplement in supplemental_files:
+ AddInclude(supplement)
+
+ if os.environ.get('GYP_INCLUDE_LAST') != None:
+ AddInclude(os.path.join(chrome_src, os.environ.get('GYP_INCLUDE_LAST')))
+
+ return result
+
+
+if __name__ == '__main__':
+ # Disabling garbage collection saves about 1 second out of 16 on a Linux
+ # z620 workstation. Since this is a short-lived process it's not a problem to
+ # leak a few cyclyc references in order to spare the CPU cycles for
+ # scanning the heap.
+ import gc
+ gc.disable()
+
+ args = sys.argv[1:]
+
+ use_analyzer = len(args) and args[0] == '--analyzer'
+ if use_analyzer:
+ args.pop(0)
+ os.environ['GYP_GENERATORS'] = 'analyzer'
+ args.append('-Gconfig_path=' + args.pop(0))
+ args.append('-Ganalyzer_output_path=' + args.pop(0))
+
+ if int(os.environ.get('GYP_CHROMIUM_NO_ACTION', 0)):
+ print 'Skipping gyp_chromium due to GYP_CHROMIUM_NO_ACTION env var.'
+ sys.exit(0)
+
+ # Use the Psyco JIT if available.
+ if psyco:
+ psyco.profile()
+ print "Enabled Psyco JIT."
+
+ # Fall back on hermetic python if we happen to get run under cygwin.
+ # TODO(bradnelson): take this out once this issue is fixed:
+ # http://code.google.com/p/gyp/issues/detail?id=177
+ if sys.platform == 'cygwin':
+ import find_depot_tools
+ depot_tools_path = find_depot_tools.add_depot_tools_to_path()
+ python_dir = sorted(glob.glob(os.path.join(depot_tools_path,
+ 'python2*_bin')))[-1]
+ env = os.environ.copy()
+ env['PATH'] = python_dir + os.pathsep + env.get('PATH', '')
+ cmd = [os.path.join(python_dir, 'python.exe')] + sys.argv
+ sys.exit(subprocess.call(cmd, env=env))
+
+ # This could give false positives since it doesn't actually do real option
+ # parsing. Oh well.
+ gyp_file_specified = any(arg.endswith('.gyp') for arg in args)
+
+ gyp_environment.SetEnvironment()
+
+ # If we didn't get a file, check an env var, and then fall back to
+ # assuming 'all.gyp' from the same directory as the script.
+ if not gyp_file_specified:
+ gyp_file = os.environ.get('CHROMIUM_GYP_FILE')
+ if gyp_file:
+ # Note that CHROMIUM_GYP_FILE values can't have backslashes as
+ # path separators even on Windows due to the use of shlex.split().
+ args.extend(shlex.split(gyp_file))
+ else:
+ args.append(os.path.join(script_dir, 'all.gyp'))
+
+ supplemental_includes = GetSupplementalFiles()
+ gyp_vars_dict = GetGypVars(supplemental_includes)
+ # There shouldn't be a circular dependency relationship between .gyp files,
+ # but in Chromium's .gyp files, on non-Mac platforms, circular relationships
+ # currently exist. The check for circular dependencies is currently
+ # bypassed on other platforms, but is left enabled on iOS, where a violation
+ # of the rule causes Xcode to misbehave badly.
+ # TODO(mark): Find and kill remaining circular dependencies, and remove this
+ # option. http://crbug.com/35878.
+ # TODO(tc): Fix circular dependencies in ChromiumOS then add linux2 to the
+ # list.
+ if gyp_vars_dict.get('OS') != 'ios':
+ args.append('--no-circular-check')
+
+ # libtool on Mac warns about duplicate basenames in static libraries, so
+ # they're disallowed in general by gyp. We are lax on this point, so disable
+ # this check other than on Mac. GN does not use static libraries as heavily,
+ # so over time this restriction will mostly go away anyway, even on Mac.
+ # https://code.google.com/p/gyp/issues/detail?id=384
+ if sys.platform != 'darwin':
+ args.append('--no-duplicate-basename-check')
+
+ # We explicitly don't support the make gyp generator (crbug.com/348686). Be
+ # nice and fail here, rather than choking in gyp.
+ if re.search(r'(^|,|\s)make($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
+ print 'Error: make gyp generator not supported (check GYP_GENERATORS).'
+ sys.exit(1)
+
+ # We explicitly don't support the native msvs gyp generator. Be nice and
+ # fail here, rather than generating broken projects.
+ if re.search(r'(^|,|\s)msvs($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
+ print 'Error: msvs gyp generator not supported (check GYP_GENERATORS).'
+ print 'Did you mean to use the `msvs-ninja` generator?'
+ sys.exit(1)
+
+ # If CHROMIUM_GYP_SYNTAX_CHECK is set to 1, it will invoke gyp with --check
+ # to enfore syntax checking.
+ syntax_check = os.environ.get('CHROMIUM_GYP_SYNTAX_CHECK')
+ if syntax_check and int(syntax_check):
+ args.append('--check')
+
+ # TODO(dmikurube): Remove these checks and messages after a while.
+ if ('linux_use_tcmalloc' in gyp_vars_dict or
+ 'android_use_tcmalloc' in gyp_vars_dict):
+ print '*****************************************************************'
+ print '"linux_use_tcmalloc" and "android_use_tcmalloc" are deprecated!'
+ print '-----------------------------------------------------------------'
+ print 'You specify "linux_use_tcmalloc" or "android_use_tcmalloc" in'
+ print 'your GYP_DEFINES. Please switch them into "use_allocator" now.'
+ print 'See http://crbug.com/345554 for the details.'
+ print '*****************************************************************'
+
+ # Automatically turn on crosscompile support for platforms that need it.
+ # (The Chrome OS build sets CC_host / CC_target which implicitly enables
+ # this mode.)
+ if all(('ninja' in os.environ.get('GYP_GENERATORS', ''),
+ gyp_vars_dict.get('OS') in ['android', 'ios'],
+ 'GYP_CROSSCOMPILE' not in os.environ)):
+ os.environ['GYP_CROSSCOMPILE'] = '1'
+ if gyp_vars_dict.get('OS') == 'android':
+ args.append('--check')
+
+ args.extend(
+ ['-I' + i for i in additional_include_files(supplemental_includes, args)])
+
+ args.extend(['-D', 'gyp_output_dir=' + GetOutputDirectory()])
+
+ if not use_analyzer:
+ print 'Updating projects from gyp files...'
+ sys.stdout.flush()
+
+ # Off we go...
+ gyp_rc = gyp.main(args)
+
+ if not use_analyzer:
+ vs2013_runtime_dll_dirs = vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
+ if vs2013_runtime_dll_dirs:
+ x64_runtime, x86_runtime = vs2013_runtime_dll_dirs
+ vs_toolchain.CopyVsRuntimeDlls(
+ os.path.join(chrome_src, GetOutputDirectory()),
+ (x86_runtime, x64_runtime))
+
+ sys.exit(gyp_rc)
diff --git a/build/gyp_chromium.py b/build/gyp_chromium.py
new file mode 100644
index 00000000000..f9e8ac8ed83
--- /dev/null
+++ b/build/gyp_chromium.py
@@ -0,0 +1,18 @@
+# Copyright 2012 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 file is (possibly, depending on python version) imported by
+# gyp_chromium when GYP_PARALLEL=1 and it creates sub-processes
+# through the multiprocessing library.
+
+# Importing in Python 2.6 (fixed in 2.7) on Windows doesn't search for
+# imports that don't end in .py (and aren't directories with an
+# __init__.py). This wrapper makes "import gyp_chromium" work with
+# those old versions and makes it possible to execute gyp_chromium.py
+# directly on Windows where the extension is useful.
+
+import os
+
+path = os.path.abspath(os.path.split(__file__)[0])
+execfile(os.path.join(path, 'gyp_chromium'))
diff --git a/build/gyp_chromium_test.py b/build/gyp_chromium_test.py
new file mode 100755
index 00000000000..0c0e479d1c3
--- /dev/null
+++ b/build/gyp_chromium_test.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+import os
+import sys
+import unittest
+
+SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
+SRC_DIR = os.path.dirname(SCRIPT_DIR)
+
+sys.path.append(os.path.join(SRC_DIR, 'third_party', 'pymock'))
+
+import mock
+
+# TODO(sbc): Make gyp_chromium more testable by putting the code in
+# a .py file.
+gyp_chromium = __import__('gyp_chromium')
+
+
+class TestGetOutputDirectory(unittest.TestCase):
+ @mock.patch('os.environ', {})
+ @mock.patch('sys.argv', [__file__])
+ def testDefaultValue(self):
+ self.assertEqual(gyp_chromium.GetOutputDirectory(), 'out')
+
+ @mock.patch('os.environ', {'GYP_GENERATOR_FLAGS': 'output_dir=envfoo'})
+ @mock.patch('sys.argv', [__file__])
+ def testEnvironment(self):
+ self.assertEqual(gyp_chromium.GetOutputDirectory(), 'envfoo')
+
+ @mock.patch('os.environ', {'GYP_GENERATOR_FLAGS': 'output_dir=envfoo'})
+ @mock.patch('sys.argv', [__file__, '-Goutput_dir=cmdfoo'])
+ def testGFlagOverridesEnv(self):
+ self.assertEqual(gyp_chromium.GetOutputDirectory(), 'cmdfoo')
+
+ @mock.patch('os.environ', {})
+ @mock.patch('sys.argv', [__file__, '-G', 'output_dir=foo'])
+ def testGFlagWithSpace(self):
+ self.assertEqual(gyp_chromium.GetOutputDirectory(), 'foo')
+
+
+class TestGetGypVars(unittest.TestCase):
+ @mock.patch('os.environ', {})
+ def testDefault(self):
+ self.assertEqual(gyp_chromium.GetGypVars([]), {})
+
+ @mock.patch('os.environ', {})
+ @mock.patch('sys.argv', [__file__, '-D', 'foo=bar'])
+ def testDFlags(self):
+ self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': 'bar'})
+
+ @mock.patch('os.environ', {})
+ @mock.patch('sys.argv', [__file__, '-D', 'foo'])
+ def testDFlagsNoValue(self):
+ self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': '1'})
+
+ @mock.patch('os.environ', {})
+ @mock.patch('sys.argv', [__file__, '-D', 'foo=bar', '-Dbaz'])
+ def testDFlagMulti(self):
+ self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': 'bar', 'baz': '1'})
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/gyp_environment.py b/build/gyp_environment.py
new file mode 100644
index 00000000000..fb50645d56f
--- /dev/null
+++ b/build/gyp_environment.py
@@ -0,0 +1,33 @@
+# Copyright 2014 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.
+
+"""
+Sets up various automatic gyp environment variables. These are used by
+gyp_chromium and landmines.py which run at different stages of runhooks. To
+make sure settings are consistent between them, all setup should happen here.
+"""
+
+import gyp_helper
+import os
+import sys
+import vs_toolchain
+
+def SetEnvironment():
+ """Sets defaults for GYP_* variables."""
+ gyp_helper.apply_chromium_gyp_env()
+
+ # Default to ninja on linux and windows, but only if no generator has
+ # explicitly been set.
+ # Also default to ninja on mac, but only when not building chrome/ios.
+ # . -f / --format has precedence over the env var, no need to check for it
+ # . set the env var only if it hasn't been set yet
+ # . chromium.gyp_env has been applied to os.environ at this point already
+ if sys.platform.startswith(('linux', 'win', 'freebsd')) and \
+ not os.environ.get('GYP_GENERATORS'):
+ os.environ['GYP_GENERATORS'] = 'ninja'
+ elif sys.platform == 'darwin' and not os.environ.get('GYP_GENERATORS') and \
+ not 'OS=ios' in os.environ.get('GYP_DEFINES', []):
+ os.environ['GYP_GENERATORS'] = 'ninja'
+
+ vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
diff --git a/build/gyp_helper.py b/build/gyp_helper.py
new file mode 100644
index 00000000000..c840f2d6dc2
--- /dev/null
+++ b/build/gyp_helper.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2012 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 file helps gyp_chromium and landmines correctly set up the gyp
+# environment from chromium.gyp_env on disk
+
+import os
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+CHROME_SRC = os.path.dirname(SCRIPT_DIR)
+
+
+def apply_gyp_environment_from_file(file_path):
+ """Reads in a *.gyp_env file and applies the valid keys to os.environ."""
+ if not os.path.exists(file_path):
+ return
+ with open(file_path, 'rU') as f:
+ file_contents = f.read()
+ try:
+ file_data = eval(file_contents, {'__builtins__': None}, None)
+ except SyntaxError, e:
+ e.filename = os.path.abspath(file_path)
+ raise
+ supported_vars = (
+ 'CC',
+ 'CC_wrapper',
+ 'CC.host_wrapper',
+ 'CHROMIUM_GYP_FILE',
+ 'CHROMIUM_GYP_SYNTAX_CHECK',
+ 'CXX',
+ 'CXX_wrapper',
+ 'CXX.host_wrapper',
+ 'GYP_DEFINES',
+ 'GYP_GENERATOR_FLAGS',
+ 'GYP_CROSSCOMPILE',
+ 'GYP_GENERATOR_OUTPUT',
+ 'GYP_GENERATORS',
+ 'GYP_INCLUDE_FIRST',
+ 'GYP_INCLUDE_LAST',
+ 'GYP_MSVS_VERSION',
+ )
+ for var in supported_vars:
+ file_val = file_data.get(var)
+ if file_val:
+ if var in os.environ:
+ behavior = 'replaces'
+ if var == 'GYP_DEFINES':
+ result = file_val + ' ' + os.environ[var]
+ behavior = 'merges with, and individual components override,'
+ else:
+ result = os.environ[var]
+ print 'INFO: Environment value for "%s" %s value in %s' % (
+ var, behavior, os.path.abspath(file_path)
+ )
+ string_padding = max(len(var), len(file_path), len('result'))
+ print ' %s: %s' % (var.rjust(string_padding), os.environ[var])
+ print ' %s: %s' % (file_path.rjust(string_padding), file_val)
+ os.environ[var] = result
+ else:
+ os.environ[var] = file_val
+
+
+def apply_chromium_gyp_env():
+ if 'SKIP_CHROMIUM_GYP_ENV' not in os.environ:
+ # Update the environment based on chromium.gyp_env
+ path = os.path.join(os.path.dirname(CHROME_SRC), 'chromium.gyp_env')
+ apply_gyp_environment_from_file(path)
diff --git a/build/gypi_to_gn.py b/build/gypi_to_gn.py
new file mode 100644
index 00000000000..a107f94fcaf
--- /dev/null
+++ b/build/gypi_to_gn.py
@@ -0,0 +1,167 @@
+# Copyright 2014 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.
+
+"""Converts a given gypi file to a python scope and writes the result to stdout.
+
+It is assumed that the file contains a toplevel dictionary, and this script
+will return that dictionary as a GN "scope" (see example below). This script
+does not know anything about GYP and it will not expand variables or execute
+conditions.
+
+It will strip conditions blocks.
+
+A variables block at the top level will be flattened so that the variables
+appear in the root dictionary. This way they can be returned to the GN code.
+
+Say your_file.gypi looked like this:
+ {
+ 'sources': [ 'a.cc', 'b.cc' ],
+ 'defines': [ 'ENABLE_DOOM_MELON' ],
+ }
+
+You would call it like this:
+ gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("your_file.gypi") ],
+ "scope",
+ [ "your_file.gypi" ])
+
+Notes:
+ - The rebase_path call converts the gypi file from being relative to the
+ current build file to being system absolute for calling the script, which
+ will have a different current directory than this file.
+
+ - The "scope" parameter tells GN to interpret the result as a series of GN
+ variable assignments.
+
+ - The last file argument to exec_script tells GN that the given file is a
+ dependency of the build so Ninja can automatically re-run GN if the file
+ changes.
+
+Read the values into a target like this:
+ component("mycomponent") {
+ sources = gypi_values.sources
+ defines = gypi_values.defines
+ }
+
+Sometimes your .gypi file will include paths relative to a different
+directory than the current .gn file. In this case, you can rebase them to
+be relative to the current directory.
+ sources = rebase_path(gypi_values.sources, ".",
+ "//path/gypi/input/values/are/relative/to")
+
+This script will tolerate a 'variables' in the toplevel dictionary or not. If
+the toplevel dictionary just contains one item called 'variables', it will be
+collapsed away and the result will be the contents of that dictinoary. Some
+.gypi files are written with or without this, depending on how they expect to
+be embedded into a .gyp file.
+
+This script also has the ability to replace certain substrings in the input.
+Generally this is used to emulate GYP variable expansion. If you passed the
+argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
+the input will be replaced with "bar":
+
+ gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("your_file.gypi"),
+ "--replace=<(foo)=bar"],
+ "scope",
+ [ "your_file.gypi" ])
+
+"""
+
+import gn_helpers
+from optparse import OptionParser
+import sys
+
+def LoadPythonDictionary(path):
+ file_string = open(path).read()
+ try:
+ file_data = eval(file_string, {'__builtins__': None}, None)
+ except SyntaxError, e:
+ e.filename = path
+ raise
+ except Exception, e:
+ raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
+
+ assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
+
+ # Flatten any variables to the top level.
+ if 'variables' in file_data:
+ file_data.update(file_data['variables'])
+ del file_data['variables']
+
+ # Strip any conditions.
+ if 'conditions' in file_data:
+ del file_data['conditions']
+ if 'target_conditions' in file_data:
+ del file_data['target_conditions']
+
+ # Strip targets in the toplevel, since some files define these and we can't
+ # slurp them in.
+ if 'targets' in file_data:
+ del file_data['targets']
+
+ return file_data
+
+
+def ReplaceSubstrings(values, search_for, replace_with):
+ """Recursively replaces substrings in a value.
+
+ Replaces all substrings of the "search_for" with "repace_with" for all
+ strings occurring in "values". This is done by recursively iterating into
+ lists as well as the keys and values of dictionaries."""
+ if isinstance(values, str):
+ return values.replace(search_for, replace_with)
+
+ if isinstance(values, list):
+ return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
+
+ if isinstance(values, dict):
+ # For dictionaries, do the search for both the key and values.
+ result = {}
+ for key, value in values.items():
+ new_key = ReplaceSubstrings(key, search_for, replace_with)
+ new_value = ReplaceSubstrings(value, search_for, replace_with)
+ result[new_key] = new_value
+ return result
+
+ # Assume everything else is unchanged.
+ return values
+
+def main():
+ parser = OptionParser()
+ parser.add_option("-r", "--replace", action="append",
+ help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ raise Exception("Need one argument which is the .gypi file to read.")
+
+ data = LoadPythonDictionary(args[0])
+ if options.replace:
+ # Do replacements for all specified patterns.
+ for replace in options.replace:
+ split = replace.split('=')
+ # Allow "foo=" to replace with nothing.
+ if len(split) == 1:
+ split.append('')
+ assert len(split) == 2, "Replacement must be of the form 'key=value'."
+ data = ReplaceSubstrings(data, split[0], split[1])
+
+ # Sometimes .gypi files use the GYP syntax with percents at the end of the
+ # variable name (to indicate not to overwrite a previously-defined value):
+ # 'foo%': 'bar',
+ # Convert these to regular variables.
+ for key in data:
+ if len(key) > 1 and key[len(key) - 1] == '%':
+ data[key[:-1]] = data[key]
+ del data[key]
+
+ print gn_helpers.ToGNString(data)
+
+if __name__ == '__main__':
+ try:
+ main()
+ except Exception, e:
+ print str(e)
+ sys.exit(1)
diff --git a/build/host_jar.gypi b/build/host_jar.gypi
new file mode 100644
index 00000000000..a47f6bbdd99
--- /dev/null
+++ b/build/host_jar.gypi
@@ -0,0 +1,146 @@
+# Copyright 2014 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 file is meant to be included into a target to provide a rule to build
+# a JAR file for use on a host in a consistent manner. If a main class is
+# specified, this file will also generate an executable to run the jar in the
+# output folder's /bin/ directory.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my_jar',
+# 'type': 'none',
+# 'variables': {
+# 'src_paths': [
+# 'path/to/directory',
+# 'path/to/other/directory',
+# 'path/to/individual_file.java',
+# ...
+# ],
+# },
+# 'includes': [ 'path/to/this/gypi/file' ],
+# }
+#
+# Required variables:
+# src_paths - A list of all paths containing java files that should be
+# included in the jar. Paths can be either directories or files.
+# Optional/automatic variables:
+# excluded_src_paths - A list of all paths that should be excluded from
+# the jar.
+# generated_src_dirs - Directories containing additional .java files
+# generated at build time.
+# input_jars_paths - A list of paths to the jars that should be included
+# in the classpath.
+# main_class - The class containing the main() function that should be called
+# when running the jar file.
+# jar_excluded_classes - A list of .class files that should be excluded
+# from the jar.
+
+{
+ 'dependencies': [
+ '<(DEPTH)/build/android/setup.gyp:build_output_dirs',
+ ],
+ 'variables': {
+ 'classes_dir': '<(intermediate_dir)/classes',
+ 'excluded_src_paths': [],
+ 'generated_src_dirs': [],
+ 'input_jars_paths': [],
+ 'intermediate_dir': '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)',
+ 'jar_dir': '<(PRODUCT_DIR)/lib.java',
+ 'jar_excluded_classes': [],
+ 'jar_name': '<(_target_name).jar',
+ 'jar_path': '<(jar_dir)/<(jar_name)',
+ 'main_class%': '',
+ 'stamp': '<(intermediate_dir)/jar.stamp',
+ 'enable_errorprone%': '0',
+ 'errorprone_exe_path': '<(PRODUCT_DIR)/bin.java/chromium_errorprone',
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'input_jars_paths': ['<(jar_path)']
+ },
+ },
+ 'actions': [
+ {
+ 'action_name': 'javac_<(_target_name)',
+ 'message': 'Compiling <(_target_name) java sources',
+ 'variables': {
+ 'extra_args': [],
+ 'extra_inputs': [],
+ 'java_sources': [ '(main_class)']
+ }],
+ ['enable_errorprone == 1', {
+ 'extra_inputs': [
+ '<(errorprone_exe_path)',
+ ],
+ 'extra_args': [ '--use-errorprone-path=<(errorprone_exe_path)' ],
+ }],
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/javac.py',
+ '^@(java_sources)',
+ '>@(input_jars_paths)',
+ '<@(extra_inputs)',
+ ],
+ 'outputs': [
+ '<(jar_path)',
+ '<(stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/javac.py',
+ '--classpath=>(input_jars_paths)',
+ '--src-gendirs=>(generated_src_dirs)',
+ '--chromium-code=<(chromium_code)',
+ '--stamp=<(stamp)',
+ '--jar-path=<(jar_path)',
+ '<@(extra_args)',
+ '^@(java_sources)',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['main_class != ""', {
+ 'actions': [
+ {
+ 'action_name': 'create_java_binary_script_<(_target_name)',
+ 'message': 'Creating java binary script <(_target_name)',
+ 'variables': {
+ 'output': '<(PRODUCT_DIR)/bin/<(_target_name)',
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/create_java_binary_script.py',
+ '<(jar_path)',
+ ],
+ 'outputs': [
+ '<(output)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/create_java_binary_script.py',
+ '--classpath=>(input_jars_paths)',
+ '--jar-path=<(jar_path)',
+ '--output=<(output)',
+ '--main-class=>(main_class)',
+ ]
+ }
+ ]
+ }],
+ ['enable_errorprone == 1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/errorprone/errorprone.gyp:chromium_errorprone',
+ ],
+ }],
+ ]
+}
+
diff --git a/build/host_prebuilt_jar.gypi b/build/host_prebuilt_jar.gypi
new file mode 100644
index 00000000000..feed5caebb5
--- /dev/null
+++ b/build/host_prebuilt_jar.gypi
@@ -0,0 +1,50 @@
+# Copyright 2014 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 file is meant to be included into a target to provide a rule to
+# copy a prebuilt JAR for use on a host to the output directory.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my_prebuilt_jar',
+# 'type': 'none',
+# 'variables': {
+# 'jar_path': 'path/to/prebuilt.jar',
+# },
+# 'includes': [ 'path/to/this/gypi/file' ],
+# }
+#
+# Required variables:
+# jar_path - The path to the prebuilt jar.
+
+{
+ 'dependencies': [
+ ],
+ 'variables': {
+ 'dest_path': '<(PRODUCT_DIR)/lib.java/<(_target_name).jar',
+ 'src_path': '<(jar_path)',
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'input_jars_paths': [
+ '<(dest_path)',
+ ]
+ },
+ },
+ 'actions': [
+ {
+ 'action_name': 'copy_prebuilt_jar',
+ 'message': 'Copy <(src_path) to <(dest_path)',
+ 'inputs': [
+ '<(src_path)',
+ ],
+ 'outputs': [
+ '<(dest_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/cp.py', '<(src_path)', '<(dest_path)',
+ ],
+ }
+ ]
+}
diff --git a/build/install-android-sdks.sh b/build/install-android-sdks.sh
new file mode 100755
index 00000000000..1119b7d7fdd
--- /dev/null
+++ b/build/install-android-sdks.sh
@@ -0,0 +1,20 @@
+#!/bin/bash -e
+
+# Copyright (c) 2012 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.
+
+# Script to install SDKs needed to build chromium on android.
+# See http://code.google.com/p/chromium/wiki/AndroidBuildInstructions
+
+echo 'checking for sdk packages install'
+# Use absolute path to call 'android' so script can be run from any directory.
+cwd=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+# Get the SDK extras packages to install from the DEPS file 'sdkextras' hook.
+packages="$(python ${cwd}/get_sdk_extras_packages.py)"
+if [[ -n "${packages}" ]]; then
+ ${cwd}/../third_party/android_tools/sdk/tools/android update sdk --no-ui \
+ --filter ${packages}
+fi
+
+echo "install-android-sdks.sh complete."
diff --git a/build/install-build-deps-android.sh b/build/install-build-deps-android.sh
new file mode 100755
index 00000000000..cf873811131
--- /dev/null
+++ b/build/install-build-deps-android.sh
@@ -0,0 +1,100 @@
+#!/bin/bash -e
+
+# Copyright (c) 2012 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.
+
+# Script to install everything needed to build chromium on android, including
+# items requiring sudo privileges.
+# See http://code.google.com/p/chromium/wiki/AndroidBuildInstructions
+
+# This script installs the sun-java6 packages (bin, jre and jdk). Sun requires
+# a license agreement, so upon installation it will prompt the user. To get
+# past the curses-based dialog press TAB TAB to agree.
+
+args="$@"
+if test "$1" = "--skip-sdk-packages"; then
+ skip_inst_sdk_packages=1
+ args="${@:2}"
+else
+ skip_inst_sdk_packages=0
+fi
+
+if ! uname -m | egrep -q "i686|x86_64"; then
+ echo "Only x86 architectures are currently supported" >&2
+ exit
+fi
+
+# Install first the default Linux build deps.
+"$(dirname "${BASH_SOURCE[0]}")/install-build-deps.sh" \
+ --no-syms --lib32 --no-arm --no-chromeos-fonts --no-nacl --no-prompt "${args}"
+
+lsb_release=$(lsb_release --codename --short)
+
+# The temporary directory used to store output of update-java-alternatives
+TEMPDIR=$(mktemp -d)
+cleanup() {
+ local status=${?}
+ trap - EXIT
+ rm -rf "${TEMPDIR}"
+ exit ${status}
+}
+trap cleanup EXIT
+
+# Fix deps
+sudo apt-get -f install
+
+# Install deps
+# This step differs depending on what Ubuntu release we are running
+# on since the package names are different, and Sun's Java must
+# be installed manually on late-model versions.
+
+# common
+sudo apt-get -y install lighttpd python-pexpect xvfb x11-utils
+
+# Some binaries in the Android SDK require 32-bit libraries on the host.
+# See https://developer.android.com/sdk/installing/index.html?pkg=tools
+if [[ $lsb_release == "precise" ]]; then
+ sudo apt-get -y install ia32-libs
+else
+ sudo apt-get -y install libncurses5:i386 libstdc++6:i386 zlib1g:i386
+fi
+
+sudo apt-get -y install ant
+
+# Install openjdk and openjre 7 stuff
+sudo apt-get -y install openjdk-7-jre openjdk-7-jdk
+
+# Switch version of Java to openjdk 7.
+# Some Java plugins (e.g. for firefox, mozilla) are not required to build, and
+# thus are treated only as warnings. Any errors in updating java alternatives
+# which are not '*-javaplugin.so' will cause errors and stop the script from
+# completing successfully.
+if ! sudo update-java-alternatives -s java-1.7.0-openjdk-amd64 \
+ >& "${TEMPDIR}"/update-java-alternatives.out
+then
+ # Check that there are the expected javaplugin.so errors for the update
+ if grep 'javaplugin.so' "${TEMPDIR}"/update-java-alternatives.out >& \
+ /dev/null
+ then
+ # Print as warnings all the javaplugin.so errors
+ echo 'WARNING: java-6-sun has no alternatives for the following plugins:'
+ grep 'javaplugin.so' "${TEMPDIR}"/update-java-alternatives.out
+ fi
+ # Check if there are any errors that are not javaplugin.so
+ if grep -v 'javaplugin.so' "${TEMPDIR}"/update-java-alternatives.out \
+ >& /dev/null
+ then
+ # If there are non-javaplugin.so errors, treat as errors and exit
+ echo 'ERRORS: Failed to update alternatives for java-6-sun:'
+ grep -v 'javaplugin.so' "${TEMPDIR}"/update-java-alternatives.out
+ exit 1
+ fi
+fi
+
+# Install SDK packages for android
+if test "$skip_inst_sdk_packages" != 1; then
+ "$(dirname "${BASH_SOURCE[0]}")/install-android-sdks.sh"
+fi
+
+echo "install-build-deps-android.sh complete."
diff --git a/build/install-build-deps.py b/build/install-build-deps.py
new file mode 100755
index 00000000000..7cc3760df75
--- /dev/null
+++ b/build/install-build-deps.py
@@ -0,0 +1,430 @@
+#!/usr/bin/env python
+# Copyright 2015 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.
+
+import argparse
+import operator
+import os
+import platform
+import re
+import subprocess
+import sys
+
+
+SUPPORTED_UBUNTU_VERSIONS = (
+ {'number': '12.04', 'codename': 'precise'},
+ {'number': '14.04', 'codename': 'trusty'},
+ {'number': '14.10', 'codename': 'utopic'},
+ {'number': '15.04', 'codename': 'vivid'},
+)
+
+
+# Packages needed for chromeos only.
+_packages_chromeos_dev = (
+ 'libbluetooth-dev',
+ 'libxkbcommon-dev',
+ 'realpath',
+)
+
+
+# Packages needed for development.
+_packages_dev = (
+ 'apache2.2-bin',
+ 'bison',
+ 'cdbs',
+ 'curl',
+ 'devscripts',
+ 'dpkg-dev',
+ 'elfutils',
+ 'fakeroot',
+ 'flex',
+ 'fonts-thai-tlwg',
+ 'g++',
+ 'git-core',
+ 'git-svn',
+ 'gperf',
+ 'language-pack-da',
+ 'language-pack-fr',
+ 'language-pack-he',
+ 'language-pack-zh-hant',
+ 'libapache2-mod-php5',
+ 'libasound2-dev',
+ 'libav-tools',
+ 'libbrlapi-dev',
+ 'libbz2-dev',
+ 'libcairo2-dev',
+ 'libcap-dev',
+ 'libcups2-dev',
+ 'libcurl4-gnutls-dev',
+ 'libdrm-dev',
+ 'libelf-dev',
+ 'libexif-dev',
+ 'libgconf2-dev',
+ 'libglib2.0-dev',
+ 'libglu1-mesa-dev',
+ 'libgnome-keyring-dev',
+ 'libgtk2.0-dev',
+ 'libkrb5-dev',
+ 'libnspr4-dev',
+ 'libnss3-dev',
+ 'libpam0g-dev',
+ 'libpci-dev',
+ 'libpulse-dev',
+ 'libsctp-dev',
+ 'libspeechd-dev',
+ 'libsqlite3-dev',
+ 'libssl-dev',
+ 'libudev-dev',
+ 'libwww-perl',
+ 'libxslt1-dev',
+ 'libxss-dev',
+ 'libxt-dev',
+ 'libxtst-dev',
+ 'openbox',
+ 'patch',
+ 'perl',
+ 'php5-cgi',
+ 'pkg-config',
+ 'python',
+ 'python-cherrypy3',
+ 'python-crypto',
+ 'python-dev',
+ 'python-numpy',
+ 'python-opencv',
+ 'python-openssl',
+ 'python-psutil',
+ 'python-yaml',
+ 'rpm',
+ 'ruby',
+ 'subversion',
+ 'ttf-dejavu-core',
+ 'ttf-indic-fonts',
+ 'ttf-kochi-gothic',
+ 'ttf-kochi-mincho',
+ 'wdiff',
+ 'xfonts-mathml',
+ 'zip',
+)
+
+
+# Run-time libraries required by chromeos only.
+_packages_chromeos_lib = (
+ 'libbz2-1.0',
+ 'libpulse0',
+)
+
+
+# Full list of required run-time libraries.
+_packages_lib = (
+ 'libasound2',
+ 'libatk1.0-0',
+ 'libc6',
+ 'libcairo2',
+ 'libcap2',
+ 'libcups2',
+ 'libexif12',
+ 'libexpat1',
+ 'libfontconfig1',
+ 'libfreetype6',
+ 'libglib2.0-0',
+ 'libgnome-keyring0',
+ 'libgtk2.0-0',
+ 'libpam0g',
+ 'libpango1.0-0',
+ 'libpci3',
+ 'libpcre3',
+ 'libpixman-1-0',
+ 'libpng12-0',
+ 'libspeechd2',
+ 'libsqlite3-0',
+ 'libstdc++6',
+ 'libx11-6',
+ 'libxau6',
+ 'libxcb1',
+ 'libxcomposite1',
+ 'libxcursor1',
+ 'libxdamage1',
+ 'libxdmcp6',
+ 'libxext6',
+ 'libxfixes3',
+ 'libxi6',
+ 'libxinerama1',
+ 'libxrandr2',
+ 'libxrender1',
+ 'libxtst6',
+ 'zlib1g',
+)
+
+
+# Debugging symbols for all of the run-time libraries.
+_packages_dbg = (
+ 'libatk1.0-dbg',
+ 'libc6-dbg',
+ 'libcairo2-dbg',
+ 'libfontconfig1-dbg',
+ 'libglib2.0-0-dbg',
+ 'libgtk2.0-0-dbg',
+ 'libpango1.0-0-dbg',
+ 'libpcre3-dbg',
+ 'libpixman-1-0-dbg',
+ 'libsqlite3-0-dbg',
+ 'libx11-6-dbg',
+ 'libxau6-dbg',
+ 'libxcb1-dbg',
+ 'libxcomposite1-dbg',
+ 'libxcursor1-dbg',
+ 'libxdamage1-dbg',
+ 'libxdmcp6-dbg',
+ 'libxext6-dbg',
+ 'libxfixes3-dbg',
+ 'libxi6-dbg',
+ 'libxinerama1-dbg',
+ 'libxrandr2-dbg',
+ 'libxrender1-dbg',
+ 'libxtst6-dbg',
+ 'zlib1g-dbg',
+)
+
+
+# 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf.
+_packages_lib32 = (
+ 'linux-libc-dev:i386',
+)
+
+
+# arm cross toolchain packages needed to build chrome on armhf.
+_packages_arm = (
+ 'g++-arm-linux-gnueabihf',
+ 'libc6-dev-armhf-cross',
+ 'linux-libc-dev-armhf-cross',
+)
+
+
+# Packages to build NaCl, its toolchains, and its ports.
+_packages_naclports = (
+ 'ant',
+ 'autoconf',
+ 'bison',
+ 'cmake',
+ 'gawk',
+ 'intltool',
+ 'xsltproc',
+ 'xutils-dev',
+)
+_packages_nacl = (
+ 'g++-mingw-w64-i686',
+ 'lib32ncurses5-dev',
+ 'lib32z1-dev',
+ 'libasound2:i386',
+ 'libcap2:i386',
+ 'libelf-dev:i386',
+ 'libexif12:i386',
+ 'libfontconfig1:i386',
+ 'libgconf-2-4:i386',
+ 'libglib2.0-0:i386',
+ 'libgpm2:i386',
+ 'libgtk2.0-0:i386',
+ 'libncurses5:i386',
+ 'libnss3:i386',
+ 'libpango1.0-0:i386',
+ 'libssl1.0.0:i386',
+ 'libtinfo-dev',
+ 'libtinfo-dev:i386',
+ 'libtool',
+ 'libxcomposite1:i386',
+ 'libxcursor1:i386',
+ 'libxdamage1:i386',
+ 'libxi6:i386',
+ 'libxrandr2:i386',
+ 'libxss1:i386',
+ 'libxtst6:i386',
+ 'texinfo',
+ 'xvfb',
+)
+
+
+def is_userland_64_bit():
+ return platform.architecture()[0] == '64bit'
+
+
+def package_exists(pkg):
+ return pkg in subprocess.check_output(['apt-cache', 'pkgnames']).splitlines()
+
+
+def lsb_release_short_codename():
+ return subprocess.check_output(
+ ['lsb_release', '--codename', '--short']).strip()
+
+
+def write_error(message):
+ sys.stderr.write('ERROR: %s\n' % message)
+ sys.stderr.flush()
+
+
+def nonfatal_get_output(*popenargs, **kwargs):
+ process = subprocess.Popen(
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, *popenargs, **kwargs)
+ stdout, stderr = process.communicate()
+ retcode = process.poll()
+ return retcode, stdout, stderr
+
+
+def compute_dynamic_package_lists():
+ global _packages_arm
+ global _packages_dbg
+ global _packages_dev
+ global _packages_lib
+ global _packages_lib32
+ global _packages_nacl
+
+ if is_userland_64_bit():
+ # 64-bit systems need a minimum set of 32-bit compat packages
+ # for the pre-built NaCl binaries.
+ _packages_dev += (
+ 'lib32gcc1',
+ 'lib32stdc++6',
+ 'libc6-i386',
+ )
+
+ # When cross building for arm/Android on 64-bit systems the host binaries
+ # that are part of v8 need to be compiled with -m32 which means
+ # that basic multilib support is needed.
+ # gcc-multilib conflicts with the arm cross compiler (at least in trusty)
+ # but g++-X.Y-multilib gives us the 32-bit support that we need. Find out
+ # the appropriate value of X and Y by seeing what version the current
+ # distribution's g++-multilib package depends on.
+ output = subprocess.check_output(['apt-cache', 'depends', 'g++-multilib'])
+ multilib_package = re.search(r'g\+\+-[0-9.]+-multilib', output).group()
+ _packages_lib32 += (multilib_package,)
+
+ lsb_codename = lsb_release_short_codename()
+
+ # Find the proper version of libstdc++6-4.x-dbg.
+ if lsb_codename == 'precise':
+ _packages_dbg += ('libstdc++6-4.6-dbg',)
+ elif lsb_codename == 'trusty':
+ _packages_dbg += ('libstdc++6-4.8-dbg',)
+ else:
+ _packages_dbg += ('libstdc++6-4.9-dbg',)
+
+ # Work around for dependency issue Ubuntu/Trusty: http://crbug.com/435056 .
+ if lsb_codename == 'trusty':
+ _packages_arm += (
+ 'g++-4.8-multilib-arm-linux-gnueabihf',
+ 'gcc-4.8-multilib-arm-linux-gnueabihf',
+ )
+
+ # Find the proper version of libgbm-dev. We can't just install libgbm-dev as
+ # it depends on mesa, and only one version of mesa can exists on the system.
+ # Hence we must match the same version or this entire script will fail.
+ mesa_variant = ''
+ for variant in ('-lts-trusty', '-lts-utopic'):
+ rc, stdout, stderr = nonfatal_get_output(
+ ['dpkg-query', '-Wf\'{Status}\'', 'libgl1-mesa-glx' + variant])
+ if 'ok installed' in output:
+ mesa_variant = variant
+ _packages_dev += (
+ 'libgbm-dev' + mesa_variant,
+ 'libgl1-mesa-dev' + mesa_variant,
+ 'libgles2-mesa-dev' + mesa_variant,
+ 'mesa-common-dev' + mesa_variant,
+ )
+
+ if package_exists('ttf-mscorefonts-installer'):
+ _packages_dev += ('ttf-mscorefonts-installer',)
+ else:
+ _packages_dev += ('msttcorefonts',)
+
+ if package_exists('libnspr4-dbg'):
+ _packages_dbg += ('libnspr4-dbg', 'libnss3-dbg')
+ _packages_lib += ('libnspr4', 'libnss3')
+ else:
+ _packages_dbg += ('libnspr4-0d-dbg', 'libnss3-1d-dbg')
+ _packages_lib += ('libnspr4-0d', 'libnss3-1d')
+
+ if package_exists('libjpeg-dev'):
+ _packages_dev += ('libjpeg-dev',)
+ else:
+ _packages_dev += ('libjpeg62-dev',)
+
+ if package_exists('libudev1'):
+ _packages_dev += ('libudev1',)
+ _packages_nacl += ('libudev1:i386',)
+ else:
+ _packages_dev += ('libudev0',)
+ _packages_nacl += ('libudev0:i386',)
+
+ if package_exists('libbrlapi0.6'):
+ _packages_dev += ('libbrlapi0.6',)
+ else:
+ _packages_dev += ('libbrlapi0.5',)
+
+ # Some packages are only needed if the distribution actually supports
+ # installing them.
+ if package_exists('appmenu-gtk'):
+ _packages_lib += ('appmenu-gtk',)
+
+ _packages_dev += _packages_chromeos_dev
+ _packages_lib += _packages_chromeos_lib
+ _packages_nacl += _packages_naclports
+
+
+def quick_check(packages):
+ rc, stdout, stderr = nonfatal_get_output([
+ 'dpkg-query', '-W', '-f', '${PackageSpec}:${Status}\n'] + list(packages))
+ if rc == 0 and not stderr:
+ return 0
+ print stderr
+ return 1
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--quick-check', action='store_true',
+ help='quickly try to determine if dependencies are '
+ 'installed (this avoids interactive prompts and '
+ 'sudo commands so might not be 100% accurate)')
+ parser.add_argument('--unsupported', action='store_true',
+ help='attempt installation even on unsupported systems')
+ args = parser.parse_args(argv)
+
+ lsb_codename = lsb_release_short_codename()
+ if not args.unsupported and not args.quick_check:
+ if lsb_codename not in map(
+ operator.itemgetter('codename'), SUPPORTED_UBUNTU_VERSIONS):
+ supported_ubuntus = ['%(number)s (%(codename)s)' % v
+ for v in SUPPORTED_UBUNTU_VERSIONS]
+ write_error('Only Ubuntu %s are currently supported.' %
+ ', '.join(supported_ubuntus))
+ return 1
+
+ if platform.machine() not in ('i686', 'x86_64'):
+ write_error('Only x86 architectures are currently supported.')
+ return 1
+
+ if os.geteuid() != 0 and not args.quick_check:
+ print 'Running as non-root user.'
+ print 'You might have to enter your password one or more times'
+ print 'for \'sudo\'.'
+ print
+
+ compute_dynamic_package_lists()
+
+ packages = (_packages_dev + _packages_lib + _packages_dbg + _packages_lib32 +
+ _packages_arm + _packages_nacl)
+ def packages_key(pkg):
+ s = pkg.rsplit(':', 1)
+ if len(s) == 1:
+ return (s, '')
+ return s
+ packages = sorted(set(packages), key=packages_key)
+
+ if args.quick_check:
+ return quick_check(packages)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/install-build-deps.sh b/build/install-build-deps.sh
new file mode 100755
index 00000000000..57f72160965
--- /dev/null
+++ b/build/install-build-deps.sh
@@ -0,0 +1,477 @@
+#!/bin/bash -e
+
+# Copyright (c) 2012 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.
+
+# Script to install everything needed to build chromium (well, ideally, anyway)
+# See http://code.google.com/p/chromium/wiki/LinuxBuildInstructions
+# and http://code.google.com/p/chromium/wiki/LinuxBuild64Bit
+
+usage() {
+ echo "Usage: $0 [--options]"
+ echo "Options:"
+ echo "--[no-]syms: enable or disable installation of debugging symbols"
+ echo "--lib32: enable installation of 32-bit libraries, e.g. for V8 snapshot"
+ echo "--[no-]arm: enable or disable installation of arm cross toolchain"
+ echo "--[no-]chromeos-fonts: enable or disable installation of Chrome OS"\
+ "fonts"
+ echo "--[no-]nacl: enable or disable installation of prerequisites for"\
+ "building standalone NaCl and all its toolchains"
+ echo "--no-prompt: silently select standard options/defaults"
+ echo "--quick-check: quickly try to determine if dependencies are installed"
+ echo " (this avoids interactive prompts and sudo commands,"
+ echo " so might not be 100% accurate)"
+ echo "--unsupported: attempt installation even on unsupported systems"
+ echo "Script will prompt interactively if options not given."
+ exit 1
+}
+
+# Checks whether a particular package is available in the repos.
+# USAGE: $ package_exists
+package_exists() {
+ apt-cache pkgnames | grep -x "$1" > /dev/null 2>&1
+}
+
+# These default to on because (some) bots need them and it keeps things
+# simple for the bot setup if all bots just run the script in its default
+# mode. Developers who don't want stuff they don't need installed on their
+# own workstations can pass --no-arm --no-nacl when running the script.
+do_inst_arm=1
+do_inst_nacl=1
+
+while test "$1" != ""
+do
+ case "$1" in
+ --syms) do_inst_syms=1;;
+ --no-syms) do_inst_syms=0;;
+ --lib32) do_inst_lib32=1;;
+ --arm) do_inst_arm=1;;
+ --no-arm) do_inst_arm=0;;
+ --chromeos-fonts) do_inst_chromeos_fonts=1;;
+ --no-chromeos-fonts) do_inst_chromeos_fonts=0;;
+ --nacl) do_inst_nacl=1;;
+ --no-nacl) do_inst_nacl=0;;
+ --no-prompt) do_default=1
+ do_quietly="-qq --assume-yes"
+ ;;
+ --quick-check) do_quick_check=1;;
+ --unsupported) do_unsupported=1;;
+ *) usage;;
+ esac
+ shift
+done
+
+if test "$do_inst_arm" = "1"; then
+ do_inst_lib32=1
+fi
+
+# Check for lsb_release command in $PATH
+if ! which lsb_release > /dev/null; then
+ echo "ERROR: lsb_release not found in \$PATH" >&2
+ exit 1;
+fi
+
+distro=$(lsb_release --id --short)
+codename=$(lsb_release --codename --short)
+ubuntu_codenames="(precise|trusty|utopic|vivid)"
+debian_codenames="(stretch)"
+if [ 0 -eq "${do_unsupported-0}" ] && [ 0 -eq "${do_quick_check-0}" ] ; then
+ if [[ ! $codename =~ $ubuntu_codenames && ! $codename =~ $debian_codenames ]]; then
+ echo "ERROR: Only Ubuntu 12.04 (precise), 14.04 (trusty), " \
+ "14.10 (utopic) and 15.04 (vivid), and Debian Testing (stretch) are currently supported" >&2
+ exit 1
+ fi
+
+ if ! uname -m | egrep -q "i686|x86_64"; then
+ echo "Only x86 architectures are currently supported" >&2
+ exit
+ fi
+fi
+
+if [ "x$(id -u)" != x0 ] && [ 0 -eq "${do_quick_check-0}" ]; then
+ echo "Running as non-root user."
+ echo "You might have to enter your password one or more times for 'sudo'."
+ echo
+fi
+
+# Packages needed for chromeos only
+chromeos_dev_list="libbluetooth-dev libxkbcommon-dev realpath"
+
+# Packages needed for development
+if [[ $distro = Debian ]] ; then
+ # Debian-specific package names
+ dev_list="apache2-bin fonts-indic fonts-lyx"
+else
+ # Ubuntu-specific package names
+ dev_list="apache2.2-bin ttf-indic-fonts xfonts-mathml language-pack-da
+ language-pack-fr language-pack-he language-pack-zh-hant"
+fi
+dev_list="$dev_list bison cdbs curl dpkg-dev elfutils devscripts fakeroot
+ flex fonts-thai-tlwg g++ git-core git-svn gperf libapache2-mod-php5
+ libasound2-dev libbrlapi-dev libav-tools
+ libbz2-dev libcairo2-dev libcap-dev libcups2-dev libcurl4-gnutls-dev
+ libdrm-dev libelf-dev libexif-dev libgconf2-dev libglib2.0-dev
+ libglu1-mesa-dev libgnome-keyring-dev libgtk2.0-dev libkrb5-dev
+ libnspr4-dev libnss3-dev libpam0g-dev libpci-dev libpulse-dev
+ libsctp-dev libspeechd-dev libsqlite3-dev libssl-dev libudev-dev
+ libwww-perl libxslt1-dev libxss-dev libxt-dev libxtst-dev openbox
+ patch perl php5-cgi pkg-config python python-cherrypy3 python-crypto
+ python-dev python-numpy python-opencv python-openssl python-psutil
+ python-yaml rpm ruby subversion ttf-dejavu-core
+ ttf-kochi-gothic ttf-kochi-mincho wdiff zip
+ $chromeos_dev_list"
+
+# 64-bit systems need a minimum set of 32-bit compat packages for the pre-built
+# NaCl binaries.
+if file /sbin/init | grep -q 'ELF 64-bit'; then
+ dev_list="${dev_list} libc6-i386 lib32gcc1 lib32stdc++6"
+fi
+
+# Run-time libraries required by chromeos only
+chromeos_lib_list="libpulse0 libbz2-1.0"
+
+# Full list of required run-time libraries
+lib_list="libatk1.0-0 libc6 libasound2 libcairo2 libcap2 libcups2 libexpat1
+ libexif12 libfontconfig1 libfreetype6 libglib2.0-0 libgnome-keyring0
+ libgtk2.0-0 libpam0g libpango1.0-0 libpci3 libpcre3 libpixman-1-0
+ libpng12-0 libspeechd2 libstdc++6 libsqlite3-0 libx11-6
+ libxau6 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxdmcp6
+ libxext6 libxfixes3 libxi6 libxinerama1 libxrandr2 libxrender1
+ libxtst6 zlib1g $chromeos_lib_list"
+
+# Debugging symbols for all of the run-time libraries
+dbg_list="libatk1.0-dbg libc6-dbg libcairo2-dbg libfontconfig1-dbg
+ libglib2.0-0-dbg libgtk2.0-0-dbg libpango1.0-0-dbg libpcre3-dbg
+ libpixman-1-0-dbg libsqlite3-0-dbg libx11-6-dbg libxau6-dbg
+ libxcb1-dbg libxcomposite1-dbg libxcursor1-dbg libxdamage1-dbg
+ libxdmcp6-dbg libxext6-dbg libxfixes3-dbg libxi6-dbg libxinerama1-dbg
+ libxrandr2-dbg libxrender1-dbg libxtst6-dbg zlib1g-dbg"
+
+# Find the proper version of libstdc++6-4.x-dbg.
+if [ "x$codename" = "xprecise" ]; then
+ dbg_list="${dbg_list} libstdc++6-4.6-dbg"
+elif [ "x$codename" = "xtrusty" ]; then
+ dbg_list="${dbg_list} libstdc++6-4.8-dbg"
+else
+ dbg_list="${dbg_list} libstdc++6-4.9-dbg"
+fi
+
+# 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf
+lib32_list="linux-libc-dev:i386"
+
+# arm cross toolchain packages needed to build chrome on armhf
+arm_list="libc6-dev-armhf-cross
+ linux-libc-dev-armhf-cross"
+
+# Work around for dependency issue Debian/Stretch
+if [ "x$codename" = "xstretch" ]; then
+ arm_list+=" g++-5-arm-linux-gnueabihf"
+else
+ arm_list+=" g++-arm-linux-gnueabihf"
+fi
+
+# Work around for dependency issue Ubuntu/Trusty: http://crbug.com/435056
+if [ "x$codename" = "xtrusty" ]; then
+ arm_list+=" g++-4.8-multilib-arm-linux-gnueabihf
+ gcc-4.8-multilib-arm-linux-gnueabihf"
+fi
+
+# Packages to build NaCl, its toolchains, and its ports.
+naclports_list="ant autoconf bison cmake gawk intltool xutils-dev xsltproc"
+nacl_list="g++-mingw-w64-i686 lib32z1-dev
+ libasound2:i386 libcap2:i386 libelf-dev:i386 libexif12:i386
+ libfontconfig1:i386 libgconf-2-4:i386 libglib2.0-0:i386 libgpm2:i386
+ libgtk2.0-0:i386 libncurses5:i386 lib32ncurses5-dev
+ libnss3:i386 libpango1.0-0:i386
+ libssl1.0.0:i386 libtinfo-dev libtinfo-dev:i386 libtool
+ libxcomposite1:i386 libxcursor1:i386 libxdamage1:i386 libxi6:i386
+ libxrandr2:i386 libxss1:i386 libxtst6:i386 texinfo xvfb
+ ${naclports_list}"
+
+# Find the proper version of libgbm-dev. We can't just install libgbm-dev as
+# it depends on mesa, and only one version of mesa can exists on the system.
+# Hence we must match the same version or this entire script will fail.
+mesa_variant=""
+for variant in "-lts-trusty" "-lts-utopic"; do
+ if $(dpkg-query -Wf'${Status}' libgl1-mesa-glx${variant} 2>/dev/null | \
+ grep -q " ok installed"); then
+ mesa_variant="${variant}"
+ fi
+done
+dev_list="${dev_list} libgbm-dev${mesa_variant}
+ libgles2-mesa-dev${mesa_variant} libgl1-mesa-dev${mesa_variant}
+ mesa-common-dev${mesa_variant}"
+nacl_list="${nacl_list} libgl1-mesa-glx${mesa_variant}:i386"
+
+# Some package names have changed over time
+if package_exists ttf-mscorefonts-installer; then
+ dev_list="${dev_list} ttf-mscorefonts-installer"
+else
+ dev_list="${dev_list} msttcorefonts"
+fi
+if package_exists libnspr4-dbg; then
+ dbg_list="${dbg_list} libnspr4-dbg libnss3-dbg"
+ lib_list="${lib_list} libnspr4 libnss3"
+else
+ dbg_list="${dbg_list} libnspr4-0d-dbg libnss3-1d-dbg"
+ lib_list="${lib_list} libnspr4-0d libnss3-1d"
+fi
+if package_exists libjpeg-dev; then
+ dev_list="${dev_list} libjpeg-dev"
+else
+ dev_list="${dev_list} libjpeg62-dev"
+fi
+if package_exists libudev1; then
+ dev_list="${dev_list} libudev1"
+ nacl_list="${nacl_list} libudev1:i386"
+else
+ dev_list="${dev_list} libudev0"
+ nacl_list="${nacl_list} libudev0:i386"
+fi
+if package_exists libbrlapi0.6; then
+ dev_list="${dev_list} libbrlapi0.6"
+else
+ dev_list="${dev_list} libbrlapi0.5"
+fi
+
+
+# Some packages are only needed if the distribution actually supports
+# installing them.
+if package_exists appmenu-gtk; then
+ lib_list="$lib_list appmenu-gtk"
+fi
+
+# When cross building for arm/Android on 64-bit systems the host binaries
+# that are part of v8 need to be compiled with -m32 which means
+# that basic multilib support is needed.
+if file /sbin/init | grep -q 'ELF 64-bit'; then
+ # gcc-multilib conflicts with the arm cross compiler (at least in trusty) but
+ # g++-X.Y-multilib gives us the 32-bit support that we need. Find out the
+ # appropriate value of X and Y by seeing what version the current
+ # distribution's g++-multilib package depends on.
+ multilib_package=$(apt-cache depends g++-multilib --important | \
+ grep -E --color=never --only-matching '\bg\+\+-[0-9.]+-multilib\b')
+ lib32_list="$lib32_list $multilib_package"
+fi
+
+# Waits for the user to press 'Y' or 'N'. Either uppercase of lowercase is
+# accepted. Returns 0 for 'Y' and 1 for 'N'. If an optional parameter has
+# been provided to yes_no(), the function also accepts RETURN as a user input.
+# The parameter specifies the exit code that should be returned in that case.
+# The function will echo the user's selection followed by a newline character.
+# Users can abort the function by pressing CTRL-C. This will call "exit 1".
+yes_no() {
+ if [ 0 -ne "${do_default-0}" ] ; then
+ [ $1 -eq 0 ] && echo "Y" || echo "N"
+ return $1
+ fi
+ local c
+ while :; do
+ c="$(trap 'stty echo -iuclc icanon 2>/dev/null' EXIT INT TERM QUIT
+ stty -echo iuclc -icanon 2>/dev/null
+ dd count=1 bs=1 2>/dev/null | od -An -tx1)"
+ case "$c" in
+ " 0a") if [ -n "$1" ]; then
+ [ $1 -eq 0 ] && echo "Y" || echo "N"
+ return $1
+ fi
+ ;;
+ " 79") echo "Y"
+ return 0
+ ;;
+ " 6e") echo "N"
+ return 1
+ ;;
+ "") echo "Aborted" >&2
+ exit 1
+ ;;
+ *) # The user pressed an unrecognized key. As we are not echoing
+ # any incorrect user input, alert the user by ringing the bell.
+ (tput bel) 2>/dev/null
+ ;;
+ esac
+ done
+}
+
+if test "$do_inst_syms" = "" && test 0 -eq ${do_quick_check-0}
+then
+ echo "This script installs all tools and libraries needed to build Chromium."
+ echo ""
+ echo "For most of the libraries, it can also install debugging symbols, which"
+ echo "will allow you to debug code in the system libraries. Most developers"
+ echo "won't need these symbols."
+ echo -n "Do you want me to install them for you (y/N) "
+ if yes_no 1; then
+ do_inst_syms=1
+ fi
+fi
+if test "$do_inst_syms" = "1"; then
+ echo "Including debugging symbols."
+else
+ echo "Skipping debugging symbols."
+ dbg_list=
+fi
+
+if test "$do_inst_lib32" = "1" ; then
+ echo "Including 32-bit libraries for ARM/Android."
+else
+ echo "Skipping 32-bit libraries for ARM/Android."
+ lib32_list=
+fi
+
+if test "$do_inst_arm" = "1" ; then
+ echo "Including ARM cross toolchain."
+else
+ echo "Skipping ARM cross toolchain."
+ arm_list=
+fi
+
+if test "$do_inst_nacl" = "1"; then
+ echo "Including NaCl, NaCl toolchain, NaCl ports dependencies."
+else
+ echo "Skipping NaCl, NaCl toolchain, NaCl ports dependencies."
+ nacl_list=
+fi
+
+# The `sort -r -s -t: -k2` sorts all the :i386 packages to the front, to avoid
+# confusing dpkg-query (crbug.com/446172).
+packages="$(
+ echo "${dev_list} ${lib_list} ${dbg_list} ${lib32_list} ${arm_list}"\
+ "${nacl_list}" | tr " " "\n" | sort -u | sort -r -s -t: -k2 | tr "\n" " "
+)"
+
+if [ 1 -eq "${do_quick_check-0}" ] ; then
+ failed_check="$(dpkg-query -W -f '${PackageSpec}:${Status}\n' \
+ ${packages} 2>&1 | grep -v "ok installed" || :)"
+ if [ -n "${failed_check}" ]; then
+ echo
+ nomatch="$(echo "${failed_check}" | \
+ sed -e "s/^No packages found matching \(.*\).$/\1/;t;d")"
+ missing="$(echo "${failed_check}" | \
+ sed -e "/^No packages found matching/d;s/^\(.*\):.*$/\1/")"
+ if [ "$nomatch" ]; then
+ # Distinguish between packages that actually aren't available to the
+ # system (i.e. not in any repo) and packages that just aren't known to
+ # dpkg (i.e. managed by apt).
+ unknown=""
+ for p in ${nomatch}; do
+ if apt-cache show ${p} > /dev/null 2>&1; then
+ missing="${p}\n${missing}"
+ else
+ unknown="${p}\n${unknown}"
+ fi
+ done
+ if [ -n "${unknown}" ]; then
+ echo "WARNING: The following packages are unknown to your system"
+ echo "(maybe missing a repo or need to 'sudo apt-get update'):"
+ echo -e "${unknown}" | sed -e "s/^/ /"
+ fi
+ fi
+ if [ -n "${missing}" ]; then
+ echo "WARNING: The following packages are not installed:"
+ echo -e "${missing}" | sed -e "s/^/ /"
+ fi
+ exit 1
+ fi
+ exit 0
+fi
+
+if test "$do_inst_lib32" = "1" || test "$do_inst_nacl" = "1"; then
+ if [[ ! $codename =~ (precise) ]]; then
+ sudo dpkg --add-architecture i386
+ fi
+fi
+sudo apt-get update
+
+# We initially run "apt-get" with the --reinstall option and parse its output.
+# This way, we can find all the packages that need to be newly installed
+# without accidentally promoting any packages from "auto" to "manual".
+# We then re-run "apt-get" with just the list of missing packages.
+echo "Finding missing packages..."
+# Intentionally leaving $packages unquoted so it's more readable.
+echo "Packages required: " $packages
+echo
+new_list_cmd="sudo apt-get install --reinstall $(echo $packages)"
+if new_list="$(yes n | LANGUAGE=en LANG=C $new_list_cmd)"; then
+ # We probably never hit this following line.
+ echo "No missing packages, and the packages are up-to-date."
+elif [ $? -eq 1 ]; then
+ # We expect apt-get to have exit status of 1.
+ # This indicates that we cancelled the install with "yes n|".
+ new_list=$(echo "$new_list" |
+ sed -e '1,/The following NEW packages will be installed:/d;s/^ //;t;d')
+ new_list=$(echo "$new_list" | sed 's/ *$//')
+ if [ -z "$new_list" ] ; then
+ echo "No missing packages, and the packages are up-to-date."
+ else
+ echo "Installing missing packages: $new_list."
+ sudo apt-get install ${do_quietly-} ${new_list}
+ fi
+ echo
+else
+ # An apt-get exit status of 100 indicates that a real error has occurred.
+
+ # I am intentionally leaving out the '"'s around new_list_cmd,
+ # as this makes it easier to cut and paste the output
+ echo "The following command failed: " ${new_list_cmd}
+ echo
+ echo "It produces the following output:"
+ yes n | $new_list_cmd || true
+ echo
+ echo "You will have to install the above packages yourself."
+ echo
+ exit 100
+fi
+
+# Install the Chrome OS default fonts. This must go after running
+# apt-get, since install-chromeos-fonts depends on curl.
+if test "$do_inst_chromeos_fonts" != "0"; then
+ echo
+ echo "Installing Chrome OS fonts."
+ dir=`echo $0 | sed -r -e 's/\/[^/]+$//'`
+ if ! sudo $dir/linux/install-chromeos-fonts.py; then
+ echo "ERROR: The installation of the Chrome OS default fonts failed."
+ if [ `stat -f -c %T $dir` == "nfs" ]; then
+ echo "The reason is that your repo is installed on a remote file system."
+ else
+ echo "This is expected if your repo is installed on a remote file system."
+ fi
+ echo "It is recommended to install your repo on a local file system."
+ echo "You can skip the installation of the Chrome OS default founts with"
+ echo "the command line option: --no-chromeos-fonts."
+ exit 1
+ fi
+else
+ echo "Skipping installation of Chrome OS fonts."
+fi
+
+# $1 - target name
+# $2 - link name
+create_library_symlink() {
+ target=$1
+ linkname=$2
+ if [ -L $linkname ]; then
+ if [ "$(basename $(readlink $linkname))" != "$(basename $target)" ]; then
+ sudo rm $linkname
+ fi
+ fi
+ if [ ! -r $linkname ]; then
+ echo "Creating link: $linkname"
+ sudo ln -fs $target $linkname
+ fi
+}
+
+if test "$do_inst_nacl" = "1"; then
+ echo "Installing symbolic links for NaCl."
+ # naclports needs to cross build python for i386, but libssl1.0.0:i386
+ # only contains libcrypto.so.1.0.0 and not the symlink needed for
+ # linking (libcrypto.so).
+ create_library_symlink /lib/i386-linux-gnu/libcrypto.so.1.0.0 \
+ /usr/lib/i386-linux-gnu/libcrypto.so
+
+ create_library_symlink /lib/i386-linux-gnu/libssl.so.1.0.0 \
+ /usr/lib/i386-linux-gnu/libssl.so
+else
+ echo "Skipping symbolic links for NaCl."
+fi
diff --git a/build/install-chroot.sh b/build/install-chroot.sh
new file mode 100755
index 00000000000..99451ed7ea4
--- /dev/null
+++ b/build/install-chroot.sh
@@ -0,0 +1,888 @@
+#!/bin/bash -e
+
+# Copyright (c) 2012 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 script installs Debian-derived distributions in a chroot environment.
+# It can for example be used to have an accurate 32bit build and test
+# environment when otherwise working on a 64bit machine.
+# N. B. it is unlikely that this script will ever work on anything other than a
+# Debian-derived system.
+
+# Older Debian based systems had both "admin" and "adm" groups, with "admin"
+# apparently being used in more places. Newer distributions have standardized
+# on just the "adm" group. Check /etc/group for the preferred name of the
+# administrator group.
+admin=$(grep '^admin:' /etc/group >&/dev/null && echo admin || echo adm)
+
+usage() {
+ echo "usage: ${0##*/} [-m mirror] [-g group,...] [-s] [-c]"
+ echo "-b dir additional directories that should be bind mounted,"
+ echo ' or "NONE".'
+ echo " Default: if local filesystems present, ask user for help"
+ echo "-g group,... groups that can use the chroot unauthenticated"
+ echo " Default: '${admin}' and current user's group ('$(id -gn)')"
+ echo "-l List all installed chroot environments"
+ echo "-m mirror an alternate repository mirror for package downloads"
+ echo "-s configure default deb-srcs"
+ echo "-c always copy 64bit helper binaries to 32bit chroot"
+ echo "-h this help message"
+}
+
+process_opts() {
+ local OPTNAME OPTIND OPTERR OPTARG
+ while getopts ":b:g:lm:sch" OPTNAME; do
+ case "$OPTNAME" in
+ b)
+ if [ "${OPTARG}" = "NONE" -a -z "${bind_mounts}" ]; then
+ bind_mounts="${OPTARG}"
+ else
+ if [ "${bind_mounts}" = "NONE" -o "${OPTARG}" = "${OPTARG#/}" -o \
+ ! -d "${OPTARG}" ]; then
+ echo "Invalid -b option(s)"
+ usage
+ exit 1
+ fi
+ bind_mounts="${bind_mounts}
+${OPTARG} ${OPTARG} none rw,bind 0 0"
+ fi
+ ;;
+ g)
+ [ -n "${OPTARG}" ] &&
+ chroot_groups="${chroot_groups}${chroot_groups:+,}${OPTARG}"
+ ;;
+ l)
+ list_all_chroots
+ exit
+ ;;
+ m)
+ if [ -n "${mirror}" ]; then
+ echo "You can only specify exactly one mirror location"
+ usage
+ exit 1
+ fi
+ mirror="$OPTARG"
+ ;;
+ s)
+ add_srcs="y"
+ ;;
+ c)
+ copy_64="y"
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ \:)
+ echo "'-$OPTARG' needs an argument."
+ usage
+ exit 1
+ ;;
+ *)
+ echo "invalid command-line option: $OPTARG"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ if [ $# -ge ${OPTIND} ]; then
+ eval echo "Unexpected command line argument: \${${OPTIND}}"
+ usage
+ exit 1
+ fi
+}
+
+list_all_chroots() {
+ for i in /var/lib/chroot/*; do
+ i="${i##*/}"
+ [ "${i}" = "*" ] && continue
+ [ -x "/usr/local/bin/${i%bit}" ] || continue
+ grep -qs "^\[${i%bit}\]\$" /etc/schroot/schroot.conf || continue
+ [ -r "/etc/schroot/script-${i}" -a \
+ -r "/etc/schroot/mount-${i}" ] || continue
+ echo "${i%bit}"
+ done
+}
+
+getkey() {
+ (
+ trap 'stty echo -iuclc icanon 2>/dev/null' EXIT INT TERM QUIT HUP
+ stty -echo iuclc -icanon 2>/dev/null
+ dd count=1 bs=1 2>/dev/null
+ )
+}
+
+chr() {
+ printf "\\$(printf '%03o' "$1")"
+}
+
+ord() {
+ printf '%d' $(printf '%c' "$1" | od -tu1 -An)
+}
+
+is_network_drive() {
+ stat -c %T -f "$1/" 2>/dev/null |
+ egrep -qs '^nfs|cifs|smbfs'
+}
+
+# Check that we are running as a regular user
+[ "$(id -nu)" = root ] && {
+ echo "Run this script as a regular user and provide your \"sudo\"" \
+ "password if requested" >&2
+ exit 1
+}
+
+process_opts "$@"
+
+echo "This script will help you through the process of installing a"
+echo "Debian or Ubuntu distribution in a chroot environment. You will"
+echo "have to provide your \"sudo\" password when requested."
+echo
+
+# Error handler
+trap 'exit 1' INT TERM QUIT HUP
+trap 'sudo apt-get clean; tput bel; echo; echo Failed' EXIT
+
+# Install any missing applications that this script relies on. If these packages
+# are already installed, don't force another "apt-get install". That would
+# prevent them from being auto-removed, if they ever become eligible for that.
+# And as this script only needs the packages once, there is no good reason to
+# introduce a hard dependency on things such as dchroot and debootstrap.
+dep=
+for i in dchroot debootstrap libwww-perl; do
+ [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
+done
+[ -n "$dep" ] && sudo apt-get -y install $dep
+sudo apt-get -y install schroot
+
+# Create directory for chroot
+sudo mkdir -p /var/lib/chroot
+
+# Find chroot environments that can be installed with debootstrap
+targets="$(cd /usr/share/debootstrap/scripts
+ ls | grep '^[a-z]*$')"
+
+# Ask user to pick one of the available targets
+echo "The following targets are available to be installed in a chroot:"
+j=1; for i in $targets; do
+ printf '%4d: %s\n' "$j" "$i"
+ j=$(($j+1))
+done
+while :; do
+ printf "Which target would you like to install: "
+ read n
+ [ "$n" -gt 0 -a "$n" -lt "$j" ] >&/dev/null && break
+done
+j=1; for i in $targets; do
+ [ "$j" -eq "$n" ] && { distname="$i"; break; }
+ j=$(($j+1))
+done
+echo
+
+# On x86-64, ask whether the user wants to install x86-32 or x86-64
+archflag=
+arch=
+if [ "$(uname -m)" = x86_64 ]; then
+ while :; do
+ echo "You are running a 64bit kernel. This allows you to install either a"
+ printf "32bit or a 64bit chroot environment. %s" \
+ "Which one do you want (32, 64) "
+ read arch
+ [ "${arch}" == 32 -o "${arch}" == 64 ] && break
+ done
+ [ "${arch}" == 32 ] && archflag="--arch i386" || archflag="--arch amd64"
+ arch="${arch}bit"
+ echo
+fi
+target="${distname}${arch}"
+
+# Don't accidentally overwrite an existing installation
+[ -d /var/lib/chroot/"${target}" ] && {
+ while :; do
+ echo "This chroot already exists on your machine."
+ if schroot -l --all-sessions 2>&1 |
+ sed 's/^session://' |
+ grep -qs "^${target%bit}-"; then
+ echo "And it appears to be in active use. Terminate all programs that"
+ echo "are currently using the chroot environment and then re-run this"
+ echo "script."
+ echo "If you still get an error message, you might have stale mounts"
+ echo "that you forgot to delete. You can always clean up mounts by"
+ echo "executing \"${target%bit} -c\"."
+ exit 1
+ fi
+ echo "I can abort installation, I can overwrite the existing chroot,"
+ echo "or I can delete the old one and then exit. What would you like to"
+ printf "do (a/o/d)? "
+ read choice
+ case "${choice}" in
+ a|A) exit 1;;
+ o|O) sudo rm -rf "/var/lib/chroot/${target}"; break;;
+ d|D) sudo rm -rf "/var/lib/chroot/${target}" \
+ "/usr/local/bin/${target%bit}" \
+ "/etc/schroot/mount-${target}" \
+ "/etc/schroot/script-${target}" \
+ "/etc/schroot/${target}"
+ sudo sed -ni '/^[[]'"${target%bit}"']$/,${
+ :1;n;/^[[]/b2;b1;:2;p;n;b2};p' \
+ "/etc/schroot/schroot.conf"
+ trap '' INT TERM QUIT HUP
+ trap '' EXIT
+ echo "Deleted!"
+ exit 0;;
+ esac
+ done
+ echo
+}
+sudo mkdir -p /var/lib/chroot/"${target}"
+
+# Offer to include additional standard repositories for Ubuntu-based chroots.
+alt_repos=
+grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && {
+ while :; do
+ echo "Would you like to add ${distname}-updates and ${distname}-security "
+ printf "to the chroot's sources.list (y/n)? "
+ read alt_repos
+ case "${alt_repos}" in
+ y|Y)
+ alt_repos="y"
+ break
+ ;;
+ n|N)
+ break
+ ;;
+ esac
+ done
+ echo
+}
+
+# Check for non-standard file system mount points and ask the user whether
+# they should be imported into the chroot environment
+# We limit to the first 26 mount points that much some basic heuristics,
+# because a) that allows us to enumerate choices with a single character,
+# and b) if we find more than 26 mount points, then these are probably
+# false-positives and something is very unusual about the system's
+# configuration. No need to spam the user with even more information that
+# is likely completely irrelevant.
+if [ -z "${bind_mounts}" ]; then
+ mounts="$(awk '$2 != "/" && $2 !~ "^/boot" && $2 !~ "^/home" &&
+ $2 !~ "^/media" && $2 !~ "^/run" &&
+ ($3 ~ "ext[2-4]" || $3 == "reiserfs" || $3 == "btrfs" ||
+ $3 == "xfs" || $3 == "jfs" || $3 == "u?msdos" ||
+ $3 == "v?fat" || $3 == "hfs" || $3 == "ntfs" ||
+ $3 ~ "nfs[4-9]?" || $3 == "smbfs" || $3 == "cifs") {
+ print $2
+ }' /proc/mounts |
+ head -n26)"
+ if [ -n "${mounts}" ]; then
+ echo "You appear to have non-standard mount points that you"
+ echo "might want to import into the chroot environment:"
+ echo
+ sel=
+ while :; do
+ # Print a menu, listing all non-default mounts of local or network
+ # file systems.
+ j=1; for m in ${mounts}; do
+ c="$(printf $(printf '\\%03o' $((64+$j))))"
+ echo "$sel" | grep -qs $c &&
+ state="mounted in chroot" || state="$(tput el)"
+ printf " $c) %-40s${state}\n" "$m"
+ j=$(($j+1))
+ done
+ # Allow user to interactively (de-)select any of the entries
+ echo
+ printf "Select mount points that you want to be included or press %s" \
+ "SPACE to continue"
+ c="$(getkey | tr a-z A-Z)"
+ [ "$c" == " " ] && { echo; echo; break; }
+ if [ -z "$c" ] ||
+ [ "$c" '<' 'A' -o $(ord "$c") -gt $((64 + $(ord "$j"))) ]; then
+ # Invalid input, ring the console bell
+ tput bel
+ else
+ # Toggle the selection for the given entry
+ if echo "$sel" | grep -qs $c; then
+ sel="$(printf "$sel" | sed "s/$c//")"
+ else
+ sel="$sel$c"
+ fi
+ fi
+ # Reposition cursor to the top of the list of entries
+ tput cuu $(($j + 1))
+ echo
+ done
+ fi
+ j=1; for m in ${mounts}; do
+ c="$(chr $(($j + 64)))"
+ if echo "$sel" | grep -qs $c; then
+ bind_mounts="${bind_mounts}$m $m none rw,bind 0 0
+"
+ fi
+ j=$(($j+1))
+ done
+fi
+
+# Remove stale entry from /etc/schroot/schroot.conf. Entries start
+# with the target name in square brackets, followed by an arbitrary
+# number of lines. The entry stops when either the end of file has
+# been reached, or when the beginning of a new target is encountered.
+# This means, we cannot easily match for a range of lines in
+# "sed". Instead, we actually have to iterate over each line and check
+# whether it is the beginning of a new entry.
+sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p' \
+ /etc/schroot/schroot.conf
+
+# Download base system. This takes some time
+if [ -z "${mirror}" ]; then
+ grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
+ mirror="http://archive.ubuntu.com/ubuntu" ||
+ mirror="http://ftp.us.debian.org/debian"
+fi
+
+sudo ${http_proxy:+http_proxy="${http_proxy}"} debootstrap ${archflag} \
+ "${distname}" "/var/lib/chroot/${target}" "$mirror"
+
+# Add new entry to /etc/schroot/schroot.conf
+grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
+ brand="Ubuntu" || brand="Debian"
+if [ -z "${chroot_groups}" ]; then
+ chroot_groups="${admin},$(id -gn)"
+fi
+
+if [ -d '/etc/schroot/default' ]; then
+ new_version=1
+ fstab="/etc/schroot/${target}/fstab"
+else
+ new_version=0
+ fstab="/etc/schroot/mount-${target}"
+fi
+
+if [ "$new_version" = "1" ]; then
+ sudo cp -ar /etc/schroot/default /etc/schroot/${target}
+
+ sudo sh -c 'cat >>/etc/schroot/schroot.conf' <>${fstab}"
+else
+ # Older versions of schroot wanted a "priority=" line, whereas recent
+ # versions deprecate "priority=" and warn if they see it. We don't have
+ # a good feature test, but scanning for the string "priority=" in the
+ # existing "schroot.conf" file is a good indication of what to do.
+ priority=$(grep -qs 'priority=' /etc/schroot/schroot.conf &&
+ echo 'priority=3' || :)
+ sudo sh -c 'cat >>/etc/schroot/schroot.conf' </etc/schroot/script-'"${target}"
+ sed '\,^/home[/[:space:]],s/\([,[:space:]]\)bind[[:space:]]/\1rbind /' \
+ /etc/schroot/mount-defaults |
+ sudo sh -c "cat > ${fstab}"
+fi
+
+# Add the extra mount points that the user told us about
+[ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] &&
+ printf "${bind_mounts}" |
+ sudo sh -c 'cat >>'"${fstab}"
+
+# If this system has a "/media" mountpoint, import it into the chroot
+# environment. Most modern distributions use this mount point to
+# automatically mount devices such as CDROMs, USB sticks, etc...
+if [ -d /media ] &&
+ ! grep -qs '^/media' "${fstab}"; then
+ echo '/media /media none rw,rbind 0 0' |
+ sudo sh -c 'cat >>'"${fstab}"
+fi
+
+# Share /dev/shm, /run and /run/shm.
+grep -qs '^/dev/shm' "${fstab}" ||
+ echo '/dev/shm /dev/shm none rw,bind 0 0' |
+ sudo sh -c 'cat >>'"${fstab}"
+if [ ! -d "/var/lib/chroot/${target}/run" ] &&
+ ! grep -qs '^/run' "${fstab}"; then
+ echo '/run /run none rw,bind 0 0' |
+ sudo sh -c 'cat >>'"${fstab}"
+fi
+if ! grep -qs '^/run/shm' "${fstab}"; then
+ { [ -d /run ] && echo '/run/shm /run/shm none rw,bind 0 0' ||
+ echo '/dev/shm /run/shm none rw,bind 0 0'; } |
+ sudo sh -c 'cat >>'"${fstab}"
+fi
+
+# Set up a special directory that changes contents depending on the target
+# that is executing.
+d="$(readlink -f "${HOME}/chroot" 2>/dev/null || echo "${HOME}/chroot")"
+s="${d}/.${target}"
+echo "${s} ${d} none rw,bind 0 0" |
+ sudo sh -c 'cat >>'"${target}"
+mkdir -p "${s}"
+
+# Install a helper script to launch commands in the chroot
+sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<'EOF'
+#!/bin/bash
+
+chroot="${0##*/}"
+
+wrap() {
+ # Word-wrap the text passed-in on stdin. Optionally, on continuation lines
+ # insert the same number of spaces as the number of characters in the
+ # parameter(s) passed to this function.
+ # If the "fold" program cannot be found, or if the actual width of the
+ # terminal cannot be determined, this function doesn't attempt to do any
+ # wrapping.
+ local f="$(type -P fold)"
+ [ -z "${f}" ] && { cat; return; }
+ local c="$(stty -a /dev/null |
+ sed 's/.*columns[[:space:]]*\([0-9]*\).*/\1/;t;d')"
+ [ -z "${c}" ] && { cat; return; }
+ local i="$(echo "$*"|sed 's/./ /g')"
+ local j="$(printf %s "${i}"|wc -c)"
+ if [ "${c}" -gt "${j}" ]; then
+ dd bs=1 count="${j}" 2>/dev/null
+ "${f}" -sw "$((${c}-${j}))" | sed '2,$s/^/'"${i}"'/'
+ else
+ "${f}" -sw "${c}"
+ fi
+}
+
+help() {
+ echo "Usage ${0##*/} [-h|--help] [-c|--clean] [-C|--clean-all] [-l|--list] [--] args" | wrap "Usage ${0##*/} "
+ echo " help: print this message" | wrap " "
+ echo " list: list all known chroot environments" | wrap " "
+ echo " clean: remove all old chroot sessions for \"${chroot}\"" | wrap " "
+ echo " clean-all: remove all old chroot sessions for all environments" | wrap " "
+ exit 0
+}
+
+clean() {
+ local s t rc
+ rc=0
+ for s in $(schroot -l --all-sessions); do
+ if [ -n "$1" ]; then
+ t="${s#session:}"
+ [ "${t#${chroot}-}" == "${t}" ] && continue
+ fi
+ if ls -l /proc/*/{cwd,fd} 2>/dev/null |
+ fgrep -qs "/var/lib/schroot/mount/${t}"; then
+ echo "Session \"${t}\" still has active users, not cleaning up" | wrap
+ rc=1
+ continue
+ fi
+ sudo schroot -c "${s}" -e || rc=1
+ done
+ exit ${rc}
+}
+
+list() {
+ for e in $(schroot -l); do
+ e="${e#chroot:}"
+ [ -x "/usr/local/bin/${e}" ] || continue
+ if schroot -l --all-sessions 2>/dev/null |
+ sed 's/^session://' |
+ grep -qs "^${e}-"; then
+ echo "${e} is currently active"
+ else
+ echo "${e}"
+ fi
+ done
+ exit 0
+}
+
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ --) shift; break;;
+ -h|--help) shift; help;;
+ -l|--list) shift; list;;
+ -c|--clean) shift; clean "${chroot}";;
+ -C|--clean-all) shift; clean;;
+ *) break;;
+ esac
+done
+
+# Start a new chroot session and keep track of the session id. We inject this
+# id into all processes that run inside the chroot. Unless they go out of their
+# way to clear their environment, we can then later identify our child and
+# grand-child processes by scanning their environment.
+session="$(schroot -c "${chroot}" -b)"
+export CHROOT_SESSION_ID="${session}"
+
+# Set GOMA_TMP_DIR for better handling of goma inside chroot.
+export GOMA_TMP_DIR="/tmp/goma_tmp_$CHROOT_SESSION_ID"
+mkdir -p "$GOMA_TMP_DIR"
+
+if [ $# -eq 0 ]; then
+ # Run an interactive shell session
+ schroot -c "${session}" -r -p
+else
+ # Run a command inside of the chroot environment
+ p="$1"; shift
+ schroot -c "${session}" -r -p "$p" -- "$@"
+fi
+rc=$?
+
+# Compute the inode of the root directory inside of the chroot environment.
+i=$(schroot -c "${session}" -r -p ls -- -id /proc/self/root/. |
+ awk '{ print $1 }') 2>/dev/null
+other_pids=
+while [ -n "$i" ]; do
+ # Identify processes by the inode number of their root directory. Then
+ # remove all processes that we know belong to other sessions. We use
+ # "sort | uniq -u" to do what amounts to a "set substraction operation".
+ pids=$({ ls -id1 /proc/*/root/. 2>/dev/null |
+ sed -e 's,^[^0-9]*'$i'.*/\([1-9][0-9]*\)/.*$,\1,
+ t
+ d';
+ echo "${other_pids}";
+ echo "${other_pids}"; } | sort | uniq -u) >/dev/null 2>&1
+ # Kill all processes that are still left running in the session. This is
+ # typically an assortment of daemon processes that were started
+ # automatically. They result in us being unable to tear down the session
+ # cleanly.
+ [ -z "${pids}" ] && break
+ for j in $pids; do
+ # Unfortunately, the way that schroot sets up sessions has the
+ # side-effect of being unable to tell one session apart from another.
+ # This can result in us attempting to kill processes in other sessions.
+ # We make a best-effort to avoid doing so.
+ k="$( ( xargs -0 -n1 /dev/null |
+ sed 's/^CHROOT_SESSION_ID=/x/;t1;d;:1;q')"
+ if [ -n "${k}" -a "${k#x}" != "${session}" ]; then
+ other_pids="${other_pids}
+${j}"
+ continue
+ fi
+ kill -9 $pids
+ done
+done
+# End the chroot session. This should clean up all temporary files. But if we
+# earlier failed to terminate all (daemon) processes inside of the session,
+# deleting the session could fail. When that happens, the user has to manually
+# clean up the stale files by invoking us with "--clean" after having killed
+# all running processes.
+schroot -c "${session}" -e
+# Since no goma processes are running, we can remove goma directory.
+rm -rf "$GOMA_TMP_DIR"
+exit $rc
+EOF
+sudo chown root:root /usr/local/bin/"${target%bit}"
+sudo chmod 755 /usr/local/bin/"${target%bit}"
+
+# Add the standard Ubuntu update repositories if requested.
+[ "${alt_repos}" = "y" -a \
+ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
+sudo sed -i '/^deb .* [^ -]\+ main$/p
+ s/^\(deb .* [^ -]\+\) main/\1-security main/
+ p
+ t1
+ d
+ :1;s/-security main/-updates main/
+ t
+ d' "/var/lib/chroot/${target}/etc/apt/sources.list"
+
+# Add a few more repositories to the chroot
+[ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
+sudo sed -i 's/ main$/ main restricted universe multiverse/' \
+ "/var/lib/chroot/${target}/etc/apt/sources.list"
+
+# Add the Ubuntu "partner" repository, if available
+if [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
+ HEAD "http://archive.canonical.com/ubuntu/dists/${distname}/partner" \
+ >&/dev/null; then
+ sudo sh -c '
+ echo "deb http://archive.canonical.com/ubuntu" \
+ "'"${distname}"' partner" \
+ >>"/var/lib/chroot/'"${target}"'/etc/apt/sources.list"'
+fi
+
+# Add source repositories, if the user requested we do so
+[ "${add_srcs}" = "y" -a \
+ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
+sudo sed -i '/^deb[^-]/p
+ s/^deb\([^-]\)/deb-src\1/' \
+ "/var/lib/chroot/${target}/etc/apt/sources.list"
+
+# Set apt proxy if host has set http_proxy
+if [ -n "${http_proxy}" ]; then
+ sudo sh -c '
+ echo "Acquire::http::proxy \"'"${http_proxy}"'\";" \
+ >>"/var/lib/chroot/'"${target}"'/etc/apt/apt.conf"'
+fi
+
+# Update packages
+sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
+ apt-get update; apt-get -y dist-upgrade' || :
+
+# Install a couple of missing packages
+for i in debian-keyring ubuntu-keyring locales sudo; do
+ [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] ||
+ sudo "/usr/local/bin/${target%bit}" apt-get -y install "$i" || :
+done
+
+# Configure locales
+sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
+ l='"${LANG:-en_US}"'; l="${l%%.*}"
+ [ -r /etc/locale.gen ] &&
+ sed -i "s/^# \($l\)/\1/" /etc/locale.gen
+ locale-gen $LANG en_US en_US.UTF-8' || :
+
+# Enable multi-arch support, if available
+sudo "/usr/local/bin/${target%bit}" dpkg --assert-multi-arch >&/dev/null &&
+ [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && {
+ sudo sed -i 's/ / [arch=amd64,i386] /' \
+ "/var/lib/chroot/${target}/etc/apt/sources.list"
+ [ -d /var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/ ] &&
+ sudo "/usr/local/bin/${target%bit}" dpkg --add-architecture \
+ $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) >&/dev/null ||
+ echo foreign-architecture \
+ $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) |
+ sudo sh -c \
+ "cat >'/var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/multiarch'"
+}
+
+# Configure "sudo" package
+sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
+ egrep -qs '"'^$(id -nu) '"' /etc/sudoers ||
+ echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers'
+
+# Install a few more commonly used packages
+sudo "/usr/local/bin/${target%bit}" apt-get -y install \
+ autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool \
+ lsof strace
+
+# If running a 32bit environment on a 64bit machine, install a few binaries
+# as 64bit. This is only done automatically if the chroot distro is the same as
+# the host, otherwise there might be incompatibilities in build settings or
+# runtime dependencies. The user can force it with the '-c' flag.
+host_distro=$(grep -s DISTRIB_CODENAME /etc/lsb-release | \
+ cut -d "=" -f 2)
+if [ "${copy_64}" = "y" -o \
+ "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \
+ file /bin/bash 2>/dev/null | grep -q x86-64; then
+ readlinepkg=$(sudo "/usr/local/bin/${target%bit}" sh -c \
+ 'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1')
+ sudo "/usr/local/bin/${target%bit}" apt-get -y install \
+ lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1 lib64stdc++6
+ dep=
+ for i in binutils gdb; do
+ [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
+ done
+ [ -n "$dep" ] && sudo apt-get -y install $dep
+ sudo mkdir -p "/var/lib/chroot/${target}/usr/local/lib/amd64"
+ for i in libbfd libpython; do
+ lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } |
+ grep -s "$i" | awk '{ print $3 }')"
+ if [ -n "$lib" -a -r "$lib" ]; then
+ sudo cp "$lib" "/var/lib/chroot/${target}/usr/local/lib/amd64"
+ fi
+ done
+ for lib in libssl libcrypt; do
+ for path in /usr/lib /usr/lib/x86_64-linux-gnu; do
+ sudo cp $path/$lib* \
+ "/var/lib/chroot/${target}/usr/local/lib/amd64/" >&/dev/null || :
+ done
+ done
+ for i in gdb ld; do
+ sudo cp /usr/bin/$i "/var/lib/chroot/${target}/usr/local/lib/amd64/"
+ sudo sh -c "cat >'/var/lib/chroot/${target}/usr/local/bin/$i'" <&/dev/null; then
+ tmp_script="/tmp/${script##*/}"
+ cp "${script}" "${tmp_script}"
+ fi
+ # Some distributions automatically start an instance of the system-
+ # wide dbus daemon, cron daemon or of the logging daemon, when
+ # installing the Chrome build depencies. This prevents the chroot
+ # session from being closed. So, we always try to shut down any running
+ # instance of dbus and rsyslog.
+ sudo /usr/local/bin/"${target%bit}" sh -c "${script};
+ rc=$?;
+ /etc/init.d/cron stop >/dev/null 2>&1 || :;
+ /etc/init.d/rsyslog stop >/dev/null 2>&1 || :;
+ /etc/init.d/dbus stop >/dev/null 2>&1 || :;
+ exit $rc"
+ rc=$?
+ [ -n "${tmp_script}" ] && rm -f "${tmp_script}"
+ [ $rc -ne 0 ] && exit $rc
+ break
+ ;;
+ n|N)
+ break
+ ;;
+ esac
+ done
+ echo
+fi
+
+# Check whether ~/chroot is on a (slow) network file system and offer to
+# relocate it. Also offer relocation, if the user appears to have multiple
+# spindles (as indicated by "${bind_mount}" being non-empty).
+# We only offer this option, if it doesn't look as if a chroot environment
+# is currently active. Otherwise, relocation is unlikely to work and it
+# can be difficult for the user to recover from the failed attempt to relocate
+# the ~/chroot directory.
+# We don't aim to solve this problem for every configuration,
+# but try to help with the common cases. For more advanced configuration
+# options, the user can always manually adjust things.
+mkdir -p "${HOME}/chroot/"
+if [ ! -h "${HOME}/chroot" ] &&
+ ! egrep -qs '^[^[:space:]]*/chroot' /etc/fstab &&
+ { [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] ||
+ is_network_drive "${HOME}/chroot"; } &&
+ ! egrep -qs '/var/lib/[^/]*chroot/.*/chroot' /proc/mounts; then
+ echo "${HOME}/chroot is currently located on the same device as your"
+ echo "home directory."
+ echo "This might not be what you want. Do you want me to move it somewhere"
+ echo "else?"
+ # If the computer has multiple spindles, many users configure all or part of
+ # the secondary hard disk to be writable by the primary user of this machine.
+ # Make some reasonable effort to detect this type of configuration and
+ # then offer a good location for where to put the ~/chroot directory.
+ suggest=
+ for i in $(echo "${bind_mounts}"|cut -d ' ' -f 1); do
+ if [ -d "$i" -a -w "$i" -a \( ! -a "$i/chroot" -o -w "$i/chroot/." \) ] &&
+ ! is_network_drive "$i"; then
+ suggest="$i"
+ else
+ for j in "$i/"*; do
+ if [ -d "$j" -a -w "$j" -a \
+ \( ! -a "$j/chroot" -o -w "$j/chroot/." \) ] &&
+ ! is_network_drive "$j"; then
+ suggest="$j"
+ else
+ for k in "$j/"*; do
+ if [ -d "$k" -a -w "$k" -a \
+ \( ! -a "$k/chroot" -o -w "$k/chroot/." \) ] &&
+ ! is_network_drive "$k"; then
+ suggest="$k"
+ break
+ fi
+ done
+ fi
+ [ -n "${suggest}" ] && break
+ done
+ fi
+ [ -n "${suggest}" ] && break
+ done
+ def_suggest="${HOME}"
+ if [ -n "${suggest}" ]; then
+ # For home directories that reside on network drives, make our suggestion
+ # the default option. For home directories that reside on a local drive,
+ # require that the user manually enters the new location.
+ if is_network_drive "${HOME}"; then
+ def_suggest="${suggest}"
+ else
+ echo "A good location would probably be in \"${suggest}\""
+ fi
+ fi
+ while :; do
+ printf "Physical location [${def_suggest}]: "
+ read dir
+ [ -z "${dir}" ] && dir="${def_suggest}"
+ [ "${dir%%/}" == "${HOME%%/}" ] && break
+ if ! [ -d "${dir}" -a -w "${dir}" ] ||
+ [ -a "${dir}/chroot" -a ! -w "${dir}/chroot/." ]; then
+ echo "Cannot write to ${dir}/chroot. Please try again"
+ else
+ mv "${HOME}/chroot" "${dir}/chroot"
+ ln -s "${dir}/chroot" "${HOME}/chroot"
+ for i in $(list_all_chroots); do
+ sudo "$i" mkdir -p "${dir}/chroot"
+ done
+ sudo sed -i "s,${HOME}/chroot,${dir}/chroot,g" /etc/schroot/mount-*
+ break
+ fi
+ done
+fi
+
+# Clean up package files
+sudo schroot -c "${target%bit}" -p -- apt-get clean
+sudo apt-get clean
+
+trap '' INT TERM QUIT HUP
+trap '' EXIT
+
+# Let the user know what we did
+cat < 0)
+
+ add_to_path = [];
+ first_entry = argv[0];
+ if first_entry.startswith('ADD_TO_PATH='):
+ argv = argv[1:];
+ add_to_path = first_entry.replace('ADD_TO_PATH=', '', 1).split(':')
+
+ # Still need something to run.
+ assert(len(argv) > 0)
+
+ clean_env = {}
+
+ # Pull over the whitelisted keys.
+ for key in env_key_whitelist:
+ val = os.environ.get(key, None)
+ if not val is None:
+ clean_env[key] = val
+
+ # Collect the developer dir as set via Xcode, defaulting it.
+ dev_prefix = os.environ.get('DEVELOPER_DIR', '/Developer/')
+ if dev_prefix[-1:] != '/':
+ dev_prefix += '/'
+
+ # Now pull in PATH, but remove anything Xcode might have added.
+ initial_path = os.environ.get('PATH', '')
+ filtered_chunks = \
+ [x for x in initial_path.split(':') if not x.startswith(dev_prefix)]
+ if filtered_chunks:
+ clean_env['PATH'] = ':'.join(add_to_path + filtered_chunks)
+
+ # Add any KEY=VALUE args before the command to the cleaned environment.
+ args = argv[:]
+ while '=' in args[0]:
+ (key, val) = args[0].split('=', 1)
+ clean_env[key] = val
+ args = args[1:]
+
+ # Still need something to run.
+ assert(len(args) > 0)
+
+ # Off it goes...
+ os.execvpe(args[0], args, clean_env)
+ # Should never get here, so return a distinctive, non-zero status code.
+ return 66
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/build/ios/coverage.gypi b/build/ios/coverage.gypi
new file mode 100644
index 00000000000..e82208902cc
--- /dev/null
+++ b/build/ios/coverage.gypi
@@ -0,0 +1,32 @@
+# Copyright 2014 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.
+
+{
+ 'variables': {
+ 'enable_coverage%': 0,
+ },
+ 'conditions': [
+ ['enable_coverage', {
+ 'target_defaults': {
+ 'defines': [
+ 'ENABLE_TEST_CODE_COVERAGE=1'
+ ],
+ 'link_settings': {
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [
+ '-fprofile-arcs',
+ ],
+ },
+ },
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [
+ '-fprofile-arcs',
+ '-ftest-coverage',
+ ],
+ },
+ },
+ }],
+ ],
+}
+
diff --git a/build/ios/grit_whitelist.txt b/build/ios/grit_whitelist.txt
new file mode 100644
index 00000000000..a976daf16b5
--- /dev/null
+++ b/build/ios/grit_whitelist.txt
@@ -0,0 +1,1155 @@
+IDR_ABOUT_DOM_DISTILLER_CSS
+IDR_ABOUT_DOM_DISTILLER_HTML
+IDR_ABOUT_DOM_DISTILLER_JS
+IDR_ABOUT_STATS_HTML
+IDR_ABOUT_STATS_JS
+IDR_ABOUT_VERSION_CSS
+IDR_ABOUT_VERSION_HTML
+IDR_ABOUT_VERSION_JS
+IDR_CONTEXTUAL_SEARCH_PROMO_HTML
+IDR_CONTROLLED_SETTING_MANDATORY
+IDR_CRASHES_HTML
+IDR_CRASHES_JS
+IDR_CREDITS_HTML
+IDR_CREDITS_JS
+IDR_CREDIT_CARD_CVC_HINT
+IDR_CREDIT_CARD_CVC_HINT_AMEX
+IDR_DATA_REDUCTION_PROXY_INTERSTITIAL_HTML
+IDR_DEFAULT_FAVICON
+IDR_DEFAULT_FAVICON_32
+IDR_DEFAULT_FAVICON_64
+IDR_DIR_HEADER_HTML
+IDR_DISTILLABLE_PAGE_SERIALIZED_MODEL
+IDR_DISTILLER_CSS
+IDR_DISTILLER_IOS_CSS
+IDR_DISTILLER_JS
+IDR_DOM_DISTILLER_VIEWER_HTML
+IDR_DOM_DISTILLER_VIEWER_JS
+IDR_EXTRACT_PAGE_FEATURES_JS
+IDR_FLAGS_FAVICON
+IDR_FLAGS_HTML
+IDR_FLAGS_JS
+IDR_GCM_INTERNALS_CSS
+IDR_GCM_INTERNALS_HTML
+IDR_GCM_INTERNALS_JS
+IDR_HISTORY_FAVICON
+IDR_HISTORY_HTML
+IDR_HISTORY_JS
+IDR_INCOGNITO_TAB_HTML
+IDR_INFOBAR_AUTOFILL_CC
+IDR_INFOBAR_AUTOLOGIN
+IDR_INFOBAR_RESTORE_SESSION
+IDR_INFOBAR_SAVE_PASSWORD
+IDR_INFOBAR_TRANSLATE_IOS
+IDR_INFOBAR_WARNING
+IDR_IS_DISTILLABLE_JS
+IDR_LOCATION_BAR_HTTP
+IDR_NET_ERROR_HTML
+IDR_NET_EXPORT_HTML
+IDR_NET_EXPORT_JS
+IDR_NET_INTERNALS_INDEX_HTML
+IDR_NET_INTERNALS_INDEX_JS
+IDR_OMAHA_HTML
+IDR_OMAHA_JS
+IDR_OMNIBOX_CALCULATOR
+IDR_OMNIBOX_CLEAR_IOS
+IDR_OMNIBOX_CLEAR_OTR_IOS
+IDR_OMNIBOX_CLEAR_OTR_PRESSED_IOS
+IDR_OMNIBOX_CLEAR_PRESSED_IOS
+IDR_OMNIBOX_EXTENSION_APP
+IDR_OMNIBOX_HISTORY
+IDR_OMNIBOX_HISTORY_INCOGNITO
+IDR_OMNIBOX_HTTP
+IDR_OMNIBOX_HTTPS_INVALID
+IDR_OMNIBOX_HTTPS_POLICY_WARNING
+IDR_OMNIBOX_HTTPS_VALID
+IDR_OMNIBOX_HTTPS_WARNING
+IDR_OMNIBOX_HTTP_INCOGNITO
+IDR_OMNIBOX_KEYBOARD_VIEW_APPEND
+IDR_OMNIBOX_KEYBOARD_VIEW_APPEND_HIGHLIGHTED
+IDR_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO
+IDR_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO_HIGHLIGHTED
+IDR_OMNIBOX_SEARCH
+IDR_OMNIBOX_SEARCH_INCOGNITO
+IDR_OMNIBOX_SEARCH_SECURED
+IDR_OMNIBOX_STAR
+IDR_OMNIBOX_STAR_INCOGNITO
+IDR_OTHER_DEVICES_JS
+IDR_PAGEINFO_BAD
+IDR_PAGEINFO_GOOD
+IDR_PAGEINFO_INFO
+IDR_PAGEINFO_WARNING_MAJOR
+IDR_PAGEINFO_WARNING_MINOR
+IDR_POLICY_CSS
+IDR_POLICY_HTML
+IDR_POLICY_JS
+IDR_PRINTER_FAVICON
+IDR_SAD_FAVICON
+IDR_SAD_TAB
+IDR_SECURITY_INTERSTITIAL_HTML
+IDR_SIGNIN_INTERNALS_INDEX_HTML
+IDR_SIGNIN_INTERNALS_INDEX_JS
+IDR_SYNC_INTERNALS_ABOUT_JS
+IDR_SYNC_INTERNALS_CHROME_SYNC_JS
+IDR_SYNC_INTERNALS_DATA_JS
+IDR_SYNC_INTERNALS_EVENTS_JS
+IDR_SYNC_INTERNALS_INDEX_HTML
+IDR_SYNC_INTERNALS_INDEX_JS
+IDR_SYNC_INTERNALS_SEARCH_JS
+IDR_SYNC_INTERNALS_SYNC_LOG_JS
+IDR_SYNC_INTERNALS_SYNC_NODE_BROWSER_JS
+IDR_SYNC_INTERNALS_SYNC_SEARCH_JS
+IDR_SYNC_INTERNALS_TYPES_JS
+IDR_TOOLBAR_SHADOW_FULL_BLEED
+IDR_TRANSLATE_JS
+IDR_UBER_UTILS_JS
+IDR_WEBUI_CSS_TEXT_DEFAULTS
+IDR_WEBUI_I18N_TEMPLATE_JS
+IDR_WEBUI_JSTEMPLATE_JS
+IDR_WEBUI_JS_LOAD_TIME_DATA
+IDS_ABOUT_MAC
+IDS_ABOUT_VERSION_COMMAND_LINE
+IDS_ABOUT_VERSION_COMPANY_NAME
+IDS_ABOUT_VERSION_COPYRIGHT
+IDS_ABOUT_VERSION_EXECUTABLE_PATH
+IDS_ABOUT_VERSION_OFFICIAL
+IDS_ABOUT_VERSION_OS
+IDS_ABOUT_VERSION_PATH_NOTFOUND
+IDS_ABOUT_VERSION_PROFILE_PATH
+IDS_ABOUT_VERSION_REVISION
+IDS_ABOUT_VERSION_TITLE
+IDS_ABOUT_VERSION_UNOFFICIAL
+IDS_ABOUT_VERSION_USER_AGENT
+IDS_ABOUT_VERSION_VARIATIONS
+IDS_ACCEPT_LANGUAGES
+IDS_ACCNAME_BACK
+IDS_ACCNAME_CLEAR_TEXT
+IDS_ACCNAME_FORWARD
+IDS_ACCNAME_LOCATION
+IDS_ACCNAME_VOICE_SEARCH
+IDS_ALLOW_INSECURE_CONTENT_BUTTON
+IDS_ALTERNATE_NAV_URL_VIEW_LABEL
+IDS_ANNOTATED_SUGGESTION
+IDS_APP_CANCEL
+IDS_APP_OK
+IDS_APP_UNTITLED_SHORTCUT_FILE_NAME
+IDS_AUTOCOMPLETE_SEARCH_DESCRIPTION
+IDS_AUTOFILL_ADDRESS_LINE_SEPARATOR
+IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_NETWORK
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_PERMANENT
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_TRY_AGAIN
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_AMEX
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED
+IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED_AMEX
+IDS_AUTOFILL_CC_AMEX
+IDS_AUTOFILL_CC_AMEX_SHORT
+IDS_AUTOFILL_CC_DINERS
+IDS_AUTOFILL_CC_DISCOVER
+IDS_AUTOFILL_CC_GENERIC
+IDS_AUTOFILL_CC_INFOBAR_ACCEPT
+IDS_AUTOFILL_CC_INFOBAR_DENY
+IDS_AUTOFILL_CC_INFOBAR_TEXT
+IDS_AUTOFILL_CC_JCB
+IDS_AUTOFILL_CC_MASTERCARD
+IDS_AUTOFILL_CC_UNION_PAY
+IDS_AUTOFILL_CC_VISA
+IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM
+IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY
+IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY
+IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY
+IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK
+IDS_AUTOFILL_FIELD_LABEL_AREA
+IDS_AUTOFILL_FIELD_LABEL_COUNTY
+IDS_AUTOFILL_FIELD_LABEL_DEPARTMENT
+IDS_AUTOFILL_FIELD_LABEL_DISTRICT
+IDS_AUTOFILL_FIELD_LABEL_EMIRATE
+IDS_AUTOFILL_FIELD_LABEL_ISLAND
+IDS_AUTOFILL_FIELD_LABEL_PARISH
+IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE
+IDS_AUTOFILL_FIELD_LABEL_PREFECTURE
+IDS_AUTOFILL_FIELD_LABEL_PROVINCE
+IDS_AUTOFILL_FIELD_LABEL_STATE
+IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE
+IDS_AUTOFILL_OPTIONS_POPUP
+IDS_AUTOFILL_PASSWORD_FIELD_SUGGESTIONS_TITLE
+IDS_AUTOFILL_SCAN_CREDIT_CARD
+IDS_AUTOFILL_WARNING_FORM_DISABLED
+IDS_AUTOFILL_WARNING_INSECURE_CONNECTION
+IDS_AUTOLOGIN_INFOBAR_CANCEL_BUTTON
+IDS_AUTOLOGIN_INFOBAR_MESSAGE
+IDS_AUTOLOGIN_INFOBAR_OK_BUTTON
+IDS_BLOCKED_DISPLAYING_INSECURE_CONTENT
+IDS_BLOCK_INSECURE_CONTENT_BUTTON
+IDS_BOOKMARK_ADD_EDITOR_TITLE
+IDS_BOOKMARK_ALL_TABS_DIALOG_TITLE
+IDS_BOOKMARK_BAR_FOLDER_NAME
+IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME
+IDS_BOOKMARK_BAR_MANAGED_FOLDER_DOMAIN_NAME
+IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME
+IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME
+IDS_BOOKMARK_BAR_REDO
+IDS_BOOKMARK_BAR_REDO_ADD
+IDS_BOOKMARK_BAR_REDO_DELETE
+IDS_BOOKMARK_BAR_REDO_EDIT
+IDS_BOOKMARK_BAR_REDO_MOVE
+IDS_BOOKMARK_BAR_REDO_REORDER
+IDS_BOOKMARK_BAR_SUPERVISED_FOLDER_DEFAULT_NAME
+IDS_BOOKMARK_BAR_UNDO
+IDS_BOOKMARK_BAR_UNDO_ADD
+IDS_BOOKMARK_BAR_UNDO_DELETE
+IDS_BOOKMARK_BAR_UNDO_EDIT
+IDS_BOOKMARK_BAR_UNDO_MOVE
+IDS_BOOKMARK_BAR_UNDO_REORDER
+IDS_BOOKMARK_BUBBLE_CHOOSER_ANOTHER_FOLDER
+IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK
+IDS_BOOKMARK_EDITOR_CONFIRM_DELETE
+IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME
+IDS_BOOKMARK_EDITOR_TITLE
+IDS_BOOKMARK_FOLDER_CHOOSER_TITLE
+IDS_BOOKMARK_FOLDER_EDITOR_TITLE
+IDS_BOOKMARK_FOLDER_EDITOR_WINDOW_TITLE
+IDS_BOOKMARK_FOLDER_EDITOR_WINDOW_TITLE_NEW
+IDS_BOOKMARK_MANAGER_FOLDER_SECTION
+IDS_BOOKMARK_MANAGER_FOLDER_TITLE
+IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER
+IDS_BOOKMARK_MANAGER_REMOVE_TITLE
+IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER
+IDS_BOOKMARK_NEW_FOLDER_BUTTON_TITLE
+IDS_CANCEL
+IDS_CERT_ERROR_AUTHORITY_INVALID_DESCRIPTION
+IDS_CERT_ERROR_AUTHORITY_INVALID_DETAILS
+IDS_CERT_ERROR_AUTHORITY_INVALID_EXTRA_INFO_2
+IDS_CERT_ERROR_AUTHORITY_INVALID_TITLE
+IDS_CERT_ERROR_CHAIN_EXPIRED_DESCRIPTION
+IDS_CERT_ERROR_CHAIN_EXPIRED_DETAILS
+IDS_CERT_ERROR_COMMON_NAME_INVALID_DESCRIPTION
+IDS_CERT_ERROR_COMMON_NAME_INVALID_DETAILS
+IDS_CERT_ERROR_COMMON_NAME_INVALID_EXTRA_INFO_2
+IDS_CERT_ERROR_COMMON_NAME_INVALID_TITLE
+IDS_CERT_ERROR_CONTAINS_ERRORS_DESCRIPTION
+IDS_CERT_ERROR_CONTAINS_ERRORS_DETAILS
+IDS_CERT_ERROR_CONTAINS_ERRORS_EXTRA_INFO_2
+IDS_CERT_ERROR_CONTAINS_ERRORS_TITLE
+IDS_CERT_ERROR_EXPIRED_DESCRIPTION
+IDS_CERT_ERROR_EXPIRED_DETAILS
+IDS_CERT_ERROR_EXPIRED_DETAILS_EXTRA_INFO_2
+IDS_CERT_ERROR_EXPIRED_TITLE
+IDS_CERT_ERROR_EXTRA_INFO_1
+IDS_CERT_ERROR_EXTRA_INFO_TITLE
+IDS_CERT_ERROR_INVALID_CERT_DESCRIPTION
+IDS_CERT_ERROR_INVALID_CERT_DETAILS
+IDS_CERT_ERROR_INVALID_CERT_EXTRA_INFO_2
+IDS_CERT_ERROR_INVALID_CERT_TITLE
+IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DESCRIPTION
+IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DETAILS
+IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_TITLE
+IDS_CERT_ERROR_NOT_YET_VALID_DESCRIPTION
+IDS_CERT_ERROR_NOT_YET_VALID_DETAILS
+IDS_CERT_ERROR_NOT_YET_VALID_DETAILS_EXTRA_INFO_2
+IDS_CERT_ERROR_NOT_YET_VALID_TITLE
+IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DESCRIPTION
+IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DETAILS
+IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_TITLE
+IDS_CERT_ERROR_REVOKED_CERT_DESCRIPTION
+IDS_CERT_ERROR_REVOKED_CERT_DETAILS
+IDS_CERT_ERROR_REVOKED_CERT_EXTRA_INFO_2
+IDS_CERT_ERROR_REVOKED_CERT_TITLE
+IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DESCRIPTION
+IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DETAILS
+IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_TITLE
+IDS_CERT_ERROR_UNKNOWN_ERROR_DESCRIPTION
+IDS_CERT_ERROR_UNKNOWN_ERROR_DETAILS
+IDS_CERT_ERROR_UNKNOWN_ERROR_TITLE
+IDS_CERT_ERROR_WEAK_KEY_DESCRIPTION
+IDS_CERT_ERROR_WEAK_KEY_DETAILS
+IDS_CERT_ERROR_WEAK_KEY_EXTRA_INFO_2
+IDS_CERT_ERROR_WEAK_KEY_TITLE
+IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DESCRIPTION
+IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DETAILS
+IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_EXTRA_INFO_2
+IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_TITLE
+IDS_CHROME_TO_DEVICE_PRINT_TO_PHONE
+IDS_CHROME_TO_DEVICE_SNAPSHOTS
+IDS_CLOSE
+IDS_CONTEXTUAL_SEARCH_HEADER
+IDS_CONTEXTUAL_SEARCH_PROMO_DESCRIPTION_1
+IDS_CONTEXTUAL_SEARCH_PROMO_DESCRIPTION_2
+IDS_CONTEXTUAL_SEARCH_PROMO_FEATURE_NAME
+IDS_CONTEXTUAL_SEARCH_PROMO_OPTIN
+IDS_CONTEXTUAL_SEARCH_PROMO_OPTOUT
+IDS_COULDNT_OPEN_PROFILE_ERROR
+IDS_CRASHES_BUG_LINK_LABEL
+IDS_CRASHES_CRASH_COUNT_BANNER_FORMAT
+IDS_CRASHES_CRASH_HEADER_FORMAT
+IDS_CRASHES_CRASH_TIME_FORMAT
+IDS_CRASHES_DISABLED_HEADER
+IDS_CRASHES_DISABLED_MESSAGE
+IDS_CRASHES_NO_CRASHES_MESSAGE
+IDS_CRASHES_TITLE
+IDS_CRASHES_UPLOAD_MESSAGE
+IDS_DATA_REDUCTION_PROXY_BACK_BUTTON
+IDS_DATA_REDUCTION_PROXY_CANNOT_PROXY_HEADING
+IDS_DATA_REDUCTION_PROXY_CANNOT_PROXY_PRIMARY_PARAGRAPH
+IDS_DATA_REDUCTION_PROXY_CANNOT_PROXY_SECONDARY_PARAGRAPH
+IDS_DATA_REDUCTION_PROXY_CONTINUE_BUTTON
+IDS_DATA_REDUCTION_PROXY_TITLE
+IDS_DEFAULT_AVATAR_NAME_10
+IDS_DEFAULT_AVATAR_NAME_11
+IDS_DEFAULT_AVATAR_NAME_12
+IDS_DEFAULT_AVATAR_NAME_13
+IDS_DEFAULT_AVATAR_NAME_14
+IDS_DEFAULT_AVATAR_NAME_15
+IDS_DEFAULT_AVATAR_NAME_16
+IDS_DEFAULT_AVATAR_NAME_17
+IDS_DEFAULT_AVATAR_NAME_18
+IDS_DEFAULT_AVATAR_NAME_19
+IDS_DEFAULT_AVATAR_NAME_20
+IDS_DEFAULT_AVATAR_NAME_21
+IDS_DEFAULT_AVATAR_NAME_22
+IDS_DEFAULT_AVATAR_NAME_23
+IDS_DEFAULT_AVATAR_NAME_24
+IDS_DEFAULT_AVATAR_NAME_25
+IDS_DEFAULT_AVATAR_NAME_26
+IDS_DEFAULT_AVATAR_NAME_8
+IDS_DEFAULT_AVATAR_NAME_9
+IDS_DEFAULT_ENCODING
+IDS_DEFAULT_PROFILE_NAME
+IDS_DEFAULT_TAB_TITLE
+IDS_DELETE
+IDS_DISABLE_TOUCH_ADJUSTMENT_DESCRIPTION
+IDS_DISABLE_TOUCH_ADJUSTMENT_NAME
+IDS_DOM_DISTILLER_JAVASCRIPT_DISABLED_CONTENT
+IDS_DOM_DISTILLER_QUALITY_ANSWER_NO
+IDS_DOM_DISTILLER_QUALITY_ANSWER_YES
+IDS_DOM_DISTILLER_QUALITY_QUESTION
+IDS_DOM_DISTILLER_VIEWER_CLOSE_READER_VIEW
+IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT
+IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_TITLE
+IDS_DOM_DISTILLER_VIEWER_LOADING_STRING
+IDS_DOM_DISTILLER_VIEWER_LOADING_TITLE
+IDS_DOM_DISTILLER_VIEWER_NO_DATA_CONTENT
+IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE
+IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL
+IDS_DOM_DISTILLER_WEBUI_ENTRY_ADD
+IDS_DOM_DISTILLER_WEBUI_ENTRY_ADD_FAILED
+IDS_DOM_DISTILLER_WEBUI_ENTRY_URL
+IDS_DOM_DISTILLER_WEBUI_FETCHING_ENTRIES
+IDS_DOM_DISTILLER_WEBUI_REFRESH
+IDS_DOM_DISTILLER_WEBUI_TITLE
+IDS_DOM_DISTILLER_WEBUI_VIEW_URL
+IDS_DOM_DISTILLER_WEBUI_VIEW_URL_FAILED
+IDS_DONE
+IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE
+IDS_EDIT_FIND_MAC
+IDS_EMPTY_KEYWORD_VALUE
+IDS_ERRORPAGES_BUTTON_LESS
+IDS_ERRORPAGES_BUTTON_MORE
+IDS_ERRORPAGES_BUTTON_RELOAD
+IDS_ERRORPAGES_BUTTON_SHOW_SAVED_COPY
+IDS_ERRORPAGES_BUTTON_SHOW_SAVED_COPY_HELP
+IDS_ERRORPAGES_DETAILS_ADDRESS_UNREACHABLE
+IDS_ERRORPAGES_DETAILS_BAD_GATEWAY
+IDS_ERRORPAGES_DETAILS_BAD_SSL_CLIENT_AUTH_CERT
+IDS_ERRORPAGES_DETAILS_BLOCKED
+IDS_ERRORPAGES_DETAILS_BLOCKED_BY_ADMINISTRATOR
+IDS_ERRORPAGES_DETAILS_BLOCKED_ENROLLMENT_CHECK_PENDING
+IDS_ERRORPAGES_DETAILS_CACHE_MISS
+IDS_ERRORPAGES_DETAILS_CACHE_READ_FAILURE
+IDS_ERRORPAGES_DETAILS_CONNECTION_CLOSED
+IDS_ERRORPAGES_DETAILS_CONNECTION_FAILED
+IDS_ERRORPAGES_DETAILS_CONNECTION_REFUSED
+IDS_ERRORPAGES_DETAILS_CONNECTION_RESET
+IDS_ERRORPAGES_DETAILS_DNS_PROBE_RUNNING
+IDS_ERRORPAGES_DETAILS_DOWNLOAD_FILE_TYPE_ERROR
+IDS_ERRORPAGES_DETAILS_EMPTY_RESPONSE
+IDS_ERRORPAGES_DETAILS_FILE_ACCESS_DENIED
+IDS_ERRORPAGES_DETAILS_FILE_NOT_FOUND
+IDS_ERRORPAGES_DETAILS_FORBIDDEN
+IDS_ERRORPAGES_DETAILS_GATEWAY_TIMEOUT
+IDS_ERRORPAGES_DETAILS_GONE
+IDS_ERRORPAGES_DETAILS_HTTP_VERSION_NOT_SUPPORTED
+IDS_ERRORPAGES_DETAILS_ICANN_NAME_COLLISION
+IDS_ERRORPAGES_DETAILS_INTERNAL_SERVER_ERROR
+IDS_ERRORPAGES_DETAILS_INTERNET_DISCONNECTED
+IDS_ERRORPAGES_DETAILS_NAME_NOT_RESOLVED
+IDS_ERRORPAGES_DETAILS_NETWORK_ACCESS_DENIED
+IDS_ERRORPAGES_DETAILS_NETWORK_CHANGED
+IDS_ERRORPAGES_DETAILS_NETWORK_IO_SUSPENDED
+IDS_ERRORPAGES_DETAILS_NOT_IMPLEMENTED
+IDS_ERRORPAGES_DETAILS_PINNING_FAILURE
+IDS_ERRORPAGES_DETAILS_PROXY_CONNECTION_FAILED
+IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION
+IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH
+IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_LOCATION
+IDS_ERRORPAGES_DETAILS_SERVICE_UNAVAILABLE
+IDS_ERRORPAGES_DETAILS_SSL_FALLBACK_BEYOND_MINIMUM_VERSION
+IDS_ERRORPAGES_DETAILS_SSL_PROTOCOL_ERROR
+IDS_ERRORPAGES_DETAILS_SSL_VERSION_OR_CIPHER_MISMATCH
+IDS_ERRORPAGES_DETAILS_TEMPORARILY_THROTTLED
+IDS_ERRORPAGES_DETAILS_TIMED_OUT
+IDS_ERRORPAGES_DETAILS_TOO_MANY_REDIRECTS
+IDS_ERRORPAGES_DETAILS_UNKNOWN
+IDS_ERRORPAGES_ERROR_CODE
+IDS_ERRORPAGES_HEADING_ACCESS_DENIED
+IDS_ERRORPAGES_HEADING_BAD_SSL_CLIENT_AUTH_CERT
+IDS_ERRORPAGES_HEADING_BLOCKED
+IDS_ERRORPAGES_HEADING_BLOCKED_BY_ADMINISTRATOR
+IDS_ERRORPAGES_HEADING_CACHE_MISS
+IDS_ERRORPAGES_HEADING_CACHE_READ_FAILURE
+IDS_ERRORPAGES_HEADING_DOWNLOAD_FILE_TYPE_ERROR
+IDS_ERRORPAGES_HEADING_DUPLICATE_HEADERS
+IDS_ERRORPAGES_HEADING_EMPTY_RESPONSE
+IDS_ERRORPAGES_HEADING_FILE_ACCESS_DENIED
+IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR
+IDS_ERRORPAGES_HEADING_INTERNET_DISCONNECTED
+IDS_ERRORPAGES_HEADING_NETWORK_ACCESS_DENIED
+IDS_ERRORPAGES_HEADING_NETWORK_IO_SUSPENDED
+IDS_ERRORPAGES_HEADING_NOT_AVAILABLE
+IDS_ERRORPAGES_HEADING_NOT_FOUND
+IDS_ERRORPAGES_HEADING_PINNING_FAILURE
+IDS_ERRORPAGES_HEADING_PROXY_CONNECTION_FAILED
+IDS_ERRORPAGES_HEADING_SSL_FALLBACK_BEYOND_MINIMUM_VERSION
+IDS_ERRORPAGES_HEADING_SSL_PROTOCOL_ERROR
+IDS_ERRORPAGES_HEADING_SSL_VERSION_OR_CIPHER_MISMATCH
+IDS_ERRORPAGES_HEADING_TOO_MANY_REDIRECTS
+IDS_ERRORPAGES_HEADING_WEAK_SERVER_EPHEMERAL_DH_KEY
+IDS_ERRORPAGES_HTTP_POST_WARNING
+IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_BODY
+IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_HEADER
+IDS_ERRORPAGES_SUGGESTION_CONTACT_ADMINISTRATOR
+IDS_ERRORPAGES_SUGGESTION_DNS_CONFIG
+IDS_ERRORPAGES_SUGGESTION_FIREWALL_CONFIG
+IDS_ERRORPAGES_SUGGESTION_GOOGLE_SEARCH
+IDS_ERRORPAGES_SUGGESTION_LEARNMORE_BODY
+IDS_ERRORPAGES_SUGGESTION_NETWORK_PREDICTION
+IDS_ERRORPAGES_SUGGESTION_PROXY_CONFIG
+IDS_ERRORPAGES_SUGGESTION_PROXY_DISABLE_PLATFORM
+IDS_ERRORPAGES_SUGGESTION_RELOAD
+IDS_ERRORPAGES_SUGGESTION_RELOAD_REPOST_BODY
+IDS_ERRORPAGES_SUGGESTION_RELOAD_REPOST_HEADER
+IDS_ERRORPAGES_SUGGESTION_VIEW_POLICIES
+IDS_ERRORPAGES_SUMMARY_ADDRESS_UNREACHABLE
+IDS_ERRORPAGES_SUMMARY_BAD_GATEWAY
+IDS_ERRORPAGES_SUMMARY_BAD_SSL_CLIENT_AUTH_CERT
+IDS_ERRORPAGES_SUMMARY_BLOCKED
+IDS_ERRORPAGES_SUMMARY_BLOCKED_BY_ADMINISTRATOR
+IDS_ERRORPAGES_SUMMARY_BLOCKED_ENROLLMENT_CHECK_PENDING
+IDS_ERRORPAGES_SUMMARY_CACHE_MISS
+IDS_ERRORPAGES_SUMMARY_CACHE_READ_FAILURE
+IDS_ERRORPAGES_SUMMARY_CONNECTION_REFUSED
+IDS_ERRORPAGES_SUMMARY_CONNECTION_RESET
+IDS_ERRORPAGES_SUMMARY_DNS_PROBE_RUNNING
+IDS_ERRORPAGES_SUMMARY_DOWNLOAD_FILE_TYPE_ERROR
+IDS_ERRORPAGES_SUMMARY_DUPLICATE_HEADERS
+IDS_ERRORPAGES_SUMMARY_EMPTY_RESPONSE
+IDS_ERRORPAGES_SUMMARY_FILE_ACCESS_DENIED
+IDS_ERRORPAGES_SUMMARY_FORBIDDEN
+IDS_ERRORPAGES_SUMMARY_GATEWAY_TIMEOUT
+IDS_ERRORPAGES_SUMMARY_GONE
+IDS_ERRORPAGES_SUMMARY_ICANN_NAME_COLLISION
+IDS_ERRORPAGES_SUMMARY_INTERNAL_SERVER_ERROR
+IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED
+IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_INSTRUCTIONS_TEMPLATE
+IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_PLATFORM
+IDS_ERRORPAGES_SUMMARY_NAME_NOT_RESOLVED
+IDS_ERRORPAGES_SUMMARY_NETWORK_ACCESS_DENIED
+IDS_ERRORPAGES_SUMMARY_NETWORK_CHANGED
+IDS_ERRORPAGES_SUMMARY_NETWORK_IO_SUSPENDED
+IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE
+IDS_ERRORPAGES_SUMMARY_NOT_FOUND
+IDS_ERRORPAGES_SUMMARY_PINNING_FAILURE
+IDS_ERRORPAGES_SUMMARY_PROXY_CONNECTION_FAILED
+IDS_ERRORPAGES_SUMMARY_SERVICE_UNAVAILABLE
+IDS_ERRORPAGES_SUMMARY_SSL_FALLBACK_BEYOND_MINIMUM_VERSION
+IDS_ERRORPAGES_SUMMARY_SSL_PROTOCOL_ERROR
+IDS_ERRORPAGES_SUMMARY_SSL_VERSION_OR_CIPHER_MISMATCH
+IDS_ERRORPAGES_SUMMARY_TEMPORARILY_THROTTLED
+IDS_ERRORPAGES_SUMMARY_TIMED_OUT
+IDS_ERRORPAGES_SUMMARY_TOO_MANY_REDIRECTS
+IDS_ERRORPAGES_SUMMARY_WEAK_SERVER_EPHEMERAL_DH_KEY
+IDS_ERRORPAGES_SUMMARY_WEBSITE_CANNOT_HANDLE
+IDS_ERRORPAGES_TITLE_ACCESS_DENIED
+IDS_ERRORPAGES_TITLE_BLOCKED
+IDS_ERRORPAGES_TITLE_LOAD_FAILED
+IDS_ERRORPAGES_TITLE_NOT_AVAILABLE
+IDS_ERRORPAGES_TITLE_NOT_FOUND
+IDS_ERRORPAGE_NET_BUTTON_DETAILS
+IDS_ERRORPAGE_NET_BUTTON_HIDE_DETAILS
+IDS_EXTENSION_KEYWORD_COMMAND
+IDS_FEEDBACK_REPORT_PAGE_TITLE
+IDS_FEEDBACK_REPORT_URL_LABEL
+IDS_FEEDBACK_SEND_REPORT
+IDS_FEEDBACK_USER_EMAIL_LABEL
+IDS_FIND_IN_PAGE_CLOSE_TOOLTIP
+IDS_FIND_IN_PAGE_COUNT
+IDS_FIND_IN_PAGE_NEXT_TOOLTIP
+IDS_FIND_IN_PAGE_PREVIOUS_TOOLTIP
+IDS_FLAGS_ACCELERATED_FIXED_ROOT_BACKGROUND_DESCRIPTION
+IDS_FLAGS_ACCELERATED_FIXED_ROOT_BACKGROUND_NAME
+IDS_FLAGS_ALLOW_NACL_SOCKET_API_DESCRIPTION
+IDS_FLAGS_ALLOW_NACL_SOCKET_API_NAME
+IDS_FLAGS_ALLOW_TOUCHPAD_THREE_FINGER_CLICK_DESCRIPTION
+IDS_FLAGS_ALLOW_TOUCHPAD_THREE_FINGER_CLICK_NAME
+IDS_FLAGS_COMPOSITED_LAYER_BORDERS
+IDS_FLAGS_COMPOSITED_LAYER_BORDERS_DESCRIPTION
+IDS_FLAGS_COMPOSITING_FOR_FIXED_POSITION_DESCRIPTION
+IDS_FLAGS_COMPOSITING_FOR_FIXED_POSITION_HIGH_DPI
+IDS_FLAGS_COMPOSITING_FOR_FIXED_POSITION_NAME
+IDS_FLAGS_CONFLICTS_CHECK_DESCRIPTION
+IDS_FLAGS_CONFLICTS_CHECK_NAME
+IDS_FLAGS_DEBUG_PACKED_APP_DESCRIPTION
+IDS_FLAGS_DEBUG_PACKED_APP_NAME
+IDS_FLAGS_DEBUG_SHORTCUTS_DESCRIPTION
+IDS_FLAGS_DEBUG_SHORTCUTS_NAME
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_DESCRIPTION
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_GRANDE
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_NAME
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_SHORT
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_TALL
+IDS_FLAGS_DEFAULT_TILE_HEIGHT_VENTI
+IDS_FLAGS_DEFAULT_TILE_WIDTH_DESCRIPTION
+IDS_FLAGS_DEFAULT_TILE_WIDTH_GRANDE
+IDS_FLAGS_DEFAULT_TILE_WIDTH_NAME
+IDS_FLAGS_DEFAULT_TILE_WIDTH_SHORT
+IDS_FLAGS_DEFAULT_TILE_WIDTH_TALL
+IDS_FLAGS_DEFAULT_TILE_WIDTH_VENTI
+IDS_FLAGS_DISABLE
+IDS_FLAGS_DISABLE_ACCELERATED_2D_CANVAS_DESCRIPTION
+IDS_FLAGS_DISABLE_ACCELERATED_2D_CANVAS_NAME
+IDS_FLAGS_DISABLE_ACCELERATED_VIDEO_DECODE_DESCRIPTION
+IDS_FLAGS_DISABLE_ACCELERATED_VIDEO_DECODE_NAME
+IDS_FLAGS_DISABLE_BOOT_ANIMATION
+IDS_FLAGS_DISABLE_BOOT_ANIMATION_DESCRIPTION
+IDS_FLAGS_DISABLE_GESTURE_REQUIREMENT_FOR_MEDIA_PLAYBACK_DESCRIPTION
+IDS_FLAGS_DISABLE_GESTURE_REQUIREMENT_FOR_MEDIA_PLAYBACK_NAME
+IDS_FLAGS_DISABLE_HYPERLINK_AUDITING_DESCRIPTION
+IDS_FLAGS_DISABLE_HYPERLINK_AUDITING_NAME
+IDS_FLAGS_DISABLE_PNACL_DESCRIPTION
+IDS_FLAGS_DISABLE_PNACL_NAME
+IDS_FLAGS_DISABLE_SOFTWARE_RASTERIZER_DESCRIPTION
+IDS_FLAGS_DISABLE_SOFTWARE_RASTERIZER_NAME
+IDS_FLAGS_DISABLE_WEBGL_DESCRIPTION
+IDS_FLAGS_DISABLE_WEBGL_NAME
+IDS_FLAGS_DISABLE_WEBRTC_DESCRIPTION
+IDS_FLAGS_DISABLE_WEBRTC_NAME
+IDS_FLAGS_ENABLE
+IDS_FLAGS_ENABLE_ACCELERATED_MJPEG_DECODE_DESCRIPTION
+IDS_FLAGS_ENABLE_ACCELERATED_MJPEG_DECODE_NAME
+IDS_FLAGS_ENABLE_APPS_SHOW_ON_FIRST_PAINT_DESCRIPTION
+IDS_FLAGS_ENABLE_APPS_SHOW_ON_FIRST_PAINT_NAME
+IDS_FLAGS_ENABLE_CONTEXTUAL_SEARCH
+IDS_FLAGS_ENABLE_CONTEXTUAL_SEARCH_DESCRIPTION
+IDS_FLAGS_ENABLE_DEVTOOLS_EXPERIMENTS_DESCRIPTION
+IDS_FLAGS_ENABLE_DEVTOOLS_EXPERIMENTS_NAME
+IDS_FLAGS_ENABLE_DOWNLOAD_RESUMPTION_DESCRIPTION
+IDS_FLAGS_ENABLE_DOWNLOAD_RESUMPTION_NAME
+IDS_FLAGS_ENABLE_ENHANCED_BOOKMARKS_DESCRIPTION
+IDS_FLAGS_ENABLE_ENHANCED_BOOKMARKS_NAME
+IDS_FLAGS_ENABLE_EXPERIMENTAL_CANVAS_FEATURES_DESCRIPTION
+IDS_FLAGS_ENABLE_EXPERIMENTAL_CANVAS_FEATURES_NAME
+IDS_FLAGS_ENABLE_GESTURE_TAP_HIGHLIGHTING_DESCRIPTION
+IDS_FLAGS_ENABLE_GESTURE_TAP_HIGHLIGHTING_NAME
+IDS_FLAGS_ENABLE_ICON_NTP_DESCRIPTION
+IDS_FLAGS_ENABLE_ICON_NTP_NAME
+IDS_FLAGS_ENABLE_JAVASCRIPT_HARMONY_DESCRIPTION
+IDS_FLAGS_ENABLE_JAVASCRIPT_HARMONY_NAME
+IDS_FLAGS_ENABLE_NACL_DEBUG_DESCRIPTION
+IDS_FLAGS_ENABLE_NACL_DEBUG_NAME
+IDS_FLAGS_ENABLE_NACL_DESCRIPTION
+IDS_FLAGS_ENABLE_NACL_NAME
+IDS_FLAGS_ENABLE_PANELS_DESCRIPTION
+IDS_FLAGS_ENABLE_PANELS_NAME
+IDS_FLAGS_ENABLE_PASSWORD_GENERATION_DESCRIPTION
+IDS_FLAGS_ENABLE_PASSWORD_GENERATION_NAME
+IDS_FLAGS_ENABLE_PINCH_SCALE_DESCRIPTION
+IDS_FLAGS_ENABLE_PINCH_SCALE_NAME
+IDS_FLAGS_ENABLE_REQUEST_TABLET_SITE_DESCRIPTION
+IDS_FLAGS_ENABLE_REQUEST_TABLET_SITE_NAME
+IDS_FLAGS_ENABLE_SCREEN_CAPTURE_DESCRIPTION
+IDS_FLAGS_ENABLE_SCREEN_CAPTURE_NAME
+IDS_FLAGS_ENABLE_SIMPLE_CACHE_BACKEND_DESCRIPTION
+IDS_FLAGS_ENABLE_SIMPLE_CACHE_BACKEND_NAME
+IDS_FLAGS_ENABLE_SMOOTH_SCROLLING_DESCRIPTION
+IDS_FLAGS_ENABLE_SMOOTH_SCROLLING_NAME
+IDS_FLAGS_ENABLE_STALE_WHILE_REVALIDATE_DESCRIPTION
+IDS_FLAGS_ENABLE_STALE_WHILE_REVALIDATE_NAME
+IDS_FLAGS_ENABLE_SUGGESTIONS_SERVICE_DESCRIPTION
+IDS_FLAGS_ENABLE_SUGGESTIONS_SERVICE_NAME
+IDS_FLAGS_ENABLE_SYNCED_NOTIFICATIONS_DESCRIPTION
+IDS_FLAGS_ENABLE_SYNCED_NOTIFICATIONS_NAME
+IDS_FLAGS_ENABLE_TCP_FAST_OPEN_DESCRIPTION
+IDS_FLAGS_ENABLE_TCP_FAST_OPEN_NAME
+IDS_FLAGS_ENABLE_TOUCH_DRAG_DROP_DESCRIPTION
+IDS_FLAGS_ENABLE_TOUCH_DRAG_DROP_NAME
+IDS_FLAGS_ENABLE_TOUCH_EDITING_DESCRIPTION
+IDS_FLAGS_ENABLE_TOUCH_EDITING_NAME
+IDS_FLAGS_ENABLE_TRANSLATE_NEW_UX_DESCRIPTION
+IDS_FLAGS_ENABLE_TRANSLATE_NEW_UX_NAME
+IDS_FLAGS_EXPERIMENTAL_EXTENSION_APIS_DESCRIPTION
+IDS_FLAGS_EXPERIMENTAL_EXTENSION_APIS_NAME
+IDS_FLAGS_EXPERIMENTAL_WEB_PLATFORM_FEATURES_DESCRIPTION
+IDS_FLAGS_EXPERIMENTAL_WEB_PLATFORM_FEATURES_NAME
+IDS_FLAGS_EXTENSIONS_ON_CHROME_URLS_DESCRIPTION
+IDS_FLAGS_EXTENSIONS_ON_CHROME_URLS_NAME
+IDS_FLAGS_FORCE_ACCELERATED_OVERFLOW_SCROLL_MODE_DESCRIPTION
+IDS_FLAGS_FORCE_ACCELERATED_OVERFLOW_SCROLL_MODE_NAME
+IDS_FLAGS_FORCE_HIGH_DPI_DESCRIPTION
+IDS_FLAGS_FORCE_HIGH_DPI_NAME
+IDS_FLAGS_IGNORE_GPU_BLACKLIST_DESCRIPTION
+IDS_FLAGS_IGNORE_GPU_BLACKLIST_NAME
+IDS_FLAGS_LONG_TITLE
+IDS_FLAGS_NACL_DEBUG_MASK_DESCRIPTION
+IDS_FLAGS_NACL_DEBUG_MASK_NAME
+IDS_FLAGS_NOT_AVAILABLE
+IDS_FLAGS_NO_EXPERIMENTS_AVAILABLE
+IDS_FLAGS_NO_UNSUPPORTED_EXPERIMENTS
+IDS_FLAGS_NTP_OTHER_SESSIONS_MENU_DESCRIPTION
+IDS_FLAGS_NTP_OTHER_SESSIONS_MENU_NAME
+IDS_FLAGS_PERFORMANCE_MONITOR_GATHERING_DESCRIPTION
+IDS_FLAGS_PERFORMANCE_MONITOR_GATHERING_NAME
+IDS_FLAGS_RELAUNCH_BUTTON
+IDS_FLAGS_RELAUNCH_NOTICE
+IDS_FLAGS_RESET_ALL_BUTTON
+IDS_FLAGS_SAVE_PAGE_AS_MHTML_DESCRIPTION
+IDS_FLAGS_SAVE_PAGE_AS_MHTML_NAME
+IDS_FLAGS_SHOW_AUTOFILL_TYPE_PREDICTIONS_DESCRIPTION
+IDS_FLAGS_SHOW_AUTOFILL_TYPE_PREDICTIONS_NAME
+IDS_FLAGS_SHOW_FPS_COUNTER
+IDS_FLAGS_SHOW_FPS_COUNTER_DESCRIPTION
+IDS_FLAGS_SHOW_TOUCH_HUD_DESCRIPTION
+IDS_FLAGS_SHOW_TOUCH_HUD_NAME
+IDS_FLAGS_SILENT_DEBUGGER_EXTENSION_API_DESCRIPTION
+IDS_FLAGS_SILENT_DEBUGGER_EXTENSION_API_NAME
+IDS_FLAGS_SPELLCHECK_AUTOCORRECT
+IDS_FLAGS_SPELLCHECK_AUTOCORRECT_DESCRIPTION
+IDS_FLAGS_STACKED_TAB_STRIP_DESCRIPTION
+IDS_FLAGS_STACKED_TAB_STRIP_NAME
+IDS_FLAGS_TABLE_TITLE
+IDS_FLAGS_THREADED_COMPOSITING_MODE_DESCRIPTION
+IDS_FLAGS_THREADED_COMPOSITING_MODE_NAME
+IDS_FLAGS_TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE
+IDS_FLAGS_TOUCH_SCROLLING_MODE_DESCRIPTION
+IDS_FLAGS_TOUCH_SCROLLING_MODE_NAME
+IDS_FLAGS_TOUCH_SCROLLING_MODE_SYNC_TOUCHMOVE
+IDS_FLAGS_TOUCH_SCROLLING_MODE_TOUCHCANCEL
+IDS_FLAGS_UNSUPPORTED_TABLE_TITLE
+IDS_FLAGS_WALLET_SERVICE_USE_SANDBOX_DESCRIPTION
+IDS_FLAGS_WALLET_SERVICE_USE_SANDBOX_NAME
+IDS_FLAGS_WARNING_HEADER
+IDS_FLAGS_WARNING_TEXT
+IDS_FULLSCREEN
+IDS_GENERIC_EXPERIMENT_CHOICE_AUTOMATIC
+IDS_GENERIC_EXPERIMENT_CHOICE_DEFAULT
+IDS_GENERIC_EXPERIMENT_CHOICE_DISABLED
+IDS_GENERIC_EXPERIMENT_CHOICE_ENABLED
+IDS_GROUP_BY_DOMAIN_LABEL
+IDS_GUEST_PROFILE_NAME
+IDS_HARMFUL_V3_EXPLANATION_PARAGRAPH
+IDS_HARMFUL_V3_HEADING
+IDS_HARMFUL_V3_PRIMARY_PARAGRAPH
+IDS_HARMFUL_V3_PROCEED_PARAGRAPH
+IDS_HISTORY_ACTION_MENU_DESCRIPTION
+IDS_HISTORY_BLOCKED_VISIT_TEXT
+IDS_HISTORY_BROWSERESULTS
+IDS_HISTORY_CONTINUED
+IDS_HISTORY_DATE_WITH_RELATIVE_TIME
+IDS_HISTORY_DELETE_PRIOR_VISITS_CONFIRM_BUTTON
+IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING
+IDS_HISTORY_FILTER_ALLOWED
+IDS_HISTORY_FILTER_ALLOW_ITEMS
+IDS_HISTORY_FILTER_BLOCKED
+IDS_HISTORY_FILTER_BLOCK_ITEMS
+IDS_HISTORY_HAS_SYNCED_RESULTS
+IDS_HISTORY_INTERVAL
+IDS_HISTORY_IN_CONTENT_PACK
+IDS_HISTORY_LOADING
+IDS_HISTORY_LOCK_BUTTON
+IDS_HISTORY_MORE_FROM_SITE
+IDS_HISTORY_NEWER
+IDS_HISTORY_NEWEST
+IDS_HISTORY_NO_RESULTS
+IDS_HISTORY_NO_SEARCH_RESULTS
+IDS_HISTORY_NO_SYNCED_RESULTS
+IDS_HISTORY_NUMBER_VISITS
+IDS_HISTORY_OLDER
+IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG
+IDS_HISTORY_OTHER_SESSIONS_COLLAPSE_SESSION
+IDS_HISTORY_OTHER_SESSIONS_EXPAND_SESSION
+IDS_HISTORY_OTHER_SESSIONS_OPEN_ALL
+IDS_HISTORY_RANGE_ALL_TIME
+IDS_HISTORY_RANGE_LABEL
+IDS_HISTORY_RANGE_MONTH
+IDS_HISTORY_RANGE_NEXT
+IDS_HISTORY_RANGE_PREVIOUS
+IDS_HISTORY_RANGE_TODAY
+IDS_HISTORY_RANGE_WEEK
+IDS_HISTORY_REMOVE_BOOKMARK
+IDS_HISTORY_REMOVE_PAGE
+IDS_HISTORY_REMOVE_SELECTED_ITEMS
+IDS_HISTORY_SEARCHRESULTSFOR
+IDS_HISTORY_SEARCH_BUTTON
+IDS_HISTORY_TITLE
+IDS_HISTORY_UNKNOWN_DEVICE
+IDS_HISTORY_UNLOCK_BUTTON
+IDS_HTTP_POST_WARNING
+IDS_HTTP_POST_WARNING_RESEND
+IDS_HTTP_POST_WARNING_TITLE
+IDS_IMPORT_FROM_FIREFOX
+IDS_IMPORT_FROM_ICEWEASEL
+IDS_JAVASCRIPT_ALERT_DEFAULT_TITLE
+IDS_JAVASCRIPT_ALERT_TITLE
+IDS_JAVASCRIPT_MESSAGEBOX_DEFAULT_TITLE
+IDS_JAVASCRIPT_MESSAGEBOX_TITLE
+IDS_KEYWORD_SEARCH
+IDS_LEARN_MORE
+IDS_LEGACY_DEFAULT_PROFILE_NAME
+IDS_LIBADDRESSINPUT_ADDRESS_LINE_1_LABEL
+IDS_LIBADDRESSINPUT_AREA
+IDS_LIBADDRESSINPUT_COUNTRY_OR_REGION_LABEL
+IDS_LIBADDRESSINPUT_COUNTY
+IDS_LIBADDRESSINPUT_DEPARTMENT
+IDS_LIBADDRESSINPUT_DISTRICT
+IDS_LIBADDRESSINPUT_DO_SI
+IDS_LIBADDRESSINPUT_EMIRATE
+IDS_LIBADDRESSINPUT_ISLAND
+IDS_LIBADDRESSINPUT_LOCALITY_LABEL
+IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE
+IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE_URL
+IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP
+IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP_URL
+IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD
+IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE
+IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE_AND_URL
+IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE
+IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE_AND_URL
+IDS_LIBADDRESSINPUT_NEIGHBORHOOD
+IDS_LIBADDRESSINPUT_OBLAST
+IDS_LIBADDRESSINPUT_ORGANIZATION_LABEL
+IDS_LIBADDRESSINPUT_PARISH
+IDS_LIBADDRESSINPUT_PIN_CODE_LABEL
+IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL
+IDS_LIBADDRESSINPUT_POST_TOWN
+IDS_LIBADDRESSINPUT_PO_BOX_FORBIDDEN_VALUE
+IDS_LIBADDRESSINPUT_PREFECTURE
+IDS_LIBADDRESSINPUT_PROVINCE
+IDS_LIBADDRESSINPUT_RECIPIENT_LABEL
+IDS_LIBADDRESSINPUT_STATE
+IDS_LIBADDRESSINPUT_SUBURB
+IDS_LIBADDRESSINPUT_UNKNOWN_VALUE
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE_AND_URL
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE
+IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL
+IDS_LIBADDRESSINPUT_VILLAGE_TOWNSHIP
+IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL
+IDS_LINK_FROM_CLIPBOARD
+IDS_LOGIN_DIALOG_OK_BUTTON_LABEL
+IDS_LOGIN_DIALOG_PASSWORD_FIELD
+IDS_LOGIN_DIALOG_TITLE
+IDS_LOGIN_DIALOG_USERNAME_FIELD
+IDS_MALWARE_V3_ADVICE_HEADING
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH_ADVICE
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH_HISTORY
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH_SUBRESOURCE
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH_SUBRESOURCE_ADVICE
+IDS_MALWARE_V3_EXPLANATION_PARAGRAPH_SUBRESOURCE_HISTORY
+IDS_MALWARE_V3_HEADING
+IDS_MALWARE_V3_PRIMARY_PARAGRAPH
+IDS_MALWARE_V3_PROCEED_PARAGRAPH
+IDS_MALWARE_V3_PROCEED_PARAGRAPH_NOT_RECOMMEND
+IDS_MALWARE_V3_PROCEED_PARAGRAPH_SOCIAL
+IDS_MANAGED_USER_AVATAR_LABEL
+IDS_MIDI_SYSEX_INFOBAR_QUESTION
+IDS_MIDI_SYSEX_PERMISSION_FRAGMENT
+IDS_MOBILE_WELCOME_URL
+IDS_NACL_DEBUG_MASK_CHOICE_DEBUG_ALL
+IDS_NACL_DEBUG_MASK_CHOICE_EXCLUDE_UTILS_PNACL
+IDS_NACL_DEBUG_MASK_CHOICE_INCLUDE_DEBUG
+IDS_NETWORK_PREDICTION_ENABLED_DESCRIPTION
+IDS_NET_EXPORT_NO_EMAIL_ACCOUNTS_ALERT_MESSAGE
+IDS_NET_EXPORT_NO_EMAIL_ACCOUNTS_ALERT_TITLE
+IDS_NEW_INCOGNITO_WINDOW_MAC
+IDS_NEW_NUMBERED_PROFILE_NAME
+IDS_NEW_TAB_CHROME_WELCOME_PAGE_TITLE
+IDS_NEW_TAB_MOST_VISITED
+IDS_NEW_TAB_RECENTLY_CLOSED
+IDS_NEW_TAB_RESTORE_THUMBNAILS_SHORT_LINK
+IDS_NEW_TAB_THUMBNAIL_REMOVED_NOTIFICATION
+IDS_NEW_TAB_TITLE
+IDS_NEW_TAB_UNDO_THUMBNAIL_REMOVE
+IDS_NUMBERED_PROFILE_NAME
+IDS_OK
+IDS_OMNIBOX_EMPTY_HINT
+IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON
+IDS_OPEN_TABS_NOTYETSYNCED
+IDS_OPEN_TABS_PROMOCOMPUTER
+IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY
+IDS_OPTIONS_DISABLE_WEB_SERVICES
+IDS_OPTIONS_ENABLE_LOGGING
+IDS_OPTIONS_IMPROVE_BROWSING_EXPERIENCE
+IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON
+IDS_OTHER_DEVICES_X_MORE
+IDS_PAGEINFO_ADDRESS
+IDS_PAGEINFO_CERT_INFO_BUTTON
+IDS_PAGEINFO_PARTIAL_ADDRESS
+IDS_PAGE_INFO_HELP_CENTER_LINK
+IDS_PAGE_INFO_INTERNAL_PAGE
+IDS_PAGE_INFO_SECURITY_BUTTON_ACCESSIBILITY_LABEL
+IDS_PAGE_INFO_SECURITY_TAB_DEPRECATED_SIGNATURE_ALGORITHM
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_CONNECTION_TEXT
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_ERROR
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_WARNING
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_SENTENCE_LINK
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTION_DETAILS
+IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTION_DETAILS_AEAD
+IDS_PAGE_INFO_SECURITY_TAB_FALLBACK_MESSAGE
+IDS_PAGE_INFO_SECURITY_TAB_FIRST_VISITED_TODAY
+IDS_PAGE_INFO_SECURITY_TAB_INSECURE_IDENTITY
+IDS_PAGE_INFO_SECURITY_TAB_NON_UNIQUE_NAME
+IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT
+IDS_PAGE_INFO_SECURITY_TAB_NO_REVOCATION_MECHANISM
+IDS_PAGE_INFO_SECURITY_TAB_RENEGOTIATION_MESSAGE
+IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY_EV_NO_CT
+IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY_NO_CT
+IDS_PAGE_INFO_SECURITY_TAB_SSL_VERSION
+IDS_PAGE_INFO_SECURITY_TAB_UNABLE_TO_CHECK_REVOCATION
+IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY
+IDS_PAGE_INFO_SECURITY_TAB_WEAK_ENCRYPTION_CONNECTION_TEXT
+IDS_PASSWORDS_EXCEPTIONS_TAB_TITLE
+IDS_PASSWORDS_SHOW_PASSWORDS_TAB_TITLE
+IDS_PASSWORD_MANAGER_BLACKLIST_BUTTON
+IDS_PASSWORD_MANAGER_EMPTY_LOGIN
+IDS_PASSWORD_MANAGER_SAVE_BUTTON
+IDS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT
+IDS_PAST_TIME_TODAY
+IDS_PAST_TIME_YESTERDAY
+IDS_PDF_INFOBAR_ALWAYS_USE_READER_BUTTON
+IDS_PERMISSION_ALLOW
+IDS_PERMISSION_DENY
+IDS_PHISHING_V3_EXPLANATION_PARAGRAPH
+IDS_PHISHING_V3_HEADING
+IDS_PHISHING_V3_PRIMARY_PARAGRAPH
+IDS_PHISHING_V3_PROCEED_PARAGRAPH
+IDS_PLATFORM_LABEL
+IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_ACCEPT_BUTTON
+IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_TITLE
+IDS_PLUGIN_NOT_SUPPORTED
+IDS_POLICY_ASSOCIATION_STATE_ACTIVE
+IDS_POLICY_ASSOCIATION_STATE_DEPROVISIONED
+IDS_POLICY_ASSOCIATION_STATE_UNMANAGED
+IDS_POLICY_DEFAULT_SEARCH_DISABLED
+IDS_POLICY_DEPRECATED
+IDS_POLICY_DM_STATUS_HTTP_STATUS_ERROR
+IDS_POLICY_DM_STATUS_REQUEST_FAILED
+IDS_POLICY_DM_STATUS_REQUEST_INVALID
+IDS_POLICY_DM_STATUS_RESPONSE_DECODING_ERROR
+IDS_POLICY_DM_STATUS_SERVICE_ACTIVATION_PENDING
+IDS_POLICY_DM_STATUS_SERVICE_DEPROVISIONED
+IDS_POLICY_DM_STATUS_SERVICE_DEVICE_ID_CONFLICT
+IDS_POLICY_DM_STATUS_SERVICE_DEVICE_NOT_FOUND
+IDS_POLICY_DM_STATUS_SERVICE_DOMAIN_MISMATCH
+IDS_POLICY_DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER
+IDS_POLICY_DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED
+IDS_POLICY_DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID
+IDS_POLICY_DM_STATUS_SERVICE_MISSING_LICENSES
+IDS_POLICY_DM_STATUS_SERVICE_POLICY_NOT_FOUND
+IDS_POLICY_DM_STATUS_SUCCESS
+IDS_POLICY_DM_STATUS_TEMPORARY_UNAVAILABLE
+IDS_POLICY_DM_STATUS_UNKNOWN_ERROR
+IDS_POLICY_FILTER_PLACEHOLDER
+IDS_POLICY_HEADER_LEVEL
+IDS_POLICY_HEADER_NAME
+IDS_POLICY_HEADER_SCOPE
+IDS_POLICY_HEADER_STATUS
+IDS_POLICY_HEADER_VALUE
+IDS_POLICY_HIDE_EXPANDED_VALUE
+IDS_POLICY_INVALID_BOOKMARK
+IDS_POLICY_INVALID_PROXY_MODE_ERROR
+IDS_POLICY_INVALID_SEARCH_URL_ERROR
+IDS_POLICY_LABEL_ASSET_ID
+IDS_POLICY_LABEL_CLIENT_ID
+IDS_POLICY_LABEL_DIRECTORY_API_ID
+IDS_POLICY_LABEL_DOMAIN
+IDS_POLICY_LABEL_LOCATION
+IDS_POLICY_LABEL_REFRESH_INTERVAL
+IDS_POLICY_LABEL_STATUS
+IDS_POLICY_LABEL_TIME_SINCE_LAST_REFRESH
+IDS_POLICY_LABEL_USERNAME
+IDS_POLICY_LEVEL_ERROR
+IDS_POLICY_LEVEL_MANDATORY
+IDS_POLICY_LEVEL_RECOMMENDED
+IDS_POLICY_LIST_ENTRY_ERROR
+IDS_POLICY_NEVER_FETCHED
+IDS_POLICY_NOT_SPECIFIED
+IDS_POLICY_NOT_SPECIFIED_ERROR
+IDS_POLICY_NO_POLICIES_SET
+IDS_POLICY_OK
+IDS_POLICY_OUT_OF_RANGE_ERROR
+IDS_POLICY_OVERRIDDEN
+IDS_POLICY_PROXY_BOTH_SPECIFIED_ERROR
+IDS_POLICY_PROXY_MODE_AUTO_DETECT_ERROR
+IDS_POLICY_PROXY_MODE_DISABLED_ERROR
+IDS_POLICY_PROXY_MODE_FIXED_SERVERS_ERROR
+IDS_POLICY_PROXY_MODE_PAC_URL_ERROR
+IDS_POLICY_PROXY_MODE_SYSTEM_ERROR
+IDS_POLICY_PROXY_NEITHER_SPECIFIED_ERROR
+IDS_POLICY_RELOAD_POLICIES
+IDS_POLICY_SCHEMA_VALIDATION_ERROR
+IDS_POLICY_SCOPE_DEVICE
+IDS_POLICY_SCOPE_USER
+IDS_POLICY_SHOW_EXPANDED_VALUE
+IDS_POLICY_SHOW_UNSET
+IDS_POLICY_STATUS
+IDS_POLICY_STATUS_DEVICE
+IDS_POLICY_STATUS_USER
+IDS_POLICY_STORE_STATUS_BAD_STATE
+IDS_POLICY_STORE_STATUS_LOAD_ERROR
+IDS_POLICY_STORE_STATUS_OK
+IDS_POLICY_STORE_STATUS_PARSE_ERROR
+IDS_POLICY_STORE_STATUS_SERIALIZE_ERROR
+IDS_POLICY_STORE_STATUS_STORE_ERROR
+IDS_POLICY_STORE_STATUS_UNKNOWN_ERROR
+IDS_POLICY_STORE_STATUS_VALIDATION_ERROR
+IDS_POLICY_SUBKEY_ERROR
+IDS_POLICY_TITLE
+IDS_POLICY_TYPE_ERROR
+IDS_POLICY_UNKNOWN
+IDS_POLICY_UNSET
+IDS_POLICY_VALIDATION_BAD_INITIAL_SIGNATURE
+IDS_POLICY_VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE
+IDS_POLICY_VALIDATION_BAD_SIGNATURE
+IDS_POLICY_VALIDATION_BAD_TIMESTAMP
+IDS_POLICY_VALIDATION_BAD_USERNAME
+IDS_POLICY_VALIDATION_ERROR_CODE_PRESENT
+IDS_POLICY_VALIDATION_OK
+IDS_POLICY_VALIDATION_PAYLOAD_PARSE_ERROR
+IDS_POLICY_VALIDATION_POLICY_PARSE_ERROR
+IDS_POLICY_VALIDATION_UNKNOWN_ERROR
+IDS_POLICY_VALIDATION_WRONG_POLICY_TYPE
+IDS_POLICY_VALIDATION_WRONG_SETTINGS_ENTITY_ID
+IDS_POLICY_VALIDATION_WRONG_TOKEN
+IDS_PREFERENCES_CORRUPT_ERROR
+IDS_PREFERENCES_UNREADABLE_ERROR
+IDS_PRINT
+IDS_PRIVACY_POLICY_URL
+IDS_PRODUCT_NAME
+IDS_PROFILES_GUEST_PROFILE_NAME
+IDS_PROFILES_LOCAL_PROFILE_STATE
+IDS_PROFILE_TOO_NEW_ERROR
+IDS_PUSH_MESSAGES_BUBBLE_FRAGMENT
+IDS_PUSH_MESSAGES_BUBBLE_TEXT
+IDS_PUSH_MESSAGES_PERMISSION_QUESTION
+IDS_RECENT_TABS_MENU
+IDS_SAD_TAB_MESSAGE
+IDS_SAD_TAB_RELOAD_LABEL
+IDS_SAD_TAB_TITLE
+IDS_SAFEBROWSING_OVERRIDABLE_SAFETY_BUTTON
+IDS_SAFEBROWSING_V3_CLOSE_DETAILS_BUTTON
+IDS_SAFEBROWSING_V3_OPEN_DETAILS_BUTTON
+IDS_SAFEBROWSING_V3_TITLE
+IDS_SAFE_BROWSING_MALWARE_BACK_BUTTON
+IDS_SAFE_BROWSING_MALWARE_BACK_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_COLLAB_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_DIAGNOSTIC_PAGE
+IDS_SAFE_BROWSING_MALWARE_FEAR_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_LABEL
+IDS_SAFE_BROWSING_MALWARE_QUESTION_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_REPORTING_AGREE
+IDS_SAFE_BROWSING_MALWARE_TITLE
+IDS_SAFE_BROWSING_MALWARE_V2_DESCRIPTION1
+IDS_SAFE_BROWSING_MALWARE_V2_DESCRIPTION1_SUBRESOURCE
+IDS_SAFE_BROWSING_MALWARE_V2_DESCRIPTION2
+IDS_SAFE_BROWSING_MALWARE_V2_DESCRIPTION2_SUBRESOURCE
+IDS_SAFE_BROWSING_MALWARE_V2_DESCRIPTION3
+IDS_SAFE_BROWSING_MALWARE_V2_DETAILS
+IDS_SAFE_BROWSING_MALWARE_V2_DETAILS_SUBRESOURCE
+IDS_SAFE_BROWSING_MALWARE_V2_HEADLINE
+IDS_SAFE_BROWSING_MALWARE_V2_HEADLINE_SUBRESOURCE
+IDS_SAFE_BROWSING_MALWARE_V2_LEARN_MORE
+IDS_SAFE_BROWSING_MALWARE_V2_PROCEED_LINK
+IDS_SAFE_BROWSING_MALWARE_V2_REPORTING_AGREE
+IDS_SAFE_BROWSING_MALWARE_V2_SEE_MORE
+IDS_SAFE_BROWSING_MALWARE_V2_TITLE
+IDS_SAFE_BROWSING_MULTI_MALWARE_DESCRIPTION1
+IDS_SAFE_BROWSING_MULTI_MALWARE_DESCRIPTION2
+IDS_SAFE_BROWSING_MULTI_MALWARE_DESCRIPTION3
+IDS_SAFE_BROWSING_MULTI_MALWARE_DESCRIPTION_AGREE
+IDS_SAFE_BROWSING_MULTI_MALWARE_PROCEED_BUTTON
+IDS_SAFE_BROWSING_MULTI_PHISHING_DESCRIPTION1
+IDS_SAFE_BROWSING_MULTI_THREAT_DESCRIPTION1
+IDS_SAFE_BROWSING_MULTI_THREAT_DESCRIPTION2
+IDS_SAFE_BROWSING_MULTI_THREAT_TITLE
+IDS_SAFE_BROWSING_PHISHING_BACK_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_COLLAB_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_FEAR_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_LABEL
+IDS_SAFE_BROWSING_PHISHING_QUESTION_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_REPORT_ERROR
+IDS_SAFE_BROWSING_PHISHING_TITLE
+IDS_SAFE_BROWSING_PHISHING_V2_DESCRIPTION1
+IDS_SAFE_BROWSING_PHISHING_V2_DESCRIPTION2
+IDS_SAFE_BROWSING_PHISHING_V2_HEADLINE
+IDS_SAFE_BROWSING_PHISHING_V2_REPORT_ERROR
+IDS_SAFE_BROWSING_PHISHING_V2_TITLE
+IDS_SAFE_BROWSING_PRIVACY_POLICY_PAGE
+IDS_SAFE_BROWSING_PRIVACY_POLICY_PAGE_V2
+IDS_SAFE_BROWSING_PRIVACY_POLICY_URL
+IDS_SAVE
+IDS_SEARCH_BOX_EMPTY_HINT
+IDS_SECURE_CONNECTION_EV
+IDS_SESSION_CRASHED_VIEW_MESSAGE
+IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON
+IDS_SETTINGS_SHOW_ADVANCED_SETTINGS
+IDS_SHORT_PRODUCT_NAME
+IDS_SHOW_HISTORY
+IDS_SIGNED_IN_WITH_SYNC_DISABLED
+IDS_SIGNED_IN_WITH_SYNC_SUPPRESSED
+IDS_SIGNIN_ERROR_BUBBLE_VIEW_TITLE
+IDS_SINGLE_PROFILE_DISPLAY_NAME
+IDS_SSL_CLOCK_ERROR
+IDS_SSL_CLOCK_ERROR_EXPLANATION
+IDS_SSL_NONOVERRIDABLE_HSTS
+IDS_SSL_NONOVERRIDABLE_INVALID
+IDS_SSL_NONOVERRIDABLE_MORE
+IDS_SSL_NONOVERRIDABLE_MORE_INVALID_SP3
+IDS_SSL_NONOVERRIDABLE_PINNED
+IDS_SSL_NONOVERRIDABLE_REVOKED
+IDS_SSL_OVERRIDABLE_PRIMARY_PARAGRAPH
+IDS_SSL_OVERRIDABLE_PROCEED_LINK_TEXT
+IDS_SSL_OVERRIDABLE_PROCEED_PARAGRAPH
+IDS_SSL_OVERRIDABLE_SAFETY_BUTTON
+IDS_SSL_OVERRIDABLE_TITLE
+IDS_SSL_RELOAD
+IDS_SSL_V2_CLOCK_AHEAD_HEADING
+IDS_SSL_V2_CLOCK_BEHIND_HEADING
+IDS_SSL_V2_CLOCK_PRIMARY_PARAGRAPH
+IDS_SSL_V2_CLOCK_TITLE
+IDS_SSL_V2_CLOCK_UPDATE_DATE_AND_TIME
+IDS_SSL_V2_CLOSE_DETAILS_BUTTON
+IDS_SSL_V2_HEADING
+IDS_SSL_V2_OPEN_DETAILS_BUTTON
+IDS_SSL_V2_PRIMARY_PARAGRAPH
+IDS_SSL_V2_TITLE
+IDS_STARS_PROMO_LABEL_IOS
+IDS_SUPERVISED_USER_AVATAR_LABEL
+IDS_SUPERVISED_USER_NEW_AVATAR_LABEL
+IDS_SYNC_ACCOUNT_DETAILS_NOT_ENTERED
+IDS_SYNC_ACCOUNT_SYNCING_TO_USER
+IDS_SYNC_ACCOUNT_SYNCING_TO_USER_WITH_MANAGE_LINK
+IDS_SYNC_AUTHENTICATING_LABEL
+IDS_SYNC_BASIC_ENCRYPTION_DATA
+IDS_SYNC_CLEAR_USER_DATA
+IDS_SYNC_CONFIGURE_ENCRYPTION
+IDS_SYNC_DATATYPE_AUTOFILL
+IDS_SYNC_DATATYPE_BOOKMARKS
+IDS_SYNC_DATATYPE_PASSWORDS
+IDS_SYNC_DATATYPE_PREFERENCES
+IDS_SYNC_DATATYPE_TABS
+IDS_SYNC_DATATYPE_TYPED_URLS
+IDS_SYNC_EMPTY_PASSPHRASE_ERROR
+IDS_SYNC_ENABLE_SYNC_ON_ACCOUNT
+IDS_SYNC_ENCRYPTION_SECTION_TITLE
+IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY
+IDS_SYNC_ENTER_PASSPHRASE_BODY
+IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE
+IDS_SYNC_ENTER_PASSPHRASE_TITLE
+IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE
+IDS_SYNC_ERROR_SIGNING_IN
+IDS_SYNC_FULL_ENCRYPTION_DATA
+IDS_SYNC_INVALID_USER_CREDENTIALS
+IDS_SYNC_LOGIN_INFO_OUT_OF_DATE
+IDS_SYNC_LOGIN_SETTING_UP
+IDS_SYNC_MENU_PRE_SYNCED_LABEL
+IDS_SYNC_MENU_SYNCED_LABEL
+IDS_SYNC_NTP_PASSWORD_ENABLE
+IDS_SYNC_NTP_PASSWORD_PROMO
+IDS_SYNC_NTP_PASSWORD_PROMO,
+IDS_SYNC_NTP_SETUP_IN_PROGRESS
+IDS_SYNC_OPTIONS_GROUP_NAME
+IDS_SYNC_OTHER_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE
+IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_ACCEPT
+IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE
+IDS_SYNC_PASSPHRASE_ERROR_WRENCH_MENU_ITEM
+IDS_SYNC_PASSPHRASE_LABEL
+IDS_SYNC_PASSPHRASE_MISMATCH_ERROR
+IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX
+IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX
+IDS_SYNC_PASSWORD_SYNC_ATTENTION
+IDS_SYNC_PROMO_NTP_BUBBLE_MESSAGE
+IDS_SYNC_PROMO_TAB_TITLE
+IDS_SYNC_RELOGIN_LINK_LABEL
+IDS_SYNC_SERVER_IS_UNREACHABLE
+IDS_SYNC_SERVICE_UNAVAILABLE
+IDS_SYNC_SETUP_ERROR
+IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_ACCEPT
+IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE
+IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM
+IDS_SYNC_START_SYNC_BUTTON_LABEL
+IDS_SYNC_STATUS_UNRECOVERABLE_ERROR
+IDS_SYNC_STOP_AND_RESTART_SYNC
+IDS_SYNC_TIME_JUST_NOW
+IDS_SYNC_TIME_NEVER
+IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_ACCEPT
+IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_MESSAGE
+IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL
+IDS_SYNC_UPGRADE_CLIENT
+IDS_SYSTEM_FLAGS_OWNER_ONLY
+IDS_TERMS_HTML
+IDS_TIME_DAYS
+IDS_TIME_DAYS_1ST
+IDS_TIME_ELAPSED_DAYS
+IDS_TIME_ELAPSED_HOURS
+IDS_TIME_ELAPSED_MINS
+IDS_TIME_ELAPSED_SECS
+IDS_TIME_HOURS
+IDS_TIME_HOURS_1ST
+IDS_TIME_HOURS_2ND
+IDS_TIME_LONG_MINS
+IDS_TIME_LONG_MINS_1ST
+IDS_TIME_LONG_MINS_2ND
+IDS_TIME_LONG_SECS
+IDS_TIME_LONG_SECS_2ND
+IDS_TIME_MINS
+IDS_TIME_REMAINING_DAYS
+IDS_TIME_REMAINING_HOURS
+IDS_TIME_REMAINING_LONG_MINS
+IDS_TIME_REMAINING_LONG_SECS
+IDS_TIME_REMAINING_MINS
+IDS_TIME_REMAINING_SECS
+IDS_TIME_SECS
+IDS_TOOLTIP_STAR
+IDS_TOUCH_EVENTS_DESCRIPTION
+IDS_TOUCH_EVENTS_NAME
+IDS_TRANSLATE_INFOBAR_ACCEPT
+IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE
+IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE_AUTODETERMINED_SOURCE_LANGUAGE
+IDS_TRANSLATE_INFOBAR_ALWAYS_TRANSLATE
+IDS_TRANSLATE_INFOBAR_BEFORE_MESSAGE
+IDS_TRANSLATE_INFOBAR_BEFORE_MESSAGE_IOS
+IDS_TRANSLATE_INFOBAR_DENY
+IDS_TRANSLATE_INFOBAR_ERROR_CANT_CONNECT
+IDS_TRANSLATE_INFOBAR_ERROR_CANT_TRANSLATE
+IDS_TRANSLATE_INFOBAR_ERROR_SAME_LANGUAGE
+IDS_TRANSLATE_INFOBAR_NEVER_MESSAGE_IOS
+IDS_TRANSLATE_INFOBAR_NEVER_TRANSLATE
+IDS_TRANSLATE_INFOBAR_OPTIONS_ABOUT
+IDS_TRANSLATE_INFOBAR_OPTIONS_ALWAYS
+IDS_TRANSLATE_INFOBAR_OPTIONS_NEVER_TRANSLATE_LANG
+IDS_TRANSLATE_INFOBAR_OPTIONS_NEVER_TRANSLATE_SITE
+IDS_TRANSLATE_INFOBAR_OPTIONS_REPORT_ERROR
+IDS_TRANSLATE_INFOBAR_RETRY
+IDS_TRANSLATE_INFOBAR_REVERT
+IDS_TRANSLATE_INFOBAR_TRANSLATING_TO
+IDS_TRANSLATE_INFOBAR_UNKNOWN_PAGE_LANGUAGE
+IDS_TRANSLATE_INFOBAR_UNSUPPORTED_PAGE_LANGUAGE
+IDS_UPGRADE_AVAILABLE
+IDS_UPGRADE_AVAILABLE_BUTTON
+IDS_WEB_FONT_FAMILY
+IDS_WEB_FONT_SIZE
diff --git a/build/ios/mac_build.gypi b/build/ios/mac_build.gypi
new file mode 100644
index 00000000000..4da21eb2535
--- /dev/null
+++ b/build/ios/mac_build.gypi
@@ -0,0 +1,83 @@
+# Copyright (c) 2012 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.
+
+# Xcode throws an error if an iOS target depends on a Mac OS X target. So
+# any place a utility program needs to be build and run, an action is
+# used to run ninja as script to work around this.
+# Example:
+# {
+# 'target_name': 'foo',
+# 'type': 'none',
+# 'variables': {
+# # The name of a directory used for ninja. This cannot be shared with
+# # another mac build.
+# 'ninja_output_dir': 'ninja-foo',
+# # The full path to the location in which the ninja executable should be
+# # placed. This cannot be shared with another mac build.
+# 'ninja_product_dir':
+# '<(DEPTH)/xcodebuild/<(ninja_output_dir)/<(CONFIGURATION_NAME)',
+# # The list of all the gyp files that contain the targets to run.
+# 're_run_targets': [
+# 'foo.gyp',
+# ],
+# },
+# 'includes': ['path_to/mac_build.gypi'],
+# 'actions': [
+# {
+# 'action_name': 'compile foo',
+# 'inputs': [],
+# 'outputs': [],
+# 'action': [
+# '<@(ninja_cmd)',
+# # All the targets to build.
+# 'foo1',
+# 'foo2',
+# ],
+# },
+# ],
+# }
+{
+ 'variables': {
+ 'variables': {
+ 'parent_generator%': '<(GENERATOR)',
+ },
+ 'parent_generator%': '<(parent_generator)',
+ # Common ninja command line flags.
+ 'ninja_cmd': [
+ # Bounce through clean_env to clean up the environment so things
+ # set by the iOS build don't pollute the Mac build.
+ '<(DEPTH)/build/ios/clean_env.py',
+ # ninja must be found in the PATH.
+ 'ADD_TO_PATH=/src.
+# Optional/automatic variables:
+# add_to_dependents_classpaths - Set to 0 if the resulting jar file should not
+# be added to its dependents' classpaths.
+# additional_input_paths - These paths will be included in the 'inputs' list to
+# ensure that this target is rebuilt when one of these paths changes.
+# additional_src_dirs - Additional directories with .java files to be compiled
+# and included in the output of this target.
+# generated_src_dirs - Same as additional_src_dirs except used for .java files
+# that are generated at build time. This should be set automatically by a
+# target's dependencies. The .java files in these directories are not
+# included in the 'inputs' list (unlike additional_src_dirs).
+# input_jars_paths - The path to jars to be included in the classpath. This
+# should be filled automatically by depending on the appropriate targets.
+# javac_includes - A list of specific files to include. This is by default
+# empty, which leads to inclusion of all files specified. May include
+# wildcard, and supports '**/' for recursive path wildcards, ie.:
+# '**/MyFileRegardlessOfDirectory.java', '**/IncludedPrefix*.java'.
+# has_java_resources - Set to 1 if the java target contains an
+# Android-compatible resources folder named res. If 1, R_package and
+# R_package_relpath must also be set.
+# R_package - The java package in which the R class (which maps resources to
+# integer IDs) should be generated, e.g. org.chromium.content.
+# R_package_relpath - Same as R_package, but replace each '.' with '/'.
+# res_extra_dirs - A list of extra directories containing Android resources.
+# These directories may be generated at build time.
+# res_extra_files - A list of the files in res_extra_dirs.
+# never_lint - Set to 1 to not run lint on this target.
+
+{
+ 'dependencies': [
+ '<(DEPTH)/build/android/setup.gyp:build_output_dirs'
+ ],
+ 'variables': {
+ 'add_to_dependents_classpaths%': 1,
+ 'android_jar': '<(android_sdk)/android.jar',
+ 'input_jars_paths': [ '<(android_jar)' ],
+ 'additional_src_dirs': [],
+ 'javac_includes': [],
+ 'jar_name': '<(_target_name).jar',
+ 'jar_dir': '<(PRODUCT_DIR)/lib.java',
+ 'jar_path': '<(intermediate_dir)/<(jar_name)',
+ 'jar_final_path': '<(jar_dir)/<(jar_name)',
+ 'jar_excluded_classes': [ '*/R.class', '*/R##*.class' ],
+ 'instr_stamp': '<(intermediate_dir)/instr.stamp',
+ 'additional_input_paths': [],
+ 'dex_path': '<(PRODUCT_DIR)/lib.java/<(_target_name).dex.jar',
+ 'generated_src_dirs': ['>@(generated_R_dirs)'],
+ 'generated_R_dirs': [],
+ 'has_java_resources%': 0,
+ 'res_extra_dirs': [],
+ 'res_extra_files': [],
+ 'res_v14_skip%': 0,
+ 'resource_input_paths': ['>@(res_extra_files)'],
+ 'intermediate_dir': '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)',
+ 'compile_stamp': '<(intermediate_dir)/compile.stamp',
+ 'lint_stamp': '<(intermediate_dir)/lint.stamp',
+ 'lint_result': '<(intermediate_dir)/lint_result.xml',
+ 'lint_config': '<(intermediate_dir)/lint_config.xml',
+ 'never_lint%': 0,
+ 'findbugs_stamp': '<(intermediate_dir)/findbugs.stamp',
+ 'run_findbugs%': 0,
+ 'java_in_dir_suffix%': '/src',
+ 'proguard_config%': '',
+ 'proguard_preprocess%': '0',
+ 'enable_errorprone%': '0',
+ 'errorprone_exe_path': '<(PRODUCT_DIR)/bin.java/chromium_errorprone',
+ 'variables': {
+ 'variables': {
+ 'proguard_preprocess%': 0,
+ 'emma_never_instrument%': 0,
+ },
+ 'conditions': [
+ ['proguard_preprocess == 1', {
+ 'javac_jar_path': '<(intermediate_dir)/<(_target_name).pre.jar'
+ }, {
+ 'javac_jar_path': '<(jar_path)'
+ }],
+ ['chromium_code != 0 and emma_coverage != 0 and emma_never_instrument == 0', {
+ 'emma_instrument': 1,
+ }, {
+ 'emma_instrument': 0,
+ }],
+ ],
+ },
+ 'emma_instrument': '<(emma_instrument)',
+ 'javac_jar_path': '<(javac_jar_path)',
+ },
+ 'conditions': [
+ ['add_to_dependents_classpaths == 1', {
+ # This all_dependent_settings is used for java targets only. This will add the
+ # jar path to the classpath of dependent java targets.
+ 'all_dependent_settings': {
+ 'variables': {
+ 'input_jars_paths': ['<(jar_final_path)'],
+ 'library_dexed_jars_paths': ['<(dex_path)'],
+ },
+ },
+ }],
+ ['has_java_resources == 1', {
+ 'variables': {
+ 'resource_dir': '<(java_in_dir)/res',
+ 'res_input_dirs': ['<(resource_dir)', '<@(res_extra_dirs)'],
+ 'resource_input_paths': ['|(java_resources.<(_target_name).gypcmd >@(resource_input_paths))',
+ 'process_resources_options': [],
+ 'conditions': [
+ ['res_v14_skip == 1', {
+ 'process_resources_options': ['--v14-skip']
+ }],
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/process_resources.py',
+ '<(DEPTH)/build/android/gyp/generate_v14_compatible_resources.py',
+ '>@(resource_input_paths)',
+ '>@(dependencies_res_zip_paths)',
+ '>(inputs_list_file)',
+ ],
+ 'outputs': [
+ '<(resource_zip_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/process_resources.py',
+ '--android-sdk', '<(android_sdk)',
+ '--aapt-path', '<(android_aapt_path)',
+ '--non-constant-id',
+
+ '--android-manifest', '<(android_manifest)',
+ '--custom-package', '<(R_package)',
+
+ '--dependencies-res-zips', '>(dependencies_res_zip_paths)',
+ '--resource-dirs', '<(res_input_dirs)',
+
+ '--R-dir', '<(R_dir)',
+ '--resource-zip-out', '<(resource_zip_path)',
+
+ '<@(process_resources_options)',
+ ],
+ },
+ ],
+ }],
+ ['proguard_preprocess == 1', {
+ 'actions': [
+ {
+ 'action_name': 'proguard_<(_target_name)',
+ 'message': 'Proguard preprocessing <(_target_name) jar',
+ 'inputs': [
+ '<(android_sdk_root)/tools/proguard/lib/proguard.jar',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/proguard.py',
+ '<(javac_jar_path)',
+ '<(proguard_config)',
+ ],
+ 'outputs': [
+ '<(jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/proguard.py',
+ '--proguard-path=<(android_sdk_root)/tools/proguard/lib/proguard.jar',
+ '--input-path=<(javac_jar_path)',
+ '--output-path=<(jar_path)',
+ '--proguard-config=<(proguard_config)',
+ '--classpath=<(android_sdk_jar) >(input_jars_paths)',
+ ]
+ },
+ ],
+ }],
+ ['run_findbugs == 1', {
+ 'actions': [
+ {
+ 'action_name': 'findbugs_<(_target_name)',
+ 'message': 'Running findbugs on <(_target_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/findbugs_diff.py',
+ '<(DEPTH)/build/android/findbugs_filter/findbugs_exclude.xml',
+ '<(DEPTH)/build/android/pylib/utils/findbugs.py',
+ '>@(input_jars_paths)',
+ '<(jar_final_path)',
+ '<(compile_stamp)',
+ ],
+ 'outputs': [
+ '<(findbugs_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/findbugs_diff.py',
+ '--auxclasspath-gyp', '>(input_jars_paths)',
+ '--stamp', '<(findbugs_stamp)',
+ '<(jar_final_path)',
+ ],
+ },
+ ],
+ }],
+ ['enable_errorprone == 1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/errorprone/errorprone.gyp:chromium_errorprone',
+ ],
+ }],
+ ],
+ 'actions': [
+ {
+ 'action_name': 'javac_<(_target_name)',
+ 'message': 'Compiling <(_target_name) java sources',
+ 'variables': {
+ 'extra_args': [],
+ 'extra_inputs': [],
+ 'java_sources': ['>!@(find >(java_in_dir)>(java_in_dir_suffix) >(additional_src_dirs) -name "*.java")'],
+ 'conditions': [
+ ['enable_errorprone == 1', {
+ 'extra_inputs': [
+ '<(errorprone_exe_path)',
+ ],
+ 'extra_args': [ '--use-errorprone-path=<(errorprone_exe_path)' ],
+ }],
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/javac.py',
+ '>@(java_sources)',
+ '>@(input_jars_paths)',
+ '>@(additional_input_paths)',
+ '<@(extra_inputs)',
+ ],
+ 'outputs': [
+ '<(compile_stamp)',
+ '<(javac_jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/javac.py',
+ '--bootclasspath=<(android_sdk_jar)',
+ '--classpath=>(input_jars_paths)',
+ '--src-gendirs=>(generated_src_dirs)',
+ '--javac-includes=<(javac_includes)',
+ '--chromium-code=<(chromium_code)',
+ '--jar-path=<(javac_jar_path)',
+ '--jar-excluded-classes=<(jar_excluded_classes)',
+ '--stamp=<(compile_stamp)',
+ '>@(java_sources)',
+ '<@(extra_args)',
+ ]
+ },
+ {
+ 'action_name': 'instr_jar_<(_target_name)',
+ 'message': 'Instrumenting <(_target_name) jar',
+ 'variables': {
+ 'input_path': '<(jar_path)',
+ 'output_path': '<(jar_final_path)',
+ 'stamp_path': '<(instr_stamp)',
+ 'instr_type': 'jar',
+ },
+ 'outputs': [
+ '<(jar_final_path)',
+ ],
+ 'inputs': [
+ '<(jar_path)',
+ ],
+ 'includes': [ 'android/instr_action.gypi' ],
+ },
+ {
+ 'variables': {
+ 'src_dirs': [
+ '<(java_in_dir)<(java_in_dir_suffix)',
+ '>@(additional_src_dirs)',
+ ],
+ 'stamp_path': '<(lint_stamp)',
+ 'result_path': '<(lint_result)',
+ 'config_path': '<(lint_config)',
+ 'lint_jar_path': '<(jar_final_path)',
+ },
+ 'inputs': [
+ '<(jar_final_path)',
+ '<(compile_stamp)',
+ ],
+ 'outputs': [
+ '<(lint_stamp)',
+ ],
+ 'includes': [ 'android/lint_action.gypi' ],
+ },
+ {
+ 'action_name': 'jar_toc_<(_target_name)',
+ 'message': 'Creating <(_target_name) jar.TOC',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/util/md5_check.py',
+ '<(DEPTH)/build/android/gyp/jar_toc.py',
+ '<(jar_final_path)',
+ ],
+ 'outputs': [
+ '<(jar_final_path).TOC',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/jar_toc.py',
+ '--jar-path=<(jar_final_path)',
+ '--toc-path=<(jar_final_path).TOC',
+ ]
+ },
+ {
+ 'action_name': 'dex_<(_target_name)',
+ 'variables': {
+ 'conditions': [
+ ['emma_instrument != 0', {
+ 'dex_no_locals': 1,
+ }],
+ ],
+ 'dex_input_paths': [ '<(jar_final_path)' ],
+ 'output_path': '<(dex_path)',
+ },
+ 'includes': [ 'android/dex_action.gypi' ],
+ },
+ ],
+}
diff --git a/build/java_aidl.gypi b/build/java_aidl.gypi
new file mode 100644
index 00000000000..dda28942e2b
--- /dev/null
+++ b/build/java_aidl.gypi
@@ -0,0 +1,79 @@
+# Copyright (c) 2012 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 file is meant to be included into a target to provide a rule
+# to build Java aidl files in a consistent manner.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'aidl_aidl-file-name',
+# 'type': 'none',
+# 'variables': {
+# 'aidl_interface_file': '/.aidl',
+# 'aidl_import_include': '<(DEPTH)/',
+# },
+# 'sources': {
+# '/.aidl',
+# '/.aidl',
+# ...
+# },
+# 'includes': ['/java_aidl.gypi'],
+# }
+#
+#
+# The generated java files will be:
+# <(PRODUCT_DIR)/lib.java/.java
+# <(PRODUCT_DIR)/lib.java/.java
+# ...
+#
+# Optional variables:
+# aidl_import_include - This should be an absolute path to your java src folder
+# that contains the classes that are imported by your aidl files.
+#
+# TODO(cjhopman): dependents need to rebuild when this target's inputs have changed.
+
+{
+ 'variables': {
+ 'aidl_path%': '<(android_sdk_tools)/aidl',
+ 'intermediate_dir': '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)/aidl',
+ 'aidl_import_include%': '',
+ 'additional_aidl_arguments': [],
+ 'additional_aidl_input_paths': [],
+ },
+ 'direct_dependent_settings': {
+ 'variables': {
+ 'generated_src_dirs': ['<(intermediate_dir)/'],
+ },
+ },
+ 'conditions': [
+ ['aidl_import_include != ""', {
+ 'variables': {
+ 'additional_aidl_arguments': [ '-I<(aidl_import_include)' ],
+ 'additional_aidl_input_paths': [ '.apk
+# java_in_dir - The top-level java directory. The src should be in
+# <(java_in_dir)/src.
+# Optional/automatic variables:
+# additional_input_paths - These paths will be included in the 'inputs' list to
+# ensure that this target is rebuilt when one of these paths changes.
+# additional_res_packages - Package names of R.java files generated in addition
+# to the default package name defined in AndroidManifest.xml.
+# additional_src_dirs - Additional directories with .java files to be compiled
+# and included in the output of this target.
+# additional_bundled_libs - Additional libraries what will be stripped and
+# bundled in the apk.
+# asset_location - The directory where assets are located.
+# create_abi_split - Whether to create abi-based spilts. Splits
+# are supported only for minSdkVersion >= 21.
+# create_density_splits - Whether to create density-based apk splits.
+# language_splits - List of languages to create apk splits for.
+# generated_src_dirs - Same as additional_src_dirs except used for .java files
+# that are generated at build time. This should be set automatically by a
+# target's dependencies. The .java files in these directories are not
+# included in the 'inputs' list (unlike additional_src_dirs).
+# input_jars_paths - The path to jars to be included in the classpath. This
+# should be filled automatically by depending on the appropriate targets.
+# is_test_apk - Set to 1 if building a test apk. This prevents resources from
+# dependencies from being re-included.
+# native_lib_target - The target_name of the target which generates the final
+# shared library to be included in this apk. A stripped copy of the
+# library will be included in the apk.
+# resource_dir - The directory for resources.
+# shared_resources - Make a resource package that can be loaded by a different
+# application at runtime to access the package's resources.
+# R_package - A custom Java package to generate the resource file R.java in.
+# By default, the package given in AndroidManifest.xml will be used.
+# include_all_resources - Set to 1 to include all resource IDs in all generated
+# R.java files.
+# use_chromium_linker - Enable the content dynamic linker that allows sharing the
+# RELRO section of the native libraries between the different processes.
+# load_library_from_zip - When using the dynamic linker, load the library
+# directly out of the zip file.
+# use_relocation_packer - Enable relocation packing. Relies on the chromium
+# linker, so use_chromium_linker must also be enabled.
+# enable_chromium_linker_tests - Enable the content dynamic linker test support
+# code. This allows a test APK to inject a Linker.TestRunner instance at
+# runtime. Should only be used by the chromium_linker_test_apk target!!
+# never_lint - Set to 1 to not run lint on this target.
+# java_in_dir_suffix - To override the /src suffix on java_in_dir.
+# app_manifest_version_name - set the apps 'human readable' version number.
+# app_manifest_version_code - set the apps version number.
+{
+ 'variables': {
+ 'tested_apk_obfuscated_jar_path%': '/',
+ 'tested_apk_dex_path%': '/',
+ 'additional_input_paths': [],
+ 'create_density_splits%': 0,
+ 'language_splits': [],
+ 'input_jars_paths': [],
+ 'library_dexed_jars_paths': [],
+ 'additional_src_dirs': [],
+ 'generated_src_dirs': [],
+ 'app_manifest_version_name%': '<(android_app_version_name)',
+ 'app_manifest_version_code%': '<(android_app_version_code)',
+ # aapt generates this proguard.txt.
+ 'generated_proguard_file': '<(intermediate_dir)/proguard.txt',
+ 'proguard_enabled%': 'false',
+ 'proguard_flags_paths': ['<(generated_proguard_file)'],
+ 'jar_name': 'chromium_apk_<(_target_name).jar',
+ 'resource_dir%':'<(DEPTH)/build/android/ant/empty/res',
+ 'R_package%':'',
+ 'include_all_resources%': 0,
+ 'additional_R_text_files': [],
+ 'dependencies_res_zip_paths': [],
+ 'additional_res_packages': [],
+ 'additional_bundled_libs%': [],
+ 'is_test_apk%': 0,
+ # Allow icu data, v8 snapshots, and pak files to be loaded directly from the .apk.
+ # Note: These are actually suffix matches, not necessarily extensions.
+ 'extensions_to_not_compress%': '.dat,.bin,.pak',
+ 'resource_input_paths': [],
+ 'intermediate_dir': '<(PRODUCT_DIR)/<(_target_name)',
+ 'asset_location%': '<(intermediate_dir)/assets',
+ 'codegen_stamp': '<(intermediate_dir)/codegen.stamp',
+ 'package_input_paths': [],
+ 'ordered_libraries_file': '<(intermediate_dir)/native_libraries.json',
+ 'additional_ordered_libraries_file': '<(intermediate_dir)/additional_native_libraries.json',
+ 'native_libraries_template': '<(DEPTH)/base/android/java/templates/NativeLibraries.template',
+ 'native_libraries_java_dir': '<(intermediate_dir)/native_libraries_java/',
+ 'native_libraries_java_file': '<(native_libraries_java_dir)/NativeLibraries.java',
+ 'native_libraries_java_stamp': '<(intermediate_dir)/native_libraries_java.stamp',
+ 'native_libraries_template_data_dir': '<(intermediate_dir)/native_libraries/',
+ 'native_libraries_template_data_file': '<(native_libraries_template_data_dir)/native_libraries_array.h',
+ 'native_libraries_template_version_file': '<(native_libraries_template_data_dir)/native_libraries_version.h',
+ 'compile_stamp': '<(intermediate_dir)/compile.stamp',
+ 'lint_stamp': '<(intermediate_dir)/lint.stamp',
+ 'lint_result': '<(intermediate_dir)/lint_result.xml',
+ 'lint_config': '<(intermediate_dir)/lint_config.xml',
+ 'never_lint%': 0,
+ 'findbugs_stamp': '<(intermediate_dir)/findbugs.stamp',
+ 'run_findbugs%': 0,
+ 'java_in_dir_suffix%': '/src',
+ 'instr_stamp': '<(intermediate_dir)/instr.stamp',
+ 'jar_stamp': '<(intermediate_dir)/jar.stamp',
+ 'obfuscate_stamp': '<(intermediate_dir)/obfuscate.stamp',
+ 'pack_relocations_stamp': '<(intermediate_dir)/pack_relocations.stamp',
+ 'strip_stamp': '<(intermediate_dir)/strip.stamp',
+ 'stripped_libraries_dir': '<(intermediate_dir)/stripped_libraries',
+ 'strip_additional_stamp': '<(intermediate_dir)/strip_additional.stamp',
+ 'version_stamp': '<(intermediate_dir)/version.stamp',
+ 'javac_includes': [],
+ 'jar_excluded_classes': [],
+ 'javac_jar_path': '<(intermediate_dir)/<(_target_name).javac.jar',
+ 'jar_path': '<(PRODUCT_DIR)/lib.java/<(jar_name)',
+ 'obfuscated_jar_path': '<(intermediate_dir)/obfuscated.jar',
+ 'test_jar_path': '<(PRODUCT_DIR)/test.lib.java/<(apk_name).jar',
+ 'dex_path': '<(intermediate_dir)/classes.dex',
+ 'emma_device_jar': '<(android_sdk_root)/tools/lib/emma_device.jar',
+ 'android_manifest_path%': '<(java_in_dir)/AndroidManifest.xml',
+ 'split_android_manifest_path': '<(intermediate_dir)/split-manifests/<(android_app_abi)/AndroidManifest.xml',
+ 'push_stamp': '<(intermediate_dir)/push.stamp',
+ 'link_stamp': '<(intermediate_dir)/link.stamp',
+ 'resource_zip_path': '<(intermediate_dir)/<(_target_name).resources.zip',
+ 'shared_resources%': 0,
+ 'final_apk_path%': '<(PRODUCT_DIR)/apks/<(apk_name).apk',
+ 'final_apk_path_no_extension%': '<(PRODUCT_DIR)/apks/<(apk_name)',
+ 'final_abi_split_apk_path%': '<(PRODUCT_DIR)/apks/<(apk_name)-abi-<(android_app_abi).apk',
+ 'incomplete_apk_path': '<(intermediate_dir)/<(apk_name)-incomplete.apk',
+ 'apk_install_record': '<(intermediate_dir)/apk_install.record.stamp',
+ 'device_intermediate_dir': '/data/data/org.chromium.gyp_managed_install/<(_target_name)/<(CONFIGURATION_NAME)',
+ 'symlink_script_host_path': '<(intermediate_dir)/create_symlinks.sh',
+ 'symlink_script_device_path': '<(device_intermediate_dir)/create_symlinks.sh',
+ 'create_standalone_apk%': 1,
+ 'res_v14_skip%': 0,
+ 'variables': {
+ 'variables': {
+ 'native_lib_target%': '',
+ 'native_lib_version_name%': '',
+ 'use_chromium_linker%' : 0,
+ 'use_relocation_packer%' : 0,
+ 'enable_chromium_linker_tests%': 0,
+ 'is_test_apk%': 0,
+ 'unsigned_apk_path': '<(intermediate_dir)/<(apk_name)-unsigned.apk',
+ 'unsigned_abi_split_apk_path': '<(intermediate_dir)/<(apk_name)-abi-<(android_app_abi)-unsigned.apk',
+ 'create_abi_split%': 0,
+ },
+ 'unsigned_apk_path': '<(unsigned_apk_path)',
+ 'unsigned_abi_split_apk_path': '<(unsigned_abi_split_apk_path)',
+ 'create_abi_split%': '<(create_abi_split)',
+ 'conditions': [
+ ['gyp_managed_install == 1 and native_lib_target != ""', {
+ 'conditions': [
+ ['create_abi_split == 0', {
+ 'unsigned_standalone_apk_path': '<(intermediate_dir)/<(apk_name)-standalone-unsigned.apk',
+ }, {
+ 'unsigned_standalone_apk_path': '<(intermediate_dir)/<(apk_name)-abi-<(android_app_abi)-standalone-unsigned.apk',
+ }],
+ ],
+ }, {
+ 'unsigned_standalone_apk_path': '<(unsigned_apk_path)',
+ }],
+ ['gyp_managed_install == 1', {
+ 'apk_package_native_libs_dir': '<(intermediate_dir)/libs.managed',
+ }, {
+ 'apk_package_native_libs_dir': '<(intermediate_dir)/libs',
+ }],
+ ['is_test_apk == 0 and emma_coverage != 0', {
+ 'emma_instrument%': 1,
+ },{
+ 'emma_instrument%': 0,
+ }],
+ # When using abi splits, the abi split is modified by
+ # gyp_managed_install rather than the main .apk
+ ['create_abi_split == 1', {
+ 'managed_input_apk_path': '<(unsigned_abi_split_apk_path)',
+ }, {
+ 'managed_input_apk_path': '<(unsigned_apk_path)',
+ }],
+ ],
+ },
+ 'native_lib_target%': '',
+ 'native_lib_version_name%': '',
+ 'use_chromium_linker%' : 0,
+ 'load_library_from_zip%' : 0,
+ 'use_relocation_packer%' : 0,
+ 'enable_chromium_linker_tests%': 0,
+ 'emma_instrument%': '<(emma_instrument)',
+ 'apk_package_native_libs_dir': '<(apk_package_native_libs_dir)',
+ 'unsigned_standalone_apk_path': '<(unsigned_standalone_apk_path)',
+ 'unsigned_apk_path': '<(unsigned_apk_path)',
+ 'unsigned_abi_split_apk_path': '<(unsigned_abi_split_apk_path)',
+ 'create_abi_split%': '<(create_abi_split)',
+ 'managed_input_apk_path': '<(managed_input_apk_path)',
+ 'libchromium_android_linker': 'libchromium_android_linker.>(android_product_extension)',
+ 'extra_native_libs': [],
+ 'native_lib_placeholder_stamp': '<(apk_package_native_libs_dir)/<(android_app_abi)/native_lib_placeholder.stamp',
+ 'native_lib_placeholders': [],
+ 'main_apk_name': '<(apk_name)',
+ 'enable_errorprone%': '0',
+ 'errorprone_exe_path': '<(PRODUCT_DIR)/bin.java/chromium_errorprone',
+ },
+ # Pass the jar path to the apk's "fake" jar target. This would be better as
+ # direct_dependent_settings, but a variable set by a direct_dependent_settings
+ # cannot be lifted in a dependent to all_dependent_settings.
+ 'all_dependent_settings': {
+ 'conditions': [
+ ['proguard_enabled == "true"', {
+ 'variables': {
+ 'proguard_enabled': 'true',
+ }
+ }],
+ ],
+ 'variables': {
+ 'apk_output_jar_path': '<(jar_path)',
+ 'tested_apk_obfuscated_jar_path': '<(obfuscated_jar_path)',
+ 'tested_apk_dex_path': '<(dex_path)',
+ },
+ },
+ 'conditions': [
+ ['resource_dir!=""', {
+ 'variables': {
+ 'resource_input_paths': [ '(android_product_extension)',
+ '<@(chromium_linker_path)'
+ ],
+ 'package_input_paths': [
+ '<(apk_package_native_libs_dir)/<(android_app_abi)/gdbserver',
+ ],
+ },
+ 'copies': [
+ {
+ # gdbserver is always copied into the APK's native libs dir. The ant
+ # build scripts (apkbuilder task) will only include it in a debug
+ # build.
+ 'destination': '<(apk_package_native_libs_dir)/<(android_app_abi)',
+ 'files': [
+ '<(android_gdbserver)',
+ ],
+ },
+ ],
+ 'actions': [
+ {
+ 'variables': {
+ 'input_libraries': [
+ '<@(native_libs_paths)',
+ '<@(extra_native_libs)',
+ ],
+ },
+ 'includes': ['../build/android/write_ordered_libraries.gypi'],
+ },
+ {
+ 'action_name': 'native_libraries_<(_target_name)',
+ 'variables': {
+ 'conditions': [
+ ['use_chromium_linker == 1', {
+ 'variables': {
+ 'linker_gcc_preprocess_defines': [
+ '--defines', 'ENABLE_CHROMIUM_LINKER',
+ ],
+ }
+ }, {
+ 'variables': {
+ 'linker_gcc_preprocess_defines': [],
+ },
+ }],
+ ['load_library_from_zip == 1', {
+ 'variables': {
+ 'linker_load_from_zip_file_preprocess_defines': [
+ '--defines', 'ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE',
+ ],
+ }
+ }, {
+ 'variables': {
+ 'linker_load_from_zip_file_preprocess_defines': [],
+ },
+ }],
+ ['enable_chromium_linker_tests == 1', {
+ 'variables': {
+ 'linker_tests_gcc_preprocess_defines': [
+ '--defines', 'ENABLE_CHROMIUM_LINKER_TESTS',
+ ],
+ }
+ }, {
+ 'variables': {
+ 'linker_tests_gcc_preprocess_defines': [],
+ },
+ }],
+ ],
+ 'gcc_preprocess_defines': [
+ '<@(linker_load_from_zip_file_preprocess_defines)',
+ '<@(linker_gcc_preprocess_defines)',
+ '<@(linker_tests_gcc_preprocess_defines)',
+ ],
+ },
+ 'message': 'Creating NativeLibraries.java for <(_target_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/gcc_preprocess.py',
+ '<(ordered_libraries_file)',
+ '<(native_libraries_template)',
+ ],
+ 'outputs': [
+ '<(native_libraries_java_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/gcc_preprocess.py',
+ '--include-path=',
+ '--output=<(native_libraries_java_file)',
+ '--template=<(native_libraries_template)',
+ '--stamp=<(native_libraries_java_stamp)',
+ '--defines', 'NATIVE_LIBRARIES_LIST=@FileArg(<(ordered_libraries_file):java_libraries_list)',
+ '--defines', 'NATIVE_LIBRARIES_VERSION_NUMBER="<(native_lib_version_name)"',
+ '<@(gcc_preprocess_defines)',
+ ],
+ },
+ {
+ 'action_name': 'strip_native_libraries',
+ 'variables': {
+ 'ordered_libraries_file%': '<(ordered_libraries_file)',
+ 'stripped_libraries_dir%': '<(stripped_libraries_dir)',
+ 'input_paths': [
+ '<@(native_libs_paths)',
+ '<@(extra_native_libs)',
+ ],
+ 'stamp': '<(strip_stamp)'
+ },
+ 'includes': ['../build/android/strip_native_libraries.gypi'],
+ },
+ {
+ 'action_name': 'insert_chromium_version',
+ 'variables': {
+ 'ordered_libraries_file%': '<(ordered_libraries_file)',
+ 'stripped_libraries_dir%': '<(stripped_libraries_dir)',
+ 'version_string': '<(native_lib_version_name)',
+ 'input_paths': [
+ '<(strip_stamp)',
+ ],
+ 'stamp': '<(version_stamp)'
+ },
+ 'includes': ['../build/android/insert_chromium_version.gypi'],
+ },
+ {
+ 'action_name': 'pack_relocations',
+ 'variables': {
+ 'conditions': [
+ ['use_chromium_linker == 1 and use_relocation_packer == 1 and profiling != 1', {
+ 'enable_packing': 1,
+ }, {
+ 'enable_packing': 0,
+ }],
+ ],
+ 'exclude_packing_list': [
+ '<(libchromium_android_linker)',
+ ],
+ 'ordered_libraries_file%': '<(ordered_libraries_file)',
+ 'stripped_libraries_dir%': '<(stripped_libraries_dir)',
+ 'packed_libraries_dir': '<(libraries_source_dir)',
+ 'input_paths': [
+ '<(version_stamp)'
+ ],
+ 'stamp': '<(pack_relocations_stamp)',
+ },
+ 'includes': ['../build/android/pack_relocations.gypi'],
+ },
+ {
+ 'variables': {
+ 'input_libraries': [
+ '<@(additional_bundled_libs)',
+ ],
+ 'ordered_libraries_file': '<(additional_ordered_libraries_file)',
+ 'subtarget': '_additional_libraries',
+ },
+ 'includes': ['../build/android/write_ordered_libraries.gypi'],
+ },
+ {
+ 'action_name': 'strip_additional_libraries',
+ 'variables': {
+ 'ordered_libraries_file': '<(additional_ordered_libraries_file)',
+ 'stripped_libraries_dir': '<(libraries_source_dir)',
+ 'input_paths': [
+ '<@(additional_bundled_libs)',
+ '<(strip_stamp)',
+ ],
+ 'stamp': '<(strip_additional_stamp)'
+ },
+ 'includes': ['../build/android/strip_native_libraries.gypi'],
+ },
+ {
+ 'action_name': 'Create native lib placeholder files for previous releases',
+ 'variables': {
+ 'placeholders': ['<@(native_lib_placeholders)'],
+ 'conditions': [
+ ['gyp_managed_install == 1', {
+ # This "library" just needs to be put in the .apk. It is not loaded
+ # at runtime.
+ 'placeholders': ['libfix.crbug.384638.so'],
+ }]
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/create_placeholder_files.py',
+ ],
+ 'outputs': [
+ '<(native_lib_placeholder_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/create_placeholder_files.py',
+ '--dest-lib-dir=<(apk_package_native_libs_dir)/<(android_app_abi)/',
+ '--stamp=<(native_lib_placeholder_stamp)',
+ '<@(placeholders)',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['gyp_managed_install == 1', {
+ 'variables': {
+ 'libraries_top_dir': '<(intermediate_dir)/lib.stripped',
+ 'libraries_source_dir': '<(libraries_top_dir)/lib/<(android_app_abi)',
+ 'device_library_dir': '<(device_intermediate_dir)/lib.stripped',
+ 'configuration_name': '<(CONFIGURATION_NAME)',
+ },
+ 'dependencies': [
+ '<(DEPTH)/build/android/setup.gyp:get_build_device_configurations',
+ '<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
+ ],
+ 'actions': [
+ {
+ 'includes': ['../build/android/push_libraries.gypi'],
+ },
+ {
+ 'action_name': 'create device library symlinks',
+ 'message': 'Creating links on device for <(_target_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/create_device_library_links.py',
+ '<(apk_install_record)',
+ '<(build_device_config_path)',
+ '<(ordered_libraries_file)',
+ ],
+ 'outputs': [
+ '<(link_stamp)'
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/create_device_library_links.py',
+ '--build-device-configuration=<(build_device_config_path)',
+ '--libraries=@FileArg(<(ordered_libraries_file):libraries)',
+ '--script-host-path=<(symlink_script_host_path)',
+ '--script-device-path=<(symlink_script_device_path)',
+ '--target-dir=<(device_library_dir)',
+ '--apk=<(incomplete_apk_path)',
+ '--stamp=<(link_stamp)',
+ '--configuration-name=<(CONFIGURATION_NAME)',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['create_standalone_apk == 1', {
+ 'actions': [
+ {
+ 'action_name': 'create standalone APK',
+ 'variables': {
+ 'inputs': [
+ '<(ordered_libraries_file)',
+ '<(strip_additional_stamp)',
+ '<(pack_relocations_stamp)',
+ ],
+ 'output_apk_path': '<(unsigned_standalone_apk_path)',
+ 'libraries_top_dir%': '<(libraries_top_dir)',
+ 'input_apk_path': '<(managed_input_apk_path)',
+ },
+ 'includes': [ 'android/create_standalone_apk_action.gypi' ],
+ },
+ ],
+ }],
+ ],
+ }, {
+ # gyp_managed_install != 1
+ 'variables': {
+ 'libraries_source_dir': '<(apk_package_native_libs_dir)/<(android_app_abi)',
+ 'package_input_paths': [
+ '<(strip_additional_stamp)',
+ '<(pack_relocations_stamp)',
+ ],
+ },
+ }],
+ ],
+ }], # native_lib_target != ''
+ ['gyp_managed_install == 0 or create_standalone_apk == 1 or create_abi_split == 1', {
+ 'dependencies': [
+ '<(DEPTH)/build/android/rezip.gyp:rezip_apk_jar',
+ ],
+ }],
+ ['create_abi_split == 1 or gyp_managed_install == 0 or create_standalone_apk == 1', {
+ 'actions': [
+ {
+ 'action_name': 'finalize_base',
+ 'variables': {
+ 'output_apk_path': '<(final_apk_path)',
+ 'conditions': [
+ ['create_abi_split == 0', {
+ 'input_apk_path': '<(unsigned_standalone_apk_path)',
+ }, {
+ 'input_apk_path': '<(unsigned_apk_path)',
+ 'load_library_from_zip': 0,
+ }]
+ ],
+ },
+ 'includes': [ 'android/finalize_apk_action.gypi']
+ },
+ ],
+ }],
+ ['create_abi_split == 1', {
+ 'actions': [
+ {
+ 'action_name': 'generate_split_manifest_<(_target_name)',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/generate_split_manifest.py',
+ '<(android_manifest_path)',
+ ],
+ 'outputs': [
+ '<(split_android_manifest_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/generate_split_manifest.py',
+ '--main-manifest', '<(android_manifest_path)',
+ '--out-manifest', '<(split_android_manifest_path)',
+ '--split', 'abi_<(android_app_abi)',
+ ],
+ },
+ {
+ 'variables': {
+ 'apk_name': '<(main_apk_name)-abi-<(android_app_abi)',
+ 'asset_location': '',
+ 'android_manifest_path': '<(split_android_manifest_path)',
+ 'create_density_splits': 0,
+ 'language_splits=': [],
+ },
+ 'includes': [ 'android/package_resources_action.gypi' ],
+ },
+ {
+ 'variables': {
+ 'apk_name': '<(main_apk_name)-abi-<(android_app_abi)',
+ 'apk_path': '<(unsigned_abi_split_apk_path)',
+ 'has_code': 0,
+ 'native_libs_dir': '<(apk_package_native_libs_dir)',
+ 'extra_inputs': ['<(native_lib_placeholder_stamp)'],
+ },
+ 'includes': ['android/apkbuilder_action.gypi'],
+ },
+ ],
+ }],
+ ['create_abi_split == 1 and (gyp_managed_install == 0 or create_standalone_apk == 1)', {
+ 'actions': [
+ {
+ 'action_name': 'finalize_split',
+ 'variables': {
+ 'output_apk_path': '<(final_abi_split_apk_path)',
+ 'conditions': [
+ ['gyp_managed_install == 1', {
+ 'input_apk_path': '<(unsigned_standalone_apk_path)',
+ }, {
+ 'input_apk_path': '<(unsigned_abi_split_apk_path)',
+ }],
+ ],
+ },
+ 'includes': [ 'android/finalize_apk_action.gypi']
+ },
+ ],
+ }],
+ ['gyp_managed_install == 1', {
+ 'actions': [
+ {
+ 'action_name': 'finalize incomplete apk',
+ 'variables': {
+ 'load_library_from_zip': 0,
+ 'input_apk_path': '<(managed_input_apk_path)',
+ 'output_apk_path': '<(incomplete_apk_path)',
+ },
+ 'includes': [ 'android/finalize_apk_action.gypi']
+ },
+ {
+ 'action_name': 'apk_install_<(_target_name)',
+ 'message': 'Installing <(apk_name).apk',
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/apk_install.py',
+ '<(build_device_config_path)',
+ '<(incomplete_apk_path)',
+ ],
+ 'outputs': [
+ '<(apk_install_record)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/apk_install.py',
+ '--build-device-configuration=<(build_device_config_path)',
+ '--install-record=<(apk_install_record)',
+ '--configuration-name=<(CONFIGURATION_NAME)',
+ '--android-sdk-tools', '<(android_sdk_tools)',
+ ],
+ 'conditions': [
+ ['create_abi_split == 1', {
+ 'inputs': [
+ '<(final_apk_path)',
+ ],
+ 'action': [
+ '--apk-path=<(final_apk_path)',
+ '--split-apk-path=<(incomplete_apk_path)',
+ ],
+ }, {
+ 'action': [
+ '--apk-path=<(incomplete_apk_path)',
+ ],
+ }],
+ ['create_density_splits == 1', {
+ 'inputs': [
+ '<(final_apk_path_no_extension)-density-hdpi.apk',
+ '<(final_apk_path_no_extension)-density-xhdpi.apk',
+ '<(final_apk_path_no_extension)-density-xxhdpi.apk',
+ '<(final_apk_path_no_extension)-density-xxxhdpi.apk',
+ '<(final_apk_path_no_extension)-density-tvdpi.apk',
+ ],
+ 'action': [
+ '--split-apk-path=<(final_apk_path_no_extension)-density-hdpi.apk',
+ '--split-apk-path=<(final_apk_path_no_extension)-density-xhdpi.apk',
+ '--split-apk-path=<(final_apk_path_no_extension)-density-xxhdpi.apk',
+ '--split-apk-path=<(final_apk_path_no_extension)-density-xxxhdpi.apk',
+ '--split-apk-path=<(final_apk_path_no_extension)-density-tvdpi.apk',
+ ],
+ }],
+ ['language_splits != []', {
+ 'inputs': [
+ "@(input_jars_paths)',
+ '<(jar_path)',
+ '<(compile_stamp)',
+ ],
+ 'outputs': [
+ '<(findbugs_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/findbugs_diff.py',
+ '--auxclasspath-gyp', '>(input_jars_paths)',
+ '--stamp', '<(findbugs_stamp)',
+ '<(jar_path)',
+ ],
+ },
+ ],
+ },
+ ]
+ ],
+ 'dependencies': [
+ '<(DEPTH)/tools/android/md5sum/md5sum.gyp:md5sum',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'process_resources',
+ 'message': 'processing resources for <(_target_name)',
+ 'variables': {
+ # Write the inputs list to a file, so that its mtime is updated when
+ # the list of inputs changes.
+ 'inputs_list_file': '>|(apk_codegen.<(_target_name).gypcmd >@(additional_input_paths) >@(resource_input_paths))',
+ 'process_resources_options': [],
+ 'conditions': [
+ ['is_test_apk == 1', {
+ 'dependencies_res_zip_paths=': [],
+ 'additional_res_packages=': [],
+ }],
+ ['res_v14_skip == 1', {
+ 'process_resources_options+': ['--v14-skip']
+ }],
+ ['shared_resources == 1', {
+ 'process_resources_options+': ['--shared-resources']
+ }],
+ ['R_package != ""', {
+ 'process_resources_options+': ['--custom-package', '<(R_package)']
+ }],
+ ['include_all_resources == 1', {
+ 'process_resources_options+': ['--include-all-resources']
+ }]
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/process_resources.py',
+ '<(android_manifest_path)',
+ '>@(additional_input_paths)',
+ '>@(resource_input_paths)',
+ '>@(dependencies_res_zip_paths)',
+ '>(inputs_list_file)',
+ ],
+ 'outputs': [
+ '<(resource_zip_path)',
+ '<(generated_proguard_file)',
+ '<(codegen_stamp)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/process_resources.py',
+ '--android-sdk', '<(android_sdk)',
+ '--aapt-path', '<(android_aapt_path)',
+
+ '--android-manifest', '<(android_manifest_path)',
+ '--dependencies-res-zips', '>(dependencies_res_zip_paths)',
+
+ '--extra-res-packages', '>(additional_res_packages)',
+ '--extra-r-text-files', '>(additional_R_text_files)',
+
+ '--proguard-file', '<(generated_proguard_file)',
+
+ '--resource-dirs', '<(resource_dir)',
+ '--resource-zip-out', '<(resource_zip_path)',
+
+ '--R-dir', '<(intermediate_dir)/gen',
+
+ '--stamp', '<(codegen_stamp)',
+
+ '<@(process_resources_options)',
+ ],
+ },
+ {
+ 'action_name': 'javac_<(_target_name)',
+ 'message': 'Compiling java for <(_target_name)',
+ 'variables': {
+ 'extra_args': [],
+ 'extra_inputs': [],
+ 'gen_src_dirs': [
+ '<(intermediate_dir)/gen',
+ '>@(generated_src_dirs)',
+ ],
+ # If there is a separate find for additional_src_dirs, it will find the
+ # wrong .java files when additional_src_dirs is empty.
+ # TODO(thakis): Gyp caches >! evaluation by command. Both java.gypi and
+ # java_apk.gypi evaluate the same command, and at the moment two targets
+ # set java_in_dir to "java". Add a dummy comment here to make sure
+ # that the two targets (one uses java.gypi, the other java_apk.gypi)
+ # get distinct source lists. Medium-term, make targets list all their
+ # Java files instead of using find. (As is, this will be broken if two
+ # targets use the same java_in_dir and both use java_apk.gypi or
+ # both use java.gypi.)
+ 'java_sources': ['>!@(find >(java_in_dir)>(java_in_dir_suffix) >(additional_src_dirs) -name "*.java" # apk)'],
+ 'conditions': [
+ ['enable_errorprone == 1', {
+ 'extra_inputs': [
+ '<(errorprone_exe_path)',
+ ],
+ 'extra_args': [ '--use-errorprone-path=<(errorprone_exe_path)' ],
+ }],
+ ],
+ },
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/javac.py',
+ '>@(java_sources)',
+ '>@(input_jars_paths)',
+ '<(codegen_stamp)',
+ '<@(extra_inputs)',
+ ],
+ 'conditions': [
+ ['native_lib_target != ""', {
+ 'inputs': [ '<(native_libraries_java_stamp)' ],
+ }],
+ ],
+ 'outputs': [
+ '<(compile_stamp)',
+ '<(javac_jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/javac.py',
+ '--bootclasspath=<(android_sdk_jar)',
+ '--classpath=>(input_jars_paths) <(android_sdk_jar)',
+ '--src-gendirs=>(gen_src_dirs)',
+ '--javac-includes=<(javac_includes)',
+ '--chromium-code=<(chromium_code)',
+ '--jar-path=<(javac_jar_path)',
+ '--jar-excluded-classes=<(jar_excluded_classes)',
+ '--stamp=<(compile_stamp)',
+ '<@(extra_args)',
+ '>@(java_sources)',
+ ],
+ },
+ {
+ 'action_name': 'instr_jar_<(_target_name)',
+ 'message': 'Instrumenting <(_target_name) jar',
+ 'variables': {
+ 'input_path': '<(javac_jar_path)',
+ 'output_path': '<(jar_path)',
+ 'stamp_path': '<(instr_stamp)',
+ 'instr_type': 'jar',
+ },
+ 'outputs': [
+ '<(instr_stamp)',
+ '<(jar_path)',
+ ],
+ 'inputs': [
+ '<(javac_jar_path)',
+ ],
+ 'includes': [ 'android/instr_action.gypi' ],
+ },
+ {
+ 'variables': {
+ 'src_dirs': [
+ '<(java_in_dir)<(java_in_dir_suffix)',
+ '>@(additional_src_dirs)',
+ ],
+ 'lint_jar_path': '<(jar_path)',
+ 'stamp_path': '<(lint_stamp)',
+ 'result_path': '<(lint_result)',
+ 'config_path': '<(lint_config)',
+ },
+ 'outputs': [
+ '<(lint_stamp)',
+ ],
+ 'includes': [ 'android/lint_action.gypi' ],
+ },
+ {
+ 'action_name': 'obfuscate_<(_target_name)',
+ 'message': 'Obfuscating <(_target_name)',
+ 'variables': {
+ 'additional_obfuscate_options': [],
+ 'additional_obfuscate_input_paths': [],
+ 'proguard_out_dir': '<(intermediate_dir)/proguard',
+ 'proguard_input_jar_paths': [
+ '>@(input_jars_paths)',
+ '<(jar_path)',
+ ],
+ 'target_conditions': [
+ ['is_test_apk == 1', {
+ 'additional_obfuscate_options': [
+ '--testapp',
+ ],
+ }],
+ ['is_test_apk == 1 and tested_apk_obfuscated_jar_path != "/"', {
+ 'additional_obfuscate_options': [
+ '--tested-apk-obfuscated-jar-path', '>(tested_apk_obfuscated_jar_path)',
+ ],
+ 'additional_obfuscate_input_paths': [
+ '>(tested_apk_obfuscated_jar_path).info',
+ ],
+ }],
+ ['proguard_enabled == "true"', {
+ 'additional_obfuscate_options': [
+ '--proguard-enabled',
+ ],
+ }],
+ ],
+ 'obfuscate_input_jars_paths': [
+ '>@(input_jars_paths)',
+ '<(jar_path)',
+ ],
+ },
+ 'conditions': [
+ ['is_test_apk == 1', {
+ 'outputs': [
+ '<(test_jar_path)',
+ ],
+ }],
+ ],
+ 'inputs': [
+ '<(DEPTH)/build/android/gyp/apk_obfuscate.py',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '>@(proguard_flags_paths)',
+ '>@(obfuscate_input_jars_paths)',
+ '>@(additional_obfuscate_input_paths)',
+ '<(instr_stamp)',
+ ],
+ 'outputs': [
+ '<(obfuscate_stamp)',
+
+ # In non-Release builds, these paths will all be empty files.
+ '<(obfuscated_jar_path)',
+ '<(obfuscated_jar_path).info',
+ '<(obfuscated_jar_path).dump',
+ '<(obfuscated_jar_path).seeds',
+ '<(obfuscated_jar_path).mapping',
+ '<(obfuscated_jar_path).usage',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/apk_obfuscate.py',
+
+ '--configuration-name', '<(CONFIGURATION_NAME)',
+
+ '--android-sdk', '<(android_sdk)',
+ '--android-sdk-tools', '<(android_sdk_tools)',
+ '--android-sdk-jar', '<(android_sdk_jar)',
+
+ '--input-jars-paths=>(proguard_input_jar_paths)',
+ '--proguard-configs=>(proguard_flags_paths)',
+
+ '--test-jar-path', '<(test_jar_path)',
+ '--obfuscated-jar-path', '<(obfuscated_jar_path)',
+
+ '--proguard-jar-path', '<(android_sdk_root)/tools/proguard/lib/proguard.jar',
+
+ '--stamp', '<(obfuscate_stamp)',
+
+ '>@(additional_obfuscate_options)',
+ ],
+ },
+ {
+ 'action_name': 'dex_<(_target_name)',
+ 'variables': {
+ 'dex_input_paths': [
+ '>@(library_dexed_jars_paths)',
+ '<(jar_path)',
+ ],
+ 'output_path': '<(dex_path)',
+ 'proguard_enabled_input_path': '<(obfuscated_jar_path)',
+ },
+ 'target_conditions': [
+ ['emma_instrument != 0', {
+ 'variables': {
+ 'dex_no_locals': 1,
+ 'dex_input_paths': [
+ '<(emma_device_jar)'
+ ],
+ },
+ }],
+ ['is_test_apk == 1 and tested_apk_dex_path != "/"', {
+ 'variables': {
+ 'dex_additional_options': [
+ '--excluded-paths', '@FileArg(>(tested_apk_dex_path).inputs)'
+ ],
+ },
+ 'inputs': [
+ '>(tested_apk_dex_path).inputs',
+ ],
+ }],
+ ['proguard_enabled == "true"', {
+ 'inputs': [ '<(obfuscate_stamp)' ]
+ }, {
+ 'inputs': [ '<(instr_stamp)' ]
+ }],
+ ],
+ 'includes': [ 'android/dex_action.gypi' ],
+ },
+ {
+ 'variables': {
+ 'extra_inputs': ['<(codegen_stamp)'],
+ 'resource_zips': [
+ '<(resource_zip_path)',
+ ],
+ 'conditions': [
+ ['is_test_apk == 0', {
+ 'resource_zips': [
+ '>@(dependencies_res_zip_paths)',
+ ],
+ }],
+ ],
+ },
+ 'includes': [ 'android/package_resources_action.gypi' ],
+ },
+ {
+ 'variables': {
+ 'apk_path': '<(unsigned_apk_path)',
+ 'conditions': [
+ ['native_lib_target != ""', {
+ 'extra_inputs': ['<(native_lib_placeholder_stamp)'],
+ }],
+ ['create_abi_split == 0', {
+ 'native_libs_dir': '<(apk_package_native_libs_dir)',
+ }, {
+ 'native_libs_dir': '<(DEPTH)/build/android/ant/empty/res',
+ }],
+ ],
+ },
+ 'includes': ['android/apkbuilder_action.gypi'],
+ },
+ ],
+}
diff --git a/build/java_prebuilt.gypi b/build/java_prebuilt.gypi
new file mode 100644
index 00000000000..8efc4ef7f4d
--- /dev/null
+++ b/build/java_prebuilt.gypi
@@ -0,0 +1,102 @@
+# Copyright (c) 2012 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 file is meant to be included into a target to provide a rule
+# to package prebuilt Java JARs in a consistent manner.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my-package_java',
+# 'type': 'none',
+# 'variables': {
+# 'jar_path': 'path/to/your.jar',
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+# Required variables:
+# jar_path - The path to the prebuilt Java JAR file.
+
+{
+ 'dependencies': [
+ '<(DEPTH)/build/android/setup.gyp:build_output_dirs'
+ ],
+ 'variables': {
+ 'dex_path': '<(PRODUCT_DIR)/lib.java/<(_target_name).dex.jar',
+ 'intermediate_dir': '<(SHARED_INTERMEDIATE_DIR)/<(_target_name)',
+ 'android_jar': '<(android_sdk)/android.jar',
+ 'input_jars_paths': [ '<(android_jar)' ],
+ 'neverlink%': 0,
+ 'proguard_config%': '',
+ 'proguard_preprocess%': '0',
+ 'variables': {
+ 'variables': {
+ 'proguard_preprocess%': 0,
+ },
+ 'conditions': [
+ ['proguard_preprocess == 1', {
+ 'dex_input_jar_path': '<(intermediate_dir)/<(_target_name).pre.jar'
+ }, {
+ 'dex_input_jar_path': '<(jar_path)'
+ }],
+ ],
+ },
+ 'dex_input_jar_path': '<(dex_input_jar_path)',
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'input_jars_paths': ['<(dex_input_jar_path)'],
+ 'conditions': [
+ ['neverlink == 1', {
+ 'library_dexed_jars_paths': [],
+ }, {
+ 'library_dexed_jars_paths': ['<(dex_path)'],
+ }],
+ ],
+ },
+ },
+ 'conditions' : [
+ ['proguard_preprocess == 1', {
+ 'actions': [
+ {
+ 'action_name': 'proguard_<(_target_name)',
+ 'message': 'Proguard preprocessing <(_target_name) jar',
+ 'inputs': [
+ '<(android_sdk_root)/tools/proguard/lib/proguard.jar',
+ '<(DEPTH)/build/android/gyp/util/build_utils.py',
+ '<(DEPTH)/build/android/gyp/proguard.py',
+ '<(jar_path)',
+ '<(proguard_config)',
+ ],
+ 'outputs': [
+ '<(dex_input_jar_path)',
+ ],
+ 'action': [
+ 'python', '<(DEPTH)/build/android/gyp/proguard.py',
+ '--proguard-path=<(android_sdk_root)/tools/proguard/lib/proguard.jar',
+ '--input-path=<(jar_path)',
+ '--output-path=<(dex_input_jar_path)',
+ '--proguard-config=<(proguard_config)',
+ '--classpath=>(input_jars_paths)',
+ ]
+ },
+ ],
+ }],
+ ['neverlink == 0', {
+ 'actions': [
+ {
+ 'action_name': 'dex_<(_target_name)',
+ 'message': 'Dexing <(_target_name) jar',
+ 'variables': {
+ 'dex_input_paths': [
+ '<(dex_input_jar_path)',
+ ],
+ 'output_path': '<(dex_path)',
+ },
+ 'includes': [ 'android/dex_action.gypi' ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/build/java_strings_grd.gypi b/build/java_strings_grd.gypi
new file mode 100644
index 00000000000..7534be5beea
--- /dev/null
+++ b/build/java_strings_grd.gypi
@@ -0,0 +1,62 @@
+# Copyright 2014 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 file is meant to be included into a target to provide a rule
+# to generate localized strings.xml from a grd file.
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my-package_strings_grd',
+# 'type': 'none',
+# 'variables': {
+# 'grd_file': 'path/to/grd/file',
+# },
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+# Required variables:
+# grd_file - The path to the grd file to use.
+{
+ 'variables': {
+ 'res_grit_dir': '<(INTERMEDIATE_DIR)/<(_target_name)/res_grit',
+ 'grit_grd_file': '<(grd_file)',
+ 'resource_zip_path': '<(PRODUCT_DIR)/res.java/<(_target_name).zip',
+ 'grit_additional_defines': ['-E', 'ANDROID_JAVA_TAGGED_ONLY=false'],
+ 'grit_out_dir': '<(res_grit_dir)',
+ # resource_ids is unneeded since we don't generate .h headers.
+ 'grit_resource_ids': '',
+ 'grit_outputs': [
+ ' %r' % (func.__name__, val[0])
+ return val[0]
+ return inner
+ return memoizer
+
+
+@memoize()
+def IsWindows():
+ return sys.platform in ['win32', 'cygwin']
+
+
+@memoize()
+def IsLinux():
+ return sys.platform.startswith(('linux', 'freebsd', 'openbsd'))
+
+
+@memoize()
+def IsMac():
+ return sys.platform == 'darwin'
+
+
+@memoize()
+def gyp_defines():
+ """Parses and returns GYP_DEFINES env var as a dictionary."""
+ return dict(arg.split('=', 1)
+ for arg in shlex.split(os.environ.get('GYP_DEFINES', '')))
+
+@memoize()
+def gyp_generator_flags():
+ """Parses and returns GYP_GENERATOR_FLAGS env var as a dictionary."""
+ return dict(arg.split('=', 1)
+ for arg in shlex.split(os.environ.get('GYP_GENERATOR_FLAGS', '')))
+
+@memoize()
+def gyp_msvs_version():
+ return os.environ.get('GYP_MSVS_VERSION', '')
+
+@memoize()
+def distributor():
+ """
+ Returns a string which is the distributed build engine in use (if any).
+ Possible values: 'goma', 'ib', ''
+ """
+ if 'goma' in gyp_defines():
+ return 'goma'
+ elif IsWindows():
+ if 'CHROME_HEADLESS' in os.environ:
+ return 'ib' # use (win and !goma and headless) as approximation of ib
+
+
+@memoize()
+def platform():
+ """
+ Returns a string representing the platform this build is targetted for.
+ Possible values: 'win', 'mac', 'linux', 'ios', 'android'
+ """
+ if 'OS' in gyp_defines():
+ if 'android' in gyp_defines()['OS']:
+ return 'android'
+ else:
+ return gyp_defines()['OS']
+ elif IsWindows():
+ return 'win'
+ elif IsLinux():
+ return 'linux'
+ else:
+ return 'mac'
+
+
+@memoize()
+def builder():
+ """
+ Returns a string representing the build engine (not compiler) to use.
+ Possible values: 'make', 'ninja', 'xcode', 'msvs', 'scons'
+ """
+ if 'GYP_GENERATORS' in os.environ:
+ # for simplicity, only support the first explicit generator
+ generator = os.environ['GYP_GENERATORS'].split(',')[0]
+ if generator.endswith('-android'):
+ return generator.split('-')[0]
+ elif generator.endswith('-ninja'):
+ return 'ninja'
+ else:
+ return generator
+ else:
+ if platform() == 'android':
+ # Good enough for now? Do any android bots use make?
+ return 'ninja'
+ elif platform() == 'ios':
+ return 'xcode'
+ elif IsWindows():
+ return 'ninja'
+ elif IsLinux():
+ return 'ninja'
+ elif IsMac():
+ return 'ninja'
+ else:
+ assert False, 'Don\'t know what builder we\'re using!'
diff --git a/build/landmines.py b/build/landmines.py
new file mode 100755
index 00000000000..0ea2b64058d
--- /dev/null
+++ b/build/landmines.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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 script runs every build as the first hook (See DEPS). If it detects that
+the build should be clobbered, it will delete the contents of the build
+directory.
+
+A landmine is tripped when a builder checks out a different revision, and the
+diff between the new landmines and the old ones is non-null. At this point, the
+build is clobbered.
+"""
+
+import difflib
+import errno
+import logging
+import optparse
+import os
+import sys
+import subprocess
+import time
+
+import clobber
+import landmine_utils
+
+
+SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+
+def get_build_dir(build_tool, is_iphone=False):
+ """
+ Returns output directory absolute path dependent on build and targets.
+ Examples:
+ r'c:\b\build\slave\win\build\src\out'
+ '/mnt/data/b/build/slave/linux/build/src/out'
+ '/b/build/slave/ios_rel_device/build/src/xcodebuild'
+
+ Keep this function in sync with tools/build/scripts/slave/compile.py
+ """
+ ret = None
+ if build_tool == 'xcode':
+ ret = os.path.join(SRC_DIR, 'xcodebuild')
+ elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios.
+ if 'CHROMIUM_OUT_DIR' in os.environ:
+ output_dir = os.environ.get('CHROMIUM_OUT_DIR').strip()
+ if not output_dir:
+ raise Error('CHROMIUM_OUT_DIR environment variable is set but blank!')
+ else:
+ output_dir = landmine_utils.gyp_generator_flags().get('output_dir', 'out')
+ ret = os.path.join(SRC_DIR, output_dir)
+ else:
+ raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
+ return os.path.abspath(ret)
+
+
+def clobber_if_necessary(new_landmines):
+ """Does the work of setting, planting, and triggering landmines."""
+ out_dir = get_build_dir(landmine_utils.builder())
+ landmines_path = os.path.normpath(os.path.join(out_dir, '..', '.landmines'))
+ try:
+ os.makedirs(out_dir)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
+
+ if os.path.exists(landmines_path):
+ with open(landmines_path, 'r') as f:
+ old_landmines = f.readlines()
+ if old_landmines != new_landmines:
+ old_date = time.ctime(os.stat(landmines_path).st_ctime)
+ diff = difflib.unified_diff(old_landmines, new_landmines,
+ fromfile='old_landmines', tofile='new_landmines',
+ fromfiledate=old_date, tofiledate=time.ctime(), n=0)
+ sys.stdout.write('Clobbering due to:\n')
+ sys.stdout.writelines(diff)
+
+ clobber.clobber(out_dir)
+
+ # Save current set of landmines for next time.
+ with open(landmines_path, 'w') as f:
+ f.writelines(new_landmines)
+
+
+def process_options():
+ """Returns a list of landmine emitting scripts."""
+ parser = optparse.OptionParser()
+ parser.add_option(
+ '-s', '--landmine-scripts', action='append',
+ default=[os.path.join(SRC_DIR, 'build', 'get_landmines.py')],
+ help='Path to the script which emits landmines to stdout. The target '
+ 'is passed to this script via option -t. Note that an extra '
+ 'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.')
+ parser.add_option('-v', '--verbose', action='store_true',
+ default=('LANDMINES_VERBOSE' in os.environ),
+ help=('Emit some extra debugging information (default off). This option '
+ 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
+ 'variable.'))
+
+ options, args = parser.parse_args()
+
+ if args:
+ parser.error('Unknown arguments %s' % args)
+
+ logging.basicConfig(
+ level=logging.DEBUG if options.verbose else logging.ERROR)
+
+ extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT')
+ if extra_script:
+ return options.landmine_scripts + [extra_script]
+ else:
+ return options.landmine_scripts
+
+
+def main():
+ landmine_scripts = process_options()
+
+ if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'):
+ return 0
+
+
+ landmines = []
+ for s in landmine_scripts:
+ proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE)
+ output, _ = proc.communicate()
+ landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()])
+ clobber_if_necessary(landmines)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/linux/OWNERS b/build/linux/OWNERS
new file mode 100644
index 00000000000..4a60b79d484
--- /dev/null
+++ b/build/linux/OWNERS
@@ -0,0 +1,3 @@
+mmoss@chromium.org
+phajdan.jr@chromium.org
+thestig@chromium.org
diff --git a/build/linux/bin/eu-strip b/build/linux/bin/eu-strip
new file mode 100755
index 00000000000..7f93eec56c8
Binary files /dev/null and b/build/linux/bin/eu-strip differ
diff --git a/build/linux/bin/eu-strip.sha1 b/build/linux/bin/eu-strip.sha1
new file mode 100644
index 00000000000..43f290a701a
--- /dev/null
+++ b/build/linux/bin/eu-strip.sha1
@@ -0,0 +1 @@
+0a9b8f68615ce388b65201e6d22da7a9cf2e729c
\ No newline at end of file
diff --git a/build/linux/chrome_linux.croc b/build/linux/chrome_linux.croc
new file mode 100644
index 00000000000..f4003060f69
--- /dev/null
+++ b/build/linux/chrome_linux.croc
@@ -0,0 +1,29 @@
+# -*- python -*-
+# Crocodile config file for Chromium linux
+
+# TODO(jhawkins): We'll need to add a chromeos.croc once we get a coverage bot
+# for that platform.
+
+{
+ # List of rules, applied in order
+ 'rules' : [
+ # Specify inclusions before exclusions, since rules are in order.
+
+ # Don't include non-Linux platform dirs
+ {
+ 'regexp' : '.*/(chromeos|views)/',
+ 'include' : 0,
+ },
+ # Don't include chromeos, windows, or mac specific files
+ {
+ 'regexp' : '.*(_|/)(chromeos|mac|win|views)(\\.|_)',
+ 'include' : 0,
+ },
+
+ # Groups
+ {
+ 'regexp' : '.*_test_linux\\.',
+ 'group' : 'test',
+ },
+ ],
+}
diff --git a/build/linux/dump_app_syms.py b/build/linux/dump_app_syms.py
new file mode 100644
index 00000000000..c18bff77694
--- /dev/null
+++ b/build/linux/dump_app_syms.py
@@ -0,0 +1,29 @@
+# Copyright 2015 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.
+
+# Helper script to run dump_syms on Chrome Linux executables and strip
+# them if needed.
+
+import os
+import subprocess
+import sys
+
+if len(sys.argv) != 5:
+ print "dump_app_syms.py "
+ print " "
+ sys.exit(1)
+
+dumpsyms = sys.argv[1]
+strip_binary = sys.argv[2]
+infile = sys.argv[3]
+outfile = sys.argv[4]
+
+# Dump only when the output file is out-of-date.
+if not os.path.isfile(outfile) or \
+ os.stat(outfile).st_mtime > os.stat(infile).st_mtime:
+ with open(outfile, 'w') as outfileobj:
+ subprocess.check_call([dumpsyms, '-r', infile], stdout=outfileobj)
+
+if strip_binary != '0':
+ subprocess.check_call(['strip', infile])
diff --git a/build/linux/install-chromeos-fonts.py b/build/linux/install-chromeos-fonts.py
new file mode 100755
index 00000000000..a24adc920ce
--- /dev/null
+++ b/build/linux/install-chromeos-fonts.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# Copyright 2013 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.
+
+# Script to install the Chrome OS fonts on Linux.
+# This script can be run manually (as root), but is also run as part
+# install-build-deps.sh.
+
+import os
+import shutil
+import subprocess
+import sys
+
+# Taken from the media-fonts/notofonts ebuild in chromiumos-overlay.
+VERSION = '20140815'
+URL = ('https://commondatastorage.googleapis.com/chromeos-localmirror/'
+ 'distfiles/notofonts-%s.tar.bz2') % (VERSION)
+FONTS_DIR = '/usr/local/share/fonts'
+
+def main(args):
+ if not sys.platform.startswith('linux'):
+ print "Error: %s must be run on Linux." % __file__
+ return 1
+
+ if os.getuid() != 0:
+ print "Error: %s must be run as root." % __file__
+ return 1
+
+ if not os.path.isdir(FONTS_DIR):
+ print "Error: Destination directory does not exist: %s" % FONTS_DIR
+ return 1
+
+ dest_dir = os.path.join(FONTS_DIR, 'chromeos')
+
+ stamp = os.path.join(dest_dir, ".stamp02")
+ if os.path.exists(stamp):
+ with open(stamp) as s:
+ if s.read() == URL:
+ print "Chrome OS fonts already up-to-date in %s." % dest_dir
+ return 0
+
+ if os.path.isdir(dest_dir):
+ shutil.rmtree(dest_dir)
+ os.mkdir(dest_dir)
+ os.chmod(dest_dir, 0755)
+
+ print "Installing Chrome OS fonts to %s." % dest_dir
+ tarball = os.path.join(dest_dir, os.path.basename(URL))
+ subprocess.check_call(['curl', '-L', URL, '-o', tarball])
+ subprocess.check_call(['tar', '--no-same-owner', '--no-same-permissions',
+ '-xf', tarball, '-C', dest_dir])
+ os.remove(tarball)
+
+ readme = os.path.join(dest_dir, "README")
+ with open(readme, 'w') as s:
+ s.write("This directory and its contents are auto-generated.\n")
+ s.write("It may be deleted and recreated. Do not modify.\n")
+ s.write("Script: %s\n" % __file__)
+
+ with open(stamp, 'w') as s:
+ s.write(URL)
+
+ for base, dirs, files in os.walk(dest_dir):
+ for dir in dirs:
+ os.chmod(os.path.join(base, dir), 0755)
+ for file in files:
+ os.chmod(os.path.join(base, file), 0644)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/linux/pkg-config-wrapper b/build/linux/pkg-config-wrapper
new file mode 100755
index 00000000000..b7595645a11
--- /dev/null
+++ b/build/linux/pkg-config-wrapper
@@ -0,0 +1,59 @@
+#!/bin/bash
+# Copyright (c) 2012 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 program wraps around pkg-config to generate the correct include and
+# library paths when cross-compiling using a sysroot.
+# The assumption is that the sysroot contains the .pc files in usr/lib/pkgconfig
+# and usr/share/pkgconfig (relative to the sysroot) and that they output paths
+# relative to some parent path of the sysroot.
+# This assumption is valid for a range of sysroots, in particular: a
+# LSB-compliant root filesystem mounted at the sysroot, and a board build
+# directory of a Chromium OS chroot.
+# Additional directories containing .pc files may be specified by setting
+# the PKG_CONFIG_PATH environment variable- these will be prepended to the
+# generated paths.
+
+root="$1"
+shift
+target_arch="$1"
+shift
+libpath="$1"
+shift
+
+if [ -z "$root" -o -z "$target_arch" ]
+then
+ echo "usage: $0 /path/to/sysroot target_arch libdir [pkg-config-arguments] package" >&2
+ exit 1
+fi
+
+if [ "$target_arch" = "x64" ]
+then
+ : ${libpath:="lib64"}
+else
+ : ${libpath:="lib"}
+fi
+
+rewrite=`dirname $0`/rewrite_dirs.py
+package=${!#}
+
+config_path=$root/usr/$libpath/pkgconfig:$root/usr/share/pkgconfig
+
+# prepend any paths specified by the environment
+if [ -n "$PKG_CONFIG_PATH" ]
+then
+ config_path="$PKG_CONFIG_PATH:$config_path"
+fi
+
+set -e
+# Some sysroots, like the Chromium OS ones, may generate paths that are not
+# relative to the sysroot. For example,
+# /path/to/chroot/build/x86-generic/usr/lib/pkgconfig/pkg.pc may have all paths
+# relative to /path/to/chroot (i.e. prefix=/build/x86-generic/usr) instead of
+# relative to /path/to/chroot/build/x86-generic (i.e prefix=/usr).
+# To support this correctly, it's necessary to extract the prefix to strip from
+# pkg-config's |prefix| variable.
+prefix=`PKG_CONFIG_PATH=$config_path pkg-config --variable=prefix "$package" | sed -e 's|/usr$||'`
+result=`PKG_CONFIG_PATH=$config_path pkg-config "$@"`
+echo "$result"| $rewrite --sysroot "$root" --strip-prefix "$prefix"
diff --git a/build/linux/rewrite_dirs.py b/build/linux/rewrite_dirs.py
new file mode 100755
index 00000000000..30f22f0cd61
--- /dev/null
+++ b/build/linux/rewrite_dirs.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 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.
+
+"""Rewrites paths in -I, -L and other option to be relative to a sysroot."""
+
+import sys
+import os
+import optparse
+
+REWRITE_PREFIX = ['-I',
+ '-idirafter',
+ '-imacros',
+ '-imultilib',
+ '-include',
+ '-iprefix',
+ '-iquote',
+ '-isystem',
+ '-L']
+
+def RewritePath(path, opts):
+ """Rewrites a path by stripping the prefix and prepending the sysroot."""
+ sysroot = opts.sysroot
+ prefix = opts.strip_prefix
+ if os.path.isabs(path) and not path.startswith(sysroot):
+ if path.startswith(prefix):
+ path = path[len(prefix):]
+ path = path.lstrip('/')
+ return os.path.join(sysroot, path)
+ else:
+ return path
+
+
+def RewriteLine(line, opts):
+ """Rewrites all the paths in recognized options."""
+ args = line.split()
+ count = len(args)
+ i = 0
+ while i < count:
+ for prefix in REWRITE_PREFIX:
+ # The option can be either in the form "-I /path/to/dir" or
+ # "-I/path/to/dir" so handle both.
+ if args[i] == prefix:
+ i += 1
+ try:
+ args[i] = RewritePath(args[i], opts)
+ except IndexError:
+ sys.stderr.write('Missing argument following %s\n' % prefix)
+ break
+ elif args[i].startswith(prefix):
+ args[i] = prefix + RewritePath(args[i][len(prefix):], opts)
+ i += 1
+
+ return ' '.join(args)
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-s', '--sysroot', default='/', help='sysroot to prepend')
+ parser.add_option('-p', '--strip-prefix', default='', help='prefix to strip')
+ opts, args = parser.parse_args(argv[1:])
+
+ for line in sys.stdin.readlines():
+ line = RewriteLine(line.strip(), opts)
+ print line
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/linux/sysroot_ld_path.sh b/build/linux/sysroot_ld_path.sh
new file mode 100755
index 00000000000..4b8bf7305e2
--- /dev/null
+++ b/build/linux/sysroot_ld_path.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+# Copyright (c) 2013 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.
+
+# Reads etc/ld.so.conf and/or etc/ld.so.conf.d/*.conf and returns the
+# appropriate linker flags.
+#
+# sysroot_ld_path.sh /abspath/to/sysroot
+#
+
+log_error_and_exit() {
+ echo $0: $@
+ exit 1
+}
+
+process_entry() {
+ if [ -z "$1" ] || [ -z "$2" ]; then
+ log_error_and_exit "bad arguments to process_entry()"
+ fi
+ local root="$1"
+ local localpath="$2"
+
+ echo $localpath | grep -qs '^/'
+ if [ $? -ne 0 ]; then
+ log_error_and_exit $localpath does not start with /
+ fi
+ local entry="$root$localpath"
+ echo -L$entry
+ echo -Wl,-rpath-link=$entry
+}
+
+process_ld_so_conf() {
+ if [ -z "$1" ] || [ -z "$2" ]; then
+ log_error_and_exit "bad arguments to process_ld_so_conf()"
+ fi
+ local root="$1"
+ local ld_so_conf="$2"
+
+ # ld.so.conf may include relative include paths. pushd is a bashism.
+ local saved_pwd=$(pwd)
+ cd $(dirname "$ld_so_conf")
+
+ cat "$ld_so_conf" | \
+ while read ENTRY; do
+ echo "$ENTRY" | grep -qs ^include
+ if [ $? -eq 0 ]; then
+ local included_files=$(echo "$ENTRY" | sed 's/^include //')
+ echo "$included_files" | grep -qs ^/
+ if [ $? -eq 0 ]; then
+ if ls $root$included_files >/dev/null 2>&1 ; then
+ for inc_file in $root$included_files; do
+ process_ld_so_conf "$root" "$inc_file"
+ done
+ fi
+ else
+ if ls $(pwd)/$included_files >/dev/null 2>&1 ; then
+ for inc_file in $(pwd)/$included_files; do
+ process_ld_so_conf "$root" "$inc_file"
+ done
+ fi
+ fi
+ continue
+ fi
+
+ echo "$ENTRY" | grep -qs ^/
+ if [ $? -eq 0 ]; then
+ process_entry "$root" "$ENTRY"
+ fi
+ done
+
+ # popd is a bashism
+ cd "$saved_pwd"
+}
+
+# Main
+
+if [ $# -ne 1 ]; then
+ echo Usage $0 /abspath/to/sysroot
+ exit 1
+fi
+
+echo $1 | grep -qs ' '
+if [ $? -eq 0 ]; then
+ log_error_and_exit $1 contains whitespace.
+fi
+
+LD_SO_CONF="$1/etc/ld.so.conf"
+LD_SO_CONF_D="$1/etc/ld.so.conf.d"
+
+if [ -e "$LD_SO_CONF" ]; then
+ process_ld_so_conf "$1" "$LD_SO_CONF" | xargs echo
+elif [ -e "$LD_SO_CONF_D" ]; then
+ find "$LD_SO_CONF_D" -maxdepth 1 -name '*.conf' -print -quit > /dev/null
+ if [ $? -eq 0 ]; then
+ for entry in $LD_SO_CONF_D/*.conf; do
+ process_ld_so_conf "$1" "$entry"
+ done | xargs echo
+ fi
+fi
diff --git a/build/linux/sysroot_scripts/install-sysroot.py b/build/linux/sysroot_scripts/install-sysroot.py
new file mode 100755
index 00000000000..99fc2d6f459
--- /dev/null
+++ b/build/linux/sysroot_scripts/install-sysroot.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+# Script to install a Debian Wheezy sysroot for making official Google Chrome
+# Linux builds.
+# The sysroot is needed to make Chrome work for Debian Wheezy.
+# This script can be run manually but is more often run as part of gclient
+# hooks. When run from hooks this script should be a no-op on non-linux
+# platforms.
+
+# The sysroot image could be constructed from scratch based on the current
+# state or Debian Wheezy but for consistency we currently use a pre-built root
+# image. The image will normally need to be rebuilt every time chrome's build
+# dependancies are changed.
+
+import hashlib
+import platform
+import optparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+URL_PREFIX = 'http://storage.googleapis.com'
+URL_PATH = 'chrome-linux-sysroot/toolchain'
+REVISION_AMD64 = 'a2d45701cb21244b9514e420950ba6ba687fb655'
+REVISION_ARM = 'a2d45701cb21244b9514e420950ba6ba687fb655'
+REVISION_I386 = 'a2d45701cb21244b9514e420950ba6ba687fb655'
+REVISION_MIPS = '7749d2957387abf225b6d45154c3ddad142148dc'
+TARBALL_AMD64 = 'debian_wheezy_amd64_sysroot.tgz'
+TARBALL_ARM = 'debian_wheezy_arm_sysroot.tgz'
+TARBALL_I386 = 'debian_wheezy_i386_sysroot.tgz'
+TARBALL_MIPS = 'debian_wheezy_mips_sysroot.tgz'
+TARBALL_AMD64_SHA1SUM = '601216c0f980e798e7131635f3dd8171b3dcbcde'
+TARBALL_ARM_SHA1SUM = '6289593b36616526562a4d85ae9c92b694b8ce7e'
+TARBALL_I386_SHA1SUM = '0090e5a4b56ab9ffb5d557da6a520195ab59b446'
+TARBALL_MIPS_SHA1SUM = '3b4d782a237db4aac185a638572a7747c1a21825'
+SYSROOT_DIR_AMD64 = 'debian_wheezy_amd64-sysroot'
+SYSROOT_DIR_ARM = 'debian_wheezy_arm-sysroot'
+SYSROOT_DIR_I386 = 'debian_wheezy_i386-sysroot'
+SYSROOT_DIR_MIPS = 'debian_wheezy_mips-sysroot'
+
+valid_archs = ('arm', 'i386', 'amd64', 'mips')
+
+
+def GetSha1(filename):
+ sha1 = hashlib.sha1()
+ with open(filename, 'rb') as f:
+ while True:
+ # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
+ chunk = f.read(1024*1024)
+ if not chunk:
+ break
+ sha1.update(chunk)
+ return sha1.hexdigest()
+
+
+def DetectArch(gyp_defines):
+ # Check for optional target_arch and only install for that architecture.
+ # If target_arch is not specified, then only install for the host
+ # architecture.
+ if 'target_arch=x64' in gyp_defines:
+ return 'amd64'
+ elif 'target_arch=ia32' in gyp_defines:
+ return 'i386'
+ elif 'target_arch=arm' in gyp_defines:
+ return 'arm'
+ elif 'target_arch=mipsel' in gyp_defines:
+ return 'mips'
+
+ # Figure out host arch using build/detect_host_arch.py and
+ # set target_arch to host arch
+ build_dir = os.path.dirname(os.path.dirname(os.path.join(SCRIPT_DIR)))
+ sys.path.append(build_dir)
+ import detect_host_arch
+
+ detected_host_arch = detect_host_arch.HostArch()
+ if detected_host_arch == 'x64':
+ return 'amd64'
+ elif detected_host_arch == 'ia32':
+ return 'i386'
+ elif detected_host_arch == 'arm':
+ return 'arm'
+ elif detected_host_arch == 'mips':
+ return 'mips'
+ else:
+ print "Unknown host arch: %s" % detected_host_arch
+
+ return None
+
+
+def main():
+ if options.running_as_hook and not sys.platform.startswith('linux'):
+ return 0
+
+ gyp_defines = os.environ.get('GYP_DEFINES', '')
+
+ if options.arch:
+ target_arch = options.arch
+ else:
+ target_arch = DetectArch(gyp_defines)
+ if not target_arch:
+ print 'Unable to detect host architecture'
+ return 1
+
+ if options.running_as_hook and target_arch != 'arm' and target_arch != 'mips':
+ # When run from runhooks, only install the sysroot for an Official Chrome
+ # Linux build, except on ARM where we always use a sysroot.
+ skip_if_defined = ['branding=Chrome', 'buildtype=Official']
+ skip_if_undefined = ['chromeos=1']
+ for option in skip_if_defined:
+ if option not in gyp_defines:
+ return 0
+ for option in skip_if_undefined:
+ if option in gyp_defines:
+ return 0
+
+ # The sysroot directory should match the one specified in build/common.gypi.
+ # TODO(thestig) Consider putting this else where to avoid having to recreate
+ # it on every build.
+ linux_dir = os.path.dirname(SCRIPT_DIR)
+ if target_arch == 'amd64':
+ sysroot = os.path.join(linux_dir, SYSROOT_DIR_AMD64)
+ tarball_filename = TARBALL_AMD64
+ tarball_sha1sum = TARBALL_AMD64_SHA1SUM
+ revision = REVISION_AMD64
+ elif target_arch == 'arm':
+ sysroot = os.path.join(linux_dir, SYSROOT_DIR_ARM)
+ tarball_filename = TARBALL_ARM
+ tarball_sha1sum = TARBALL_ARM_SHA1SUM
+ revision = REVISION_ARM
+ elif target_arch == 'i386':
+ sysroot = os.path.join(linux_dir, SYSROOT_DIR_I386)
+ tarball_filename = TARBALL_I386
+ tarball_sha1sum = TARBALL_I386_SHA1SUM
+ revision = REVISION_I386
+ elif target_arch == 'mips':
+ sysroot = os.path.join(linux_dir, SYSROOT_DIR_MIPS)
+ tarball_filename = TARBALL_MIPS
+ tarball_sha1sum = TARBALL_MIPS_SHA1SUM
+ revision = REVISION_MIPS
+ else:
+ print 'Unknown architecture: %s' % target_arch
+ assert(False)
+
+ url = '%s/%s/%s/%s' % (URL_PREFIX, URL_PATH, revision, tarball_filename)
+
+ stamp = os.path.join(sysroot, '.stamp')
+ if os.path.exists(stamp):
+ with open(stamp) as s:
+ if s.read() == url:
+ print 'Debian Wheezy %s root image already up-to-date: %s' % \
+ (target_arch, sysroot)
+ return 0
+
+ print 'Installing Debian Wheezy %s root image: %s' % (target_arch, sysroot)
+ if os.path.isdir(sysroot):
+ shutil.rmtree(sysroot)
+ os.mkdir(sysroot)
+ tarball = os.path.join(sysroot, tarball_filename)
+ print 'Downloading %s' % url
+ sys.stdout.flush()
+ sys.stderr.flush()
+ subprocess.check_call(['curl', '--fail', '-L', url, '-o', tarball])
+ sha1sum = GetSha1(tarball)
+ if sha1sum != tarball_sha1sum:
+ print 'Tarball sha1sum is wrong.'
+ print 'Expected %s, actual: %s' % (tarball_sha1sum, sha1sum)
+ return 1
+ subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot])
+ os.remove(tarball)
+
+ with open(stamp, 'w') as s:
+ s.write(url)
+ return 0
+
+
+if __name__ == '__main__':
+ parser = optparse.OptionParser('usage: %prog [OPTIONS]')
+ parser.add_option('--running-as-hook', action='store_true',
+ default=False, help='Used when running from gclient hooks.'
+ ' In this mode the sysroot will only '
+ 'be installed for official Linux '
+ 'builds or ARM Linux builds')
+ parser.add_option('--arch', type='choice', choices=valid_archs,
+ help='Sysroot architecture: %s' % ', '.join(valid_archs))
+ options, _ = parser.parse_args()
+ sys.exit(main())
diff --git a/build/linux/sysroot_scripts/packagelist.trusty.arm b/build/linux/sysroot_scripts/packagelist.trusty.arm
new file mode 100644
index 00000000000..cd4b671f8b4
--- /dev/null
+++ b/build/linux/sysroot_scripts/packagelist.trusty.arm
@@ -0,0 +1,158 @@
+main/a/alsa-lib/libasound2_1.0.27.2-3ubuntu7_armhf.deb
+main/a/alsa-lib/libasound2-dev_1.0.27.2-3ubuntu7_armhf.deb
+main/a/atk1.0/libatk1.0-0_2.10.0-2ubuntu2_armhf.deb
+main/a/atk1.0/libatk1.0-dev_2.10.0-2ubuntu2_armhf.deb
+main/a/avahi/libavahi-client3_0.6.31-4ubuntu1_armhf.deb
+main/a/avahi/libavahi-common3_0.6.31-4ubuntu1_armhf.deb
+main/c/cairo/libcairo2_1.13.0~20140204-0ubuntu1_armhf.deb
+main/c/cairo/libcairo2-dev_1.13.0~20140204-0ubuntu1_armhf.deb
+main/c/cairo/libcairo-gobject2_1.13.0~20140204-0ubuntu1_armhf.deb
+main/c/cairo/libcairo-script-interpreter2_1.13.0~20140204-0ubuntu1_armhf.deb
+main/c/cups/libcups2_1.7.2-0ubuntu1_armhf.deb
+main/c/cups/libcups2-dev_1.7.2-0ubuntu1_armhf.deb
+main/d/dbus-glib/libdbus-glib-1-2_0.100.2-1_armhf.deb
+main/d/dbus/libdbus-1-3_1.6.18-0ubuntu4_armhf.deb
+main/d/dbus/libdbus-1-dev_1.6.18-0ubuntu4_armhf.deb
+main/e/e2fsprogs/comerr-dev_2.1-1.42.9-3ubuntu1_armhf.deb
+main/e/e2fsprogs/libcomerr2_1.42.9-3ubuntu1_armhf.deb
+main/e/eglibc/libc6_2.19-0ubuntu6_armhf.deb
+main/e/eglibc/libc6-dev_2.19-0ubuntu6_armhf.deb
+main/e/elfutils/libelf1_0.158-0ubuntu5_armhf.deb
+main/e/elfutils/libelf-dev_0.158-0ubuntu5_armhf.deb
+main/e/expat/libexpat1_2.1.0-4ubuntu1_armhf.deb
+main/e/expat/libexpat1-dev_2.1.0-4ubuntu1_armhf.deb
+main/f/fontconfig/libfontconfig1_2.11.0-0ubuntu4_armhf.deb
+main/f/fontconfig/libfontconfig1-dev_2.11.0-0ubuntu4_armhf.deb
+main/f/freetype/libfreetype6_2.5.2-1ubuntu2_armhf.deb
+main/f/freetype/libfreetype6-dev_2.5.2-1ubuntu2_armhf.deb
+main/g/gcc-4.8/gcc-4.8_4.8.2-19ubuntu1_armhf.deb
+main/g/gcc-4.8/libgomp1_4.8.2-19ubuntu1_armhf.deb
+main/g/gcc-4.8/libstdc++-4.8-dev_4.8.2-19ubuntu1_armhf.deb
+main/g/gcc-4.8/libstdc++6_4.8.2-19ubuntu1_armhf.deb
+main/g/gccgo-4.9/libgcc1_4.9-20140406-0ubuntu1_armhf.deb
+main/g/gconf/libgconf2-4_3.2.6-0ubuntu2_armhf.deb
+main/g/gconf/libgconf-2-4_3.2.6-0ubuntu2_armhf.deb
+main/g/gconf/libgconf2-dev_3.2.6-0ubuntu2_armhf.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-0_2.30.7-0ubuntu1_armhf.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-dev_2.30.7-0ubuntu1_armhf.deb
+main/g/glib2.0/libglib2.0-0_2.40.0-2_armhf.deb
+main/g/glib2.0/libglib2.0-dev_2.40.0-2_armhf.deb
+main/g/gnutls26/libgnutls26_2.12.23-12ubuntu2_armhf.deb
+main/g/gnutls26/libgnutls-dev_2.12.23-12ubuntu2_armhf.deb
+main/g/gnutls26/libgnutls-openssl27_2.12.23-12ubuntu2_armhf.deb
+main/g/gnutls26/libgnutlsxx27_2.12.23-12ubuntu2_armhf.deb
+main/g/gtk+2.0/libgtk2.0-0_2.24.23-0ubuntu1_armhf.deb
+main/g/gtk+2.0/libgtk2.0-dev_2.24.23-0ubuntu1_armhf.deb
+main/k/keyutils/libkeyutils1_1.5.6-1_armhf.deb
+main/k/krb5/krb5-multidev_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libgssapi-krb5-2_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libgssrpc4_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libk5crypto3_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkadm5clnt-mit9_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkadm5srv-mit9_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkdb5-7_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkrb5-3_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkrb5-dev_1.12+dfsg-2ubuntu4_armhf.deb
+main/k/krb5/libkrb5support0_1.12+dfsg-2ubuntu4_armhf.deb
+main/libc/libcap2/libcap2_2.24-0ubuntu2_armhf.deb
+main/libc/libcap2/libcap-dev_2.24-0ubuntu2_armhf.deb
+main/libd/libdrm/libdrm2_2.4.52-1_armhf.deb
+main/libe/libexif/libexif12_0.6.21-1ubuntu1_armhf.deb
+main/libe/libexif/libexif-dev_0.6.21-1ubuntu1_armhf.deb
+main/libf/libffi/libffi6_3.1~rc1+r3.0.13-12_armhf.deb
+main/libg/libgcrypt11/libgcrypt11_1.5.3-2ubuntu4_armhf.deb
+main/libg/libgcrypt11/libgcrypt11-dev_1.5.3-2ubuntu4_armhf.deb
+main/libg/libgnome-keyring/libgnome-keyring0_3.8.0-2_armhf.deb
+main/libg/libgnome-keyring/libgnome-keyring-dev_3.8.0-2_armhf.deb
+main/libg/libgpg-error/libgpg-error0_1.12-0.2ubuntu1_armhf.deb
+main/libg/libgpg-error/libgpg-error-dev_1.12-0.2ubuntu1_armhf.deb
+main/libn/libnss-db/libnss-db_2.2.3pre1-5build3_armhf.deb
+main/libp/libp11/libp11-2_0.2.8-3ubuntu1_armhf.deb
+main/libp/libpng/libpng12-0_1.2.50-1ubuntu2_armhf.deb
+main/libp/libpng/libpng12-dev_1.2.50-1ubuntu2_armhf.deb
+main/libs/libselinux/libselinux1_2.2.2-1_armhf.deb
+main/libt/libtasn1-6/libtasn1-6_3.4-3_armhf.deb
+main/libx/libx11/libx11-6_1.6.2-1ubuntu2_armhf.deb
+main/libx/libx11/libx11-dev_1.6.2-1ubuntu2_armhf.deb
+main/libx/libx11/libx11-xcb1_1.6.2-1ubuntu2_armhf.deb
+main/libx/libxau/libxau6_1.0.8-1_armhf.deb
+main/libx/libxau/libxau-dev_1.0.8-1_armhf.deb
+main/libx/libxcb/libxcb1_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb1-dev_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb-glx0_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb-render0_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb-render0-dev_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb-shm0_1.10-2ubuntu1_armhf.deb
+main/libx/libxcb/libxcb-shm0-dev_1.10-2ubuntu1_armhf.deb
+main/libx/libxcomposite/libxcomposite1_0.4.4-1_armhf.deb
+main/libx/libxcomposite/libxcomposite-dev_0.4.4-1_armhf.deb
+main/libx/libxcursor/libxcursor1_1.1.14-1_armhf.deb
+main/libx/libxcursor/libxcursor-dev_1.1.14-1_armhf.deb
+main/libx/libxdamage/libxdamage1_1.1.4-1ubuntu1_armhf.deb
+main/libx/libxdamage/libxdamage-dev_1.1.4-1ubuntu1_armhf.deb
+main/libx/libxdmcp/libxdmcp6_1.1.1-1_armhf.deb
+main/libx/libxext/libxext6_1.3.2-1_armhf.deb
+main/libx/libxext/libxext-dev_1.3.2-1_armhf.deb
+main/libx/libxfixes/libxfixes3_5.0.1-1ubuntu1_armhf.deb
+main/libx/libxfixes/libxfixes-dev_5.0.1-1ubuntu1_armhf.deb
+main/libx/libxi/libxi6_1.7.1.901-1ubuntu1_armhf.deb
+main/libx/libxi/libxi-dev_1.7.1.901-1ubuntu1_armhf.deb
+main/libx/libxinerama/libxinerama1_1.1.3-1_armhf.deb
+main/libx/libxinerama/libxinerama-dev_1.1.3-1_armhf.deb
+main/libx/libxrandr/libxrandr2_1.4.2-1_armhf.deb
+main/libx/libxrandr/libxrandr-dev_1.4.2-1_armhf.deb
+main/libx/libxrender/libxrender1_0.9.8-1_armhf.deb
+main/libx/libxrender/libxrender-dev_0.9.8-1_armhf.deb
+main/libx/libxss/libxss1_1.2.2-1_armhf.deb
+main/libx/libxss/libxss-dev_1.2.2-1_armhf.deb
+main/libx/libxt/libxt6_1.1.4-1_armhf.deb
+main/libx/libxt/libxt-dev_1.1.4-1_armhf.deb
+main/libx/libxtst/libxtst6_1.2.2-1_armhf.deb
+main/libx/libxtst/libxtst-dev_1.2.2-1_armhf.deb
+main/libx/libxxf86vm/libxxf86vm1_1.1.3-1_armhf.deb
+main/l/linux/linux-libc-dev_3.13.0-24.46_armhf.deb
+main/m/mesa/libgl1-mesa-dev_10.1.0-4ubuntu5_armhf.deb
+main/m/mesa/libgl1-mesa-glx_10.1.0-4ubuntu5_armhf.deb
+main/m/mesa/libglapi-mesa_10.1.0-4ubuntu5_armhf.deb
+main/m/mesa/mesa-common-dev_10.1.0-4ubuntu5_armhf.deb
+main/n/nspr/libnspr4_4.10.2-1ubuntu1_armhf.deb
+main/n/nspr/libnspr4-dev_4.10.2-1ubuntu1_armhf.deb
+main/n/nss/libnss3_3.15.4-1ubuntu7_armhf.deb
+main/n/nss/libnss3-dev_3.15.4-1ubuntu7_armhf.deb
+main/o/openssl/libssl1.0.0_1.0.1f-1ubuntu2_armhf.deb
+main/o/openssl/libssl-dev_1.0.1f-1ubuntu2_armhf.deb
+main/o/orbit2/liborbit2_2.14.19-0.3_armhf.deb
+main/p/p11-kit/libp11-kit0_0.20.2-2ubuntu2_armhf.deb
+main/p/pam/libpam0g_1.1.8-1ubuntu2_armhf.deb
+main/p/pam/libpam0g-dev_1.1.8-1ubuntu2_armhf.deb
+main/p/pango1.0/libpango-1.0-0_1.36.3-1ubuntu1_armhf.deb
+main/p/pango1.0/libpango1.0-dev_1.36.3-1ubuntu1_armhf.deb
+main/p/pango1.0/libpangocairo-1.0-0_1.36.3-1ubuntu1_armhf.deb
+main/p/pango1.0/libpangoft2-1.0-0_1.36.3-1ubuntu1_armhf.deb
+main/p/pango1.0/libpangoxft-1.0-0_1.36.3-1ubuntu1_armhf.deb
+main/p/pciutils/libpci3_3.2.1-1ubuntu5_armhf.deb
+main/p/pciutils/libpci-dev_3.2.1-1ubuntu5_armhf.deb
+main/p/pcre3/libpcre3_8.31-2ubuntu2_armhf.deb
+main/p/pcre3/libpcre3-dev_8.31-2ubuntu2_armhf.deb
+main/p/pcre3/libpcrecpp0_8.31-2ubuntu2_armhf.deb
+main/p/pixman/libpixman-1-0_0.30.2-2ubuntu1_armhf.deb
+main/p/pixman/libpixman-1-dev_0.30.2-2ubuntu1_armhf.deb
+main/p/pulseaudio/libpulse0_4.0-0ubuntu11_armhf.deb
+main/p/pulseaudio/libpulse-dev_4.0-0ubuntu11_armhf.deb
+main/p/pulseaudio/libpulse-mainloop-glib0_4.0-0ubuntu11_armhf.deb
+main/s/speech-dispatcher/libspeechd2_0.8-5ubuntu1_armhf.deb
+main/s/speech-dispatcher/libspeechd-dev_0.8-5ubuntu1_armhf.deb
+main/s/speech-dispatcher/speech-dispatcher_0.8-5ubuntu1_armhf.deb
+main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb
+main/x/x11proto-core/x11proto-core-dev_7.0.24-1_all.deb
+main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb
+main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2ubuntu2_all.deb
+main/x/x11proto-input/x11proto-input-dev_2.3-1_all.deb
+main/x/x11proto-kb/x11proto-kb-dev_1.0.6-2_all.deb
+main/x/x11proto-randr/x11proto-randr-dev_1.4.0+git20120101.is.really.1.4.0-0ubuntu1_all.deb
+main/x/x11proto-record/x11proto-record-dev_1.14.2-1_all.deb
+main/x/x11proto-render/x11proto-render-dev_0.11.1-2_all.deb
+main/x/x11proto-scrnsaver/x11proto-scrnsaver-dev_1.2.2-1_all.deb
+main/x/x11proto-xext/x11proto-xext-dev_7.3.0-1_all.deb
+main/z/zlib/zlib1g_1.2.8.dfsg-1ubuntu1_armhf.deb
+main/z/zlib/zlib1g-dev_1.2.8.dfsg-1ubuntu1_armhf.deb
diff --git a/build/linux/sysroot_scripts/packagelist.wheezy.amd64 b/build/linux/sysroot_scripts/packagelist.wheezy.amd64
new file mode 100644
index 00000000000..ced7dfdb5ea
--- /dev/null
+++ b/build/linux/sysroot_scripts/packagelist.wheezy.amd64
@@ -0,0 +1,157 @@
+main/a/alsa-lib/libasound2_1.0.25-4_amd64.deb
+main/a/alsa-lib/libasound2-dev_1.0.25-4_amd64.deb
+main/a/atk1.0/libatk1.0-0_2.4.0-2_amd64.deb
+main/a/atk1.0/libatk1.0-dev_2.4.0-2_amd64.deb
+main/a/attr/libattr1_2.4.46-8_amd64.deb
+main/a/avahi/libavahi-client3_0.6.31-2_amd64.deb
+main/a/avahi/libavahi-common3_0.6.31-2_amd64.deb
+main/c/cairo/libcairo2_1.12.2-3_amd64.deb
+main/c/cairo/libcairo2-dev_1.12.2-3_amd64.deb
+main/c/cairo/libcairo-gobject2_1.12.2-3_amd64.deb
+main/c/cairo/libcairo-script-interpreter2_1.12.2-3_amd64.deb
+main/c/cups/libcups2_1.5.3-5+deb7u4_amd64.deb
+main/c/cups/libcups2-dev_1.5.3-5+deb7u4_amd64.deb
+main/d/dbus-glib/libdbus-glib-1-2_0.100.2-1_amd64.deb
+main/d/dbus/libdbus-1-3_1.6.8-1+deb7u5_amd64.deb
+main/d/dbus/libdbus-1-dev_1.6.8-1+deb7u5_amd64.deb
+main/e/e2fsprogs/comerr-dev_2.1-1.42.5-1.1_amd64.deb
+main/e/e2fsprogs/libcomerr2_1.42.5-1.1_amd64.deb
+main/e/eglibc/libc6_2.13-38+deb7u6_amd64.deb
+main/e/eglibc/libc6-dev_2.13-38+deb7u6_amd64.deb
+main/e/elfutils/libelf1_0.152-1+wheezy1_amd64.deb
+main/e/elfutils/libelf-dev_0.152-1+wheezy1_amd64.deb
+main/e/expat/libexpat1_2.1.0-1+deb7u1_amd64.deb
+main/e/expat/libexpat1-dev_2.1.0-1+deb7u1_amd64.deb
+main/f/fontconfig/libfontconfig1_2.9.0-7.1_amd64.deb
+main/f/fontconfig/libfontconfig1-dev_2.9.0-7.1_amd64.deb
+main/f/freetype/libfreetype6_2.4.9-1.1_amd64.deb
+main/f/freetype/libfreetype6-dev_2.4.9-1.1_amd64.deb
+main/g/gcc-4.6/gcc-4.6_4.6.3-14_amd64.deb
+main/g/gcc-4.6/libstdc++6-4.6-dev_4.6.3-14_amd64.deb
+main/g/gcc-4.7/libgcc1_4.7.2-5_amd64.deb
+main/g/gcc-4.7/libgomp1_4.7.2-5_amd64.deb
+main/g/gcc-4.7/libquadmath0_4.7.2-5_amd64.deb
+main/g/gcc-4.7/libstdc++6_4.7.2-5_amd64.deb
+main/g/gconf/libgconf-2-4_3.2.5-1+build1_amd64.deb
+main/g/gconf/libgconf2-4_3.2.5-1+build1_amd64.deb
+main/g/gconf/libgconf2-dev_3.2.5-1+build1_amd64.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-0_2.26.1-1_amd64.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-dev_2.26.1-1_amd64.deb
+main/g/glib2.0/libglib2.0-0_2.33.12+really2.32.4-5_amd64.deb
+main/g/glib2.0/libglib2.0-dev_2.33.12+really2.32.4-5_amd64.deb
+main/g/gnutls26/libgnutls26_2.12.20-8+deb7u2_amd64.deb
+main/g/gnutls26/libgnutls-dev_2.12.20-8+deb7u2_amd64.deb
+main/g/gnutls26/libgnutls-openssl27_2.12.20-8+deb7u2_amd64.deb
+main/g/gnutls26/libgnutlsxx27_2.12.20-8+deb7u2_amd64.deb
+main/g/gtk+2.0/libgtk2.0-0_2.24.10-2_amd64.deb
+main/g/gtk+2.0/libgtk2.0-dev_2.24.10-2_amd64.deb
+main/k/keyutils/libkeyutils1_1.5.5-3+deb7u1_amd64.deb
+main/k/krb5/krb5-multidev_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libgssapi-krb5-2_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libgssrpc4_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libk5crypto3_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkadm5clnt-mit8_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkadm5srv-mit8_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkdb5-6_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkrb5-3_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkrb5-dev_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/k/krb5/libkrb5support0_1.10.1+dfsg-5+deb7u2_amd64.deb
+main/libc/libcap2/libcap2_2.22-1.2_amd64.deb
+main/libc/libcap2/libcap-dev_2.22-1.2_amd64.deb
+main/libd/libdrm/libdrm2_2.4.40-1~deb7u2_amd64.deb
+main/libe/libexif/libexif12_0.6.20-3_amd64.deb
+main/libe/libexif/libexif-dev_0.6.20-3_amd64.deb
+main/libf/libffi/libffi5_3.0.10-3_amd64.deb
+main/libg/libgcrypt11/libgcrypt11_1.5.0-5+deb7u2_amd64.deb
+main/libg/libgcrypt11/libgcrypt11-dev_1.5.0-5+deb7u2_amd64.deb
+main/libg/libgnome-keyring/libgnome-keyring0_3.4.1-1_amd64.deb
+main/libg/libgnome-keyring/libgnome-keyring-dev_3.4.1-1_amd64.deb
+main/libg/libgpg-error/libgpg-error0_1.10-3.1_amd64.deb
+main/libg/libgpg-error/libgpg-error-dev_1.10-3.1_amd64.deb
+main/libn/libnss-db/libnss-db_2.2.3pre1-4_amd64.deb
+main/libp/libp11/libp11-2_0.2.8-2_amd64.deb
+main/libp/libpng/libpng12-0_1.2.49-1_amd64.deb
+main/libp/libpng/libpng12-dev_1.2.49-1_amd64.deb
+main/libs/libselinux/libselinux1_2.1.9-5_amd64.deb
+main/libt/libtasn1-3/libtasn1-3_2.13-2+deb7u1_amd64.deb
+main/libx/libx11/libx11-6_1.5.0-1+deb7u1_amd64.deb
+main/libx/libx11/libx11-dev_1.5.0-1+deb7u1_amd64.deb
+main/libx/libx11/libx11-xcb1_1.5.0-1+deb7u1_amd64.deb
+main/libx/libxau/libxau6_1.0.7-1_amd64.deb
+main/libx/libxau/libxau-dev_1.0.7-1_amd64.deb
+main/libx/libxcb/libxcb1_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb1-dev_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb-glx0_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb-render0_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb-render0-dev_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb-shm0_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcb/libxcb-shm0-dev_1.8.1-2+deb7u1_amd64.deb
+main/libx/libxcomposite/libxcomposite1_0.4.3-2_amd64.deb
+main/libx/libxcomposite/libxcomposite-dev_0.4.3-2_amd64.deb
+main/libx/libxcursor/libxcursor1_1.1.13-1+deb7u1_amd64.deb
+main/libx/libxcursor/libxcursor-dev_1.1.13-1+deb7u1_amd64.deb
+main/libx/libxdamage/libxdamage1_1.1.3-2_amd64.deb
+main/libx/libxdamage/libxdamage-dev_1.1.3-2_amd64.deb
+main/libx/libxdmcp/libxdmcp6_1.1.1-1_amd64.deb
+main/libx/libxext/libxext6_1.3.1-2+deb7u1_amd64.deb
+main/libx/libxext/libxext-dev_1.3.1-2+deb7u1_amd64.deb
+main/libx/libxfixes/libxfixes3_5.0-4+deb7u1_amd64.deb
+main/libx/libxfixes/libxfixes-dev_5.0-4+deb7u1_amd64.deb
+main/libx/libxi/libxi6_1.6.1-1+deb7u1_amd64.deb
+main/libx/libxi/libxi-dev_1.6.1-1+deb7u1_amd64.deb
+main/libx/libxinerama/libxinerama1_1.1.2-1+deb7u1_amd64.deb
+main/libx/libxinerama/libxinerama-dev_1.1.2-1+deb7u1_amd64.deb
+main/libx/libxrandr/libxrandr2_1.3.2-2+deb7u1_amd64.deb
+main/libx/libxrandr/libxrandr-dev_1.3.2-2+deb7u1_amd64.deb
+main/libx/libxrender/libxrender1_0.9.7-1+deb7u1_amd64.deb
+main/libx/libxrender/libxrender-dev_0.9.7-1+deb7u1_amd64.deb
+main/libx/libxss/libxss1_1.2.2-1_amd64.deb
+main/libx/libxss/libxss-dev_1.2.2-1_amd64.deb
+main/libx/libxt/libxt6_1.1.3-1+deb7u1_amd64.deb
+main/libx/libxt/libxt-dev_1.1.3-1+deb7u1_amd64.deb
+main/libx/libxtst/libxtst6_1.2.1-1+deb7u1_amd64.deb
+main/libx/libxtst/libxtst-dev_1.2.1-1+deb7u1_amd64.deb
+main/libx/libxxf86vm/libxxf86vm1_1.1.2-1+deb7u1_amd64.deb
+main/l/linux/linux-libc-dev_3.2.65-1_amd64.deb
+main/m/mesa/libgl1-mesa-dev_8.0.5-4+deb7u2_amd64.deb
+main/m/mesa/libgl1-mesa-glx_8.0.5-4+deb7u2_amd64.deb
+main/m/mesa/libglapi-mesa_8.0.5-4+deb7u2_amd64.deb
+main/m/mesa/mesa-common-dev_8.0.5-4+deb7u2_amd64.deb
+main/n/nspr/libnspr4_4.9.2-1+deb7u2_amd64.deb
+main/n/nspr/libnspr4-dev_4.9.2-1+deb7u2_amd64.deb
+main/n/nss/libnss3_3.14.5-1+deb7u3_amd64.deb
+main/n/nss/libnss3-dev_3.14.5-1+deb7u3_amd64.deb
+main/o/openssl/libssl1.0.0_1.0.1e-2+deb7u13_amd64.deb
+main/o/openssl/libssl-dev_1.0.1e-2+deb7u13_amd64.deb
+main/o/orbit2/liborbit2_2.14.19-0.1_amd64.deb
+main/p/p11-kit/libp11-kit0_0.12-3_amd64.deb
+main/p/pam/libpam0g_1.1.3-7.1_amd64.deb
+main/p/pam/libpam0g-dev_1.1.3-7.1_amd64.deb
+main/p/pango1.0/libpango1.0-0_1.30.0-1_amd64.deb
+main/p/pango1.0/libpango1.0-dev_1.30.0-1_amd64.deb
+main/p/pciutils/libpci3_3.1.9-6_amd64.deb
+main/p/pciutils/libpci-dev_3.1.9-6_amd64.deb
+main/p/pcre3/libpcre3_8.30-5_amd64.deb
+main/p/pcre3/libpcre3-dev_8.30-5_amd64.deb
+main/p/pcre3/libpcrecpp0_8.30-5_amd64.deb
+main/p/pixman/libpixman-1-0_0.26.0-4+deb7u1_amd64.deb
+main/p/pixman/libpixman-1-dev_0.26.0-4+deb7u1_amd64.deb
+main/p/pulseaudio/libpulse0_2.0-6.1_amd64.deb
+main/p/pulseaudio/libpulse-dev_2.0-6.1_amd64.deb
+main/p/pulseaudio/libpulse-mainloop-glib0_2.0-6.1_amd64.deb
+main/s/speech-dispatcher/libspeechd2_0.7.1-6.2_amd64.deb
+main/s/speech-dispatcher/libspeechd-dev_0.7.1-6.2_amd64.deb
+main/s/speech-dispatcher/speech-dispatcher_0.7.1-6.2_amd64.deb
+main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb
+main/x/x11proto-core/x11proto-core-dev_7.0.23-1_all.deb
+main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb
+main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb
+main/x/x11proto-input/x11proto-input-dev_2.2-1_all.deb
+main/x/x11proto-kb/x11proto-kb-dev_1.0.6-2_all.deb
+main/x/x11proto-randr/x11proto-randr-dev_1.3.2-2_all.deb
+main/x/x11proto-record/x11proto-record-dev_1.14.2-1_all.deb
+main/x/x11proto-render/x11proto-render-dev_0.11.1-2_all.deb
+main/x/x11proto-scrnsaver/x11proto-scrnsaver-dev_1.2.2-1_all.deb
+main/x/x11proto-xext/x11proto-xext-dev_7.2.1-1_all.deb
+main/z/zlib/zlib1g_1.2.7.dfsg-13_amd64.deb
+main/z/zlib/zlib1g-dev_1.2.7.dfsg-13_amd64.deb
diff --git a/build/linux/sysroot_scripts/packagelist.wheezy.arm b/build/linux/sysroot_scripts/packagelist.wheezy.arm
new file mode 100644
index 00000000000..3d79cb37c6a
--- /dev/null
+++ b/build/linux/sysroot_scripts/packagelist.wheezy.arm
@@ -0,0 +1,156 @@
+main/a/alsa-lib/libasound2_1.0.25-4_armhf.deb
+main/a/alsa-lib/libasound2-dev_1.0.25-4_armhf.deb
+main/a/atk1.0/libatk1.0-0_2.4.0-2_armhf.deb
+main/a/atk1.0/libatk1.0-dev_2.4.0-2_armhf.deb
+main/a/attr/libattr1_2.4.46-8_armhf.deb
+main/a/avahi/libavahi-client3_0.6.31-2_armhf.deb
+main/a/avahi/libavahi-common3_0.6.31-2_armhf.deb
+main/c/cairo/libcairo2_1.12.2-3_armhf.deb
+main/c/cairo/libcairo2-dev_1.12.2-3_armhf.deb
+main/c/cairo/libcairo-gobject2_1.12.2-3_armhf.deb
+main/c/cairo/libcairo-script-interpreter2_1.12.2-3_armhf.deb
+main/c/cups/libcups2_1.5.3-5+deb7u4_armhf.deb
+main/c/cups/libcups2-dev_1.5.3-5+deb7u4_armhf.deb
+main/d/dbus-glib/libdbus-glib-1-2_0.100.2-1_armhf.deb
+main/d/dbus/libdbus-1-3_1.6.8-1+deb7u5_armhf.deb
+main/d/dbus/libdbus-1-dev_1.6.8-1+deb7u5_armhf.deb
+main/e/e2fsprogs/comerr-dev_2.1-1.42.5-1.1_armhf.deb
+main/e/e2fsprogs/libcomerr2_1.42.5-1.1_armhf.deb
+main/e/eglibc/libc6_2.13-38+deb7u6_armhf.deb
+main/e/eglibc/libc6-dev_2.13-38+deb7u6_armhf.deb
+main/e/elfutils/libelf1_0.152-1+wheezy1_armhf.deb
+main/e/elfutils/libelf-dev_0.152-1+wheezy1_armhf.deb
+main/e/expat/libexpat1_2.1.0-1+deb7u1_armhf.deb
+main/e/expat/libexpat1-dev_2.1.0-1+deb7u1_armhf.deb
+main/f/fontconfig/libfontconfig1_2.9.0-7.1_armhf.deb
+main/f/fontconfig/libfontconfig1-dev_2.9.0-7.1_armhf.deb
+main/f/freetype/libfreetype6_2.4.9-1.1_armhf.deb
+main/f/freetype/libfreetype6-dev_2.4.9-1.1_armhf.deb
+main/g/gcc-4.6/gcc-4.6_4.6.3-14_armhf.deb
+main/g/gcc-4.6/libstdc++6-4.6-dev_4.6.3-14_armhf.deb
+main/g/gcc-4.7/libgcc1_4.7.2-5_armhf.deb
+main/g/gcc-4.7/libgomp1_4.7.2-5_armhf.deb
+main/g/gcc-4.7/libstdc++6_4.7.2-5_armhf.deb
+main/g/gconf/libgconf2-4_3.2.5-1+build1_armhf.deb
+main/g/gconf/libgconf-2-4_3.2.5-1+build1_armhf.deb
+main/g/gconf/libgconf2-dev_3.2.5-1+build1_armhf.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-0_2.26.1-1_armhf.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-dev_2.26.1-1_armhf.deb
+main/g/glib2.0/libglib2.0-0_2.33.12+really2.32.4-5_armhf.deb
+main/g/glib2.0/libglib2.0-dev_2.33.12+really2.32.4-5_armhf.deb
+main/g/gnutls26/libgnutls26_2.12.20-8+deb7u2_armhf.deb
+main/g/gnutls26/libgnutls-dev_2.12.20-8+deb7u2_armhf.deb
+main/g/gnutls26/libgnutls-openssl27_2.12.20-8+deb7u2_armhf.deb
+main/g/gnutls26/libgnutlsxx27_2.12.20-8+deb7u2_armhf.deb
+main/g/gtk+2.0/libgtk2.0-0_2.24.10-2_armhf.deb
+main/g/gtk+2.0/libgtk2.0-dev_2.24.10-2_armhf.deb
+main/k/keyutils/libkeyutils1_1.5.5-3+deb7u1_armhf.deb
+main/k/krb5/krb5-multidev_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libgssapi-krb5-2_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libgssrpc4_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libk5crypto3_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkadm5clnt-mit8_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkadm5srv-mit8_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkdb5-6_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkrb5-3_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkrb5-dev_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/k/krb5/libkrb5support0_1.10.1+dfsg-5+deb7u2_armhf.deb
+main/libc/libcap2/libcap2_2.22-1.2_armhf.deb
+main/libc/libcap2/libcap-dev_2.22-1.2_armhf.deb
+main/libd/libdrm/libdrm2_2.4.40-1~deb7u2_armhf.deb
+main/libe/libexif/libexif12_0.6.20-3_armhf.deb
+main/libe/libexif/libexif-dev_0.6.20-3_armhf.deb
+main/libf/libffi/libffi5_3.0.10-3+b1_armhf.deb
+main/libg/libgcrypt11/libgcrypt11_1.5.0-5+deb7u2_armhf.deb
+main/libg/libgcrypt11/libgcrypt11-dev_1.5.0-5+deb7u2_armhf.deb
+main/libg/libgnome-keyring/libgnome-keyring0_3.4.1-1_armhf.deb
+main/libg/libgnome-keyring/libgnome-keyring-dev_3.4.1-1_armhf.deb
+main/libg/libgpg-error/libgpg-error0_1.10-3.1_armhf.deb
+main/libg/libgpg-error/libgpg-error-dev_1.10-3.1_armhf.deb
+main/libn/libnss-db/libnss-db_2.2.3pre1-4_armhf.deb
+main/libp/libp11/libp11-2_0.2.8-2_armhf.deb
+main/libp/libpng/libpng12-0_1.2.49-1_armhf.deb
+main/libp/libpng/libpng12-dev_1.2.49-1_armhf.deb
+main/libs/libselinux/libselinux1_2.1.9-5_armhf.deb
+main/libt/libtasn1-3/libtasn1-3_2.13-2+deb7u1_armhf.deb
+main/libx/libx11/libx11-6_1.5.0-1+deb7u1_armhf.deb
+main/libx/libx11/libx11-dev_1.5.0-1+deb7u1_armhf.deb
+main/libx/libx11/libx11-xcb1_1.5.0-1+deb7u1_armhf.deb
+main/libx/libxau/libxau6_1.0.7-1_armhf.deb
+main/libx/libxau/libxau-dev_1.0.7-1_armhf.deb
+main/libx/libxcb/libxcb1_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb1-dev_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb-glx0_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb-render0_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb-render0-dev_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb-shm0_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcb/libxcb-shm0-dev_1.8.1-2+deb7u1_armhf.deb
+main/libx/libxcomposite/libxcomposite1_0.4.3-2+b1_armhf.deb
+main/libx/libxcomposite/libxcomposite-dev_0.4.3-2+b1_armhf.deb
+main/libx/libxcursor/libxcursor1_1.1.13-1+deb7u1_armhf.deb
+main/libx/libxcursor/libxcursor-dev_1.1.13-1+deb7u1_armhf.deb
+main/libx/libxdamage/libxdamage1_1.1.3-2+b1_armhf.deb
+main/libx/libxdamage/libxdamage-dev_1.1.3-2+b1_armhf.deb
+main/libx/libxdmcp/libxdmcp6_1.1.1-1_armhf.deb
+main/libx/libxext/libxext6_1.3.1-2+deb7u1_armhf.deb
+main/libx/libxext/libxext-dev_1.3.1-2+deb7u1_armhf.deb
+main/libx/libxfixes/libxfixes3_5.0-4+deb7u1_armhf.deb
+main/libx/libxfixes/libxfixes-dev_5.0-4+deb7u1_armhf.deb
+main/libx/libxi/libxi6_1.6.1-1+deb7u1_armhf.deb
+main/libx/libxi/libxi-dev_1.6.1-1+deb7u1_armhf.deb
+main/libx/libxinerama/libxinerama1_1.1.2-1+deb7u1_armhf.deb
+main/libx/libxinerama/libxinerama-dev_1.1.2-1+deb7u1_armhf.deb
+main/libx/libxrandr/libxrandr2_1.3.2-2+deb7u1_armhf.deb
+main/libx/libxrandr/libxrandr-dev_1.3.2-2+deb7u1_armhf.deb
+main/libx/libxrender/libxrender1_0.9.7-1+deb7u1_armhf.deb
+main/libx/libxrender/libxrender-dev_0.9.7-1+deb7u1_armhf.deb
+main/libx/libxss/libxss1_1.2.2-1_armhf.deb
+main/libx/libxss/libxss-dev_1.2.2-1_armhf.deb
+main/libx/libxt/libxt6_1.1.3-1+deb7u1_armhf.deb
+main/libx/libxt/libxt-dev_1.1.3-1+deb7u1_armhf.deb
+main/libx/libxtst/libxtst6_1.2.1-1+deb7u1_armhf.deb
+main/libx/libxtst/libxtst-dev_1.2.1-1+deb7u1_armhf.deb
+main/libx/libxxf86vm/libxxf86vm1_1.1.2-1+deb7u1_armhf.deb
+main/l/linux/linux-libc-dev_3.2.65-1_armhf.deb
+main/m/mesa/libgl1-mesa-dev_8.0.5-4+deb7u2_armhf.deb
+main/m/mesa/libgl1-mesa-glx_8.0.5-4+deb7u2_armhf.deb
+main/m/mesa/libglapi-mesa_8.0.5-4+deb7u2_armhf.deb
+main/m/mesa/mesa-common-dev_8.0.5-4+deb7u2_armhf.deb
+main/n/nspr/libnspr4_4.9.2-1+deb7u2_armhf.deb
+main/n/nspr/libnspr4-dev_4.9.2-1+deb7u2_armhf.deb
+main/n/nss/libnss3_3.14.5-1+deb7u3_armhf.deb
+main/n/nss/libnss3-dev_3.14.5-1+deb7u3_armhf.deb
+main/o/openssl/libssl1.0.0_1.0.1e-2+deb7u13_armhf.deb
+main/o/openssl/libssl-dev_1.0.1e-2+deb7u13_armhf.deb
+main/o/orbit2/liborbit2_2.14.19-0.1_armhf.deb
+main/p/p11-kit/libp11-kit0_0.12-3_armhf.deb
+main/p/pam/libpam0g_1.1.3-7.1_armhf.deb
+main/p/pam/libpam0g-dev_1.1.3-7.1_armhf.deb
+main/p/pango1.0/libpango1.0-0_1.30.0-1_armhf.deb
+main/p/pango1.0/libpango1.0-dev_1.30.0-1_armhf.deb
+main/p/pciutils/libpci3_3.1.9-6_armhf.deb
+main/p/pciutils/libpci-dev_3.1.9-6_armhf.deb
+main/p/pcre3/libpcre3_8.30-5_armhf.deb
+main/p/pcre3/libpcre3-dev_8.30-5_armhf.deb
+main/p/pcre3/libpcrecpp0_8.30-5_armhf.deb
+main/p/pixman/libpixman-1-0_0.26.0-4+deb7u1_armhf.deb
+main/p/pixman/libpixman-1-dev_0.26.0-4+deb7u1_armhf.deb
+main/p/pulseaudio/libpulse0_2.0-6.1_armhf.deb
+main/p/pulseaudio/libpulse-dev_2.0-6.1_armhf.deb
+main/p/pulseaudio/libpulse-mainloop-glib0_2.0-6.1_armhf.deb
+main/s/speech-dispatcher/libspeechd2_0.7.1-6.2_armhf.deb
+main/s/speech-dispatcher/libspeechd-dev_0.7.1-6.2_armhf.deb
+main/s/speech-dispatcher/speech-dispatcher_0.7.1-6.2_armhf.deb
+main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb
+main/x/x11proto-core/x11proto-core-dev_7.0.23-1_all.deb
+main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb
+main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb
+main/x/x11proto-input/x11proto-input-dev_2.2-1_all.deb
+main/x/x11proto-kb/x11proto-kb-dev_1.0.6-2_all.deb
+main/x/x11proto-randr/x11proto-randr-dev_1.3.2-2_all.deb
+main/x/x11proto-record/x11proto-record-dev_1.14.2-1_all.deb
+main/x/x11proto-render/x11proto-render-dev_0.11.1-2_all.deb
+main/x/x11proto-scrnsaver/x11proto-scrnsaver-dev_1.2.2-1_all.deb
+main/x/x11proto-xext/x11proto-xext-dev_7.2.1-1_all.deb
+main/z/zlib/zlib1g_1.2.7.dfsg-13_armhf.deb
+main/z/zlib/zlib1g-dev_1.2.7.dfsg-13_armhf.deb
diff --git a/build/linux/sysroot_scripts/packagelist.wheezy.i386 b/build/linux/sysroot_scripts/packagelist.wheezy.i386
new file mode 100644
index 00000000000..1379fee4b87
--- /dev/null
+++ b/build/linux/sysroot_scripts/packagelist.wheezy.i386
@@ -0,0 +1,157 @@
+main/a/alsa-lib/libasound2_1.0.25-4_i386.deb
+main/a/alsa-lib/libasound2-dev_1.0.25-4_i386.deb
+main/a/atk1.0/libatk1.0-0_2.4.0-2_i386.deb
+main/a/atk1.0/libatk1.0-dev_2.4.0-2_i386.deb
+main/a/attr/libattr1_2.4.46-8_i386.deb
+main/a/avahi/libavahi-client3_0.6.31-2_i386.deb
+main/a/avahi/libavahi-common3_0.6.31-2_i386.deb
+main/c/cairo/libcairo2_1.12.2-3_i386.deb
+main/c/cairo/libcairo2-dev_1.12.2-3_i386.deb
+main/c/cairo/libcairo-gobject2_1.12.2-3_i386.deb
+main/c/cairo/libcairo-script-interpreter2_1.12.2-3_i386.deb
+main/c/cups/libcups2_1.5.3-5+deb7u4_i386.deb
+main/c/cups/libcups2-dev_1.5.3-5+deb7u4_i386.deb
+main/d/dbus-glib/libdbus-glib-1-2_0.100.2-1_i386.deb
+main/d/dbus/libdbus-1-3_1.6.8-1+deb7u5_i386.deb
+main/d/dbus/libdbus-1-dev_1.6.8-1+deb7u5_i386.deb
+main/e/e2fsprogs/comerr-dev_2.1-1.42.5-1.1_i386.deb
+main/e/e2fsprogs/libcomerr2_1.42.5-1.1_i386.deb
+main/e/eglibc/libc6_2.13-38+deb7u6_i386.deb
+main/e/eglibc/libc6-dev_2.13-38+deb7u6_i386.deb
+main/e/elfutils/libelf1_0.152-1+wheezy1_i386.deb
+main/e/elfutils/libelf-dev_0.152-1+wheezy1_i386.deb
+main/e/expat/libexpat1_2.1.0-1+deb7u1_i386.deb
+main/e/expat/libexpat1-dev_2.1.0-1+deb7u1_i386.deb
+main/f/fontconfig/libfontconfig1_2.9.0-7.1_i386.deb
+main/f/fontconfig/libfontconfig1-dev_2.9.0-7.1_i386.deb
+main/f/freetype/libfreetype6_2.4.9-1.1_i386.deb
+main/f/freetype/libfreetype6-dev_2.4.9-1.1_i386.deb
+main/g/gcc-4.6/gcc-4.6_4.6.3-14_i386.deb
+main/g/gcc-4.6/libstdc++6-4.6-dev_4.6.3-14_i386.deb
+main/g/gcc-4.7/libgcc1_4.7.2-5_i386.deb
+main/g/gcc-4.7/libgomp1_4.7.2-5_i386.deb
+main/g/gcc-4.7/libquadmath0_4.7.2-5_i386.deb
+main/g/gcc-4.7/libstdc++6_4.7.2-5_i386.deb
+main/g/gconf/libgconf-2-4_3.2.5-1+build1_i386.deb
+main/g/gconf/libgconf2-4_3.2.5-1+build1_i386.deb
+main/g/gconf/libgconf2-dev_3.2.5-1+build1_i386.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-0_2.26.1-1_i386.deb
+main/g/gdk-pixbuf/libgdk-pixbuf2.0-dev_2.26.1-1_i386.deb
+main/g/glib2.0/libglib2.0-0_2.33.12+really2.32.4-5_i386.deb
+main/g/glib2.0/libglib2.0-dev_2.33.12+really2.32.4-5_i386.deb
+main/g/gnutls26/libgnutls26_2.12.20-8+deb7u2_i386.deb
+main/g/gnutls26/libgnutls-dev_2.12.20-8+deb7u2_i386.deb
+main/g/gnutls26/libgnutls-openssl27_2.12.20-8+deb7u2_i386.deb
+main/g/gnutls26/libgnutlsxx27_2.12.20-8+deb7u2_i386.deb
+main/g/gtk+2.0/libgtk2.0-0_2.24.10-2_i386.deb
+main/g/gtk+2.0/libgtk2.0-dev_2.24.10-2_i386.deb
+main/k/keyutils/libkeyutils1_1.5.5-3+deb7u1_i386.deb
+main/k/krb5/krb5-multidev_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libgssapi-krb5-2_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libgssrpc4_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libk5crypto3_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkadm5clnt-mit8_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkadm5srv-mit8_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkdb5-6_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkrb5-3_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkrb5-dev_1.10.1+dfsg-5+deb7u2_i386.deb
+main/k/krb5/libkrb5support0_1.10.1+dfsg-5+deb7u2_i386.deb
+main/libc/libcap2/libcap2_2.22-1.2_i386.deb
+main/libc/libcap2/libcap-dev_2.22-1.2_i386.deb
+main/libd/libdrm/libdrm2_2.4.40-1~deb7u2_i386.deb
+main/libe/libexif/libexif12_0.6.20-3_i386.deb
+main/libe/libexif/libexif-dev_0.6.20-3_i386.deb
+main/libf/libffi/libffi5_3.0.10-3_i386.deb
+main/libg/libgcrypt11/libgcrypt11_1.5.0-5+deb7u2_i386.deb
+main/libg/libgcrypt11/libgcrypt11-dev_1.5.0-5+deb7u2_i386.deb
+main/libg/libgnome-keyring/libgnome-keyring0_3.4.1-1_i386.deb
+main/libg/libgnome-keyring/libgnome-keyring-dev_3.4.1-1_i386.deb
+main/libg/libgpg-error/libgpg-error0_1.10-3.1_i386.deb
+main/libg/libgpg-error/libgpg-error-dev_1.10-3.1_i386.deb
+main/libn/libnss-db/libnss-db_2.2.3pre1-4_i386.deb
+main/libp/libp11/libp11-2_0.2.8-2_i386.deb
+main/libp/libpng/libpng12-0_1.2.49-1_i386.deb
+main/libp/libpng/libpng12-dev_1.2.49-1_i386.deb
+main/libs/libselinux/libselinux1_2.1.9-5_i386.deb
+main/libt/libtasn1-3/libtasn1-3_2.13-2+deb7u1_i386.deb
+main/libx/libx11/libx11-6_1.5.0-1+deb7u1_i386.deb
+main/libx/libx11/libx11-dev_1.5.0-1+deb7u1_i386.deb
+main/libx/libx11/libx11-xcb1_1.5.0-1+deb7u1_i386.deb
+main/libx/libxau/libxau6_1.0.7-1_i386.deb
+main/libx/libxau/libxau-dev_1.0.7-1_i386.deb
+main/libx/libxcb/libxcb1_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb1-dev_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb-glx0_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb-render0_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb-render0-dev_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb-shm0_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcb/libxcb-shm0-dev_1.8.1-2+deb7u1_i386.deb
+main/libx/libxcomposite/libxcomposite1_0.4.3-2_i386.deb
+main/libx/libxcomposite/libxcomposite-dev_0.4.3-2_i386.deb
+main/libx/libxcursor/libxcursor1_1.1.13-1+deb7u1_i386.deb
+main/libx/libxcursor/libxcursor-dev_1.1.13-1+deb7u1_i386.deb
+main/libx/libxdamage/libxdamage1_1.1.3-2_i386.deb
+main/libx/libxdamage/libxdamage-dev_1.1.3-2_i386.deb
+main/libx/libxdmcp/libxdmcp6_1.1.1-1_i386.deb
+main/libx/libxext/libxext6_1.3.1-2+deb7u1_i386.deb
+main/libx/libxext/libxext-dev_1.3.1-2+deb7u1_i386.deb
+main/libx/libxfixes/libxfixes3_5.0-4+deb7u1_i386.deb
+main/libx/libxfixes/libxfixes-dev_5.0-4+deb7u1_i386.deb
+main/libx/libxi/libxi6_1.6.1-1+deb7u1_i386.deb
+main/libx/libxi/libxi-dev_1.6.1-1+deb7u1_i386.deb
+main/libx/libxinerama/libxinerama1_1.1.2-1+deb7u1_i386.deb
+main/libx/libxinerama/libxinerama-dev_1.1.2-1+deb7u1_i386.deb
+main/libx/libxrandr/libxrandr2_1.3.2-2+deb7u1_i386.deb
+main/libx/libxrandr/libxrandr-dev_1.3.2-2+deb7u1_i386.deb
+main/libx/libxrender/libxrender1_0.9.7-1+deb7u1_i386.deb
+main/libx/libxrender/libxrender-dev_0.9.7-1+deb7u1_i386.deb
+main/libx/libxss/libxss1_1.2.2-1_i386.deb
+main/libx/libxss/libxss-dev_1.2.2-1_i386.deb
+main/libx/libxt/libxt6_1.1.3-1+deb7u1_i386.deb
+main/libx/libxt/libxt-dev_1.1.3-1+deb7u1_i386.deb
+main/libx/libxtst/libxtst6_1.2.1-1+deb7u1_i386.deb
+main/libx/libxtst/libxtst-dev_1.2.1-1+deb7u1_i386.deb
+main/libx/libxxf86vm/libxxf86vm1_1.1.2-1+deb7u1_i386.deb
+main/l/linux/linux-libc-dev_3.2.65-1_i386.deb
+main/m/mesa/libgl1-mesa-dev_8.0.5-4+deb7u2_i386.deb
+main/m/mesa/libgl1-mesa-glx_8.0.5-4+deb7u2_i386.deb
+main/m/mesa/libglapi-mesa_8.0.5-4+deb7u2_i386.deb
+main/m/mesa/mesa-common-dev_8.0.5-4+deb7u2_i386.deb
+main/n/nspr/libnspr4_4.9.2-1+deb7u2_i386.deb
+main/n/nspr/libnspr4-dev_4.9.2-1+deb7u2_i386.deb
+main/n/nss/libnss3_3.14.5-1+deb7u3_i386.deb
+main/n/nss/libnss3-dev_3.14.5-1+deb7u3_i386.deb
+main/o/openssl/libssl1.0.0_1.0.1e-2+deb7u13_i386.deb
+main/o/openssl/libssl-dev_1.0.1e-2+deb7u13_i386.deb
+main/o/orbit2/liborbit2_2.14.19-0.1_i386.deb
+main/p/p11-kit/libp11-kit0_0.12-3_i386.deb
+main/p/pam/libpam0g_1.1.3-7.1_i386.deb
+main/p/pam/libpam0g-dev_1.1.3-7.1_i386.deb
+main/p/pango1.0/libpango1.0-0_1.30.0-1_i386.deb
+main/p/pango1.0/libpango1.0-dev_1.30.0-1_i386.deb
+main/p/pciutils/libpci3_3.1.9-6_i386.deb
+main/p/pciutils/libpci-dev_3.1.9-6_i386.deb
+main/p/pcre3/libpcre3_8.30-5_i386.deb
+main/p/pcre3/libpcre3-dev_8.30-5_i386.deb
+main/p/pcre3/libpcrecpp0_8.30-5_i386.deb
+main/p/pixman/libpixman-1-0_0.26.0-4+deb7u1_i386.deb
+main/p/pixman/libpixman-1-dev_0.26.0-4+deb7u1_i386.deb
+main/p/pulseaudio/libpulse0_2.0-6.1_i386.deb
+main/p/pulseaudio/libpulse-dev_2.0-6.1_i386.deb
+main/p/pulseaudio/libpulse-mainloop-glib0_2.0-6.1_i386.deb
+main/s/speech-dispatcher/libspeechd2_0.7.1-6.2_i386.deb
+main/s/speech-dispatcher/libspeechd-dev_0.7.1-6.2_i386.deb
+main/s/speech-dispatcher/speech-dispatcher_0.7.1-6.2_i386.deb
+main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb
+main/x/x11proto-core/x11proto-core-dev_7.0.23-1_all.deb
+main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb
+main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb
+main/x/x11proto-input/x11proto-input-dev_2.2-1_all.deb
+main/x/x11proto-kb/x11proto-kb-dev_1.0.6-2_all.deb
+main/x/x11proto-randr/x11proto-randr-dev_1.3.2-2_all.deb
+main/x/x11proto-record/x11proto-record-dev_1.14.2-1_all.deb
+main/x/x11proto-render/x11proto-render-dev_0.11.1-2_all.deb
+main/x/x11proto-scrnsaver/x11proto-scrnsaver-dev_1.2.2-1_all.deb
+main/x/x11proto-xext/x11proto-xext-dev_7.2.1-1_all.deb
+main/z/zlib/zlib1g_1.2.7.dfsg-13_i386.deb
+main/z/zlib/zlib1g-dev_1.2.7.dfsg-13_i386.deb
diff --git a/build/linux/sysroot_scripts/sysroot-creator-test.sh b/build/linux/sysroot_scripts/sysroot-creator-test.sh
new file mode 100755
index 00000000000..b346bb70885
--- /dev/null
+++ b/build/linux/sysroot_scripts/sysroot-creator-test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright 2014 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.
+#
+# Rudimentry test suite for sysroot-creator.
+
+SCRIPT_DIR=$(dirname $0)
+
+set -o errexit
+
+TestUpdateAllLists() {
+ echo "[ RUN ] TestUpdateAllLists"
+ "$SCRIPT_DIR/sysroot-creator-trusty.sh" UpdatePackageListsAmd64
+ "$SCRIPT_DIR/sysroot-creator-trusty.sh" UpdatePackageListsI386
+ "$SCRIPT_DIR/sysroot-creator-trusty.sh" UpdatePackageListsARM
+ "$SCRIPT_DIR/sysroot-creator-wheezy.sh" UpdatePackageListsAmd64
+ "$SCRIPT_DIR/sysroot-creator-wheezy.sh" UpdatePackageListsI386
+ "$SCRIPT_DIR/sysroot-creator-wheezy.sh" UpdatePackageListsARM
+ echo "[ OK ]"
+}
+
+TestUpdateAllLists
diff --git a/build/linux/sysroot_scripts/sysroot-creator-trusty.sh b/build/linux/sysroot_scripts/sysroot-creator-trusty.sh
new file mode 100755
index 00000000000..c0d82ec74a2
--- /dev/null
+++ b/build/linux/sysroot_scripts/sysroot-creator-trusty.sh
@@ -0,0 +1,182 @@
+#!/bin/sh
+# Copyright 2014 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.
+
+SCRIPT_DIR=$(dirname $0)
+
+DISTRO=ubuntu
+DIST=trusty
+
+# This is where we get all the debian packages from.
+APT_REPO=http://archive.ubuntu.com/ubuntu
+APT_REPO_ARM=http://ports.ubuntu.com
+REPO_BASEDIR="${APT_REPO}/dists/${DIST}"
+KEYRING_FILE=/usr/share/keyrings/ubuntu-archive-keyring.gpg
+
+# Sysroot packages: these are the packages needed to build chrome.
+# NOTE: When DEBIAN_PACKAGES is modified, the packagelist files must be updated
+# by running this script in GeneratePackageList mode.
+DEBIAN_PACKAGES="\
+ comerr-dev \
+ gcc-4.8 \
+ krb5-multidev \
+ libasound2 \
+ libasound2-dev \
+ libatk1.0-0 \
+ libatk1.0-dev \
+ libavahi-client3 \
+ libavahi-common3 \
+ libc6 \
+ libc6-dev \
+ libcairo2 \
+ libcairo2-dev \
+ libcairo-gobject2 \
+ libcairo-script-interpreter2 \
+ libcap-dev \
+ libcap2 \
+ libcomerr2 \
+ libcups2 \
+ libcups2-dev \
+ libdbus-1-3 \
+ libdbus-1-dev \
+ libdbus-glib-1-2 \
+ libdrm2 \
+ libelf1 \
+ libelf-dev \
+ libexif12 \
+ libexif-dev \
+ libexpat1 \
+ libexpat1-dev \
+ libffi6 \
+ libfontconfig1 \
+ libfontconfig1-dev \
+ libfreetype6 \
+ libfreetype6-dev \
+ libgcc1 \
+ libgconf-2-4 \
+ libgconf2-4 \
+ libgconf2-dev \
+ libgcrypt11 \
+ libgcrypt11-dev \
+ libgdk-pixbuf2.0-0 \
+ libgdk-pixbuf2.0-dev \
+ libgl1-mesa-dev \
+ libgl1-mesa-glx \
+ libglapi-mesa \
+ libglib2.0-0 \
+ libglib2.0-dev \
+ libgnome-keyring0 \
+ libgnome-keyring-dev \
+ libgnutls26 \
+ libgnutls-dev \
+ libgnutls-openssl27 \
+ libgnutlsxx27 \
+ libgomp1 \
+ libgpg-error0 \
+ libgpg-error-dev \
+ libgssapi-krb5-2 \
+ libgssrpc4 \
+ libgtk2.0-0 \
+ libgtk2.0-dev \
+ libk5crypto3 \
+ libkadm5clnt-mit9 \
+ libkadm5srv-mit9 \
+ libkdb5-7 \
+ libkeyutils1 \
+ libkrb5-3 \
+ libkrb5-dev \
+ libkrb5support0 \
+ libnspr4 \
+ libnspr4-dev \
+ libnss3 \
+ libnss3-dev \
+ libnss-db \
+ liborbit2 \
+ libp11-2 \
+ libp11-kit0 \
+ libpam0g \
+ libpam0g-dev \
+ libpango-1.0-0 \
+ libpango1.0-dev \
+ libpangocairo-1.0-0 \
+ libpangoft2-1.0-0 \
+ libpangoxft-1.0-0 \
+ libpci3 \
+ libpci-dev \
+ libpcre3 \
+ libpcre3-dev \
+ libpcrecpp0 \
+ libpixman-1-0 \
+ libpixman-1-dev \
+ libpng12-0 \
+ libpng12-dev \
+ libpulse0 \
+ libpulse-dev \
+ libpulse-mainloop-glib0 \
+ libselinux1 \
+ libspeechd2 \
+ libspeechd-dev \
+ libssl1.0.0 \
+ libssl-dev \
+ libstdc++6 \
+ libstdc++-4.8-dev \
+ libtasn1-6 \
+ libx11-6 \
+ libx11-dev \
+ libx11-xcb1 \
+ libxau6 \
+ libxau-dev \
+ libxcb1 \
+ libxcb1-dev \
+ libxcb-glx0 \
+ libxcb-render0 \
+ libxcb-render0-dev \
+ libxcb-shm0 \
+ libxcb-shm0-dev \
+ libxcomposite1 \
+ libxcomposite-dev \
+ libxcursor1 \
+ libxcursor-dev \
+ libxdamage1 \
+ libxdamage-dev \
+ libxdmcp6 \
+ libxext6 \
+ libxext-dev \
+ libxfixes3 \
+ libxfixes-dev \
+ libxi6 \
+ libxi-dev \
+ libxinerama1 \
+ libxinerama-dev \
+ libxrandr2 \
+ libxrandr-dev \
+ libxrender1 \
+ libxrender-dev \
+ libxss1 \
+ libxss-dev \
+ libxt6 \
+ libxt-dev \
+ libxtst6 \
+ libxtst-dev \
+ libxxf86vm1 \
+ linux-libc-dev \
+ mesa-common-dev \
+ speech-dispatcher \
+ x11proto-composite-dev \
+ x11proto-core-dev \
+ x11proto-damage-dev \
+ x11proto-fixes-dev \
+ x11proto-input-dev \
+ x11proto-kb-dev \
+ x11proto-randr-dev \
+ x11proto-record-dev \
+ x11proto-render-dev \
+ x11proto-scrnsaver-dev \
+ x11proto-xext-dev \
+ zlib1g \
+ zlib1g-dev"
+
+DEBIAN_PACKAGES_X86="libquadmath0"
+
+. ${SCRIPT_DIR}/sysroot-creator.sh
diff --git a/build/linux/sysroot_scripts/sysroot-creator-wheezy.sh b/build/linux/sysroot_scripts/sysroot-creator-wheezy.sh
new file mode 100755
index 00000000000..9a4d1bf81cc
--- /dev/null
+++ b/build/linux/sysroot_scripts/sysroot-creator-wheezy.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+# Copyright 2014 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.
+
+SCRIPT_DIR=$(dirname $0)
+
+DISTRO=debian
+DIST=wheezy
+APT_REPO=http://http.us.debian.org/debian
+REPO_BASEDIR="${APT_REPO}/dists/${DIST}"
+KEYRING_FILE=/usr/share/keyrings/debian-archive-keyring.gpg
+
+# Sysroot packages: these are the packages needed to build chrome.
+# NOTE: When DEBIAN_PACKAGES is modified, the packagelist files must be updated
+# by running this script in GeneratePackageList mode.
+DEBIAN_PACKAGES="\
+ comerr-dev \
+ gcc-4.6 \
+ krb5-multidev \
+ libasound2 \
+ libasound2-dev \
+ libatk1.0-0 \
+ libatk1.0-dev \
+ libattr1 \
+ libavahi-client3 \
+ libavahi-common3 \
+ libc6 \
+ libc6-dev \
+ libcairo2 \
+ libcairo2-dev \
+ libcairo-gobject2 \
+ libcairo-script-interpreter2 \
+ libcap-dev \
+ libcap2 \
+ libcomerr2 \
+ libcups2 \
+ libcups2-dev \
+ libdbus-1-3 \
+ libdbus-1-dev \
+ libdbus-glib-1-2 \
+ libdrm2 \
+ libelf1 \
+ libelf-dev \
+ libexif12 \
+ libexif-dev \
+ libexpat1 \
+ libexpat1-dev \
+ libffi5 \
+ libfontconfig1 \
+ libfontconfig1-dev \
+ libfreetype6 \
+ libfreetype6-dev \
+ libgcc1 \
+ libgconf-2-4 \
+ libgconf2-4 \
+ libgconf2-dev \
+ libgcrypt11 \
+ libgcrypt11-dev \
+ libgdk-pixbuf2.0-0 \
+ libgdk-pixbuf2.0-dev \
+ libgl1-mesa-dev \
+ libgl1-mesa-glx \
+ libglapi-mesa \
+ libglib2.0-0 \
+ libglib2.0-dev \
+ libgnome-keyring0 \
+ libgnome-keyring-dev \
+ libgnutls26 \
+ libgnutls-dev \
+ libgnutls-openssl27 \
+ libgnutlsxx27 \
+ libgomp1 \
+ libgpg-error0 \
+ libgpg-error-dev \
+ libgssapi-krb5-2 \
+ libgssrpc4 \
+ libgtk2.0-0 \
+ libgtk2.0-dev \
+ libk5crypto3 \
+ libkadm5clnt-mit8 \
+ libkadm5srv-mit8 \
+ libkdb5-6 \
+ libkeyutils1 \
+ libkrb5-3 \
+ libkrb5-dev \
+ libkrb5support0 \
+ libnspr4 \
+ libnspr4-dev \
+ libnss3 \
+ libnss3-dev \
+ libnss-db \
+ liborbit2 \
+ libp11-2 \
+ libp11-kit0 \
+ libpam0g \
+ libpam0g-dev \
+ libpango1.0-0 \
+ libpango1.0-dev \
+ libpci3 \
+ libpci-dev \
+ libpcre3 \
+ libpcre3-dev \
+ libpcrecpp0 \
+ libpixman-1-0 \
+ libpixman-1-dev \
+ libpng12-0 \
+ libpng12-dev \
+ libpulse0 \
+ libpulse-dev \
+ libpulse-mainloop-glib0 \
+ libselinux1 \
+ libspeechd2 \
+ libspeechd-dev \
+ libssl1.0.0 \
+ libssl-dev \
+ libstdc++6 \
+ libstdc++6-4.6-dev \
+ libtasn1-3 \
+ libx11-6 \
+ libx11-dev \
+ libx11-xcb1 \
+ libxau6 \
+ libxau-dev \
+ libxcb1 \
+ libxcb1-dev \
+ libxcb-glx0 \
+ libxcb-render0 \
+ libxcb-render0-dev \
+ libxcb-shm0 \
+ libxcb-shm0-dev \
+ libxcomposite1 \
+ libxcomposite-dev \
+ libxcursor1 \
+ libxcursor-dev \
+ libxdamage1 \
+ libxdamage-dev \
+ libxdmcp6 \
+ libxext6 \
+ libxext-dev \
+ libxfixes3 \
+ libxfixes-dev \
+ libxi6 \
+ libxi-dev \
+ libxinerama1 \
+ libxinerama-dev \
+ libxrandr2 \
+ libxrandr-dev \
+ libxrender1 \
+ libxrender-dev \
+ libxss1 \
+ libxss-dev \
+ libxt6 \
+ libxt-dev \
+ libxtst6 \
+ libxtst-dev \
+ libxxf86vm1 \
+ linux-libc-dev \
+ mesa-common-dev \
+ speech-dispatcher \
+ x11proto-composite-dev \
+ x11proto-core-dev \
+ x11proto-damage-dev \
+ x11proto-fixes-dev \
+ x11proto-input-dev \
+ x11proto-kb-dev \
+ x11proto-randr-dev \
+ x11proto-record-dev \
+ x11proto-render-dev \
+ x11proto-scrnsaver-dev \
+ x11proto-xext-dev \
+ zlib1g \
+ zlib1g-dev"
+
+DEBIAN_PACKAGES_X86="libquadmath0"
+
+. ${SCRIPT_DIR}/sysroot-creator.sh
diff --git a/build/linux/sysroot_scripts/sysroot-creator.sh b/build/linux/sysroot_scripts/sysroot-creator.sh
new file mode 100644
index 00000000000..822a5e8d9e1
--- /dev/null
+++ b/build/linux/sysroot_scripts/sysroot-creator.sh
@@ -0,0 +1,700 @@
+# Copyright 2014 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 script should not be run directly but sourced by the other
+# scripts (e.g. sysroot-creator-trusty.sh). Its up to the parent scripts
+# to define certain environment variables: e.g.
+# DISTRO=ubuntu
+# DIST=trusty
+# APT_REPO=http://archive.ubuntu.com/ubuntu
+# KEYRING_FILE=/usr/share/keyrings/ubuntu-archive-keyring.gpg
+# DEBIAN_PACKAGES="gcc libz libssl"
+
+#@ This script builds a Debian sysroot images for building Google Chrome.
+#@
+#@ Generally this script is invoked as:
+#@ sysroot-creator-.sh *
+#@ Available modes are shown below.
+#@
+#@ List of modes:
+
+######################################################################
+# Config
+######################################################################
+
+set -o nounset
+set -o errexit
+
+SCRIPT_DIR=$(cd $(dirname $0) && pwd)
+
+if [ -z "${DIST:-}" ]; then
+ echo "error: DIST not defined"
+ exit 1
+fi
+
+if [ -z "${APT_REPO:-}" ]; then
+ echo "error: APT_REPO not defined"
+ exit 1
+fi
+
+if [ -z "${KEYRING_FILE:-}" ]; then
+ echo "error: KEYRING_FILE not defined"
+ exit 1
+fi
+
+if [ -z "${DEBIAN_PACKAGES:-}" ]; then
+ echo "error: DEBIAN_PACKAGES not defined"
+ exit 1
+fi
+
+readonly REPO_BASEDIR="${APT_REPO}/dists/${DIST}"
+
+readonly REQUIRED_TOOLS="wget"
+
+######################################################################
+# Package Config
+######################################################################
+
+readonly RELEASE_FILE="Release"
+readonly RELEASE_FILE_GPG="Release.gpg"
+readonly RELEASE_LIST="${REPO_BASEDIR}/${RELEASE_FILE}"
+readonly RELEASE_LIST_GPG="${REPO_BASEDIR}/${RELEASE_FILE_GPG}"
+readonly PACKAGE_FILE_AMD64="main/binary-amd64/Packages.bz2"
+readonly PACKAGE_FILE_I386="main/binary-i386/Packages.bz2"
+readonly PACKAGE_FILE_ARM="main/binary-armhf/Packages.bz2"
+readonly PACKAGE_FILE_MIPS="main/binary-mipsel/Packages.bz2"
+readonly PACKAGE_LIST_AMD64="${REPO_BASEDIR}/${PACKAGE_FILE_AMD64}"
+readonly PACKAGE_LIST_I386="${REPO_BASEDIR}/${PACKAGE_FILE_I386}"
+readonly PACKAGE_LIST_ARM="${REPO_BASEDIR}/${PACKAGE_FILE_ARM}"
+readonly PACKAGE_LIST_MIPS="${REPO_BASEDIR}/${PACKAGE_FILE_MIPS}"
+
+readonly DEBIAN_DEP_LIST_AMD64="packagelist.${DIST}.amd64"
+readonly DEBIAN_DEP_LIST_I386="packagelist.${DIST}.i386"
+readonly DEBIAN_DEP_LIST_ARM="packagelist.${DIST}.arm"
+readonly DEBIAN_DEP_LIST_MIPS="packagelist.${DIST}.mipsel"
+
+######################################################################
+# Helper
+######################################################################
+
+Banner() {
+ echo "######################################################################"
+ echo $*
+ echo "######################################################################"
+}
+
+
+SubBanner() {
+ echo "----------------------------------------------------------------------"
+ echo $*
+ echo "----------------------------------------------------------------------"
+}
+
+
+Usage() {
+ egrep "^#@" "${BASH_SOURCE[0]}" | cut --bytes=3-
+}
+
+
+DownloadOrCopy() {
+ if [ -f "$2" ] ; then
+ echo "$2 already in place"
+ return
+ fi
+
+ HTTP=0
+ echo "$1" | grep -qs ^http:// && HTTP=1
+ if [ "$HTTP" = "1" ]; then
+ SubBanner "downloading from $1 -> $2"
+ wget "$1" -O "${2}.partial"
+ mv "${2}.partial" $2
+ else
+ SubBanner "copying from $1"
+ cp "$1" "$2"
+ fi
+}
+
+
+SetEnvironmentVariables() {
+ ARCH=""
+ echo $1 | grep -qs Amd64$ && ARCH=AMD64
+ if [ -z "$ARCH" ]; then
+ echo $1 | grep -qs I386$ && ARCH=I386
+ fi
+ if [ -z "$ARCH" ]; then
+ echo $1 | grep -qs Mips$ && ARCH=MIPS
+ fi
+ if [ -z "$ARCH" ]; then
+ echo $1 | grep -qs ARM$ && ARCH=ARM
+ fi
+ if [ -z "${ARCH}" ]; then
+ echo "ERROR: Unable to determine architecture based on: $1"
+ exit 1
+ fi
+ ARCH_LOWER=$(echo $ARCH | tr '[:upper:]' '[:lower:]')
+}
+
+
+# some sanity checks to make sure this script is run from the right place
+# with the right tools
+SanityCheck() {
+ Banner "Sanity Checks"
+
+ local chrome_dir=$(cd "${SCRIPT_DIR}/../../../.." && pwd)
+ BUILD_DIR="${chrome_dir}/out/sysroot-build/${DIST}"
+ mkdir -p ${BUILD_DIR}
+ echo "Using build directory: ${BUILD_DIR}"
+
+ for tool in ${REQUIRED_TOOLS} ; do
+ if ! which ${tool} > /dev/null ; then
+ echo "Required binary $tool not found."
+ echo "Exiting."
+ exit 1
+ fi
+ done
+
+ # This is where the staging sysroot is.
+ INSTALL_ROOT="${BUILD_DIR}/${DIST}_${ARCH_LOWER}_staging"
+ TARBALL="${BUILD_DIR}/${DISTRO}_${DIST}_${ARCH_LOWER}_sysroot.tgz"
+
+ if ! mkdir -p "${INSTALL_ROOT}" ; then
+ echo "ERROR: ${INSTALL_ROOT} can't be created."
+ exit 1
+ fi
+}
+
+
+ChangeDirectory() {
+ # Change directory to where this script is.
+ cd ${SCRIPT_DIR}
+}
+
+
+ClearInstallDir() {
+ Banner "Clearing dirs in ${INSTALL_ROOT}"
+ rm -rf ${INSTALL_ROOT}/*
+}
+
+
+CreateTarBall() {
+ Banner "Creating tarball ${TARBALL}"
+ tar zcf ${TARBALL} -C ${INSTALL_ROOT} .
+}
+
+ExtractPackageBz2() {
+ bzcat "$1" | egrep '^(Package:|Filename:|SHA256:) ' > "$2"
+}
+
+GeneratePackageListAmd64() {
+ local output_file="$1"
+ local package_list="${BUILD_DIR}/Packages.${DIST}_amd64.bz2"
+ local tmp_package_list="${BUILD_DIR}/Packages.${DIST}_amd64"
+ DownloadOrCopy "${PACKAGE_LIST_AMD64}" "${package_list}"
+ VerifyPackageListing "${PACKAGE_FILE_AMD64}" "${package_list}"
+ ExtractPackageBz2 "$package_list" "$tmp_package_list"
+ GeneratePackageList "$tmp_package_list" "$output_file" "${DEBIAN_PACKAGES}
+ ${DEBIAN_PACKAGES_X86}"
+}
+
+GeneratePackageListI386() {
+ local output_file="$1"
+ local package_list="${BUILD_DIR}/Packages.${DIST}_i386.bz2"
+ local tmp_package_list="${BUILD_DIR}/Packages.${DIST}_amd64"
+ DownloadOrCopy "${PACKAGE_LIST_I386}" "${package_list}"
+ VerifyPackageListing "${PACKAGE_FILE_I386}" "${package_list}"
+ ExtractPackageBz2 "$package_list" "$tmp_package_list"
+ GeneratePackageList "$tmp_package_list" "$output_file" "${DEBIAN_PACKAGES}
+ ${DEBIAN_PACKAGES_X86}"
+}
+
+GeneratePackageListARM() {
+ local output_file="$1"
+ local package_list="${BUILD_DIR}/Packages.${DIST}_arm.bz2"
+ local tmp_package_list="${BUILD_DIR}/Packages.${DIST}_arm"
+ DownloadOrCopy "${PACKAGE_LIST_ARM}" "${package_list}"
+ VerifyPackageListing "${PACKAGE_FILE_ARM}" "${package_list}"
+ ExtractPackageBz2 "$package_list" "$tmp_package_list"
+ GeneratePackageList "$tmp_package_list" "$output_file" "${DEBIAN_PACKAGES}"
+}
+
+GeneratePackageListMips() {
+ local output_file="$1"
+ local package_list="${BUILD_DIR}/Packages.${DIST}_mips.bz2"
+ local tmp_package_list="${BUILD_DIR}/Packages.${DIST}_mips"
+ DownloadOrCopy "${PACKAGE_LIST_MIPS}" "${package_list}"
+ VerifyPackageListing "${PACKAGE_FILE_MIPS}" "${package_list}"
+ ExtractPackageBz2 "$package_list" "$tmp_package_list"
+ GeneratePackageList "$tmp_package_list" "$output_file" "${DEBIAN_PACKAGES}"
+}
+
+StripChecksumsFromPackageList() {
+ local package_file="$1"
+ sed -i 's/ [a-f0-9]\{64\}$//' "$package_file"
+}
+
+VerifyPackageFilesMatch() {
+ local downloaded_package_file="$1"
+ local stored_package_file="$2"
+ diff -u "$downloaded_package_file" "$stored_package_file"
+ if [ "$?" -ne "0" ]; then
+ echo "ERROR: downloaded package files does not match $2."
+ echo "You may need to run UpdatePackageLists."
+ exit 1
+ fi
+}
+
+######################################################################
+#
+######################################################################
+
+HacksAndPatchesAmd64() {
+ Banner "Misc Hacks & Patches"
+ # these are linker scripts with absolute pathnames in them
+ # which we rewrite here
+ lscripts="${INSTALL_ROOT}/usr/lib/x86_64-linux-gnu/libpthread.so \
+ ${INSTALL_ROOT}/usr/lib/x86_64-linux-gnu/libc.so"
+
+ # Rewrite linker scripts
+ sed -i -e 's|/usr/lib/x86_64-linux-gnu/||g' ${lscripts}
+ sed -i -e 's|/lib/x86_64-linux-gnu/||g' ${lscripts}
+
+ # This is for chrome's ./build/linux/pkg-config-wrapper
+ # which overwrites PKG_CONFIG_PATH internally
+ SubBanner "Package Configs Symlink"
+ mkdir -p ${INSTALL_ROOT}/usr/share
+ ln -s ../lib/x86_64-linux-gnu/pkgconfig ${INSTALL_ROOT}/usr/share/pkgconfig
+
+ SubBanner "Adding an additional ld.conf include"
+ LD_SO_HACK_CONF="${INSTALL_ROOT}/etc/ld.so.conf.d/zz_hack.conf"
+ echo /usr/lib/gcc/x86_64-linux-gnu/4.6 > "$LD_SO_HACK_CONF"
+ echo /usr/lib >> "$LD_SO_HACK_CONF"
+}
+
+
+HacksAndPatchesI386() {
+ Banner "Misc Hacks & Patches"
+ # these are linker scripts with absolute pathnames in them
+ # which we rewrite here
+ lscripts="${INSTALL_ROOT}/usr/lib/i386-linux-gnu/libpthread.so \
+ ${INSTALL_ROOT}/usr/lib/i386-linux-gnu/libc.so"
+
+ # Rewrite linker scripts
+ sed -i -e 's|/usr/lib/i386-linux-gnu/||g' ${lscripts}
+ sed -i -e 's|/lib/i386-linux-gnu/||g' ${lscripts}
+
+ # This is for chrome's ./build/linux/pkg-config-wrapper
+ # which overwrites PKG_CONFIG_PATH internally
+ SubBanner "Package Configs Symlink"
+ mkdir -p ${INSTALL_ROOT}/usr/share
+ ln -s ../lib/i386-linux-gnu/pkgconfig ${INSTALL_ROOT}/usr/share/pkgconfig
+
+ SubBanner "Adding an additional ld.conf include"
+ LD_SO_HACK_CONF="${INSTALL_ROOT}/etc/ld.so.conf.d/zz_hack.conf"
+ echo /usr/lib/gcc/i486-linux-gnu/4.6 > "$LD_SO_HACK_CONF"
+ echo /usr/lib >> "$LD_SO_HACK_CONF"
+}
+
+
+HacksAndPatchesARM() {
+ Banner "Misc Hacks & Patches"
+ # these are linker scripts with absolute pathnames in them
+ # which we rewrite here
+ lscripts="${INSTALL_ROOT}/usr/lib/arm-linux-gnueabihf/libpthread.so \
+ ${INSTALL_ROOT}/usr/lib/arm-linux-gnueabihf/libc.so"
+
+ # Rewrite linker scripts
+ sed -i -e 's|/usr/lib/arm-linux-gnueabihf/||g' ${lscripts}
+ sed -i -e 's|/lib/arm-linux-gnueabihf/||g' ${lscripts}
+
+ # This is for chrome's ./build/linux/pkg-config-wrapper
+ # which overwrites PKG_CONFIG_PATH internally
+ SubBanner "Package Configs Symlink"
+ mkdir -p ${INSTALL_ROOT}/usr/share
+ ln -s ../lib/arm-linux-gnueabihf/pkgconfig ${INSTALL_ROOT}/usr/share/pkgconfig
+}
+
+
+HacksAndPatchesMips() {
+ Banner "Misc Hacks & Patches"
+ # these are linker scripts with absolute pathnames in them
+ # which we rewrite here
+ lscripts="${INSTALL_ROOT}/usr/lib/mipsel-linux-gnu/libpthread.so \
+ ${INSTALL_ROOT}/usr/lib/mipsel-linux-gnu/libc.so"
+
+ # Rewrite linker scripts
+ sed -i -e 's|/usr/lib/mipsel-linux-gnu/||g' ${lscripts}
+ sed -i -e 's|/lib/mipsel-linux-gnu/||g' ${lscripts}
+
+ # This is for chrome's ./build/linux/pkg-config-wrapper
+ # which overwrites PKG_CONFIG_PATH internally
+ SubBanner "Package Configs Symlink"
+ mkdir -p ${INSTALL_ROOT}/usr/share
+ ln -s ../lib/mipsel-linux-gnu/pkgconfig ${INSTALL_ROOT}/usr/share/pkgconfig
+}
+
+
+InstallIntoSysroot() {
+ Banner "Install Libs And Headers Into Jail"
+
+ mkdir -p ${BUILD_DIR}/debian-packages
+ mkdir -p ${INSTALL_ROOT}
+ while (( "$#" )); do
+ local file="$1"
+ local package="${BUILD_DIR}/debian-packages/${file##*/}"
+ shift
+ local sha256sum="$1"
+ shift
+ if [ "${#sha256sum}" -ne "64" ]; then
+ echo "Bad sha256sum from package list"
+ exit 1
+ fi
+
+ Banner "Installing ${file}"
+ DownloadOrCopy ${APT_REPO}/pool/${file} ${package}
+ if [ ! -s "${package}" ] ; then
+ echo
+ echo "ERROR: bad package ${package}"
+ exit 1
+ fi
+ echo "${sha256sum} ${package}" | sha256sum --quiet -c
+
+ SubBanner "Extracting to ${INSTALL_ROOT}"
+ dpkg --fsys-tarfile ${package}\
+ | tar -xf - --exclude=./usr/share -C ${INSTALL_ROOT}
+ done
+}
+
+
+CleanupJailSymlinks() {
+ Banner "Jail symlink cleanup"
+
+ SAVEDPWD=$(pwd)
+ cd ${INSTALL_ROOT}
+ local libdirs="lib usr/lib"
+ if [ "${ARCH}" != "MIPS" ]; then
+ libdirs+=" lib64"
+ fi
+ find $libdirs -type l -printf '%p %l\n' | while read link target; do
+ # skip links with non-absolute paths
+ echo "${target}" | grep -qs ^/ || continue
+ echo "${link}: ${target}"
+ case "${link}" in
+ usr/lib/gcc/x86_64-linux-gnu/4.*/* | usr/lib/gcc/i486-linux-gnu/4.*/* | \
+ usr/lib/gcc/arm-linux-gnueabihf/4.*/* | \
+ usr/lib/gcc/mipsel-linux-gnu/4.*/*)
+ # Relativize the symlink.
+ ln -snfv "../../../../..${target}" "${link}"
+ ;;
+ usr/lib/x86_64-linux-gnu/* | usr/lib/i386-linux-gnu/* | \
+ usr/lib/arm-linux-gnueabihf/* | usr/lib/mipsel-linux-gnu/* )
+ # Relativize the symlink.
+ ln -snfv "../../..${target}" "${link}"
+ ;;
+ usr/lib/*)
+ # Relativize the symlink.
+ ln -snfv "../..${target}" "${link}"
+ ;;
+ lib64/* | lib/*)
+ # Relativize the symlink.
+ ln -snfv "..${target}" "${link}"
+ ;;
+ esac
+ done
+
+ find $libdirs -type l -printf '%p %l\n' | while read link target; do
+ # Make sure we catch new bad links.
+ if [ ! -r "${link}" ]; then
+ echo "ERROR: FOUND BAD LINK ${link}"
+ ls -l ${link}
+ exit 1
+ fi
+ done
+ cd "$SAVEDPWD"
+}
+
+#@
+#@ BuildSysrootAmd64
+#@
+#@ Build everything and package it
+BuildSysrootAmd64() {
+ ClearInstallDir
+ local package_file="$BUILD_DIR/package_with_sha256sum_amd64"
+ GeneratePackageListAmd64 "$package_file"
+ local files_and_sha256sums="$(cat ${package_file})"
+ StripChecksumsFromPackageList "$package_file"
+ VerifyPackageFilesMatch "$package_file" "$DEBIAN_DEP_LIST_AMD64"
+ InstallIntoSysroot ${files_and_sha256sums}
+ CleanupJailSymlinks
+ HacksAndPatchesAmd64
+ CreateTarBall
+}
+
+#@
+#@ BuildSysrootI386
+#@
+#@ Build everything and package it
+BuildSysrootI386() {
+ ClearInstallDir
+ local package_file="$BUILD_DIR/package_with_sha256sum_i386"
+ GeneratePackageListI386 "$package_file"
+ local files_and_sha256sums="$(cat ${package_file})"
+ StripChecksumsFromPackageList "$package_file"
+ VerifyPackageFilesMatch "$package_file" "$DEBIAN_DEP_LIST_I386"
+ InstallIntoSysroot ${files_and_sha256sums}
+ CleanupJailSymlinks
+ HacksAndPatchesI386
+ CreateTarBall
+}
+
+#@
+#@ BuildSysrootARM
+#@
+#@ Build everything and package it
+BuildSysrootARM() {
+ ClearInstallDir
+ local package_file="$BUILD_DIR/package_with_sha256sum_arm"
+ GeneratePackageListARM "$package_file"
+ local files_and_sha256sums="$(cat ${package_file})"
+ StripChecksumsFromPackageList "$package_file"
+ VerifyPackageFilesMatch "$package_file" "$DEBIAN_DEP_LIST_ARM"
+ APT_REPO=${APR_REPO_ARM:=$APT_REPO}
+ InstallIntoSysroot ${files_and_sha256sums}
+ CleanupJailSymlinks
+ HacksAndPatchesARM
+ CreateTarBall
+}
+
+#@
+#@ BuildSysrootMips
+#@
+#@ Build everything and package it
+BuildSysrootMips() {
+ ClearInstallDir
+ local package_file="$BUILD_DIR/package_with_sha256sum_arm"
+ GeneratePackageListMips "$package_file"
+ local files_and_sha256sums="$(cat ${package_file})"
+ StripChecksumsFromPackageList "$package_file"
+ VerifyPackageFilesMatch "$package_file" "$DEBIAN_DEP_LIST_MIPS"
+ APT_REPO=${APR_REPO_MIPS:=$APT_REPO}
+ InstallIntoSysroot ${files_and_sha256sums}
+ CleanupJailSymlinks
+ HacksAndPatchesMips
+ CreateTarBall
+}
+
+#@
+#@ BuildSysrootAll
+#@
+#@ Build sysroot images for all architectures
+BuildSysrootAll() {
+ RunCommand BuildSysrootAmd64
+ RunCommand BuildSysrootI386
+ RunCommand BuildSysrootARM
+ RunCommand BuildSysrootMips
+}
+
+UploadSysroot() {
+ local rev=$1
+ if [ -z "${rev}" ]; then
+ echo "Please specify a revision to upload at."
+ exit 1
+ fi
+ set -x
+ gsutil cp -a public-read "${TARBALL}" \
+ "gs://chrome-linux-sysroot/toolchain/$rev/"
+ set +x
+}
+
+#@
+#@ UploadSysrootAmd64
+#@
+UploadSysrootAmd64() {
+ UploadSysroot "$@"
+}
+
+#@
+#@ UploadSysrootI386
+#@
+UploadSysrootI386() {
+ UploadSysroot "$@"
+}
+
+#@
+#@ UploadSysrootARM
+#@
+UploadSysrootARM() {
+ UploadSysroot "$@"
+}
+
+#@
+#@ UploadSysrootMips
+#@
+UploadSysrootMips() {
+ UploadSysroot "$@"
+}
+
+#@
+#@ UploadSysrootAll
+#@
+#@ Upload sysroot image for all architectures
+UploadSysrootAll() {
+ RunCommand UploadSysrootAmd64 "$@"
+ RunCommand UploadSysrootI386 "$@"
+ RunCommand UploadSysrootARM "$@"
+ RunCommand UploadSysrootMips "$@"
+}
+
+#
+# CheckForDebianGPGKeyring
+#
+# Make sure the Debian GPG keys exist. Otherwise print a helpful message.
+#
+CheckForDebianGPGKeyring() {
+ if [ ! -e "$KEYRING_FILE" ]; then
+ echo "Debian GPG keys missing. Install the debian-archive-keyring package."
+ exit 1
+ fi
+}
+
+#
+# VerifyPackageListing
+#
+# Verifies the downloaded Packages.bz2 file has the right checksums.
+#
+VerifyPackageListing() {
+ local file_path=$1
+ local output_file=$2
+ local release_file="${BUILD_DIR}/${RELEASE_FILE}"
+ local release_file_gpg="${BUILD_DIR}/${RELEASE_FILE_GPG}"
+ local tmp_keyring_file="${BUILD_DIR}/keyring.gpg"
+
+ CheckForDebianGPGKeyring
+
+ DownloadOrCopy ${RELEASE_LIST} ${release_file}
+ DownloadOrCopy ${RELEASE_LIST_GPG} ${release_file_gpg}
+ echo "Verifying: ${release_file} with ${release_file_gpg}"
+ cp "${KEYRING_FILE}" "${tmp_keyring_file}"
+ gpg --primary-keyring "${tmp_keyring_file}" --recv-keys 2B90D010
+ gpgv --keyring "${tmp_keyring_file}" "${release_file_gpg}" "${release_file}"
+
+ echo "Verifying: ${output_file}"
+ local checksums=$(grep ${file_path} ${release_file} | cut -d " " -f 2)
+ local sha256sum=$(echo ${checksums} | cut -d " " -f 3)
+
+ if [ "${#sha256sum}" -ne "64" ]; then
+ echo "Bad sha256sum from ${RELEASE_LIST}"
+ exit 1
+ fi
+
+ echo "${sha256sum} ${output_file}" | sha256sum --quiet -c
+}
+
+#
+# GeneratePackageList
+#
+# Looks up package names in ${BUILD_DIR}/Packages and write list of URLs
+# to output file.
+#
+GeneratePackageList() {
+ local input_file="$1"
+ local output_file="$2"
+ echo "Updating: ${output_file} from ${input_file}"
+ /bin/rm -f "${output_file}"
+ shift
+ shift
+ for pkg in $@ ; do
+ local pkg_full=$(grep -A 1 " ${pkg}\$" "$input_file" | \
+ egrep -o "pool/.*")
+ if [ -z "${pkg_full}" ]; then
+ echo "ERROR: missing package: $pkg"
+ exit 1
+ fi
+ local pkg_nopool=$(echo "$pkg_full" | sed "s/^pool\///")
+ local sha256sum=$(grep -A 4 " ${pkg}\$" "$input_file" | \
+ grep ^SHA256: | sed 's/^SHA256: //')
+ if [ "${#sha256sum}" -ne "64" ]; then
+ echo "Bad sha256sum from Packages"
+ exit 1
+ fi
+ echo $pkg_nopool $sha256sum >> "$output_file"
+ done
+ # sort -o does an in-place sort of this file
+ sort "$output_file" -o "$output_file"
+}
+
+#@
+#@ UpdatePackageListsAmd64
+#@
+#@ Regenerate the package lists such that they contain an up-to-date
+#@ list of URLs within the Debian archive. (For amd64)
+UpdatePackageListsAmd64() {
+ GeneratePackageListAmd64 "$DEBIAN_DEP_LIST_AMD64"
+ StripChecksumsFromPackageList "$DEBIAN_DEP_LIST_AMD64"
+}
+
+#@
+#@ UpdatePackageListsI386
+#@
+#@ Regenerate the package lists such that they contain an up-to-date
+#@ list of URLs within the Debian archive. (For i386)
+UpdatePackageListsI386() {
+ GeneratePackageListI386 "$DEBIAN_DEP_LIST_I386"
+ StripChecksumsFromPackageList "$DEBIAN_DEP_LIST_I386"
+}
+
+#@
+#@ UpdatePackageListsARM
+#@
+#@ Regenerate the package lists such that they contain an up-to-date
+#@ list of URLs within the Debian archive. (For arm)
+UpdatePackageListsARM() {
+ GeneratePackageListARM "$DEBIAN_DEP_LIST_ARM"
+ StripChecksumsFromPackageList "$DEBIAN_DEP_LIST_ARM"
+}
+
+#@
+#@ UpdatePackageListsMips
+#@
+#@ Regenerate the package lists such that they contain an up-to-date
+#@ list of URLs within the Debian archive. (For arm)
+UpdatePackageListsMips() {
+ GeneratePackageListMips "$DEBIAN_DEP_LIST_MIPS"
+ StripChecksumsFromPackageList "$DEBIAN_DEP_LIST_MIPS"
+}
+
+#@
+#@ UpdatePackageListsAll
+#@
+#@ Regenerate the package lists for all architectures.
+UpdatePackageListsAll() {
+ RunCommand UpdatePackageListsAmd64
+ RunCommand UpdatePackageListsI386
+ RunCommand UpdatePackageListsARM
+ RunCommand UpdatePackageListsMips
+}
+
+RunCommand() {
+ SetEnvironmentVariables "$1"
+ SanityCheck
+ "$@"
+}
+
+if [ $# -eq 0 ] ; then
+ echo "ERROR: you must specify a mode on the commandline"
+ echo
+ Usage
+ exit 1
+elif [ "$(type -t $1)" != "function" ]; then
+ echo "ERROR: unknown function '$1'." >&2
+ echo "For help, try:"
+ echo " $0 help"
+ exit 1
+else
+ ChangeDirectory
+ if echo $1 | grep -qs "All$"; then
+ "$@"
+ else
+ RunCommand "$@"
+ fi
+fi
diff --git a/build/linux/system.gyp b/build/linux/system.gyp
new file mode 100644
index 00000000000..ab20951b9c9
--- /dev/null
+++ b/build/linux/system.gyp
@@ -0,0 +1,1222 @@
+# Copyright (c) 2012 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.
+
+{
+ 'variables': {
+ # If any of the linux_link_FOO below are set to 1, then the corresponding
+ # target will be linked against the FOO library (either dynamically or
+ # statically, depending on the pkg-config files), as opposed to loading the
+ # FOO library dynamically with dlopen.
+ 'linux_link_libgps%': 0,
+ 'linux_link_libpci%': 0,
+ 'linux_link_libspeechd%': 0,
+ 'linux_link_libbrlapi%': 0,
+
+ # Used below for the various libraries. In this scope for sharing with GN.
+ 'libbrlapi_functions': [
+ 'brlapi_getHandleSize',
+ 'brlapi_error_location',
+ 'brlapi_strerror',
+ 'brlapi__acceptKeys',
+ 'brlapi__openConnection',
+ 'brlapi__closeConnection',
+ 'brlapi__getDisplaySize',
+ 'brlapi__enterTtyModeWithPath',
+ 'brlapi__leaveTtyMode',
+ 'brlapi__writeDots',
+ 'brlapi__readKey',
+ ],
+ 'libgio_functions': [
+ 'g_settings_new',
+ 'g_settings_get_child',
+ 'g_settings_get_string',
+ 'g_settings_get_boolean',
+ 'g_settings_get_int',
+ 'g_settings_get_strv',
+ 'g_settings_list_schemas',
+ ],
+ 'libpci_functions': [
+ 'pci_alloc',
+ 'pci_init',
+ 'pci_cleanup',
+ 'pci_scan_bus',
+ 'pci_fill_info',
+ 'pci_lookup_name',
+ ],
+ 'libudev_functions': [
+ 'udev_device_get_action',
+ 'udev_device_get_devnode',
+ 'udev_device_get_parent',
+ 'udev_device_get_parent_with_subsystem_devtype',
+ 'udev_device_get_property_value',
+ 'udev_device_get_subsystem',
+ 'udev_device_get_sysattr_value',
+ 'udev_device_get_sysname',
+ 'udev_device_get_syspath',
+ 'udev_device_new_from_devnum',
+ 'udev_device_new_from_subsystem_sysname',
+ 'udev_device_new_from_syspath',
+ 'udev_device_unref',
+ 'udev_enumerate_add_match_subsystem',
+ 'udev_enumerate_get_list_entry',
+ 'udev_enumerate_new',
+ 'udev_enumerate_scan_devices',
+ 'udev_enumerate_unref',
+ 'udev_list_entry_get_next',
+ 'udev_list_entry_get_name',
+ 'udev_monitor_enable_receiving',
+ 'udev_monitor_filter_add_match_subsystem_devtype',
+ 'udev_monitor_get_fd',
+ 'udev_monitor_new_from_netlink',
+ 'udev_monitor_receive_device',
+ 'udev_monitor_unref',
+ 'udev_new',
+ 'udev_set_log_fn',
+ 'udev_set_log_priority',
+ 'udev_unref',
+ ],
+ },
+ 'conditions': [
+ [ 'chromeos==0 and use_ozone==0', {
+ # Hide GTK and related dependencies for Chrome OS and Ozone, so they won't get
+ # added back to Chrome OS and Ozone. Don't try to use GTK on Chrome OS and Ozone.
+ 'targets': [
+ {
+ 'target_name': 'atk',
+ 'type': 'none',
+ 'conditions': [
+ ['_toolset=="target"', {
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '',
+ # TODO(phajdan.jr): Report problem to pciutils project
+ # and get it fixed so that we don't need --use-extern-c.
+ '--use-extern-c',
+ '--link-directly=<(linux_link_libpci)',
+ '<@(libpci_functions)',
+ ],
+ 'message': 'Generating libpci library loader',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+ },
+ ],
+ }],
+ ], # conditions
+ 'targets': [
+ {
+ 'target_name': 'dbus',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '=2.40 deprecate g_settings_list_schemas in favor of
+ # g_settings_schema_source_list_schemas. This function is not
+ # available on earlier versions that we still need to support
+ # (specifically, 2.32), so disable the warning.
+ # TODO(mgiuca): Remove this suppression (and variable) when we
+ # drop support for Ubuntu 13.10 (saucy) and earlier. Update the
+ # code to use g_settings_schema_source_list_schemas instead.
+ 'GLIB_DISABLE_DEPRECATION_WARNINGS',
+ ],
+ },
+ 'defines': [
+ '<(gio_warning_define)',
+ ],
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '',
+ '--link-directly=<(linux_link_gsettings)',
+ '<@(libgio_functions)',
+ ],
+ 'message': 'Generating libgio library loader',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'glib',
+ 'type': 'none',
+ 'toolsets': ['host', 'target'],
+ 'variables': {
+ 'glib_packages': 'glib-2.0 gmodule-2.0 gobject-2.0 gthread-2.0',
+ },
+ 'conditions': [
+ ['_toolset=="target"', {
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '',
+ '--link-directly=<(linux_link_libbrlapi)',
+ '<@(libbrlapi_functions)',
+ ],
+ 'message': 'Generating libbrlapi library loader',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+ },
+ {
+ 'target_name': 'libcap',
+ 'type': 'none',
+ 'link_settings': {
+ 'libraries': [
+ '-lcap',
+ ],
+ },
+ },
+ {
+ 'target_name': 'libresolv',
+ 'type': 'none',
+ 'link_settings': {
+ 'libraries': [
+ '-lresolv',
+ ],
+ },
+ },
+ {
+ # GN version: //third_party/speech-dispatcher
+ 'target_name': 'libspeechd',
+ 'type': 'static_library',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ 'conditions': [
+ ['linux_link_libspeechd==1', {
+ 'link_settings': {
+ 'libraries': [
+ '-lspeechd',
+ ],
+ }
+ }],
+ ],
+ },
+ 'include_dirs': [
+ '../..',
+ ],
+ 'hard_dependency': 1,
+ 'actions': [
+ {
+ 'variables': {
+ 'output_h': '<(SHARED_INTERMEDIATE_DIR)/library_loaders/libspeechd.h',
+ 'output_cc': '<(INTERMEDIATE_DIR)/libspeechd_loader.cc',
+ 'generator': '../../tools/generate_library_loader/generate_library_loader.py',
+
+ # speech-dispatcher >= 0.8 installs libspeechd.h into
+ # speech-dispatcher/libspeechd.h, whereas speech-dispatcher < 0.8
+ # puts libspeechd.h in the top-level include directory.
+ # Since we need to support both cases for now, we ship a copy of
+ # libspeechd.h in third_party/speech-dispatcher. If the user
+ # prefers to link against the speech-dispatcher directly, the
+ # `libspeechd_h_prefix' variable can be passed to gyp with a value
+ # such as "speech-dispatcher/" that will be prepended to
+ # "libspeechd.h" in the #include directive.
+ # TODO(phaldan.jr): Once we do not need to support
+ # speech-dispatcher < 0.8 we can get rid of all this (including
+ # third_party/speech-dispatcher) and just include
+ # speech-dispatcher/libspeechd.h unconditionally.
+ 'libspeechd_h_prefix%': '',
+ },
+ 'action_name': 'generate_libspeechd_loader',
+ 'inputs': [
+ '<(generator)',
+ ],
+ 'outputs': [
+ '<(output_h)',
+ '<(output_cc)',
+ ],
+ 'action': ['python',
+ '<(generator)',
+ '--name', 'LibSpeechdLoader',
+ '--output-h', '<(output_h)',
+ '--output-cc', '<(output_cc)',
+ '--header', '<<(libspeechd_h_prefix)libspeechd.h>',
+ '--bundled-header',
+ '"third_party/speech-dispatcher/libspeechd.h"',
+ '--link-directly=<(linux_link_libspeechd)',
+ 'spd_open',
+ 'spd_say',
+ 'spd_stop',
+ 'spd_close',
+ 'spd_pause',
+ 'spd_resume',
+ 'spd_set_notification_on',
+ 'spd_set_voice_rate',
+ 'spd_set_voice_pitch',
+ 'spd_list_synthesis_voices',
+ 'spd_set_synthesis_voice',
+ 'spd_list_modules',
+ 'spd_set_output_module',
+ 'spd_set_language',
+ ],
+ 'message': 'Generating libspeechd library loader',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+ },
+ {
+ 'target_name': 'pangocairo',
+ 'type': 'none',
+ 'toolsets': ['host', 'target'],
+ 'conditions': [
+ ['use_pango==1 and use_cairo==1', {
+ 'conditions': [
+ ['_toolset=="target"', {
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '
+
+ For example: remove_bundled_libraries.py third_party/mesa
+
+ The script scans sources looking for third_party directories.
+ Everything that is not explicitly preserved is removed (except for
+ gyp files), and the script fails if any directory passed on command
+ line does not exist (to ensure list is kept up to date).
+
+ This is intended to be used on sources extracted from a tarball,
+ not a repository.
+
+ NOTE: by default this will not remove anything (for safety). Pass
+ --do-remove flag to actually remove files.
+
+2. replace_gyp_files.py
+
+ For example: replace_gyp_files.py -Duse_system_harfbuzz=1
+
+ The script ignores flags other than -D for convenience. This makes it
+ possible to have a variable e.g. ${myconf} with all the options, and
+ execute:
+
+ build/linux/unbundle/replace_gyp_files.py ${myconf}
+ build/gyp_chromium ${myconf}
diff --git a/build/linux/unbundle/expat.gyp b/build/linux/unbundle/expat.gyp
new file mode 100644
index 00000000000..030fb856bb7
--- /dev/null
+++ b/build/linux/unbundle/expat.gyp
@@ -0,0 +1,17 @@
+# Copyright 2013 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'expat',
+ 'type': 'none',
+ 'link_settings': {
+ 'libraries': [
+ '-lexpat',
+ ],
+ },
+ },
+ ],
+}
diff --git a/build/linux/unbundle/ffmpeg.gyp b/build/linux/unbundle/ffmpeg.gyp
new file mode 100644
index 00000000000..e3c3723894c
--- /dev/null
+++ b/build/linux/unbundle/ffmpeg.gyp
@@ -0,0 +1,54 @@
+# Copyright 2013 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'ffmpeg',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '\n'
+ 'int test() { return AV_CODEC_ID_OPUS; }" '
+ '--on-failure -DCHROMIUM_OMIT_AV_CODEC_ID_OPUS=1)',
+
+ '\n'
+ 'int test() { return AV_CODEC_ID_VP9; }" '
+ '--on-failure -DCHROMIUM_OMIT_AV_CODEC_ID_VP9=1)',
+
+ '\n'
+ 'int test() { return AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL; }" '
+ '--on-failure -DCHROMIUM_OMIT_AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL=1)',
+
+ '\n'
+ 'int test() { struct AVFrame frame;\n'
+ 'return av_frame_get_channels(&frame); }" '
+ '--on-failure -DCHROMIUM_NO_AVFRAME_CHANNELS=1)',
+ ],
+ 'defines': [
+ '__STDC_CONSTANT_MACROS',
+ 'USE_SYSTEM_FFMPEG',
+ ],
+ },
+ 'link_settings': {
+ 'ldflags': [
+ ', which requires RTTI.
+ 'GOOGLE_PROTOBUF_NO_RTTI',
+ 'GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER',
+ ],
+ },
+ 'link_settings': {
+ # Use full protobuf, because vanilla protobuf doesn't have
+ # our custom patch to retain unknown fields in lite mode.
+ 'ldflags': [
+ ' 1 and split[0].startswith(exclusion):
+ continue
+
+ if relpath.startswith(exclusion):
+ # Multiple exclusions can match the same path. Go through all of them
+ # and mark each one as used.
+ exclusion_used[exclusion] = True
+ excluded = True
+ if excluded:
+ continue
+
+ # Deleting gyp files almost always leads to gyp failures.
+ # These files come from Chromium project, and can be replaced if needed.
+ if f.endswith('.gyp') or f.endswith('.gypi'):
+ continue
+
+ # Deleting .isolate files leads to gyp failures. They are usually
+ # not used by a distro build anyway.
+ # See http://www.chromium.org/developers/testing/isolated-testing
+ # for more info.
+ if f.endswith('.isolate'):
+ continue
+
+ if options.do_remove:
+ # Delete the file - best way to ensure it's not used during build.
+ os.remove(path)
+ else:
+ # By default just print paths that would be removed.
+ print path
+
+ exit_code = 0
+
+ # Fail if exclusion list contains stale entries - this helps keep it
+ # up to date.
+ for exclusion, used in exclusion_used.iteritems():
+ if not used:
+ print '%s does not exist' % exclusion
+ exit_code = 1
+
+ if not options.do_remove:
+ print ('To actually remove files printed above, please pass ' +
+ '--do-remove flag.')
+
+ return exit_code
+
+
+if __name__ == '__main__':
+ sys.exit(DoMain(sys.argv[1:]))
diff --git a/build/linux/unbundle/replace_gyp_files.py b/build/linux/unbundle/replace_gyp_files.py
new file mode 100755
index 00000000000..d06ae411aee
--- /dev/null
+++ b/build/linux/unbundle/replace_gyp_files.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# Copyright 2013 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.
+
+"""
+Replaces gyp files in tree with files from here that
+make the build use system libraries.
+"""
+
+
+import optparse
+import os.path
+import shutil
+import sys
+
+
+REPLACEMENTS = {
+ 'use_system_expat': 'third_party/expat/expat.gyp',
+ 'use_system_ffmpeg': 'third_party/ffmpeg/ffmpeg.gyp',
+ 'use_system_flac': 'third_party/flac/flac.gyp',
+ 'use_system_harfbuzz': 'third_party/harfbuzz-ng/harfbuzz.gyp',
+ 'use_system_icu': 'third_party/icu/icu.gyp',
+ 'use_system_jsoncpp': 'third_party/jsoncpp/jsoncpp.gyp',
+ 'use_system_libevent': 'third_party/libevent/libevent.gyp',
+ 'use_system_libjpeg': 'third_party/libjpeg/libjpeg.gyp',
+ 'use_system_libpng': 'third_party/libpng/libpng.gyp',
+ 'use_system_libusb': 'third_party/libusb/libusb.gyp',
+ 'use_system_libvpx': 'third_party/libvpx/libvpx.gyp',
+ 'use_system_libwebp': 'third_party/libwebp/libwebp.gyp',
+ 'use_system_libxml': 'third_party/libxml/libxml.gyp',
+ 'use_system_libxnvctrl' : 'third_party/libXNVCtrl/libXNVCtrl.gyp',
+ 'use_system_libxslt': 'third_party/libxslt/libxslt.gyp',
+ 'use_system_opus': 'third_party/opus/opus.gyp',
+ 'use_system_protobuf': 'third_party/protobuf/protobuf.gyp',
+ 'use_system_re2': 'third_party/re2/re2.gyp',
+ 'use_system_snappy': 'third_party/snappy/snappy.gyp',
+ 'use_system_speex': 'third_party/speex/speex.gyp',
+ 'use_system_sqlite': 'third_party/sqlite/sqlite.gyp',
+ 'use_system_v8': 'v8/tools/gyp/v8.gyp',
+ 'use_system_zlib': 'third_party/zlib/zlib.gyp',
+}
+
+
+def DoMain(argv):
+ my_dirname = os.path.dirname(__file__)
+ source_tree_root = os.path.abspath(
+ os.path.join(my_dirname, '..', '..', '..'))
+
+ parser = optparse.OptionParser()
+
+ # Accept arguments in gyp command-line syntax, so that the caller can re-use
+ # command-line for this script and gyp.
+ parser.add_option('-D', dest='defines', action='append')
+
+ parser.add_option('--undo', action='store_true')
+
+ options, args = parser.parse_args(argv)
+
+ for flag, path in REPLACEMENTS.items():
+ if '%s=1' % flag not in options.defines:
+ continue
+
+ if options.undo:
+ # Restore original file, and also remove the backup.
+ # This is meant to restore the source tree to its original state.
+ os.rename(os.path.join(source_tree_root, path + '.orig'),
+ os.path.join(source_tree_root, path))
+ else:
+ # Create a backup copy for --undo.
+ shutil.copyfile(os.path.join(source_tree_root, path),
+ os.path.join(source_tree_root, path + '.orig'))
+
+ # Copy the gyp file from directory of this script to target path.
+ shutil.copyfile(os.path.join(my_dirname, os.path.basename(path)),
+ os.path.join(source_tree_root, path))
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(DoMain(sys.argv))
diff --git a/build/linux/unbundle/snappy.gyp b/build/linux/unbundle/snappy.gyp
new file mode 100644
index 00000000000..ab856ed89cb
--- /dev/null
+++ b/build/linux/unbundle/snappy.gyp
@@ -0,0 +1,29 @@
+# Copyright 2013 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'snappy',
+ 'type': 'none',
+ 'variables': {
+ 'headers_root_path': 'src',
+ 'header_filenames': [
+ 'snappy-c.h',
+ 'snappy-sinksource.h',
+ 'snappy-stubs-public.h',
+ 'snappy.h',
+ ],
+ },
+ 'includes': [
+ '../../build/shim_headers.gypi',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lsnappy',
+ ],
+ },
+ },
+ ],
+}
diff --git a/build/linux/unbundle/speex.gyp b/build/linux/unbundle/speex.gyp
new file mode 100644
index 00000000000..75376c8cf23
--- /dev/null
+++ b/build/linux/unbundle/speex.gyp
@@ -0,0 +1,45 @@
+# Copyright 2013 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'libspeex',
+ 'type': 'none',
+ 'variables': {
+ 'headers_root_path': 'include',
+ 'header_filenames': [
+ 'speex/speex_types.h',
+ 'speex/speex_callbacks.h',
+ 'speex/speex_config_types.h',
+ 'speex/speex_stereo.h',
+ 'speex/speex_echo.h',
+ 'speex/speex_preprocess.h',
+ 'speex/speex_jitter.h',
+ 'speex/speex.h',
+ 'speex/speex_resampler.h',
+ 'speex/speex_buffer.h',
+ 'speex/speex_header.h',
+ 'speex/speex_bits.h',
+ ],
+ },
+ 'includes': [
+ '../../build/shim_headers.gypi',
+ ],
+ 'direct_dependent_settings': {
+ 'cflags': [
+ '",
+ type=str,
+ required=True,
+ help="The target directory")
+
+ args = parser.parse_args()
+ sys.exit(main(args.target_directory))
diff --git a/build/mac/OWNERS b/build/mac/OWNERS
new file mode 100644
index 00000000000..c56e89dd123
--- /dev/null
+++ b/build/mac/OWNERS
@@ -0,0 +1,2 @@
+mark@chromium.org
+thomasvl@chromium.org
diff --git a/build/mac/asan.gyp b/build/mac/asan.gyp
new file mode 100644
index 00000000000..52316810080
--- /dev/null
+++ b/build/mac/asan.gyp
@@ -0,0 +1,53 @@
+# Copyright (c) 2013 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'asan_dynamic_runtime',
+ 'type': 'none',
+ 'variables': {
+ # Every target is going to depend on asan_dynamic_runtime, so allow
+ # this one to depend on itself.
+ 'prune_self_dependency': 1,
+ # Path is relative to this GYP file.
+ 'asan_rtl_mask_path':
+ '../../third_party/llvm-build/Release+Asserts/lib/clang/*/lib/darwin',
+ 'asan_osx_dynamic':
+ '<(asan_rtl_mask_path)/libclang_rt.asan_osx_dynamic.dylib',
+ 'asan_iossim_dynamic':
+ '<(asan_rtl_mask_path)/libclang_rt.asan_iossim_dynamic.dylib',
+ },
+ 'conditions': [
+ ['OS=="mac"', {
+ 'copies': [
+ {
+ 'destination': '<(PRODUCT_DIR)',
+ 'files': [
+ '
+
+Arranges for the executable at |executable_path| to have its data (heap)
+pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have
+the PIE (position independent executable) bit set to enable ASLR (address
+space layout randomization). With --executable-heap or --no-pie, the
+respective bits are cleared instead of set, making the heap executable or
+disabling PIE/ASLR.
+
+This script is able to operate on thin (single-architecture) Mach-O files
+and fat (universal, multi-architecture) files. When operating on fat files,
+it will set or clear the bits for each architecture contained therein.
+
+NON-EXECUTABLE HEAP
+
+Traditionally in Mac OS X, 32-bit processes did not have data pages set to
+prohibit execution. Although user programs could call mprotect and
+mach_vm_protect to deny execution of code in data pages, the kernel would
+silently ignore such requests without updating the page tables, and the
+hardware would happily execute code on such pages. 64-bit processes were
+always given proper hardware protection of data pages. This behavior was
+controllable on a system-wide level via the vm.allow_data_exec sysctl, which
+is set by default to 1. The bit with value 1 (set by default) allows code
+execution on data pages for 32-bit processes, and the bit with value 2
+(clear by default) does the same for 64-bit processes.
+
+In Mac OS X 10.7, executables can "opt in" to having hardware protection
+against code execution on data pages applied. This is done by setting a new
+bit in the |flags| field of an executable's |mach_header|. When
+MH_NO_HEAP_EXECUTION is set, proper protections will be applied, regardless
+of the setting of vm.allow_data_exec. See xnu-1699.22.73/osfmk/vm/vm_map.c
+override_nx and xnu-1699.22.73/bsd/kern/mach_loader.c load_machfile.
+
+The Apple toolchain has been revised to set the MH_NO_HEAP_EXECUTION when
+producing executables, provided that -allow_heap_execute is not specified
+at link time. Only linkers shipping with Xcode 4.0 and later (ld64-123.2 and
+later) have this ability. See ld64-123.2.1/src/ld/Options.cpp
+Options::reconfigureDefaults() and
+ld64-123.2.1/src/ld/HeaderAndLoadCommands.hpp
+HeaderAndLoadCommandsAtom::flags().
+
+This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is
+intended for use with executables produced by a linker that predates Apple's
+modifications to set this bit itself. It is also useful for setting this bit
+for non-i386 executables, including x86_64 executables. Apple's linker only
+sets it for 32-bit i386 executables, presumably under the assumption that
+the value of vm.allow_data_exec is set in stone. However, if someone were to
+change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run
+without hardware protection against code execution on data pages. This
+script can set the bit for x86_64 executables, guaranteeing that they run
+with appropriate protection even when vm.allow_data_exec has been tampered
+with.
+
+POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION
+
+This script sets or clears the MH_PIE bit in an executable's Mach-O header,
+enabling or disabling position independence on Mac OS X 10.5 and later.
+Processes running position-independent executables have varying levels of
+ASLR protection depending on the OS release. The main executable's load
+address, shared library load addresess, and the heap and stack base
+addresses may be randomized. Position-independent executables are produced
+by supplying the -pie flag to the linker (or defeated by supplying -no_pie).
+Executables linked with a deployment target of 10.7 or higher have PIE on
+by default.
+
+This script is never strictly needed during the build to enable PIE, as all
+linkers used are recent enough to support -pie. However, it's used to
+disable the PIE bit as needed on already-linked executables.
+"""
+
+import optparse
+import os
+import struct
+import sys
+
+
+#
+FAT_MAGIC = 0xcafebabe
+FAT_CIGAM = 0xbebafeca
+
+#
+MH_MAGIC = 0xfeedface
+MH_CIGAM = 0xcefaedfe
+MH_MAGIC_64 = 0xfeedfacf
+MH_CIGAM_64 = 0xcffaedfe
+MH_EXECUTE = 0x2
+MH_PIE = 0x00200000
+MH_NO_HEAP_EXECUTION = 0x01000000
+
+
+class MachOError(Exception):
+ """A class for exceptions thrown by this module."""
+
+ pass
+
+
+def CheckedSeek(file, offset):
+ """Seeks the file-like object at |file| to offset |offset| and raises a
+ MachOError if anything funny happens."""
+
+ file.seek(offset, os.SEEK_SET)
+ new_offset = file.tell()
+ if new_offset != offset:
+ raise MachOError, \
+ 'seek: expected offset %d, observed %d' % (offset, new_offset)
+
+
+def CheckedRead(file, count):
+ """Reads |count| bytes from the file-like |file| object, raising a
+ MachOError if any other number of bytes is read."""
+
+ bytes = file.read(count)
+ if len(bytes) != count:
+ raise MachOError, \
+ 'read: expected length %d, observed %d' % (count, len(bytes))
+
+ return bytes
+
+
+def ReadUInt32(file, endian):
+ """Reads an unsinged 32-bit integer from the file-like |file| object,
+ treating it as having endianness specified by |endian| (per the |struct|
+ module), and returns it as a number. Raises a MachOError if the proper
+ length of data can't be read from |file|."""
+
+ bytes = CheckedRead(file, 4)
+
+ (uint32,) = struct.unpack(endian + 'I', bytes)
+ return uint32
+
+
+def ReadMachHeader(file, endian):
+ """Reads an entire |mach_header| structure () from the
+ file-like |file| object, treating it as having endianness specified by
+ |endian| (per the |struct| module), and returns a 7-tuple of its members
+ as numbers. Raises a MachOError if the proper length of data can't be read
+ from |file|."""
+
+ bytes = CheckedRead(file, 28)
+
+ magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
+ struct.unpack(endian + '7I', bytes)
+ return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags
+
+
+def ReadFatArch(file):
+ """Reads an entire |fat_arch| structure () from the file-like
+ |file| object, treating it as having endianness specified by |endian|
+ (per the |struct| module), and returns a 5-tuple of its members as numbers.
+ Raises a MachOError if the proper length of data can't be read from
+ |file|."""
+
+ bytes = CheckedRead(file, 20)
+
+ cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes)
+ return cputype, cpusubtype, offset, size, align
+
+
+def WriteUInt32(file, uint32, endian):
+ """Writes |uint32| as an unsinged 32-bit integer to the file-like |file|
+ object, treating it as having endianness specified by |endian| (per the
+ |struct| module)."""
+
+ bytes = struct.pack(endian + 'I', uint32)
+ assert len(bytes) == 4
+
+ file.write(bytes)
+
+
+def HandleMachOFile(file, options, offset=0):
+ """Seeks the file-like |file| object to |offset|, reads its |mach_header|,
+ and rewrites the header's |flags| field if appropriate. The header's
+ endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported
+ (mach_header and mach_header_64). Raises MachOError if used on a header that
+ does not have a known magic number or is not of type MH_EXECUTE. The
+ MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field
+ according to |options| and written to |file| if any changes need to be made.
+ If already set or clear as specified by |options|, nothing is written."""
+
+ CheckedSeek(file, offset)
+ magic = ReadUInt32(file, '<')
+ if magic == MH_MAGIC or magic == MH_MAGIC_64:
+ endian = '<'
+ elif magic == MH_CIGAM or magic == MH_CIGAM_64:
+ endian = '>'
+ else:
+ raise MachOError, \
+ 'Mach-O file at offset %d has illusion of magic' % offset
+
+ CheckedSeek(file, offset)
+ magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
+ ReadMachHeader(file, endian)
+ assert magic == MH_MAGIC or magic == MH_MAGIC_64
+ if filetype != MH_EXECUTE:
+ raise MachOError, \
+ 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \
+ (offset, filetype)
+
+ original_flags = flags
+
+ if options.no_heap_execution:
+ flags |= MH_NO_HEAP_EXECUTION
+ else:
+ flags &= ~MH_NO_HEAP_EXECUTION
+
+ if options.pie:
+ flags |= MH_PIE
+ else:
+ flags &= ~MH_PIE
+
+ if flags != original_flags:
+ CheckedSeek(file, offset + 24)
+ WriteUInt32(file, flags, endian)
+
+
+def HandleFatFile(file, options, fat_offset=0):
+ """Seeks the file-like |file| object to |offset| and loops over its
+ |fat_header| entries, calling HandleMachOFile for each."""
+
+ CheckedSeek(file, fat_offset)
+ magic = ReadUInt32(file, '>')
+ assert magic == FAT_MAGIC
+
+ nfat_arch = ReadUInt32(file, '>')
+
+ for index in xrange(0, nfat_arch):
+ cputype, cpusubtype, offset, size, align = ReadFatArch(file)
+ assert size >= 28
+
+ # HandleMachOFile will seek around. Come back here after calling it, in
+ # case it sought.
+ fat_arch_offset = file.tell()
+ HandleMachOFile(file, options, offset)
+ CheckedSeek(file, fat_arch_offset)
+
+
+def main(me, args):
+ parser = optparse.OptionParser('%prog [options] ')
+ parser.add_option('--executable-heap', action='store_false',
+ dest='no_heap_execution', default=True,
+ help='Clear the MH_NO_HEAP_EXECUTION bit')
+ parser.add_option('--no-pie', action='store_false',
+ dest='pie', default=True,
+ help='Clear the MH_PIE bit')
+ (options, loose_args) = parser.parse_args(args)
+ if len(loose_args) != 1:
+ parser.print_usage()
+ return 1
+
+ executable_path = loose_args[0]
+ executable_file = open(executable_path, 'rb+')
+
+ magic = ReadUInt32(executable_file, '<')
+ if magic == FAT_CIGAM:
+ # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian.
+ HandleFatFile(executable_file, options)
+ elif magic == MH_MAGIC or magic == MH_CIGAM or \
+ magic == MH_MAGIC_64 or magic == MH_CIGAM_64:
+ HandleMachOFile(executable_file, options)
+ else:
+ raise MachOError, '%s is not a Mach-O or fat file' % executable_file
+
+ executable_file.close()
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[0], sys.argv[1:]))
diff --git a/build/mac/change_mach_o_flags_from_xcode.sh b/build/mac/change_mach_o_flags_from_xcode.sh
new file mode 100755
index 00000000000..1824f8db52f
--- /dev/null
+++ b/build/mac/change_mach_o_flags_from_xcode.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Copyright (c) 2011 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 small wrapper script around change_mach_o_flags.py allowing it to
+# be invoked easily from Xcode. change_mach_o_flags.py expects its arguments
+# on the command line, but Xcode puts its parameters in the environment.
+
+set -e
+
+exec "$(dirname "${0}")/change_mach_o_flags.py" \
+ "${@}" \
+ "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}"
diff --git a/build/mac/chrome_mac.croc b/build/mac/chrome_mac.croc
new file mode 100644
index 00000000000..8cde00ce206
--- /dev/null
+++ b/build/mac/chrome_mac.croc
@@ -0,0 +1,36 @@
+# -*- python -*-
+# Crocodile config file for Chromium mac
+
+{
+ # List of rules, applied in order
+ 'rules' : [
+ # Specify inclusions before exclusions, since rules are in order.
+
+ # Don't include chromeos, linux, or windows specific files
+ {
+ 'regexp' : '.*(_|/)(chromeos|linux|win|views)(\\.|_)',
+ 'include' : 0,
+ },
+ # Don't include ChromeOS dirs
+ {
+ 'regexp' : '.*/chromeos/',
+ 'include' : 0,
+ },
+
+ # Groups
+ {
+ 'regexp' : '.*_test_mac\\.',
+ 'group' : 'test',
+ },
+
+ # Languages
+ {
+ 'regexp' : '.*\\.m$',
+ 'language' : 'ObjC',
+ },
+ {
+ 'regexp' : '.*\\.mm$',
+ 'language' : 'ObjC++',
+ },
+ ],
+}
diff --git a/build/mac/copy_asan_runtime_dylib.sh b/build/mac/copy_asan_runtime_dylib.sh
new file mode 100755
index 00000000000..f221c4a66dd
--- /dev/null
+++ b/build/mac/copy_asan_runtime_dylib.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+
+# Copyright (c) 2013 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.
+
+# For app bundles built with ASan, copies the runtime lib
+# (libclang_rt.asan_osx_dynamic.dylib), on which their executables depend, from
+# the compiler installation path into the bundle and fixes the dylib's install
+# name in the binary to be relative to @executable_path.
+
+set -e
+
+BINARY="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}"
+
+if [[ ! -f "$BINARY" ]]; then
+ # This is neither an .app bundle nor a standalone executable.
+ # Most certainly the script has been called for a data bundle.
+ exit 0
+fi
+
+BINARY_DIR="$(dirname "${BINARY}")"
+
+# Find the link to the ASan runtime encoded in the binary.
+BUILTIN_DYLIB_PATH=$(otool -L "${BINARY}" | \
+ sed -Ene 's/^[[:blank:]]+(.*libclang_rt\.asan_.*_dynamic\.dylib).*$/\1/p')
+
+if [[ "${BUILTIN_DYLIB_PATH}" == *asan_iossim_dynamic* ]]; then
+ ASAN_DYLIB_NAME=libclang_rt.asan_iossim_dynamic.dylib
+elif [[ "${BUILTIN_DYLIB_PATH}" == *asan_osx_dynamic* ]]; then
+ ASAN_DYLIB_NAME=libclang_rt.asan_osx_dynamic.dylib
+fi
+
+if [[ -z "${BUILTIN_DYLIB_PATH}" ]]; then
+ echo "${BINARY} does not depend on the ASan runtime library!" >&2
+ exit 1
+fi
+
+# TODO(glider): this doesn't work if we set CC and CXX to override the default
+# Clang.
+ASAN_DYLIB=$(find \
+ "${BUILT_PRODUCTS_DIR}/../../third_party/llvm-build/Release+Asserts/lib/clang/" \
+ -type f -path "*${ASAN_DYLIB_NAME}")
+
+DYLIB_BASENAME=$(basename "${ASAN_DYLIB}")
+if [[ "${DYLIB_BASENAME}" != "${ASAN_DYLIB_NAME}" ]]; then
+ echo "basename(${ASAN_DYLIB}) != ${ASAN_DYLIB_NAME}" >&2
+ exit 1
+fi
+
+# Check whether the directory containing the executable binary is named
+# "MacOS". In this case we're building a full-fledged OSX app and will put
+# the runtime into appname.app/Contents/Libraries/. Otherwise this is probably
+# an iOS gtest app, and the ASan runtime is put next to the executable.
+UPPER_DIR=$(dirname "${BINARY_DIR}")
+if [ "${UPPER_DIR}" == "MacOS" ]; then
+ LIBRARIES_DIR="${UPPER_DIR}/Libraries"
+ mkdir -p "${LIBRARIES_DIR}"
+ NEW_LC_ID_DYLIB="@executable_path/../Libraries/${ASAN_DYLIB_NAME}"
+else
+ LIBRARIES_DIR="${BINARY_DIR}"
+ NEW_LC_ID_DYLIB="@executable_path/${ASAN_DYLIB_NAME}"
+fi
+
+cp "${ASAN_DYLIB}" "${LIBRARIES_DIR}"
+
+# Make LC_ID_DYLIB of the runtime copy point to its location.
+install_name_tool \
+ -id "${NEW_LC_ID_DYLIB}" \
+ "${LIBRARIES_DIR}/${ASAN_DYLIB_NAME}"
+
+# Fix the rpath to the runtime library recorded in the binary.
+install_name_tool \
+ -change "${BUILTIN_DYLIB_PATH}" \
+ "${NEW_LC_ID_DYLIB}" \
+ "${BINARY}"
diff --git a/build/mac/copy_framework_unversioned.sh b/build/mac/copy_framework_unversioned.sh
new file mode 100755
index 00000000000..380cc908407
--- /dev/null
+++ b/build/mac/copy_framework_unversioned.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+
+# Copyright (c) 2012 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.
+
+# Copies a framework to its new home, "unversioning" it.
+#
+# Normally, frameworks are versioned bundles. The contents of a framework are
+# stored in a versioned directory within the bundle, and symbolic links
+# provide access to the actual code and resources. See
+# http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
+#
+# The symbolic links usually found in frameworks create problems. Symbolic
+# links are excluded from code signatures. That means that it's possible to
+# remove or retarget a symbolic link within a framework without affecting the
+# seal. In Chrome's case, the outer .app bundle contains a framework where
+# all application code and resources live. In order for the signature on the
+# .app to be meaningful, it encompasses the framework. Because framework
+# resources are accessed through the framework's symbolic links, this
+# arrangement results in a case where the resources can be altered without
+# affecting the .app signature's validity.
+#
+# Indirection through symbolic links also carries a runtime performance
+# penalty on open() operations, although open() typically completes so quickly
+# that this is not considered a major performance problem.
+#
+# To resolve these problems, the frameworks that ship within Chrome's .app
+# bundle are unversioned. Unversioning is simple: instead of using the
+# original outer .framework directory as the framework that ships within the
+# .app, the inner versioned directory is used. Instead of accessing bundled
+# resources through symbolic links, they are accessed directly. In normal
+# situations, the only hard-coded use of the versioned directory is by dyld,
+# when loading the framework's code, but this is handled through a normal
+# Mach-O load command, and it is easy to adjust the load command to point to
+# the unversioned framework code rather than the versioned counterpart.
+#
+# The resulting framework bundles aren't strictly conforming, but they work
+# as well as normal versioned framework bundles.
+#
+# An option to skip running install_name_tool is available. By passing -I as
+# the first argument to this script, install_name_tool will be skipped. This
+# is only suitable for copied frameworks that will not be linked against, or
+# when install_name_tool will be run on any linker output when something is
+# linked against the copied framework. This option exists to allow signed
+# frameworks to pass through without subjecting them to any modifications that
+# would break their signatures.
+
+set -e
+
+RUN_INSTALL_NAME_TOOL=1
+if [ $# -eq 3 ] && [ "${1}" = "-I" ] ; then
+ shift
+ RUN_INSTALL_NAME_TOOL=
+fi
+
+if [ $# -ne 2 ] ; then
+ echo "usage: ${0} [-I] FRAMEWORK DESTINATION_DIR" >& 2
+ exit 1
+fi
+
+# FRAMEWORK should be a path to a versioned framework bundle, ending in
+# .framework. DESTINATION_DIR is the directory that the unversioned framework
+# bundle will be copied to.
+
+FRAMEWORK="${1}"
+DESTINATION_DIR="${2}"
+
+FRAMEWORK_NAME="$(basename "${FRAMEWORK}")"
+if [ "${FRAMEWORK_NAME: -10}" != ".framework" ] ; then
+ echo "${0}: ${FRAMEWORK_NAME} does not end in .framework" >& 2
+ exit 1
+fi
+FRAMEWORK_NAME_NOEXT="${FRAMEWORK_NAME:0:$((${#FRAMEWORK_NAME} - 10))}"
+
+# Find the current version.
+VERSIONS="${FRAMEWORK}/Versions"
+CURRENT_VERSION_LINK="${VERSIONS}/Current"
+CURRENT_VERSION_ID="$(readlink "${VERSIONS}/Current")"
+CURRENT_VERSION="${VERSIONS}/${CURRENT_VERSION_ID}"
+
+# Make sure that the framework's structure makes sense as a versioned bundle.
+if [ ! -e "${CURRENT_VERSION}/${FRAMEWORK_NAME_NOEXT}" ] ; then
+ echo "${0}: ${FRAMEWORK_NAME} does not contain a dylib" >& 2
+ exit 1
+fi
+
+DESTINATION="${DESTINATION_DIR}/${FRAMEWORK_NAME}"
+
+# Copy the versioned directory within the versioned framework to its
+# destination location.
+mkdir -p "${DESTINATION_DIR}"
+rsync -acC --delete --exclude Headers --exclude PrivateHeaders \
+ --include '*.so' "${CURRENT_VERSION}/" "${DESTINATION}"
+
+if [[ -n "${RUN_INSTALL_NAME_TOOL}" ]]; then
+ # Adjust the Mach-O LC_ID_DYLIB load command in the framework. This does not
+ # change the LC_LOAD_DYLIB load commands in anything that may have already
+ # linked against the framework. Not all frameworks will actually need this
+ # to be changed. Some frameworks may already be built with the proper
+ # LC_ID_DYLIB for use as an unversioned framework. Xcode users can do this
+ # by setting LD_DYLIB_INSTALL_NAME to
+ # $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(WRAPPER_NAME)/$(PRODUCT_NAME)
+ # If invoking ld via gcc or g++, pass the desired path to -Wl,-install_name
+ # at link time.
+ FRAMEWORK_DYLIB="${DESTINATION}/${FRAMEWORK_NAME_NOEXT}"
+ LC_ID_DYLIB_OLD="$(otool -l "${FRAMEWORK_DYLIB}" |
+ grep -A10 "^ *cmd LC_ID_DYLIB$" |
+ grep -m1 "^ *name" |
+ sed -Ee 's/^ *name (.*) \(offset [0-9]+\)$/\1/')"
+ VERSION_PATH="/Versions/${CURRENT_VERSION_ID}/${FRAMEWORK_NAME_NOEXT}"
+ LC_ID_DYLIB_NEW="$(echo "${LC_ID_DYLIB_OLD}" |
+ sed -Ee "s%${VERSION_PATH}$%/${FRAMEWORK_NAME_NOEXT}%")"
+
+ if [ "${LC_ID_DYLIB_NEW}" != "${LC_ID_DYLIB_OLD}" ] ; then
+ install_name_tool -id "${LC_ID_DYLIB_NEW}" "${FRAMEWORK_DYLIB}"
+ fi
+fi
diff --git a/build/mac/edit_xibs.sh b/build/mac/edit_xibs.sh
new file mode 100755
index 00000000000..b7b749e1601
--- /dev/null
+++ b/build/mac/edit_xibs.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Copyright (c) 2012 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 script is a convenience to run GYP for /src/chrome/chrome_nibs.gyp
+# with the Xcode generator (as you likely use ninja). Documentation:
+# http://dev.chromium.org/developers/design-documents/mac-xib-files
+
+set -e
+
+RELSRC=$(dirname "$0")/../..
+SRC=$(cd "$RELSRC" && pwd)
+export PYTHONPATH="$PYTHONPATH:$SRC/build"
+export GYP_GENERATORS=xcode
+"$SRC/tools/gyp/gyp" -I"$SRC/build/common.gypi" "$SRC/chrome/chrome_nibs.gyp"
+echo "You can now edit XIB files in Xcode using:"
+echo " $SRC/chrome/chrome_nibs.xcodeproj"
diff --git a/build/mac/find_sdk.py b/build/mac/find_sdk.py
new file mode 100755
index 00000000000..0534766e807
--- /dev/null
+++ b/build/mac/find_sdk.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Prints the lowest locally available SDK version greater than or equal to a
+given minimum sdk version to standard output.
+
+Usage:
+ python find_sdk.py 10.6 # Ignores SDKs < 10.6
+"""
+
+import os
+import re
+import subprocess
+import sys
+
+
+from optparse import OptionParser
+
+
+def parse_version(version_str):
+ """'10.6' => [10, 6]"""
+ return map(int, re.findall(r'(\d+)', version_str))
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option("--verify",
+ action="store_true", dest="verify", default=False,
+ help="return the sdk argument and warn if it doesn't exist")
+ parser.add_option("--sdk_path",
+ action="store", type="string", dest="sdk_path", default="",
+ help="user-specified SDK path; bypasses verification")
+ parser.add_option("--print_sdk_path",
+ action="store_true", dest="print_sdk_path", default=False,
+ help="Additionaly print the path the SDK (appears first).")
+ (options, args) = parser.parse_args()
+ min_sdk_version = args[0]
+
+ job = subprocess.Popen(['xcode-select', '-print-path'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ out, err = job.communicate()
+ if job.returncode != 0:
+ print >> sys.stderr, out
+ print >> sys.stderr, err
+ raise Exception(('Error %d running xcode-select, you might have to run '
+ '|sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer| '
+ 'if you are using Xcode 4.') % job.returncode)
+ # The Developer folder moved in Xcode 4.3.
+ xcode43_sdk_path = os.path.join(
+ out.rstrip(), 'Platforms/MacOSX.platform/Developer/SDKs')
+ if os.path.isdir(xcode43_sdk_path):
+ sdk_dir = xcode43_sdk_path
+ else:
+ sdk_dir = os.path.join(out.rstrip(), 'SDKs')
+ sdks = [re.findall('^MacOSX(10\.\d+)\.sdk$', s) for s in os.listdir(sdk_dir)]
+ sdks = [s[0] for s in sdks if s] # [['10.5'], ['10.6']] => ['10.5', '10.6']
+ sdks = [s for s in sdks # ['10.5', '10.6'] => ['10.6']
+ if parse_version(s) >= parse_version(min_sdk_version)]
+ if not sdks:
+ raise Exception('No %s+ SDK found' % min_sdk_version)
+ best_sdk = sorted(sdks, key=parse_version)[0]
+
+ if options.verify and best_sdk != min_sdk_version and not options.sdk_path:
+ print >> sys.stderr, ''
+ print >> sys.stderr, ' vvvvvvv'
+ print >> sys.stderr, ''
+ print >> sys.stderr, \
+ 'This build requires the %s SDK, but it was not found on your system.' \
+ % min_sdk_version
+ print >> sys.stderr, \
+ 'Either install it, or explicitly set mac_sdk in your GYP_DEFINES.'
+ print >> sys.stderr, ''
+ print >> sys.stderr, ' ^^^^^^^'
+ print >> sys.stderr, ''
+ return min_sdk_version
+
+ if options.print_sdk_path:
+ print subprocess.check_output(['xcodebuild', '-version', '-sdk',
+ 'macosx' + best_sdk, 'Path']).strip()
+
+ return best_sdk
+
+
+if __name__ == '__main__':
+ if sys.platform != 'darwin':
+ raise Exception("This script only runs on Mac")
+ print main()
diff --git a/build/mac/make_more_helpers.sh b/build/mac/make_more_helpers.sh
new file mode 100755
index 00000000000..6f5c4749ebf
--- /dev/null
+++ b/build/mac/make_more_helpers.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+
+# Copyright (c) 2012 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: make_more_helpers.sh
+#
+# This script creates additional helper .app bundles for Chromium, based on
+# the existing helper .app bundle, changing their Mach-O header's flags to
+# enable and disable various features. Based on Chromium Helper.app, it will
+# create Chromium Helper EH.app, which has the MH_NO_HEAP_EXECUTION bit
+# cleared to support Chromium child processes that require an executable heap,
+# and Chromium Helper NP.app, which has the MH_PIE bit cleared to support
+# Chromium child processes that cannot tolerate ASLR.
+#
+# This script expects to be called from the chrome_exe target as a postbuild,
+# and operates directly within the built-up browser app's versioned directory.
+#
+# Each helper is adjusted by giving it the proper bundle name, renaming the
+# executable, adjusting several Info.plist keys, and changing the executable's
+# Mach-O flags.
+
+set -eu
+
+make_helper() {
+ local containing_dir="${1}"
+ local app_name="${2}"
+ local feature="${3}"
+ local flags="${4}"
+
+ local helper_name="${app_name} Helper"
+ local helper_stem="${containing_dir}/${helper_name}"
+ local original_helper="${helper_stem}.app"
+ if [[ ! -d "${original_helper}" ]]; then
+ echo "${0}: error: ${original_helper} is a required directory" >& 2
+ exit 1
+ fi
+ local original_helper_exe="${original_helper}/Contents/MacOS/${helper_name}"
+ if [[ ! -f "${original_helper_exe}" ]]; then
+ echo "${0}: error: ${original_helper_exe} is a required file" >& 2
+ exit 1
+ fi
+
+ local feature_helper="${helper_stem} ${feature}.app"
+
+ rsync -acC --delete --include '*.so' "${original_helper}/" "${feature_helper}"
+
+ local helper_feature="${helper_name} ${feature}"
+ local helper_feature_exe="${feature_helper}/Contents/MacOS/${helper_feature}"
+ mv "${feature_helper}/Contents/MacOS/${helper_name}" "${helper_feature_exe}"
+
+ local change_flags="$(dirname "${0}")/change_mach_o_flags.py"
+ "${change_flags}" ${flags} "${helper_feature_exe}"
+
+ local feature_info="${feature_helper}/Contents/Info"
+ local feature_info_plist="${feature_info}.plist"
+
+ defaults write "${feature_info}" "CFBundleDisplayName" "${helper_feature}"
+ defaults write "${feature_info}" "CFBundleExecutable" "${helper_feature}"
+
+ cfbundleid="$(defaults read "${feature_info}" "CFBundleIdentifier")"
+ feature_cfbundleid="${cfbundleid}.${feature}"
+ defaults write "${feature_info}" "CFBundleIdentifier" "${feature_cfbundleid}"
+
+ cfbundlename="$(defaults read "${feature_info}" "CFBundleName")"
+ feature_cfbundlename="${cfbundlename} ${feature}"
+ defaults write "${feature_info}" "CFBundleName" "${feature_cfbundlename}"
+
+ # As usual, defaults might have put the plist into whatever format excites
+ # it, but Info.plists get converted back to the expected XML format.
+ plutil -convert xml1 "${feature_info_plist}"
+
+ # `defaults` also changes the file permissions, so make the file
+ # world-readable again.
+ chmod a+r "${feature_info_plist}"
+}
+
+if [[ ${#} -ne 2 ]]; then
+ echo "usage: ${0} " >& 2
+ exit 1
+fi
+
+DIRECTORY_WITHIN_CONTENTS="${1}"
+APP_NAME="${2}"
+
+CONTENTS_DIR="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}"
+CONTAINING_DIR="${CONTENTS_DIR}/${DIRECTORY_WITHIN_CONTENTS}"
+
+make_helper "${CONTAINING_DIR}" "${APP_NAME}" "EH" "--executable-heap"
+make_helper "${CONTAINING_DIR}" "${APP_NAME}" "NP" "--no-pie"
diff --git a/build/mac/strip_from_xcode b/build/mac/strip_from_xcode
new file mode 100755
index 00000000000..c26b9fb492b
--- /dev/null
+++ b/build/mac/strip_from_xcode
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+# 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
+
+declare -a FLAGS
+
+# 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 (no special flags). No-op.
+ true
+elif [ "${MACH_O_TYPE}" = "mh_dylib" ] || \
+ [ "${MACH_O_TYPE}" = "mh_bundle" ]; then
+ # Strip debugging symbols and local symbols
+ FLAGS[${#FLAGS[@]}]=-S
+ FLAGS[${#FLAGS[@]}]=-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
+
+if [ -n "${STRIPFLAGS}" ] ; then
+ # Pick up the standard STRIPFLAGS Xcode setting, used for "Additional Strip
+ # Flags".
+ for stripflag in "${STRIPFLAGS}" ; do
+ FLAGS[${#FLAGS[@]}]="${stripflag}"
+ done
+fi
+
+if [ -n "${CHROMIUM_STRIP_SAVE_FILE}" ] ; then
+ # An Xcode project can communicate a file listing symbols to saved in this
+ # environment variable by setting it as a build setting. This isn't a
+ # standard Xcode setting. It's used in preference to STRIPFLAGS to
+ # eliminate quoting ambiguity concerns.
+ FLAGS[${#FLAGS[@]}]=-s
+ FLAGS[${#FLAGS[@]}]="${CHROMIUM_STRIP_SAVE_FILE}"
+fi
+
+exec "$(dirname ${0})/strip_save_dsym" "${FLAGS[@]}" \
+ "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}"
diff --git a/build/mac/strip_save_dsym b/build/mac/strip_save_dsym
new file mode 100755
index 00000000000..c9cf2266375
--- /dev/null
+++ b/build/mac/strip_save_dsym
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2011 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
+#
+# 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):
+ macho_types = ["executable",
+ "dynamically linked shared library",
+ "bundle"]
+ macho_types_re = "Mach-O (?:64-bit )?(?:" + "|".join(macho_types) + ")"
+
+ file_cmd = subprocess.Popen(["/usr/bin/file", "-b", "--", macho],
+ stdout=subprocess.PIPE)
+
+ archs = []
+
+ type_line = file_cmd.stdout.readline()
+ type_match = re.match("^%s (.*)$" % macho_types_re, 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 (.*)\):\t%s .*$" % macho_types_re,
+ arch_line)
+ if arch_match:
+ archs.append(arch_match.group(1))
+
+ if file_cmd.wait() != 0:
+ archs = []
+
+ if len(archs) == 0:
+ print >> sys.stderr, "No architectures in %s" % macho
+
+ 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):
+ uuids = {}
+
+ archs = macho_archs(macho)
+ if len(archs) == 0:
+ return uuids
+
+ for arch in archs:
+ if arch == "":
+ continue
+
+ otool_cmd = subprocess.Popen(["/usr/bin/otool", "-arch", arch, "-l", "-",
+ macho],
+ stdout=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:
+ # The UUID display format changed in the version of otool shipping
+ # with the Xcode 3.2.2 prerelease. The new format is traditional:
+ # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955
+ # and with Xcode 3.2.6, then line is indented one more space:
+ # uuid 4D7135B2-9C56-C5F5-5F49-A994258E0955
+ # The old format, from cctools-750 and older's otool, breaks the UUID
+ # up into a sequence of bytes:
+ # uuid 0x4d 0x71 0x35 0xb2 0x9c 0x56 0xc5 0xf5
+ # 0x5f 0x49 0xa9 0x94 0x25 0x8e 0x09 0x55
+ new_uuid_match = re.match("^ {3,4}uuid (.{8}-.{4}-.{4}-.{4}-.{12})$",
+ otool_line)
+ if new_uuid_match:
+ uuid = new_uuid_match.group(1)
+
+ # Skip state 4, there is no second line to read.
+ state = 5
+ else:
+ old_uuid_match = re.match("^ uuid 0x(..) 0x(..) 0x(..) 0x(..) "
+ "0x(..) 0x(..) 0x(..) 0x(..)$",
+ otool_line)
+ if old_uuid_match:
+ state = 4
+ uuid = old_uuid_match.group(1) + old_uuid_match.group(2) + \
+ old_uuid_match.group(3) + old_uuid_match.group(4) + "-" + \
+ old_uuid_match.group(5) + old_uuid_match.group(6) + "-" + \
+ old_uuid_match.group(7) + old_uuid_match.group(8) + "-"
+ else:
+ state = 6
+ elif state == 4:
+ old_uuid_match = re.match("^ 0x(..) 0x(..) 0x(..) 0x(..) "
+ "0x(..) 0x(..) 0x(..) 0x(..)$",
+ otool_line)
+ if old_uuid_match:
+ state = 5
+ uuid += old_uuid_match.group(1) + old_uuid_match.group(2) + "-" + \
+ old_uuid_match.group(3) + old_uuid_match.group(4) + \
+ old_uuid_match.group(5) + old_uuid_match.group(6) + \
+ old_uuid_match.group(7) + old_uuid_match.group(8)
+ else:
+ state = 6
+
+ if otool_cmd.wait() != 0:
+ state = 6
+
+ if state == 5:
+ uuids[arch] = uuid.upper()
+
+ if len(uuids) == 0:
+ print >> sys.stderr, "No UUIDs in %s" % macho
+
+ 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 = \
+'''
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleIdentifier
+ com.apple.xcode.dsym.%(root_name)s
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ dSYM
+ CFBundleSignature
+ ????
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ dSYM_UUID
+
+%(uuid_dict)s
+ fake_dsym
+
+
+
+'''
+
+ root_name = os.path.basename(dsym)[:-5] # whatever.dSYM without .dSYM
+ uuid_dict = ""
+ for arch in sorted(uuids):
+ uuid_dict += "\t\t\t" + arch + " \n"\
+ "\t\t\t" + uuids[arch] + " \n"
+ info_dict = {
+ "root_name": root_name,
+ "uuid_dict": uuid_dict,
+ }
+ info_contents = info_template % info_dict
+ info_file = os.path.join(dsym, "Contents", "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_cmdline = ['xcrun', 'strip'] + sys.argv[1:]
+ 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/mac/tweak_info_plist.py b/build/mac/tweak_info_plist.py
new file mode 100755
index 00000000000..2057bac8386
--- /dev/null
+++ b/build/mac/tweak_info_plist.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012 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.
+
+#
+# Xcode supports build variable substitutions and CPP; sadly, that doesn't work
+# because:
+#
+# 1. Xcode wants to do the Info.plist work before it runs any build phases,
+# this means if we were to generate a .h file for INFOPLIST_PREFIX_HEADER
+# we'd have to put it in another target so it runs in time.
+# 2. Xcode also doesn't check to see if the header being used as a prefix for
+# the Info.plist has changed. So even if we updated it, it's only looking
+# at the modtime of the info.plist to see if that's changed.
+#
+# So, we work around all of this by making a script build phase that will run
+# during the app build, and simply update the info.plist in place. This way
+# by the time the app target is done, the info.plist is correct.
+#
+
+import optparse
+import os
+from os import environ as env
+import plistlib
+import re
+import subprocess
+import sys
+import tempfile
+
+TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+
+
+def _GetOutput(args):
+ """Runs a subprocess and waits for termination. Returns (stdout, returncode)
+ of the process. stderr is attached to the parent."""
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+ (stdout, stderr) = proc.communicate()
+ return (stdout, proc.returncode)
+
+
+def _GetOutputNoError(args):
+ """Similar to _GetOutput() but ignores stderr. If there's an error launching
+ the child (like file not found), the exception will be caught and (None, 1)
+ will be returned to mimic quiet failure."""
+ try:
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ except OSError:
+ return (None, 1)
+ (stdout, stderr) = proc.communicate()
+ return (stdout, proc.returncode)
+
+
+def _RemoveKeys(plist, *keys):
+ """Removes a varargs of keys from the plist."""
+ for key in keys:
+ try:
+ del plist[key]
+ except KeyError:
+ pass
+
+
+def _AddVersionKeys(plist, version=None):
+ """Adds the product version number into the plist. Returns True on success and
+ False on error. The error will be printed to stderr."""
+ if version:
+ match = re.match('\d+\.\d+\.(\d+\.\d+)$', version)
+ if not match:
+ print >>sys.stderr, 'Invalid version string specified: "%s"' % version
+ return False
+
+ full_version = match.group(0)
+ bundle_version = match.group(1)
+
+ else:
+ # Pull in the Chrome version number.
+ VERSION_TOOL = os.path.join(TOP, 'build/util/version.py')
+ VERSION_FILE = os.path.join(TOP, 'chrome/VERSION')
+
+ (stdout, retval1) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
+ '@MAJOR@.@MINOR@.@BUILD@.@PATCH@'])
+ full_version = stdout.rstrip()
+
+ (stdout, retval2) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t',
+ '@BUILD@.@PATCH@'])
+ bundle_version = stdout.rstrip()
+
+ # If either of the two version commands finished with non-zero returncode,
+ # report the error up.
+ if retval1 or retval2:
+ return False
+
+ # Add public version info so "Get Info" works.
+ plist['CFBundleShortVersionString'] = full_version
+
+ # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1
+ # into 6, 2, 2 digits. The limitation was present in Tiger, but it could
+ # have been fixed in later OS release, but hasn't been tested (it's easy
+ # enough to find out with "lsregister -dump).
+ # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html
+ # BUILD will always be an increasing value, so BUILD_PATH gives us something
+ # unique that meetings what LS wants.
+ plist['CFBundleVersion'] = bundle_version
+
+ # Return with no error.
+ return True
+
+
+def _DoSCMKeys(plist, add_keys):
+ """Adds the SCM information, visible in about:version, to property list. If
+ |add_keys| is True, it will insert the keys, otherwise it will remove them."""
+ scm_revision = None
+ if add_keys:
+ # Pull in the Chrome revision number.
+ VERSION_TOOL = os.path.join(TOP, 'build/util/version.py')
+ LASTCHANGE_FILE = os.path.join(TOP, 'build/util/LASTCHANGE')
+ (stdout, retval) = _GetOutput([VERSION_TOOL, '-f', LASTCHANGE_FILE, '-t',
+ '@LASTCHANGE@'])
+ if retval:
+ return False
+ scm_revision = stdout.rstrip()
+
+ # See if the operation failed.
+ _RemoveKeys(plist, 'SCMRevision')
+ if scm_revision != None:
+ plist['SCMRevision'] = scm_revision
+ elif add_keys:
+ print >>sys.stderr, 'Could not determine SCM revision. This may be OK.'
+
+ return True
+
+
+def _AddBreakpadKeys(plist, branding):
+ """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and
+ also requires the |branding| argument."""
+ plist['BreakpadReportInterval'] = '3600' # Deliberately a string.
+ plist['BreakpadProduct'] = '%s_Mac' % branding
+ plist['BreakpadProductDisplay'] = branding
+ plist['BreakpadVersion'] = plist['CFBundleShortVersionString']
+ # These are both deliberately strings and not boolean.
+ plist['BreakpadSendAndExit'] = 'YES'
+ plist['BreakpadSkipConfirm'] = 'YES'
+
+
+def _RemoveBreakpadKeys(plist):
+ """Removes any set Breakpad keys."""
+ _RemoveKeys(plist,
+ 'BreakpadURL',
+ 'BreakpadReportInterval',
+ 'BreakpadProduct',
+ 'BreakpadProductDisplay',
+ 'BreakpadVersion',
+ 'BreakpadSendAndExit',
+ 'BreakpadSkipConfirm')
+
+
+def _TagSuffixes():
+ # Keep this list sorted in the order that tag suffix components are to
+ # appear in a tag value. That is to say, it should be sorted per ASCII.
+ components = ('32bit', 'full')
+ assert tuple(sorted(components)) == components
+
+ components_len = len(components)
+ combinations = 1 << components_len
+ tag_suffixes = []
+ for combination in xrange(0, combinations):
+ tag_suffix = ''
+ for component_index in xrange(0, components_len):
+ if combination & (1 << component_index):
+ tag_suffix += '-' + components[component_index]
+ tag_suffixes.append(tag_suffix)
+ return tag_suffixes
+
+
+def _AddKeystoneKeys(plist, bundle_identifier):
+ """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
+ also requires the |bundle_identifier| argument (com.example.product)."""
+ plist['KSVersion'] = plist['CFBundleShortVersionString']
+ plist['KSProductID'] = bundle_identifier
+ plist['KSUpdateURL'] = 'https://tools.google.com/service/update2'
+
+ _RemoveKeys(plist, 'KSChannelID')
+ for tag_suffix in _TagSuffixes():
+ if tag_suffix:
+ plist['KSChannelID' + tag_suffix] = tag_suffix
+
+
+def _RemoveKeystoneKeys(plist):
+ """Removes any set Keystone keys."""
+ _RemoveKeys(plist,
+ 'KSVersion',
+ 'KSProductID',
+ 'KSUpdateURL')
+
+ tag_keys = []
+ for tag_suffix in _TagSuffixes():
+ tag_keys.append('KSChannelID' + tag_suffix)
+ _RemoveKeys(plist, *tag_keys)
+
+
+def Main(argv):
+ parser = optparse.OptionParser('%prog [options]')
+ parser.add_option('--breakpad', dest='use_breakpad', action='store',
+ type='int', default=False, help='Enable Breakpad [1 or 0]')
+ parser.add_option('--breakpad_uploads', dest='breakpad_uploads',
+ action='store', type='int', default=False,
+ help='Enable Breakpad\'s uploading of crash dumps [1 or 0]')
+ parser.add_option('--keystone', dest='use_keystone', action='store',
+ type='int', default=False, help='Enable Keystone [1 or 0]')
+ parser.add_option('--scm', dest='add_scm_info', action='store', type='int',
+ default=True, help='Add SCM metadata [1 or 0]')
+ parser.add_option('--branding', dest='branding', action='store',
+ type='string', default=None, help='The branding of the binary')
+ parser.add_option('--bundle_id', dest='bundle_identifier',
+ action='store', type='string', default=None,
+ help='The bundle id of the binary')
+ parser.add_option('--version', dest='version', action='store', type='string',
+ default=None, help='The version string [major.minor.build.patch]')
+ (options, args) = parser.parse_args(argv)
+
+ if len(args) > 0:
+ print >>sys.stderr, parser.get_usage()
+ return 1
+
+ # Read the plist into its parsed format.
+ DEST_INFO_PLIST = os.path.join(env['TARGET_BUILD_DIR'], env['INFOPLIST_PATH'])
+ plist = plistlib.readPlist(DEST_INFO_PLIST)
+
+ # Insert the product version.
+ if not _AddVersionKeys(plist, version=options.version):
+ return 2
+
+ # Add Breakpad if configured to do so.
+ if options.use_breakpad:
+ if options.branding is None:
+ print >>sys.stderr, 'Use of Breakpad requires branding.'
+ return 1
+ _AddBreakpadKeys(plist, options.branding)
+ if options.breakpad_uploads:
+ plist['BreakpadURL'] = 'https://clients2.google.com/cr/report'
+ else:
+ # This allows crash dumping to a file without uploading the
+ # dump, for testing purposes. Breakpad does not recognise
+ # "none" as a special value, but this does stop crash dump
+ # uploading from happening. We need to specify something
+ # because if "BreakpadURL" is not present, Breakpad will not
+ # register its crash handler and no crash dumping will occur.
+ plist['BreakpadURL'] = 'none'
+ else:
+ _RemoveBreakpadKeys(plist)
+
+ # Only add Keystone in Release builds.
+ if options.use_keystone and env['CONFIGURATION'] == 'Release':
+ if options.bundle_identifier is None:
+ print >>sys.stderr, 'Use of Keystone requires the bundle id.'
+ return 1
+ _AddKeystoneKeys(plist, options.bundle_identifier)
+ else:
+ _RemoveKeystoneKeys(plist)
+
+ # Adds or removes any SCM keys.
+ if not _DoSCMKeys(plist, options.add_scm_info):
+ return 3
+
+ # Now that all keys have been mutated, rewrite the file.
+ temp_info_plist = tempfile.NamedTemporaryFile()
+ plistlib.writePlist(plist, temp_info_plist.name)
+
+ # Info.plist will work perfectly well in any plist format, but traditionally
+ # applications use xml1 for this, so convert it to ensure that it's valid.
+ proc = subprocess.Popen(['plutil', '-convert', 'xml1', '-o', DEST_INFO_PLIST,
+ temp_info_plist.name])
+ proc.wait()
+ return proc.returncode
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/build/mac/verify_no_objc.sh b/build/mac/verify_no_objc.sh
new file mode 100755
index 00000000000..e18a5eafb00
--- /dev/null
+++ b/build/mac/verify_no_objc.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Copyright (c) 2011 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 script makes sure that no __OBJC,__image_info section appears in the
+# executable file built by the Xcode target that runs the script. If such a
+# section appears, the script prints an error message and exits nonzero.
+#
+# Why is this important?
+#
+# On 10.5, there's a bug in CFBundlePreflightExecutable that causes it to
+# crash when operating in an executable that has not loaded at its default
+# address (that is, when it's a position-independent executable with the
+# MH_PIE bit set in its mach_header) and the executable has an
+# __OBJC,__image_info section. See http://crbug.com/88697.
+#
+# Chrome's main executables don't use any Objective-C at all, and don't need
+# to carry this section around. Not linking them as Objective-C when they
+# don't need it anyway saves about 4kB in the linked executable, although most
+# of that 4kB is just filled with zeroes.
+#
+# This script makes sure that nobody goofs and accidentally introduces these
+# sections into the main executables.
+
+set -eu
+
+executable="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}"
+
+if xcrun otool -arch i386 -o "${executable}" | grep -q '^Contents.*section$'; \
+then
+ echo "${0}: ${executable} has an __OBJC,__image_info section" 2>&1
+ exit 1
+fi
+
+if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
+ echo "${0}: otool failed" 2>&1
+ exit 1
+fi
+
+exit 0
diff --git a/build/module_args/dart.gni b/build/module_args/dart.gni
new file mode 100644
index 00000000000..ee6b0387670
--- /dev/null
+++ b/build/module_args/dart.gni
@@ -0,0 +1,6 @@
+# Copyright 2015 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 variable should point to the Dart SDK.
+dart_sdk_root = "//third_party/dart-sdk/dart-sdk"
diff --git a/build/module_args/mojo.gni b/build/module_args/mojo.gni
new file mode 100644
index 00000000000..fee9114a791
--- /dev/null
+++ b/build/module_args/mojo.gni
@@ -0,0 +1,16 @@
+# Copyright 2015 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 variable should point to the parent directory of the Mojo SDK.
+mojo_sdk_root = "//"
+
+# To build the Mojo shell from source, set this variable to true. To use the
+# prebuilt shell, omit this variable or set it to false. Note that the prebuilt
+# shell will be used only on platforms for which it is published (currently
+# Linux and Android).
+mojo_build_mojo_shell_from_source = true
+
+# To build the network service from source, set this variable to true. To use
+# the prebuilt network service, omit this variable or set it to false.
+mojo_build_network_service_from_source = true
diff --git a/build/module_args/nacl.gni b/build/module_args/nacl.gni
new file mode 100644
index 00000000000..61e0768e13a
--- /dev/null
+++ b/build/module_args/nacl.gni
@@ -0,0 +1,6 @@
+# Copyright 2015 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.
+
+# Override nacl's build directory.
+nacl_shared_build_dir = "//build"
diff --git a/build/module_args/v8.gni b/build/module_args/v8.gni
new file mode 100644
index 00000000000..8b5204c68ec
--- /dev/null
+++ b/build/module_args/v8.gni
@@ -0,0 +1,13 @@
+# Copyright 2014 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.
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+}
+
+# TODO(sky): nuke this. Temporary while sorting out http://crbug.com/465456.
+enable_correct_v8_arch = false
+
+v8_use_external_startup_data = !(is_chromeos || is_win)
+v8_extra_library_files = []
diff --git a/build/nocompile.gypi b/build/nocompile.gypi
new file mode 100644
index 00000000000..8c0f288b5a5
--- /dev/null
+++ b/build/nocompile.gypi
@@ -0,0 +1,96 @@
+# Copyright (c) 2011 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 file is meant to be included into an target to create a unittest that
+# invokes a set of no-compile tests. A no-compile test is a test that asserts
+# a particular construct will not compile.
+#
+# Also see:
+# http://dev.chromium.org/developers/testing/no-compile-tests
+#
+# To use this, create a gyp target with the following form:
+# {
+# 'target_name': 'my_module_nc_unittests',
+# 'type': 'executable',
+# 'sources': [
+# 'nc_testset_1.nc',
+# 'nc_testset_2.nc',
+# ],
+# 'includes': ['path/to/this/gypi/file'],
+# }
+#
+# The .nc files are C++ files that contain code we wish to assert will not
+# compile. Each individual test case in the file should be put in its own
+# #ifdef section. The expected output should be appended with a C++-style
+# comment that has a python list of regular expressions. This will likely
+# be greater than 80-characters. Giving a solid expected output test is
+# important so that random compile failures do not cause the test to pass.
+#
+# Example .nc file:
+#
+# #if defined(TEST_NEEDS_SEMICOLON) // [r"expected ',' or ';' at end of input"]
+#
+# int a = 1
+#
+# #elif defined(TEST_NEEDS_CAST) // [r"invalid conversion from 'void*' to 'char*'"]
+#
+# void* a = NULL;
+# char* b = a;
+#
+# #endif
+#
+# If we needed disable TEST_NEEDS_SEMICOLON, then change the define to:
+#
+# DISABLE_TEST_NEEDS_SEMICOLON
+# TEST_NEEDS_CAST
+#
+# The lines above are parsed by a regexp so avoid getting creative with the
+# formatting or ifdef logic; it will likely just not work.
+#
+# Implementation notes:
+# The .nc files are actually processed by a python script which executes the
+# compiler and generates a .cc file that is empty on success, or will have a
+# series of #error lines on failure, and a set of trivially passing gunit
+# TEST() functions on success. This allows us to fail at the compile step when
+# something goes wrong, and know during the unittest run that the test was at
+# least processed when things go right.
+
+{
+ # TODO(awong): Disabled until http://crbug.com/105388 is resolved.
+ 'sources/': [['exclude', '\\.nc$']],
+ 'conditions': [
+ [ 'OS!="win" and clang==1', {
+ 'rules': [
+ {
+ 'variables': {
+ 'nocompile_driver': '<(DEPTH)/tools/nocompile_driver.py',
+ 'nc_result_path': ('<(INTERMEDIATE_DIR)/<(module_dir)/'
+ '<(RULE_INPUT_ROOT)_nc.cc'),
+ },
+ 'rule_name': 'run_nocompile',
+ 'extension': 'nc',
+ 'inputs': [
+ '<(nocompile_driver)',
+ ],
+ 'outputs': [
+ '<(nc_result_path)'
+ ],
+ 'action': [
+ 'python',
+ '<(nocompile_driver)',
+ '4', # number of compilers to invoke in parallel.
+ '<(RULE_INPUT_PATH)',
+ '-Wall -Werror -Wfatal-errors -I<(DEPTH)',
+ '<(nc_result_path)',
+ ],
+ 'message': 'Generating no compile results for <(RULE_INPUT_PATH)',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+ }, {
+ 'sources/': [['exclude', '\\.nc$']]
+ }], # 'OS!="win" and clang=="1"'
+ ],
+}
+
diff --git a/build/output_dll_copy.rules b/build/output_dll_copy.rules
new file mode 100644
index 00000000000..c6e905131d4
--- /dev/null
+++ b/build/output_dll_copy.rules
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/build/precompile.cc b/build/precompile.cc
new file mode 100644
index 00000000000..db1ef6dfe55
--- /dev/null
+++ b/build/precompile.cc
@@ -0,0 +1,7 @@
+// Copyright (c) 2011 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.
+
+// Precompiled header generator for Windows builds. No include is needed
+// in this file as the PCH include is forced via the "Forced Include File"
+// flag in the projects generated by GYP.
diff --git a/build/precompile.h b/build/precompile.h
new file mode 100644
index 00000000000..32c2f11059a
--- /dev/null
+++ b/build/precompile.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+// Precompiled header for Chromium project on Windows, not used by
+// other build configurations. Using precompiled headers speeds the
+// build up significantly, around 1/4th on VS 2010 on an HP Z600 with 12
+// GB of memory.
+//
+// Numeric comments beside includes are the number of times they were
+// included under src/chrome/browser on 2011/8/20, which was used as a
+// baseline for deciding what to include in the PCH. Includes without
+// a numeric comment are generally included at least 5 times. It may
+// be possible to tweak the speed of the build by commenting out or
+// removing some of the less frequently used headers.
+
+#if defined(BUILD_PRECOMPILE_H_)
+#error You shouldn't include the precompiled header file more than once.
+#endif
+
+#define BUILD_PRECOMPILE_H_
+
+#define _USE_MATH_DEFINES
+
+// The Windows header needs to come before almost all the other
+// Windows-specific headers.
+#include
+#include
+#include
+#include // 2
+
+// Defines in atlbase.h cause conflicts; if we could figure out how
+// this family of headers can be included in the PCH, it might speed
+// up the build as several of them are used frequently.
+/*
+#include
+#include
+#include
+#include // 2
+#include // 2
+#include // 2
+#include // 1
+#include // 1
+#include // 2
+*/
+
+// Objbase.h and other files that rely on it bring in [ #define
+// interface struct ] which can cause problems in a multi-platform
+// build like Chrome's. #undef-ing it does not work as there are
+// currently 118 targets that break if we do this, so leaving out of
+// the precompiled header for now.
+//#include // 2
+//#include // 3
+//#include // 2
+//#include // 2
+//#include // 1
+//#include // 1
+//#include // 2
+//#include // 1
+//#include // 1
+//#include // 2
+//#include // 2
+//#include // 2
+//#include // 1
+//#include // 1
+//#include // 4
+//#include // 2
+
+// Caused other conflicts in addition to the 'interface' issue above.
+// #include
+
+#include
+#include
+#include // 4
+#include
+#include // 1
+#include
+#include // 1
+#include
+#include
+#include
+#include
+#include // 4
+
+#include
+#include // 3
+#include
+#include
+#include // 3
+#include // 2
+#include
+#include
+#include // 3
+#include
+#include