From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- third_party/libwebrtc/build/.gitignore | 29 + third_party/libwebrtc/build/.style.yapf | 6 + third_party/libwebrtc/build/BUILD.gn | 62 + third_party/libwebrtc/build/DIR_METADATA | 5 + third_party/libwebrtc/build/OWNERS | 26 + third_party/libwebrtc/build/OWNERS.setnoparent | 70 + third_party/libwebrtc/build/OWNERS.status | 12 + third_party/libwebrtc/build/README.md | 36 + third_party/libwebrtc/build/add_rts_filters.py | 47 + .../libwebrtc/build/android/AndroidManifest.xml | 18 + third_party/libwebrtc/build/android/BUILD.gn | 167 + .../build/android/CheckInstallApk-debug.apk | Bin 0 -> 37106 bytes third_party/libwebrtc/build/android/DIR_METADATA | 1 + third_party/libwebrtc/build/android/OWNERS | 7 + third_party/libwebrtc/build/android/PRESUBMIT.py | 129 + .../build/android/adb_chrome_public_command_line | 16 + .../libwebrtc/build/android/adb_command_line.py | 98 + third_party/libwebrtc/build/android/adb_gdb | 1000 ++++ .../libwebrtc/build/android/adb_install_apk.py | 134 + .../libwebrtc/build/android/adb_logcat_monitor.py | 158 + .../libwebrtc/build/android/adb_logcat_printer.py | 222 + .../libwebrtc/build/android/adb_profile_chrome | 9 + .../build/android/adb_profile_chrome_startup | 9 + .../build/android/adb_reverse_forwarder.py | 87 + .../build/android/adb_system_webview_command_line | 16 + .../android/android_only_explicit_jni_exports.lst | 13 + .../build/android/android_only_jni_exports.lst | 13 + .../libwebrtc/build/android/apk_operations.py | 1944 ++++++++ .../libwebrtc/build/android/apk_operations.pydeps | 113 + .../build/android/apply_shared_preference_file.py | 50 + .../libwebrtc/build/android/asan_symbolize.py | 151 + .../libwebrtc/build/android/bytecode/BUILD.gn | 86 + .../org/chromium/bytecode/ByteCodeProcessor.java | 167 + .../org/chromium/bytecode/ByteCodeRewriter.java | 101 + .../org/chromium/bytecode/ClassPathValidator.java | 233 + .../EmptyOverrideGeneratorClassAdapter.java | 103 + .../bytecode/FragmentActivityReplacer.java | 238 + .../bytecode/MethodCheckerClassAdapter.java | 136 + .../org/chromium/bytecode/MethodDescription.java | 20 + .../bytecode/ParentMethodCheckerClassAdapter.java | 94 + .../org/chromium/bytecode/TraceEventAdder.java | 87 + .../bytecode/TraceEventAdderClassAdapter.java | 47 + .../bytecode/TraceEventAdderMethodAdapter.java | 83 + .../java/org/chromium/bytecode/TypeUtils.java | 87 + .../build/android/chromium-debug.keystore | Bin 0 -> 2223 bytes .../libwebrtc/build/android/convert_dex_profile.py | 568 +++ .../build/android/convert_dex_profile_tests.py | 277 ++ .../libwebrtc/build/android/dcheck_is_off.flags | 12 + .../libwebrtc/build/android/devil_chromium.json | 120 + .../libwebrtc/build/android/devil_chromium.py | 200 + .../libwebrtc/build/android/devil_chromium.pydeps | 39 + .../libwebrtc/build/android/diff_resource_sizes.py | 200 + third_party/libwebrtc/build/android/docs/README.md | 16 + .../libwebrtc/build/android/docs/build_config.md | 168 + .../android/docs/class_verification_failures.md | 286 ++ .../libwebrtc/build/android/docs/coverage.md | 83 + .../build/android/docs/java_optimization.md | 149 + .../libwebrtc/build/android/docs/java_toolchain.md | 284 ++ .../build/android/docs/life_of_a_resource.md | 289 ++ third_party/libwebrtc/build/android/docs/lint.md | 140 + .../libwebrtc/build/android/download_doclava.py | 32 + .../build/android/dump_apk_resource_strings.py | 659 +++ .../libwebrtc/build/android/emma_coverage_stats.py | 483 ++ .../build/android/emma_coverage_stats_test.py | 593 +++ third_party/libwebrtc/build/android/envsetup.sh | 35 + .../build/android/fast_local_dev_server.py | 314 ++ .../build/android/generate_jacoco_report.py | 274 ++ .../build/android/gradle/AndroidManifest.xml | 14 + third_party/libwebrtc/build/android/gradle/OWNERS | 2 + .../libwebrtc/build/android/gradle/android.jinja | 114 + .../libwebrtc/build/android/gradle/cmake.jinja | 25 + .../build/android/gradle/dependencies.jinja | 28 + .../build/android/gradle/generate_gradle.py | 930 ++++ .../libwebrtc/build/android/gradle/gn_to_cmake.py | 689 +++ .../libwebrtc/build/android/gradle/java.jinja | 41 + .../libwebrtc/build/android/gradle/manifest.jinja | 7 + .../libwebrtc/build/android/gradle/root.jinja | 26 + .../libwebrtc/build/android/gtest_apk/BUILD.gn | 15 + .../NativeTestInstrumentationTestRunner.java | 281 ++ .../chromium/build/gtest_apk/NativeTestIntent.java | 22 + .../chromium/build/gtest_apk/TestStatusIntent.java | 21 + .../build/gtest_apk/TestStatusReceiver.java | 89 + third_party/libwebrtc/build/android/gyp/OWNERS | 4 + third_party/libwebrtc/build/android/gyp/aar.py | 210 + third_party/libwebrtc/build/android/gyp/aar.pydeps | 6 + third_party/libwebrtc/build/android/gyp/aidl.py | 64 + .../libwebrtc/build/android/gyp/aidl.pydeps | 6 + .../build/android/gyp/allot_native_libraries.py | 185 + .../android/gyp/allot_native_libraries.pydeps | 6 + .../libwebrtc/build/android/gyp/apkbuilder.py | 561 +++ .../libwebrtc/build/android/gyp/apkbuilder.pydeps | 9 + .../android/gyp/assert_static_initializers.py | 187 + .../android/gyp/assert_static_initializers.pydeps | 6 + .../libwebrtc/build/android/gyp/bundletool.py | 46 + .../build/android/gyp/bytecode_processor.py | 78 + .../build/android/gyp/bytecode_processor.pydeps | 7 + .../build/android/gyp/bytecode_rewriter.py | 37 + .../build/android/gyp/bytecode_rewriter.pydeps | 6 + .../build/android/gyp/check_flag_expectations.py | 132 + .../android/gyp/check_flag_expectations.pydeps | 7 + .../libwebrtc/build/android/gyp/compile_java.py | 787 +++ .../build/android/gyp/compile_java.pydeps | 30 + .../build/android/gyp/compile_resources.py | 1032 ++++ .../build/android/gyp/compile_resources.pydeps | 39 + third_party/libwebrtc/build/android/gyp/copy_ex.py | 129 + .../libwebrtc/build/android/gyp/copy_ex.pydeps | 6 + .../android/gyp/create_apk_operations_script.py | 88 + .../gyp/create_apk_operations_script.pydeps | 6 + .../build/android/gyp/create_app_bundle.py | 543 ++ .../build/android/gyp/create_app_bundle.pydeps | 49 + .../build/android/gyp/create_app_bundle_apks.py | 53 + .../android/gyp/create_app_bundle_apks.pydeps | 37 + .../android/gyp/create_bundle_wrapper_script.py | 122 + .../gyp/create_bundle_wrapper_script.pydeps | 6 + .../build/android/gyp/create_java_binary_script.py | 120 + .../android/gyp/create_java_binary_script.pydeps | 6 + .../libwebrtc/build/android/gyp/create_r_java.py | 62 + .../build/android/gyp/create_r_java.pydeps | 31 + .../libwebrtc/build/android/gyp/create_r_txt.py | 31 + .../build/android/gyp/create_r_txt.pydeps | 32 + .../build/android/gyp/create_size_info_files.py | 195 + .../android/gyp/create_size_info_files.pydeps | 7 + .../android/gyp/create_ui_locale_resources.py | 87 + .../android/gyp/create_ui_locale_resources.pydeps | 31 + third_party/libwebrtc/build/android/gyp/desugar.py | 67 + .../libwebrtc/build/android/gyp/desugar.pydeps | 6 + third_party/libwebrtc/build/android/gyp/dex.py | 650 +++ third_party/libwebrtc/build/android/gyp/dex.pydeps | 10 + .../libwebrtc/build/android/gyp/dex_jdk_libs.py | 93 + .../build/android/gyp/dex_jdk_libs.pydeps | 6 + .../libwebrtc/build/android/gyp/dexsplitter.py | 132 + .../libwebrtc/build/android/gyp/dexsplitter.pydeps | 6 + .../libwebrtc/build/android/gyp/dist_aar.py | 159 + .../libwebrtc/build/android/gyp/dist_aar.pydeps | 7 + .../build/android/gyp/extract_unwind_tables.py | 283 ++ .../android/gyp/extract_unwind_tables_tests.py | 120 + .../libwebrtc/build/android/gyp/filter_zip.py | 65 + .../libwebrtc/build/android/gyp/filter_zip.pydeps | 6 + .../libwebrtc/build/android/gyp/finalize_apk.py | 78 + third_party/libwebrtc/build/android/gyp/find.py | 33 + .../libwebrtc/build/android/gyp/gcc_preprocess.py | 63 + .../build/android/gyp/gcc_preprocess.pydeps | 6 + .../build/android/gyp/generate_android_wrapper.py | 42 + .../android/gyp/generate_linker_version_script.py | 82 + .../gyp/generate_linker_version_script.pydeps | 6 + third_party/libwebrtc/build/android/gyp/ijar.py | 34 + .../libwebrtc/build/android/gyp/ijar.pydeps | 6 + .../libwebrtc/build/android/gyp/jacoco_instr.py | 242 + .../build/android/gyp/jacoco_instr.pydeps | 6 + .../libwebrtc/build/android/gyp/java_cpp_enum.py | 437 ++ .../build/android/gyp/java_cpp_enum.pydeps | 7 + .../build/android/gyp/java_cpp_enum_tests.py | 783 +++ .../build/android/gyp/java_cpp_features.py | 110 + .../build/android/gyp/java_cpp_features.pydeps | 7 + .../build/android/gyp/java_cpp_features_tests.py | 198 + .../build/android/gyp/java_cpp_strings.py | 103 + .../build/android/gyp/java_cpp_strings.pydeps | 7 + .../build/android/gyp/java_cpp_strings_tests.py | 151 + .../build/android/gyp/java_google_api_keys.py | 123 + .../build/android/gyp/java_google_api_keys.pydeps | 7 + .../android/gyp/java_google_api_keys_tests.py | 42 + .../build/android/gyp/javac_output_processor.py | 198 + .../libwebrtc/build/android/gyp/jetify_jar.py | 68 + .../libwebrtc/build/android/gyp/jetify_jar.pydeps | 6 + .../libwebrtc/build/android/gyp/jinja_template.py | 160 + .../build/android/gyp/jinja_template.pydeps | 43 + third_party/libwebrtc/build/android/gyp/lint.py | 494 ++ .../libwebrtc/build/android/gyp/lint.pydeps | 8 + .../libwebrtc/build/android/gyp/merge_manifest.py | 149 + .../build/android/gyp/merge_manifest.pydeps | 7 + .../build/android/gyp/native_libraries_template.py | 39 + .../libwebrtc/build/android/gyp/nocompile_test.py | 212 + .../build/android/gyp/optimize_resources.py | 151 + .../build/android/gyp/optimize_resources.pydeps | 0 .../build/android/gyp/prepare_resources.py | 207 + .../build/android/gyp/prepare_resources.pydeps | 35 + .../build/android/gyp/process_native_prebuilt.py | 38 + .../android/gyp/process_native_prebuilt.pydeps | 6 + .../libwebrtc/build/android/gyp/proguard.py | 710 +++ .../libwebrtc/build/android/gyp/proguard.pydeps | 16 + .../build/android/gyp/proto/Configuration_pb2.py | 697 +++ .../libwebrtc/build/android/gyp/proto/README.md | 13 + .../build/android/gyp/proto/Resources_pb2.py | 2779 +++++++++++ .../libwebrtc/build/android/gyp/proto/__init__.py | 0 .../libwebrtc/build/android/gyp/test/BUILD.gn | 11 + .../org/chromium/helloworld/HelloWorldMain.java | 15 + .../org/chromium/helloworld/HelloWorldPrinter.java | 12 + third_party/libwebrtc/build/android/gyp/turbine.py | 170 + .../libwebrtc/build/android/gyp/turbine.pydeps | 6 + .../build/android/gyp/unused_resources.py | 89 + .../build/android/gyp/unused_resources.pydeps | 31 + .../libwebrtc/build/android/gyp/util/__init__.py | 3 + .../build/android/gyp/util/build_utils.py | 725 +++ .../build/android/gyp/util/build_utils_test.py | 48 + .../libwebrtc/build/android/gyp/util/diff_utils.py | 127 + .../build/android/gyp/util/jar_info_utils.py | 59 + .../build/android/gyp/util/java_cpp_utils.py | 194 + .../build/android/gyp/util/manifest_utils.py | 321 ++ .../build/android/gyp/util/manifest_utils_test.py | 128 + .../libwebrtc/build/android/gyp/util/md5_check.py | 471 ++ .../build/android/gyp/util/md5_check_test.py | 178 + .../libwebrtc/build/android/gyp/util/parallel.py | 214 + .../build/android/gyp/util/protoresources.py | 308 ++ .../build/android/gyp/util/resource_utils.py | 1078 ++++ .../build/android/gyp/util/resource_utils_test.py | 275 ++ .../build/android/gyp/util/resources_parser.py | 142 + .../build/android/gyp/util/server_utils.py | 41 + .../libwebrtc/build/android/gyp/util/zipalign.py | 97 + .../gyp/validate_static_library_dex_references.py | 93 + .../validate_static_library_dex_references.pydeps | 9 + .../build/android/gyp/write_build_config.py | 2091 ++++++++ .../build/android/gyp/write_build_config.pydeps | 31 + .../android/gyp/write_native_libraries_java.py | 130 + .../android/gyp/write_native_libraries_java.pydeps | 6 + third_party/libwebrtc/build/android/gyp/zip.py | 72 + third_party/libwebrtc/build/android/gyp/zip.pydeps | 6 + .../libwebrtc/build/android/host_heartbeat.py | 36 + .../build/android/incremental_install/BUILD.gn | 23 + .../build/android/incremental_install/README.md | 83 + .../build/android/incremental_install/__init__.py | 3 + .../generate_android_manifest.py | 141 + .../generate_android_manifest.pydeps | 32 + .../build/android/incremental_install/installer.py | 372 ++ .../incrementalinstall/BootstrapApplication.java | 297 ++ .../BootstrapInstrumentation.java | 25 + .../incrementalinstall/ClassLoaderPatcher.java | 312 ++ .../org/chromium/incrementalinstall/LockFile.java | 129 + .../org/chromium/incrementalinstall/Reflect.java | 142 + .../incrementalinstall/SecondInstrumentation.java | 12 + .../incremental_install/write_installer_json.py | 68 + .../write_installer_json.pydeps | 6 + .../android/java/templates/BuildConfig.template | 95 + .../android/java/templates/ProductConfig.template | 34 + .../android/java/test/DefaultLocaleLintTest.java | 17 + .../android/java/test/IncrementalJavacTest.java | 32 + .../build/android/java/test/NewApiLintTest.java | 17 + ...natureChangeIncrementalJavacTestHelper.template | 18 + ...SignatureChangeIncrementalJavacTestHelper2.java | 11 + .../build/android/java/test/missing_symbol/B.java | 9 + .../android/java/test/missing_symbol/D.template | 9 + .../java/test/missing_symbol/Importer.template | 13 + .../java/test/missing_symbol/ImportsSubB.java | 13 + .../build/android/java/test/missing_symbol/c.jar | Bin 0 -> 393 bytes .../missing_symbol/sub/BInMethodSignature.java | 13 + .../android/java/test/missing_symbol/sub/SubB.java | 9 + .../libwebrtc/build/android/lighttpd_server.py | 264 + .../android/list_class_verification_failures.py | 282 ++ .../list_class_verification_failures_test.py | 237 + .../libwebrtc/build/android/list_java_targets.py | 221 + .../libwebrtc/build/android/main_dex_classes.flags | 52 + .../libwebrtc/build/android/method_count.py | 118 + .../libwebrtc/build/android/native_flags/BUILD.gn | 37 + .../build/android/native_flags/argcapture.py | 17 + .../libwebrtc/build/android/native_flags/empty.cc | 5 + .../libwebrtc/build/android/provision_devices.py | 563 +++ .../libwebrtc/build/android/pylib/__init__.py | 45 + .../build/android/pylib/android/__init__.py | 3 + .../android/pylib/android/logcat_symbolizer.py | 99 + .../libwebrtc/build/android/pylib/base/__init__.py | 3 + .../build/android/pylib/base/base_test_result.py | 276 ++ .../pylib/base/base_test_result_unittest.py | 83 + .../build/android/pylib/base/environment.py | 49 + .../android/pylib/base/environment_factory.py | 34 + .../build/android/pylib/base/mock_environment.py | 11 + .../build/android/pylib/base/mock_test_instance.py | 11 + .../build/android/pylib/base/output_manager.py | 159 + .../android/pylib/base/output_manager_factory.py | 18 + .../android/pylib/base/output_manager_test_case.py | 15 + .../build/android/pylib/base/test_collection.py | 81 + .../build/android/pylib/base/test_exception.py | 8 + .../build/android/pylib/base/test_instance.py | 40 + .../android/pylib/base/test_instance_factory.py | 26 + .../libwebrtc/build/android/pylib/base/test_run.py | 50 + .../build/android/pylib/base/test_run_factory.py | 36 + .../build/android/pylib/base/test_server.py | 18 + .../build/android/pylib/constants/__init__.py | 288 ++ .../build/android/pylib/constants/host_paths.py | 97 + .../android/pylib/constants/host_paths_unittest.py | 51 + .../build/android/pylib/content_settings.py | 80 + .../build/android/pylib/device/__init__.py | 0 .../build/android/pylib/device/commands/BUILD.gn | 20 + .../org/chromium/android/commands/unzip/Unzip.java | 93 + .../build/android/pylib/device_settings.py | 201 + .../libwebrtc/build/android/pylib/dex/__init__.py | 3 + .../build/android/pylib/dex/dex_parser.py | 551 +++ .../build/android/pylib/gtest/__init__.py | 3 + .../build/android/pylib/gtest/filter/OWNERS | 1 + .../pylib/gtest/filter/base_unittests_disabled | 25 + .../base_unittests_emulator_additional_disabled | 10 + .../pylib/gtest/filter/breakpad_unittests_disabled | 9 + .../gtest/filter/content_browsertests_disabled | 45 + .../android/pylib/gtest/filter/unit_tests_disabled | 74 + .../build/android/pylib/gtest/gtest_config.py | 57 + .../android/pylib/gtest/gtest_test_instance.py | 627 +++ .../pylib/gtest/gtest_test_instance_test.py | 348 ++ .../android/pylib/instrumentation/__init__.py | 3 + .../instrumentation/instrumentation_parser.py | 112 + .../instrumentation/instrumentation_parser_test.py | 135 + .../instrumentation_test_instance.py | 1171 +++++ .../instrumentation_test_instance_test.py | 1250 +++++ .../pylib/instrumentation/json_perf_parser.py | 162 + .../pylib/instrumentation/render_test.html.jinja | 40 + .../android/pylib/instrumentation/test_result.py | 33 + .../build/android/pylib/junit/__init__.py | 3 + .../android/pylib/junit/junit_test_instance.py | 76 + .../build/android/pylib/local/__init__.py | 3 + .../build/android/pylib/local/device/__init__.py | 3 + .../pylib/local/device/local_device_environment.py | 328 ++ .../pylib/local/device/local_device_gtest_run.py | 896 ++++ .../local/device/local_device_gtest_run_test.py | 79 + .../local_device_instrumentation_test_run.py | 1512 ++++++ .../local_device_instrumentation_test_run_test.py | 169 + .../local/device/local_device_monkey_test_run.py | 128 + .../pylib/local/device/local_device_test_run.py | 395 ++ .../local/device/local_device_test_run_test.py | 171 + .../build/android/pylib/local/emulator/OWNERS | 4 + .../build/android/pylib/local/emulator/__init__.py | 3 + .../build/android/pylib/local/emulator/avd.py | 629 +++ .../build/android/pylib/local/emulator/ini.py | 58 + .../build/android/pylib/local/emulator/ini_test.py | 69 + .../local/emulator/local_emulator_environment.py | 102 + .../android/pylib/local/emulator/proto/__init__.py | 3 + .../android/pylib/local/emulator/proto/avd.proto | 75 + .../android/pylib/local/emulator/proto/avd_pb2.py | 362 ++ .../pylib/local/local_test_server_spawner.py | 101 + .../build/android/pylib/local/machine/__init__.py | 3 + .../local/machine/local_machine_environment.py | 25 + .../local/machine/local_machine_junit_test_run.py | 309 ++ .../machine/local_machine_junit_test_run_test.py | 89 + .../build/android/pylib/monkey/__init__.py | 0 .../android/pylib/monkey/monkey_test_instance.py | 73 + .../build/android/pylib/output/__init__.py | 3 + .../android/pylib/output/local_output_manager.py | 49 + .../pylib/output/local_output_manager_test.py | 34 + .../android/pylib/output/noop_output_manager.py | 42 + .../pylib/output/noop_output_manager_test.py | 27 + .../android/pylib/output/remote_output_manager.py | 89 + .../pylib/output/remote_output_manager_test.py | 32 + .../libwebrtc/build/android/pylib/pexpect.py | 21 + .../libwebrtc/build/android/pylib/restart_adbd.sh | 20 + .../build/android/pylib/results/__init__.py | 3 + .../pylib/results/flakiness_dashboard/__init__.py | 3 + .../flakiness_dashboard/json_results_generator.py | 702 +++ .../json_results_generator_unittest.py | 213 + .../flakiness_dashboard/results_uploader.py | 176 + .../build/android/pylib/results/json_results.py | 239 + .../android/pylib/results/json_results_test.py | 311 ++ .../android/pylib/results/presentation/__init__.py | 3 + .../results/presentation/javascript/main_html.js | 193 + .../results/presentation/standard_gtest_merge.py | 173 + .../pylib/results/presentation/template/main.html | 93 + .../pylib/results/presentation/template/table.html | 60 + .../presentation/test_results_presentation.py | 549 +++ .../build/android/pylib/results/report_results.py | 136 + .../build/android/pylib/symbols/__init__.py | 0 .../build/android/pylib/symbols/apk_lib_dump.py | 61 + .../build/android/pylib/symbols/apk_native_libs.py | 419 ++ .../pylib/symbols/apk_native_libs_unittest.py | 397 ++ .../build/android/pylib/symbols/deobfuscator.py | 178 + .../build/android/pylib/symbols/elf_symbolizer.py | 497 ++ .../pylib/symbols/elf_symbolizer_unittest.py | 196 + .../pylib/symbols/mock_addr2line/__init__.py | 0 .../pylib/symbols/mock_addr2line/mock_addr2line | 81 + .../android/pylib/symbols/stack_symbolizer.py | 86 + .../build/android/pylib/symbols/symbol_utils.py | 813 +++ .../android/pylib/symbols/symbol_utils_unittest.py | 942 ++++ .../build/android/pylib/utils/__init__.py | 0 .../build/android/pylib/utils/app_bundle_utils.py | 169 + .../build/android/pylib/utils/argparse_utils.py | 52 + .../android/pylib/utils/chrome_proxy_utils.py | 171 + .../android/pylib/utils/chrome_proxy_utils_test.py | 235 + .../build/android/pylib/utils/decorators.py | 37 + .../build/android/pylib/utils/decorators_test.py | 104 + .../android/pylib/utils/device_dependencies.py | 136 + .../pylib/utils/device_dependencies_test.py | 52 + .../libwebrtc/build/android/pylib/utils/dexdump.py | 136 + .../build/android/pylib/utils/dexdump_test.py | 141 + .../build/android/pylib/utils/gold_utils.py | 78 + .../build/android/pylib/utils/gold_utils_test.py | 123 + .../android/pylib/utils/google_storage_helper.py | 129 + .../android/pylib/utils/instrumentation_tracing.py | 204 + .../build/android/pylib/utils/local_utils.py | 19 + .../build/android/pylib/utils/logdog_helper.py | 96 + .../build/android/pylib/utils/logging_utils.py | 136 + .../build/android/pylib/utils/maven_downloader.py | 140 + .../build/android/pylib/utils/proguard.py | 285 ++ .../build/android/pylib/utils/proguard_test.py | 495 ++ .../build/android/pylib/utils/repo_utils.py | 22 + .../android/pylib/utils/shared_preference_utils.py | 116 + .../build/android/pylib/utils/simpleperf.py | 260 + .../build/android/pylib/utils/test_filter.py | 148 + .../build/android/pylib/utils/test_filter_test.py | 247 + .../build/android/pylib/utils/time_profile.py | 45 + .../libwebrtc/build/android/pylib/utils/xvfb.py | 58 + .../build/android/pylib/valgrind_tools.py | 116 + third_party/libwebrtc/build/android/pylintrc | 15 + .../libwebrtc/build/android/resource_sizes.gni | 100 + .../libwebrtc/build/android/resource_sizes.py | 910 ++++ .../libwebrtc/build/android/resource_sizes.pydeps | 58 + third_party/libwebrtc/build/android/screenshot.py | 13 + .../libwebrtc/build/android/stacktrace/BUILD.gn | 28 + .../libwebrtc/build/android/stacktrace/README.md | 28 + .../android/stacktrace/crashpad_stackwalker.py | 175 + .../java/org/chromium/build/FlushingReTrace.java | 116 + .../build/android/stacktrace/java_deobfuscate.jar | Bin 0 -> 3113 bytes .../build/android/stacktrace/java_deobfuscate.py | 39 + .../android/stacktrace/java_deobfuscate_test.py | 172 + .../build/android/stacktrace/stackwalker.py | 137 + third_party/libwebrtc/build/android/test/BUILD.gn | 95 + .../android/test/incremental_javac_gn/BUILD.gn | 98 + .../incremental_javac_test_android_library.py | 153 + .../build/android/test/missing_symbol_test.gni | 57 + .../build/android/test/nocompile_gn/BUILD.gn | 101 + .../test/nocompile_gn/nocompile_sources.gni | 14 + third_party/libwebrtc/build/android/test_runner.py | 1194 +++++ .../libwebrtc/build/android/test_runner.pydeps | 229 + .../build/android/test_wrapper/logdog_wrapper.py | 145 + .../android/test_wrapper/logdog_wrapper.pydeps | 12 + .../build/android/tests/symbolize/Makefile | 11 + .../libwebrtc/build/android/tests/symbolize/a.cc | 14 + .../libwebrtc/build/android/tests/symbolize/b.cc | 14 + .../build/android/tests/symbolize/liba.so | Bin 0 -> 6908 bytes .../build/android/tests/symbolize/libb.so | Bin 0 -> 6896 bytes third_party/libwebrtc/build/android/tombstones.py | 280 ++ .../build/android/unused_resources/BUILD.gn | 19 + .../android/unused_resources/UnusedResources.java | 594 +++ .../android/update_deps/update_third_party_deps.py | 142 + .../libwebrtc/build/android/update_verification.py | 116 + .../libwebrtc/build/android/video_recorder.py | 13 + third_party/libwebrtc/build/apple/OWNERS | 4 + third_party/libwebrtc/build/apple/README.md | 12 + .../libwebrtc/build/apple/apple_info_plist.gni | 60 + .../libwebrtc/build/apple/compile_entitlements.gni | 51 + .../libwebrtc/build/apple/compile_plist.gni | 76 + .../libwebrtc/build/apple/convert_plist.gni | 41 + third_party/libwebrtc/build/apple/plist_util.py | 265 + .../libwebrtc/build/apple/tweak_info_plist.gni | 86 + .../libwebrtc/build/apple/tweak_info_plist.py | 447 ++ .../libwebrtc/build/apple/write_pkg_info.py | 54 + third_party/libwebrtc/build/apple/xcrun.py | 52 + third_party/libwebrtc/build/args/OWNERS | 1 + third_party/libwebrtc/build/args/README.txt | 31 + .../libwebrtc/build/args/chromeos/README.md | 52 + third_party/libwebrtc/build/args/headless.gn | 58 + third_party/libwebrtc/build/build-ctags.sh | 49 + third_party/libwebrtc/build/build_config.h | 245 + third_party/libwebrtc/build/buildflag.h | 47 + third_party/libwebrtc/build/buildflag_header.gni | 137 + third_party/libwebrtc/build/check_gn_headers.py | 310 ++ .../libwebrtc/build/check_gn_headers_unittest.py | 101 + .../libwebrtc/build/check_gn_headers_whitelist.txt | 264 + third_party/libwebrtc/build/check_return_value.py | 19 + third_party/libwebrtc/build/chromeos/.style.yapf | 2 + third_party/libwebrtc/build/chromeos/OWNERS | 1 + third_party/libwebrtc/build/chromeos/PRESUBMIT.py | 34 + .../libwebrtc/build/chromeos/gen_skylab_runner.py | 108 + third_party/libwebrtc/build/chromeos/pylintrc | 15 + .../libwebrtc/build/chromeos/test_runner.py | 1044 ++++ .../libwebrtc/build/chromeos/test_runner_test.py | 352 ++ third_party/libwebrtc/build/ciopfs.sha1 | 1 + third_party/libwebrtc/build/cipd/cipd.gni | 140 + third_party/libwebrtc/build/cipd/cipd_from_file.py | 65 + .../libwebrtc/build/cipd/clobber_cipd_root.py | 33 + third_party/libwebrtc/build/clobber.py | 134 + third_party/libwebrtc/build/compiled_action.gni | 167 + .../libwebrtc/build/compute_build_timestamp.py | 126 + third_party/libwebrtc/build/config/BUILD.gn | 391 ++ third_party/libwebrtc/build/config/BUILDCONFIG.gn | 588 +++ third_party/libwebrtc/build/config/OWNERS | 5 + third_party/libwebrtc/build/config/aix/BUILD.gn | 61 + .../libwebrtc/build/config/android/BUILD.gn | 161 + third_party/libwebrtc/build/config/android/OWNERS | 1 + third_party/libwebrtc/build/config/android/abi.gni | 101 + .../build/config/android/android_nocompile.gni | 113 + .../libwebrtc/build/config/android/build_vars.gni | 29 + .../libwebrtc/build/config/android/channel.gni | 14 + .../libwebrtc/build/config/android/config.gni | 353 ++ .../libwebrtc/build/config/android/copy_ex.gni | 72 + .../build/config/android/extract_unwind_tables.gni | 44 + .../build/config/android/internal_rules.gni | 4368 +++++++++++++++++ .../build/config/android/linker_version_script.gni | 43 + .../libwebrtc/build/config/android/rules.gni | 5183 ++++++++++++++++++++ third_party/libwebrtc/build/config/android/sdk.gni | 10 + .../config/android/test/classpath_order/BUILD.gn | 111 + .../java/res_template/values/values.xml | 9 + .../build/classpath_order/ClassPathOrderTest.java | 32 + .../build/classpath_order/Dummy.java.jinja2 | 8 + .../build/config/android/test/proto/BUILD.gn | 103 + .../test/proto/absolute_dep/absolute_dep.proto | 10 + .../test/proto/relative_dep/relative_dep.proto | 10 + .../android/test/proto/root/absolute_child.proto | 10 + .../android/test/proto/root/absolute_root.proto | 18 + .../android/test/proto/root/relative_child.proto | 10 + .../android/test/proto/root/relative_root.proto | 18 + .../config/android/test/resource_overlay/BUILD.gn | 60 + .../java/res_template/values/values.xml | 10 + .../resource_overlay/ResourceOverlayTest.java | 49 + third_party/libwebrtc/build/config/apple/OWNERS | 1 + .../libwebrtc/build/config/apple/sdk_info.py | 177 + .../libwebrtc/build/config/apple/symbols.gni | 30 + third_party/libwebrtc/build/config/arm.gni | 136 + .../build/config/buildflags_paint_preview.gni | 16 + third_party/libwebrtc/build/config/c++/BUILD.gn | 145 + third_party/libwebrtc/build/config/c++/c++.gni | 81 + .../libwebrtc/build/config/c++/libc++.natvis | 435 ++ .../libwebrtc/build/config/chrome_build.gni | 25 + .../libwebrtc/build/config/chromecast/BUILD.gn | 95 + .../libwebrtc/build/config/chromecast_build.gni | 121 + .../libwebrtc/build/config/chromeos/BUILD.gn | 34 + .../libwebrtc/build/config/chromeos/args.gni | 30 + .../libwebrtc/build/config/chromeos/rules.gni | 510 ++ .../libwebrtc/build/config/chromeos/ui_mode.gni | 40 + third_party/libwebrtc/build/config/clang/BUILD.gn | 59 + third_party/libwebrtc/build/config/clang/clang.gni | 18 + .../libwebrtc/build/config/compiler/BUILD.gn | 2648 ++++++++++ .../libwebrtc/build/config/compiler/compiler.gni | 324 ++ .../libwebrtc/build/config/compiler/pgo/BUILD.gn | 100 + .../libwebrtc/build/config/compiler/pgo/pgo.gni | 25 + .../build/config/compute_inputs_for_analyze.gni | 14 + .../libwebrtc/build/config/coverage/BUILD.gn | 34 + third_party/libwebrtc/build/config/coverage/OWNERS | 3 + .../libwebrtc/build/config/coverage/coverage.gni | 40 + third_party/libwebrtc/build/config/crypto.gni | 15 + .../libwebrtc/build/config/dcheck_always_on.gni | 40 + third_party/libwebrtc/build/config/devtools.gni | 37 + third_party/libwebrtc/build/config/features.gni | 40 + .../libwebrtc/build/config/freetype/BUILD.gn | 14 + third_party/libwebrtc/build/config/freetype/OWNERS | 2 + .../libwebrtc/build/config/freetype/freetype.gni | 14 + .../libwebrtc/build/config/fuchsia/BUILD.gn | 30 + .../libwebrtc/build/config/fuchsia/DIR_METADATA | 7 + third_party/libwebrtc/build/config/fuchsia/OWNERS | 4 + .../config/fuchsia/build_cmx_from_fragment.py | 49 + .../build/config/fuchsia/build_symbol_archive.py | 62 + .../libwebrtc/build/config/fuchsia/config.gni | 11 + .../libwebrtc/build/config/fuchsia/extend_fvm.py | 26 + .../config/fuchsia/generate_runner_scripts.gni | 284 ++ .../libwebrtc/build/config/fuchsia/gfx_tests.cmx | 31 + .../libwebrtc/build/config/fuchsia/package.gni | 114 + .../libwebrtc/build/config/fuchsia/rules.gni | 5 + .../libwebrtc/build/config/fuchsia/sizes.gni | 46 + .../build/config/fuchsia/symbol_archive.gni | 47 + .../libwebrtc/build/config/fuchsia/test/OWNERS | 7 + .../libwebrtc/build/config/fuchsia/test/README.md | 61 + .../fuchsia/test/audio_capabilities.test-cmx | 18 + .../config/fuchsia/test/font_capabilities.test-cmx | 14 + .../config/fuchsia/test/jit_capabilities.test-cmx | 7 + .../fuchsia/test/minimum_capabilities.test-cmx | 30 + .../fuchsia/test/network_capabilities.test-cmx | 25 + .../test/present_view_capabilities.test-cmx | 24 + .../config/fuchsia/test/read_debug_data.test-cmx | 7 + .../fuchsia/test/test_logger_capabilities.test-cmx | 7 + .../fuchsia/test/vulkan_capabilities.test-cmx | 19 + .../test/web_engine_required_capabilities.test-cmx | 25 + .../test/web_instance_host_capabilities.test-cmx | 8 + third_party/libwebrtc/build/config/gcc/BUILD.gn | 116 + .../libwebrtc/build/config/gclient_args.gni | 1 + .../libwebrtc/build/config/get_host_byteorder.py | 12 + .../libwebrtc/build/config/host_byteorder.gni | 27 + third_party/libwebrtc/build/config/ios/BUILD.gn | 264 + .../libwebrtc/build/config/ios/BuildInfo.plist | 35 + .../libwebrtc/build/config/ios/Host-Info.plist | 126 + .../libwebrtc/build/config/ios/Module-Info.plist | 26 + third_party/libwebrtc/build/config/ios/OWNERS | 1 + .../libwebrtc/build/config/ios/asset_catalog.gni | 150 + third_party/libwebrtc/build/config/ios/codesign.py | 691 +++ .../libwebrtc/build/config/ios/compile_ib_files.py | 57 + .../build/config/ios/compile_xcassets_unittests.py | 141 + third_party/libwebrtc/build/config/ios/config.gni | 28 + third_party/libwebrtc/build/config/ios/dummy.py | 15 + .../libwebrtc/build/config/ios/entitlements.plist | 12 + .../build/config/ios/find_signing_identity.py | 89 + .../build/config/ios/generate_umbrella_header.py | 75 + third_party/libwebrtc/build/config/ios/hardlink.py | 71 + third_party/libwebrtc/build/config/ios/ios_sdk.gni | 185 + .../build/config/ios/ios_sdk_overrides.gni | 17 + .../build/config/ios/ios_test_runner_wrapper.gni | 143 + .../ios/resources/XCTRunnerAddition+Info.plist | 12 + third_party/libwebrtc/build/config/ios/rules.gni | 2173 ++++++++ .../libwebrtc/build/config/ios/strip_arm64e.py | 70 + .../build/config/ios/write_framework_hmap.py | 103 + .../build/config/ios/write_framework_modulemap.py | 28 + .../libwebrtc/build/config/ios/xctest_shell.mm | 19 + third_party/libwebrtc/build/config/linux/BUILD.gn | 79 + third_party/libwebrtc/build/config/linux/OWNERS | 1 + .../libwebrtc/build/config/linux/atk/BUILD.gn | 35 + .../libwebrtc/build/config/linux/atspi2/BUILD.gn | 29 + .../libwebrtc/build/config/linux/dbus/BUILD.gn | 14 + .../libwebrtc/build/config/linux/dri/BUILD.gn | 18 + .../libwebrtc/build/config/linux/gtk/BUILD.gn | 69 + .../libwebrtc/build/config/linux/gtk/gtk.gni | 10 + .../libwebrtc/build/config/linux/libdrm/BUILD.gn | 31 + .../libwebrtc/build/config/linux/libffi/BUILD.gn | 18 + .../libwebrtc/build/config/linux/libva/BUILD.gn | 17 + .../libwebrtc/build/config/linux/nss/BUILD.gn | 22 + .../build/config/linux/pangocairo/BUILD.gn | 19 + .../build/config/linux/pangocairo/pangocairo.gni | 10 + .../libwebrtc/build/config/linux/pkg-config.py | 248 + .../libwebrtc/build/config/linux/pkg_config.gni | 128 + third_party/libwebrtc/build/config/locales.gni | 251 + third_party/libwebrtc/build/config/logging.gni | 13 + third_party/libwebrtc/build/config/mac/BUILD.gn | 136 + .../libwebrtc/build/config/mac/BuildInfo.plist | 16 + third_party/libwebrtc/build/config/mac/OWNERS | 1 + third_party/libwebrtc/build/config/mac/mac_sdk.gni | 127 + .../build/config/mac/mac_sdk_overrides.gni | 16 + .../build/config/mac/package_framework.py | 60 + .../build/config/mac/prepare_framework_version.py | 42 + third_party/libwebrtc/build/config/mac/rules.gni | 628 +++ third_party/libwebrtc/build/config/mips.gni | 67 + third_party/libwebrtc/build/config/nacl/BUILD.gn | 143 + third_party/libwebrtc/build/config/nacl/config.gni | 61 + third_party/libwebrtc/build/config/nacl/rules.gni | 180 + third_party/libwebrtc/build/config/ozone.gni | 152 + third_party/libwebrtc/build/config/ozone_extra.gni | 24 + third_party/libwebrtc/build/config/pch.gni | 15 + third_party/libwebrtc/build/config/posix/BUILD.gn | 59 + .../libwebrtc/build/config/profiling/OWNERS | 2 + .../libwebrtc/build/config/profiling/profiling.gni | 14 + third_party/libwebrtc/build/config/python.gni | 161 + third_party/libwebrtc/build/config/rts.gni | 5 + third_party/libwebrtc/build/config/rust.gni | 138 + .../libwebrtc/build/config/sanitizers/BUILD.gn | 610 +++ .../libwebrtc/build/config/sanitizers/OWNERS | 3 + .../build/config/sanitizers/sanitizers.gni | 298 ++ third_party/libwebrtc/build/config/sysroot.gni | 81 + third_party/libwebrtc/build/config/ui.gni | 52 + .../libwebrtc/build/config/v8_target_cpu.gni | 61 + third_party/libwebrtc/build/config/win/BUILD.gn | 610 +++ .../libwebrtc/build/config/win/console_app.gni | 18 + .../build/config/win/control_flow_guard.gni | 23 + .../libwebrtc/build/config/win/manifest.gni | 118 + .../build/config/win/visual_studio_version.gni | 39 + third_party/libwebrtc/build/config/x64.gni | 24 + third_party/libwebrtc/build/config/zip.gni | 48 + third_party/libwebrtc/build/copy_test_data_ios.py | 107 + third_party/libwebrtc/build/cp.py | 23 + third_party/libwebrtc/build/detect_host_arch.py | 55 + third_party/libwebrtc/build/dir_exists.py | 23 + .../libwebrtc/build/docs/debugging_slow_builds.md | 19 + .../libwebrtc/build/docs/mac_hermetic_toolchain.md | 34 + third_party/libwebrtc/build/docs/rust_toolchain.md | 21 + .../libwebrtc/build/docs/writing_gn_templates.md | 344 ++ third_party/libwebrtc/build/dotfile_settings.gni | 42 + .../libwebrtc/build/download_nacl_toolchains.py | 56 + third_party/libwebrtc/build/env_dump.py | 56 + third_party/libwebrtc/build/extract_from_cab.py | 65 + third_party/libwebrtc/build/extract_partition.py | 174 + third_party/libwebrtc/build/find_depot_tools.py | 75 + third_party/libwebrtc/build/fix_gn_headers.py | 220 + third_party/libwebrtc/build/fuchsia/DIR_METADATA | 5 + third_party/libwebrtc/build/fuchsia/OWNERS | 9 + third_party/libwebrtc/build/fuchsia/PRESUBMIT.py | 35 + third_party/libwebrtc/build/fuchsia/__init__.py | 0 third_party/libwebrtc/build/fuchsia/aemu_target.py | 116 + .../libwebrtc/build/fuchsia/binary_sizes.py | 556 +++ .../libwebrtc/build/fuchsia/binary_sizes_test.py | 61 + third_party/libwebrtc/build/fuchsia/boot_data.py | 120 + .../libwebrtc/build/fuchsia/boot_data_test.py | 46 + third_party/libwebrtc/build/fuchsia/common.py | 140 + third_party/libwebrtc/build/fuchsia/common_args.py | 173 + .../libwebrtc/build/fuchsia/deploy_to_pkg_repo.py | 67 + .../libwebrtc/build/fuchsia/device_target.py | 266 + .../libwebrtc/build/fuchsia/device_target_test.py | 103 + third_party/libwebrtc/build/fuchsia/emu_target.py | 145 + third_party/libwebrtc/build/fuchsia/fvdl_target.py | 204 + .../libwebrtc/build/fuchsia/fvdl_target_test.py | 102 + .../libwebrtc/build/fuchsia/generic_x64_target.py | 99 + third_party/libwebrtc/build/fuchsia/linux.sdk.sha1 | 1 + third_party/libwebrtc/build/fuchsia/mac.sdk.sha1 | 1 + .../libwebrtc/build/fuchsia/net_test_server.py | 90 + third_party/libwebrtc/build/fuchsia/pkg_repo.py | 209 + third_party/libwebrtc/build/fuchsia/qemu_image.py | 75 + third_party/libwebrtc/build/fuchsia/qemu_target.py | 243 + .../libwebrtc/build/fuchsia/qemu_target_test.py | 58 + third_party/libwebrtc/build/fuchsia/remote_cmd.py | 131 + .../libwebrtc/build/fuchsia/run_test_package.py | 278 ++ .../libwebrtc/build/fuchsia/runner_exceptions.py | 78 + third_party/libwebrtc/build/fuchsia/runner_logs.py | 96 + third_party/libwebrtc/build/fuchsia/sdk-bucket.txt | 0 .../libwebrtc/build/fuchsia/sdk-hash-files.list | 1 + .../libwebrtc/build/fuchsia/start_emulator.py | 84 + third_party/libwebrtc/build/fuchsia/symbolizer.py | 70 + third_party/libwebrtc/build/fuchsia/target.py | 336 ++ third_party/libwebrtc/build/fuchsia/test_runner.py | 264 + .../libwebrtc/build/fuchsia/update_images.py | 142 + third_party/libwebrtc/build/fuchsia/update_sdk.py | 168 + third_party/libwebrtc/build/gdb-add-index | 184 + third_party/libwebrtc/build/get_landmines.py | 89 + third_party/libwebrtc/build/get_symlink_targets.py | 27 + third_party/libwebrtc/build/gn_helpers.py | 542 ++ third_party/libwebrtc/build/gn_helpers_unittest.py | 315 ++ third_party/libwebrtc/build/gn_logs.gni | 8 + third_party/libwebrtc/build/gn_run_binary.py | 36 + .../libwebrtc/build/install-build-deps-android.sh | 37 + third_party/libwebrtc/build/install-build-deps.sh | 763 +++ third_party/libwebrtc/build/install-chroot.sh | 888 ++++ .../libwebrtc/build/internal/README.chromium | 24 + third_party/libwebrtc/build/ios/OWNERS | 1 + .../libwebrtc/build/ios/intent_definition.gni | 121 + third_party/libwebrtc/build/lacros/BUILD.gn | 20 + third_party/libwebrtc/build/lacros/OWNERS | 3 + third_party/libwebrtc/build/lacros/PRESUBMIT.py | 25 + .../build/lacros/lacros_resource_sizes.gni | 21 + .../build/lacros/lacros_resource_sizes.py | 350 ++ .../build/lacros/lacros_resource_sizes.pydeps | 12 + .../libwebrtc/build/lacros/metadata.json.in | 6 + .../lacros/mojo_connection_lacros_launcher.py | 160 + third_party/libwebrtc/build/lacros/test_runner.py | 581 +++ .../libwebrtc/build/lacros/test_runner_test.py | 267 + third_party/libwebrtc/build/landmine_utils.py | 33 + third_party/libwebrtc/build/landmines.py | 151 + third_party/libwebrtc/build/linux/BUILD.gn | 31 + third_party/libwebrtc/build/linux/OWNERS | 3 + third_party/libwebrtc/build/linux/chrome.map | 89 + third_party/libwebrtc/build/linux/dump_app_syms.py | 31 + .../libwebrtc/build/linux/extract_symbols.gni | 41 + .../build/linux/install-chromeos-fonts.py | 120 + .../libwebrtc/build/linux/libncursesw/DIR_METADATA | 5 + .../libwebrtc/build/linux/libncursesw/OWNERS | 1 + third_party/libwebrtc/build/linux/libpci/BUILD.gn | 22 + third_party/libwebrtc/build/linux/libudev/BUILD.gn | 66 + third_party/libwebrtc/build/linux/rewrite_dirs.py | 73 + third_party/libwebrtc/build/linux/strip_binary.gni | 44 + third_party/libwebrtc/build/linux/strip_binary.py | 32 + .../linux/sysroot_scripts/build_and_upload.py | 101 + .../sysroot_scripts/debian_archive_unstable.gpg | Bin 0 -> 81498 bytes .../find_incompatible_glibc_symbols.py | 63 + .../find_incompatible_glibc_symbols_unittest.py | 41 + .../generate_debian_archive_unstable_gpg.sh | 37 + .../generated_package_lists/sid.amd64 | 372 ++ .../generated_package_lists/sid.arm | 368 ++ .../generated_package_lists/sid.arm64 | 371 ++ .../generated_package_lists/sid.armel | 367 ++ .../generated_package_lists/sid.i386 | 368 ++ .../generated_package_lists/sid.mips64el | 359 ++ .../generated_package_lists/sid.mipsel | 359 ++ .../build/linux/sysroot_scripts/install-sysroot.py | 169 + .../linux/sysroot_scripts/libdbus-1-3-symbols | 235 + .../linux/sysroot_scripts/libxcomposite1-symbols | 15 + .../linux/sysroot_scripts/libxkbcommon0-symbols | 93 + .../linux/sysroot_scripts/merge-package-lists.py | 34 + .../linux/sysroot_scripts/sysroot-creator-sid.sh | 463 ++ .../build/linux/sysroot_scripts/sysroot-creator.sh | 968 ++++ .../build/linux/sysroot_scripts/sysroots.json | 37 + .../sysroot_scripts/update-archive-timestamp.sh | 18 + third_party/libwebrtc/build/linux/unbundle/README | 57 + .../libwebrtc/build/linux/unbundle/ffmpeg.gn | 38 + third_party/libwebrtc/build/linux/unbundle/flac.gn | 30 + .../libwebrtc/build/linux/unbundle/fontconfig.gn | 13 + .../libwebrtc/build/linux/unbundle/freetype.gn | 14 + .../libwebrtc/build/linux/unbundle/harfbuzz-ng.gn | 13 + third_party/libwebrtc/build/linux/unbundle/icu.gn | 254 + .../libwebrtc/build/linux/unbundle/libdrm.gn | 20 + .../libwebrtc/build/linux/unbundle/libevent.gn | 15 + .../libwebrtc/build/linux/unbundle/libjpeg.gn | 12 + .../libwebrtc/build/linux/unbundle/libpng.gn | 23 + .../libwebrtc/build/linux/unbundle/libvpx.gn | 32 + .../libwebrtc/build/linux/unbundle/libwebp.gn | 35 + .../libwebrtc/build/linux/unbundle/libxml.gn | 55 + .../libwebrtc/build/linux/unbundle/libxslt.gn | 13 + .../libwebrtc/build/linux/unbundle/openh264.gn | 36 + third_party/libwebrtc/build/linux/unbundle/opus.gn | 43 + third_party/libwebrtc/build/linux/unbundle/re2.gn | 27 + .../linux/unbundle/remove_bundled_libraries.py | 107 + .../build/linux/unbundle/replace_gn_files.py | 83 + .../libwebrtc/build/linux/unbundle/snappy.gn | 20 + third_party/libwebrtc/build/linux/unbundle/zlib.gn | 64 + third_party/libwebrtc/build/locale_tool.py | 1512 ++++++ third_party/libwebrtc/build/mac/OWNERS | 1 + third_party/libwebrtc/build/mac/find_sdk.py | 99 + .../build/mac/should_use_hermetic_xcode.py | 63 + third_party/libwebrtc/build/mac_toolchain.py | 208 + third_party/libwebrtc/build/nocompile.gni | 137 + third_party/libwebrtc/build/noop.py | 4 + .../libwebrtc/build/partitioned_shared_library.gni | 143 + third_party/libwebrtc/build/precompile.cc | 7 + third_party/libwebrtc/build/precompile.h | 53 + third_party/libwebrtc/build/print_python_deps.py | 223 + third_party/libwebrtc/build/protoc_java.py | 98 + third_party/libwebrtc/build/protoc_java.pydeps | 6 + third_party/libwebrtc/build/redirect_stdout.py | 21 + third_party/libwebrtc/build/rm.py | 40 + third_party/libwebrtc/build/rust/OWNERS | 8 + third_party/libwebrtc/build/rust/std/BUILD.gn | 153 + .../libwebrtc/build/rust/std/find_std_rlibs.py | 97 + .../libwebrtc/build/rust/std/immediate_crash.h | 170 + third_party/libwebrtc/build/rust/std/remap_alloc.c | 70 + third_party/libwebrtc/build/rust/tests/BUILD.gn | 22 + .../tests/test_cpp_exe_including_rust/BUILD.gn | 8 + .../rust/tests/test_cpp_exe_including_rust/main.cc | 11 + .../build/rust/tests/test_rust_exe/BUILD.gn | 12 + .../build/rust/tests/test_rust_exe/main.rs | 9 + .../build/rust/tests/test_rust_source_set/BUILD.gn | 42 + .../build/rust/tests/test_rust_source_set/main.rs | 21 + third_party/libwebrtc/build/sample_arg_file.gn | 6 + .../libwebrtc/build/sanitize-mac-build-log.sed | 33 + .../libwebrtc/build/sanitize-mac-build-log.sh | 5 + .../libwebrtc/build/sanitize-win-build-log.sed | 15 + .../libwebrtc/build/sanitize-win-build-log.sh | 5 + third_party/libwebrtc/build/sanitizers/OWNERS | 8 + .../build/sanitizers/asan_suppressions.cc | 23 + .../build/sanitizers/lsan_suppressions.cc | 78 + .../build/sanitizers/sanitizer_options.cc | 205 + .../build/sanitizers/tsan_suppressions.cc | 138 + third_party/libwebrtc/build/shim_headers.gni | 39 + .../libwebrtc/build/skia_gold_common/.style.yapf | 6 + .../libwebrtc/build/skia_gold_common/OWNERS | 1 + .../libwebrtc/build/skia_gold_common/PRESUBMIT.py | 36 + .../libwebrtc/build/skia_gold_common/README.md | 6 + .../libwebrtc/build/skia_gold_common/__init__.py | 3 + .../output_managerless_skia_gold_session.py | 68 + ...utput_managerless_skia_gold_session_unittest.py | 137 + .../build/skia_gold_common/skia_gold_properties.py | 148 + .../skia_gold_properties_unittest.py | 187 + .../build/skia_gold_common/skia_gold_session.py | 552 +++ .../skia_gold_common/skia_gold_session_manager.py | 121 + .../skia_gold_session_manager_unittest.py | 180 + .../skia_gold_common/skia_gold_session_unittest.py | 834 ++++ .../build/skia_gold_common/unittest_utils.py | 36 + third_party/libwebrtc/build/symlink.gni | 79 + third_party/libwebrtc/build/symlink.py | 91 + third_party/libwebrtc/build/timestamp.gni | 34 + third_party/libwebrtc/build/toolchain/BUILD.gn | 26 + third_party/libwebrtc/build/toolchain/OWNERS | 5 + third_party/libwebrtc/build/toolchain/aix/BUILD.gn | 24 + .../libwebrtc/build/toolchain/android/BUILD.gn | 156 + .../libwebrtc/build/toolchain/android/OWNERS | 1 + .../libwebrtc/build/toolchain/apple/BUILD.gn | 22 + third_party/libwebrtc/build/toolchain/apple/OWNERS | 1 + .../build/toolchain/apple/filter_libtool.py | 52 + .../build/toolchain/apple/get_tool_mtime.py | 19 + .../build/toolchain/apple/linker_driver.py | 306 ++ .../libwebrtc/build/toolchain/apple/toolchain.gni | 611 +++ .../libwebrtc/build/toolchain/cc_wrapper.gni | 43 + .../build/toolchain/clang_code_coverage_wrapper.py | 241 + .../libwebrtc/build/toolchain/concurrent_links.gni | 106 + .../libwebrtc/build/toolchain/cros/BUILD.gn | 176 + .../libwebrtc/build/toolchain/cros_toolchain.gni | 88 + .../libwebrtc/build/toolchain/fuchsia/BUILD.gn | 37 + .../libwebrtc/build/toolchain/fuchsia/DIR_METADATA | 7 + .../libwebrtc/build/toolchain/fuchsia/OWNERS | 1 + .../libwebrtc/build/toolchain/gcc_link_wrapper.py | 94 + .../build/toolchain/gcc_solink_wrapper.py | 198 + .../libwebrtc/build/toolchain/gcc_toolchain.gni | 762 +++ .../build/toolchain/get_concurrent_links.py | 134 + .../libwebrtc/build/toolchain/get_cpu_count.py | 23 + .../libwebrtc/build/toolchain/get_goma_dir.py | 42 + third_party/libwebrtc/build/toolchain/goma.gni | 35 + third_party/libwebrtc/build/toolchain/ios/BUILD.gn | 113 + third_party/libwebrtc/build/toolchain/ios/OWNERS | 1 + .../build/toolchain/ios/compile_xcassets.py | 258 + .../libwebrtc/build/toolchain/ios/swiftc.py | 174 + third_party/libwebrtc/build/toolchain/kythe.gni | 11 + .../libwebrtc/build/toolchain/linux/BUILD.gn | 445 ++ .../build/toolchain/linux/unbundle/BUILD.gn | 41 + .../build/toolchain/linux/unbundle/README.md | 41 + third_party/libwebrtc/build/toolchain/mac/BUILD.gn | 102 + third_party/libwebrtc/build/toolchain/mac/OWNERS | 1 + .../libwebrtc/build/toolchain/nacl/BUILD.gn | 305 ++ .../libwebrtc/build/toolchain/nacl_toolchain.gni | 73 + third_party/libwebrtc/build/toolchain/rbe.gni | 45 + .../libwebrtc/build/toolchain/toolchain.gni | 107 + third_party/libwebrtc/build/toolchain/win/BUILD.gn | 587 +++ third_party/libwebrtc/build/toolchain/win/midl.gni | 199 + third_party/libwebrtc/build/toolchain/win/midl.py | 487 ++ third_party/libwebrtc/build/toolchain/win/ml.py | 290 ++ .../libwebrtc/build/toolchain/win/rc/.gitignore | 3 + .../libwebrtc/build/toolchain/win/rc/README.md | 30 + .../build/toolchain/win/rc/linux64/rc.sha1 | 1 + .../libwebrtc/build/toolchain/win/rc/mac/rc.sha1 | 1 + third_party/libwebrtc/build/toolchain/win/rc/rc.py | 276 ++ .../build/toolchain/win/rc/upload_rc_binaries.sh | 46 + .../build/toolchain/win/rc/win/rc.exe.sha1 | 1 + .../build/toolchain/win/setup_toolchain.py | 314 ++ .../libwebrtc/build/toolchain/win/tool_wrapper.py | 190 + .../libwebrtc/build/toolchain/wrapper_utils.py | 93 + third_party/libwebrtc/build/tree_truth.sh | 102 + .../libwebrtc/build/update-linux-sandbox.sh | 82 + third_party/libwebrtc/build/util/BUILD.gn | 38 + .../libwebrtc/build/util/LASTCHANGE.committime | 1 + third_party/libwebrtc/build/util/LASTCHANGE.dummy | 1 + third_party/libwebrtc/build/util/PRESUBMIT.py | 64 + .../libwebrtc/build/util/android_chrome_version.py | 214 + .../build/util/android_chrome_version_test.py | 308 ++ third_party/libwebrtc/build/util/branding.gni | 45 + .../build/util/chromium_git_revision.h.in | 8 + .../libwebrtc/build/util/generate_wrapper.gni | 105 + .../libwebrtc/build/util/generate_wrapper.py | 217 + third_party/libwebrtc/build/util/java_action.gni | 99 + third_party/libwebrtc/build/util/java_action.py | 82 + third_party/libwebrtc/build/util/lastchange.gni | 16 + third_party/libwebrtc/build/util/lastchange.py | 332 ++ third_party/libwebrtc/build/util/lib/__init__.py | 0 .../libwebrtc/build/util/lib/common/PRESUBMIT.py | 19 + .../libwebrtc/build/util/lib/common/__init__.py | 0 .../util/lib/common/chrome_test_server_spawner.py | 503 ++ .../build/util/lib/common/perf_result_data_type.py | 20 + .../util/lib/common/perf_tests_results_helper.py | 202 + .../build/util/lib/common/unittest_util.py | 155 + .../build/util/lib/common/unittest_util_test.py | 65 + .../libwebrtc/build/util/lib/common/util.py | 151 + .../libwebrtc/build/util/lib/results/__init__.py | 0 .../build/util/lib/results/result_sink.py | 180 + .../build/util/lib/results/result_sink_test.py | 102 + .../build/util/lib/results/result_types.py | 25 + .../libwebrtc/build/util/process_version.gni | 128 + third_party/libwebrtc/build/util/version.gni | 155 + third_party/libwebrtc/build/util/version.py | 268 + third_party/libwebrtc/build/util/version_test.py | 174 + third_party/libwebrtc/build/vs_toolchain.py | 575 +++ third_party/libwebrtc/build/whitespace_file.txt | 190 + third_party/libwebrtc/build/win/BUILD.gn | 177 + .../libwebrtc/build/win/as_invoker.manifest | 9 + .../libwebrtc/build/win/common_controls.manifest | 8 + .../libwebrtc/build/win/compatibility.manifest | 17 + .../libwebrtc/build/win/copy_cdb_to_output.py | 127 + third_party/libwebrtc/build/win/gn_meta_sln.py | 214 + .../libwebrtc/build/win/message_compiler.gni | 87 + .../libwebrtc/build/win/message_compiler.py | 148 + third_party/libwebrtc/build/win/reorder-imports.py | 98 + .../build/win/require_administrator.manifest | 9 + .../libwebrtc/build/win/segment_heap.manifest | 8 + .../libwebrtc/build/win/set_appcontainer_acls.py | 35 + third_party/libwebrtc/build/win/use_ansi_codes.py | 12 + .../libwebrtc/build/write_build_date_header.py | 37 + .../libwebrtc/build/write_buildflag_header.py | 103 + third_party/libwebrtc/build/xcode_binaries.yaml | 57 + 928 files changed, 137173 insertions(+) create mode 100644 third_party/libwebrtc/build/.gitignore create mode 100644 third_party/libwebrtc/build/.style.yapf create mode 100644 third_party/libwebrtc/build/BUILD.gn create mode 100644 third_party/libwebrtc/build/DIR_METADATA create mode 100644 third_party/libwebrtc/build/OWNERS create mode 100644 third_party/libwebrtc/build/OWNERS.setnoparent create mode 100644 third_party/libwebrtc/build/OWNERS.status create mode 100644 third_party/libwebrtc/build/README.md create mode 100755 third_party/libwebrtc/build/add_rts_filters.py create mode 100644 third_party/libwebrtc/build/android/AndroidManifest.xml create mode 100644 third_party/libwebrtc/build/android/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/CheckInstallApk-debug.apk create mode 100644 third_party/libwebrtc/build/android/DIR_METADATA create mode 100644 third_party/libwebrtc/build/android/OWNERS create mode 100644 third_party/libwebrtc/build/android/PRESUBMIT.py create mode 100755 third_party/libwebrtc/build/android/adb_chrome_public_command_line create mode 100755 third_party/libwebrtc/build/android/adb_command_line.py create mode 100755 third_party/libwebrtc/build/android/adb_gdb create mode 100755 third_party/libwebrtc/build/android/adb_install_apk.py create mode 100755 third_party/libwebrtc/build/android/adb_logcat_monitor.py create mode 100755 third_party/libwebrtc/build/android/adb_logcat_printer.py create mode 100755 third_party/libwebrtc/build/android/adb_profile_chrome create mode 100755 third_party/libwebrtc/build/android/adb_profile_chrome_startup create mode 100755 third_party/libwebrtc/build/android/adb_reverse_forwarder.py create mode 100755 third_party/libwebrtc/build/android/adb_system_webview_command_line create mode 100644 third_party/libwebrtc/build/android/android_only_explicit_jni_exports.lst create mode 100644 third_party/libwebrtc/build/android/android_only_jni_exports.lst create mode 100755 third_party/libwebrtc/build/android/apk_operations.py create mode 100644 third_party/libwebrtc/build/android/apk_operations.pydeps create mode 100755 third_party/libwebrtc/build/android/apply_shared_preference_file.py create mode 100755 third_party/libwebrtc/build/android/asan_symbolize.py create mode 100644 third_party/libwebrtc/build/android/bytecode/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java create mode 100644 third_party/libwebrtc/build/android/chromium-debug.keystore create mode 100755 third_party/libwebrtc/build/android/convert_dex_profile.py create mode 100755 third_party/libwebrtc/build/android/convert_dex_profile_tests.py create mode 100644 third_party/libwebrtc/build/android/dcheck_is_off.flags create mode 100644 third_party/libwebrtc/build/android/devil_chromium.json create mode 100644 third_party/libwebrtc/build/android/devil_chromium.py create mode 100644 third_party/libwebrtc/build/android/devil_chromium.pydeps create mode 100755 third_party/libwebrtc/build/android/diff_resource_sizes.py create mode 100644 third_party/libwebrtc/build/android/docs/README.md create mode 100644 third_party/libwebrtc/build/android/docs/build_config.md create mode 100644 third_party/libwebrtc/build/android/docs/class_verification_failures.md create mode 100644 third_party/libwebrtc/build/android/docs/coverage.md create mode 100644 third_party/libwebrtc/build/android/docs/java_optimization.md create mode 100644 third_party/libwebrtc/build/android/docs/java_toolchain.md create mode 100644 third_party/libwebrtc/build/android/docs/life_of_a_resource.md create mode 100644 third_party/libwebrtc/build/android/docs/lint.md create mode 100755 third_party/libwebrtc/build/android/download_doclava.py create mode 100755 third_party/libwebrtc/build/android/dump_apk_resource_strings.py create mode 100755 third_party/libwebrtc/build/android/emma_coverage_stats.py create mode 100755 third_party/libwebrtc/build/android/emma_coverage_stats_test.py create mode 100755 third_party/libwebrtc/build/android/envsetup.sh create mode 100755 third_party/libwebrtc/build/android/fast_local_dev_server.py create mode 100755 third_party/libwebrtc/build/android/generate_jacoco_report.py create mode 100644 third_party/libwebrtc/build/android/gradle/AndroidManifest.xml create mode 100644 third_party/libwebrtc/build/android/gradle/OWNERS create mode 100644 third_party/libwebrtc/build/android/gradle/android.jinja create mode 100644 third_party/libwebrtc/build/android/gradle/cmake.jinja create mode 100644 third_party/libwebrtc/build/android/gradle/dependencies.jinja create mode 100755 third_party/libwebrtc/build/android/gradle/generate_gradle.py create mode 100755 third_party/libwebrtc/build/android/gradle/gn_to_cmake.py create mode 100644 third_party/libwebrtc/build/android/gradle/java.jinja create mode 100644 third_party/libwebrtc/build/android/gradle/manifest.jinja create mode 100644 third_party/libwebrtc/build/android/gradle/root.jinja create mode 100644 third_party/libwebrtc/build/android/gtest_apk/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestInstrumentationTestRunner.java create mode 100644 third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestIntent.java create mode 100644 third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusIntent.java create mode 100644 third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusReceiver.java create mode 100644 third_party/libwebrtc/build/android/gyp/OWNERS create mode 100755 third_party/libwebrtc/build/android/gyp/aar.py create mode 100644 third_party/libwebrtc/build/android/gyp/aar.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/aidl.py create mode 100644 third_party/libwebrtc/build/android/gyp/aidl.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/allot_native_libraries.py create mode 100644 third_party/libwebrtc/build/android/gyp/allot_native_libraries.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/apkbuilder.py create mode 100644 third_party/libwebrtc/build/android/gyp/apkbuilder.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/assert_static_initializers.py create mode 100644 third_party/libwebrtc/build/android/gyp/assert_static_initializers.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/bundletool.py create mode 100755 third_party/libwebrtc/build/android/gyp/bytecode_processor.py create mode 100644 third_party/libwebrtc/build/android/gyp/bytecode_processor.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/bytecode_rewriter.py create mode 100644 third_party/libwebrtc/build/android/gyp/bytecode_rewriter.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/check_flag_expectations.py create mode 100644 third_party/libwebrtc/build/android/gyp/check_flag_expectations.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/compile_java.py create mode 100644 third_party/libwebrtc/build/android/gyp/compile_java.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/compile_resources.py create mode 100644 third_party/libwebrtc/build/android/gyp/compile_resources.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/copy_ex.py create mode 100644 third_party/libwebrtc/build/android/gyp/copy_ex.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_apk_operations_script.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_apk_operations_script.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_app_bundle.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_app_bundle.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_java_binary_script.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_java_binary_script.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_r_java.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_r_java.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_r_txt.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_r_txt.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_size_info_files.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_size_info_files.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.py create mode 100644 third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/desugar.py create mode 100644 third_party/libwebrtc/build/android/gyp/desugar.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/dex.py create mode 100644 third_party/libwebrtc/build/android/gyp/dex.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/dex_jdk_libs.py create mode 100644 third_party/libwebrtc/build/android/gyp/dex_jdk_libs.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/dexsplitter.py create mode 100644 third_party/libwebrtc/build/android/gyp/dexsplitter.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/dist_aar.py create mode 100644 third_party/libwebrtc/build/android/gyp/dist_aar.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/extract_unwind_tables.py create mode 100755 third_party/libwebrtc/build/android/gyp/extract_unwind_tables_tests.py create mode 100755 third_party/libwebrtc/build/android/gyp/filter_zip.py create mode 100644 third_party/libwebrtc/build/android/gyp/filter_zip.pydeps create mode 100644 third_party/libwebrtc/build/android/gyp/finalize_apk.py create mode 100755 third_party/libwebrtc/build/android/gyp/find.py create mode 100755 third_party/libwebrtc/build/android/gyp/gcc_preprocess.py create mode 100644 third_party/libwebrtc/build/android/gyp/gcc_preprocess.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/generate_android_wrapper.py create mode 100755 third_party/libwebrtc/build/android/gyp/generate_linker_version_script.py create mode 100644 third_party/libwebrtc/build/android/gyp/generate_linker_version_script.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/ijar.py create mode 100644 third_party/libwebrtc/build/android/gyp/ijar.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/jacoco_instr.py create mode 100644 third_party/libwebrtc/build/android/gyp/jacoco_instr.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_enum.py create mode 100644 third_party/libwebrtc/build/android/gyp/java_cpp_enum.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_enum_tests.py create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_features.py create mode 100644 third_party/libwebrtc/build/android/gyp/java_cpp_features.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_features_tests.py create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_strings.py create mode 100644 third_party/libwebrtc/build/android/gyp/java_cpp_strings.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/java_cpp_strings_tests.py create mode 100755 third_party/libwebrtc/build/android/gyp/java_google_api_keys.py create mode 100644 third_party/libwebrtc/build/android/gyp/java_google_api_keys.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/java_google_api_keys_tests.py create mode 100755 third_party/libwebrtc/build/android/gyp/javac_output_processor.py create mode 100755 third_party/libwebrtc/build/android/gyp/jetify_jar.py create mode 100644 third_party/libwebrtc/build/android/gyp/jetify_jar.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/jinja_template.py create mode 100644 third_party/libwebrtc/build/android/gyp/jinja_template.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/lint.py create mode 100644 third_party/libwebrtc/build/android/gyp/lint.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/merge_manifest.py create mode 100644 third_party/libwebrtc/build/android/gyp/merge_manifest.pydeps create mode 100644 third_party/libwebrtc/build/android/gyp/native_libraries_template.py create mode 100755 third_party/libwebrtc/build/android/gyp/nocompile_test.py create mode 100755 third_party/libwebrtc/build/android/gyp/optimize_resources.py create mode 100644 third_party/libwebrtc/build/android/gyp/optimize_resources.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/prepare_resources.py create mode 100644 third_party/libwebrtc/build/android/gyp/prepare_resources.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/process_native_prebuilt.py create mode 100644 third_party/libwebrtc/build/android/gyp/process_native_prebuilt.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/proguard.py create mode 100644 third_party/libwebrtc/build/android/gyp/proguard.pydeps create mode 100644 third_party/libwebrtc/build/android/gyp/proto/Configuration_pb2.py create mode 100644 third_party/libwebrtc/build/android/gyp/proto/README.md create mode 100644 third_party/libwebrtc/build/android/gyp/proto/Resources_pb2.py create mode 100644 third_party/libwebrtc/build/android/gyp/proto/__init__.py create mode 100644 third_party/libwebrtc/build/android/gyp/test/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java create mode 100644 third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java create mode 100755 third_party/libwebrtc/build/android/gyp/turbine.py create mode 100644 third_party/libwebrtc/build/android/gyp/turbine.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/unused_resources.py create mode 100644 third_party/libwebrtc/build/android/gyp/unused_resources.pydeps create mode 100644 third_party/libwebrtc/build/android/gyp/util/__init__.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/build_utils.py create mode 100755 third_party/libwebrtc/build/android/gyp/util/build_utils_test.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/diff_utils.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/jar_info_utils.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/java_cpp_utils.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/manifest_utils.py create mode 100755 third_party/libwebrtc/build/android/gyp/util/manifest_utils_test.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/md5_check.py create mode 100755 third_party/libwebrtc/build/android/gyp/util/md5_check_test.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/parallel.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/protoresources.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/resource_utils.py create mode 100755 third_party/libwebrtc/build/android/gyp/util/resource_utils_test.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/resources_parser.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/server_utils.py create mode 100644 third_party/libwebrtc/build/android/gyp/util/zipalign.py create mode 100755 third_party/libwebrtc/build/android/gyp/validate_static_library_dex_references.py create mode 100644 third_party/libwebrtc/build/android/gyp/validate_static_library_dex_references.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/write_build_config.py create mode 100644 third_party/libwebrtc/build/android/gyp/write_build_config.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/write_native_libraries_java.py create mode 100644 third_party/libwebrtc/build/android/gyp/write_native_libraries_java.pydeps create mode 100755 third_party/libwebrtc/build/android/gyp/zip.py create mode 100644 third_party/libwebrtc/build/android/gyp/zip.pydeps create mode 100755 third_party/libwebrtc/build/android/host_heartbeat.py create mode 100644 third_party/libwebrtc/build/android/incremental_install/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/incremental_install/README.md create mode 100644 third_party/libwebrtc/build/android/incremental_install/__init__.py create mode 100755 third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.py create mode 100644 third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.pydeps create mode 100755 third_party/libwebrtc/build/android/incremental_install/installer.py create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapInstrumentation.java create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/LockFile.java create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/Reflect.java create mode 100644 third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/SecondInstrumentation.java create mode 100755 third_party/libwebrtc/build/android/incremental_install/write_installer_json.py create mode 100644 third_party/libwebrtc/build/android/incremental_install/write_installer_json.pydeps create mode 100644 third_party/libwebrtc/build/android/java/templates/BuildConfig.template create mode 100644 third_party/libwebrtc/build/android/java/templates/ProductConfig.template create mode 100644 third_party/libwebrtc/build/android/java/test/DefaultLocaleLintTest.java create mode 100644 third_party/libwebrtc/build/android/java/test/IncrementalJavacTest.java create mode 100644 third_party/libwebrtc/build/android/java/test/NewApiLintTest.java create mode 100644 third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template create mode 100644 third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper2.java create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/B.java create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/D.template create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/Importer.template create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/ImportsSubB.java create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/c.jar create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/sub/BInMethodSignature.java create mode 100644 third_party/libwebrtc/build/android/java/test/missing_symbol/sub/SubB.java create mode 100755 third_party/libwebrtc/build/android/lighttpd_server.py create mode 100755 third_party/libwebrtc/build/android/list_class_verification_failures.py create mode 100755 third_party/libwebrtc/build/android/list_class_verification_failures_test.py create mode 100755 third_party/libwebrtc/build/android/list_java_targets.py create mode 100644 third_party/libwebrtc/build/android/main_dex_classes.flags create mode 100755 third_party/libwebrtc/build/android/method_count.py create mode 100644 third_party/libwebrtc/build/android/native_flags/BUILD.gn create mode 100755 third_party/libwebrtc/build/android/native_flags/argcapture.py create mode 100644 third_party/libwebrtc/build/android/native_flags/empty.cc create mode 100755 third_party/libwebrtc/build/android/provision_devices.py create mode 100644 third_party/libwebrtc/build/android/pylib/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/android/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/android/logcat_symbolizer.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/base_test_result.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/base_test_result_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/environment.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/environment_factory.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/mock_environment.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/mock_test_instance.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/output_manager.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/output_manager_factory.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/output_manager_test_case.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_collection.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_exception.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_instance.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_instance_factory.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_run.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_run_factory.py create mode 100644 third_party/libwebrtc/build/android/pylib/base/test_server.py create mode 100644 third_party/libwebrtc/build/android/pylib/constants/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/constants/host_paths.py create mode 100755 third_party/libwebrtc/build/android/pylib/constants/host_paths_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/content_settings.py create mode 100644 third_party/libwebrtc/build/android/pylib/device/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/device/commands/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java create mode 100644 third_party/libwebrtc/build/android/pylib/device_settings.py create mode 100644 third_party/libwebrtc/build/android/pylib/dex/__init__.py create mode 100755 third_party/libwebrtc/build/android/pylib/dex/dex_parser.py create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/OWNERS create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/base_unittests_disabled create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/base_unittests_emulator_additional_disabled create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/breakpad_unittests_disabled create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/content_browsertests_disabled create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/filter/unit_tests_disabled create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/gtest_config.py create mode 100644 third_party/libwebrtc/build/android/pylib/gtest/gtest_test_instance.py create mode 100755 third_party/libwebrtc/build/android/pylib/gtest/gtest_test_instance_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser.py create mode 100755 third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance.py create mode 100755 third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/json_perf_parser.py create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/render_test.html.jinja create mode 100644 third_party/libwebrtc/build/android/pylib/instrumentation/test_result.py create mode 100644 third_party/libwebrtc/build/android/pylib/junit/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/junit/junit_test_instance.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/local_device_environment.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run.py create mode 100755 third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run.py create mode 100755 third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/local_device_monkey_test_run.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run.py create mode 100755 third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/OWNERS create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/avd.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/ini.py create mode 100755 third_party/libwebrtc/build/android/pylib/local/emulator/ini_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/local_emulator_environment.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/proto/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd.proto create mode 100644 third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd_pb2.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/local_test_server_spawner.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/machine/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/machine/local_machine_environment.py create mode 100644 third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run.py create mode 100755 third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/monkey/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/monkey/monkey_test_instance.py create mode 100644 third_party/libwebrtc/build/android/pylib/output/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/output/local_output_manager.py create mode 100755 third_party/libwebrtc/build/android/pylib/output/local_output_manager_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/output/noop_output_manager.py create mode 100755 third_party/libwebrtc/build/android/pylib/output/noop_output_manager_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/output/remote_output_manager.py create mode 100755 third_party/libwebrtc/build/android/pylib/output/remote_output_manager_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/pexpect.py create mode 100755 third_party/libwebrtc/build/android/pylib/restart_adbd.sh create mode 100644 third_party/libwebrtc/build/android/pylib/results/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/results_uploader.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/json_results.py create mode 100755 third_party/libwebrtc/build/android/pylib/results/json_results_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/presentation/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/presentation/javascript/main_html.js create mode 100755 third_party/libwebrtc/build/android/pylib/results/presentation/standard_gtest_merge.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/presentation/template/main.html create mode 100644 third_party/libwebrtc/build/android/pylib/results/presentation/template/table.html create mode 100755 third_party/libwebrtc/build/android/pylib/results/presentation/test_results_presentation.py create mode 100644 third_party/libwebrtc/build/android/pylib/results/report_results.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/__init__.py create mode 100755 third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py create mode 100755 third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py create mode 100755 third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py create mode 100755 third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py create mode 100644 third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py create mode 100755 third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/__init__.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/app_bundle_utils.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/argparse_utils.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/decorators.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/decorators_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/device_dependencies.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/device_dependencies_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/dexdump.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/dexdump_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/gold_utils.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/gold_utils_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/google_storage_helper.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/instrumentation_tracing.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/local_utils.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/logdog_helper.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/logging_utils.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/maven_downloader.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/proguard.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/proguard_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/repo_utils.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/shared_preference_utils.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/simpleperf.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/test_filter.py create mode 100755 third_party/libwebrtc/build/android/pylib/utils/test_filter_test.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/time_profile.py create mode 100644 third_party/libwebrtc/build/android/pylib/utils/xvfb.py create mode 100644 third_party/libwebrtc/build/android/pylib/valgrind_tools.py create mode 100644 third_party/libwebrtc/build/android/pylintrc create mode 100644 third_party/libwebrtc/build/android/resource_sizes.gni create mode 100755 third_party/libwebrtc/build/android/resource_sizes.py create mode 100644 third_party/libwebrtc/build/android/resource_sizes.pydeps create mode 100755 third_party/libwebrtc/build/android/screenshot.py create mode 100644 third_party/libwebrtc/build/android/stacktrace/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/stacktrace/README.md create mode 100755 third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py create mode 100644 third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java create mode 100644 third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar create mode 100755 third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py create mode 100755 third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py create mode 100755 third_party/libwebrtc/build/android/stacktrace/stackwalker.py create mode 100644 third_party/libwebrtc/build/android/test/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/test/incremental_javac_gn/BUILD.gn create mode 100755 third_party/libwebrtc/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py create mode 100644 third_party/libwebrtc/build/android/test/missing_symbol_test.gni create mode 100644 third_party/libwebrtc/build/android/test/nocompile_gn/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/test/nocompile_gn/nocompile_sources.gni create mode 100755 third_party/libwebrtc/build/android/test_runner.py create mode 100644 third_party/libwebrtc/build/android/test_runner.pydeps create mode 100755 third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.py create mode 100644 third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.pydeps create mode 100644 third_party/libwebrtc/build/android/tests/symbolize/Makefile create mode 100644 third_party/libwebrtc/build/android/tests/symbolize/a.cc create mode 100644 third_party/libwebrtc/build/android/tests/symbolize/b.cc create mode 100644 third_party/libwebrtc/build/android/tests/symbolize/liba.so create mode 100644 third_party/libwebrtc/build/android/tests/symbolize/libb.so create mode 100755 third_party/libwebrtc/build/android/tombstones.py create mode 100644 third_party/libwebrtc/build/android/unused_resources/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/unused_resources/UnusedResources.java create mode 100755 third_party/libwebrtc/build/android/update_deps/update_third_party_deps.py create mode 100755 third_party/libwebrtc/build/android/update_verification.py create mode 100755 third_party/libwebrtc/build/android/video_recorder.py create mode 100644 third_party/libwebrtc/build/apple/OWNERS create mode 100644 third_party/libwebrtc/build/apple/README.md create mode 100644 third_party/libwebrtc/build/apple/apple_info_plist.gni create mode 100644 third_party/libwebrtc/build/apple/compile_entitlements.gni create mode 100644 third_party/libwebrtc/build/apple/compile_plist.gni create mode 100644 third_party/libwebrtc/build/apple/convert_plist.gni create mode 100644 third_party/libwebrtc/build/apple/plist_util.py create mode 100644 third_party/libwebrtc/build/apple/tweak_info_plist.gni create mode 100755 third_party/libwebrtc/build/apple/tweak_info_plist.py create mode 100644 third_party/libwebrtc/build/apple/write_pkg_info.py create mode 100755 third_party/libwebrtc/build/apple/xcrun.py create mode 100644 third_party/libwebrtc/build/args/OWNERS create mode 100644 third_party/libwebrtc/build/args/README.txt create mode 100644 third_party/libwebrtc/build/args/chromeos/README.md create mode 100644 third_party/libwebrtc/build/args/headless.gn create mode 100755 third_party/libwebrtc/build/build-ctags.sh create mode 100644 third_party/libwebrtc/build/build_config.h create mode 100644 third_party/libwebrtc/build/buildflag.h create mode 100644 third_party/libwebrtc/build/buildflag_header.gni create mode 100755 third_party/libwebrtc/build/check_gn_headers.py create mode 100755 third_party/libwebrtc/build/check_gn_headers_unittest.py create mode 100644 third_party/libwebrtc/build/check_gn_headers_whitelist.txt create mode 100755 third_party/libwebrtc/build/check_return_value.py create mode 100644 third_party/libwebrtc/build/chromeos/.style.yapf create mode 100644 third_party/libwebrtc/build/chromeos/OWNERS create mode 100644 third_party/libwebrtc/build/chromeos/PRESUBMIT.py create mode 100755 third_party/libwebrtc/build/chromeos/gen_skylab_runner.py create mode 100644 third_party/libwebrtc/build/chromeos/pylintrc create mode 100755 third_party/libwebrtc/build/chromeos/test_runner.py create mode 100755 third_party/libwebrtc/build/chromeos/test_runner_test.py create mode 100644 third_party/libwebrtc/build/ciopfs.sha1 create mode 100644 third_party/libwebrtc/build/cipd/cipd.gni create mode 100755 third_party/libwebrtc/build/cipd/cipd_from_file.py create mode 100755 third_party/libwebrtc/build/cipd/clobber_cipd_root.py create mode 100755 third_party/libwebrtc/build/clobber.py create mode 100644 third_party/libwebrtc/build/compiled_action.gni create mode 100755 third_party/libwebrtc/build/compute_build_timestamp.py create mode 100644 third_party/libwebrtc/build/config/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/BUILDCONFIG.gn create mode 100644 third_party/libwebrtc/build/config/OWNERS create mode 100644 third_party/libwebrtc/build/config/aix/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/android/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/android/OWNERS create mode 100644 third_party/libwebrtc/build/config/android/abi.gni create mode 100644 third_party/libwebrtc/build/config/android/android_nocompile.gni create mode 100644 third_party/libwebrtc/build/config/android/build_vars.gni create mode 100644 third_party/libwebrtc/build/config/android/channel.gni create mode 100644 third_party/libwebrtc/build/config/android/config.gni create mode 100644 third_party/libwebrtc/build/config/android/copy_ex.gni create mode 100644 third_party/libwebrtc/build/config/android/extract_unwind_tables.gni create mode 100644 third_party/libwebrtc/build/config/android/internal_rules.gni create mode 100644 third_party/libwebrtc/build/config/android/linker_version_script.gni create mode 100644 third_party/libwebrtc/build/config/android/rules.gni create mode 100644 third_party/libwebrtc/build/config/android/sdk.gni create mode 100644 third_party/libwebrtc/build/config/android/test/classpath_order/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/android/test/classpath_order/java/res_template/values/values.xml create mode 100644 third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/ClassPathOrderTest.java create mode 100644 third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/Dummy.java.jinja2 create mode 100644 third_party/libwebrtc/build/config/android/test/proto/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/android/test/proto/absolute_dep/absolute_dep.proto create mode 100644 third_party/libwebrtc/build/config/android/test/proto/relative_dep/relative_dep.proto create mode 100644 third_party/libwebrtc/build/config/android/test/proto/root/absolute_child.proto create mode 100644 third_party/libwebrtc/build/config/android/test/proto/root/absolute_root.proto create mode 100644 third_party/libwebrtc/build/config/android/test/proto/root/relative_child.proto create mode 100644 third_party/libwebrtc/build/config/android/test/proto/root/relative_root.proto create mode 100644 third_party/libwebrtc/build/config/android/test/resource_overlay/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/android/test/resource_overlay/java/res_template/values/values.xml create mode 100644 third_party/libwebrtc/build/config/android/test/resource_overlay/java/src/org/chromium/build/resource_overlay/ResourceOverlayTest.java create mode 100644 third_party/libwebrtc/build/config/apple/OWNERS create mode 100644 third_party/libwebrtc/build/config/apple/sdk_info.py create mode 100644 third_party/libwebrtc/build/config/apple/symbols.gni create mode 100644 third_party/libwebrtc/build/config/arm.gni create mode 100644 third_party/libwebrtc/build/config/buildflags_paint_preview.gni create mode 100644 third_party/libwebrtc/build/config/c++/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/c++/c++.gni create mode 100644 third_party/libwebrtc/build/config/c++/libc++.natvis create mode 100644 third_party/libwebrtc/build/config/chrome_build.gni create mode 100644 third_party/libwebrtc/build/config/chromecast/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/chromecast_build.gni create mode 100644 third_party/libwebrtc/build/config/chromeos/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/chromeos/args.gni create mode 100644 third_party/libwebrtc/build/config/chromeos/rules.gni create mode 100644 third_party/libwebrtc/build/config/chromeos/ui_mode.gni create mode 100644 third_party/libwebrtc/build/config/clang/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/clang/clang.gni create mode 100644 third_party/libwebrtc/build/config/compiler/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/compiler/compiler.gni create mode 100644 third_party/libwebrtc/build/config/compiler/pgo/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/compiler/pgo/pgo.gni create mode 100644 third_party/libwebrtc/build/config/compute_inputs_for_analyze.gni create mode 100644 third_party/libwebrtc/build/config/coverage/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/coverage/OWNERS create mode 100644 third_party/libwebrtc/build/config/coverage/coverage.gni create mode 100644 third_party/libwebrtc/build/config/crypto.gni create mode 100644 third_party/libwebrtc/build/config/dcheck_always_on.gni create mode 100644 third_party/libwebrtc/build/config/devtools.gni create mode 100644 third_party/libwebrtc/build/config/features.gni create mode 100644 third_party/libwebrtc/build/config/freetype/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/freetype/OWNERS create mode 100644 third_party/libwebrtc/build/config/freetype/freetype.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/fuchsia/DIR_METADATA create mode 100644 third_party/libwebrtc/build/config/fuchsia/OWNERS create mode 100644 third_party/libwebrtc/build/config/fuchsia/build_cmx_from_fragment.py create mode 100755 third_party/libwebrtc/build/config/fuchsia/build_symbol_archive.py create mode 100644 third_party/libwebrtc/build/config/fuchsia/config.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/extend_fvm.py create mode 100644 third_party/libwebrtc/build/config/fuchsia/generate_runner_scripts.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/gfx_tests.cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/package.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/rules.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/sizes.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/symbol_archive.gni create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/OWNERS create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/README.md create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/audio_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/font_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/jit_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/minimum_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/network_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/present_view_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/read_debug_data.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/test_logger_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/vulkan_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/web_engine_required_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/fuchsia/test/web_instance_host_capabilities.test-cmx create mode 100644 third_party/libwebrtc/build/config/gcc/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/gclient_args.gni create mode 100755 third_party/libwebrtc/build/config/get_host_byteorder.py create mode 100644 third_party/libwebrtc/build/config/host_byteorder.gni create mode 100644 third_party/libwebrtc/build/config/ios/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/ios/BuildInfo.plist create mode 100644 third_party/libwebrtc/build/config/ios/Host-Info.plist create mode 100644 third_party/libwebrtc/build/config/ios/Module-Info.plist create mode 100644 third_party/libwebrtc/build/config/ios/OWNERS create mode 100644 third_party/libwebrtc/build/config/ios/asset_catalog.gni create mode 100644 third_party/libwebrtc/build/config/ios/codesign.py create mode 100644 third_party/libwebrtc/build/config/ios/compile_ib_files.py create mode 100644 third_party/libwebrtc/build/config/ios/compile_xcassets_unittests.py create mode 100644 third_party/libwebrtc/build/config/ios/config.gni create mode 100644 third_party/libwebrtc/build/config/ios/dummy.py create mode 100644 third_party/libwebrtc/build/config/ios/entitlements.plist create mode 100644 third_party/libwebrtc/build/config/ios/find_signing_identity.py create mode 100644 third_party/libwebrtc/build/config/ios/generate_umbrella_header.py create mode 100644 third_party/libwebrtc/build/config/ios/hardlink.py create mode 100644 third_party/libwebrtc/build/config/ios/ios_sdk.gni create mode 100644 third_party/libwebrtc/build/config/ios/ios_sdk_overrides.gni create mode 100644 third_party/libwebrtc/build/config/ios/ios_test_runner_wrapper.gni create mode 100644 third_party/libwebrtc/build/config/ios/resources/XCTRunnerAddition+Info.plist create mode 100644 third_party/libwebrtc/build/config/ios/rules.gni create mode 100644 third_party/libwebrtc/build/config/ios/strip_arm64e.py create mode 100644 third_party/libwebrtc/build/config/ios/write_framework_hmap.py create mode 100644 third_party/libwebrtc/build/config/ios/write_framework_modulemap.py create mode 100644 third_party/libwebrtc/build/config/ios/xctest_shell.mm create mode 100644 third_party/libwebrtc/build/config/linux/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/OWNERS create mode 100644 third_party/libwebrtc/build/config/linux/atk/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/atspi2/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/dbus/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/dri/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/gtk/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/gtk/gtk.gni create mode 100644 third_party/libwebrtc/build/config/linux/libdrm/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/libffi/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/libva/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/nss/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/pangocairo/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/linux/pangocairo/pangocairo.gni create mode 100755 third_party/libwebrtc/build/config/linux/pkg-config.py create mode 100644 third_party/libwebrtc/build/config/linux/pkg_config.gni create mode 100644 third_party/libwebrtc/build/config/locales.gni create mode 100644 third_party/libwebrtc/build/config/logging.gni create mode 100644 third_party/libwebrtc/build/config/mac/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/mac/BuildInfo.plist create mode 100644 third_party/libwebrtc/build/config/mac/OWNERS create mode 100644 third_party/libwebrtc/build/config/mac/mac_sdk.gni create mode 100644 third_party/libwebrtc/build/config/mac/mac_sdk_overrides.gni create mode 100644 third_party/libwebrtc/build/config/mac/package_framework.py create mode 100644 third_party/libwebrtc/build/config/mac/prepare_framework_version.py create mode 100644 third_party/libwebrtc/build/config/mac/rules.gni create mode 100644 third_party/libwebrtc/build/config/mips.gni create mode 100644 third_party/libwebrtc/build/config/nacl/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/nacl/config.gni create mode 100644 third_party/libwebrtc/build/config/nacl/rules.gni create mode 100644 third_party/libwebrtc/build/config/ozone.gni create mode 100644 third_party/libwebrtc/build/config/ozone_extra.gni create mode 100644 third_party/libwebrtc/build/config/pch.gni create mode 100644 third_party/libwebrtc/build/config/posix/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/profiling/OWNERS create mode 100644 third_party/libwebrtc/build/config/profiling/profiling.gni create mode 100644 third_party/libwebrtc/build/config/python.gni create mode 100644 third_party/libwebrtc/build/config/rts.gni create mode 100644 third_party/libwebrtc/build/config/rust.gni create mode 100644 third_party/libwebrtc/build/config/sanitizers/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/sanitizers/OWNERS create mode 100644 third_party/libwebrtc/build/config/sanitizers/sanitizers.gni create mode 100644 third_party/libwebrtc/build/config/sysroot.gni create mode 100644 third_party/libwebrtc/build/config/ui.gni create mode 100644 third_party/libwebrtc/build/config/v8_target_cpu.gni create mode 100644 third_party/libwebrtc/build/config/win/BUILD.gn create mode 100644 third_party/libwebrtc/build/config/win/console_app.gni create mode 100644 third_party/libwebrtc/build/config/win/control_flow_guard.gni create mode 100644 third_party/libwebrtc/build/config/win/manifest.gni create mode 100644 third_party/libwebrtc/build/config/win/visual_studio_version.gni create mode 100644 third_party/libwebrtc/build/config/x64.gni create mode 100644 third_party/libwebrtc/build/config/zip.gni create mode 100755 third_party/libwebrtc/build/copy_test_data_ios.py create mode 100755 third_party/libwebrtc/build/cp.py create mode 100755 third_party/libwebrtc/build/detect_host_arch.py create mode 100755 third_party/libwebrtc/build/dir_exists.py create mode 100644 third_party/libwebrtc/build/docs/debugging_slow_builds.md create mode 100644 third_party/libwebrtc/build/docs/mac_hermetic_toolchain.md create mode 100644 third_party/libwebrtc/build/docs/rust_toolchain.md create mode 100644 third_party/libwebrtc/build/docs/writing_gn_templates.md create mode 100644 third_party/libwebrtc/build/dotfile_settings.gni create mode 100755 third_party/libwebrtc/build/download_nacl_toolchains.py create mode 100755 third_party/libwebrtc/build/env_dump.py create mode 100755 third_party/libwebrtc/build/extract_from_cab.py create mode 100755 third_party/libwebrtc/build/extract_partition.py create mode 100755 third_party/libwebrtc/build/find_depot_tools.py create mode 100755 third_party/libwebrtc/build/fix_gn_headers.py create mode 100644 third_party/libwebrtc/build/fuchsia/DIR_METADATA create mode 100644 third_party/libwebrtc/build/fuchsia/OWNERS create mode 100644 third_party/libwebrtc/build/fuchsia/PRESUBMIT.py create mode 100644 third_party/libwebrtc/build/fuchsia/__init__.py create mode 100644 third_party/libwebrtc/build/fuchsia/aemu_target.py create mode 100755 third_party/libwebrtc/build/fuchsia/binary_sizes.py create mode 100755 third_party/libwebrtc/build/fuchsia/binary_sizes_test.py create mode 100644 third_party/libwebrtc/build/fuchsia/boot_data.py create mode 100755 third_party/libwebrtc/build/fuchsia/boot_data_test.py create mode 100644 third_party/libwebrtc/build/fuchsia/common.py create mode 100644 third_party/libwebrtc/build/fuchsia/common_args.py create mode 100755 third_party/libwebrtc/build/fuchsia/deploy_to_pkg_repo.py create mode 100644 third_party/libwebrtc/build/fuchsia/device_target.py create mode 100755 third_party/libwebrtc/build/fuchsia/device_target_test.py create mode 100644 third_party/libwebrtc/build/fuchsia/emu_target.py create mode 100644 third_party/libwebrtc/build/fuchsia/fvdl_target.py create mode 100755 third_party/libwebrtc/build/fuchsia/fvdl_target_test.py create mode 100644 third_party/libwebrtc/build/fuchsia/generic_x64_target.py create mode 100644 third_party/libwebrtc/build/fuchsia/linux.sdk.sha1 create mode 100644 third_party/libwebrtc/build/fuchsia/mac.sdk.sha1 create mode 100644 third_party/libwebrtc/build/fuchsia/net_test_server.py create mode 100644 third_party/libwebrtc/build/fuchsia/pkg_repo.py create mode 100644 third_party/libwebrtc/build/fuchsia/qemu_image.py create mode 100644 third_party/libwebrtc/build/fuchsia/qemu_target.py create mode 100755 third_party/libwebrtc/build/fuchsia/qemu_target_test.py create mode 100644 third_party/libwebrtc/build/fuchsia/remote_cmd.py create mode 100644 third_party/libwebrtc/build/fuchsia/run_test_package.py create mode 100644 third_party/libwebrtc/build/fuchsia/runner_exceptions.py create mode 100644 third_party/libwebrtc/build/fuchsia/runner_logs.py create mode 100644 third_party/libwebrtc/build/fuchsia/sdk-bucket.txt create mode 100644 third_party/libwebrtc/build/fuchsia/sdk-hash-files.list create mode 100755 third_party/libwebrtc/build/fuchsia/start_emulator.py create mode 100644 third_party/libwebrtc/build/fuchsia/symbolizer.py create mode 100644 third_party/libwebrtc/build/fuchsia/target.py create mode 100755 third_party/libwebrtc/build/fuchsia/test_runner.py create mode 100755 third_party/libwebrtc/build/fuchsia/update_images.py create mode 100755 third_party/libwebrtc/build/fuchsia/update_sdk.py create mode 100755 third_party/libwebrtc/build/gdb-add-index create mode 100755 third_party/libwebrtc/build/get_landmines.py create mode 100755 third_party/libwebrtc/build/get_symlink_targets.py create mode 100644 third_party/libwebrtc/build/gn_helpers.py create mode 100644 third_party/libwebrtc/build/gn_helpers_unittest.py create mode 100644 third_party/libwebrtc/build/gn_logs.gni create mode 100644 third_party/libwebrtc/build/gn_run_binary.py create mode 100755 third_party/libwebrtc/build/install-build-deps-android.sh create mode 100755 third_party/libwebrtc/build/install-build-deps.sh create mode 100755 third_party/libwebrtc/build/install-chroot.sh create mode 100644 third_party/libwebrtc/build/internal/README.chromium create mode 100644 third_party/libwebrtc/build/ios/OWNERS create mode 100644 third_party/libwebrtc/build/ios/intent_definition.gni create mode 100644 third_party/libwebrtc/build/lacros/BUILD.gn create mode 100644 third_party/libwebrtc/build/lacros/OWNERS create mode 100644 third_party/libwebrtc/build/lacros/PRESUBMIT.py create mode 100644 third_party/libwebrtc/build/lacros/lacros_resource_sizes.gni create mode 100755 third_party/libwebrtc/build/lacros/lacros_resource_sizes.py create mode 100644 third_party/libwebrtc/build/lacros/lacros_resource_sizes.pydeps create mode 100644 third_party/libwebrtc/build/lacros/metadata.json.in create mode 100755 third_party/libwebrtc/build/lacros/mojo_connection_lacros_launcher.py create mode 100755 third_party/libwebrtc/build/lacros/test_runner.py create mode 100755 third_party/libwebrtc/build/lacros/test_runner_test.py create mode 100644 third_party/libwebrtc/build/landmine_utils.py create mode 100755 third_party/libwebrtc/build/landmines.py create mode 100644 third_party/libwebrtc/build/linux/BUILD.gn create mode 100644 third_party/libwebrtc/build/linux/OWNERS create mode 100644 third_party/libwebrtc/build/linux/chrome.map create mode 100644 third_party/libwebrtc/build/linux/dump_app_syms.py create mode 100644 third_party/libwebrtc/build/linux/extract_symbols.gni create mode 100755 third_party/libwebrtc/build/linux/install-chromeos-fonts.py create mode 100644 third_party/libwebrtc/build/linux/libncursesw/DIR_METADATA create mode 100644 third_party/libwebrtc/build/linux/libncursesw/OWNERS create mode 100644 third_party/libwebrtc/build/linux/libpci/BUILD.gn create mode 100644 third_party/libwebrtc/build/linux/libudev/BUILD.gn create mode 100755 third_party/libwebrtc/build/linux/rewrite_dirs.py create mode 100644 third_party/libwebrtc/build/linux/strip_binary.gni create mode 100755 third_party/libwebrtc/build/linux/strip_binary.py create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/build_and_upload.py create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/debian_archive_unstable.gpg create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols.py create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols_unittest.py create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/generate_debian_archive_unstable_gpg.sh create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.amd64 create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm64 create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.armel create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.i386 create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mips64el create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mipsel create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/install-sysroot.py create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/libdbus-1-3-symbols create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/libxcomposite1-symbols create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/libxkbcommon0-symbols create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/merge-package-lists.py create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator-sid.sh create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator.sh create mode 100644 third_party/libwebrtc/build/linux/sysroot_scripts/sysroots.json create mode 100755 third_party/libwebrtc/build/linux/sysroot_scripts/update-archive-timestamp.sh create mode 100644 third_party/libwebrtc/build/linux/unbundle/README create mode 100644 third_party/libwebrtc/build/linux/unbundle/ffmpeg.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/flac.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/fontconfig.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/freetype.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/harfbuzz-ng.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/icu.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libdrm.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libevent.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libjpeg.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libpng.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libvpx.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libwebp.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libxml.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/libxslt.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/openh264.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/opus.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/re2.gn create mode 100755 third_party/libwebrtc/build/linux/unbundle/remove_bundled_libraries.py create mode 100755 third_party/libwebrtc/build/linux/unbundle/replace_gn_files.py create mode 100644 third_party/libwebrtc/build/linux/unbundle/snappy.gn create mode 100644 third_party/libwebrtc/build/linux/unbundle/zlib.gn create mode 100755 third_party/libwebrtc/build/locale_tool.py create mode 100644 third_party/libwebrtc/build/mac/OWNERS create mode 100755 third_party/libwebrtc/build/mac/find_sdk.py create mode 100755 third_party/libwebrtc/build/mac/should_use_hermetic_xcode.py create mode 100755 third_party/libwebrtc/build/mac_toolchain.py create mode 100644 third_party/libwebrtc/build/nocompile.gni create mode 100644 third_party/libwebrtc/build/noop.py create mode 100644 third_party/libwebrtc/build/partitioned_shared_library.gni create mode 100644 third_party/libwebrtc/build/precompile.cc create mode 100644 third_party/libwebrtc/build/precompile.h create mode 100755 third_party/libwebrtc/build/print_python_deps.py create mode 100755 third_party/libwebrtc/build/protoc_java.py create mode 100644 third_party/libwebrtc/build/protoc_java.pydeps create mode 100644 third_party/libwebrtc/build/redirect_stdout.py create mode 100755 third_party/libwebrtc/build/rm.py create mode 100644 third_party/libwebrtc/build/rust/OWNERS create mode 100644 third_party/libwebrtc/build/rust/std/BUILD.gn create mode 100755 third_party/libwebrtc/build/rust/std/find_std_rlibs.py create mode 100644 third_party/libwebrtc/build/rust/std/immediate_crash.h create mode 100644 third_party/libwebrtc/build/rust/std/remap_alloc.c create mode 100644 third_party/libwebrtc/build/rust/tests/BUILD.gn create mode 100644 third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/BUILD.gn create mode 100644 third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/main.cc create mode 100644 third_party/libwebrtc/build/rust/tests/test_rust_exe/BUILD.gn create mode 100644 third_party/libwebrtc/build/rust/tests/test_rust_exe/main.rs create mode 100644 third_party/libwebrtc/build/rust/tests/test_rust_source_set/BUILD.gn create mode 100644 third_party/libwebrtc/build/rust/tests/test_rust_source_set/main.rs create mode 100644 third_party/libwebrtc/build/sample_arg_file.gn create mode 100644 third_party/libwebrtc/build/sanitize-mac-build-log.sed create mode 100755 third_party/libwebrtc/build/sanitize-mac-build-log.sh create mode 100644 third_party/libwebrtc/build/sanitize-win-build-log.sed create mode 100755 third_party/libwebrtc/build/sanitize-win-build-log.sh create mode 100644 third_party/libwebrtc/build/sanitizers/OWNERS create mode 100644 third_party/libwebrtc/build/sanitizers/asan_suppressions.cc create mode 100644 third_party/libwebrtc/build/sanitizers/lsan_suppressions.cc create mode 100644 third_party/libwebrtc/build/sanitizers/sanitizer_options.cc create mode 100644 third_party/libwebrtc/build/sanitizers/tsan_suppressions.cc create mode 100644 third_party/libwebrtc/build/shim_headers.gni create mode 100644 third_party/libwebrtc/build/skia_gold_common/.style.yapf create mode 100644 third_party/libwebrtc/build/skia_gold_common/OWNERS create mode 100644 third_party/libwebrtc/build/skia_gold_common/PRESUBMIT.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/README.md create mode 100644 third_party/libwebrtc/build/skia_gold_common/__init__.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session.py create mode 100755 third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session_unittest.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/skia_gold_properties.py create mode 100755 third_party/libwebrtc/build/skia_gold_common/skia_gold_properties_unittest.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/skia_gold_session.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager.py create mode 100755 third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager_unittest.py create mode 100755 third_party/libwebrtc/build/skia_gold_common/skia_gold_session_unittest.py create mode 100644 third_party/libwebrtc/build/skia_gold_common/unittest_utils.py create mode 100644 third_party/libwebrtc/build/symlink.gni create mode 100755 third_party/libwebrtc/build/symlink.py create mode 100644 third_party/libwebrtc/build/timestamp.gni create mode 100644 third_party/libwebrtc/build/toolchain/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/OWNERS create mode 100644 third_party/libwebrtc/build/toolchain/aix/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/android/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/android/OWNERS create mode 100644 third_party/libwebrtc/build/toolchain/apple/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/apple/OWNERS create mode 100644 third_party/libwebrtc/build/toolchain/apple/filter_libtool.py create mode 100644 third_party/libwebrtc/build/toolchain/apple/get_tool_mtime.py create mode 100755 third_party/libwebrtc/build/toolchain/apple/linker_driver.py create mode 100644 third_party/libwebrtc/build/toolchain/apple/toolchain.gni create mode 100644 third_party/libwebrtc/build/toolchain/cc_wrapper.gni create mode 100755 third_party/libwebrtc/build/toolchain/clang_code_coverage_wrapper.py create mode 100644 third_party/libwebrtc/build/toolchain/concurrent_links.gni create mode 100644 third_party/libwebrtc/build/toolchain/cros/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/cros_toolchain.gni create mode 100644 third_party/libwebrtc/build/toolchain/fuchsia/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/fuchsia/DIR_METADATA create mode 100644 third_party/libwebrtc/build/toolchain/fuchsia/OWNERS create mode 100755 third_party/libwebrtc/build/toolchain/gcc_link_wrapper.py create mode 100755 third_party/libwebrtc/build/toolchain/gcc_solink_wrapper.py create mode 100644 third_party/libwebrtc/build/toolchain/gcc_toolchain.gni create mode 100755 third_party/libwebrtc/build/toolchain/get_concurrent_links.py create mode 100644 third_party/libwebrtc/build/toolchain/get_cpu_count.py create mode 100644 third_party/libwebrtc/build/toolchain/get_goma_dir.py create mode 100644 third_party/libwebrtc/build/toolchain/goma.gni create mode 100644 third_party/libwebrtc/build/toolchain/ios/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/ios/OWNERS create mode 100644 third_party/libwebrtc/build/toolchain/ios/compile_xcassets.py create mode 100644 third_party/libwebrtc/build/toolchain/ios/swiftc.py create mode 100644 third_party/libwebrtc/build/toolchain/kythe.gni create mode 100644 third_party/libwebrtc/build/toolchain/linux/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/linux/unbundle/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/linux/unbundle/README.md create mode 100644 third_party/libwebrtc/build/toolchain/mac/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/mac/OWNERS create mode 100644 third_party/libwebrtc/build/toolchain/nacl/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/nacl_toolchain.gni create mode 100644 third_party/libwebrtc/build/toolchain/rbe.gni create mode 100644 third_party/libwebrtc/build/toolchain/toolchain.gni create mode 100644 third_party/libwebrtc/build/toolchain/win/BUILD.gn create mode 100644 third_party/libwebrtc/build/toolchain/win/midl.gni create mode 100644 third_party/libwebrtc/build/toolchain/win/midl.py create mode 100755 third_party/libwebrtc/build/toolchain/win/ml.py create mode 100644 third_party/libwebrtc/build/toolchain/win/rc/.gitignore create mode 100644 third_party/libwebrtc/build/toolchain/win/rc/README.md create mode 100644 third_party/libwebrtc/build/toolchain/win/rc/linux64/rc.sha1 create mode 100644 third_party/libwebrtc/build/toolchain/win/rc/mac/rc.sha1 create mode 100755 third_party/libwebrtc/build/toolchain/win/rc/rc.py create mode 100755 third_party/libwebrtc/build/toolchain/win/rc/upload_rc_binaries.sh create mode 100644 third_party/libwebrtc/build/toolchain/win/rc/win/rc.exe.sha1 create mode 100644 third_party/libwebrtc/build/toolchain/win/setup_toolchain.py create mode 100644 third_party/libwebrtc/build/toolchain/win/tool_wrapper.py create mode 100644 third_party/libwebrtc/build/toolchain/wrapper_utils.py create mode 100755 third_party/libwebrtc/build/tree_truth.sh create mode 100755 third_party/libwebrtc/build/update-linux-sandbox.sh create mode 100644 third_party/libwebrtc/build/util/BUILD.gn create mode 100644 third_party/libwebrtc/build/util/LASTCHANGE.committime create mode 100644 third_party/libwebrtc/build/util/LASTCHANGE.dummy create mode 100644 third_party/libwebrtc/build/util/PRESUBMIT.py create mode 100644 third_party/libwebrtc/build/util/android_chrome_version.py create mode 100644 third_party/libwebrtc/build/util/android_chrome_version_test.py create mode 100644 third_party/libwebrtc/build/util/branding.gni create mode 100644 third_party/libwebrtc/build/util/chromium_git_revision.h.in create mode 100644 third_party/libwebrtc/build/util/generate_wrapper.gni create mode 100755 third_party/libwebrtc/build/util/generate_wrapper.py create mode 100644 third_party/libwebrtc/build/util/java_action.gni create mode 100755 third_party/libwebrtc/build/util/java_action.py create mode 100644 third_party/libwebrtc/build/util/lastchange.gni create mode 100755 third_party/libwebrtc/build/util/lastchange.py create mode 100644 third_party/libwebrtc/build/util/lib/__init__.py create mode 100644 third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py create mode 100644 third_party/libwebrtc/build/util/lib/common/__init__.py create mode 100644 third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py create mode 100644 third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py create mode 100644 third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py create mode 100644 third_party/libwebrtc/build/util/lib/common/unittest_util.py create mode 100755 third_party/libwebrtc/build/util/lib/common/unittest_util_test.py create mode 100644 third_party/libwebrtc/build/util/lib/common/util.py create mode 100644 third_party/libwebrtc/build/util/lib/results/__init__.py create mode 100644 third_party/libwebrtc/build/util/lib/results/result_sink.py create mode 100755 third_party/libwebrtc/build/util/lib/results/result_sink_test.py create mode 100644 third_party/libwebrtc/build/util/lib/results/result_types.py create mode 100644 third_party/libwebrtc/build/util/process_version.gni create mode 100644 third_party/libwebrtc/build/util/version.gni create mode 100755 third_party/libwebrtc/build/util/version.py create mode 100644 third_party/libwebrtc/build/util/version_test.py create mode 100755 third_party/libwebrtc/build/vs_toolchain.py create mode 100644 third_party/libwebrtc/build/whitespace_file.txt create mode 100644 third_party/libwebrtc/build/win/BUILD.gn create mode 100644 third_party/libwebrtc/build/win/as_invoker.manifest create mode 100644 third_party/libwebrtc/build/win/common_controls.manifest create mode 100644 third_party/libwebrtc/build/win/compatibility.manifest create mode 100755 third_party/libwebrtc/build/win/copy_cdb_to_output.py create mode 100644 third_party/libwebrtc/build/win/gn_meta_sln.py create mode 100644 third_party/libwebrtc/build/win/message_compiler.gni create mode 100644 third_party/libwebrtc/build/win/message_compiler.py create mode 100755 third_party/libwebrtc/build/win/reorder-imports.py create mode 100644 third_party/libwebrtc/build/win/require_administrator.manifest create mode 100644 third_party/libwebrtc/build/win/segment_heap.manifest create mode 100755 third_party/libwebrtc/build/win/set_appcontainer_acls.py create mode 100755 third_party/libwebrtc/build/win/use_ansi_codes.py create mode 100755 third_party/libwebrtc/build/write_build_date_header.py create mode 100755 third_party/libwebrtc/build/write_buildflag_header.py create mode 100644 third_party/libwebrtc/build/xcode_binaries.yaml (limited to 'third_party/libwebrtc/build') diff --git a/third_party/libwebrtc/build/.gitignore b/third_party/libwebrtc/build/.gitignore new file mode 100644 index 0000000000..22046984f5 --- /dev/null +++ b/third_party/libwebrtc/build/.gitignore @@ -0,0 +1,29 @@ +# This file is needed for projects that has this directory as a separate Git +# mirror in DEPS. Without it, a lot is wiped and re-downloaded for each sync. +*.pyc +ciopfs +/android/bin +/android/binary_size/apks/**/*.apk +/args/chromeos/*.gni +/args/chromeos/rewrapper* +/config/gclient_args.gni +/cros_cache/ +/Debug +/Debug_x64 +/fuchsia/internal/ +/goma +/gomacc.lock +/ipch/ +/lacros/prebuilt_ash_chrome/ +/Release +/Release_x64 +/win_toolchain.json +/util/LASTCHANGE* +/util/support +/x64/ +/linux/debian_*-sysroot/ +/linux/ubuntu_*-sysroot/ +/ios_files +/mac_files + +!/util/LASTCHANGE.dummy diff --git a/third_party/libwebrtc/build/.style.yapf b/third_party/libwebrtc/build/.style.yapf new file mode 100644 index 0000000000..b4ebbe2467 --- /dev/null +++ b/third_party/libwebrtc/build/.style.yapf @@ -0,0 +1,6 @@ +[style] +based_on_style = pep8 + +# New directories should use a .style.yapf that does not include the following: +column_limit = 80 +indent_width = 2 diff --git a/third_party/libwebrtc/build/BUILD.gn b/third_party/libwebrtc/build/BUILD.gn new file mode 100644 index 0000000000..b1724eb6a0 --- /dev/null +++ b/third_party/libwebrtc/build/BUILD.gn @@ -0,0 +1,62 @@ +# Copyright 2018 The Chromium 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/buildflag_header.gni") +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") + +source_set("buildflag_header_h") { + sources = [ "buildflag.h" ] +} + +buildflag_header("branding_buildflags") { + header = "branding_buildflags.h" + + if (is_chrome_branded) { + flags = [ + "CHROMIUM_BRANDING=0", + "GOOGLE_CHROME_BRANDING=1", + ] + } else { + flags = [ + "CHROMIUM_BRANDING=1", + "GOOGLE_CHROME_BRANDING=0", + ] + } +} + +buildflag_header("chromecast_buildflags") { + header = "chromecast_buildflags.h" + + flags = [ "IS_CHROMECAST=$is_chromecast" ] +} + +buildflag_header("chromeos_buildflags") { + header = "chromeos_buildflags.h" + + flags = [ + "IS_CHROMEOS_DEVICE=$is_chromeos_device", + + "IS_CHROMEOS_LACROS=$is_chromeos_lacros", + "IS_CHROMEOS_ASH=$is_chromeos_ash", + ] +} + +buildflag_header("os_buildflags") { + header = "os_buildflags.h" + flags = [ + "IS_ANDROID=$is_android", + "IS_CHROMEOS=$is_chromeos", + "IS_FUCHSIA=$is_fuchsia", + "IS_IOS=$is_ios", + "IS_LINUX=$is_linux", + "IS_MAC=$is_mac", + "IS_NACL=$is_nacl", + "IS_WIN=$is_win", + "IS_APPLE=$is_apple", + "IS_POSIX=$is_posix", + ] +} diff --git a/third_party/libwebrtc/build/DIR_METADATA b/third_party/libwebrtc/build/DIR_METADATA new file mode 100644 index 0000000000..c914ddc4a7 --- /dev/null +++ b/third_party/libwebrtc/build/DIR_METADATA @@ -0,0 +1,5 @@ +monorail { + component: "Build" +} + +team_email: "build@chromium.org" diff --git a/third_party/libwebrtc/build/OWNERS b/third_party/libwebrtc/build/OWNERS new file mode 100644 index 0000000000..bd907811e7 --- /dev/null +++ b/third_party/libwebrtc/build/OWNERS @@ -0,0 +1,26 @@ +set noparent +# NOTE: keep this in sync with lsc-owners-override@chromium.org owners +agrieve@chromium.org +brucedawson@chromium.org +dpranke@google.com +jochen@chromium.org +thakis@chromium.org +thomasanderson@chromium.org +tikuta@chromium.org + +# Clang build config changes: +file://tools/clang/scripts/OWNERS + +# For java build changes: +wnwen@chromium.org + +# NOTE: keep this in sync with lsc-owners-override@chromium.org owners + +per-file .gitignore=* +per-file check_gn_headers_whitelist.txt=* +per-file mac_toolchain.py=erikchen@chromium.org +per-file mac_toolchain.py=justincohen@chromium.org +per-file whitespace_file.txt=* +per-file OWNERS.status=* +per-file OWNERS.setnoparent=set noparent +per-file OWNERS.setnoparent=file://ENG_REVIEW_OWNERS diff --git a/third_party/libwebrtc/build/OWNERS.setnoparent b/third_party/libwebrtc/build/OWNERS.setnoparent new file mode 100644 index 0000000000..02d1d287b7 --- /dev/null +++ b/third_party/libwebrtc/build/OWNERS.setnoparent @@ -0,0 +1,70 @@ +# List of OWNERS files that can be used together with "set noparent". See +# docs/code_reviews.md#owners-file-details for more details. + +# Overall project governance. +file://ENG_REVIEW_OWNERS + +# Third-party dependency review, see //docs/adding_to_third_party.md +file://third_party/OWNERS + +# Security reviews +file://chromeos/SECURITY_OWNERS +file://content/browser/SITE_ISOLATION_OWNERS +file://fuchsia/SECURITY_OWNERS +file://ipc/SECURITY_OWNERS +file://net/base/SECURITY_OWNERS +file://sandbox/linux/OWNERS +file://sandbox/mac/OWNERS +file://sandbox/OWNERS +file://sandbox/win/OWNERS +file://third_party/blink/SECURITY_OWNERS + +# Privacy reviews +file://tools/traffic_annotation/summary/TRAFFIC_ANNOTATION_OWNERS +file://tools/metrics/ukm/PRIVACY_OWNERS +file://base/metrics/OWNERS + +# Blink API owners are responsible for decisions about what APIs Blink should +# expose to the open web. +file://third_party/blink/API_OWNERS + +# third_party/blink/web_tests/VirtualTestSuites need special care. +file://third_party/blink/web_tests/VIRTUAL_OWNERS + +# Extension related files. +file://chrome/browser/extensions/component_extensions_allowlist/EXTENSION_ALLOWLIST_OWNERS +file://extensions/common/api/API_OWNERS + +# This restriction is in place to avoid accidental addition to our top level +# layout files, such as add duplicated assets, or introducing new colors when +# we don't want them. +file://ui/android/java/res/LAYOUT_OWNERS + +# Updating policy_templates.json can have drastic effects for systems depending +# on policy definitions (for example, Google's cloud management tools for +# Chrome and Chrome OS). +# The rules are documented at: +# https://sites.google.com/a/chromium.org/dev/developers/how-tos/enterprise/adding-new-policies +file://components/policy/resources/ENTERPRISE_POLICY_OWNERS + +# This restriction is in place due to the complicated compliance regulations +# around this code. +file://chrome/android/java/src/org/chromium/chrome/browser/searchwidget/COMPLIANCE_OWNERS + +# Notification channels appear in system UI and are persisted forever by +# Android, so should not be added or removed lightly, and the proper +# deprecation and versioning steps must be taken when doing so. +file://chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/channels/NOTIFICATION_CHANNEL_OWNERS + +# The Weblayer API is supposed to be stable and will be used outside of the +# chromium repository. +file://weblayer/API_OWNERS + +# New features for lock/login UI on Chrome OS need to work stably in all corner +# cases. +file://ash/login/LOGIN_LOCK_OWNERS + +# Changes to the CQ configuration can have a significant impact on the CQ cost +# and performance. Approval should be limited to a small subset of the users +# that can make infra changes. +file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS diff --git a/third_party/libwebrtc/build/OWNERS.status b/third_party/libwebrtc/build/OWNERS.status new file mode 100644 index 0000000000..f5cc1fc8ba --- /dev/null +++ b/third_party/libwebrtc/build/OWNERS.status @@ -0,0 +1,12 @@ +# Use this file to set a global status message that should be shown whenever +# git cl owners proposes to add you as a reviewer. +# +# The status messages should be somewhat stable, so please don't use this for +# short term, or frequently changing updates. +# +# The format of the file is +# +# you@chromium.org: Single line status message. +# + +jochen@chromium.org: EMEA based reviewer. diff --git a/third_party/libwebrtc/build/README.md b/third_party/libwebrtc/build/README.md new file mode 100644 index 0000000000..2667125088 --- /dev/null +++ b/third_party/libwebrtc/build/README.md @@ -0,0 +1,36 @@ +# About +`//build` contains: + * Core GN templates and configuration + * Core Python build scripts + +Since this directory is DEPS'ed in by some other repositories (webrtc, pdfium, +v8, etc), it should be kept as self-contained as possible by not referring +to files outside of it. Some exceptions exist (`//testing`, select +`//third_party` subdirectories), but new dependencies tend to break these other +projects, and so should be avoided. + +Changes to `//build` should be landed in the Chromium repo. They will then be +replicated to the stand-alone [build repo](https://chromium.googlesource.com/chromium/src/build) +by the [gsubtreed tool.](https://chromium.googlesource.com/infra/infra/+/main/infra/services/gsubtreed) +Note: You can find all directories already available through gsubtreed in the +[list of all chromium repos](https://chromium.googlesource.com/). + +## Contents + * `//build/config` - Common templates via `.gni` files. + * `//build/toolchain` - GN toolchain definitions. + * `Other .py files` - Some are used by GN/Ninja. Some by gclient hooks, some + are just random utilities. + +Files referenced by `//.gn`: + * `//build/BUILDCONFIG.gn` - Included by all `BUILD.gn` files. + * `//build/secondary` - An overlay for `BUILD.gn` files. Enables adding + `BUILD.gn` to directories that live in sub-repositories. + * `//build_overrides` - + Refer to [//build_overrides/README.md](../build_overrides/README.md). + +## Docs + +* [Writing GN Templates](docs/writing_gn_templates.md) +* [Debugging Slow Builds](docs/debugging_slow_builds.md) +* [Mac Hermetic Toolchains](docs/mac_hermetic_toolchain.md) +* [Android Build Documentation](android/docs/README.md) diff --git a/third_party/libwebrtc/build/add_rts_filters.py b/third_party/libwebrtc/build/add_rts_filters.py new file mode 100755 index 0000000000..4186c39333 --- /dev/null +++ b/third_party/libwebrtc/build/add_rts_filters.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# Copyright (c) 2021 The Chromium 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 dummy RTS filter file if a real one doesn't exist yes. + Real filter files are generated by the RTS binary for suites with any + skippable tests. The rest of the suites need to have dummy files because gn + will expect the file to be present. + + Implementation uses try / except because the filter files are written + relatively close to when this code creates the dummy files. + + The following type of implementation would have a race condition: + if not os.path.isfile(filter_file): + open(filter_file, 'w') as fp: + fp.write('*') +""" +import errno +import os +import sys + + +def main(): + filter_file = sys.argv[1] + directory = os.path.dirname(filter_file) + try: + os.makedirs(directory) + except OSError as err: + if err.errno == errno.EEXIST: + pass + else: + raise + + try: + fp = os.open(filter_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + except OSError as err: + if err.errno == errno.EEXIST: + pass + else: + raise + else: + with os.fdopen(fp, 'w') as file_obj: + file_obj.write('*') # '*' is a dummy that means run everything + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/AndroidManifest.xml b/third_party/libwebrtc/build/android/AndroidManifest.xml new file mode 100644 index 0000000000..3c4ed292e2 --- /dev/null +++ b/third_party/libwebrtc/build/android/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/third_party/libwebrtc/build/android/BUILD.gn b/third_party/libwebrtc/build/android/BUILD.gn new file mode 100644 index 0000000000..c24fce529e --- /dev/null +++ b/third_party/libwebrtc/build/android/BUILD.gn @@ -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. + +import("//build/config/android/build_vars.gni") +import("//build/config/android/config.gni") +import("//build/config/android/rules.gni") +import("//build/config/python.gni") +import("//build_overrides/build.gni") + +if (enable_java_templates) { + # Create or update the API versions cache if necessary by running a + # functionally empty lint task. This prevents racy creation of the + # cache while linting java targets in android_lint. + android_lint("prepare_android_lint_cache") { + create_cache = true + } + + if (enable_jdk_library_desugaring) { + dex_jdk_libs("all_jdk_libs") { + output = "$target_out_dir/$target_name.l8.dex" + min_sdk_version = default_min_sdk_version + } + } + + generate_build_config_srcjar("build_config_gen") { + use_final_fields = false + } + + java_library("build_config_java") { + supports_android = true + srcjar_deps = [ ":build_config_gen" ] + jar_excluded_patterns = [ "*/build/BuildConfig.class" ] + } + + write_native_libraries_java("native_libraries_gen") { + use_final_fields = false + } + + android_library("native_libraries_java") { + srcjar_deps = [ ":native_libraries_gen" ] + + # New version of NativeLibraries.java (with the actual correct values) will + # be created when creating an apk. + jar_excluded_patterns = [ "*/NativeLibraries.class" ] + } +} + +python_library("devil_chromium_py") { + pydeps_file = "devil_chromium.pydeps" + data = [ + "devil_chromium.py", + "devil_chromium.json", + "//third_party/catapult/third_party/gsutil/", + "//third_party/catapult/devil/devil/devil_dependencies.json", + + # Read by gn_helpers.BuildWithChromium() + "//build/config/gclient_args.gni", + ] +} + +# Contains runtime deps for installing apks. +# E.g. from test_runner.py or from apk_operations.py. +group("apk_installer_data") { + # Other //build users let devil library fetch these from Google Storage. + if (build_with_chromium) { + data_deps = [ + "//build/android/pylib/device/commands", + "//tools/android/md5sum", + ] + data = [ + "//third_party/android_build_tools/bundletool/bundletool-all-1.8.0.jar", + ] + } +} + +python_library("apk_operations_py") { + pydeps_file = "apk_operations.pydeps" + deps = [ ":apk_installer_data" ] +} + +python_library("test_runner_py") { + testonly = true + pydeps_file = "test_runner.pydeps" + data = [ + "pylib/gtest/filter/", + "pylib/instrumentation/render_test.html.jinja", + "test_wrapper/logdog_wrapper.py", + "${android_sdk_build_tools}/aapt", + "${android_sdk_build_tools}/dexdump", + "${android_sdk_build_tools}/lib64/libc++.so", + "${android_sdk_build_tools}/split-select", + "${android_sdk_root}/platform-tools/adb", + "//third_party/requests/", + ] + data_deps = [ + ":apk_installer_data", + ":devil_chromium_py", + ":logdog_wrapper_py", + ":stack_tools", + ] + + # Other //build users let devil library fetch these from Google Storage. + if (build_with_chromium) { + data_deps += [ "//tools/android/forwarder2" ] + data += [ "//tools/android/avd/proto/" ] + if (is_asan) { + data_deps += [ "//tools/android/asan/third_party:asan_device_setup" ] + } + } + + # Proguard is needed only when using apks (rather than native executables). + if (enable_java_templates) { + deps = [ "//build/android/stacktrace:java_deobfuscate" ] + } +} + +python_library("logdog_wrapper_py") { + pydeps_file = "test_wrapper/logdog_wrapper.pydeps" +} + +python_library("resource_sizes_py") { + pydeps_file = "resource_sizes.pydeps" + data_deps = [ + ":devil_chromium_py", + "//third_party/catapult/tracing:convert_chart_json", + ] + data = [ + build_vars_file, + android_readelf, + ] +} + +# Tools necessary for symbolizing tombstones or stack traces that are output to +# logcat. +# Hidden behind build_with_chromium because some third party repos that use +# //build don't pull in //third_party/android_platform. +# TODO(crbug.com/1120190): Move stack script into //build/third_party +# and enable unconditionally. +group("stack_tools") { + if (build_with_chromium) { + data = [ + "tombstones.py", + "pylib/symbols/", + "stacktrace/", + ] + + data_deps = + [ "//third_party/android_platform/development/scripts:stack_py" ] + } +} + +# GN evaluates each .gn file once per toolchain, so restricting to default +# toolchain will ensure write_file() is called only once. +assert(current_toolchain == default_toolchain) + +# NOTE: If other platforms would benefit from exporting variables, we should +# move this to a more top-level place. +# It is currently here (instead of //BUILD.gn) to ensure that the file is +# written even for non-chromium embedders of //build. +_build_vars_json = { + # Underscore prefix so that it appears at the top. + _HEADER = "Generated during 'gn gen' by //build/android/BUILD.gn." + forward_variables_from(android_build_vars_json, "*") +} + +write_file(build_vars_file, _build_vars_json, "json") diff --git a/third_party/libwebrtc/build/android/CheckInstallApk-debug.apk b/third_party/libwebrtc/build/android/CheckInstallApk-debug.apk new file mode 100644 index 0000000000..3dc31910a5 Binary files /dev/null and b/third_party/libwebrtc/build/android/CheckInstallApk-debug.apk differ diff --git a/third_party/libwebrtc/build/android/DIR_METADATA b/third_party/libwebrtc/build/android/DIR_METADATA new file mode 100644 index 0000000000..7a2580a646 --- /dev/null +++ b/third_party/libwebrtc/build/android/DIR_METADATA @@ -0,0 +1 @@ +os: ANDROID diff --git a/third_party/libwebrtc/build/android/OWNERS b/third_party/libwebrtc/build/android/OWNERS new file mode 100644 index 0000000000..0b64bda0ff --- /dev/null +++ b/third_party/libwebrtc/build/android/OWNERS @@ -0,0 +1,7 @@ +bjoyce@chromium.org +jbudorick@chromium.org +mheikal@chromium.org +pasko@chromium.org +skyostil@chromium.org +tiborg@chromium.org +wnwen@chromium.org diff --git a/third_party/libwebrtc/build/android/PRESUBMIT.py b/third_party/libwebrtc/build/android/PRESUBMIT.py new file mode 100644 index 0000000000..ef22547f25 --- /dev/null +++ b/third_party/libwebrtc/build/android/PRESUBMIT.py @@ -0,0 +1,129 @@ +# 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): + build_android_dir = input_api.PresubmitLocalPath() + + def J(*dirs): + """Returns a path relative to presubmit directory.""" + return input_api.os_path.join(build_android_dir, *dirs) + + build_pys = [ + r'gn/.*\.py$', + r'gyp/.*\.py$', + ] + tests = [] + # yapf likes formatting the extra_paths_list to be less readable. + # yapf: disable + tests.extend( + input_api.canned_checks.GetPylint( + input_api, + output_api, + pylintrc='pylintrc', + # Temporarily disabled until pylint-2.6: crbug.com/1100664 + disabled_warnings=[ + 'no-member', + 'superfluous-parens', + 'no-name-in-module', + 'import-error'], + files_to_skip=[ + r'.*_pb2\.py', + # The following are all temporary due to: crbug.com/1100664 + r'.*list_java_targets\.py', + r'.*fast_local_dev_server\.py', + r'.*incremental_javac_test_android_library.py', + ] + build_pys, + extra_paths_list=[ + J(), + J('gyp'), + J('buildbot'), + J('..', 'util', 'lib', 'common'), + J('..', '..', 'third_party', 'catapult', 'common', + 'py_trace_event'), + J('..', '..', 'third_party', 'catapult', 'common', 'py_utils'), + J('..', '..', 'third_party', 'catapult', 'devil'), + J('..', '..', 'third_party', 'catapult', 'tracing'), + J('..', '..', 'third_party', 'depot_tools'), + J('..', '..', 'third_party', 'colorama', 'src'), + J('..', '..', 'build'), + ])) + tests.extend( + input_api.canned_checks.GetPylint( + input_api, + output_api, + files_to_check=build_pys, + files_to_skip=[ + r'.*_pb2\.py', + r'.*_pb2\.py', + ], + extra_paths_list=[J('gyp'), J('gn')])) + # yapf: enable + + # 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': build_android_dir, + 'PYTHONDONTWRITEBYTECODE': '1', + }) + tests.extend( + input_api.canned_checks.GetUnitTests( + input_api, + output_api, + unit_tests=[ + J('.', 'convert_dex_profile_tests.py'), + J('.', 'emma_coverage_stats_test.py'), + J('.', 'list_class_verification_failures_test.py'), + J('pylib', 'constants', 'host_paths_unittest.py'), + J('pylib', 'gtest', 'gtest_test_instance_test.py'), + J('pylib', 'instrumentation', + 'instrumentation_test_instance_test.py'), + J('pylib', 'local', 'device', 'local_device_gtest_run_test.py'), + J('pylib', 'local', 'device', + 'local_device_instrumentation_test_run_test.py'), + J('pylib', 'local', 'device', 'local_device_test_run_test.py'), + J('pylib', 'local', 'machine', + 'local_machine_junit_test_run_test.py'), + J('pylib', 'output', 'local_output_manager_test.py'), + J('pylib', 'output', 'noop_output_manager_test.py'), + J('pylib', 'output', 'remote_output_manager_test.py'), + J('pylib', 'results', 'json_results_test.py'), + J('pylib', 'symbols', 'apk_native_libs_unittest.py'), + J('pylib', 'symbols', 'elf_symbolizer_unittest.py'), + J('pylib', 'symbols', 'symbol_utils_unittest.py'), + J('pylib', 'utils', 'chrome_proxy_utils_test.py'), + J('pylib', 'utils', 'decorators_test.py'), + J('pylib', 'utils', 'device_dependencies_test.py'), + J('pylib', 'utils', 'dexdump_test.py'), + J('pylib', 'utils', 'gold_utils_test.py'), + J('pylib', 'utils', 'proguard_test.py'), + J('pylib', 'utils', 'test_filter_test.py'), + J('gyp', 'util', 'build_utils_test.py'), + J('gyp', 'util', 'manifest_utils_test.py'), + J('gyp', 'util', 'md5_check_test.py'), + J('gyp', 'util', 'resource_utils_test.py'), + ], + env=pylib_test_env, + run_on_python2=False, + run_on_python3=True, + skip_shebang_check=True)) + + return input_api.RunTests(tests) + + +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/third_party/libwebrtc/build/android/adb_chrome_public_command_line b/third_party/libwebrtc/build/android/adb_chrome_public_command_line new file mode 100755 index 0000000000..86ece8cec7 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_chrome_public_command_line @@ -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. + +# 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 "" + +exec $(dirname $0)/adb_command_line.py --name chrome-command-line "$@" diff --git a/third_party/libwebrtc/build/android/adb_command_line.py b/third_party/libwebrtc/build/android/adb_command_line.py new file mode 100755 index 0000000000..ae6a9e33a5 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_command_line.py @@ -0,0 +1,98 @@ +#!/usr/bin/env vpython3 +# 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. + +"""Utility for reading / writing command-line flag files on device(s).""" + +from __future__ import print_function + +import argparse +import logging +import sys + +import devil_chromium + +from devil.android import device_errors +from devil.android import device_utils +from devil.android import flag_changer +from devil.android.tools import script_common +from devil.utils import cmd_helper +from devil.utils import logging_common + + +def CheckBuildTypeSupportsFlags(device, command_line_flags_file): + is_webview = command_line_flags_file == 'webview-command-line' + if device.IsUserBuild() and is_webview: + raise device_errors.CommandFailedError( + 'WebView only respects flags on a userdebug or eng device, yours ' + 'is a user build.', device) + elif device.IsUserBuild(): + logging.warning( + 'Your device (%s) is a user build; Chrome may or may not pick up ' + 'your commandline flags. Check your ' + '"command_line_on_non_rooted_enabled" preference, or switch ' + 'devices.', device) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.usage = '''%(prog)s --name FILENAME [--device SERIAL] [flags...] + +No flags: Prints existing command-line file. +Empty string: Deletes command-line file. +Otherwise: Writes command-line file. + +''' + parser.add_argument('--name', required=True, + help='Name of file where to store flags on the device.') + parser.add_argument('-e', '--executable', dest='executable', default='chrome', + help='(deprecated) No longer used.') + script_common.AddEnvironmentArguments(parser) + script_common.AddDeviceArguments(parser) + logging_common.AddLoggingArguments(parser) + + args, remote_args = parser.parse_known_args() + devil_chromium.Initialize(adb_path=args.adb_path) + logging_common.InitializeLogging(args) + + devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices, + default_retries=0) + all_devices = device_utils.DeviceUtils.parallel(devices) + + if not remote_args: + # No args == do not update, just print flags. + remote_args = None + action = '' + elif len(remote_args) == 1 and not remote_args[0]: + # Single empty string arg == delete flags + remote_args = [] + action = 'Deleted command line file. ' + else: + action = 'Wrote command line file. ' + + def update_flags(device): + CheckBuildTypeSupportsFlags(device, args.name) + changer = flag_changer.FlagChanger(device, args.name) + if remote_args is not None: + flags = changer.ReplaceFlags(remote_args) + else: + flags = changer.GetCurrentFlags() + return (device, device.build_description, flags) + + updated_values = all_devices.pMap(update_flags).pGet(None) + + print('%sCurrent flags (in %s):' % (action, args.name)) + for d, desc, flags in updated_values: + if flags: + # Shell-quote flags for easy copy/paste as new args on the terminal. + quoted_flags = ' '.join(cmd_helper.SingleQuote(f) for f in sorted(flags)) + else: + quoted_flags = '( empty )' + print(' %s (%s): %s' % (d, desc, quoted_flags)) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/adb_gdb b/third_party/libwebrtc/build/android/adb_gdb new file mode 100755 index 0000000000..0923210bb6 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_gdb @@ -0,0 +1,1000 @@ +#!/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") + +# Force locale to C to allow recognizing output from subprocesses. +LC_ALL=C + +# Location of Chromium-top-level sources. +CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null) + +TMPDIR= +GDBSERVER_PIDFILE= +TARGET_GDBSERVER= +COMMAND_PREFIX= +COMMAND_SUFFIX= + +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 + rm -f "$GDBSERVER_PIDFILE" + fi + if [ "$TARGET_GDBSERVER" ]; then + log "Removing target gdbserver binary: $TARGET_GDBSERVER." + "$ADB" shell "$COMMAND_PREFIX" rm "$TARGET_GDBSERVER" \ + "$TARGET_DOMAIN_SOCKET" "$COMMAND_SUFFIX" >/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/adb-gdb-support-$USER" +IDE_DIR="$DEFAULT_PULL_LIBS_DIR" + +# 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 program name through ADB_GDB_PROGNAME +PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")} + +ADB= +ANNOTATE= +CGDB= +GDBINIT= +GDBSERVER= +HELP= +IDE= +NDK_DIR= +NO_PULL_LIBS= +PACKAGE_NAME= +PID= +PORT= +PROGRAM_NAME="activity" +PULL_LIBS= +PULL_LIBS_DIR= +ATTACH_DELAY=1 +SU_PREFIX= +SYMBOL_DIR= +TARGET_ARCH= +TOOLCHAIN= +VERBOSE=0 + +for opt; do + optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)') + case $opt in + --adb=*) + ADB=$optarg + ;; + --device=*) + export ANDROID_SERIAL=$optarg + ;; + --annotate=3) + ANNOTATE=$optarg + ;; + --gdbserver=*) + GDBSERVER=$optarg + ;; + --gdb=*) + GDB=$optarg + ;; + --help|-h|-?) + HELP=true + ;; + --ide) + IDE=true + ;; + --ndk-dir=*) + NDK_DIR=$optarg + ;; + --no-pull-libs) + NO_PULL_LIBS=true + ;; + --package-name=*) + PACKAGE_NAME=$optarg + ;; + --pid=*) + PID=$optarg + ;; + --port=*) + PORT=$optarg + ;; + --program-name=*) + PROGRAM_NAME=$optarg + ;; + --pull-libs) + PULL_LIBS=true + ;; + --pull-libs-dir=*) + PULL_LIBS_DIR=$optarg + ;; + --script=*) + GDBINIT=$optarg + ;; + --attach-delay=*) + ATTACH_DELAY=$optarg + ;; + --su-prefix=*) + SU_PREFIX=$optarg + ;; + --symbol-dir=*) + SYMBOL_DIR=$optarg + ;; + --output-directory=*) + CHROMIUM_OUTPUT_DIR=$optarg + ;; + --target-arch=*) + TARGET_ARCH=$optarg + ;; + --toolchain=*) + TOOLCHAIN=$optarg + ;; + --cgdb) + CGDB=cgdb + ;; + --cgdb=*) + CGDB=$optarg + ;; + --verbose) + VERBOSE=$(( $VERBOSE + 1 )) + ;; + -*) + 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 + +if [ "$HELP" ]; then + if [ "$ADB_GDB_PROGNAME" ]; then + # Assume wrapper scripts all provide a default package name. + 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 < 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//lib/ (used by GYP builds) + \$CHROMIUM_SRC//lib.unstripped/ (used by GN builds) + +Where is determined by CHROMIUM_OUTPUT_DIR, or --output-directory. + +You can set the path manually via --symbol-dir. + +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 a device is not specified with either --device or ANDROID_SERIAL). + +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. + + --cgdb[=] Use cgdb (an interface for gdb that shows the code). + --symbol-dir= Specify directory with symbol shared libraries. + --output-directory= Specify the output directory (e.g. "out/Debug"). + --package-name= Specify package name (alternative to 1st argument). + --program-name= Specify program name (cosmetic only). + --pid= Specify application process pid. + --attach-delay= Seconds to wait for gdbserver to attach to the + remote process before starting gdb. Default 1. + may be a float if your sleep(1) supports it. + --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. + --device= ADB device serial to use (-s flag). + --port= Specify the tcp port to use. + --ide Forward gdb port, but do not enter gdb console. + + --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. + +EOF + exit 0 +fi + +if [ -z "$PACKAGE_NAME" ]; then + panic "Please specify a package name on the command line. See --help." +fi + +if [[ -z "$SYMBOL_DIR" && -z "$CHROMIUM_OUTPUT_DIR" ]]; then + if [[ -e "build.ninja" ]]; then + CHROMIUM_OUTPUT_DIR=$PWD + else + panic "Please specify an output directory by using one of: + --output-directory=out/Debug + CHROMIUM_OUTPUT_DIR=out/Debug + Setting working directory to an output directory. + See --help." + fi +fi + +if ls *.so >/dev/null 2>&1; then + panic ".so files found in your working directory. These will conflict with" \ + "library lookup logic. Change your working directory and try again." +fi + +# Detect the build type and symbol directory. This is done by finding +# the most recent sub-directory containing debug shared libraries under +# $CHROMIUM_OUTPUT_DIR. +# +# Out: nothing, but this sets SYMBOL_DIR +# +detect_symbol_dir () { + # GYP places unstripped libraries under out/lib + # GN places them under out/lib.unstripped + local PARENT_DIR="$CHROMIUM_OUTPUT_DIR" + if [[ ! -e "$PARENT_DIR" ]]; then + PARENT_DIR="$CHROMIUM_SRC/$PARENT_DIR" + fi + SYMBOL_DIR="$PARENT_DIR/lib.unstripped" + if [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then + SYMBOL_DIR="$PARENT_DIR/lib" + if [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then + panic "Could not find any symbols under \ +$PARENT_DIR/lib{.unstripped}. Please build the program first!" + fi + fi + log "Auto-config: --symbol-dir=$SYMBOL_DIR" +} + +if [ -z "$SYMBOL_DIR" ]; then + detect_symbol_dir +elif [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then + panic "Could not find any symbols under $SYMBOL_DIR" +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" -gt 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 a local shared library. +# This returns an NDK-compatible architecture name. +# out: NDK Architecture name, or empty string. +get_gyp_target_arch () { + # ls prints a broken pipe error when there are a lot of libs. + local RANDOM_LIB=$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null| head -n1) + local SO_DESC=$(file $RANDOM_LIB) + case $ARCH in + *32-bit*ARM,*) echo "arm";; + *64-bit*ARM,*) echo "arm64";; + *32-bit*Intel,*) echo "x86";; + *x86-64,*) echo "x86_64";; + *32-bit*MIPS,*) echo "mips";; + *) 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) + if [ "$HOST_ARCH" = "unknown" ]; then + # In case where "-p" returns "unknown" just use "-m" (machine hardware + # name). According to this patch from Fedora "-p" is equivalent to "-m" + # anyway: https://goo.gl/Pd47x3 + HOST_ARCH=$(uname -m) + fi + 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 and x86_64! + if [ "$1" = "x86" -o "$1" = "x86_64" ]; 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 LD CONFIG + + # NOTE: This will need to be updated if the NDK changes the names or moves + # the location of its prebuilt toolchains. + # + LD= + HOST_OS=$(get_ndk_host_system) + HOST_ARCH=$(get_ndk_host_arch) + CONFIG=$(get_arch_gnu_config $ARCH) + LD=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-ld") + if [ -z "$LD" -a "$HOST_ARCH" = "x86_64" ]; then + LD=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-ld") + fi + if [ ! -f "$LD" -a "$ARCH" = "x86" ]; then + # Special case, the x86 toolchain used to be incorrectly + # named i686-android-linux-gcc! + LD=$(get_ndk_toolchain_prebuilt \ + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-ld") + fi + if [ -z "$LD" ]; then + panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \ +Please verify your NDK installation!" + fi + echo "${LD%%ld}" +} + +# $1: NDK install path +get_ndk_host_gdb_client() { + local NDK_DIR="$1" + local HOST_OS HOST_ARCH + + HOST_OS=$(get_ndk_host_system) + HOST_ARCH=$(get_ndk_host_arch) + echo "$NDK_DIR/prebuilt/$HOST_OS-$HOST_ARCH/bin/gdb" +} + +# $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=$(get_ndk_host_gdb_client "$ANDROID_NDK_ROOT") + 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 + +# Return the timestamp of a given file, as number of seconds since epoch. +# $1: file path +# Out: file timestamp +get_file_timestamp () { + stat -c %Y "$1" 2>/dev/null +} + +# Allow several concurrent debugging sessions +APP_DATA_DIR=$(adb_shell run-as $PACKAGE_NAME /system/bin/sh -c pwd) +fail_panic "Failed to run-as $PACKAGE_NAME, is the app debuggable?" +TARGET_GDBSERVER="$APP_DATA_DIR/gdbserver-adb-gdb-$TMP_ID" +TMP_TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID + +# Select correct app_process for architecture. +case $TARGET_ARCH in + arm|x86|mips) GDBEXEC=app_process32;; + arm64|x86_64) GDBEXEC=app_process64; SUFFIX_64_BIT=64;; + *) panic "Unknown app_process for architecture!";; +esac + +# Default to app_process if bit-width specific process isn't found. +adb_shell ls /system/bin/$GDBEXEC > /dev/null +if [ $? != 0 ]; then + GDBEXEC=app_process +fi + +# 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 > /dev/null +if [ $? == 0 ]; then + GDBEXEC=$GDBEXEC_ASAN +fi + +ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR +if [[ -n "$ANDROID_SERIAL" ]]; then + DEFAULT_PULL_LIBS_DIR="$DEFAULT_PULL_LIBS_DIR/$ANDROID_SERIAL-$SUFFIX_64_BIT" +fi +PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR} + +HOST_FINGERPRINT= +DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint) +[[ "$DEVICE_FINGERPRINT" ]] || panic "Failed to get the device fingerprint" +log "Device build fingerprint: $DEVICE_FINGERPRINT" + +if [ ! -f "$PULL_LIBS_DIR/build.fingerprint" ]; then + log "Auto-config: --pull-libs (no cached libraries)" + PULL_LIBS=true +else + HOST_FINGERPRINT=$(< "$PULL_LIBS_DIR/build.fingerprint") + 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 + +# If requested, work for M-x gdb. The gdb indirections make it +# difficult to pass --annotate=3 to the gdb binary itself. +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 [ -z "$PID" ]; then + PID=$(adb_shell ps | \ + awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1) + fi + if [ -z "$PID" ]; then + panic "Can't find application process PID." + fi + log "Found process PID: $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'" + +mkdir -p "$PULL_LIBS_DIR" +fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR" + +# 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" + 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 + # Remove the fingerprint file in case pulling one of the libs fails. + rm -f "$PULL_LIBS_DIR/build.fingerprint" + SYSTEM_LIBS=$(echo "$MAPPINGS" | \ + awk '$6 ~ /\/(system|apex|vendor)\/.*\.so$/ { print $6; }' | sort -u) + for SYSLIB in /system/bin/linker$SUFFIX_64_BIT $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 "Writing the device fingerprint" + echo "$DEVICE_FINGERPRINT" > "$PULL_LIBS_DIR/build.fingerprint" +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!" + +# 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' ':') +SOLIB_DIRS=${SOLIB_DIRS%:} # Strip trailing : + +# Applications with minSdkVersion >= 24 will have their data directories +# created with rwx------ permissions, preventing adbd from forwarding to +# the gdbserver socket. +adb_shell $COMMAND_PREFIX chmod a+x $APP_DATA_DIR $COMMAND_SUFFIX + +# 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 $COMMAND_SUFFIX && \ + adb_shell rm $TMP_TARGET_GDBSERVER +fail_panic "Could not copy gdbserver to the device!" + +if [ -z "$PORT" ]; then + # Random port to allow multiple concurrent sessions. + PORT=$(( $RANDOM % 1000 + 5039 )) +fi +HOST_PORT=$PORT +TARGET_DOMAIN_SOCKET=$APP_DATA_DIR/gdb-socket-$HOST_PORT + +# Setup network redirection +log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_DOMAIN_SOCKET)" +"$ADB" forward tcp:$HOST_PORT localfilesystem:$TARGET_DOMAIN_SOCKET +fail_panic "Could not setup network redirection from \ +host:localhost:$HOST_PORT to device:$TARGET_DOMAIN_SOCKET" + +# 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? +# + +for i in 1 2; do + log "Starting gdbserver in the background:" + GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log + log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER \ + --once +$TARGET_DOMAIN_SOCKET \ + --attach $PID $COMMAND_SUFFIX" + "$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER \ + --once +$TARGET_DOMAIN_SOCKET \ + --attach $PID $COMMAND_SUFFIX > $GDBSERVER_LOG 2>&1 & + GDBSERVER_PID=$! + echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE + log "background job pid: $GDBSERVER_PID" + + # Sleep to allow gdbserver to attach to the remote process and be + # ready to connect to. + log "Sleeping ${ATTACH_DELAY}s to ensure gdbserver is alive" + sleep "$ATTACH_DELAY" + log "Job control: $(jobs -l)" + STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }') + if [ "$STATE" != "Running" ]; then + pid_msg=$(grep "is already traced by process" $GDBSERVER_LOG 2>/dev/null) + if [[ -n "$pid_msg" ]]; then + old_pid=${pid_msg##* } + old_pid=${old_pid//[$'\r\n']} # Trim trailing \r. + echo "Killing previous gdb server process (pid=$old_pid)" + adb_shell $COMMAND_PREFIX kill -9 $old_pid $COMMAND_SUFFIX + continue + fi + echo "ERROR: GDBServer either failed to run or attach to PID $PID!" + echo "Here is the output from gdbserver (also try --verbose for more):" + echo "===== gdbserver.log start =====" + cat $GDBSERVER_LOG + echo ="===== gdbserver.log end ======" + exit 1 + fi + break +done + +# Generate a file containing useful GDB initialization commands +readonly COMMANDS=$TMPDIR/gdb.init +log "Generating GDB initialization commands file: $COMMANDS" +cat > "$COMMANDS" < timeout_seconds: + print("Error: unable to connect to device.") + print(e) + return False + time.sleep(min(0.25, time_left)) + +print("Connecting to :$HOST_PORT...") +if target_remote_with_retry(':$HOST_PORT', 5): + print("Attached! Reading symbols (takes ~30 seconds).") +end +EOF + +if [ "$GDBINIT" ]; then + cat "$GDBINIT" >> "$COMMANDS" +fi + +if [ "$VERBOSE" -gt 0 ]; then + echo "### START $COMMANDS" + cat "$COMMANDS" + echo "### END $COMMANDS" +fi + +if [ "$IDE" ]; then + mkdir -p "$IDE_DIR" + SYM_GDB="$IDE_DIR/gdb" + SYM_EXE="$IDE_DIR/app_process" + SYM_INIT="$IDE_DIR/gdbinit" + ln -sf "$TMPDIR/$GDBEXEC" "$SYM_EXE" + ln -sf "$COMMANDS" "$SYM_INIT" + # gdb doesn't work when symlinked, so create a wrapper. + echo + cat > $SYM_GDB < [] + +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. +""" + +from __future__ import print_function + +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.values(): + 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/third_party/libwebrtc/build/android/adb_logcat_printer.py b/third_party/libwebrtc/build/android/adb_logcat_printer.py new file mode 100755 index 0000000000..284988f532 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_logcat_printer.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# +# 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 io +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.items(): + 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] + + log_stringio = io.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) + + if options.output_path: + if not os.path.exists(os.path.dirname(options.output_path)): + logger.warning('Output dir %s doesn\'t exist. Creating it.', + os.path.dirname(options.output_path)) + os.makedirs(os.path.dirname(options.output_path)) + output_file = open(options.output_path, 'w') + logger.info('Dumping logcat to local file %s. If running in a build, ' + 'this file will likely will be uploaded to google storage ' + 'in a later step. It can be downloaded from there.', + options.output_path) + else: + output_file = sys.stdout + + 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/third_party/libwebrtc/build/android/adb_profile_chrome b/third_party/libwebrtc/build/android/adb_profile_chrome new file mode 100755 index 0000000000..d3244ffdf6 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_profile_chrome @@ -0,0 +1,9 @@ +#!/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. +CATAPULT_DIR="$(dirname "$0")"/../../third_party/catapult +exec "${CATAPULT_DIR}"/systrace/bin/adb_profile_chrome "$@" diff --git a/third_party/libwebrtc/build/android/adb_profile_chrome_startup b/third_party/libwebrtc/build/android/adb_profile_chrome_startup new file mode 100755 index 0000000000..d5836cdf70 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_profile_chrome_startup @@ -0,0 +1,9 @@ +#!/bin/bash +# +# 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. +# +# Start / stop profiling for chrome startup. +CATAPULT_DIR="$(dirname "$0")"/../../third_party/catapult +exec "${CATAPULT_DIR}"/systrace/bin/adb_profile_chrome_startup "$@" diff --git a/third_party/libwebrtc/build/android/adb_reverse_forwarder.py b/third_party/libwebrtc/build/android/adb_reverse_forwarder.py new file mode 100755 index 0000000000..bd6f05e619 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_reverse_forwarder.py @@ -0,0 +1,87 @@ +#!/usr/bin/env vpython3 +# +# 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 argparse +import sys +import time + +import devil_chromium + +from devil.android import device_denylist +from devil.android import device_utils +from devil.android import forwarder +from devil.utils import run_tests_helper + +from pylib import constants + + +def main(argv): + parser = argparse.ArgumentParser( + usage='Usage: %(prog)s [options] device_port ' + 'host_port [device_port_2 host_port_2] ...', + description=__doc__) + parser.add_argument( + '-v', '--verbose', + dest='verbose_count', + default=0, + action='count', + help='Verbose level (multiple times for more)') + parser.add_argument( + '--device', + help='Serial number of device we should use.') + parser.add_argument('--denylist-file', help='Device denylist JSON file.') + parser.add_argument( + '--debug', + action='store_const', + const='Debug', + dest='build_type', + default='Release', + help='DEPRECATED: use --output-directory instead.') + parser.add_argument( + '--output-directory', + help='Path to the root build directory.') + parser.add_argument( + 'ports', + nargs='+', + type=int, + help='Port pair to reverse forward.') + + args = parser.parse_args(argv) + run_tests_helper.SetLogLevel(args.verbose_count) + + if len(args.ports) < 2 or len(args.ports) % 2: + parser.error('Need even number of port pairs') + + port_pairs = list(zip(args.ports[::2], args.ports[1::2])) + + if args.build_type: + constants.SetBuildType(args.build_type) + if args.output_directory: + constants.SetOutputDirectory(args.output_directory) + devil_chromium.Initialize(output_directory=constants.GetOutDirectory()) + + denylist = (device_denylist.Denylist(args.denylist_file) + if args.denylist_file else None) + device = device_utils.DeviceUtils.HealthyDevices(denylist=denylist, + device_arg=args.device)[0] + 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__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/adb_system_webview_command_line b/third_party/libwebrtc/build/android/adb_system_webview_command_line new file mode 100755 index 0000000000..a0d2705821 --- /dev/null +++ b/third_party/libwebrtc/build/android/adb_system_webview_command_line @@ -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. + +# 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_system_webview_command_line --enable-webgl +# +# To remove all content shell flags, pass an empty string for the flags: +# adb_system_webview_command_line "" + +exec $(dirname $0)/adb_command_line.py --name webview-command-line "$@" diff --git a/third_party/libwebrtc/build/android/android_only_explicit_jni_exports.lst b/third_party/libwebrtc/build/android/android_only_explicit_jni_exports.lst new file mode 100644 index 0000000000..f989691865 --- /dev/null +++ b/third_party/libwebrtc/build/android/android_only_explicit_jni_exports.lst @@ -0,0 +1,13 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Linker script that exports only JNI_OnLoad. +# Should be used for libraries that do explicit JNI registration. + +{ + global: + JNI_OnLoad; + local: + *; +}; diff --git a/third_party/libwebrtc/build/android/android_only_jni_exports.lst b/third_party/libwebrtc/build/android/android_only_jni_exports.lst new file mode 100644 index 0000000000..1336fee145 --- /dev/null +++ b/third_party/libwebrtc/build/android/android_only_jni_exports.lst @@ -0,0 +1,13 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Linker script that exports only symbols required for JNI to work. + +{ + global: + JNI_OnLoad; + Java_*; + local: + *; +}; diff --git a/third_party/libwebrtc/build/android/apk_operations.py b/third_party/libwebrtc/build/android/apk_operations.py new file mode 100755 index 0000000000..192d20bacf --- /dev/null +++ b/third_party/libwebrtc/build/android/apk_operations.py @@ -0,0 +1,1944 @@ +#!/usr/bin/env vpython3 +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Using colorama.Fore/Back/Style members +# pylint: disable=no-member + +from __future__ import print_function + +import argparse +import collections +import json +import logging +import os +import pipes +import posixpath +import random +import re +import shlex +import shutil +import subprocess +import sys +import tempfile +import textwrap +import zipfile + +import adb_command_line +import devil_chromium +from devil import devil_env +from devil.android import apk_helper +from devil.android import device_errors +from devil.android import device_utils +from devil.android import flag_changer +from devil.android.sdk import adb_wrapper +from devil.android.sdk import build_tools +from devil.android.sdk import intent +from devil.android.sdk import version_codes +from devil.utils import run_tests_helper + +_DIR_SOURCE_ROOT = os.path.normpath( + os.path.join(os.path.dirname(__file__), '..', '..')) +_JAVA_HOME = os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current') + +with devil_env.SysPath( + os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src')): + import colorama + +from incremental_install import installer +from pylib import constants +from pylib.symbols import deobfuscator +from pylib.utils import simpleperf +from pylib.utils import app_bundle_utils + +with devil_env.SysPath( + os.path.join(_DIR_SOURCE_ROOT, 'build', 'android', 'gyp')): + import bundletool + +BASE_MODULE = 'base' + + +def _Colorize(text, style=''): + return (style + + text + + colorama.Style.RESET_ALL) + + +def _InstallApk(devices, apk, install_dict): + def install(device): + if install_dict: + installer.Install(device, install_dict, apk=apk, permissions=[]) + else: + device.Install(apk, permissions=[], allow_downgrade=True, reinstall=True) + + logging.info('Installing %sincremental apk.', '' if install_dict else 'non-') + device_utils.DeviceUtils.parallel(devices).pMap(install) + + +# A named tuple containing the information needed to convert a bundle into +# an installable .apks archive. +# Fields: +# bundle_path: Path to input bundle file. +# bundle_apk_path: Path to output bundle .apks archive file. +# aapt2_path: Path to aapt2 tool. +# keystore_path: Path to keystore file. +# keystore_password: Password for the keystore file. +# keystore_alias: Signing key name alias within the keystore file. +# system_image_locales: List of Chromium locales to include in system .apks. +BundleGenerationInfo = collections.namedtuple( + 'BundleGenerationInfo', + 'bundle_path,bundle_apks_path,aapt2_path,keystore_path,keystore_password,' + 'keystore_alias,system_image_locales') + + +def _GenerateBundleApks(info, + output_path=None, + minimal=False, + minimal_sdk_version=None, + mode=None, + optimize_for=None): + """Generate an .apks archive from a bundle on demand. + + Args: + info: A BundleGenerationInfo instance. + output_path: Path of output .apks archive. + minimal: Create the minimal set of apks possible (english-only). + minimal_sdk_version: When minimal=True, use this sdkVersion. + mode: Build mode, either None, or one of app_bundle_utils.BUILD_APKS_MODES. + optimize_for: Override split config, either None, or one of + app_bundle_utils.OPTIMIZE_FOR_OPTIONS. + """ + logging.info('Generating .apks file') + app_bundle_utils.GenerateBundleApks( + info.bundle_path, + # Store .apks file beside the .aab file by default so that it gets cached. + output_path or info.bundle_apks_path, + info.aapt2_path, + info.keystore_path, + info.keystore_password, + info.keystore_alias, + system_image_locales=info.system_image_locales, + mode=mode, + minimal=minimal, + minimal_sdk_version=minimal_sdk_version, + optimize_for=optimize_for) + + +def _InstallBundle(devices, apk_helper_instance, modules, fake_modules): + + def Install(device): + device.Install( + apk_helper_instance, + permissions=[], + modules=modules, + fake_modules=fake_modules, + allow_downgrade=True) + + # Basic checks for |modules| and |fake_modules|. + # * |fake_modules| cannot include 'base'. + # * If |fake_modules| is given, ensure |modules| includes 'base'. + # * They must be disjoint (checked by device.Install). + modules_set = set(modules) if modules else set() + fake_modules_set = set(fake_modules) if fake_modules else set() + if BASE_MODULE in fake_modules_set: + raise Exception('\'-f {}\' is disallowed.'.format(BASE_MODULE)) + if fake_modules_set and BASE_MODULE not in modules_set: + raise Exception( + '\'-f FAKE\' must be accompanied by \'-m {}\''.format(BASE_MODULE)) + + logging.info('Installing bundle.') + device_utils.DeviceUtils.parallel(devices).pMap(Install) + + +def _UninstallApk(devices, install_dict, package_name): + def uninstall(device): + if install_dict: + installer.Uninstall(device, package_name) + else: + device.Uninstall(package_name) + device_utils.DeviceUtils.parallel(devices).pMap(uninstall) + + +def _IsWebViewProvider(apk_helper_instance): + meta_data = apk_helper_instance.GetAllMetadata() + meta_data_keys = [pair[0] for pair in meta_data] + return 'com.android.webview.WebViewLibrary' in meta_data_keys + + +def _SetWebViewProvider(devices, package_name): + + def switch_provider(device): + if device.build_version_sdk < version_codes.NOUGAT: + logging.error('No need to switch provider on pre-Nougat devices (%s)', + device.serial) + else: + device.SetWebViewImplementation(package_name) + + device_utils.DeviceUtils.parallel(devices).pMap(switch_provider) + + +def _NormalizeProcessName(debug_process_name, package_name): + if not debug_process_name: + debug_process_name = package_name + elif debug_process_name.startswith(':'): + debug_process_name = package_name + debug_process_name + elif '.' not in debug_process_name: + debug_process_name = package_name + ':' + debug_process_name + return debug_process_name + + +def _LaunchUrl(devices, package_name, argv=None, command_line_flags_file=None, + url=None, apk=None, wait_for_java_debugger=False, + debug_process_name=None, nokill=None): + if argv and command_line_flags_file is None: + raise Exception('This apk does not support any flags.') + if url: + # TODO(agrieve): Launch could be changed to require only package name by + # parsing "dumpsys package" rather than relying on the apk. + if not apk: + raise Exception('Launching with URL is not supported when using ' + '--package-name. Use --apk-path instead.') + view_activity = apk.GetViewActivityName() + if not view_activity: + raise Exception('APK does not support launching with URLs.') + + debug_process_name = _NormalizeProcessName(debug_process_name, package_name) + + def launch(device): + # --persistent is required to have Settings.Global.DEBUG_APP be set, which + # we currently use to allow reading of flags. https://crbug.com/784947 + if not nokill: + cmd = ['am', 'set-debug-app', '--persistent', debug_process_name] + if wait_for_java_debugger: + cmd[-1:-1] = ['-w'] + # Ignore error since it will fail if apk is not debuggable. + device.RunShellCommand(cmd, check_return=False) + + # The flags are first updated with input args. + if command_line_flags_file: + changer = flag_changer.FlagChanger(device, command_line_flags_file) + flags = [] + if argv: + adb_command_line.CheckBuildTypeSupportsFlags(device, + command_line_flags_file) + flags = shlex.split(argv) + try: + changer.ReplaceFlags(flags) + except device_errors.AdbShellCommandFailedError: + logging.exception('Failed to set flags') + + if url is None: + # Simulate app icon click if no url is present. + cmd = [ + 'am', 'start', '-p', package_name, '-c', + 'android.intent.category.LAUNCHER', '-a', 'android.intent.action.MAIN' + ] + device.RunShellCommand(cmd, check_return=True) + else: + launch_intent = intent.Intent(action='android.intent.action.VIEW', + activity=view_activity, data=url, + package=package_name) + device.StartActivity(launch_intent) + device_utils.DeviceUtils.parallel(devices).pMap(launch) + if wait_for_java_debugger: + print('Waiting for debugger to attach to process: ' + + _Colorize(debug_process_name, colorama.Fore.YELLOW)) + + +def _ChangeFlags(devices, argv, command_line_flags_file): + if argv is None: + _DisplayArgs(devices, command_line_flags_file) + else: + flags = shlex.split(argv) + def update(device): + adb_command_line.CheckBuildTypeSupportsFlags(device, + command_line_flags_file) + changer = flag_changer.FlagChanger(device, command_line_flags_file) + changer.ReplaceFlags(flags) + device_utils.DeviceUtils.parallel(devices).pMap(update) + + +def _TargetCpuToTargetArch(target_cpu): + if target_cpu == 'x64': + return 'x86_64' + if target_cpu == 'mipsel': + return 'mips' + return target_cpu + + +def _RunGdb(device, package_name, debug_process_name, pid, output_directory, + target_cpu, port, ide, verbose): + if not pid: + debug_process_name = _NormalizeProcessName(debug_process_name, package_name) + pid = device.GetApplicationPids(debug_process_name, at_most_one=True) + if not pid: + # Attaching gdb makes the app run so slow that it takes *minutes* to start + # up (as of 2018). Better to just fail than to start & attach. + raise Exception('App not running.') + + gdb_script_path = os.path.dirname(__file__) + '/adb_gdb' + cmd = [ + gdb_script_path, + '--package-name=%s' % package_name, + '--output-directory=%s' % output_directory, + '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(), + '--device=%s' % device.serial, + '--pid=%s' % pid, + '--port=%d' % port, + ] + if ide: + cmd.append('--ide') + # Enable verbose output of adb_gdb if it's set for this script. + if verbose: + cmd.append('--verbose') + if target_cpu: + cmd.append('--target-arch=%s' % _TargetCpuToTargetArch(target_cpu)) + logging.warning('Running: %s', ' '.join(pipes.quote(x) for x in cmd)) + print(_Colorize('All subsequent output is from adb_gdb script.', + colorama.Fore.YELLOW)) + os.execv(gdb_script_path, cmd) + + +def _PrintPerDeviceOutput(devices, results, single_line=False): + for d, result in zip(devices, results): + if not single_line and d is not devices[0]: + sys.stdout.write('\n') + sys.stdout.write( + _Colorize('{} ({}):'.format(d, d.build_description), + colorama.Fore.YELLOW)) + sys.stdout.write(' ' if single_line else '\n') + yield result + + +def _RunMemUsage(devices, package_name, query_app=False): + cmd_args = ['dumpsys', 'meminfo'] + if not query_app: + cmd_args.append('--local') + + def mem_usage_helper(d): + ret = [] + for process in sorted(_GetPackageProcesses(d, package_name)): + meminfo = d.RunShellCommand(cmd_args + [str(process.pid)]) + ret.append((process.name, '\n'.join(meminfo))) + return ret + + parallel_devices = device_utils.DeviceUtils.parallel(devices) + all_results = parallel_devices.pMap(mem_usage_helper).pGet(None) + for result in _PrintPerDeviceOutput(devices, all_results): + if not result: + print('No processes found.') + else: + for name, usage in sorted(result): + print(_Colorize('==== Output of "dumpsys meminfo %s" ====' % name, + colorama.Fore.GREEN)) + print(usage) + + +def _DuHelper(device, path_spec, run_as=None): + """Runs "du -s -k |path_spec|" on |device| and returns parsed result. + + Args: + device: A DeviceUtils instance. + path_spec: The list of paths to run du on. May contain shell expansions + (will not be escaped). + run_as: Package name to run as, or None to run as shell user. If not None + and app is not android:debuggable (run-as fails), then command will be + run as root. + + Returns: + A dict of path->size in KiB containing all paths in |path_spec| that exist + on device. Paths that do not exist are silently ignored. + """ + # Example output for: du -s -k /data/data/org.chromium.chrome/{*,.*} + # 144 /data/data/org.chromium.chrome/cache + # 8 /data/data/org.chromium.chrome/files + # + # du: .*: No such file or directory + + # The -d flag works differently across android version, so use -s instead. + # Without the explicit 2>&1, stderr and stdout get combined at random :(. + cmd_str = 'du -s -k ' + path_spec + ' 2>&1' + lines = device.RunShellCommand(cmd_str, run_as=run_as, shell=True, + check_return=False) + output = '\n'.join(lines) + # run-as: Package 'com.android.chrome' is not debuggable + if output.startswith('run-as:'): + # check_return=False needed for when some paths in path_spec do not exist. + lines = device.RunShellCommand(cmd_str, as_root=True, shell=True, + check_return=False) + ret = {} + try: + for line in lines: + # du: .*: No such file or directory + if line.startswith('du:'): + continue + size, subpath = line.split(None, 1) + ret[subpath] = int(size) + return ret + except ValueError: + logging.error('du command was: %s', cmd_str) + logging.error('Failed to parse du output:\n%s', output) + raise + + +def _RunDiskUsage(devices, package_name): + # Measuring dex size is a bit complicated: + # https://source.android.com/devices/tech/dalvik/jit-compiler + # + # For KitKat and below: + # dumpsys package contains: + # dataDir=/data/data/org.chromium.chrome + # codePath=/data/app/org.chromium.chrome-1.apk + # resourcePath=/data/app/org.chromium.chrome-1.apk + # nativeLibraryPath=/data/app-lib/org.chromium.chrome-1 + # To measure odex: + # ls -l /data/dalvik-cache/data@app@org.chromium.chrome-1.apk@classes.dex + # + # For Android L and M (and maybe for N+ system apps): + # dumpsys package contains: + # codePath=/data/app/org.chromium.chrome-1 + # resourcePath=/data/app/org.chromium.chrome-1 + # legacyNativeLibraryDir=/data/app/org.chromium.chrome-1/lib + # To measure odex: + # # Option 1: + # /data/dalvik-cache/arm/data@app@org.chromium.chrome-1@base.apk@classes.dex + # /data/dalvik-cache/arm/data@app@org.chromium.chrome-1@base.apk@classes.vdex + # ls -l /data/dalvik-cache/profiles/org.chromium.chrome + # (these profiles all appear to be 0 bytes) + # # Option 2: + # ls -l /data/app/org.chromium.chrome-1/oat/arm/base.odex + # + # For Android N+: + # dumpsys package contains: + # dataDir=/data/user/0/org.chromium.chrome + # codePath=/data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w== + # resourcePath=/data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w== + # legacyNativeLibraryDir=/data/app/org.chromium.chrome-GUID/lib + # Instruction Set: arm + # path: /data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w==/base.apk + # status: /data/.../oat/arm/base.odex[status=kOatUpToDate, compilation_f + # ilter=quicken] + # Instruction Set: arm64 + # path: /data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w==/base.apk + # status: /data/.../oat/arm64/base.odex[status=..., compilation_filter=q + # uicken] + # To measure odex: + # ls -l /data/app/.../oat/arm/base.odex + # ls -l /data/app/.../oat/arm/base.vdex (optional) + # To measure the correct odex size: + # cmd package compile -m speed org.chromium.chrome # For webview + # cmd package compile -m speed-profile org.chromium.chrome # For others + def disk_usage_helper(d): + package_output = '\n'.join(d.RunShellCommand( + ['dumpsys', 'package', package_name], check_return=True)) + # Does not return error when apk is not installed. + if not package_output or 'Unable to find package:' in package_output: + return None + + # Ignore system apks that have updates installed. + package_output = re.sub(r'Hidden system packages:.*?^\b', '', + package_output, flags=re.S | re.M) + + try: + data_dir = re.search(r'dataDir=(.*)', package_output).group(1) + code_path = re.search(r'codePath=(.*)', package_output).group(1) + lib_path = re.search(r'(?:legacyN|n)ativeLibrary(?:Dir|Path)=(.*)', + package_output).group(1) + except AttributeError: + raise Exception('Error parsing dumpsys output: ' + package_output) + + if code_path.startswith('/system'): + logging.warning('Measurement of system image apks can be innacurate') + + compilation_filters = set() + # Match "compilation_filter=value", where a line break can occur at any spot + # (refer to examples above). + awful_wrapping = r'\s*'.join('compilation_filter=') + for m in re.finditer(awful_wrapping + r'([\s\S]+?)[\],]', package_output): + compilation_filters.add(re.sub(r'\s+', '', m.group(1))) + # Starting Android Q, output looks like: + # arm: [status=speed-profile] [reason=install] + for m in re.finditer(r'\[status=(.+?)\]', package_output): + compilation_filters.add(m.group(1)) + compilation_filter = ','.join(sorted(compilation_filters)) + + data_dir_sizes = _DuHelper(d, '%s/{*,.*}' % data_dir, run_as=package_name) + # Measure code_cache separately since it can be large. + code_cache_sizes = {} + code_cache_dir = next( + (k for k in data_dir_sizes if k.endswith('/code_cache')), None) + if code_cache_dir: + data_dir_sizes.pop(code_cache_dir) + code_cache_sizes = _DuHelper(d, '%s/{*,.*}' % code_cache_dir, + run_as=package_name) + + apk_path_spec = code_path + if not apk_path_spec.endswith('.apk'): + apk_path_spec += '/*.apk' + apk_sizes = _DuHelper(d, apk_path_spec) + if lib_path.endswith('/lib'): + # Shows architecture subdirectory. + lib_sizes = _DuHelper(d, '%s/{*,.*}' % lib_path) + else: + lib_sizes = _DuHelper(d, lib_path) + + # Look at all possible locations for odex files. + odex_paths = [] + for apk_path in apk_sizes: + mangled_apk_path = apk_path[1:].replace('/', '@') + apk_basename = posixpath.basename(apk_path)[:-4] + for ext in ('dex', 'odex', 'vdex', 'art'): + # Easier to check all architectures than to determine active ones. + for arch in ('arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64'): + odex_paths.append( + '%s/oat/%s/%s.%s' % (code_path, arch, apk_basename, ext)) + # No app could possibly have more than 6 dex files. + for suffix in ('', '2', '3', '4', '5'): + odex_paths.append('/data/dalvik-cache/%s/%s@classes%s.%s' % ( + arch, mangled_apk_path, suffix, ext)) + # This path does not have |arch|, so don't repeat it for every arch. + if arch == 'arm': + odex_paths.append('/data/dalvik-cache/%s@classes%s.dex' % ( + mangled_apk_path, suffix)) + + odex_sizes = _DuHelper(d, ' '.join(pipes.quote(p) for p in odex_paths)) + + return (data_dir_sizes, code_cache_sizes, apk_sizes, lib_sizes, odex_sizes, + compilation_filter) + + def print_sizes(desc, sizes): + print('%s: %d KiB' % (desc, sum(sizes.values()))) + for path, size in sorted(sizes.items()): + print(' %s: %s KiB' % (path, size)) + + parallel_devices = device_utils.DeviceUtils.parallel(devices) + all_results = parallel_devices.pMap(disk_usage_helper).pGet(None) + for result in _PrintPerDeviceOutput(devices, all_results): + if not result: + print('APK is not installed.') + continue + + (data_dir_sizes, code_cache_sizes, apk_sizes, lib_sizes, odex_sizes, + compilation_filter) = result + total = sum(sum(sizes.values()) for sizes in result[:-1]) + + print_sizes('Apk', apk_sizes) + print_sizes('App Data (non-code cache)', data_dir_sizes) + print_sizes('App Data (code cache)', code_cache_sizes) + print_sizes('Native Libs', lib_sizes) + show_warning = compilation_filter and 'speed' not in compilation_filter + compilation_filter = compilation_filter or 'n/a' + print_sizes('odex (compilation_filter=%s)' % compilation_filter, odex_sizes) + if show_warning: + logging.warning('For a more realistic odex size, run:') + logging.warning(' %s compile-dex [speed|speed-profile]', sys.argv[0]) + print('Total: %s KiB (%.1f MiB)' % (total, total / 1024.0)) + + +class _LogcatProcessor(object): + ParsedLine = collections.namedtuple( + 'ParsedLine', + ['date', 'invokation_time', 'pid', 'tid', 'priority', 'tag', 'message']) + + class NativeStackSymbolizer(object): + """Buffers lines from native stacks and symbolizes them when done.""" + # E.g.: #06 pc 0x0000d519 /apex/com.android.runtime/lib/libart.so + # E.g.: #01 pc 00180c8d /data/data/.../lib/libbase.cr.so + _STACK_PATTERN = re.compile(r'\s*#\d+\s+(?:pc )?(0x)?[0-9a-f]{8,16}\s') + + def __init__(self, stack_script_context, print_func): + # To symbolize native stacks, we need to pass all lines at once. + self._stack_script_context = stack_script_context + self._print_func = print_func + self._crash_lines_buffer = None + + def _FlushLines(self): + """Prints queued lines after sending them through stack.py.""" + crash_lines = self._crash_lines_buffer + self._crash_lines_buffer = None + with tempfile.NamedTemporaryFile(mode='w') as f: + f.writelines(x[0].message + '\n' for x in crash_lines) + f.flush() + proc = self._stack_script_context.Popen( + input_file=f.name, stdout=subprocess.PIPE) + lines = proc.communicate()[0].splitlines() + + for i, line in enumerate(lines): + parsed_line, dim = crash_lines[min(i, len(crash_lines) - 1)] + d = parsed_line._asdict() + d['message'] = line + parsed_line = _LogcatProcessor.ParsedLine(**d) + self._print_func(parsed_line, dim) + + def AddLine(self, parsed_line, dim): + # Assume all lines from DEBUG are stacks. + # Also look for "stack-looking" lines to catch manual stack prints. + # It's important to not buffer non-stack lines because stack.py does not + # pass them through. + is_crash_line = parsed_line.tag == 'DEBUG' or (self._STACK_PATTERN.match( + parsed_line.message)) + + if is_crash_line: + if self._crash_lines_buffer is None: + self._crash_lines_buffer = [] + self._crash_lines_buffer.append((parsed_line, dim)) + return + + if self._crash_lines_buffer is not None: + self._FlushLines() + + self._print_func(parsed_line, dim) + + + # Logcat tags for messages that are generally relevant but are not from PIDs + # associated with the apk. + _ALLOWLISTED_TAGS = { + 'ActivityManager', # Shows activity lifecycle messages. + 'ActivityTaskManager', # More activity lifecycle messages. + 'AndroidRuntime', # Java crash dumps + 'DEBUG', # Native crash dump. + } + + # Matches messages only on pre-L (Dalvik) that are spammy and unimportant. + _DALVIK_IGNORE_PATTERN = re.compile('|'.join([ + r'^Added shared lib', + r'^Could not find ', + r'^DexOpt:', + r'^GC_', + r'^Late-enabling CheckJNI', + r'^Link of class', + r'^No JNI_OnLoad found in', + r'^Trying to load lib', + r'^Unable to resolve superclass', + r'^VFY:', + r'^WAIT_', + ])) + + def __init__(self, + device, + package_name, + stack_script_context, + deobfuscate=None, + verbose=False): + self._device = device + self._package_name = package_name + self._verbose = verbose + self._deobfuscator = deobfuscate + self._native_stack_symbolizer = _LogcatProcessor.NativeStackSymbolizer( + stack_script_context, self._PrintParsedLine) + # Process ID for the app's main process (with no :name suffix). + self._primary_pid = None + # Set of all Process IDs that belong to the app. + self._my_pids = set() + # Set of all Process IDs that we've parsed at some point. + self._seen_pids = set() + # Start proc 22953:com.google.chromeremotedesktop/ + self._pid_pattern = re.compile(r'Start proc (\d+):{}/'.format(package_name)) + # START u0 {act=android.intent.action.MAIN \ + # cat=[android.intent.category.LAUNCHER] \ + # flg=0x10000000 pkg=com.google.chromeremotedesktop} from uid 2000 + self._start_pattern = re.compile(r'START .*pkg=' + package_name) + + self.nonce = 'Chromium apk_operations.py nonce={}'.format(random.random()) + # Holds lines buffered on start-up, before we find our nonce message. + self._initial_buffered_lines = [] + self._UpdateMyPids() + # Give preference to PID reported by "ps" over those found from + # _start_pattern. There can be multiple "Start proc" messages from prior + # runs of the app. + self._found_initial_pid = self._primary_pid != None + # Retrieve any additional patterns that are relevant for the User. + self._user_defined_highlight = None + user_regex = os.environ.get('CHROMIUM_LOGCAT_HIGHLIGHT') + if user_regex: + self._user_defined_highlight = re.compile(user_regex) + if not self._user_defined_highlight: + print(_Colorize( + 'Rejecting invalid regular expression: {}'.format(user_regex), + colorama.Fore.RED + colorama.Style.BRIGHT)) + + def _UpdateMyPids(self): + # We intentionally do not clear self._my_pids to make sure that the + # ProcessLine method below also includes lines from processes which may + # have already exited. + self._primary_pid = None + for process in _GetPackageProcesses(self._device, self._package_name): + # We take only the first "main" process found in order to account for + # possibly forked() processes. + if ':' not in process.name and self._primary_pid is None: + self._primary_pid = process.pid + self._my_pids.add(process.pid) + + def _GetPidStyle(self, pid, dim=False): + if pid == self._primary_pid: + return colorama.Fore.WHITE + elif pid in self._my_pids: + # TODO(wnwen): Use one separate persistent color per process, pop LRU + return colorama.Fore.YELLOW + elif dim: + return colorama.Style.DIM + return '' + + def _GetPriorityStyle(self, priority, dim=False): + # pylint:disable=no-self-use + if dim: + return '' + style = colorama.Fore.BLACK + if priority == 'E' or priority == 'F': + style += colorama.Back.RED + elif priority == 'W': + style += colorama.Back.YELLOW + elif priority == 'I': + style += colorama.Back.GREEN + elif priority == 'D': + style += colorama.Back.BLUE + return style + + def _ParseLine(self, line): + tokens = line.split(None, 6) + + def consume_token_or_default(default): + return tokens.pop(0) if len(tokens) > 0 else default + + def consume_integer_token_or_default(default): + if len(tokens) == 0: + return default + + try: + return int(tokens.pop(0)) + except ValueError: + return default + + date = consume_token_or_default('') + invokation_time = consume_token_or_default('') + pid = consume_integer_token_or_default(-1) + tid = consume_integer_token_or_default(-1) + priority = consume_token_or_default('') + tag = consume_token_or_default('') + original_message = consume_token_or_default('') + + # Example: + # 09-19 06:35:51.113 9060 9154 W GCoreFlp: No location... + # 09-19 06:01:26.174 9060 10617 I Auth : [ReflectiveChannelBinder]... + # Parsing "GCoreFlp:" vs "Auth :", we only want tag to contain the word, + # and we don't want to keep the colon for the message. + if tag and tag[-1] == ':': + tag = tag[:-1] + elif len(original_message) > 2: + original_message = original_message[2:] + return self.ParsedLine( + date, invokation_time, pid, tid, priority, tag, original_message) + + def _PrintParsedLine(self, parsed_line, dim=False): + tid_style = colorama.Style.NORMAL + user_match = self._user_defined_highlight and ( + re.search(self._user_defined_highlight, parsed_line.tag) + or re.search(self._user_defined_highlight, parsed_line.message)) + + # Make the main thread bright. + if not dim and parsed_line.pid == parsed_line.tid: + tid_style = colorama.Style.BRIGHT + pid_style = self._GetPidStyle(parsed_line.pid, dim) + msg_style = pid_style if not user_match else (colorama.Fore.GREEN + + colorama.Style.BRIGHT) + # We have to pad before adding color as that changes the width of the tag. + pid_str = _Colorize('{:5}'.format(parsed_line.pid), pid_style) + tid_str = _Colorize('{:5}'.format(parsed_line.tid), tid_style) + tag = _Colorize('{:8}'.format(parsed_line.tag), + pid_style + ('' if dim else colorama.Style.BRIGHT)) + priority = _Colorize(parsed_line.priority, + self._GetPriorityStyle(parsed_line.priority)) + messages = [parsed_line.message] + if self._deobfuscator: + messages = self._deobfuscator.TransformLines(messages) + for message in messages: + message = _Colorize(message, msg_style) + sys.stdout.write('{} {} {} {} {} {}: {}\n'.format( + parsed_line.date, parsed_line.invokation_time, pid_str, tid_str, + priority, tag, message)) + + def _TriggerNonceFound(self): + # Once the nonce is hit, we have confidence that we know which lines + # belong to the current run of the app. Process all of the buffered lines. + if self._primary_pid: + for args in self._initial_buffered_lines: + self._native_stack_symbolizer.AddLine(*args) + self._initial_buffered_lines = None + self.nonce = None + + def ProcessLine(self, line): + if not line or line.startswith('------'): + return + + if self.nonce and self.nonce in line: + self._TriggerNonceFound() + + nonce_found = self.nonce is None + + log = self._ParseLine(line) + if log.pid not in self._seen_pids: + self._seen_pids.add(log.pid) + if nonce_found: + # Update list of owned PIDs each time a new PID is encountered. + self._UpdateMyPids() + + # Search for "Start proc $pid:$package_name/" message. + if not nonce_found: + # Capture logs before the nonce. Start with the most recent "am start". + if self._start_pattern.match(log.message): + self._initial_buffered_lines = [] + + # If we didn't find the PID via "ps", then extract it from log messages. + # This will happen if the app crashes too quickly. + if not self._found_initial_pid: + m = self._pid_pattern.match(log.message) + if m: + # Find the most recent "Start proc" line before the nonce. + # Track only the primary pid in this mode. + # The main use-case is to find app logs when no current PIDs exist. + # E.g.: When the app crashes on launch. + self._primary_pid = m.group(1) + self._my_pids.clear() + self._my_pids.add(m.group(1)) + + owned_pid = log.pid in self._my_pids + if owned_pid and not self._verbose and log.tag == 'dalvikvm': + if self._DALVIK_IGNORE_PATTERN.match(log.message): + return + + if owned_pid or self._verbose or (log.priority == 'F' or # Java crash dump + log.tag in self._ALLOWLISTED_TAGS): + if nonce_found: + self._native_stack_symbolizer.AddLine(log, not owned_pid) + else: + self._initial_buffered_lines.append((log, not owned_pid)) + + +def _RunLogcat(device, package_name, stack_script_context, deobfuscate, + verbose): + logcat_processor = _LogcatProcessor( + device, package_name, stack_script_context, deobfuscate, verbose) + device.RunShellCommand(['log', logcat_processor.nonce]) + for line in device.adb.Logcat(logcat_format='threadtime'): + try: + logcat_processor.ProcessLine(line) + except: + sys.stderr.write('Failed to process line: ' + line + '\n') + # Skip stack trace for the common case of the adb server being + # restarted. + if 'unexpected EOF' in line: + sys.exit(1) + raise + + +def _GetPackageProcesses(device, package_name): + return [ + p for p in device.ListProcesses(package_name) + if p.name == package_name or p.name.startswith(package_name + ':')] + + +def _RunPs(devices, package_name): + parallel_devices = device_utils.DeviceUtils.parallel(devices) + all_processes = parallel_devices.pMap( + lambda d: _GetPackageProcesses(d, package_name)).pGet(None) + for processes in _PrintPerDeviceOutput(devices, all_processes): + if not processes: + print('No processes found.') + else: + proc_map = collections.defaultdict(list) + for p in processes: + proc_map[p.name].append(str(p.pid)) + for name, pids in sorted(proc_map.items()): + print(name, ','.join(pids)) + + +def _RunShell(devices, package_name, cmd): + if cmd: + parallel_devices = device_utils.DeviceUtils.parallel(devices) + outputs = parallel_devices.RunShellCommand( + cmd, run_as=package_name).pGet(None) + for output in _PrintPerDeviceOutput(devices, outputs): + for line in output: + print(line) + else: + adb_path = adb_wrapper.AdbWrapper.GetAdbPath() + cmd = [adb_path, '-s', devices[0].serial, 'shell'] + # Pre-N devices do not support -t flag. + if devices[0].build_version_sdk >= version_codes.NOUGAT: + cmd += ['-t', 'run-as', package_name] + else: + print('Upon entering the shell, run:') + print('run-as', package_name) + print() + os.execv(adb_path, cmd) + + +def _RunCompileDex(devices, package_name, compilation_filter): + cmd = ['cmd', 'package', 'compile', '-f', '-m', compilation_filter, + package_name] + parallel_devices = device_utils.DeviceUtils.parallel(devices) + outputs = parallel_devices.RunShellCommand(cmd, timeout=120).pGet(None) + for output in _PrintPerDeviceOutput(devices, outputs): + for line in output: + print(line) + + +def _RunProfile(device, package_name, host_build_directory, pprof_out_path, + process_specifier, thread_specifier, extra_args): + simpleperf.PrepareDevice(device) + device_simpleperf_path = simpleperf.InstallSimpleperf(device, package_name) + with tempfile.NamedTemporaryFile() as fh: + host_simpleperf_out_path = fh.name + + with simpleperf.RunSimpleperf(device, device_simpleperf_path, package_name, + process_specifier, thread_specifier, + extra_args, host_simpleperf_out_path): + sys.stdout.write('Profiler is running; press Enter to stop...') + sys.stdin.read(1) + sys.stdout.write('Post-processing data...') + sys.stdout.flush() + + simpleperf.ConvertSimpleperfToPprof(host_simpleperf_out_path, + host_build_directory, pprof_out_path) + print(textwrap.dedent(""" + Profile data written to %(s)s. + + To view profile as a call graph in browser: + pprof -web %(s)s + + To print the hottest methods: + pprof -top %(s)s + + pprof has many useful customization options; `pprof --help` for details. + """ % {'s': pprof_out_path})) + + +class _StackScriptContext(object): + """Maintains temporary files needed by stack.py.""" + + def __init__(self, + output_directory, + apk_path, + bundle_generation_info, + quiet=False): + self._output_directory = output_directory + self._apk_path = apk_path + self._bundle_generation_info = bundle_generation_info + self._staging_dir = None + self._quiet = quiet + + def _CreateStaging(self): + # In many cases, stack decoding requires APKs to map trace lines to native + # libraries. Create a temporary directory, and either unpack a bundle's + # APKS into it, or simply symlink the standalone APK into it. This + # provides an unambiguous set of APK files for the stack decoding process + # to inspect. + logging.debug('Creating stack staging directory') + self._staging_dir = tempfile.mkdtemp() + bundle_generation_info = self._bundle_generation_info + + if bundle_generation_info: + # TODO(wnwen): Use apk_helper instead. + _GenerateBundleApks(bundle_generation_info) + logging.debug('Extracting .apks file') + with zipfile.ZipFile(bundle_generation_info.bundle_apks_path, 'r') as z: + files_to_extract = [ + f for f in z.namelist() if f.endswith('-master.apk') + ] + z.extractall(self._staging_dir, files_to_extract) + elif self._apk_path: + # Otherwise an incremental APK and an empty apks directory is correct. + output = os.path.join(self._staging_dir, os.path.basename(self._apk_path)) + os.symlink(self._apk_path, output) + + def Close(self): + if self._staging_dir: + logging.debug('Clearing stack staging directory') + shutil.rmtree(self._staging_dir) + self._staging_dir = None + + def Popen(self, input_file=None, **kwargs): + if self._staging_dir is None: + self._CreateStaging() + stack_script = os.path.join( + constants.host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, + 'stack.py') + cmd = [ + stack_script, '--output-directory', self._output_directory, + '--apks-directory', self._staging_dir + ] + if self._quiet: + cmd.append('--quiet') + if input_file: + cmd.append(input_file) + logging.info('Running stack.py') + return subprocess.Popen(cmd, universal_newlines=True, **kwargs) + + +def _GenerateAvailableDevicesMessage(devices): + devices_obj = device_utils.DeviceUtils.parallel(devices) + descriptions = devices_obj.pMap(lambda d: d.build_description).pGet(None) + msg = 'Available devices:\n' + for d, desc in zip(devices, descriptions): + msg += ' %s (%s)\n' % (d, desc) + return msg + + +# TODO(agrieve):add "--all" in the MultipleDevicesError message and use it here. +def _GenerateMissingAllFlagMessage(devices): + return ('More than one device available. Use --all to select all devices, ' + + 'or use --device to select a device by serial.\n\n' + + _GenerateAvailableDevicesMessage(devices)) + + +def _DisplayArgs(devices, command_line_flags_file): + def flags_helper(d): + changer = flag_changer.FlagChanger(d, command_line_flags_file) + return changer.GetCurrentFlags() + + parallel_devices = device_utils.DeviceUtils.parallel(devices) + outputs = parallel_devices.pMap(flags_helper).pGet(None) + print('Existing flags per-device (via /data/local/tmp/{}):'.format( + command_line_flags_file)) + for flags in _PrintPerDeviceOutput(devices, outputs, single_line=True): + quoted_flags = ' '.join(pipes.quote(f) for f in flags) + print(quoted_flags or 'No flags set.') + + +def _DeviceCachePath(device, output_directory): + file_name = 'device_cache_%s.json' % device.serial + return os.path.join(output_directory, file_name) + + +def _LoadDeviceCaches(devices, output_directory): + if not output_directory: + return + for d in devices: + cache_path = _DeviceCachePath(d, output_directory) + if os.path.exists(cache_path): + logging.debug('Using device cache: %s', cache_path) + with open(cache_path) as f: + d.LoadCacheData(f.read()) + # Delete the cached file so that any exceptions cause it to be cleared. + os.unlink(cache_path) + else: + logging.debug('No cache present for device: %s', d) + + +def _SaveDeviceCaches(devices, output_directory): + if not output_directory: + return + for d in devices: + cache_path = _DeviceCachePath(d, output_directory) + with open(cache_path, 'w') as f: + f.write(d.DumpCacheData()) + logging.info('Wrote device cache: %s', cache_path) + + +class _Command(object): + name = None + description = None + long_description = None + needs_package_name = False + needs_output_directory = False + needs_apk_helper = False + supports_incremental = False + accepts_command_line_flags = False + accepts_args = False + need_device_args = True + all_devices_by_default = False + calls_exec = False + supports_multiple_devices = True + + def __init__(self, from_wrapper_script, is_bundle): + self._parser = None + self._from_wrapper_script = from_wrapper_script + self.args = None + self.apk_helper = None + self.additional_apk_helpers = None + self.install_dict = None + self.devices = None + self.is_bundle = is_bundle + self.bundle_generation_info = None + # Only support incremental install from APK wrapper scripts. + if is_bundle or not from_wrapper_script: + self.supports_incremental = False + + def RegisterBundleGenerationInfo(self, bundle_generation_info): + self.bundle_generation_info = bundle_generation_info + + def _RegisterExtraArgs(self, subp): + pass + + def RegisterArgs(self, parser): + subp = parser.add_parser( + self.name, help=self.description, + description=self.long_description or self.description, + formatter_class=argparse.RawDescriptionHelpFormatter) + self._parser = subp + subp.set_defaults(command=self) + if self.need_device_args: + subp.add_argument('--all', + action='store_true', + default=self.all_devices_by_default, + help='Operate on all connected devices.',) + subp.add_argument('-d', + '--device', + action='append', + default=[], + dest='devices', + help='Target device for script to work on. Enter ' + 'multiple times for multiple devices.') + subp.add_argument('-v', + '--verbose', + action='count', + default=0, + dest='verbose_count', + help='Verbose level (multiple times for more)') + group = subp.add_argument_group('%s arguments' % self.name) + + if self.needs_package_name: + # Three cases to consider here, since later code assumes + # self.args.package_name always exists, even if None: + # + # - Called from a bundle wrapper script, the package_name is already + # set through parser.set_defaults(), so don't call add_argument() + # to avoid overriding its value. + # + # - Called from an apk wrapper script. The --package-name argument + # should not appear, but self.args.package_name will be gleaned from + # the --apk-path file later. + # + # - Called directly, then --package-name is required on the command-line. + # + if not self.is_bundle: + group.add_argument( + '--package-name', + help=argparse.SUPPRESS if self._from_wrapper_script else ( + "App's package name.")) + + if self.needs_apk_helper or self.needs_package_name: + # Adding this argument to the subparser would override the set_defaults() + # value set by on the parent parser (even if None). + if not self._from_wrapper_script and not self.is_bundle: + group.add_argument( + '--apk-path', required=self.needs_apk_helper, help='Path to .apk') + + if self.supports_incremental: + group.add_argument('--incremental', + action='store_true', + default=False, + help='Always install an incremental apk.') + group.add_argument('--non-incremental', + action='store_true', + default=False, + help='Always install a non-incremental apk.') + + # accepts_command_line_flags and accepts_args are mutually exclusive. + # argparse will throw if they are both set. + if self.accepts_command_line_flags: + group.add_argument( + '--args', help='Command-line flags. Use = to assign args.') + + if self.accepts_args: + group.add_argument( + '--args', help='Extra arguments. Use = to assign args') + + if not self._from_wrapper_script and self.accepts_command_line_flags: + # Provided by wrapper scripts. + group.add_argument( + '--command-line-flags-file', + help='Name of the command-line flags file') + + self._RegisterExtraArgs(group) + + def _CreateApkHelpers(self, args, incremental_apk_path, install_dict): + """Returns true iff self.apk_helper was created and assigned.""" + if self.apk_helper is None: + if args.apk_path: + self.apk_helper = apk_helper.ToHelper(args.apk_path) + elif incremental_apk_path: + self.install_dict = install_dict + self.apk_helper = apk_helper.ToHelper(incremental_apk_path) + elif self.is_bundle: + _GenerateBundleApks(self.bundle_generation_info) + self.apk_helper = apk_helper.ToHelper( + self.bundle_generation_info.bundle_apks_path) + if args.additional_apk_paths and self.additional_apk_helpers is None: + self.additional_apk_helpers = [ + apk_helper.ToHelper(apk_path) + for apk_path in args.additional_apk_paths + ] + return self.apk_helper is not None + + def ProcessArgs(self, args): + self.args = args + # Ensure these keys always exist. They are set by wrapper scripts, but not + # always added when not using wrapper scripts. + args.__dict__.setdefault('apk_path', None) + args.__dict__.setdefault('incremental_json', None) + + incremental_apk_path = None + install_dict = None + if args.incremental_json and not (self.supports_incremental and + args.non_incremental): + with open(args.incremental_json) as f: + install_dict = json.load(f) + incremental_apk_path = os.path.join(args.output_directory, + install_dict['apk_path']) + if not os.path.exists(incremental_apk_path): + incremental_apk_path = None + + if self.supports_incremental: + if args.incremental and args.non_incremental: + self._parser.error('Must use only one of --incremental and ' + '--non-incremental') + elif args.non_incremental: + if not args.apk_path: + self._parser.error('Apk has not been built.') + elif args.incremental: + if not incremental_apk_path: + self._parser.error('Incremental apk has not been built.') + args.apk_path = None + + if args.apk_path and incremental_apk_path: + self._parser.error('Both incremental and non-incremental apks exist. ' + 'Select using --incremental or --non-incremental') + + + # Gate apk_helper creation with _CreateApkHelpers since for bundles it takes + # a while to unpack the apks file from the aab file, so avoid this slowdown + # for simple commands that don't need apk_helper. + if self.needs_apk_helper: + if not self._CreateApkHelpers(args, incremental_apk_path, install_dict): + self._parser.error('App is not built.') + + if self.needs_package_name and not args.package_name: + if self._CreateApkHelpers(args, incremental_apk_path, install_dict): + args.package_name = self.apk_helper.GetPackageName() + elif self._from_wrapper_script: + self._parser.error('App is not built.') + else: + self._parser.error('One of --package-name or --apk-path is required.') + + self.devices = [] + if self.need_device_args: + abis = None + if self._CreateApkHelpers(args, incremental_apk_path, install_dict): + abis = self.apk_helper.GetAbis() + self.devices = device_utils.DeviceUtils.HealthyDevices( + device_arg=args.devices, + enable_device_files_cache=bool(args.output_directory), + default_retries=0, + abis=abis) + # TODO(agrieve): Device cache should not depend on output directory. + # Maybe put into /tmp? + _LoadDeviceCaches(self.devices, args.output_directory) + + try: + if len(self.devices) > 1: + if not self.supports_multiple_devices: + self._parser.error(device_errors.MultipleDevicesError(self.devices)) + if not args.all and not args.devices: + self._parser.error(_GenerateMissingAllFlagMessage(self.devices)) + # Save cache now if command will not get a chance to afterwards. + if self.calls_exec: + _SaveDeviceCaches(self.devices, args.output_directory) + except: + _SaveDeviceCaches(self.devices, args.output_directory) + raise + + +class _DevicesCommand(_Command): + name = 'devices' + description = 'Describe attached devices.' + all_devices_by_default = True + + def Run(self): + print(_GenerateAvailableDevicesMessage(self.devices)) + + +class _PackageInfoCommand(_Command): + name = 'package-info' + description = 'Show various attributes of this app.' + need_device_args = False + needs_package_name = True + needs_apk_helper = True + + def Run(self): + # Format all (even ints) as strings, to handle cases where APIs return None + print('Package name: "%s"' % self.args.package_name) + print('versionCode: %s' % self.apk_helper.GetVersionCode()) + print('versionName: "%s"' % self.apk_helper.GetVersionName()) + print('minSdkVersion: %s' % self.apk_helper.GetMinSdkVersion()) + print('targetSdkVersion: %s' % self.apk_helper.GetTargetSdkVersion()) + print('Supported ABIs: %r' % self.apk_helper.GetAbis()) + + +class _InstallCommand(_Command): + name = 'install' + description = 'Installs the APK or bundle to one or more devices.' + needs_apk_helper = True + supports_incremental = True + default_modules = [] + + def _RegisterExtraArgs(self, group): + if self.is_bundle: + group.add_argument( + '-m', + '--module', + action='append', + default=self.default_modules, + help='Module to install. Can be specified multiple times.') + group.add_argument( + '-f', + '--fake', + action='append', + default=[], + help='Fake bundle module install. Can be specified multiple times. ' + 'Requires \'-m {0}\' to be given, and \'-f {0}\' is illegal.'.format( + BASE_MODULE)) + # Add even if |self.default_modules| is empty, for consistency. + group.add_argument('--no-module', + action='append', + choices=self.default_modules, + default=[], + help='Module to exclude from default install.') + + def Run(self): + if self.additional_apk_helpers: + for additional_apk_helper in self.additional_apk_helpers: + _InstallApk(self.devices, additional_apk_helper, None) + if self.is_bundle: + modules = list( + set(self.args.module) - set(self.args.no_module) - + set(self.args.fake)) + _InstallBundle(self.devices, self.apk_helper, modules, self.args.fake) + else: + _InstallApk(self.devices, self.apk_helper, self.install_dict) + + +class _UninstallCommand(_Command): + name = 'uninstall' + description = 'Removes the APK or bundle from one or more devices.' + needs_package_name = True + + def Run(self): + _UninstallApk(self.devices, self.install_dict, self.args.package_name) + + +class _SetWebViewProviderCommand(_Command): + name = 'set-webview-provider' + description = ("Sets the device's WebView provider to this APK's " + "package name.") + needs_package_name = True + needs_apk_helper = True + + def Run(self): + if not _IsWebViewProvider(self.apk_helper): + raise Exception('This package does not have a WebViewLibrary meta-data ' + 'tag. Are you sure it contains a WebView implementation?') + _SetWebViewProvider(self.devices, self.args.package_name) + + +class _LaunchCommand(_Command): + name = 'launch' + description = ('Sends a launch intent for the APK or bundle after first ' + 'writing the command-line flags file.') + needs_package_name = True + accepts_command_line_flags = True + all_devices_by_default = True + + def _RegisterExtraArgs(self, group): + group.add_argument('-w', '--wait-for-java-debugger', action='store_true', + help='Pause execution until debugger attaches. Applies ' + 'only to the main process. To have renderers wait, ' + 'use --args="--renderer-wait-for-java-debugger"') + group.add_argument('--debug-process-name', + help='Name of the process to debug. ' + 'E.g. "privileged_process0", or "foo.bar:baz"') + group.add_argument('--nokill', action='store_true', + help='Do not set the debug-app, nor set command-line ' + 'flags. Useful to load a URL without having the ' + 'app restart.') + group.add_argument('url', nargs='?', help='A URL to launch with.') + + def Run(self): + if self.args.url and self.is_bundle: + # TODO(digit): Support this, maybe by using 'dumpsys' as described + # in the _LaunchUrl() comment. + raise Exception('Launching with URL not supported for bundles yet!') + _LaunchUrl(self.devices, self.args.package_name, argv=self.args.args, + command_line_flags_file=self.args.command_line_flags_file, + url=self.args.url, apk=self.apk_helper, + wait_for_java_debugger=self.args.wait_for_java_debugger, + debug_process_name=self.args.debug_process_name, + nokill=self.args.nokill) + + +class _StopCommand(_Command): + name = 'stop' + description = 'Force-stops the app.' + needs_package_name = True + all_devices_by_default = True + + def Run(self): + device_utils.DeviceUtils.parallel(self.devices).ForceStop( + self.args.package_name) + + +class _ClearDataCommand(_Command): + name = 'clear-data' + descriptions = 'Clears all app data.' + needs_package_name = True + all_devices_by_default = True + + def Run(self): + device_utils.DeviceUtils.parallel(self.devices).ClearApplicationState( + self.args.package_name) + + +class _ArgvCommand(_Command): + name = 'argv' + description = 'Display and optionally update command-line flags file.' + needs_package_name = True + accepts_command_line_flags = True + all_devices_by_default = True + + def Run(self): + _ChangeFlags(self.devices, self.args.args, + self.args.command_line_flags_file) + + +class _GdbCommand(_Command): + name = 'gdb' + description = 'Runs //build/android/adb_gdb with apk-specific args.' + long_description = description + """ + +To attach to a process other than the APK's main process, use --pid=1234. +To list all PIDs, use the "ps" command. + +If no apk process is currently running, sends a launch intent. +""" + needs_package_name = True + needs_output_directory = True + calls_exec = True + supports_multiple_devices = False + + def Run(self): + _RunGdb(self.devices[0], self.args.package_name, + self.args.debug_process_name, self.args.pid, + self.args.output_directory, self.args.target_cpu, self.args.port, + self.args.ide, bool(self.args.verbose_count)) + + def _RegisterExtraArgs(self, group): + pid_group = group.add_mutually_exclusive_group() + pid_group.add_argument('--debug-process-name', + help='Name of the process to attach to. ' + 'E.g. "privileged_process0", or "foo.bar:baz"') + pid_group.add_argument('--pid', + help='The process ID to attach to. Defaults to ' + 'the main process for the package.') + group.add_argument('--ide', action='store_true', + help='Rather than enter a gdb prompt, set up the ' + 'gdb connection and wait for an IDE to ' + 'connect.') + # Same default port that ndk-gdb.py uses. + group.add_argument('--port', type=int, default=5039, + help='Use the given port for the GDB connection') + + +class _LogcatCommand(_Command): + name = 'logcat' + description = 'Runs "adb logcat" with filters relevant the current APK.' + long_description = description + """ + +"Relevant filters" means: + * Log messages from processes belonging to the apk, + * Plus log messages from log tags: ActivityManager|DEBUG, + * Plus fatal logs from any process, + * Minus spamy dalvikvm logs (for pre-L devices). + +Colors: + * Primary process is white + * Other processes (gpu, renderer) are yellow + * Non-apk processes are grey + * UI thread has a bolded Thread-ID + +Java stack traces are detected and deobfuscated (for release builds). + +To disable filtering, (but keep coloring), use --verbose. +""" + needs_package_name = True + supports_multiple_devices = False + + def Run(self): + deobfuscate = None + if self.args.proguard_mapping_path and not self.args.no_deobfuscate: + deobfuscate = deobfuscator.Deobfuscator(self.args.proguard_mapping_path) + + stack_script_context = _StackScriptContext( + self.args.output_directory, + self.args.apk_path, + self.bundle_generation_info, + quiet=True) + try: + _RunLogcat(self.devices[0], self.args.package_name, stack_script_context, + deobfuscate, bool(self.args.verbose_count)) + except KeyboardInterrupt: + pass # Don't show stack trace upon Ctrl-C + finally: + stack_script_context.Close() + if deobfuscate: + deobfuscate.Close() + + def _RegisterExtraArgs(self, group): + if self._from_wrapper_script: + group.add_argument('--no-deobfuscate', action='store_true', + help='Disables ProGuard deobfuscation of logcat.') + else: + group.set_defaults(no_deobfuscate=False) + group.add_argument('--proguard-mapping-path', + help='Path to ProGuard map (enables deobfuscation)') + + +class _PsCommand(_Command): + name = 'ps' + description = 'Show PIDs of any APK processes currently running.' + needs_package_name = True + all_devices_by_default = True + + def Run(self): + _RunPs(self.devices, self.args.package_name) + + +class _DiskUsageCommand(_Command): + name = 'disk-usage' + description = 'Show how much device storage is being consumed by the app.' + needs_package_name = True + all_devices_by_default = True + + def Run(self): + _RunDiskUsage(self.devices, self.args.package_name) + + +class _MemUsageCommand(_Command): + name = 'mem-usage' + description = 'Show memory usage of currently running APK processes.' + needs_package_name = True + all_devices_by_default = True + + def _RegisterExtraArgs(self, group): + group.add_argument('--query-app', action='store_true', + help='Do not add --local to "dumpsys meminfo". This will output ' + 'additional metrics (e.g. Context count), but also cause memory ' + 'to be used in order to gather the metrics.') + + def Run(self): + _RunMemUsage(self.devices, self.args.package_name, + query_app=self.args.query_app) + + +class _ShellCommand(_Command): + name = 'shell' + description = ('Same as "adb shell ", but runs as the apk\'s uid ' + '(via run-as). Useful for inspecting the app\'s data ' + 'directory.') + needs_package_name = True + + @property + def calls_exec(self): + return not self.args.cmd + + @property + def supports_multiple_devices(self): + return not self.args.cmd + + def _RegisterExtraArgs(self, group): + group.add_argument( + 'cmd', nargs=argparse.REMAINDER, help='Command to run.') + + def Run(self): + _RunShell(self.devices, self.args.package_name, self.args.cmd) + + +class _CompileDexCommand(_Command): + name = 'compile-dex' + description = ('Applicable only for Android N+. Forces .odex files to be ' + 'compiled with the given compilation filter. To see existing ' + 'filter, use "disk-usage" command.') + needs_package_name = True + all_devices_by_default = True + + def _RegisterExtraArgs(self, group): + group.add_argument( + 'compilation_filter', + choices=['verify', 'quicken', 'space-profile', 'space', + 'speed-profile', 'speed'], + help='For WebView/Monochrome, use "speed". For other apks, use ' + '"speed-profile".') + + def Run(self): + _RunCompileDex(self.devices, self.args.package_name, + self.args.compilation_filter) + + +class _PrintCertsCommand(_Command): + name = 'print-certs' + description = 'Print info about certificates used to sign this APK.' + need_device_args = False + needs_apk_helper = True + + def _RegisterExtraArgs(self, group): + group.add_argument( + '--full-cert', + action='store_true', + help=("Print the certificate's full signature, Base64-encoded. " + "Useful when configuring an Android image's " + "config_webview_packages.xml.")) + + def Run(self): + keytool = os.path.join(_JAVA_HOME, 'bin', 'keytool') + if self.is_bundle: + # Bundles are not signed until converted to .apks. The wrapper scripts + # record which key will be used to sign though. + with tempfile.NamedTemporaryFile() as f: + logging.warning('Bundles are not signed until turned into .apk files.') + logging.warning('Showing signing info based on associated keystore.') + cmd = [ + keytool, '-exportcert', '-keystore', + self.bundle_generation_info.keystore_path, '-storepass', + self.bundle_generation_info.keystore_password, '-alias', + self.bundle_generation_info.keystore_alias, '-file', f.name + ] + subprocess.check_output(cmd, stderr=subprocess.STDOUT) + cmd = [keytool, '-printcert', '-file', f.name] + logging.warning('Running: %s', ' '.join(cmd)) + subprocess.check_call(cmd) + if self.args.full_cert: + # Redirect stderr to hide a keytool warning about using non-standard + # keystore format. + full_output = subprocess.check_output( + cmd + ['-rfc'], stderr=subprocess.STDOUT) + else: + cmd = [ + build_tools.GetPath('apksigner'), 'verify', '--print-certs', + '--verbose', self.apk_helper.path + ] + logging.warning('Running: %s', ' '.join(cmd)) + env = os.environ.copy() + env['PATH'] = os.path.pathsep.join( + [os.path.join(_JAVA_HOME, 'bin'), + env.get('PATH')]) + stdout = subprocess.check_output(cmd, env=env) + print(stdout) + if self.args.full_cert: + if 'v1 scheme (JAR signing): true' not in stdout: + raise Exception( + 'Cannot print full certificate because apk is not V1 signed.') + + cmd = [keytool, '-printcert', '-jarfile', self.apk_helper.path, '-rfc'] + # Redirect stderr to hide a keytool warning about using non-standard + # keystore format. + full_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + + if self.args.full_cert: + m = re.search( + r'-+BEGIN CERTIFICATE-+([\r\n0-9A-Za-z+/=]+)-+END CERTIFICATE-+', + full_output, re.MULTILINE) + if not m: + raise Exception('Unable to parse certificate:\n{}'.format(full_output)) + signature = re.sub(r'[\r\n]+', '', m.group(1)) + print() + print('Full Signature:') + print(signature) + + +class _ProfileCommand(_Command): + name = 'profile' + description = ('Run the simpleperf sampling CPU profiler on the currently-' + 'running APK. If --args is used, the extra arguments will be ' + 'passed on to simpleperf; otherwise, the following default ' + 'arguments are used: -g -f 1000 -o /data/local/tmp/perf.data') + needs_package_name = True + needs_output_directory = True + supports_multiple_devices = False + accepts_args = True + + def _RegisterExtraArgs(self, group): + group.add_argument( + '--profile-process', default='browser', + help=('Which process to profile. This may be a process name or pid ' + 'such as you would get from running `%s ps`; or ' + 'it can be one of (browser, renderer, gpu).' % sys.argv[0])) + group.add_argument( + '--profile-thread', default=None, + help=('(Optional) Profile only a single thread. This may be either a ' + 'thread ID such as you would get by running `adb shell ps -t` ' + '(pre-Oreo) or `adb shell ps -e -T` (Oreo and later); or it may ' + 'be one of (io, compositor, main, render), in which case ' + '--profile-process is also required. (Note that "render" thread ' + 'refers to a thread in the browser process that manages a ' + 'renderer; to profile the main thread of the renderer process, ' + 'use --profile-thread=main).')) + group.add_argument('--profile-output', default='profile.pb', + help='Output file for profiling data') + + def Run(self): + extra_args = shlex.split(self.args.args or '') + _RunProfile(self.devices[0], self.args.package_name, + self.args.output_directory, self.args.profile_output, + self.args.profile_process, self.args.profile_thread, + extra_args) + + +class _RunCommand(_InstallCommand, _LaunchCommand, _LogcatCommand): + name = 'run' + description = 'Install, launch, and show logcat (when targeting one device).' + all_devices_by_default = False + supports_multiple_devices = True + + def _RegisterExtraArgs(self, group): + _InstallCommand._RegisterExtraArgs(self, group) + _LaunchCommand._RegisterExtraArgs(self, group) + _LogcatCommand._RegisterExtraArgs(self, group) + group.add_argument('--no-logcat', action='store_true', + help='Install and launch, but do not enter logcat.') + + def Run(self): + logging.warning('Installing...') + _InstallCommand.Run(self) + logging.warning('Sending launch intent...') + _LaunchCommand.Run(self) + if len(self.devices) == 1 and not self.args.no_logcat: + logging.warning('Entering logcat...') + _LogcatCommand.Run(self) + + +class _BuildBundleApks(_Command): + name = 'build-bundle-apks' + description = ('Build the .apks archive from an Android app bundle, and ' + 'optionally copy it to a specific destination.') + need_device_args = False + + def _RegisterExtraArgs(self, group): + group.add_argument( + '--output-apks', required=True, help='Destination path for .apks file.') + group.add_argument( + '--minimal', + action='store_true', + help='Build .apks archive that targets the bundle\'s minSdkVersion and ' + 'contains only english splits. It still contains optional splits.') + group.add_argument( + '--sdk-version', help='The sdkVersion to build the .apks for.') + group.add_argument( + '--build-mode', + choices=app_bundle_utils.BUILD_APKS_MODES, + help='Specify which type of APKs archive to build. "default" ' + 'generates regular splits, "universal" generates an archive with a ' + 'single universal APK, "system" generates an archive with a system ' + 'image APK, while "system_compressed" generates a compressed system ' + 'APK, with an additional stub APK for the system image.') + group.add_argument( + '--optimize-for', + choices=app_bundle_utils.OPTIMIZE_FOR_OPTIONS, + help='Override split configuration.') + + def Run(self): + _GenerateBundleApks( + self.bundle_generation_info, + output_path=self.args.output_apks, + minimal=self.args.minimal, + minimal_sdk_version=self.args.sdk_version, + mode=self.args.build_mode, + optimize_for=self.args.optimize_for) + + +class _ManifestCommand(_Command): + name = 'dump-manifest' + description = 'Dump the android manifest from this bundle, as XML, to stdout.' + need_device_args = False + + def Run(self): + sys.stdout.write( + bundletool.RunBundleTool([ + 'dump', 'manifest', '--bundle', + self.bundle_generation_info.bundle_path + ])) + + +class _StackCommand(_Command): + name = 'stack' + description = 'Decodes an Android stack.' + need_device_args = False + + def _RegisterExtraArgs(self, group): + group.add_argument( + 'file', + nargs='?', + help='File to decode. If not specified, stdin is processed.') + + def Run(self): + context = _StackScriptContext(self.args.output_directory, + self.args.apk_path, + self.bundle_generation_info) + try: + proc = context.Popen(input_file=self.args.file) + if proc.wait(): + raise Exception('stack script returned {}'.format(proc.returncode)) + finally: + context.Close() + + +# Shared commands for regular APKs and app bundles. +_COMMANDS = [ + _DevicesCommand, + _PackageInfoCommand, + _InstallCommand, + _UninstallCommand, + _SetWebViewProviderCommand, + _LaunchCommand, + _StopCommand, + _ClearDataCommand, + _ArgvCommand, + _GdbCommand, + _LogcatCommand, + _PsCommand, + _DiskUsageCommand, + _MemUsageCommand, + _ShellCommand, + _CompileDexCommand, + _PrintCertsCommand, + _ProfileCommand, + _RunCommand, + _StackCommand, +] + +# Commands specific to app bundles. +_BUNDLE_COMMANDS = [ + _BuildBundleApks, + _ManifestCommand, +] + + +def _ParseArgs(parser, from_wrapper_script, is_bundle): + subparsers = parser.add_subparsers() + command_list = _COMMANDS + (_BUNDLE_COMMANDS if is_bundle else []) + commands = [clazz(from_wrapper_script, is_bundle) for clazz in command_list] + + for command in commands: + if from_wrapper_script or not command.needs_output_directory: + command.RegisterArgs(subparsers) + + # Show extended help when no command is passed. + argv = sys.argv[1:] + if not argv: + argv = ['--help'] + + return parser.parse_args(argv) + + +def _RunInternal(parser, + output_directory=None, + additional_apk_paths=None, + bundle_generation_info=None): + colorama.init() + parser.set_defaults( + additional_apk_paths=additional_apk_paths, + output_directory=output_directory) + from_wrapper_script = bool(output_directory) + args = _ParseArgs(parser, from_wrapper_script, bool(bundle_generation_info)) + run_tests_helper.SetLogLevel(args.verbose_count) + if bundle_generation_info: + args.command.RegisterBundleGenerationInfo(bundle_generation_info) + if args.additional_apk_paths: + for path in additional_apk_paths: + if not path or not os.path.exists(path): + raise Exception('Invalid additional APK path "{}"'.format(path)) + args.command.ProcessArgs(args) + args.command.Run() + # Incremental install depends on the cache being cleared when uninstalling. + if args.command.name != 'uninstall': + _SaveDeviceCaches(args.command.devices, output_directory) + + +def Run(output_directory, apk_path, additional_apk_paths, incremental_json, + command_line_flags_file, target_cpu, proguard_mapping_path): + """Entry point for generated wrapper scripts.""" + constants.SetOutputDirectory(output_directory) + devil_chromium.Initialize(output_directory=output_directory) + parser = argparse.ArgumentParser() + exists_or_none = lambda p: p if p and os.path.exists(p) else None + + parser.set_defaults( + command_line_flags_file=command_line_flags_file, + target_cpu=target_cpu, + apk_path=exists_or_none(apk_path), + incremental_json=exists_or_none(incremental_json), + proguard_mapping_path=proguard_mapping_path) + _RunInternal( + parser, + output_directory=output_directory, + additional_apk_paths=additional_apk_paths) + + +def RunForBundle(output_directory, bundle_path, bundle_apks_path, + additional_apk_paths, aapt2_path, keystore_path, + keystore_password, keystore_alias, package_name, + command_line_flags_file, proguard_mapping_path, target_cpu, + system_image_locales, default_modules): + """Entry point for generated app bundle wrapper scripts. + + Args: + output_dir: Chromium output directory path. + bundle_path: Input bundle path. + bundle_apks_path: Output bundle .apks archive path. + additional_apk_paths: Additional APKs to install prior to bundle install. + aapt2_path: Aapt2 tool path. + keystore_path: Keystore file path. + keystore_password: Keystore password. + keystore_alias: Signing key name alias in keystore file. + package_name: Application's package name. + command_line_flags_file: Optional. Name of an on-device file that will be + used to store command-line flags for this bundle. + proguard_mapping_path: Input path to the Proguard mapping file, used to + deobfuscate Java stack traces. + target_cpu: Chromium target CPU name, used by the 'gdb' command. + system_image_locales: List of Chromium locales that should be included in + system image APKs. + default_modules: List of modules that are installed in addition to those + given by the '-m' switch. + """ + constants.SetOutputDirectory(output_directory) + devil_chromium.Initialize(output_directory=output_directory) + bundle_generation_info = BundleGenerationInfo( + bundle_path=bundle_path, + bundle_apks_path=bundle_apks_path, + aapt2_path=aapt2_path, + keystore_path=keystore_path, + keystore_password=keystore_password, + keystore_alias=keystore_alias, + system_image_locales=system_image_locales) + _InstallCommand.default_modules = default_modules + + parser = argparse.ArgumentParser() + parser.set_defaults( + package_name=package_name, + command_line_flags_file=command_line_flags_file, + proguard_mapping_path=proguard_mapping_path, + target_cpu=target_cpu) + _RunInternal( + parser, + output_directory=output_directory, + additional_apk_paths=additional_apk_paths, + bundle_generation_info=bundle_generation_info) + + +def main(): + devil_chromium.Initialize() + _RunInternal(argparse.ArgumentParser()) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/apk_operations.pydeps b/third_party/libwebrtc/build/android/apk_operations.pydeps new file mode 100644 index 0000000000..0bd7b7f9dc --- /dev/null +++ b/third_party/libwebrtc/build/android/apk_operations.pydeps @@ -0,0 +1,113 @@ +# Generated by running: +# build/print_python_deps.py --root build/android --output build/android/apk_operations.pydeps build/android/apk_operations.py +../../third_party/catapult/common/py_utils/py_utils/__init__.py +../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py +../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py +../../third_party/catapult/common/py_utils/py_utils/lock.py +../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py +../../third_party/catapult/dependency_manager/dependency_manager/__init__.py +../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py +../../third_party/catapult/dependency_manager/dependency_manager/base_config.py +../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py +../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py +../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py +../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py +../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py +../../third_party/catapult/dependency_manager/dependency_manager/manager.py +../../third_party/catapult/dependency_manager/dependency_manager/uploader.py +../../third_party/catapult/devil/devil/__init__.py +../../third_party/catapult/devil/devil/android/__init__.py +../../third_party/catapult/devil/devil/android/apk_helper.py +../../third_party/catapult/devil/devil/android/constants/__init__.py +../../third_party/catapult/devil/devil/android/constants/chrome.py +../../third_party/catapult/devil/devil/android/constants/file_system.py +../../third_party/catapult/devil/devil/android/decorators.py +../../third_party/catapult/devil/devil/android/device_denylist.py +../../third_party/catapult/devil/devil/android/device_errors.py +../../third_party/catapult/devil/devil/android/device_signal.py +../../third_party/catapult/devil/devil/android/device_temp_file.py +../../third_party/catapult/devil/devil/android/device_utils.py +../../third_party/catapult/devil/devil/android/flag_changer.py +../../third_party/catapult/devil/devil/android/install_commands.py +../../third_party/catapult/devil/devil/android/logcat_monitor.py +../../third_party/catapult/devil/devil/android/md5sum.py +../../third_party/catapult/devil/devil/android/ndk/__init__.py +../../third_party/catapult/devil/devil/android/ndk/abis.py +../../third_party/catapult/devil/devil/android/sdk/__init__.py +../../third_party/catapult/devil/devil/android/sdk/aapt.py +../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py +../../third_party/catapult/devil/devil/android/sdk/build_tools.py +../../third_party/catapult/devil/devil/android/sdk/bundletool.py +../../third_party/catapult/devil/devil/android/sdk/intent.py +../../third_party/catapult/devil/devil/android/sdk/keyevent.py +../../third_party/catapult/devil/devil/android/sdk/split_select.py +../../third_party/catapult/devil/devil/android/sdk/version_codes.py +../../third_party/catapult/devil/devil/android/tools/__init__.py +../../third_party/catapult/devil/devil/android/tools/script_common.py +../../third_party/catapult/devil/devil/base_error.py +../../third_party/catapult/devil/devil/constants/__init__.py +../../third_party/catapult/devil/devil/constants/exit_codes.py +../../third_party/catapult/devil/devil/devil_env.py +../../third_party/catapult/devil/devil/utils/__init__.py +../../third_party/catapult/devil/devil/utils/cmd_helper.py +../../third_party/catapult/devil/devil/utils/host_utils.py +../../third_party/catapult/devil/devil/utils/lazy/__init__.py +../../third_party/catapult/devil/devil/utils/lazy/weak_constant.py +../../third_party/catapult/devil/devil/utils/logging_common.py +../../third_party/catapult/devil/devil/utils/lsusb.py +../../third_party/catapult/devil/devil/utils/parallelizer.py +../../third_party/catapult/devil/devil/utils/reraiser_thread.py +../../third_party/catapult/devil/devil/utils/reset_usb.py +../../third_party/catapult/devil/devil/utils/run_tests_helper.py +../../third_party/catapult/devil/devil/utils/timeout_retry.py +../../third_party/catapult/devil/devil/utils/watchdog_timer.py +../../third_party/catapult/devil/devil/utils/zip_utils.py +../../third_party/catapult/third_party/six/six.py +../../third_party/jinja2/__init__.py +../../third_party/jinja2/_compat.py +../../third_party/jinja2/_identifier.py +../../third_party/jinja2/asyncfilters.py +../../third_party/jinja2/asyncsupport.py +../../third_party/jinja2/bccache.py +../../third_party/jinja2/compiler.py +../../third_party/jinja2/defaults.py +../../third_party/jinja2/environment.py +../../third_party/jinja2/exceptions.py +../../third_party/jinja2/filters.py +../../third_party/jinja2/idtracking.py +../../third_party/jinja2/lexer.py +../../third_party/jinja2/loaders.py +../../third_party/jinja2/nodes.py +../../third_party/jinja2/optimizer.py +../../third_party/jinja2/parser.py +../../third_party/jinja2/runtime.py +../../third_party/jinja2/tests.py +../../third_party/jinja2/utils.py +../../third_party/jinja2/visitor.py +../../third_party/markupsafe/__init__.py +../../third_party/markupsafe/_compat.py +../../third_party/markupsafe/_native.py +../gn_helpers.py +../print_python_deps.py +adb_command_line.py +apk_operations.py +convert_dex_profile.py +devil_chromium.py +gyp/bundletool.py +gyp/dex.py +gyp/util/__init__.py +gyp/util/build_utils.py +gyp/util/md5_check.py +gyp/util/resource_utils.py +gyp/util/zipalign.py +incremental_install/__init__.py +incremental_install/installer.py +pylib/__init__.py +pylib/constants/__init__.py +pylib/constants/host_paths.py +pylib/symbols/__init__.py +pylib/symbols/deobfuscator.py +pylib/utils/__init__.py +pylib/utils/app_bundle_utils.py +pylib/utils/simpleperf.py +pylib/utils/time_profile.py diff --git a/third_party/libwebrtc/build/android/apply_shared_preference_file.py b/third_party/libwebrtc/build/android/apply_shared_preference_file.py new file mode 100755 index 0000000000..fbeed28c16 --- /dev/null +++ b/third_party/libwebrtc/build/android/apply_shared_preference_file.py @@ -0,0 +1,50 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Manually applies a shared preference JSON file. + +If needed during automation, use the --shared-prefs-file in test_runner.py +instead. +""" + +import argparse +import sys + +# pylint: disable=ungrouped-imports +from pylib.constants import host_paths +if host_paths.DEVIL_PATH not in sys.path: + sys.path.append(host_paths.DEVIL_PATH) + +from devil.android import device_utils +from devil.android.sdk import shared_prefs +from pylib.utils import shared_preference_utils + + +def main(): + parser = argparse.ArgumentParser( + description='Manually apply shared preference JSON files.') + parser.add_argument('filepaths', nargs='*', + help='Any number of paths to shared preference JSON ' + 'files to apply.') + args = parser.parse_args() + + all_devices = device_utils.DeviceUtils.HealthyDevices() + if not all_devices: + raise RuntimeError('No healthy devices attached') + + for filepath in args.filepaths: + all_settings = shared_preference_utils.ExtractSettingsFromJson(filepath) + for setting in all_settings: + for device in all_devices: + shared_pref = shared_prefs.SharedPrefs( + device, setting['package'], setting['filename'], + use_encrypted_path=setting.get('supports_encrypted_path', False)) + shared_preference_utils.ApplySharedPreferenceSetting( + shared_pref, setting) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/asan_symbolize.py b/third_party/libwebrtc/build/android/asan_symbolize.py new file mode 100755 index 0000000000..60b00d0049 --- /dev/null +++ b/third_party/libwebrtc/build/android/asan_symbolize.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# 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 __future__ import print_function + +import collections +import optparse +import os +import re +import sys + +from pylib import constants +from pylib.constants import host_paths + +# pylint: disable=wrong-import-order +# Uses symbol.py from third_party/android_platform, not python's. +with host_paths.SysPath( + host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, + position=0): + import symbol + + +_RE_ASAN = re.compile( + r""" + (?P.*?) + (?P\#\S*?) # position of the call in stack. + # escape the char "#" due to the VERBOSE flag. + \s+(\S*?)\s+ + \( # match the char "(". + (?P.*?) # library path. + \+0[xX](?P.*?) # address of the symbol in hex. + # the prefix "0x" is skipped. + \) # match the char ")". + """, re.VERBOSE) + +# This named tuple models a parsed Asan log line. +AsanParsedLine = collections.namedtuple('AsanParsedLine', + 'prefix,library,pos,rel_address') + +# This named tuple models an Asan log line. 'raw' is the raw content +# while 'parsed' is None or an AsanParsedLine instance. +AsanLogLine = collections.namedtuple('AsanLogLine', 'raw,parsed') + +def _ParseAsanLogLine(line): + """Parse line into corresponding AsanParsedLine value, if any, or None.""" + m = re.match(_RE_ASAN, line) + if not m: + return None + return AsanParsedLine(prefix=m.group('prefix'), + library=m.group('lib'), + pos=m.group('pos'), + rel_address='%08x' % int(m.group('addr'), 16)) + + +def _FindASanLibraries(): + asan_lib_dir = os.path.join(host_paths.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 + # pylint: disable=no-member + return symbol.TranslateLibPath(library) + + +def _PrintSymbolized(asan_input, arch): + """Print symbolized logcat output for Asan symbols. + + Args: + asan_input: list of input lines. + arch: Target CPU architecture. + """ + asan_libs = _FindASanLibraries() + + # Maps library -> [ AsanParsedLine... ] + libraries = collections.defaultdict(list) + + asan_log_lines = [] + for line in asan_input: + line = line.rstrip() + parsed = _ParseAsanLogLine(line) + if parsed: + libraries[parsed.library].append(parsed) + asan_log_lines.append(AsanLogLine(raw=line, parsed=parsed)) + + # Maps library -> { address -> [(symbol, location, obj_sym_with_offset)...] } + all_symbols = collections.defaultdict(dict) + + for library, items in libraries.items(): + libname = _TranslateLibPath(library, asan_libs) + lib_relative_addrs = set([i.rel_address for i in items]) + # pylint: disable=no-member + info_dict = symbol.SymbolInformationForSet(libname, + lib_relative_addrs, + True, + cpu_arch=arch) + if info_dict: + all_symbols[library] = info_dict + + for log_line in asan_log_lines: + m = log_line.parsed + if (m and m.library in all_symbols and + m.rel_address in all_symbols[m.library]): + # NOTE: all_symbols[lib][address] is a never-emtpy list of tuples. + # NOTE: The documentation for SymbolInformationForSet() indicates + # that usually one wants to display the last list item, not the first. + # The code below takes the first, is this the best choice here? + s = all_symbols[m.library][m.rel_address][0] + print('%s%s %s %s' % (m.prefix, m.pos, s[0], s[1])) + else: + print(log_line.raw) + + +def main(): + parser = optparse.OptionParser() + parser.add_option('-l', '--logcat', + help='File containing adb logcat output with ASan stacks. ' + 'Use stdin if not specified.') + parser.add_option('--output-directory', + help='Path to the root build directory.') + parser.add_option('--arch', default='arm', + help='CPU architecture name') + options, _ = parser.parse_args() + + if options.output_directory: + constants.SetOutputDirectory(options.output_directory) + # Do an up-front test that the output directory is known. + constants.CheckOutputDirectory() + + if options.logcat: + asan_input = open(options.logcat, 'r') + else: + asan_input = sys.stdin + + _PrintSymbolized(asan_input.readlines(), options.arch) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/bytecode/BUILD.gn b/third_party/libwebrtc/build/android/bytecode/BUILD.gn new file mode 100644 index 0000000000..8d717eb0ad --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/BUILD.gn @@ -0,0 +1,86 @@ +# Copyright 2017 The Chromium 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") + +java_binary("bytecode_processor") { + main_class = "org.chromium.bytecode.ByteCodeProcessor" + wrapper_script_name = "helper/bytecode_processor" + deps = [ ":bytecode_processor_java" ] +} + +java_library("bytecode_processor_java") { + sources = [ + "java/org/chromium/bytecode/ByteCodeProcessor.java", + "java/org/chromium/bytecode/ClassPathValidator.java", + "java/org/chromium/bytecode/TypeUtils.java", + ] + deps = [ + "//third_party/android_deps:org_ow2_asm_asm_java", + "//third_party/android_deps:org_ow2_asm_asm_util_java", + ] + enable_bytecode_checks = false +} + +# A bytecode rewriter that replaces all calls to +# `FragmentActivity Fragment.getActivity()` with +# `Activity Fragment.getActivity()`. +java_binary("fragment_activity_replacer") { + main_class = "org.chromium.bytecode.FragmentActivityReplacer" + deps = [ ":fragment_activity_replacer_java" ] + wrapper_script_name = "helper/fragment_activity_replacer" +} + +# A bytecode rewriter that replaces all calls to +# `FragmentActivity Fragment.getActivity()` with +# `Activity Fragment.getActivity()` followed by a cast to FragmentActivity. +# Prefer :fragment_activity_replacer. This rewriter should only be used for +# libraries that rely on getActivity() returning a FragmentActivity *and* are +# not going to be used in an app that contains multiple copies of the AndroidX +# Fragment library (i.e. WebLayer). +java_binary("fragment_activity_replacer_single_androidx") { + main_class = "org.chromium.bytecode.FragmentActivityReplacer" + deps = [ ":fragment_activity_replacer_java" ] + wrapper_script_name = "helper/fragment_activity_replacer_single_androidx" + wrapper_script_args = [ "--single-androidx" ] +} + +java_library("fragment_activity_replacer_java") { + visibility = [ ":*" ] + sources = [ + "java/org/chromium/bytecode/ByteCodeRewriter.java", + "java/org/chromium/bytecode/FragmentActivityReplacer.java", + ] + deps = [ + "//third_party/android_deps:org_ow2_asm_asm_commons_java", + "//third_party/android_deps:org_ow2_asm_asm_java", + "//third_party/android_deps:org_ow2_asm_asm_util_java", + ] +} + +java_binary("trace_event_adder") { + main_class = "org.chromium.bytecode.TraceEventAdder" + deps = [ ":trace_event_adder_java" ] + wrapper_script_name = "helper/trace_event_adder" +} + +java_library("trace_event_adder_java") { + visibility = [ ":*" ] + sources = [ + "java/org/chromium/bytecode/ByteCodeRewriter.java", + "java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java", + "java/org/chromium/bytecode/MethodCheckerClassAdapter.java", + "java/org/chromium/bytecode/MethodDescription.java", + "java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java", + "java/org/chromium/bytecode/TraceEventAdder.java", + "java/org/chromium/bytecode/TraceEventAdderClassAdapter.java", + "java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java", + ] + deps = [ + ":bytecode_processor_java", + "//third_party/android_deps:org_ow2_asm_asm_commons_java", + "//third_party/android_deps:org_ow2_asm_asm_java", + "//third_party/android_deps:org_ow2_asm_asm_util_java", + ] +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java new file mode 100644 index 0000000000..b767f4f089 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java @@ -0,0 +1,167 @@ +// Copyright 2017 The Chromium 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.bytecode; + +import org.objectweb.asm.ClassReader; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Java application that takes in an input jar, performs a series of bytecode + * transformations, and generates an output jar. + */ +class ByteCodeProcessor { + private static final String CLASS_FILE_SUFFIX = ".class"; + private static final int BUFFER_SIZE = 16384; + private static boolean sVerbose; + private static boolean sIsPrebuilt; + private static ClassLoader sDirectClassPathClassLoader; + private static ClassLoader sFullClassPathClassLoader; + private static Set sFullClassPathJarPaths; + private static Set sMissingClassesAllowlist; + private static Map sJarToGnTarget; + private static ClassPathValidator sValidator; + + private static Void processEntry(ZipEntry entry, byte[] data) { + ClassReader reader = new ClassReader(data); + if (sIsPrebuilt) { + sValidator.validateFullClassPath( + reader, sFullClassPathClassLoader, sMissingClassesAllowlist); + } else { + sValidator.validateDirectClassPath(reader, sDirectClassPathClassLoader, + sFullClassPathClassLoader, sFullClassPathJarPaths, sMissingClassesAllowlist, + sVerbose); + } + return null; + } + + private static void process(String gnTarget, String inputJarPath) + throws ExecutionException, InterruptedException { + ExecutorService executorService = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + try (ZipInputStream inputStream = new ZipInputStream( + new BufferedInputStream(new FileInputStream(inputJarPath)))) { + while (true) { + ZipEntry entry = inputStream.getNextEntry(); + if (entry == null) { + break; + } + byte[] data = readAllBytes(inputStream); + executorService.submit(() -> processEntry(entry, data)); + } + executorService.shutdown(); // This is essential in order to avoid waiting infinitely. + executorService.awaitTermination(1, TimeUnit.HOURS); + } catch (IOException e) { + throw new RuntimeException(e); + } + + if (sValidator.hasErrors()) { + sValidator.printAll(gnTarget, sJarToGnTarget); + System.exit(1); + } + } + + private static byte[] readAllBytes(InputStream inputStream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int numRead = 0; + byte[] data = new byte[BUFFER_SIZE]; + while ((numRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, numRead); + } + return buffer.toByteArray(); + } + + /** + * Loads a list of jars and returns a ClassLoader capable of loading all classes found in the + * given jars. + */ + static ClassLoader loadJars(Collection paths) { + URL[] jarUrls = new URL[paths.size()]; + int i = 0; + for (String path : paths) { + try { + jarUrls[i++] = new File(path).toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + return new URLClassLoader(jarUrls); + } + + /** + * Extracts a length-encoded list of strings from the arguments, and adds them to |out|. Returns + * the new "next index" to be processed. + */ + private static int parseListArgument(String[] args, int index, Collection out) { + int argLength = Integer.parseInt(args[index++]); + out.addAll(Arrays.asList(Arrays.copyOfRange(args, index, index + argLength))); + return index + argLength; + } + + public static void main(String[] args) throws ClassPathValidator.ClassNotLoadedException, + ExecutionException, InterruptedException { + // Invoke this script using //build/android/gyp/bytecode_processor.py + int currIndex = 0; + String gnTarget = args[currIndex++]; + String inputJarPath = args[currIndex++]; + sVerbose = args[currIndex++].equals("--verbose"); + sIsPrebuilt = args[currIndex++].equals("--is-prebuilt"); + + sMissingClassesAllowlist = new HashSet<>(); + currIndex = parseListArgument(args, currIndex, sMissingClassesAllowlist); + + ArrayList sdkJarPaths = new ArrayList<>(); + currIndex = parseListArgument(args, currIndex, sdkJarPaths); + + ArrayList directClassPathJarPaths = new ArrayList<>(); + directClassPathJarPaths.add(inputJarPath); + directClassPathJarPaths.addAll(sdkJarPaths); + currIndex = parseListArgument(args, currIndex, directClassPathJarPaths); + sDirectClassPathClassLoader = loadJars(directClassPathJarPaths); + + ArrayList fullClassPathJarPaths = new ArrayList<>(); + currIndex = parseListArgument(args, currIndex, fullClassPathJarPaths); + ArrayList gnTargets = new ArrayList<>(); + parseListArgument(args, currIndex, gnTargets); + sJarToGnTarget = new HashMap<>(); + assert fullClassPathJarPaths.size() == gnTargets.size(); + for (int i = 0; i < fullClassPathJarPaths.size(); ++i) { + sJarToGnTarget.put(fullClassPathJarPaths.get(i), gnTargets.get(i)); + } + + // Load all jars that are on the classpath for the input jar for analyzing class + // hierarchy. + sFullClassPathJarPaths = new HashSet<>(); + sFullClassPathJarPaths.add(inputJarPath); + sFullClassPathJarPaths.addAll(sdkJarPaths); + sFullClassPathJarPaths.addAll(fullClassPathJarPaths); + sFullClassPathClassLoader = loadJars(sFullClassPathJarPaths); + sFullClassPathJarPaths.removeAll(directClassPathJarPaths); + + sValidator = new ClassPathValidator(); + process(gnTarget, inputJarPath); + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java new file mode 100644 index 0000000000..37b0e86348 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java @@ -0,0 +1,101 @@ +// Copyright 2020 The Chromium 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.bytecode; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Base class for scripts that perform bytecode modifications on a jar file. + */ +public abstract class ByteCodeRewriter { + private static final String CLASS_FILE_SUFFIX = ".class"; + + public void rewrite(File inputJar, File outputJar) throws IOException { + if (!inputJar.exists()) { + throw new FileNotFoundException("Input jar not found: " + inputJar.getPath()); + } + try (InputStream inputStream = new BufferedInputStream(new FileInputStream(inputJar))) { + try (OutputStream outputStream = new FileOutputStream(outputJar)) { + processZip(inputStream, outputStream); + } + } + } + + /** Returns true if the class at the given path in the archive should be rewritten. */ + protected abstract boolean shouldRewriteClass(String classPath); + + /** + * Returns true if the class at the given {@link ClassReader} should be rewritten. + */ + protected boolean shouldRewriteClass(ClassReader classReader) { + return true; + } + + /** + * Returns the ClassVisitor that should be used to modify the bytecode of class at the given + * path in the archive. + */ + protected abstract ClassVisitor getClassVisitorForClass( + String classPath, ClassVisitor delegate); + + private void processZip(InputStream inputStream, OutputStream outputStream) { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + ZipInputStream zipInputStream = new ZipInputStream(inputStream); + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + boolean handled = processClassEntry(entry, zipInputStream, buffer); + if (handled) { + ZipEntry newEntry = new ZipEntry(entry.getName()); + zipOutputStream.putNextEntry(newEntry); + zipOutputStream.write(buffer.toByteArray(), 0, buffer.size()); + } else { + zipOutputStream.putNextEntry(entry); + zipInputStream.transferTo(zipOutputStream); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private boolean processClassEntry( + ZipEntry entry, InputStream inputStream, OutputStream outputStream) { + if (!entry.getName().endsWith(CLASS_FILE_SUFFIX) || !shouldRewriteClass(entry.getName())) { + return false; + } + try { + ClassReader reader = new ClassReader(inputStream); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); + ClassVisitor classVisitor = writer; + if (shouldRewriteClass(reader)) { + classVisitor = getClassVisitorForClass(entry.getName(), writer); + } + reader.accept(classVisitor, ClassReader.EXPAND_FRAMES); + + writer.visitEnd(); + byte[] classData = writer.toByteArray(); + outputStream.write(classData, 0, classData.length); + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java new file mode 100644 index 0000000000..9f45df5117 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java @@ -0,0 +1,233 @@ +// Copyright 2018 The Chromium 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.bytecode; + +import org.objectweb.asm.ClassReader; + +import java.io.PrintStream; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Consumer; + +/** + * Checks classpaths (given as ClassLoaders) by reading the constant pool of the class file and + * attempting to load every referenced class. If there are some that are unable to be found, it + * stores a helpful error message if it knows where it might find them, and exits the program if it + * can't find the class with any given classpath. + */ +public class ClassPathValidator { + // Number of warnings to print. + private static final int MAX_MISSING_CLASS_WARNINGS = 10; + // Number of missing classes to show per missing jar. + private static final int MAX_ERRORS_PER_JAR = 2; + // Map of missing .jar -> Missing class -> Classes that failed. + // TreeMap so that error messages have sorted list of jars. + private final Map>> mDirectErrors = + Collections.synchronizedMap(new TreeMap<>()); + // Missing classes we only track the first one for each jar. + // Map of missingClass -> srcClass. + private final Map mMissingClasses = + Collections.synchronizedMap(new TreeMap<>()); + + static class ClassNotLoadedException extends ClassNotFoundException { + private final String mClassName; + + ClassNotLoadedException(String className, Throwable ex) { + super("Couldn't load " + className, ex); + mClassName = className; + } + + public String getClassName() { + return mClassName; + } + } + + private static void validateClass(ClassLoader classLoader, String className) + throws ClassNotLoadedException { + if (className.startsWith("[")) { + // Dealing with an array type which isn't encoded nicely in the constant pool. + // For example, [[Lorg/chromium/Class$1; + className = className.substring(className.lastIndexOf('[') + 1); + if (className.charAt(0) == 'L' && className.endsWith(";")) { + className = className.substring(1, className.length() - 1); + } else { + // Bailing out if we have an non-class array type. + // This could be something like [B + return; + } + } + if (className.matches(".*\\bR(\\$\\w+)?$")) { + // Resources in R.java files are not expected to be valid at this stage in the build. + return; + } + if (className.matches("^libcore\\b.*")) { + // libcore exists on devices, but is not included in the Android sdk as it is a private + // API. + return; + } + try { + classLoader.loadClass(className.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw new ClassNotLoadedException(className, e); + } catch (NoClassDefFoundError e) { + // We assume that this is caused by another class that is not going to able to be + // loaded, so we will skip this and let that class fail with ClassNotFoundException. + } + } + + /** + * Given a .class file, see if every class referenced in the main class' constant pool can be + * loaded by the given ClassLoader. + * + * @param classReader .class file interface for reading the constant pool. + * @param classLoader classpath you wish to validate. + * @param errorConsumer Called for each missing class. + */ + private static void validateClassPath(ClassReader classReader, ClassLoader classLoader, + Consumer errorConsumer) { + char[] charBuffer = new char[classReader.getMaxStringLength()]; + // According to the Java spec, the constant pool is indexed from 1 to constant_pool_count - + // 1. See https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 + for (int i = 1; i < classReader.getItemCount(); i++) { + int offset = classReader.getItem(i); + // Class entries correspond to 7 in the constant pool + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 + if (offset > 0 && classReader.readByte(offset - 1) == 7) { + try { + validateClass(classLoader, classReader.readUTF8(offset, charBuffer)); + } catch (ClassNotLoadedException e) { + errorConsumer.accept(e); + } + } + } + } + + public void validateFullClassPath(ClassReader classReader, ClassLoader fullClassLoader, + Set missingClassAllowlist) { + // Prebuilts only need transitive dependencies checked, not direct dependencies. + validateClassPath(classReader, fullClassLoader, (e) -> { + if (!missingClassAllowlist.contains(e.getClassName())) { + addMissingError(classReader.getClassName(), e.getClassName()); + } + }); + } + + public void validateDirectClassPath(ClassReader classReader, ClassLoader directClassLoader, + ClassLoader fullClassLoader, Collection jarsOnlyInFullClassPath, + Set missingClassAllowlist, boolean verbose) { + validateClassPath(classReader, directClassLoader, (e) -> { + try { + validateClass(fullClassLoader, e.getClassName()); + } catch (ClassNotLoadedException d) { + if (!missingClassAllowlist.contains(e.getClassName())) { + addMissingError(classReader.getClassName(), e.getClassName()); + } + return; + } + if (verbose) { + System.err.println("Class \"" + e.getClassName() + + "\" not found in direct dependencies," + + " but found in indirect dependiences."); + } + // Iterating through all jars that are in the full classpath but not the direct + // classpath to find which one provides the class we are looking for. + for (String jarPath : jarsOnlyInFullClassPath) { + try { + ClassLoader smallLoader = + ByteCodeProcessor.loadJars(Collections.singletonList(jarPath)); + validateClass(smallLoader, e.getClassName()); + addDirectError(jarPath, classReader.getClassName(), e.getClassName()); + break; + } catch (ClassNotLoadedException f) { + } + } + }); + } + + private void addMissingError(String srcClass, String missingClass) { + mMissingClasses.put(missingClass, srcClass); + } + + private void addDirectError(String jarPath, String srcClass, String missingClass) { + synchronized (mDirectErrors) { + Map> failedClassesByMissingClass = mDirectErrors.get(jarPath); + if (failedClassesByMissingClass == null) { + // TreeMap so that error messages have sorted list of classes. + failedClassesByMissingClass = new TreeMap<>(); + mDirectErrors.put(jarPath, failedClassesByMissingClass); + } + Set failedClasses = failedClassesByMissingClass.get(missingClass); + if (failedClasses == null) { + failedClasses = new TreeSet<>(); + failedClassesByMissingClass.put(missingClass, failedClasses); + } + failedClasses.add(srcClass); + } + } + + public boolean hasErrors() { + return !mDirectErrors.isEmpty() || !mMissingClasses.isEmpty(); + } + + private static void printValidationError( + PrintStream out, String gnTarget, Map> missingClasses) { + out.print(" * "); + out.println(gnTarget); + int i = 0; + // The list of missing classes is non-exhaustive because each class that fails to validate + // reports only the first missing class. + for (Map.Entry> entry : missingClasses.entrySet()) { + String missingClass = entry.getKey(); + Set filesThatNeededIt = entry.getValue(); + out.print(" * "); + if (i == MAX_ERRORS_PER_JAR) { + out.print(String.format( + "And %d more...", missingClasses.size() - MAX_ERRORS_PER_JAR)); + break; + } + out.print(missingClass.replace('/', '.')); + out.print(" (needed by "); + out.print(filesThatNeededIt.iterator().next().replace('/', '.')); + if (filesThatNeededIt.size() > 1) { + out.print(String.format(" and %d more", filesThatNeededIt.size() - 1)); + } + out.println(")"); + i++; + } + } + + public void printAll(String gnTarget, Map jarToGnTarget) { + String streamer = "============================="; + System.err.println(); + System.err.println(streamer + " Dependency Checks Failed " + streamer); + System.err.println("Target: " + gnTarget); + if (!mMissingClasses.isEmpty()) { + int i = 0; + for (Map.Entry entry : mMissingClasses.entrySet()) { + if (++i > MAX_MISSING_CLASS_WARNINGS) { + System.err.println(String.format("... and %d more.", + mMissingClasses.size() - MAX_MISSING_CLASS_WARNINGS)); + break; + } + System.err.println(String.format( + "Class \"%s\" not found on any classpath. Used by class \"%s\"", + entry.getKey(), entry.getValue())); + } + System.err.println(); + } + if (!mDirectErrors.isEmpty()) { + System.err.println("Direct classpath is incomplete. To fix, add deps on:"); + for (Map.Entry>> entry : mDirectErrors.entrySet()) { + printValidationError( + System.err, jarToGnTarget.get(entry.getKey()), entry.getValue()); + } + System.err.println(); + } + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java new file mode 100644 index 0000000000..d0957625d7 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java @@ -0,0 +1,103 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ASM7; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.IRETURN; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import java.util.ArrayList; + +class EmptyOverrideGeneratorClassAdapter extends ClassVisitor { + private final ArrayList mMethodsToGenerate; + private String mSuperClassName; + private boolean mIsAbstract; + private boolean mIsInterface; + + public EmptyOverrideGeneratorClassAdapter( + ClassVisitor cv, ArrayList methodsToGenerate) { + super(ASM7, cv); + mMethodsToGenerate = methodsToGenerate; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + mSuperClassName = superName; + mIsAbstract = (access & ACC_ABSTRACT) == ACC_ABSTRACT; + mIsInterface = (access & ACC_INTERFACE) == ACC_INTERFACE; + } + + @Override + public void visitEnd() { + if (mIsAbstract || mIsInterface || mMethodsToGenerate.isEmpty()) { + super.visitEnd(); + return; + } + + for (MethodDescription method : mMethodsToGenerate) { + if (!method.shouldCreateOverride) { + continue; + } + + MethodVisitor mv = super.visitMethod( + method.access, method.methodName, method.description, null, null); + writeOverrideCode(mv, method.access, method.methodName, method.description); + } + + super.visitEnd(); + } + + /** + * Writes code to a method to call that method's parent implementation. + *
+     * {@code
+     * // Calling writeOverrideCode(mv, ACC_PUBLIC, "doFoo", "(Ljava/lang/String;)I") writes the
+     * following method body: public int doFoo(String arg){ return super.doFoo(arg);
+     * }
+     * }
+     * 
+ * + * This will be rewritten later by TraceEventAdderClassAdapter to wrap the body in a trace + * event. + */ + private void writeOverrideCode( + MethodVisitor mv, final int access, final String name, final String descriptor) { + Type[] argTypes = Type.getArgumentTypes(descriptor); + Type returnType = Type.getReturnType(descriptor); + + mv.visitCode(); + + // Variable 0 contains `this`, load it into the operand stack. + mv.visitVarInsn(ALOAD, 0); + + // Variables 1..n contain all arguments, load them all into the operand stack. + int i = 1; + for (Type arg : argTypes) { + // getOpcode(ILOAD) returns the ILOAD equivalent to the current argument's type. + mv.visitVarInsn(arg.getOpcode(ILOAD), i); + i += arg.getSize(); + } + + // Call the parent class method with the same arguments. + mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, name, descriptor, false); + + // Return the result. + mv.visitInsn(returnType.getOpcode(IRETURN)); + + mv.visitMaxs(0, 0); + mv.visitEnd(); + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java new file mode 100644 index 0000000000..a40f39c4ce --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java @@ -0,0 +1,238 @@ +// Copyright 2020 The Chromium 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.bytecode; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.MethodRemapper; +import org.objectweb.asm.commons.Remapper; + +import java.io.File; +import java.io.IOException; + +/** + * Java application that modifies Fragment.getActivity() to return an Activity instead of a + * FragmentActivity, and updates any existing getActivity() calls to reference the updated method. + * + * See crbug.com/1144345 for more context. + */ +public class FragmentActivityReplacer extends ByteCodeRewriter { + private static final String GET_ACTIVITY_METHOD_NAME = "getActivity"; + private static final String GET_LIFECYCLE_ACTIVITY_METHOD_NAME = "getLifecycleActivity"; + private static final String NEW_METHOD_DESCRIPTOR = "()Landroid/app/Activity;"; + private static final String OLD_METHOD_DESCRIPTOR = + "()Landroidx/fragment/app/FragmentActivity;"; + private static final String REQUIRE_ACTIVITY_METHOD_NAME = "requireActivity"; + private static final String SUPPORT_LIFECYCLE_FRAGMENT_IMPL_BINARY_NAME = + "com.google.android.gms.common.api.internal.SupportLifecycleFragmentImpl"; + + public static void main(String[] args) throws IOException { + // Invoke this script using //build/android/gyp/bytecode_rewriter.py + if (!(args.length == 2 || args.length == 3 && args[0].equals("--single-androidx"))) { + System.err.println("Expected arguments: [--single-androidx] "); + System.exit(1); + } + + if (args.length == 2) { + FragmentActivityReplacer rewriter = new FragmentActivityReplacer(false); + rewriter.rewrite(new File(args[0]), new File(args[1])); + } else { + FragmentActivityReplacer rewriter = new FragmentActivityReplacer(true); + rewriter.rewrite(new File(args[1]), new File(args[2])); + } + } + + private final boolean mSingleAndroidX; + + public FragmentActivityReplacer(boolean singleAndroidX) { + mSingleAndroidX = singleAndroidX; + } + + @Override + protected boolean shouldRewriteClass(String classPath) { + return true; + } + + @Override + protected ClassVisitor getClassVisitorForClass(String classPath, ClassVisitor delegate) { + ClassVisitor invocationVisitor = new InvocationReplacer(delegate, mSingleAndroidX); + switch (classPath) { + case "androidx/fragment/app/Fragment.class": + return new FragmentClassVisitor(invocationVisitor); + case "com/google/android/gms/common/api/internal/SupportLifecycleFragmentImpl.class": + return new SupportLifecycleFragmentImplClassVisitor(invocationVisitor); + default: + return invocationVisitor; + } + } + + /** + * Updates any Fragment.getActivity/requireActivity() or getLifecycleActivity() calls to call + * the replaced method. + */ + private static class InvocationReplacer extends ClassVisitor { + private final boolean mSingleAndroidX; + + private InvocationReplacer(ClassVisitor baseVisitor, boolean singleAndroidX) { + super(Opcodes.ASM7, baseVisitor); + mSingleAndroidX = singleAndroidX; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor base = super.visitMethod(access, name, descriptor, signature, exceptions); + return new MethodVisitor(Opcodes.ASM7, base) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String descriptor, boolean isInterface) { + boolean isFragmentGetActivity = name.equals(GET_ACTIVITY_METHOD_NAME) + && descriptor.equals(OLD_METHOD_DESCRIPTOR) + && isFragmentSubclass(owner); + boolean isFragmentRequireActivity = name.equals(REQUIRE_ACTIVITY_METHOD_NAME) + && descriptor.equals(OLD_METHOD_DESCRIPTOR) + && isFragmentSubclass(owner); + boolean isSupportLifecycleFragmentImplGetLifecycleActivity = + name.equals(GET_LIFECYCLE_ACTIVITY_METHOD_NAME) + && descriptor.equals(OLD_METHOD_DESCRIPTOR) + && owner.equals(SUPPORT_LIFECYCLE_FRAGMENT_IMPL_BINARY_NAME); + if ((opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKESPECIAL) + && (isFragmentGetActivity || isFragmentRequireActivity + || isSupportLifecycleFragmentImplGetLifecycleActivity)) { + super.visitMethodInsn( + opcode, owner, name, NEW_METHOD_DESCRIPTOR, isInterface); + if (mSingleAndroidX) { + super.visitTypeInsn( + Opcodes.CHECKCAST, "androidx/fragment/app/FragmentActivity"); + } + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + + private boolean isFragmentSubclass(String internalType) { + // Look up classes with a ClassLoader that will resolve any R classes to Object. + // This is fine in this case as resource classes shouldn't be in the class + // hierarchy of any Fragments. + ClassLoader resourceStubbingClassLoader = new ClassLoader() { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.matches(".*\\.R(\\$.+)?")) { + return Object.class; + } + return super.findClass(name); + } + }; + + // This doesn't use Class#isAssignableFrom to avoid us needing to load + // AndroidX's Fragment class, which may not be on the classpath. + try { + String binaryName = Type.getObjectType(internalType).getClassName(); + Class clazz = resourceStubbingClassLoader.loadClass(binaryName); + while (clazz != null) { + if (clazz.getName().equals("androidx.fragment.app.Fragment")) { + return true; + } + clazz = clazz.getSuperclass(); + } + return false; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }; + } + } + + /** + * Updates the implementation of Fragment.getActivity() and Fragment.requireActivity(). + */ + private static class FragmentClassVisitor extends ClassVisitor { + private FragmentClassVisitor(ClassVisitor baseVisitor) { + super(Opcodes.ASM7, baseVisitor); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + // Update the descriptor of getActivity() and requireActivity(). + MethodVisitor baseVisitor; + if (descriptor.equals(OLD_METHOD_DESCRIPTOR) + && (name.equals(GET_ACTIVITY_METHOD_NAME) + || name.equals(REQUIRE_ACTIVITY_METHOD_NAME))) { + // Some Fragments in a Clank library implement an interface that defines an + // `Activity getActivity()` method. Fragment.getActivity() is considered its + // implementation from a typechecking perspective, but javac still generates a + // getActivity() method in these Fragments that call Fragment.getActivity(). This + // isn't an issue when the methods return different types, but after changing + // Fragment.getActivity() to return an Activity, this generated implementation is + // now overriding Fragment's, which it can't do because Fragment.getActivity() is + // final. We make it non-final here to avoid this issue. + baseVisitor = super.visitMethod( + access & ~Opcodes.ACC_FINAL, name, NEW_METHOD_DESCRIPTOR, null, exceptions); + } else { + baseVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); + } + + // Replace getActivity() with `return ContextUtils.activityFromContext(getContext());` + if (name.equals(GET_ACTIVITY_METHOD_NAME) && descriptor.equals(OLD_METHOD_DESCRIPTOR)) { + baseVisitor.visitVarInsn(Opcodes.ALOAD, 0); + baseVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "androidx/fragment/app/Fragment", + "getContext", "()Landroid/content/Context;", false); + baseVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "org/chromium/utils/ContextUtils", + "activityFromContext", "(Landroid/content/Context;)Landroid/app/Activity;", + false); + baseVisitor.visitInsn(Opcodes.ARETURN); + // Since we set COMPUTE_FRAMES, the arguments of visitMaxs are ignored, but calling + // it forces ClassWriter to actually recompute the correct stack/local values. + // Without this call ClassWriter keeps the original stack=0,locals=1 which is wrong. + baseVisitor.visitMaxs(0, 0); + return null; + } + + return new MethodRemapper(baseVisitor, new Remapper() { + @Override + public String mapType(String internalName) { + if (internalName.equals("androidx/fragment/app/FragmentActivity")) { + return "android/app/Activity"; + } + return internalName; + } + }); + } + } + + /** + * Update SupportLifecycleFragmentImpl.getLifecycleActivity(). + */ + private static class SupportLifecycleFragmentImplClassVisitor extends ClassVisitor { + private SupportLifecycleFragmentImplClassVisitor(ClassVisitor baseVisitor) { + super(Opcodes.ASM7, baseVisitor); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + // SupportLifecycleFragmentImpl has two getActivity methods: + // 1. public FragmentActivity getLifecycleActivity(): + // This is what you'll see in the source. This delegates to Fragment.getActivity(). + // 2. public Activity getLifecycleActivity(): + // This is generated because the class implements LifecycleFragment, which + // declares this method, and delegates to #1. + // + // Here we change the return type of #1 and delete #2. + if (name.equals(GET_LIFECYCLE_ACTIVITY_METHOD_NAME)) { + if (descriptor.equals(OLD_METHOD_DESCRIPTOR)) { + return super.visitMethod( + access, name, NEW_METHOD_DESCRIPTOR, signature, exceptions); + } + return null; + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java new file mode 100644 index 0000000000..5aef275319 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java @@ -0,0 +1,136 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import static org.objectweb.asm.ClassReader.EXPAND_FRAMES; +import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ASM7; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * This ClassVisitor verifies that a class and its methods are suitable for rewriting. + * Given a class and a list of methods it performs the following checks: + * 1. Class is subclass of {@link android.view.View}. + * 2. Class is not abstract or an interface. + * + * For each method provided in {@code methodsToCheck}: + * If the class overrides the method then we can rewrite it directly. + * If the class doesn't override the method then we can generate an override with {@link + * EmptyOverrideGeneratorClassAdapter}, but first we must check if the parent method is private or + * final using {@link ParentMethodCheckerClassAdapter}. + * + * This adapter modifies the provided method list to indicate which methods should be overridden or + * skipped. + */ +class MethodCheckerClassAdapter extends ClassVisitor { + private static final String VIEW_CLASS_DESCRIPTOR = "android/view/View"; + + private final ArrayList mMethodsToCheck; + private final ClassLoader mJarClassLoader; + private String mSuperName; + + public MethodCheckerClassAdapter( + ArrayList methodsToCheck, ClassLoader jarClassLoader) { + super(ASM7); + mMethodsToCheck = methodsToCheck; + mJarClassLoader = jarClassLoader; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + mSuperName = superName; + + boolean isAbstract = (access & ACC_ABSTRACT) == ACC_ABSTRACT; + boolean isInterface = (access & ACC_INTERFACE) == ACC_INTERFACE; + + if (isAbstract || isInterface || !isClassView(name)) { + mMethodsToCheck.clear(); + return; + } + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + if (mMethodsToCheck.isEmpty()) { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + for (MethodDescription method : mMethodsToCheck) { + if (method.methodName.equals(name) && method.description.equals(descriptor)) { + method.shouldCreateOverride = false; + } + } + + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + if (mMethodsToCheck.isEmpty()) { + super.visitEnd(); + return; + } + + boolean areAnyUncheckedMethods = false; + + for (MethodDescription method : mMethodsToCheck) { + if (method.shouldCreateOverride == null) { + areAnyUncheckedMethods = true; + break; + } + } + + if (areAnyUncheckedMethods) { + checkParentClass(mSuperName, mMethodsToCheck, mJarClassLoader); + } + + super.visitEnd(); + } + + private boolean isClassView(String desc) { + Class currentClass = getClass(desc); + Class viewClass = getClass(VIEW_CLASS_DESCRIPTOR); + if (currentClass != null && viewClass != null) { + return viewClass.isAssignableFrom(currentClass); + } + return false; + } + + private Class getClass(String desc) { + try { + return mJarClassLoader.loadClass(desc.replace('/', '.')); + } catch (ClassNotFoundException | NoClassDefFoundError | IllegalAccessError e) { + return null; + } + } + + static void checkParentClass(String superClassName, ArrayList methodsToCheck, + ClassLoader jarClassLoader) { + try { + ClassReader cr = new ClassReader(getClassAsStream(jarClassLoader, superClassName)); + ParentMethodCheckerClassAdapter parentChecker = + new ParentMethodCheckerClassAdapter(methodsToCheck, jarClassLoader); + cr.accept(parentChecker, EXPAND_FRAMES); + } catch (IOException ex) { + // Ignore errors in case class can't be loaded. + } + } + + private static InputStream getClassAsStream(ClassLoader jarClassLoader, String desc) { + return jarClassLoader.getResourceAsStream(desc.replace('.', '/') + ".class"); + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java new file mode 100644 index 0000000000..23b14536e1 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java @@ -0,0 +1,20 @@ +// Copyright 2021 The Chromium 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.bytecode; + +class MethodDescription { + public final String methodName; + public final String description; + public final int access; + public Boolean shouldCreateOverride; + + public MethodDescription(String methodName, String description, int access) { + this.methodName = methodName; + this.description = description; + this.access = access; + // A null value means we haven't checked the method. + this.shouldCreateOverride = null; + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java new file mode 100644 index 0000000000..d913f1a73e --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java @@ -0,0 +1,94 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ASM7; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.util.ArrayList; + +/** + * This ClassVisitor checks if the given class overrides methods on {@code methodsToCheck}, and if + * so it determines whether they can be overridden by a child class. If at the end any unchecked + * methods remain then we recurse on the class's superclass. + */ +class ParentMethodCheckerClassAdapter extends ClassVisitor { + private static final String OBJECT_CLASS_DESCRIPTOR = "java.lang.Object"; + + private final ArrayList mMethodsToCheck; + private final ClassLoader mJarClassLoader; + private String mSuperName; + private boolean mIsCheckingObjectClass; + + public ParentMethodCheckerClassAdapter( + ArrayList methodsToCheck, ClassLoader jarClassLoader) { + super(ASM7); + mMethodsToCheck = methodsToCheck; + mJarClassLoader = jarClassLoader; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + if (name.equals(OBJECT_CLASS_DESCRIPTOR)) { + mIsCheckingObjectClass = true; + return; + } + + mSuperName = superName; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + if (mIsCheckingObjectClass) { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + for (MethodDescription methodToCheck : mMethodsToCheck) { + if (methodToCheck.shouldCreateOverride != null || !methodToCheck.methodName.equals(name) + || !methodToCheck.description.equals(descriptor)) { + continue; + } + + // This class contains methodToCheck. + boolean isMethodPrivate = (access & ACC_PRIVATE) == ACC_PRIVATE; + boolean isMethodFinal = (access & ACC_FINAL) == ACC_FINAL; + // If the method is private or final then don't create an override. + methodToCheck.shouldCreateOverride = !isMethodPrivate && !isMethodFinal; + } + + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + if (mIsCheckingObjectClass) { + return; + } + + boolean areAnyUncheckedMethods = false; + + for (MethodDescription method : mMethodsToCheck) { + if (method.shouldCreateOverride == null) { + areAnyUncheckedMethods = true; + break; + } + } + + if (areAnyUncheckedMethods) { + MethodCheckerClassAdapter.checkParentClass( + mSuperName, mMethodsToCheck, mJarClassLoader); + } + + super.visitEnd(); + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java new file mode 100644 index 0000000000..51f323f00a --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java @@ -0,0 +1,87 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Java application that modifies all implementations of "draw", "onMeasure" and "onLayout" on all + * {@link android.view.View} subclasses to wrap them in trace events. + */ +public class TraceEventAdder extends ByteCodeRewriter { + private final ClassLoader mClassPathJarsClassLoader; + private ArrayList mMethodsToTrace; + + public static void main(String[] args) throws IOException { + // Invoke this script using //build/android/gyp/bytecode_rewriter.py + if (args.length < 2) { + System.err.println( + "Expected arguments: "); + System.exit(1); + } + + String input = args[0]; + String output = args[1]; + + ArrayList classPathJarsPaths = new ArrayList<>(); + classPathJarsPaths.add(input); + classPathJarsPaths.addAll(Arrays.asList(Arrays.copyOfRange(args, 2, args.length))); + ClassLoader classPathJarsClassLoader = ByteCodeProcessor.loadJars(classPathJarsPaths); + + TraceEventAdder adder = new TraceEventAdder(classPathJarsClassLoader); + adder.rewrite(new File(input), new File(output)); + } + + public TraceEventAdder(ClassLoader classPathJarsClassLoader) { + mClassPathJarsClassLoader = classPathJarsClassLoader; + } + + @Override + protected boolean shouldRewriteClass(String classPath) { + try { + // If this jar's dependencies can't find Chromium's TraceEvent class then skip this + // class. Conceptually this could be fixed by adding a dependency on //base:base_java + // but that would cause circular dependencies and any changes to base_java would cause + // all android_library targets to require rebuilding. + mClassPathJarsClassLoader.loadClass("org.chromium.base.TraceEvent"); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } + + @Override + protected boolean shouldRewriteClass(ClassReader classReader) { + mMethodsToTrace = new ArrayList<>(Arrays.asList( + new MethodDescription("draw", "(Landroid/graphics/Canvas;)V", Opcodes.ACC_PUBLIC), + new MethodDescription("onMeasure", "(II)V", Opcodes.ACC_PROTECTED), + new MethodDescription("onLayout", "(ZIIII)V", Opcodes.ACC_PROTECTED))); + + // This adapter will modify mMethodsToTrace to indicate which methods already exist in the + // class and which ones need to be overridden. In case the class is not an Android view + // we'll clear the list and skip rewriting. + MethodCheckerClassAdapter methodChecker = + new MethodCheckerClassAdapter(mMethodsToTrace, mClassPathJarsClassLoader); + + classReader.accept(methodChecker, ClassReader.EXPAND_FRAMES); + + return !mMethodsToTrace.isEmpty(); + } + + @Override + protected ClassVisitor getClassVisitorForClass(String classPath, ClassVisitor delegate) { + ClassVisitor chain = new TraceEventAdderClassAdapter(delegate, mMethodsToTrace); + chain = new EmptyOverrideGeneratorClassAdapter(chain, mMethodsToTrace); + + return chain; + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java new file mode 100644 index 0000000000..c4a152d995 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java @@ -0,0 +1,47 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import static org.objectweb.asm.Opcodes.ASM7; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.util.ArrayList; + +/** + * A ClassVisitor for adding TraceEvent.begin and TraceEvent.end methods to any methods specified in + * a list. + */ +class TraceEventAdderClassAdapter extends ClassVisitor { + private final ArrayList mMethodsToTrace; + private String mShortClassName; + + TraceEventAdderClassAdapter(ClassVisitor visitor, ArrayList methodsToTrace) { + super(ASM7, visitor); + mMethodsToTrace = methodsToTrace; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + mShortClassName = name.substring(name.lastIndexOf('/') + 1); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, String desc, + String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + + for (MethodDescription method : mMethodsToTrace) { + if (method.methodName.equals(name) && method.description.equals(desc)) { + return new TraceEventAdderMethodAdapter(mv, mShortClassName, name); + } + } + + return mv; + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java new file mode 100644 index 0000000000..042b3d3c5f --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java @@ -0,0 +1,83 @@ +// Copyright 2021 The Chromium 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.bytecode; + +import static org.objectweb.asm.Opcodes.ASM7; +import static org.objectweb.asm.Opcodes.ATHROW; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.IRETURN; +import static org.objectweb.asm.Opcodes.RETURN; + +import static org.chromium.bytecode.TypeUtils.STRING; +import static org.chromium.bytecode.TypeUtils.VOID; + +import org.objectweb.asm.MethodVisitor; + +/** + * MethodVisitor that wraps all code in TraceEvent.begin and TraceEvent.end calls. TraceEvent.end + * calls are added on all returns and thrown exceptions. + * + * Example: + *
+ *   {@code
+ *      int methodToTrace(String foo){
+ *
+ *        //Line added by rewriter:
+ *        TraceEvent.begin("ClassName.methodToTrace");
+ *
+ *        if(foo == null){
+ *          //Line added by rewriter:
+ *          TraceEvent.end("ClassName.methodToTrace");
+ *
+ *          throw new Exception();
+ *        }
+ *        else if(foo.equals("Two")){
+ *          //Line added by rewriter:
+ *          TraceEvent.end("ClassName.methodToTrace");
+ *
+ *          return 2;
+ *        }
+ *
+ *        //Line added by rewriter:
+ *        TraceEvent.end("ClassName.methodToTrace");
+ *
+ *        return 0;
+ *      }
+ *   }
+ * 
+ * + */ +class TraceEventAdderMethodAdapter extends MethodVisitor { + private static final String TRACE_EVENT_DESCRIPTOR = "org/chromium/base/TraceEvent"; + private static final String TRACE_EVENT_SIGNATURE = TypeUtils.getMethodDescriptor(VOID, STRING); + private final String mEventName; + + public TraceEventAdderMethodAdapter( + MethodVisitor methodVisitor, String shortClassName, String methodName) { + super(ASM7, methodVisitor); + + mEventName = shortClassName + "." + methodName; + } + + @Override + public void visitCode() { + super.visitCode(); + + mv.visitLdcInsn(mEventName); + mv.visitMethodInsn( + INVOKESTATIC, TRACE_EVENT_DESCRIPTOR, "begin", TRACE_EVENT_SIGNATURE, false); + } + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { + mv.visitLdcInsn(mEventName); + mv.visitMethodInsn( + INVOKESTATIC, TRACE_EVENT_DESCRIPTOR, "end", TRACE_EVENT_SIGNATURE, false); + } + + mv.visitInsn(opcode); + } +} diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java new file mode 100644 index 0000000000..ed2dc2dc24 --- /dev/null +++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java @@ -0,0 +1,87 @@ +// Copyright 2017 The Chromium 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.bytecode; + +import org.objectweb.asm.Type; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility methods for accessing {@link Type}s Strings. + * + * Useful definitions to keep in mind when using this class: + * Internal name - The fully qualified name for a type with dots replaced by slashes. Not really + * relevant for primitive types. + * Type descriptor - Single letters for primitive types, "L" + internal name + ";" for class types. + * + * The methods in this class accept internal names or primitive type descriptors. + */ +class TypeUtils { + static final String ASSERTION_ERROR = "java/lang/AssertionError"; + static final String ASSET_MANAGER = "android/content/res/AssetManager"; + static final String BUILD_HOOKS = "org/chromium/build/BuildHooks"; + static final String BUILD_HOOKS_ANDROID = "org/chromium/build/BuildHooksAndroid"; + static final String CONFIGURATION = "android/content/res/Configuration"; + static final String CONTEXT = "android/content/Context"; + static final String CONTEXT_WRAPPER = "android/content/ContextWrapper"; + static final String RESOURCES = "android/content/res/Resources"; + static final String STRING = "java/lang/String"; + static final String THEME = "android/content/res/Resources$Theme"; + + static final String BOOLEAN = "Z"; + static final String INT = "I"; + static final String VOID = "V"; + private static final Map PRIMITIVE_DESCRIPTORS; + static { + PRIMITIVE_DESCRIPTORS = new HashMap<>(); + PRIMITIVE_DESCRIPTORS.put(Type.BOOLEAN_TYPE.toString(), Type.BOOLEAN_TYPE); + PRIMITIVE_DESCRIPTORS.put(Type.INT_TYPE.toString(), Type.INT_TYPE); + PRIMITIVE_DESCRIPTORS.put(Type.VOID_TYPE.toString(), Type.VOID_TYPE); + } + + /** + * Returns the full method signature with internal names. + * + * @param methodName Name of the method (ex. "getResources"). + * @param returnType Internal name for the return type. + * @param argumentTypes List of internal names for argument types. + * @return String representation of the method signature. + */ + static String getMethodSignature( + String methodName, String returnType, String... argumentTypes) { + return methodName + getMethodDescriptor(returnType, argumentTypes); + } + + /** + * Builds a method descriptor suitable for use with {@link org.objectweb.asm.MethodVisitor}. + * + * @param returnType Internal name for the return type of the method (primitive or class). + * @param argumentTypes Internal names for the argument types (primitive or class). + * @return The generated method descriptor. + */ + static String getMethodDescriptor(String returnType, String... argumentTypes) { + Type[] typedArguments = new Type[argumentTypes.length]; + for (int i = 0; i < argumentTypes.length; ++i) { + // Argument list should be empty in this case, not V (void). + assert !Type.VOID_TYPE.toString().equals(argumentTypes[i]); + typedArguments[i] = convert(argumentTypes[i]); + } + return Type.getMethodDescriptor(convert(returnType), typedArguments); + } + + /** + * Converts an internal name for a type to a {@link Type}. + * + * @param type Internal name for a type (primitive or class). + * @return The resulting Type. + */ + private static Type convert(String type) { + if (PRIMITIVE_DESCRIPTORS.containsKey(type)) { + return PRIMITIVE_DESCRIPTORS.get(type); + } + return Type.getObjectType(type); + } +} diff --git a/third_party/libwebrtc/build/android/chromium-debug.keystore b/third_party/libwebrtc/build/android/chromium-debug.keystore new file mode 100644 index 0000000000..67eb0aa34c Binary files /dev/null and b/third_party/libwebrtc/build/android/chromium-debug.keystore differ diff --git a/third_party/libwebrtc/build/android/convert_dex_profile.py b/third_party/libwebrtc/build/android/convert_dex_profile.py new file mode 100755 index 0000000000..1b11094672 --- /dev/null +++ b/third_party/libwebrtc/build/android/convert_dex_profile.py @@ -0,0 +1,568 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2018 The Chromium 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 collections +import functools +import logging +import re +import subprocess +import sys + +DEX_CLASS_NAME_RE = re.compile(r'\'L(?P[^;]+);\'') +DEX_METHOD_NAME_RE = re.compile(r'\'(?P[^\']+)\'') +DEX_METHOD_TYPE_RE = re.compile( # type descriptor method signature re + r'\'' + r'\(' + r'(?P[^)]*)' + r'\)' + r'(?P[^\']+)' + r'\'') +DEX_METHOD_LINE_NR_RE = re.compile(r'line=(?P\d+)') + +PROFILE_METHOD_RE = re.compile( + r'(?P[HSP]+)' # tags such as H/S/P + r'(?PL[^;]+;)' # class name in type descriptor format + r'->(?P[^(]+)' + r'\((?P[^)]*)\)' + r'(?P.+)') + +PROGUARD_CLASS_MAPPING_RE = re.compile( + r'(?P[^ ]+)' + r' -> ' + r'(?P[^:]+):') +PROGUARD_METHOD_MAPPING_RE = re.compile( + # line_start:line_end: (optional) + r'((?P\d+):(?P\d+):)?' + r'(?P[^ ]+)' # original method return type + # original method class name (if exists) + r' (?:(?P[a-zA-Z_\d.$]+)\.)?' + r'(?P[^.\(]+)' + r'\((?P[^\)]*)\)' # original method params + r'(?:[^ ]*)' # original method line numbers (ignored) + r' -> ' + r'(?P.+)') # obfuscated method name + +TYPE_DESCRIPTOR_RE = re.compile( + r'(?P\[*)' + r'(?:' + r'(?PL[^;]+;)' + r'|' + r'[VZBSCIJFD]' + r')') + +DOT_NOTATION_MAP = { + '': '', + 'boolean': 'Z', + 'byte': 'B', + 'void': 'V', + 'short': 'S', + 'char': 'C', + 'int': 'I', + 'long': 'J', + 'float': 'F', + 'double': 'D' +} + + +@functools.total_ordering +class Method(object): + def __init__(self, name, class_name, param_types=None, return_type=None): + self.name = name + self.class_name = class_name + self.param_types = param_types + self.return_type = return_type + + def __str__(self): + return '{}->{}({}){}'.format(self.class_name, self.name, + self.param_types or '', self.return_type or '') + + def __repr__(self): + return 'Method<{}->{}({}){}>'.format(self.class_name, self.name, + self.param_types or '', self.return_type or '') + + @staticmethod + def serialize(method): + return (method.class_name, method.name, method.param_types, + method.return_type) + + def __eq__(self, other): + return self.serialize(self) == self.serialize(other) + + def __lt__(self, other): + return self.serialize(self) < self.serialize(other) + + def __hash__(self): + # only hash name and class_name since other fields may not be set yet. + return hash((self.name, self.class_name)) + + +class Class(object): + def __init__(self, name): + self.name = name + self._methods = [] + + def AddMethod(self, method, line_numbers): + self._methods.append((method, set(line_numbers))) + + def FindMethodsAtLine(self, method_name, line_start, line_end=None): + """Searches through dex class for a method given a name and line numbers + + The dex maps methods to line numbers, this method, given the a method name + in this class as well as a start line and an optional end line (which act as + hints as to which function in the class is being looked for), returns a list + of possible matches (or none if none are found). + + Args: + method_name: name of method being searched for + line_start: start of hint range for lines in this method + line_end: end of hint range for lines in this method (optional) + + Returns: + A list of Method objects that could match the hints given, or None if no + method is found. + """ + found_methods = [] + if line_end is None: + hint_lines = set([line_start]) + else: + hint_lines = set(range(line_start, line_end+1)) + + named_methods = [(method, l) for method, l in self._methods + if method.name == method_name] + + if len(named_methods) == 1: + return [method for method, l in named_methods] + if len(named_methods) == 0: + return None + + for method, line_numbers in named_methods: + if not hint_lines.isdisjoint(line_numbers): + found_methods.append(method) + + if len(found_methods) > 0: + if len(found_methods) > 1: + logging.warning('ambigous methods in dex %s at lines %s in class "%s"', + found_methods, hint_lines, self.name) + return found_methods + + for method, line_numbers in named_methods: + if (max(hint_lines) >= min(line_numbers) + and min(hint_lines) <= max(line_numbers)): + found_methods.append(method) + + if len(found_methods) > 0: + if len(found_methods) > 1: + logging.warning('ambigous methods in dex %s at lines %s in class "%s"', + found_methods, hint_lines, self.name) + return found_methods + else: + logging.warning('No method named "%s" in class "%s" is ' + 'mapped to lines %s', method_name, self.name, hint_lines) + return None + + +class Profile(object): + def __init__(self): + # {Method: set(char)} + self._methods = collections.defaultdict(set) + self._classes = [] + + def AddMethod(self, method, tags): + for tag in tags: + self._methods[method].add(tag) + + def AddClass(self, cls): + self._classes.append(cls) + + def WriteToFile(self, path): + with open(path, 'w') as output_profile: + for cls in sorted(self._classes): + output_profile.write(cls + '\n') + for method in sorted(self._methods): + tags = sorted(self._methods[method]) + line = '{}{}\n'.format(''.join(tags), str(method)) + output_profile.write(line) + + +class ProguardMapping(object): + def __init__(self): + # {Method: set(Method)} + self._method_mapping = collections.defaultdict(set) + # {String: String} String is class name in type descriptor format + self._class_mapping = dict() + + def AddMethodMapping(self, from_method, to_method): + self._method_mapping[from_method].add(to_method) + + def AddClassMapping(self, from_class, to_class): + self._class_mapping[from_class] = to_class + + def GetMethodMapping(self, from_method): + return self._method_mapping.get(from_method) + + def GetClassMapping(self, from_class): + return self._class_mapping.get(from_class, from_class) + + def MapTypeDescriptor(self, type_descriptor): + match = TYPE_DESCRIPTOR_RE.search(type_descriptor) + assert match is not None + class_name = match.group('class_name') + if class_name is not None: + return match.group('brackets') + self.GetClassMapping(class_name) + # just a native type, return as is + return match.group() + + def MapTypeDescriptorList(self, type_descriptor_list): + return TYPE_DESCRIPTOR_RE.sub( + lambda match: self.MapTypeDescriptor(match.group()), + type_descriptor_list) + + +class MalformedLineException(Exception): + def __init__(self, message, line_number): + super(MalformedLineException, self).__init__(message) + self.line_number = line_number + + def __str__(self): + return self.message + ' at line {}'.format(self.line_number) + + +class MalformedProguardMappingException(MalformedLineException): + pass + + +class MalformedProfileException(MalformedLineException): + pass + + +def _RunDexDump(dexdump_path, dex_file_path): + return subprocess.check_output([dexdump_path, + dex_file_path]).decode('utf-8').splitlines() + + +def _ReadFile(file_path): + with open(file_path, 'r') as f: + return f.readlines() + + +def _ToTypeDescriptor(dot_notation): + """Parses a dot notation type and returns it in type descriptor format + + eg: + org.chromium.browser.ChromeActivity -> Lorg/chromium/browser/ChromeActivity; + boolean -> Z + int[] -> [I + + Args: + dot_notation: trimmed string with a single type in dot notation format + + Returns: + A string with the type in type descriptor format + """ + dot_notation = dot_notation.strip() + prefix = '' + while dot_notation.endswith('[]'): + prefix += '[' + dot_notation = dot_notation[:-2] + if dot_notation in DOT_NOTATION_MAP: + return prefix + DOT_NOTATION_MAP[dot_notation] + return prefix + 'L' + dot_notation.replace('.', '/') + ';' + + +def _DotNotationListToTypeDescriptorList(dot_notation_list_string): + """Parses a param list of dot notation format and returns it in type + descriptor format + + eg: + org.chromium.browser.ChromeActivity,boolean,int[] -> + Lorg/chromium/browser/ChromeActivity;Z[I + + Args: + dot_notation_list_string: single string with multiple comma separated types + in dot notation format + + Returns: + A string with the param list in type descriptor format + """ + return ''.join(_ToTypeDescriptor(param) for param in + dot_notation_list_string.split(',')) + + +def ProcessDex(dex_dump): + """Parses dexdump output returning a dict of class names to Class objects + + Parses output of the dexdump command on a dex file and extracts information + about classes and their respective methods and which line numbers a method is + mapped to. + + Methods that are not mapped to any line number are ignored and not listed + inside their respective Class objects. + + Args: + dex_dump: An array of lines of dexdump output + + Returns: + A dict that maps from class names in type descriptor format (but without the + surrounding 'L' and ';') to Class objects. + """ + # class_name: Class + classes_by_name = {} + current_class = None + current_method = None + reading_positions = False + reading_methods = False + method_line_numbers = [] + for line in dex_dump: + line = line.strip() + if line.startswith('Class descriptor'): + # New class started, no longer reading methods. + reading_methods = False + current_class = Class(DEX_CLASS_NAME_RE.search(line).group('class_name')) + classes_by_name[current_class.name] = current_class + elif (line.startswith('Direct methods') + or line.startswith('Virtual methods')): + reading_methods = True + elif reading_methods and line.startswith('name'): + assert current_class is not None + current_method = Method( + DEX_METHOD_NAME_RE.search(line).group('method_name'), + "L" + current_class.name + ";") + elif reading_methods and line.startswith('type'): + assert current_method is not None + match = DEX_METHOD_TYPE_RE.search(line) + current_method.param_types = match.group('method_params') + current_method.return_type = match.group('method_return_type') + elif line.startswith('positions'): + assert reading_methods + reading_positions = True + method_line_numbers = [] + elif reading_positions and line.startswith('0x'): + line_number = DEX_METHOD_LINE_NR_RE.search(line).group('line_number') + method_line_numbers.append(int(line_number)) + elif reading_positions and line.startswith('locals'): + if len(method_line_numbers) > 0: + current_class.AddMethod(current_method, method_line_numbers) + # finished reading method line numbers + reading_positions = False + return classes_by_name + + +def ProcessProguardMapping(proguard_mapping_lines, dex): + """Parses a proguard mapping file + + This takes proguard mapping file lines and then uses the obfuscated dex to + create a mapping of unobfuscated methods to obfuscated ones and vice versa. + + The dex is used because the proguard mapping file only has the name of the + obfuscated methods but not their signature, thus the dex is read to look up + which method with a specific name was mapped to the lines mentioned in the + proguard mapping file. + + Args: + proguard_mapping_lines: Array of strings, each is a line from the proguard + mapping file (in order). + dex: a dict of class name (in type descriptor format but without the + enclosing 'L' and ';') to a Class object. + Returns: + Two dicts the first maps from obfuscated methods to a set of non-obfuscated + ones. It also maps the obfuscated class names to original class names, both + in type descriptor format (with the enclosing 'L' and ';') + """ + mapping = ProguardMapping() + reverse_mapping = ProguardMapping() + to_be_obfuscated = [] + current_class_orig = None + current_class_obfs = None + for index, line in enumerate(proguard_mapping_lines): + if line.strip() == '': + continue + if not line.startswith(' '): + match = PROGUARD_CLASS_MAPPING_RE.search(line) + if match is None: + raise MalformedProguardMappingException( + 'Malformed class mapping', index) + current_class_orig = match.group('original_name') + current_class_obfs = match.group('obfuscated_name') + mapping.AddClassMapping(_ToTypeDescriptor(current_class_obfs), + _ToTypeDescriptor(current_class_orig)) + reverse_mapping.AddClassMapping(_ToTypeDescriptor(current_class_orig), + _ToTypeDescriptor(current_class_obfs)) + continue + + assert current_class_orig is not None + assert current_class_obfs is not None + line = line.strip() + match = PROGUARD_METHOD_MAPPING_RE.search(line) + # check if is a method mapping (we ignore field mappings) + if match is not None: + # check if this line is an inlining by reading ahead 1 line. + if index + 1 < len(proguard_mapping_lines): + next_match = PROGUARD_METHOD_MAPPING_RE.search( + proguard_mapping_lines[index+1].strip()) + if (next_match and match.group('line_start') is not None + and next_match.group('line_start') == match.group('line_start') + and next_match.group('line_end') == match.group('line_end')): + continue # This is an inlining, skip + + original_method = Method( + match.group('original_method_name'), + _ToTypeDescriptor( + match.group('original_method_class') or current_class_orig), + _DotNotationListToTypeDescriptorList(match.group('params')), + _ToTypeDescriptor(match.group('return_type'))) + + if match.group('line_start') is not None: + obfs_methods = (dex[current_class_obfs.replace('.', '/')] + .FindMethodsAtLine( + match.group('obfuscated_name'), + int(match.group('line_start')), + int(match.group('line_end')))) + + if obfs_methods is None: + continue + + for obfs_method in obfs_methods: + mapping.AddMethodMapping(obfs_method, original_method) + reverse_mapping.AddMethodMapping(original_method, obfs_method) + else: + to_be_obfuscated.append( + (original_method, match.group('obfuscated_name'))) + + for original_method, obfuscated_name in to_be_obfuscated: + obfuscated_method = Method( + obfuscated_name, + reverse_mapping.GetClassMapping(original_method.class_name), + reverse_mapping.MapTypeDescriptorList(original_method.param_types), + reverse_mapping.MapTypeDescriptor(original_method.return_type)) + mapping.AddMethodMapping(obfuscated_method, original_method) + reverse_mapping.AddMethodMapping(original_method, obfuscated_method) + return mapping, reverse_mapping + + +def ProcessProfile(input_profile, proguard_mapping): + """Parses an android profile and uses the proguard mapping to (de)obfuscate it + + This takes the android profile lines and for each method or class for the + profile, it uses the mapping to either obfuscate or deobfuscate (based on the + provided mapping) and returns a Profile object that stores this information. + + Args: + input_profile: array of lines of the input profile + proguard_mapping: a proguard mapping that would map from the classes and + methods in the input profile to the classes and methods + that should be in the output profile. + + Returns: + A Profile object that stores the information (ie list of mapped classes and + methods + tags) + """ + profile = Profile() + for index, line in enumerate(input_profile): + line = line.strip() + if line.startswith('L'): + profile.AddClass(proguard_mapping.GetClassMapping(line)) + continue + match = PROFILE_METHOD_RE.search(line) + if not match: + raise MalformedProfileException("Malformed line", index) + + method = Method( + match.group('method_name'), + match.group('class_name'), + match.group('method_params'), + match.group('method_return_type')) + + mapped_methods = proguard_mapping.GetMethodMapping(method) + if mapped_methods is None: + logging.warning('No method matching "%s" has been found in the proguard ' + 'mapping file', method) + continue + + for original_method in mapped_methods: + profile.AddMethod(original_method, match.group('tags')) + + return profile + + +def ObfuscateProfile(nonobfuscated_profile, dex_file, proguard_mapping, + dexdump_path, output_filename): + """Helper method for obfuscating a profile. + + Args: + nonobfuscated_profile: a profile with nonobfuscated symbols. + dex_file: path to the dex file matching the mapping. + proguard_mapping: a mapping from nonobfuscated to obfuscated symbols used + in the dex file. + dexdump_path: path to the dexdump utility. + output_filename: output filename in which to write the obfuscated profile. + """ + dexinfo = ProcessDex(_RunDexDump(dexdump_path, dex_file)) + _, reverse_mapping = ProcessProguardMapping( + _ReadFile(proguard_mapping), dexinfo) + obfuscated_profile = ProcessProfile( + _ReadFile(nonobfuscated_profile), reverse_mapping) + obfuscated_profile.WriteToFile(output_filename) + + +def main(args): + parser = argparse.ArgumentParser() + parser.add_argument( + '--dexdump-path', + required=True, + help='Path to dexdump binary.') + parser.add_argument( + '--dex-path', + required=True, + help='Path to dex file corresponding to the proguard mapping file.') + parser.add_argument( + '--proguard-mapping-path', + required=True, + help='Path to input proguard mapping file corresponding to the dex file.') + parser.add_argument( + '--output-profile-path', + required=True, + help='Path to output profile.') + parser.add_argument( + '--input-profile-path', + required=True, + help='Path to output profile.') + parser.add_argument( + '--verbose', + action='store_true', + default=False, + help='Print verbose output.') + obfuscation = parser.add_mutually_exclusive_group(required=True) + obfuscation.add_argument('--obfuscate', action='store_true', + help='Indicates to output an obfuscated profile given a deobfuscated ' + 'one.') + obfuscation.add_argument('--deobfuscate', dest='obfuscate', + action='store_false', help='Indicates to output a deobfuscated profile ' + 'given an obfuscated one.') + options = parser.parse_args(args) + + if options.verbose: + log_level = logging.WARNING + else: + log_level = logging.ERROR + logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) + + dex = ProcessDex(_RunDexDump(options.dexdump_path, options.dex_path)) + proguard_mapping, reverse_proguard_mapping = ProcessProguardMapping( + _ReadFile(options.proguard_mapping_path), dex) + if options.obfuscate: + profile = ProcessProfile( + _ReadFile(options.input_profile_path), + reverse_proguard_mapping) + else: + profile = ProcessProfile( + _ReadFile(options.input_profile_path), + proguard_mapping) + profile.WriteToFile(options.output_profile_path) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/convert_dex_profile_tests.py b/third_party/libwebrtc/build/android/convert_dex_profile_tests.py new file mode 100755 index 0000000000..c92e0855ec --- /dev/null +++ b/third_party/libwebrtc/build/android/convert_dex_profile_tests.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 convert_dex_profile. + +Can be run from build/android/: + $ cd build/android + $ python convert_dex_profile_tests.py +""" + +import os +import sys +import tempfile +import unittest + +import convert_dex_profile as cp + +sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'gyp')) +from util import build_utils + +cp.logging.disable(cp.logging.CRITICAL) + +# There are two obfuscations used in the tests below, each with the same +# unobfuscated profile. The first, corresponding to DEX_DUMP, PROGUARD_MAPPING, +# and OBFUSCATED_PROFILE, has an ambiguous method a() which is mapped to both +# getInstance and initialize. The second, corresponding to DEX_DUMP_2, +# PROGUARD_MAPPING_2 and OBFUSCATED_PROFILE_2, removes the ambiguity. + +DEX_DUMP = """ + +Class descriptor : 'La;' + Direct methods - + #0 : (in La;) + name : '' + type : '(Ljava/lang/String;)V' + code - + catches : 1 + 0x000f - 0x001e + -> 0x0093 + positions : + 0x0001 line=310 + 0x0057 line=313 + locals : + #1 : (in La;) + name : '' + type : '()V' + positions : + locals : + Virtual methods - + #0 : (in La;) + name : 'a' + type : '(Ljava/lang/String;)I' + positions : + 0x0000 line=2 + 0x0003 line=3 + 0x001b line=8 + locals : + 0x0000 - 0x0021 reg=3 this La; + #1 : (in La;) + name : 'a' + type : '(Ljava/lang/Object;)I' + positions : + 0x0000 line=8 + 0x0003 line=9 + locals : + 0x0000 - 0x0021 reg=3 this La; + #2 : (in La;) + name : 'b' + type : '()La;' + positions : + 0x0000 line=1 + locals : +""" + +# pylint: disable=line-too-long +PROGUARD_MAPPING = \ +"""org.chromium.Original -> a: + org.chromium.Original sDisplayAndroidManager -> e + org.chromium.Original another() -> b + 4:4:void inlined():237:237 -> a + 4:4:org.chromium.Original getInstance():203 -> a + 5:5:void org.chromium.Original$Subclass.(org.chromium.Original,byte):130:130 -> a + 5:5:void initialize():237 -> a + 5:5:org.chromium.Original getInstance():203 -> a + 6:6:void initialize():237:237 -> a + 9:9:android.content.Context org.chromium.base.ContextUtils.getApplicationContext():49:49 -> a + 9:9:android.content.Context getContext():219 -> a + 9:9:void initialize():245 -> a + 9:9:org.chromium.Original getInstance():203 -> a""" + +OBFUSCATED_PROFILE = \ +"""La; +PLa;->b()La; +SLa;->a(Ljava/lang/Object;)I +HPLa;->a(Ljava/lang/String;)I""" + +DEX_DUMP_2 = """ + +Class descriptor : 'La;' + Direct methods - + #0 : (in La;) + name : '' + type : '(Ljava/lang/String;)V' + code - + catches : 1 + 0x000f - 0x001e + -> 0x0093 + positions : + 0x0001 line=310 + 0x0057 line=313 + locals : + #1 : (in La;) + name : '' + type : '()V' + positions : + locals : + Virtual methods - + #0 : (in La;) + name : 'a' + type : '(Ljava/lang/String;)I' + positions : + 0x0000 line=2 + 0x0003 line=3 + 0x001b line=8 + locals : + 0x0000 - 0x0021 reg=3 this La; + #1 : (in La;) + name : 'c' + type : '(Ljava/lang/Object;)I' + positions : + 0x0000 line=8 + 0x0003 line=9 + locals : + 0x0000 - 0x0021 reg=3 this La; + #2 : (in La;) + name : 'b' + type : '()La;' + positions : + 0x0000 line=1 + locals : +""" + +# pylint: disable=line-too-long +PROGUARD_MAPPING_2 = \ +"""org.chromium.Original -> a: + org.chromium.Original sDisplayAndroidManager -> e + org.chromium.Original another() -> b + void initialize() -> c + org.chromium.Original getInstance():203 -> a + 4:4:void inlined():237:237 -> a""" + +OBFUSCATED_PROFILE_2 = \ +"""La; +PLa;->b()La; +HPSLa;->a()La; +HPLa;->c()V""" + +UNOBFUSCATED_PROFILE = \ +"""Lorg/chromium/Original; +PLorg/chromium/Original;->another()Lorg/chromium/Original; +HPSLorg/chromium/Original;->getInstance()Lorg/chromium/Original; +HPLorg/chromium/Original;->initialize()V""" + +class GenerateProfileTests(unittest.TestCase): + def testProcessDex(self): + dex = cp.ProcessDex(DEX_DUMP.splitlines()) + self.assertIsNotNone(dex['a']) + + self.assertEqual(len(dex['a'].FindMethodsAtLine('', 311, 313)), 1) + self.assertEqual(len(dex['a'].FindMethodsAtLine('', 309, 315)), 1) + clinit = dex['a'].FindMethodsAtLine('', 311, 313)[0] + self.assertEqual(clinit.name, '') + self.assertEqual(clinit.return_type, 'V') + self.assertEqual(clinit.param_types, 'Ljava/lang/String;') + + self.assertEqual(len(dex['a'].FindMethodsAtLine('a', 8, None)), 2) + self.assertIsNone(dex['a'].FindMethodsAtLine('a', 100, None)) + +# pylint: disable=protected-access + def testProcessProguardMapping(self): + dex = cp.ProcessDex(DEX_DUMP.splitlines()) + mapping, reverse = cp.ProcessProguardMapping( + PROGUARD_MAPPING.splitlines(), dex) + + self.assertEqual('La;', reverse.GetClassMapping('Lorg/chromium/Original;')) + + getInstance = cp.Method( + 'getInstance', 'Lorg/chromium/Original;', '', 'Lorg/chromium/Original;') + initialize = cp.Method('initialize', 'Lorg/chromium/Original;', '', 'V') + another = cp.Method( + 'another', 'Lorg/chromium/Original;', '', 'Lorg/chromium/Original;') + subclassInit = cp.Method( + '', 'Lorg/chromium/Original$Subclass;', + 'Lorg/chromium/Original;B', 'V') + + mapped = mapping.GetMethodMapping( + cp.Method('a', 'La;', 'Ljava/lang/String;', 'I')) + self.assertEqual(len(mapped), 2) + self.assertIn(getInstance, mapped) + self.assertNotIn(subclassInit, mapped) + self.assertNotIn( + cp.Method('inlined', 'Lorg/chromium/Original;', '', 'V'), mapped) + self.assertIn(initialize, mapped) + + mapped = mapping.GetMethodMapping( + cp.Method('a', 'La;', 'Ljava/lang/Object;', 'I')) + self.assertEqual(len(mapped), 1) + self.assertIn(getInstance, mapped) + + mapped = mapping.GetMethodMapping(cp.Method('b', 'La;', '', 'La;')) + self.assertEqual(len(mapped), 1) + self.assertIn(another, mapped) + + for from_method, to_methods in mapping._method_mapping.items(): + for to_method in to_methods: + self.assertIn(from_method, reverse.GetMethodMapping(to_method)) + for from_class, to_class in mapping._class_mapping.items(): + self.assertEqual(from_class, reverse.GetClassMapping(to_class)) + + def testProcessProfile(self): + dex = cp.ProcessDex(DEX_DUMP.splitlines()) + mapping, _ = cp.ProcessProguardMapping(PROGUARD_MAPPING.splitlines(), dex) + profile = cp.ProcessProfile(OBFUSCATED_PROFILE.splitlines(), mapping) + + getInstance = cp.Method( + 'getInstance', 'Lorg/chromium/Original;', '', 'Lorg/chromium/Original;') + initialize = cp.Method('initialize', 'Lorg/chromium/Original;', '', 'V') + another = cp.Method( + 'another', 'Lorg/chromium/Original;', '', 'Lorg/chromium/Original;') + + self.assertIn('Lorg/chromium/Original;', profile._classes) + self.assertIn(getInstance, profile._methods) + self.assertIn(initialize, profile._methods) + self.assertIn(another, profile._methods) + + self.assertEqual(profile._methods[getInstance], set(['H', 'S', 'P'])) + self.assertEqual(profile._methods[initialize], set(['H', 'P'])) + self.assertEqual(profile._methods[another], set(['P'])) + + def testEndToEnd(self): + dex = cp.ProcessDex(DEX_DUMP.splitlines()) + mapping, _ = cp.ProcessProguardMapping(PROGUARD_MAPPING.splitlines(), dex) + + profile = cp.ProcessProfile(OBFUSCATED_PROFILE.splitlines(), mapping) + with tempfile.NamedTemporaryFile() as temp: + profile.WriteToFile(temp.name) + with open(temp.name, 'r') as f: + for a, b in zip(sorted(f), sorted(UNOBFUSCATED_PROFILE.splitlines())): + self.assertEqual(a.strip(), b.strip()) + + def testObfuscateProfile(self): + with build_utils.TempDir() as temp_dir: + # The dex dump is used as the dexfile, by passing /bin/cat as the dexdump + # program. + dex_path = os.path.join(temp_dir, 'dexdump') + with open(dex_path, 'w') as dex_file: + dex_file.write(DEX_DUMP_2) + mapping_path = os.path.join(temp_dir, 'mapping') + with open(mapping_path, 'w') as mapping_file: + mapping_file.write(PROGUARD_MAPPING_2) + unobfuscated_path = os.path.join(temp_dir, 'unobfuscated') + with open(unobfuscated_path, 'w') as unobfuscated_file: + unobfuscated_file.write(UNOBFUSCATED_PROFILE) + obfuscated_path = os.path.join(temp_dir, 'obfuscated') + cp.ObfuscateProfile(unobfuscated_path, dex_path, mapping_path, '/bin/cat', + obfuscated_path) + with open(obfuscated_path) as obfuscated_file: + obfuscated_profile = sorted(obfuscated_file.readlines()) + for a, b in zip( + sorted(OBFUSCATED_PROFILE_2.splitlines()), obfuscated_profile): + self.assertEqual(a.strip(), b.strip()) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/dcheck_is_off.flags b/third_party/libwebrtc/build/android/dcheck_is_off.flags new file mode 100644 index 0000000000..f9059c39d7 --- /dev/null +++ b/third_party/libwebrtc/build/android/dcheck_is_off.flags @@ -0,0 +1,12 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Contains flags that are applied only when ENABLE_DCHECK=false. + +-checkdiscard @org.chromium.base.annotations.CheckDiscard class ** { + *; +} +-checkdiscard class ** { + @org.chromium.base.annotations.CheckDiscard *; +} diff --git a/third_party/libwebrtc/build/android/devil_chromium.json b/third_party/libwebrtc/build/android/devil_chromium.json new file mode 100644 index 0000000000..d19e6b6589 --- /dev/null +++ b/third_party/libwebrtc/build/android/devil_chromium.json @@ -0,0 +1,120 @@ +{ + "config_type": "BaseConfig", + "dependencies": { + "aapt": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public/build-tools/27.0.3/aapt" + ] + } + } + }, + "adb": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public/platform-tools/adb" + ] + } + } + }, + "android_build_tools_libc++": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public/build-tools/27.0.3/lib64/libc++.so" + ] + } + } + }, + "android_sdk": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public" + ] + } + } + }, + "dexdump": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public/build-tools/27.0.3/dexdump" + ] + } + } + }, + "split-select": { + "file_info": { + "linux2_x86_64": { + "local_paths": [ + "../../third_party/android_sdk/public/build-tools/27.0.3/split-select" + ] + } + } + }, + "simpleperf": { + "file_info": { + "android_armeabi-v7a": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/android/arm/simpleperf" + ] + }, + "android_arm64-v8a": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/android/arm64/simpleperf" + ] + }, + "android_x86": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/android/x86/simpleperf" + ] + }, + "android_x86_64": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/android/x86_64/simpleperf" + ] + }, + "linux_x86": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/linux/x86/simpleperf" + ] + }, + "linux_x86_64": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf/bin/linux/x86_64/simpleperf" + ] + } + } + }, + "simpleperf_scripts": { + "file_info": { + "default": { + "local_paths": [ + "../../third_party/android_ndk/simpleperf" + ] + } + } + }, + "llvm-symbolizer": { + "file_info": { + "default": { + "local_paths": [ + "../../third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer" + ] + } + } + }, + "bundletool": { + "file_info": { + "default": { + "local_paths": [ + "../../third_party/android_build_tools/bundletool/bundletool-all-1.8.0.jar" + ] + } + } + } + } +} diff --git a/third_party/libwebrtc/build/android/devil_chromium.py b/third_party/libwebrtc/build/android/devil_chromium.py new file mode 100644 index 0000000000..d3bb56fd98 --- /dev/null +++ b/third_party/libwebrtc/build/android/devil_chromium.py @@ -0,0 +1,200 @@ +# 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. + +"""Configures devil for use in chromium.""" + +import os +import sys + +from pylib import constants +from pylib.constants import host_paths + +if host_paths.DEVIL_PATH not in sys.path: + sys.path.insert(1, host_paths.DEVIL_PATH) + +from devil import devil_env +from devil.android.ndk import abis + +_BUILD_DIR = os.path.join(constants.DIR_SOURCE_ROOT, 'build') +if _BUILD_DIR not in sys.path: + sys.path.insert(1, _BUILD_DIR) + +import gn_helpers + +_DEVIL_CONFIG = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'devil_chromium.json')) + +_DEVIL_BUILD_PRODUCT_DEPS = { + 'chromium_commands': [ + { + 'platform': 'linux2', + 'arch': 'x86_64', + 'path_components': ['lib.java', 'chromium_commands.dex.jar'], + } + ], + 'forwarder_device': [ + { + 'platform': 'android', + 'arch': abis.ARM, + 'path_components': ['forwarder_dist'], + }, + { + 'platform': 'android', + 'arch': abis.ARM_64, + 'path_components': ['forwarder_dist'], + }, + { + 'platform': 'android', + 'arch': 'mips', + 'path_components': ['forwarder_dist'], + }, + { + 'platform': 'android', + 'arch': 'mips64', + 'path_components': ['forwarder_dist'], + }, + { + 'platform': 'android', + 'arch': abis.X86, + 'path_components': ['forwarder_dist'], + }, + { + 'platform': 'android', + 'arch': abis.X86_64, + 'path_components': ['forwarder_dist'], + }, + ], + 'forwarder_host': [ + { + 'platform': 'linux2', + 'arch': 'x86_64', + 'path_components': ['host_forwarder'], + }, + ], + 'md5sum_device': [ + { + 'platform': 'android', + 'arch': abis.ARM, + 'path_components': ['md5sum_dist'], + }, + { + 'platform': 'android', + 'arch': abis.ARM_64, + 'path_components': ['md5sum_dist'], + }, + { + 'platform': 'android', + 'arch': 'mips', + 'path_components': ['md5sum_dist'], + }, + { + 'platform': 'android', + 'arch': 'mips64', + 'path_components': ['md5sum_dist'], + }, + { + 'platform': 'android', + 'arch': abis.X86, + 'path_components': ['md5sum_dist'], + }, + { + 'platform': 'android', + 'arch': abis.X86_64, + 'path_components': ['md5sum_dist'], + }, + ], + 'md5sum_host': [ + { + 'platform': 'linux2', + 'arch': 'x86_64', + 'path_components': ['md5sum_bin_host'], + }, + ], +} + + +def _UseLocalBuildProducts(output_directory, devil_dynamic_config): + output_directory = os.path.abspath(output_directory) + devil_dynamic_config['dependencies'] = { + dep_name: { + 'file_info': { + '%s_%s' % (dep_config['platform'], dep_config['arch']): { + 'local_paths': [ + os.path.join(output_directory, + *dep_config['path_components']), + ], + } + for dep_config in dep_configs + } + } + for dep_name, dep_configs in _DEVIL_BUILD_PRODUCT_DEPS.items() + } + + +def _BuildWithChromium(): + """Returns value of gclient's |build_with_chromium|.""" + gni_path = os.path.join(_BUILD_DIR, 'config', 'gclient_args.gni') + if not os.path.exists(gni_path): + return False + with open(gni_path) as f: + data = f.read() + args = gn_helpers.FromGNArgs(data) + return args.get('build_with_chromium', False) + + +def Initialize(output_directory=None, custom_deps=None, adb_path=None): + """Initializes devil with chromium's binaries and third-party libraries. + + This includes: + - Libraries: + - the android SDK ("android_sdk") + - Build products: + - host & device forwarder binaries + ("forwarder_device" and "forwarder_host") + - host & device md5sum binaries ("md5sum_device" and "md5sum_host") + + Args: + output_directory: An optional path to the output directory. If not set, + no built dependencies are configured. + custom_deps: An optional dictionary specifying custom dependencies. + This should be of the form: + + { + 'dependency_name': { + 'platform': 'path', + ... + }, + ... + } + adb_path: An optional path to use for the adb binary. If not set, this uses + the adb binary provided by the Android SDK. + """ + build_with_chromium = _BuildWithChromium() + + devil_dynamic_config = { + 'config_type': 'BaseConfig', + 'dependencies': {}, + } + if build_with_chromium and output_directory: + # Non-chromium users of chromium's //build directory fetch build products + # from google storage rather than use locally built copies. Chromium uses + # locally-built copies so that changes to the tools can be easily tested. + _UseLocalBuildProducts(output_directory, devil_dynamic_config) + + if custom_deps: + devil_dynamic_config['dependencies'].update(custom_deps) + if adb_path: + devil_dynamic_config['dependencies'].update({ + 'adb': { + 'file_info': { + devil_env.GetPlatform(): { + 'local_paths': [adb_path] + } + } + } + }) + + config_files = [_DEVIL_CONFIG] if build_with_chromium else None + devil_env.config.Initialize(configs=[devil_dynamic_config], + config_files=config_files) diff --git a/third_party/libwebrtc/build/android/devil_chromium.pydeps b/third_party/libwebrtc/build/android/devil_chromium.pydeps new file mode 100644 index 0000000000..4143805929 --- /dev/null +++ b/third_party/libwebrtc/build/android/devil_chromium.pydeps @@ -0,0 +1,39 @@ +# Generated by running: +# build/print_python_deps.py --root build/android --output build/android/devil_chromium.pydeps build/android/devil_chromium.py +../../third_party/catapult/common/py_utils/py_utils/__init__.py +../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py +../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py +../../third_party/catapult/common/py_utils/py_utils/lock.py +../../third_party/catapult/dependency_manager/dependency_manager/__init__.py +../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py +../../third_party/catapult/dependency_manager/dependency_manager/base_config.py +../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py +../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py +../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py +../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py +../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py +../../third_party/catapult/dependency_manager/dependency_manager/manager.py +../../third_party/catapult/dependency_manager/dependency_manager/uploader.py +../../third_party/catapult/devil/devil/__init__.py +../../third_party/catapult/devil/devil/android/__init__.py +../../third_party/catapult/devil/devil/android/constants/__init__.py +../../third_party/catapult/devil/devil/android/constants/chrome.py +../../third_party/catapult/devil/devil/android/ndk/__init__.py +../../third_party/catapult/devil/devil/android/ndk/abis.py +../../third_party/catapult/devil/devil/android/sdk/__init__.py +../../third_party/catapult/devil/devil/android/sdk/keyevent.py +../../third_party/catapult/devil/devil/android/sdk/version_codes.py +../../third_party/catapult/devil/devil/base_error.py +../../third_party/catapult/devil/devil/constants/__init__.py +../../third_party/catapult/devil/devil/constants/exit_codes.py +../../third_party/catapult/devil/devil/devil_env.py +../../third_party/catapult/devil/devil/utils/__init__.py +../../third_party/catapult/devil/devil/utils/reraiser_thread.py +../../third_party/catapult/devil/devil/utils/timeout_retry.py +../../third_party/catapult/devil/devil/utils/watchdog_timer.py +../../third_party/catapult/third_party/six/six.py +../gn_helpers.py +devil_chromium.py +pylib/__init__.py +pylib/constants/__init__.py +pylib/constants/host_paths.py diff --git a/third_party/libwebrtc/build/android/diff_resource_sizes.py b/third_party/libwebrtc/build/android/diff_resource_sizes.py new file mode 100755 index 0000000000..0bd2c47b40 --- /dev/null +++ b/third_party/libwebrtc/build/android/diff_resource_sizes.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Chromium 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 resource_sizes.py on two apks and outputs the diff.""" + +from __future__ import print_function + +import argparse +import json +import logging +import os +import subprocess +import sys + +from pylib.constants import host_paths +from pylib.utils import shared_preference_utils + +with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): + import perf_tests_results_helper # pylint: disable=import-error + +with host_paths.SysPath(host_paths.TRACING_PATH): + from tracing.value import convert_chart_json # pylint: disable=import-error + +_ANDROID_DIR = os.path.dirname(os.path.abspath(__file__)) +with host_paths.SysPath(os.path.join(_ANDROID_DIR, 'gyp')): + from util import build_utils # pylint: disable=import-error + + +_BASE_CHART = { + 'format_version': '0.1', + 'benchmark_name': 'resource_sizes_diff', + 'benchmark_description': 'APK resource size diff information', + 'trace_rerun_options': [], + 'charts': {}, +} + +_CHARTJSON_FILENAME = 'results-chart.json' +_HISTOGRAMS_FILENAME = 'perf_results.json' + + +def DiffResults(chartjson, base_results, diff_results): + """Reports the diff between the two given results. + + Args: + chartjson: A dictionary that chartjson results will be placed in, or None + to only print results. + base_results: The chartjson-formatted size results of the base APK. + diff_results: The chartjson-formatted size results of the diff APK. + """ + for graph_title, graph in base_results['charts'].items(): + for trace_title, trace in graph.items(): + perf_tests_results_helper.ReportPerfResult( + chartjson, graph_title, trace_title, + diff_results['charts'][graph_title][trace_title]['value'] + - trace['value'], + trace['units'], trace['improvement_direction'], + trace['important']) + + +def AddIntermediateResults(chartjson, base_results, diff_results): + """Copies the intermediate size results into the output chartjson. + + Args: + chartjson: A dictionary that chartjson results will be placed in. + base_results: The chartjson-formatted size results of the base APK. + diff_results: The chartjson-formatted size results of the diff APK. + """ + for graph_title, graph in base_results['charts'].items(): + for trace_title, trace in graph.items(): + perf_tests_results_helper.ReportPerfResult( + chartjson, graph_title + '_base_apk', trace_title, + trace['value'], trace['units'], trace['improvement_direction'], + trace['important']) + + # Both base_results and diff_results should have the same charts/traces, but + # loop over them separately in case they don't + for graph_title, graph in diff_results['charts'].items(): + for trace_title, trace in graph.items(): + perf_tests_results_helper.ReportPerfResult( + chartjson, graph_title + '_diff_apk', trace_title, + trace['value'], trace['units'], trace['improvement_direction'], + trace['important']) + + +def _CreateArgparser(): + def chromium_path(arg): + if arg.startswith('//'): + return os.path.join(host_paths.DIR_SOURCE_ROOT, arg[2:]) + return arg + + argparser = argparse.ArgumentParser( + description='Diff resource sizes of two APKs. Arguments not listed here ' + 'will be passed on to both invocations of resource_sizes.py.') + argparser.add_argument('--chromium-output-directory-base', + dest='out_dir_base', + type=chromium_path, + help='Location of the build artifacts for the base ' + 'APK, i.e. what the size increase/decrease will ' + 'be measured from.') + argparser.add_argument('--chromium-output-directory-diff', + dest='out_dir_diff', + type=chromium_path, + help='Location of the build artifacts for the diff ' + 'APK.') + argparser.add_argument('--chartjson', + action='store_true', + help='DEPRECATED. Use --output-format=chartjson ' + 'instead.') + argparser.add_argument('--output-format', + choices=['chartjson', 'histograms'], + help='Output the results to a file in the given ' + 'format instead of printing the results.') + argparser.add_argument('--include-intermediate-results', + action='store_true', + help='Include the results from the resource_sizes.py ' + 'runs in the chartjson output.') + argparser.add_argument('--output-dir', + default='.', + type=chromium_path, + help='Directory to save chartjson to.') + argparser.add_argument('--base-apk', + required=True, + type=chromium_path, + help='Path to the base APK, i.e. what the size ' + 'increase/decrease will be measured from.') + argparser.add_argument('--diff-apk', + required=True, + type=chromium_path, + help='Path to the diff APK, i.e. the APK whose size ' + 'increase/decrease will be measured against the ' + 'base APK.') + return argparser + + +def main(): + args, unknown_args = _CreateArgparser().parse_known_args() + # TODO(bsheedy): Remove this once all uses of --chartjson are removed. + if args.chartjson: + args.output_format = 'chartjson' + + chartjson = _BASE_CHART.copy() if args.output_format else None + + with build_utils.TempDir() as base_dir, build_utils.TempDir() as diff_dir: + # Run resource_sizes.py on the two APKs + resource_sizes_path = os.path.join(_ANDROID_DIR, 'resource_sizes.py') + shared_args = (['python', resource_sizes_path, '--output-format=chartjson'] + + unknown_args) + + base_args = shared_args + ['--output-dir', base_dir, args.base_apk] + if args.out_dir_base: + base_args += ['--chromium-output-directory', args.out_dir_base] + try: + subprocess.check_output(base_args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print(e.output) + raise + + diff_args = shared_args + ['--output-dir', diff_dir, args.diff_apk] + if args.out_dir_diff: + diff_args += ['--chromium-output-directory', args.out_dir_diff] + try: + subprocess.check_output(diff_args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print(e.output) + raise + + # Combine the separate results + base_file = os.path.join(base_dir, _CHARTJSON_FILENAME) + diff_file = os.path.join(diff_dir, _CHARTJSON_FILENAME) + base_results = shared_preference_utils.ExtractSettingsFromJson(base_file) + diff_results = shared_preference_utils.ExtractSettingsFromJson(diff_file) + DiffResults(chartjson, base_results, diff_results) + if args.include_intermediate_results: + AddIntermediateResults(chartjson, base_results, diff_results) + + if args.output_format: + chartjson_path = os.path.join(os.path.abspath(args.output_dir), + _CHARTJSON_FILENAME) + logging.critical('Dumping diff chartjson to %s', chartjson_path) + with open(chartjson_path, 'w') as outfile: + json.dump(chartjson, outfile) + + if args.output_format == 'histograms': + histogram_result = convert_chart_json.ConvertChartJson(chartjson_path) + if histogram_result.returncode != 0: + logging.error('chartjson conversion failed with error: %s', + histogram_result.stdout) + return 1 + + histogram_path = os.path.join(os.path.abspath(args.output_dir), + 'perf_results.json') + logging.critical('Dumping diff histograms to %s', histogram_path) + with open(histogram_path, 'w') as json_file: + json_file.write(histogram_result.stdout) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/docs/README.md b/third_party/libwebrtc/build/android/docs/README.md new file mode 100644 index 0000000000..5ee0ca638f --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/README.md @@ -0,0 +1,16 @@ +# Android Build Docs + +* [//docs/android_build_instructions.md](/docs/android_build_instructions.md) +* [//docs/android_dynamic_feature_modules.md](/docs/android_dynamic_feature_modules.md) +* [build_config.md](build_config.md) +* [coverage.md](coverage.md) +* [java_toolchain.md](java_toolchain.md) +* [java_optimization.md](java_optimization.md) +* [lint.md](lint.md) +* [life_of_a_resource.md](life_of_a_resource.md) +* [../incremental_install/README.md](../incremental_install/README.md) +* [//docs/ui/android/bytecode_rewriting.md](/docs/ui/android/bytecode_rewriting.md) +* [go/doubledown](https://goto.google.com/doubledown) (Googlers only) + +See also: +* [//build/README.md](../../README.md) diff --git a/third_party/libwebrtc/build/android/docs/build_config.md b/third_party/libwebrtc/build/android/docs/build_config.md new file mode 100644 index 0000000000..8f752a6691 --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/build_config.md @@ -0,0 +1,168 @@ +# Introduction + +This document describes the `.build_config.json` files that are used by the +Chromium build system for Android-specific targets like APK, resources, +and more. + +[TOC] + +# I. Overview of .build_config.json files: + +The Android build requires performing computations about dependencies in +various targets, which are not possible with the GN build language. To address +this, `.build_config.json` files are written during the build to store the needed +per-target information as JSON files. + +They are always written to `$target_gen_dir/${target_name}.build_config.json`. + +Many scripts under [`build/android/gyp/`](build/android_gyp/), which are used +during the build, can also accept parameter arguments using +`@FileArg references`, which look like: + + --some-param=@FileArg(:::..) + +This placeholder will ensure that `` is read as a JSON file, then +return the value at `[key1][key2]...[keyN]` for the `--some-param` option. + +Apart from that, the scripts do not need to know anything about the structure +of `.build_config.json` files (but the GN rules that invoke them do and select +which `@FileArg()` references to use). + +For a concrete example, consider the following GN fragment: + +```gn +# From //ui/android/BUILD.gn: +android_resources("ui_java_resources") { + custom_package = "org.chromium.ui" + resource_dirs = [ "java/res" ] + deps = [ + ":ui_strings_grd", + ] +} +``` + +This will end up generating the following JSON file under +`$CHROMIUM_OUTPUT_DIR/gen/ui/android/ui_java_resources.build_config.json`: + +```json +{ + "deps_info": { + "deps_configs": [ + "gen/ui/android/ui_strings_grd.build_config.json" + ], + "name": "ui_java_resources.build_config.json", + "package_name": "org.chromium.ui", + "path": "gen/ui/android/ui_java_resources.build_config.json", + "r_text": "gen/ui/android/ui_java_resources_R.txt", + "resources_dirs": [ + "../../ui/android/java/res" + ], + "resources_zip": "resource_zips/ui/android/ui_java_resources.resources.zip", + "srcjar": "gen/ui/android/ui_java_resources.srcjar", + "type": "android_resources" + }, + "gradle": {}, + "resources": { + "dependency_zips": [ + "resource_zips/ui/android/ui_strings_grd.resources.zip" + ], + "extra_package_names": [], + } +} +``` + +NOTE: All path values in `.build_config.json` files are relative to your +`$CHROMIUM_OUTPUT_DIR`. + +# II. Generation of .build_config.json files: + +They are generated by the GN [`write_build_config()`](gn_write_build_config) +internal template, which ends up invoking +[`write_build_config.py`](write_build_config_py). For our example above, this +is with the following parameters: + +``` +python ../../build/android/gyp/write_build_config.py \ + --type=android_resources \ + --depfile gen/ui/android/ui_java_resources__build_config_crbug_908819.d \ + --deps-configs=\[\"gen/ui/android/ui_strings_grd.build_config.json\"\] \ + --build-config gen/ui/android/ui_java_resources.build_config.json \ + --resources-zip resource_zips/ui/android/ui_java_resources.resources.zip \ + --package-name org.chromium.ui \ + --r-text gen/ui/android/ui_java_resources_R.txt \ + --resource-dirs=\[\"../../ui/android/java/res\"\] \ + --srcjar gen/ui/android/ui_java_resources.srcjar +``` + +Note that *most* of the content of the JSON file comes from command-line +parameters, but not all of it. + +In particular, the `resources['dependency_zips']` entry was computed by +inspecting the content of all dependencies (here, only +`ui_string_grd.build_config.json`), and collecting their +`deps_configs['resources_zip']` values. + +Because a target's `.build_config.json` file will always be generated after +that of all of its dependencies, +[`write_build_config.py`](write_build_config_py) can traverse the +whole (transitive) set of direct *and* indirect dependencies for a given target +and extract useful information out of it. + +This is the kind of processing that cannot be done at the GN language level, +and is very powerful for Android builds. + + +# III. Usage of .build_config.json files: + +In addition to being parsed by `write_build_config.py`, when they are listed +in the `--deps-configs` of a given target, the `.build_config.json` files are used +by other scripts under [build/android/gyp/] to build stuff. + +For example, the GN `android_resources` template uses it to invoke the +[`process_resources.py`] script with the following command, in order to +generate various related files (e.g. `ui_java_resources_R.txt`): + +```sh +python ../../build/android/gyp/process_resources.py \ + --depfile gen/ui/android/ui_java_resources_1.d \ + --android-sdk-jar ../../third_party/android_sdk/public/platforms/android-29/android.jar \ + --aapt-path ../../third_party/android_sdk/public/build-tools/29.0.2/aapt \ + --dependencies-res-zips=@FileArg\(gen/ui/android/ui_java_resources.build_config.json:resources:dependency_zips\) \ + --extra-res-packages=@FileArg\(gen/ui/android/ui_java_resources.build_config.json:resources:extra_package_names\) \ + --resource-dirs=\[\"../../ui/android/java/res\"\] \ + --debuggable \ + --resource-zip-out resource_zips/ui/android/ui_java_resources.resources.zip \ + --r-text-out gen/ui/android/ui_java_resources_R.txt \ + --srcjar-out gen/ui/android/ui_java_resources.srcjar \ + --non-constant-id \ + --custom-package org.chromium.ui \ + --shared-resources +``` + +Note the use of `@FileArg()` references here, to tell the script where to find +the information it needs. + + +# IV. Format of .build_config.json files: + +Thanks to `@FileArg()` references, Python build scripts under +[`build/android/gyp/`](build/android/gyp/) do not need to know anything +about the internal format of `.build_config.json` files. + +This format is decided between internal GN build rules and +[`write_build_config.py`][write_build_config_py]. Since these changes rather +often, the format documentation is kept inside the Python script itself, but +can be extracted as a Markdown file and visualized with the following commands: + +```sh +# Extract .build_config.json format documentation +build/android/gyp/write_build_config.py \ + --generate-markdown-format-doc > /tmp/format.md + +# Launch a browser to visualize the format documentation. +python tools/md_browser/md_browser.py -d /tmp /tmp/format.md +``` + +[build/android/gyp/]: https://chromium.googlesource.com/chromium/src/build/+/main/android/gyp/ +[gn_write_build_config]: https://cs.chromium.org/chromium/src/build/config/android/internal_rules.gni?q=write_build_config&sq=package:chromium +[write_build_config_py]: https://chromium.googlesource.com/chromium/src/build/+/main/android/gyp/write_build_config.py diff --git a/third_party/libwebrtc/build/android/docs/class_verification_failures.md b/third_party/libwebrtc/build/android/docs/class_verification_failures.md new file mode 100644 index 0000000000..e3e474539e --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/class_verification_failures.md @@ -0,0 +1,286 @@ +# Class Verification Failures + +[TOC] + +## What's this all about? + +This document aims to explain class verification on Android, how this can affect +app performance, how to identify problems, and chromium-specific solutions. For +simplicity, this document focuses on how class verification is implemented by +ART, the virtual machine which replaced Dalvik starting in Android Lollipop. + +## What is class verification? + +The Java language requires any virtual machine to _verify_ the class files it +loads and executes. Generally, verification is extra work the virtual machine is +responsible for doing, on top of the work of loading the class and performing +[class initialization][1]. + +A class may fail verification for a wide variety of reasons, but in practice +it's usually because the class's code refers to unknown classes or methods. An +example case might look like: + +```java +public class WindowHelper { + // ... + public boolean isWideColorGamut() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + return mWindow.isWideColorGamut(); + } + return false; + } +} +``` + +### Why does that fail? + +In this example, `WindowHelper` is a helper class intended to help callers +figure out wide color gamut support, even on pre-OMR1 devices. However, this +class will fail class verification on pre-OMR1 devices, because it refers to +[`Window#isWideColorGamut()`][2] (new-in-OMR1), which appears to be an undefined +method. + +### Huh? But we have an SDK check! + +SDK checks are completely irrelevant for class verification. Although readers +can see we'll never call the new-in-OMR1 API unless we're on >= OMR1 devices, +the Oreo version of ART doesn't know `isWideColorGamut()` was added in next +year's release. From ART's perspective, we may as well be calling +`methodWhichDoesNotExist()`, which would clearly be unsafe. + +All the SDK check does is protect us from crashing at runtime if we call this +method on Oreo or below. + +### Class verification on ART + +While the above is a mostly general description of class verification, it's +important to understand how the Android runtime handles this. + +Since class verification is extra work, ART has an optimization called **AOT +("ahead-of-time") verification**¹. Immediately after installing an app, ART will +scan the dex files and verify as many classes as it can. If a class fails +verification, this is usually a "soft failure" (hard failures are uncommon), and +ART marks the class with the status `RetryVerificationAtRuntime`. + +`RetryVerificationAtRuntime`, as the name suggests, means ART must try again to +verify the class at runtime. ART does so the first time you access the class +(right before class initialization/`()` method). However, depending on +the class, this verification step can be very expensive (we've observed cases +which take [several milliseconds][3]). Since apps tend to initialize most of +their classes during startup, verification significantly increases startup time. + +Another minor cost to failing class verification is that ART cannot optimize +classes which fail verification, so **all** methods in the class will perform +slower at runtime, even after the verification step. + +*** aside +¹ AOT _verification_ should not be confused with AOT _compilation_ (another ART +feature). Unlike compilation, AOT verification happens during install time for +every application, whereas recent versions of ART aim to apply AOT compilation +selectively to optimize space. +*** + +## Chromium's solution + +In Chromium, we try to avoid doing class verification at runtime by +manually out-of-lining all Android API usage like so: + +```java +public class ApiHelperForOMR1 { + public static boolean isWideColorGamut(Window window) { + return window.isWideColorGamut(); + } +} + +public class WindowHelper { + // ... + public boolean isWideColorGamut() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + return ApiHelperForOMR1.isWideColorGamut(mWindow); + } + return false; + } +} +``` + +This pushes the class verification failure out of `WindowHelper` and into the +new `ApiHelperForOMR1` class. There's no magic here: `ApiHelperForOMR1` will +fail class verification on Oreo and below, for the same reason `WindowHelper` +did previously. + +The key is that, while `WindowHelper` is used on all API levels, it only calls +into `ApiHelperForOMR1` on OMR1 and above. Because we never use +`ApiHelperForOMR1` on Oreo and below, we never load and initialize the class, +and thanks to ART's lazy runtime class verification, we never actually retry +verification. **Note:** `list_class_verification_failures.py` will still list +`ApiHelperFor*` classes in its output, although these don't cause performance +issues. + +### Creating ApiHelperFor\* classes + +There are several examples throughout the code base, but such classes should +look as follows: + +```java +/** + * Utility class to use new APIs that were added in O_MR1 (API level 27). + * These need to exist in a separate class so that Android framework can successfully verify + * classes without encountering the new APIs. + */ +@VerifiesOnOMR1 +@TargetApi(Build.VERSION_CODES.O_MR1) +public class ApiHelperForOMR1 { + private ApiHelperForOMR1() {} + + // ... +} +``` + +* `@VerifiesOnO_MR1`: this is a chromium-defined annotation to tell proguard + (and similar tools) not to inline this class or its methods (since that would + defeat the point of out-of-lining!) +* `@TargetApi(Build.VERSION_CODES.O_MR1)`: this tells Android Lint it's OK to + use OMR1 APIs since this class is only used on OMR1 and above. Substitute + `O_MR1` for the [appropriate constant][4], depending when the APIs were + introduced. +* Don't put any `SDK_INT` checks inside this class, because it must only be + called on >= OMR1. + +### Out-of-lining if your method has a new type in its signature + +Sometimes you'll run into a situation where a class **needs** to have a method +which either accepts a parameter which is a new type or returns a new type +(e.g., externally-facing code, such as WebView's glue layer). Even though it's +impossible to write such a class without referring to the new type, it's still +possible to avoid failing class verification. ART has a useful optimization: if +your class only moves a value between registers (i.e., it doesn't call any +methods or fields on the value), then ART will not check for the existence of +that value's type. This means you can write your class like so: + +```java +public class FooBar { + // FooBar needs to have the getNewTypeInAndroidP method, but it would be + // expensive to fail verification. This method will only be called on >= P + // but other methods on the class will be used on lower OS versions (and + // also can't be factored into another class). + public NewTypeInAndroidP getNewTypeInAndroidP() { + assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; + // Stores a NewTypeInAndroidP in the return register, but doesn't do + // anything else with it + return ApiHelperForP.getNewTypeInAndroidP(); + } + + // ... +} + +@VerifiesOnP +@TargetApi(Build.VERSION_CODES.P) +public class ApiHelperForP { + public static NewTypeInAndroidP getNewTypeInAndroidP() { + return new NewTypeInAndroidP(); + } + + // ... +} +``` + +**Note:** this only works in ART (L+), not Dalvik (KitKat and earlier). + +## Investigating class verification failures + +Class verification is generally surprising and nonintuitive. Fortunately, the +ART team have provided tools to investigate errors (and the chromium team has +built helpful wrappers). + +### Listing failing classes + +The main starting point is to figure out which classes fail verification (those +which ART marks as `RetryVerificationAtRuntime`). This can be done for **any +Android app** (it doesn't have to be from the chromium project) like so: + +```shell +# Install the app first. Using Chrome as an example. +autoninja -C out/Default chrome_public_apk +out/Default/bin/chrome_public_apk install + +# List all classes marked as 'RetryVerificationAtRuntime' +build/android/list_class_verification_failures.py --package="org.chromium.chrome" +W 0.000s Main Skipping deobfuscation because no map file was provided. +first.failing.Class +second.failing.Class +... +``` + +"Skipping deobfuscation because no map file was provided" is a warning, since +many Android applications (including Chrome's release builds) are built with +proguard (or similar tools) to obfuscate Java classes and shrink code. Although +it's safe to ignore this warning if you don't obfuscate Java code, the script +knows how to deobfuscate classes for you (useful for `is_debug = true` or +`is_java_debug = true`): + +```shell +build/android/list_class_verification_failures.py --package="org.chromium.chrome" \ + --mapping= # ex. out/Release/apks/ChromePublic.apk.mapping +android.support.design.widget.AppBarLayout +android.support.design.widget.TextInputLayout +... +``` + +Googlers can also download mappings for [official +builds](http://go/webview-official-builds). + +### Understanding the reason for the failure + +ART team also provide tooling for this. You can configure ART on a rooted device +to log all class verification failures (during installation), at which point the +cause is much clearer: + +```shell +# Enable ART logging (requires root). Note the 2 pairs of quotes! +adb root +adb shell setprop dalvik.vm.dex2oat-flags '"--runtime-arg -verbose:verifier"' + +# Restart Android services to pick up the settings +adb shell stop && adb shell start + +# Optional: clear logs which aren't relevant +adb logcat -c + +# Install the app and check for ART logs +adb install -d -r out/Default/apks/ChromePublic.apk +adb logcat | grep 'dex2oat' +... +... I dex2oat : Soft verification failures in boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu) +... I dex2oat : boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu): [0xF0] couldn't find method android.view.textclassifier.TextClassification.getActions ()Ljava/util/List; +... I dex2oat : boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu): [0xFA] couldn't find method android.view.textclassifier.TextClassification.getActions ()Ljava/util/List; +... +``` + +*** note +**Note:** you may want to avoid `adb` wrapper scripts (ex. +`out/Default/bin/chrome_public_apk install`). These scripts cache the package +manager state to optimize away idempotent installs. However in this case, we +**do** want to trigger idempotent installs, because we want to re-trigger AOT +verification. +*** + +In the above example, `SelectionPopupControllerImpl` fails verification on Oreo +(API 26) because it refers to [`TextClassification.getActions()`][5], which was +added in Pie (API 28). If `SelectionPopupControllerImpl` is used on pre-Pie +devices, then `TextClassification.getActions()` must be out-of-lined. + +## See also + +* Bugs or questions? Contact ntfschr@chromium.org +* ART team's Google I/O talks: [2014](https://youtu.be/EBlTzQsUoOw) and later + years +* Analysis of class verification in Chrome and WebView (Google-only + [doc](http://go/class-verification-chromium-analysis)) +* Presentation on class verification in Chrome and WebView (Google-only + [slide deck](http://go/class-verification-chromium-slides)) + +[1]: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5 +[2]: https://developer.android.com/reference/android/view/Window.html#isWideColorGamut() +[3]: https://bugs.chromium.org/p/chromium/issues/detail?id=838702 +[4]: https://developer.android.com/reference/android/os/Build.VERSION_CODES +[5]: https://developer.android.com/reference/android/view/textclassifier/TextClassification.html#getActions() diff --git a/third_party/libwebrtc/build/android/docs/coverage.md b/third_party/libwebrtc/build/android/docs/coverage.md new file mode 100644 index 0000000000..09fc29955c --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/coverage.md @@ -0,0 +1,83 @@ +# Android code coverage instructions + +These are instructions for collecting code coverage data for android +instrumentation and JUnit tests. + +[TOC] + +## How JaCoCo coverage works + +In order to use JaCoCo code coverage, we need to create build time pre-instrumented +class files and runtime **.exec** files. Then we need to process them using the +[build/android/generate_jacoco_report.py](https://source.chromium.org/chromium/chromium/src/+/main:build/android/generate_jacoco_report.py) script. + +## How to collect coverage data + +1. Use the following GN build arguments: + + ```gn + target_os = "android" + use_jacoco_coverage = true + ``` + + Now when building, pre-instrumented files will be created in the build directory. + +2. Run tests, with option `--coverage-dir `, to specify where to save + the .exec file. For example, you can run chrome JUnit tests: + `out/Debug/bin/run_chrome_junit_tests --coverage-dir /tmp/coverage`. + +3. The coverage results of JUnit and instrumentation tests will be merged + automatically if they are in the same directory. + +## How to generate coverage report + +1. Now we have generated .exec files already. We can create a JaCoCo HTML/XML/CSV + report using `generate_jacoco_report.py`, for example: + + ```shell + build/android/generate_jacoco_report.py \ + --format html \ + --output-dir /tmp/coverage_report/ \ + --coverage-dir /tmp/coverage/ \ + --sources-json-dir out/Debug/ \ + ``` + Then an index.html containing coverage info will be created in output directory: + + ``` + [INFO] Loading execution data file /tmp/coverage/testTitle.exec. + [INFO] Loading execution data file /tmp/coverage/testSelected.exec. + [INFO] Loading execution data file /tmp/coverage/testClickToSelect.exec. + [INFO] Loading execution data file /tmp/coverage/testClickToClose.exec. + [INFO] Loading execution data file /tmp/coverage/testThumbnail.exec. + [INFO] Analyzing 58 classes. + ``` + +2. For XML and CSV reports, we need to specify `--output-file` instead of `--output-dir` since + only one file will be generated as XML or CSV report. + ```shell + build/android/generate_jacoco_report.py \ + --format xml \ + --output-file /tmp/coverage_report/report.xml \ + --coverage-dir /tmp/coverage/ \ + --sources-json-dir out/Debug/ \ + ``` + + or + + ```shell + build/android/generate_jacoco_report.py \ + --format csv \ + --output-file /tmp/coverage_report/report.csv \ + --coverage-dir /tmp/coverage/ \ + --sources-json-dir out/Debug/ \ + ``` +3. If generating coverage and there are duplicate class files, as can happen + when generating coverage for downstream targets, use the + `--include-substr-filter` option to choose jars in the desired directory. Eg. + for generating coverage report for Clank internal repo + ```shell + build/android/generate_jacoco_report.py --format html \ + --output-dir /tmp/coverage_report/ --coverage-dir /tmp/coverage/ \ + --sources-json-dir out/java_coverage/ \ + --include-substr-filter obj/clank + ``` diff --git a/third_party/libwebrtc/build/android/docs/java_optimization.md b/third_party/libwebrtc/build/android/docs/java_optimization.md new file mode 100644 index 0000000000..0ba0d50358 --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/java_optimization.md @@ -0,0 +1,149 @@ +# Optimizing Java Code + +This doc describes how Java code is optimized in Chrome on Android and how to +deal with issues caused by the optimizer. For tips on how to write optimized +code, see [//docs/speed/binary_size/optimization_advice.md#optimizing-java-code](/docs/speed/binary_size/optimization_advice.md#optimizing-java-code). + +[TOC] + +## ProGuard vs R8 + +ProGuard is the original open-source tool used by many Android applications to +perform whole-program bytecode optimization. [R8](https://r8.googlesource.com/r8), +is a re-implementation that is used by Chrome (and the default for Android Studio). +The terms "ProGuard" and "R8" are used interchangeably within Chromium but +generally they're meant to refer to the tool providing Java code optimizations. + +## What does ProGuard do? + +1. Shrinking: ProGuard will remove unused code. This is especially useful + when depending on third party libraries where only a few functions are used. + +2. Obfuscation: ProGuard will rename classes/fields/methods to use shorter + names. Obfuscation is used for minification purposes only (not security). + +3. Optimization: ProGuard performs a series of optimizations to shrink code + further through various approaches (ex. inlining, outlining, class merging, + etc). + +## Build Process + +ProGuard is enabled only for release builds of Chrome because it is a slow build +step and breaks Java debugging. It can also be enabled manually via the GN arg: +```is_java_debug = false``` + +### ProGuard configuration files + +Most GN Java targets can specify ProGuard configuration files by setting the +`proguard_configs` variable. [//base/android/proguard](/base/android/proguard) +contains common flags shared by most Chrome applications. + +### GN build rules + +When `is_java_debug = false` and a target has enabled ProGuard, the `proguard` +step generates the `.dex` files for the application. The `proguard` step takes +as input a list of `.jar` files, runs R8/ProGuard on those `.jar` files, and +produces the final `.dex` file(s) that will be packaged into your `.apk` + +## Deobfuscation + +Obfuscation can be turned off for local builds while leaving ProGuard enabled +by setting `enable_proguard_obfuscation = false` in GN args. + +There are two main methods for deobfuscating Java stack traces locally: +1. Using APK wrapper scripts (stacks are automatically deobfuscated) + * `$OUT/bin/chrome_public_apk logcat` # Run adb logcat + * `$OUT/bin/chrome_public_apk run` # Launch chrome and run adb logcat + +2. Using `java_deobfuscate` + * build/android/stacktrace/java_deobfuscate.py $OUT/apks/ChromePublic.apk.mapping < logcat.txt` + * ProGuard mapping files are located beside APKs (ex. + `$OUT/apks/ChromePublic.apk` and `$OUT/apks/ChromePublic.apk.mapping`) + +Helpful links for deobfuscation: + +* [Internal bits about how mapping files are archived][proguard-site] +* [More detailed deobfuscation instructions][proguard-doc] +* [Script for deobfuscating official builds][deob-official] + +[proguard-site]: http://goto.google.com/chrome-android-proguard +[proguard-doc]: http://goto.google.com/chromejavadeobfuscation +[deob-official]: http://goto.google.com/chrome-android-official-deobfuscation + +## Debugging common failures + +ProGuard failures are often hard to debug. This section aims to outline some of +the more common errors. + +### Classes expected to be discarded + +The `-checkdiscard` directive can be used to ensure that certain items are +removed by ProGuard. A common use of `-checkdiscard` it to ensure that ProGuard +optimizations do not regress in their ability to remove code, such as code +intended only for debug builds, or generated JNI classes that are meant to be +zero-overhead abstractions. Annotating a class with +[@CheckDiscard][checkdiscard] will add a `-checkdiscard` rule automatically. + +[checkdiscard]: /base/android/java/src/org/chromium/base/annotations/CheckDiscard.java + +``` +Item void org.chromium.base.library_loader.LibraryPrefetcherJni.() was not discarded. +void org.chromium.base.library_loader.LibraryPrefetcherJni.() +|- is invoked from: +| void org.chromium.base.library_loader.LibraryPrefetcher.asyncPrefetchLibrariesToMemory() +... more code path lines +|- is referenced in keep rule: +| obj/chrome/android/chrome_public_apk/chrome_public_apk.resources.proguard.txt:104:1 + +Error: Discard checks failed. +``` + +Things to check + * Did you add code that is referenced by code path in the error message? + * If so, check the original class for why the `CheckDiscard` was added + originally and verify that the reason is still valid with your change (may + need git blame to do this). + * Try the extra debugging steps listed in the JNI section below. + +### JNI wrapper classes not discarded + +Proxy native methods (`@NativeMethods`) use generated wrapper classes to provide +access to native methods. We rely on ProGuard to fully optimize the generated +code so that native methods aren't a source of binary size bloat. The above +error message is an example when a JNI wrapper class wasn't discarded (notice +the name of the offending class). + * The ProGuard rule pointed to in the error message isn't helpful (just tells + us a code path that reaches the not-inlined class). + * Common causes: + * Caching the result of `ClassNameJni.get()` in a member variable. + * Passing a native wrapper method reference instead of using a lambda (i.e. + `Jni.get()::methodName` vs. `() -> Jni.get.methodName()`). + * For more debugging info, add to `base/android/proguard/chromium_code.flags`: + ``` + -whyareyounotinlining class org.chromium.base.library_loader.LibraryPrefetcherJni { + (); + } + ``` + +### Duplicate classes + +``` +Type YourClassName is defined multiple times: obj/jar1.jar:YourClassName.class, obj/jar2.jar:YourClassName.class +``` + +Common causes: + * Multiple targets with overlapping `srcjar_deps`: + * Each `.srcjar` can only be depended on by a single Java target in any + given APK target. `srcjar_deps` are just a convenient way to depend on + generated files and should be treated like source files rather than + `deps`. + * Solution: Wrap the `srcjar` in an `android_library` target or have only a + single Java target depend on the `srcjar` and have other targets depend on + the containing Java target instead. + * Accidentally enabling APK level generated files for multiple targets that + share generated code (ex. Trichrome or App Bundles): + * Solution: Make sure the generated file is only added once. + +Debugging ProGuard failures isn't easy, so please message java@chromium.org +or [file a bug](crbug.com/new) with `component=Build os=Android` for any +issues related to Java code optimization. diff --git a/third_party/libwebrtc/build/android/docs/java_toolchain.md b/third_party/libwebrtc/build/android/docs/java_toolchain.md new file mode 100644 index 0000000000..4a39175472 --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/java_toolchain.md @@ -0,0 +1,284 @@ +# Chromium's Java Toolchain + +This doc aims to describe the Chrome build process that takes a set of `.java` +files and turns them into a `classes.dex` file. + +[TOC] + +## Core GN Target Types + +The following have `supports_android` and `requires_android` set to false by +default: +* `java_library()`: Compiles `.java` -> `.jar` +* `java_prebuilt()`: Imports a prebuilt `.jar` file. + +The following have `supports_android` and `requires_android` set to true. They +also have a default `jar_excluded_patterns` set (more on that later): +* `android_library()` +* `android_java_prebuilt()` + +All target names must end with "_java" so that the build system can distinguish +them from non-java targets (or [other variations](https://cs.chromium.org/chromium/src/build/config/android/internal_rules.gni?rcl=ec2c17d7b4e424e060c3c7972842af87343526a1&l=20)). + +Most targets produce two separate `.jar` files: +* Device `.jar`: Used to produce `.dex.jar`, which is used on-device. +* Host `.jar`: For use on the host machine (`junit_binary` / `java_binary`). + * Host `.jar` files live in `lib.java/` so that they are archived in + builder/tester bots (which do not archive `obj/`). + +## From Source to Final Dex + +### Step 1: Create interface .jar with turbine or ijar + +For prebuilt `.jar` files, use [//third_party/ijar] to create interface `.jar` +from prebuilt `.jar`. + +For non-prebuilt targets, use [//third_party/turbine] to create interface `.jar` +from `.java` source files. Turbine is much faster than javac, and so enables +full compilation to happen more concurrently. + +What are interface jars?: + +* The contain `.class` files with all non-public symbols and function bodies + removed. +* Dependant targets use interface `.jar` files to skip having to be rebuilt + when only private implementation details change. + +[//third_party/ijar]: /third_party/ijar/README.chromium +[//third_party/turbine]: /third_party/turbine/README.chromium + +### Step 2a: Compile with javac + +This step is the only step that does not apply to prebuilt targets. + +* All `.java` files in a target are compiled by `javac` into `.class` files. + * This includes `.java` files that live within `.srcjar` files, referenced + through `srcjar_deps`. +* The `classpath` used when compiling a target is comprised of `.jar` files of + its deps. + * When deps are library targets, the Step 1 `.jar` file is used. + * When deps are prebuilt targets, the original `.jar` file is used. + * All `.jar` processing done in subsequent steps does not impact compilation + classpath. +* `.class` files are zipped into an output `.jar` file. +* There is **no support** for incremental compilation at this level. + * If one source file changes within a library, then the entire library is + recompiled. + * Prefer smaller targets to avoid slow compiles. + +### Step 2b: Compile with ErrorProne + +This step can be disabled via GN arg: `use_errorprone_java_compiler = false` + +* Concurrently with step 1a: [ErrorProne] compiles java files and checks for bug + patterns, including some [custom to Chromium][ep_plugins]. +* ErrorProne used to replace step 1a, but was changed to a concurrent step after + being identified as being slower. + +[ErrorProne]: https://errorprone.info/ +[ep_plugins]: /tools/android/errorprone_plugin/ + +### Step 3: Desugaring (Device .jar Only) + +This step happens only when targets have `supports_android = true`. It is not +applied to `.jar` files used by `junit_binary`. + +* `//third_party/bazel/desugar` converts certain Java 8 constructs, such as + lambdas and default interface methods, into constructs that are compatible + with Java 7. + +### Step 4: Instrumenting (Device .jar Only) + +This step happens only when this GN arg is set: `use_jacoco_coverage = true` + +* [Jacoco] adds instrumentation hooks to methods. + +[Jacoco]: https://www.eclemma.org/jacoco/ + +### Step 5: Filtering + +This step happens only when targets that have `jar_excluded_patterns` or +`jar_included_patterns` set (e.g. all `android_` targets). + +* Remove `.class` files that match the filters from the `.jar`. These `.class` + files are generally those that are re-created with different implementations + further on in the build process. + * E.g.: `R.class` files - a part of [Android Resources]. + * E.g.: `GEN_JNI.class` - a part of our [JNI] glue. + * E.g.: `AppHooksImpl.class` - how `chrome_java` wires up different + implementations for [non-public builds][apphooks]. + +[JNI]: /base/android/jni_generator/README.md +[Android Resources]: life_of_a_resource.md +[apphooks]: /chrome/android/java/src/org/chromium/chrome/browser/AppHooksImpl.java + +### Step 6: Per-Library Dexing + +This step happens only when targets have `supports_android = true`. + +* [d8] converts `.jar` files containing `.class` files into `.dex.jar` files + containing `classes.dex` files. +* Dexing is incremental - it will reuse dex'ed classes from a previous build if + the corresponding `.class` file is unchanged. +* These per-library `.dex.jar` files are used directly by [incremental install], + and are inputs to the Apk step when `enable_proguard = false`. + * Even when `is_java_debug = false`, many apk targets do not enable ProGuard + (e.g. unit tests). + +[d8]: https://developer.android.com/studio/command-line/d8 +[incremental install]: /build/android/incremental_install/README.md + +### Step 7: Apk / Bundle Module Compile + +* Each `android_apk` and `android_bundle_module` template has a nested + `java_library` target. The nested library includes final copies of files + stripped out by prior filtering steps. These files include: + * Final `R.java` files, created by `compile_resources.py`. + * Final `GEN_JNI.java` for [JNI glue]. + * `BuildConfig.java` and `NativeLibraries.java` (//base dependencies). + +[JNI glue]: /base/android/jni_generator/README.md + +### Step 8: Final Dexing + +This step is skipped when building using [Incremental Install]. + +When `is_java_debug = true`: +* [d8] merges all library `.dex.jar` files into a final `.mergeddex.jar`. + +When `is_java_debug = false`: +* [R8] performs whole-program optimization on all library `lib.java` `.jar` + files and outputs a final `.r8dex.jar`. + * For App Bundles, R8 creates a `.r8dex.jar` for each module. + +[Incremental Install]: /build/android/incremental_install/README.md +[R8]: https://r8.googlesource.com/r8 + +## Test APKs with apk_under_test + +Test APKs are normal APKs that contain an `` tag within their +`AndroidManifest.xml`. If this tag specifies an `android:targetPackage` +different from itself, then Android will add that package's `classes.dex` to the +test APK's Java classpath when run. In GN, you can enable this behavior using +the `apk_under_test` parameter on `instrumentation_test_apk` targets. Using it +is discouraged if APKs have `proguard_enabled=true`. + +### Difference in Final Dex + +When `enable_proguard=false`: +* Any library depended on by the test APK that is also depended on by the + apk-under-test is excluded from the test APK's final dex step. + +When `enable_proguard=true`: +* Test APKs cannot make use of the apk-under-test's dex because only symbols + explicitly kept by `-keep` directives are guaranteed to exist after + ProGuarding. As a work-around, test APKs include all of the apk-under-test's + libraries directly in its own final dex such that the under-test apk's Java + code is never used (because it is entirely shadowed by the test apk's dex). + * We've found this configuration to be fragile, and are trying to [move away + from it](https://bugs.chromium.org/p/chromium/issues/detail?id=890452). + +### Difference in GEN_JNI.java +* Calling native methods using [JNI glue] requires that a `GEN_JNI.java` class + be generated that contains all native methods for an APK. There cannot be + conflicting `GEN_JNI` classes in both the test apk and the apk-under-test, so + only the apk-under-test has one generated for it. As a result this, + instrumentation test APKs that use apk-under-test cannot use native methods + that aren't already part of the apk-under-test. + +## How to Generate Java Source Code +There are two ways to go about generating source files: Annotation Processors +and custom build steps. + +### Annotation Processors +* These are run by `javac` as part of the compile step. +* They **cannot** modify the source files that they apply to. They can only + generate new sources. +* Use these when: + * an existing Annotation Processor does what you want + (E.g. Dagger, AutoService, etc.), or + * you need to understand Java types to do generation. + +### Custom Build Steps +* These use discrete build actions to generate source files. + * Some generate `.java` directly, but most generate a zip file of sources + (called a `.srcjar`) to simplify the number of inputs / outputs. +* Examples of existing templates: + * `jinja_template`: Generates source files using [Jinja]. + * `java_cpp_template`: Generates source files using the C preprocessor. + * `java_cpp_enum`: Generates `@IntDef`s based on enums within `.h` files. + * `java_cpp_strings`: Generates String constants based on strings defined in + `.cc` files. +* Custom build steps are preferred over Annotation Processors because they are + generally easier to understand, and can run in parallel with other steps + (rather than being tied to compiles). + +[Jinja]: https://palletsprojects.com/p/jinja/ + +## Static Analysis & Code Checks + +We use several tools for static analysis. + +### [ErrorProne](https://errorprone.info/) +* Runs as part of normal compilation. Controlled by GN arg: `use_errorprone_java_compiler`. +* Most useful check: + * Enforcement of `@GuardedBy` annotations. +* List of enabled / disabled checks exists [within compile_java.py](https://cs.chromium.org/chromium/src/build/android/gyp/compile_java.py?l=30) + * Many checks are currently disabled because there is work involved in fixing + violations they introduce. Please help! +* Custom checks for Chrome: + * [//tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/](/tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/) +* Use ErrorProne checks when you need something more sophisticated than pattern + matching. +* Checks run on the entire codebase, not only on changed lines. +* Does not run when `chromium_code = false` (e.g. for //third_party). + +### [Android Lint](https://developer.android.com/studio/write/lint) +* Runs as part of normal compilation. Controlled by GN arg: `disable_android_lint` +* Most useful check: + * Enforcing `@TargetApi` annotations (ensure you don't call a function that + does not exist on all versions of Android unless guarded by an version + check). +* List of disabled checks: + * [//build/android/lint/suppressions.xml](/build/android/lint/suppressions.xml) +* Custom lint checks [are possible](lint_plugins), but we don't have any. +* Checks run on the entire codebase, not only on changed lines. +* Does not run when `chromium_code = false` (e.g. for //third_party). + +[lint_plugins]: http://tools.android.com/tips/lint-custom-rules + +### [Bytecode Processor](/build/android/bytecode/) +* Performs a single check: + * That target `deps` are not missing any entries. + * In other words: Enforces that targets do not rely on indirect dependencies + to populate their classpath. +* Checks run on the entire codebase, not only on changed lines. + +### [PRESUBMIT.py](/PRESUBMIT.py): +* Checks for banned patterns via `_BANNED_JAVA_FUNCTIONS`. + * (These should likely be moved to checkstyle). +* Checks for a random set of things in `ChecksAndroidSpecificOnUpload()`. + * Including running Checkstyle. + * (Some of these other checks should likely also be moved to checkstyle). +* Checks run only on changed lines. + +### [Checkstyle](https://checkstyle.sourceforge.io/) +* Checks Java style rules that are not covered by clang-format. + * E.g.: Unused imports and naming conventions. +* Allows custom checks to be added via XML. Here [is ours]. +* Preferred over adding checks directly in PRESUBMIT.py because the tool + understands `@SuppressWarnings` annotations. +* Checks run only on changed lines. + +[is ours]: /tools/android/checkstyle/chromium-style-5.0.xml + +### [clang-format](https://clang.llvm.org/docs/ClangFormat.html) +* Formats `.java` files via `git cl format`. +* Can be toggle on/off with code comments. + ```java + // clang-format off + ... non-formatted code here ... + // clang-format on + ``` +* Does not work great for multiple annotations or on some lambda expressions, + but is generally agreed it is better than not having it at all. diff --git a/third_party/libwebrtc/build/android/docs/life_of_a_resource.md b/third_party/libwebrtc/build/android/docs/life_of_a_resource.md new file mode 100644 index 0000000000..5e46ef66af --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/life_of_a_resource.md @@ -0,0 +1,289 @@ +# Life of an Android Resource + +[TOC] + +## Overview + +This document describes how [Android Resources][android resources] +are built in Chromium's build system. It does not mention native resources +which are [processed differently][native resources]. + +[android resources]: https://developer.android.com/guide/topics/resources/providing-resources +[native resources]: https://www.chromium.org/developers/tools-we-use-in-chromium/grit/grit-users-guide + +The steps consume the following files as inputs: +* `AndroidManifest.xml` + * Including `AndroidManifest.xml` files from libraries, which get merged + together +* res/ directories + +The steps produce the following intermediate files: +* `R.srcjar` (contains `R.java` files) +* `R.txt` +* `.resources.zip` + +The steps produce the following files within an `.apk`: +* `AndroidManifest.xml` (a binary xml file) +* `resources.arsc` (contains all values and configuration metadata) +* `res/**` (drawables and layouts) +* `classes.dex` (just a small portion of classes from generated `R.java` files) + + +## The Build Steps + +Whenever you try to compile an apk or library target, resources go through the +following steps: + +### 1. Constructs .build\_config files: + +Inputs: +* GN target metadata +* Other `.build_config.json` files + +Outputs: +* Target-specific `.build_config.json` file + +`write_build_config.py` is run to record target metadata needed by future steps. +For more details, see [build_config.md](build_config.md). + + +### 2. Prepares resources: + +Inputs: +* Target-specific `.build_config.json` file +* Files listed as `sources` + +Outputs: +* Target-specific `resources.zip` (contains all resources listed in `sources`). +* Target-specific `R.txt` (list of all resources, including dependencies). + +`prepare_resources.py` zips up the target-specific resource files and generates +`R.txt`. No optimizations, crunching, etc are done on the resources. + +**The following steps apply only to apk & bundle targets (not to library +targets).** + +### 3. Create target-specific R.java files + +Inputs: +* `R.txt` from dependencies. + +Outputs: +* Target-specific (placeholder) `R.java` file. + +A target-specific `R.java` is generated for each `android_library()` target that +sets `resources_package`. Resource IDs are not known at this phase, so all +values are set as placeholders. This copy of `R` classes are discarded and +replaced with new copies at step 4. + +Example placeholder R.java file: +```java +package org.chromium.mypackage; + +public final class R { + public static class anim { + public static int abc_fade_in = 0; + public static int abc_fade_out = 0; + ... + } + ... +} +``` + +### 4. Finalizes apk resources: + +Inputs: +* Target-specific `.build_config.json` file +* Dependencies' `R.txt` files +* Dependencies' `resources.zip` files + +Output: +* Packaged `resources zip` (named `foo.ap_`) containing: + * `AndroidManifest.xml` (as binary xml) + * `resources.arsc` + * `res/**` +* Final `R.txt` + * Contains a list of resources and their ids (including of dependencies). +* Final `R.java` files + * See [What are `R.java` files and how are they generated]( + #how-r_java-files-are-generated) + + +#### 4(a). Compiles resources: + +For each library / resources target your apk depends on, the following happens: +* Use a regex (defined in the apk target) to remove select resources (optional). +* Convert png images to webp for binary size (optional). +* Move drawables in mdpi to non-mdpi directory ([why?](http://crbug.com/289843)) +* Use `aapt2 compile` to compile xml resources to binary xml (references to + other resources will now use the id rather than the name for faster lookup at + runtime). +* `aapt2 compile` adds headers/metadata to 9-patch images about which parts of + the image are stretchable vs static. +* `aapt2 compile` outputs a zip with the compiled resources (one for each + dependency). + + +#### 4(b). Links resources: + +After each dependency is compiled into an intermediate `.zip`, all those zips +are linked by the `aapt2 link` command which does the following: +* Use the order of dependencies supplied so that some resources clober each + other. +* Compile the `AndroidManifest.xml` to binary xml (references to resources are + now using ids rather than the string names) +* Create a `resources.arsc` file that has the name and values of string + resources as well as the name and path of non-string resources (ie. layouts + and drawables). +* Combine the compiled resources into one packaged resources apk (a zip file + with an `.ap_` extension) that has all the resources related files. + + +#### 4(c). Optimizes resources: + +Targets can opt into the following optimizations: +1) Resource name collapsing: Maps all resources to the same name. Access to + resources via `Resources.getIdentifier()` no longer work unless resources are + [allowlisted](#adding-resources-to-the-allowlist). +2) Resource filename obfuscation: Renames resource file paths from e.g.: + `res/drawable/something.png` to `res/a`. Rename mapping is stored alongside + APKs / bundles in a `.pathmap` file. Renames are based on hashes, and so are + stable between builds (unless a new hash collision occurs). +3) Unused resource removal: Referenced resources are extracted from the + optimized `.dex` and `AndroidManifest.xml`. Resources that are directly or + indirectly used by these files are removed. + +## App Bundles and Modules: + +Processing resources for bundles and modules is slightly different. Each module +has its resources compiled and linked separately (ie: it goes through the +entire process for each module). The modules are then combined to form a +bundle. Moreover, during "Finalizing the apk resources" step, bundle modules +produce a `resources.proto` file instead of a `resources.arsc` file. + +Resources in a dynamic feature module may reference resources in the base +module. During the link step for feature module resources, the linked resources +of the base module are passed in. However, linking against resources currently +works only with `resources.arsc` format. Thus, when building the base module, +resources are compiled as both `resources.arsc` and `resources.proto`. + +## Debugging resource related errors when resource names are obfuscated + +An example message from a stacktrace could be something like this: +``` +java.lang.IllegalStateException: Could not find CoordinatorLayout descendant +view with id org.chromium.chrome:id/0_resource_name_obfuscated to anchor view +android.view.ViewStub{be192d5 G.E...... ......I. 0,0-0,0 #7f0a02ad +app:id/0_resource_name_obfuscated} +``` + +`0_resource_name_obfuscated` is the resource name for all resources that had +their name obfuscated/stripped during the optimize resources step. To help with +debugging, the `R.txt` file is archived. The `R.txt` file contains a mapping +from resource ids to resource names and can be used to get the original resource +name from the id. In the above message the id is `0x7f0a02ad`. + +For local builds, `R.txt` files are output in the `out/*/apks` directory. + +For official builds, Googlers can get archived `R.txt` files next to archived +apks. + +### Adding resources to the allowlist + +If a resource is accessed via `getIdentifier()` it needs to be allowed by an +aapt2 resources config file. The config file looks like this: + +``` +/#no_obfuscate +``` +eg: +``` +string/app_name#no_obfuscate +id/toolbar#no_obfuscate +``` + +The aapt2 config file is passed to the ninja target through the +`resources_config_paths` variable. To add a resource to the allowlist, check +where the config is for your target and add a new line for your resource. If +none exist, create a new config file and pass its path in your target. + +### Webview resource ids + +The first two bytes of a resource id is the package id. For regular apks, this +is `0x7f`. However, Webview is a shared library which gets loaded into other +apks. The package id for webview resources is assigned dynamically at runtime. +When webview is loaded it calls this [R file's][Base Module R.java File] +`onResourcesLoaded()` function to have the correct package id. When +deobfuscating webview resource ids, disregard the first two bytes in the id when +looking it up in the `R.txt` file. + +Monochrome, when loaded as webview, rewrites the package ids of resources used +by the webview portion to the correct value at runtime, otherwise, its resources +have package id `0x7f` when run as a regular apk. + +[Base Module R.java File]: https://cs.chromium.org/chromium/src/out/android-Debug/gen/android_webview/system_webview_apk/generated_java/gen/base_module/R.java + +## How R.java files are generated + +`R.java` contain a set of nested static classes, each with static fields +containing ids. These ids are used in java code to reference resources in +the apk. + +There are three types of `R.java` files in Chrome. +1. Root / Base Module `R.java` Files +2. DFM `R.java` Files +3. Per-Library `R.java` Files + +### Root / Base Module `R.java` Files +Contain base android resources. All `R.java` files can access base module +resources through inheritance. + +Example Root / Base Module `R.java` File: +```java +package gen.base_module; + +public final class R { + public static class anim { + public static final int abc_fade_in = 0x7f010000; + public static final int abc_fade_out = 0x7f010001; + public static final int abc_slide_in_top = 0x7f010007; + } + public static class animator { + public static final int design_appbar_state_list_animator = 0x7f020000; + } +} +``` + +### DFM `R.java` Files +Extend base module root `R.java` files. This allows DFMs to access their own +resources as well as the base module's resources. + +Example DFM Root `R.java` File +```java +package gen.vr_module; + +public final class R { + public static class anim extends gen.base_module.R.anim { + } + public static class animator extends gen.base_module.R.animator { + public static final int design_appbar_state_list_animator = 0x7f030000; + } +} +``` + +### Per-Library `R.java` Files +Generated for each `android_library()` target that sets `resources_package`. +First a placeholder copy is generated in the `android_library()` step, and then +a final copy is created during finalization. + +Example final per-library `R.java`: +```java +package org.chromium.chrome.vr; + +public final class R { + public static final class anim extends + gen.vr_module.R.anim {} + public static final class animator extends + gen.vr_module.R.animator {} +} +``` diff --git a/third_party/libwebrtc/build/android/docs/lint.md b/third_party/libwebrtc/build/android/docs/lint.md new file mode 100644 index 0000000000..67e2f8bf3e --- /dev/null +++ b/third_party/libwebrtc/build/android/docs/lint.md @@ -0,0 +1,140 @@ +# Lint + +Android's [**lint**](https://developer.android.com/tools/help/lint.html) is a +static analysis tool that Chromium uses to catch possible issues in Java code. + +This is a list of [**checks**](http://tools.android.com/tips/lint-checks) that +you might encounter. + +[TOC] + +## How Chromium uses lint + +Chromium only runs lint on apk or bundle targets that explicitly set +`enable_lint = true`. Some example targets that have this set are: + + - `//chrome/android:monochrome_public_bundle` + - `//android_webview/support_library/boundary_interfaces:boundary_interface_example_apk` + - `//remoting/android:remoting_apk` + +## My code has a lint error + +If lint reports an issue in your code, there are several possible remedies. +In descending order of preference: + +### Fix it + +While this isn't always the right response, fixing the lint error or warning +should be the default. + +### Suppress it locally + +Java provides an annotation, +[`@SuppressWarnings`](https://developer.android.com/reference/java/lang/SuppressWarnings), +that tells lint to ignore the annotated element. It can be used on classes, +constructors, methods, parameters, fields, or local variables, though usage in +Chromium is typically limited to the first three. You do not need to import it +since it is in the `java.lang` package. + +Like many suppression annotations, `@SuppressWarnings` takes a value that tells +**lint** what to ignore. It can be a single `String`: + +```java +@SuppressWarnings("NewApi") +public void foo() { + a.methodThatRequiresHighSdkLevel(); +} +``` + +It can also be a list of `String`s: + +```java +@SuppressWarnings({ + "NewApi", + "UseSparseArrays" + }) +public Map bar() { + Map shouldBeASparseArray = new HashMap(); + another.methodThatRequiresHighSdkLevel(shouldBeASparseArray); + return shouldBeASparseArray; +} +``` + +For resource xml files you can use `tools:ignore`: + +```xml + + + + @color/unused + +``` + +The examples above are the recommended ways of suppressing lint warnings. + +### Suppress it in a `lint-suppressions.xml` file + +**lint** can be given a per-target XML configuration file containing warnings or +errors that should be ignored. Each target defines its own configuration file +via the `lint_suppressions_file` gn variable. It is usually defined near its +`enable_lint` gn variable. + +These suppressions files should only be used for temporarily ignoring warnings +that are too hard (or not possible) to suppress locally, and permanently +ignoring warnings only for this target. To permanently ignore a warning for all +targets, add the warning to the `_DISABLED_ALWAYS` list in +[build/android/gyp/lint.py](https://source.chromium.org/chromium/chromium/src/+/main:build/android/gyp/lint.py). +Disabling globally makes lint a bit faster. + +The exception to the above rule is for warnings that affect multiple languages. +Feel free to suppress those in lint-suppressions.xml files since it is not +practical to suppress them in each language file and it is a lot of extra bloat +to list out every language for every violation in lint-baseline.xml files. + +Here is an example of how to structure a suppressions XML file: + +```xml + + + + + + + + + + + +``` + +## What are `lint-baseline.xml` files for? + +Baseline files are to help us introduce new lint warnings and errors without +blocking on fixing all our existing code that violate these new errors. Since +they are generated files, they should **not** be used to suppress lint warnings. +One of the approaches above should be used instead. Eventually all the errors in +baseline files should be either fixed or ignored permanently. + +The following are some common scenarios where you may need to update baseline +files. + +### I updated `cmdline-tools` and now there are tons of new errors! + +This happens every time lint is updated, since lint is provided by +`cmdline-tools`. + +Baseline files are defined via the `lint_baseline_file` gn variable. It is +usually defined near a target's `enable_lint` gn variable. To regenerate the +baseline file, delete it and re-run the lint target. The command will fail, but +the baseline file will have been generated. + +This may need to be repeated for all targets that have set `enable_lint = true`, +including downstream targets. Downstream baseline files should be updated and +first to avoid build breakages. Each target has its own `lint_baseline_file` +defined and so all these files can be removed and regenerated as needed. + +### I updated `library X` and now there are tons of new errors! + +This is usually because `library X`'s aar contains custom lint checks and/or +custom annotation definition. Follow the same procedure as updates to +`cmdline-tools`. diff --git a/third_party/libwebrtc/build/android/download_doclava.py b/third_party/libwebrtc/build/android/download_doclava.py new file mode 100755 index 0000000000..059d1cbafe --- /dev/null +++ b/third_party/libwebrtc/build/android/download_doclava.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# 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. + +"""Minimal tool to download doclava from Google storage when building for +Android.""" + +import os +import subprocess +import sys + + +def main(): + # Some Windows bots inadvertently have third_party/android_sdk installed, + # but are unable to run download_from_google_storage because depot_tools + # is not in their path, so avoid failure and bail. + if sys.platform == 'win32': + return 0 + subprocess.check_call([ + 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket', 'chromium-doclava', + '--extract', + '-s', + os.path.join(os.path.dirname(__file__), '..', '..', 'buildtools', + 'android', 'doclava.tar.gz.sha1')]) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/dump_apk_resource_strings.py b/third_party/libwebrtc/build/android/dump_apk_resource_strings.py new file mode 100755 index 0000000000..559547b8d9 --- /dev/null +++ b/third_party/libwebrtc/build/android/dump_apk_resource_strings.py @@ -0,0 +1,659 @@ +#!/usr/bin/env vpython3 +# encoding: utf-8 +# Copyright 2019 The Chromium 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 script to parse and dump localized strings in resource.arsc files.""" + +from __future__ import print_function + +import argparse +import collections +import contextlib +import cProfile +import os +import re +import subprocess +import sys +import zipfile + +# pylint: disable=bare-except + +# Assuming this script is located under build/android, try to import +# build/android/gyp/bundletool.py to get the default path to the bundletool +# jar file. If this fail, using --bundletool-path will be required to parse +# bundles, allowing this script to be relocated or reused somewhere else. +try: + sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'gyp')) + import bundletool + + _DEFAULT_BUNDLETOOL_PATH = bundletool.BUNDLETOOL_JAR_PATH +except: + _DEFAULT_BUNDLETOOL_PATH = None + +# Try to get the path of the aapt build tool from catapult/devil. +try: + import devil_chromium # pylint: disable=unused-import + from devil.android.sdk import build_tools + _AAPT_DEFAULT_PATH = build_tools.GetPath('aapt') +except: + _AAPT_DEFAULT_PATH = None + + +def AutoIndentStringList(lines, indentation=2): + """Auto-indents a input list of text lines, based on open/closed braces. + + For example, the following input text: + + 'Foo {', + 'Bar {', + 'Zoo', + '}', + '}', + + Will return the following: + + 'Foo {', + ' Bar {', + ' Zoo', + ' }', + '}', + + The rules are pretty simple: + - A line that ends with an open brace ({) increments indentation. + - A line that starts with a closing brace (}) decrements it. + + The main idea is to make outputting structured text data trivial, + since it can be assumed that the final output will be passed through + this function to make it human-readable. + + Args: + lines: an iterator over input text lines. They should not contain + line terminator (e.g. '\n'). + Returns: + A new list of text lines, properly auto-indented. + """ + margin = '' + result = [] + # NOTE: Intentional but significant speed optimizations in this function: + # - |line and line[0] == | instead of |line.startswith()|. + # - |line and line[-1] == | instead of |line.endswith()|. + for line in lines: + if line and line[0] == '}': + margin = margin[:-indentation] + result.append(margin + line) + if line and line[-1] == '{': + margin += ' ' * indentation + + return result + + +# pylint: disable=line-too-long + +# NOTE: aapt dump will quote the following characters only: \n, \ and " +# see https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/ResourceTypes.cpp#7270 + +# pylint: enable=line-too-long + + +def UnquoteString(s): + """Unquote a given string from aapt dump. + + Args: + s: An UTF-8 encoded string that contains backslashes for quotes, as found + in the output of 'aapt dump resources --values'. + Returns: + The unquoted version of the input string. + """ + if not '\\' in s: + return s + + result = '' + start = 0 + size = len(s) + while start < size: + pos = s.find('\\', start) + if pos < 0: + break + + result += s[start:pos] + count = 1 + while pos + count < size and s[pos + count] == '\\': + count += 1 + + result += '\\' * (count / 2) + start = pos + count + if count & 1: + if start < size: + ch = s[start] + if ch == 'n': # \n is the only non-printable character supported. + ch = '\n' + result += ch + start += 1 + else: + result += '\\' + + result += s[start:] + return result + + +assert UnquoteString(r'foo bar') == 'foo bar' +assert UnquoteString(r'foo\nbar') == 'foo\nbar' +assert UnquoteString(r'foo\\nbar') == 'foo\\nbar' +assert UnquoteString(r'foo\\\nbar') == 'foo\\\nbar' +assert UnquoteString(r'foo\n\nbar') == 'foo\n\nbar' +assert UnquoteString(r'foo\\bar') == r'foo\bar' + + +def QuoteString(s): + """Quote a given string for external output. + + Args: + s: An input UTF-8 encoded string. + Returns: + A quoted version of the string, using the same rules as 'aapt dump'. + """ + # NOTE: Using repr() would escape all non-ASCII bytes in the string, which + # is undesirable. + return s.replace('\\', r'\\').replace('"', '\\"').replace('\n', '\\n') + + +assert QuoteString(r'foo "bar"') == 'foo \\"bar\\"' +assert QuoteString('foo\nbar') == 'foo\\nbar' + + +def ReadStringMapFromRTxt(r_txt_path): + """Read all string resource IDs and names from an R.txt file. + + Args: + r_txt_path: Input file path. + Returns: + A {res_id -> res_name} dictionary corresponding to the string resources + from the input R.txt file. + """ + # NOTE: Typical line of interest looks like: + # int string AllowedDomainsForAppsTitle 0x7f130001 + result = {} + prefix = 'int string ' + with open(r_txt_path) as f: + for line in f: + line = line.rstrip() + if line.startswith(prefix): + res_name, res_id = line[len(prefix):].split(' ') + result[int(res_id, 0)] = res_name + return result + + +class ResourceStringValues(object): + """Models all possible values for a named string.""" + + def __init__(self): + self.res_name = None + self.res_values = {} + + def AddValue(self, res_name, res_config, res_value): + """Add a new value to this entry. + + Args: + res_name: Resource name. If this is not the first time this method + is called with the same resource name, then |res_name| should match + previous parameters for sanity checking. + res_config: Config associated with this value. This can actually be + anything that can be converted to a string. + res_value: UTF-8 encoded string value. + """ + if res_name is not self.res_name and res_name != self.res_name: + if self.res_name is None: + self.res_name = res_name + else: + # Sanity check: the resource name should be the same for all chunks. + # Resource ID is redefined with a different name!! + print('WARNING: Resource key ignored (%s, should be %s)' % + (res_name, self.res_name)) + + if self.res_values.setdefault(res_config, res_value) is not res_value: + print('WARNING: Duplicate value definition for [config %s]: %s ' \ + '(already has %s)' % ( + res_config, res_value, self.res_values[res_config])) + + def ToStringList(self, res_id): + """Convert entry to string list for human-friendly output.""" + values = sorted([(str(config), value) + for config, value in self.res_values.items()]) + if res_id is None: + # res_id will be None when the resource ID should not be part + # of the output. + result = ['name=%s count=%d {' % (self.res_name, len(values))] + else: + result = [ + 'res_id=0x%08x name=%s count=%d {' % (res_id, self.res_name, + len(values)) + ] + for config, value in values: + result.append('%-16s "%s"' % (config, QuoteString(value))) + result.append('}') + return result + + +class ResourceStringMap(object): + """Convenience class to hold the set of all localized strings in a table. + + Usage is the following: + 1) Create new (empty) instance. + 2) Call AddValue() repeatedly to add new values. + 3) Eventually call RemapResourceNames() to remap resource names. + 4) Call ToStringList() to convert the instance to a human-readable + list of strings that can later be used with AutoIndentStringList() + for example. + """ + + def __init__(self): + self._res_map = collections.defaultdict(ResourceStringValues) + + def AddValue(self, res_id, res_name, res_config, res_value): + self._res_map[res_id].AddValue(res_name, res_config, res_value) + + def RemapResourceNames(self, id_name_map): + """Rename all entries according to a given {res_id -> res_name} map.""" + for res_id, res_name in id_name_map.items(): + if res_id in self._res_map: + self._res_map[res_id].res_name = res_name + + def ToStringList(self, omit_ids=False): + """Dump content to a human-readable string list. + + Note that the strings are ordered by their resource name first, and + resource id second. + + Args: + omit_ids: If True, do not put resource IDs in the result. This might + be useful when comparing the outputs of two different builds of the + same APK, or two related APKs (e.g. ChromePublic.apk vs Chrome.apk) + where the resource IDs might be slightly different, but not the + string contents. + Return: + A list of strings that can later be sent to AutoIndentStringList(). + """ + result = ['Resource strings (count=%d) {' % len(self._res_map)] + res_map = self._res_map + + # Compare two (res_id, values) tuples by resource name first, then resource + # ID. + for res_id, _ in sorted(res_map.items(), + key=lambda x: (x[1].res_name, x[0])): + result += res_map[res_id].ToStringList(None if omit_ids else res_id) + result.append('} # Resource strings') + return result + + +@contextlib.contextmanager +def ManagedOutput(output_file): + """Create an output File object that will be closed on exit if necessary. + + Args: + output_file: Optional output file path. + Yields: + If |output_file| is empty, this simply yields sys.stdout. Otherwise, this + opens the file path for writing text, and yields its File object. The + context will ensure that the object is always closed on scope exit. + """ + close_output = False + if output_file: + output = open(output_file, 'wt') + close_output = True + else: + output = sys.stdout + try: + yield output + finally: + if close_output: + output.close() + + +@contextlib.contextmanager +def ManagedPythonProfiling(enable_profiling, sort_key='tottime'): + """Enable Python profiling if needed. + + Args: + enable_profiling: Boolean flag. True to enable python profiling. + sort_key: Sorting key for the final stats dump. + Yields: + If |enable_profiling| is False, this yields False. Otherwise, this + yields a new Profile instance just after enabling it. The manager + ensures that profiling stops and prints statistics on scope exit. + """ + pr = None + if enable_profiling: + pr = cProfile.Profile() + pr.enable() + try: + yield pr + finally: + if pr: + pr.disable() + pr.print_stats(sort=sort_key) + + +def IsFilePathABundle(input_file): + """Return True iff |input_file| holds an Android app bundle.""" + try: + with zipfile.ZipFile(input_file) as input_zip: + _ = input_zip.getinfo('BundleConfig.pb') + return True + except: + return False + + +# Example output from 'bundletool dump resources --values' corresponding +# to strings: +# +# 0x7F1200A0 - string/abc_action_menu_overflow_description +# (default) - [STR] "More options" +# locale: "ca" - [STR] "Més opcions" +# locale: "da" - [STR] "Flere muligheder" +# locale: "fa" - [STR] " گزینههای بیشتر" +# locale: "ja" - [STR] "その他のオプション" +# locale: "ta" - [STR] "மேலும் விருப்பங்கள்" +# locale: "nb" - [STR] "Flere alternativer" +# ... +# +# Fun fact #1: Bundletool uses - instead of -r +# for locales! +# +# Fun fact #2: The is terminal output for \u200c, the output is +# really UTF-8 encoded when it is read by this script. +# +# Fun fact #3: Bundletool quotes \n, \\ and \" just like aapt since 0.8.0. +# +_RE_BUNDLE_STRING_RESOURCE_HEADER = re.compile( + r'^0x([0-9A-F]+)\s\-\sstring/(\w+)$') +assert _RE_BUNDLE_STRING_RESOURCE_HEADER.match( + '0x7F1200A0 - string/abc_action_menu_overflow_description') + +_RE_BUNDLE_STRING_DEFAULT_VALUE = re.compile( + r'^\s+\(default\) - \[STR\] "(.*)"$') +assert _RE_BUNDLE_STRING_DEFAULT_VALUE.match( + ' (default) - [STR] "More options"') +assert _RE_BUNDLE_STRING_DEFAULT_VALUE.match( + ' (default) - [STR] "More options"').group(1) == "More options" + +_RE_BUNDLE_STRING_LOCALIZED_VALUE = re.compile( + r'^\s+locale: "([0-9a-zA-Z-]+)" - \[STR\] "(.*)"$') +assert _RE_BUNDLE_STRING_LOCALIZED_VALUE.match( + u' locale: "ar" - [STR] "گزینه\u200cهای بیشتر"'.encode('utf-8')) + + +def ParseBundleResources(bundle_tool_jar_path, bundle_path): + """Use bundletool to extract the localized strings of a given bundle. + + Args: + bundle_tool_jar_path: Path to bundletool .jar executable. + bundle_path: Path to input bundle. + Returns: + A new ResourceStringMap instance populated with the bundle's content. + """ + cmd_args = [ + 'java', '-jar', bundle_tool_jar_path, 'dump', 'resources', '--bundle', + bundle_path, '--values' + ] + p = subprocess.Popen(cmd_args, bufsize=1, stdout=subprocess.PIPE) + res_map = ResourceStringMap() + current_resource_id = None + current_resource_name = None + keep_parsing = True + need_value = False + while keep_parsing: + line = p.stdout.readline() + if not line: + break + # Do not use rstrip(), since this should only remove trailing newlines + # but not trailing whitespace that happen to be embedded in the string + # value for some reason. + line = line.rstrip('\n\r') + m = _RE_BUNDLE_STRING_RESOURCE_HEADER.match(line) + if m: + current_resource_id = int(m.group(1), 16) + current_resource_name = m.group(2) + need_value = True + continue + + if not need_value: + continue + + resource_config = None + m = _RE_BUNDLE_STRING_DEFAULT_VALUE.match(line) + if m: + resource_config = 'config (default)' + resource_value = m.group(1) + else: + m = _RE_BUNDLE_STRING_LOCALIZED_VALUE.match(line) + if m: + resource_config = 'config %s' % m.group(1) + resource_value = m.group(2) + + if resource_config is None: + need_value = False + continue + + res_map.AddValue(current_resource_id, current_resource_name, + resource_config, UnquoteString(resource_value)) + return res_map + + +# Name of the binary resources table file inside an APK. +RESOURCES_FILENAME = 'resources.arsc' + + +def IsFilePathAnApk(input_file): + """Returns True iff a ZipFile instance is for a regular APK.""" + try: + with zipfile.ZipFile(input_file) as input_zip: + _ = input_zip.getinfo(RESOURCES_FILENAME) + return True + except: + return False + + +# pylint: disable=line-too-long + +# Example output from 'aapt dump resources --values' corresponding +# to strings: +# +# config zh-rHK +# resource 0x7f12009c org.chromium.chrome:string/0_resource_name_obfuscated: t=0x03 d=0x0000caa9 (s=0x0008 r=0x00) +# (string8) "瀏覽首頁" +# resource 0x7f12009d org.chromium.chrome:string/0_resource_name_obfuscated: t=0x03 d=0x0000c8e0 (s=0x0008 r=0x00) +# (string8) "向上瀏覽" +# + +# The following are compiled regular expressions used to recognize each +# of line and extract relevant information. +# +_RE_AAPT_CONFIG = re.compile(r'^\s+config (.+):$') +assert _RE_AAPT_CONFIG.match(' config (default):') +assert _RE_AAPT_CONFIG.match(' config zh-rTW:') + +# Match an ISO 639-1 or ISO 639-2 locale. +_RE_AAPT_ISO_639_LOCALE = re.compile(r'^[a-z]{2,3}(-r[A-Z]{2,3})?$') +assert _RE_AAPT_ISO_639_LOCALE.match('de') +assert _RE_AAPT_ISO_639_LOCALE.match('zh-rTW') +assert _RE_AAPT_ISO_639_LOCALE.match('fil') +assert not _RE_AAPT_ISO_639_LOCALE.match('land') + +_RE_AAPT_BCP47_LOCALE = re.compile(r'^b\+[a-z][a-zA-Z0-9\+]+$') +assert _RE_AAPT_BCP47_LOCALE.match('b+sr') +assert _RE_AAPT_BCP47_LOCALE.match('b+sr+Latn') +assert _RE_AAPT_BCP47_LOCALE.match('b+en+US') +assert not _RE_AAPT_BCP47_LOCALE.match('b+') +assert not _RE_AAPT_BCP47_LOCALE.match('b+1234') + +_RE_AAPT_STRING_RESOURCE_HEADER = re.compile( + r'^\s+resource 0x([0-9a-f]+) [a-zA-Z][a-zA-Z0-9.]+:string/(\w+):.*$') +assert _RE_AAPT_STRING_RESOURCE_HEADER.match( + r' resource 0x7f12009c org.chromium.chrome:string/0_resource_name_obfuscated: t=0x03 d=0x0000caa9 (s=0x0008 r=0x00)' +) + +_RE_AAPT_STRING_RESOURCE_VALUE = re.compile(r'^\s+\(string8\) "(.*)"$') +assert _RE_AAPT_STRING_RESOURCE_VALUE.match(r' (string8) "瀏覽首頁"') + +# pylint: enable=line-too-long + + +def _ConvertAaptLocaleToBcp47(locale): + """Convert a locale name from 'aapt dump' to its BCP-47 form.""" + if locale.startswith('b+'): + return '-'.join(locale[2:].split('+')) + lang, _, region = locale.partition('-r') + if region: + return '%s-%s' % (lang, region) + return lang + + +assert _ConvertAaptLocaleToBcp47('(default)') == '(default)' +assert _ConvertAaptLocaleToBcp47('en') == 'en' +assert _ConvertAaptLocaleToBcp47('en-rUS') == 'en-US' +assert _ConvertAaptLocaleToBcp47('en-US') == 'en-US' +assert _ConvertAaptLocaleToBcp47('fil') == 'fil' +assert _ConvertAaptLocaleToBcp47('b+sr+Latn') == 'sr-Latn' + + +def ParseApkResources(aapt_path, apk_path): + """Use aapt to extract the localized strings of a given bundle. + + Args: + bundle_tool_jar_path: Path to bundletool .jar executable. + bundle_path: Path to input bundle. + Returns: + A new ResourceStringMap instance populated with the bundle's content. + """ + cmd_args = [aapt_path, 'dump', '--values', 'resources', apk_path] + p = subprocess.Popen(cmd_args, bufsize=1, stdout=subprocess.PIPE) + + res_map = ResourceStringMap() + current_locale = None + current_resource_id = None + current_resource_name = None + need_value = False + while True: + line = p.stdout.readline().rstrip() + if not line: + break + m = _RE_AAPT_CONFIG.match(line) + if m: + locale = None + aapt_locale = m.group(1) + if aapt_locale == '(default)': + locale = aapt_locale + elif _RE_AAPT_ISO_639_LOCALE.match(aapt_locale): + locale = aapt_locale + elif _RE_AAPT_BCP47_LOCALE.match(aapt_locale): + locale = aapt_locale + if locale is not None: + current_locale = _ConvertAaptLocaleToBcp47(locale) + continue + + if current_locale is None: + continue + + if need_value: + m = _RE_AAPT_STRING_RESOURCE_VALUE.match(line) + if not m: + # Should not happen + sys.stderr.write('WARNING: Missing value for string ID 0x%08x "%s"' % + (current_resource_id, current_resource_name)) + resource_value = '' % current_resource_id + else: + resource_value = UnquoteString(m.group(1)) + + res_map.AddValue(current_resource_id, current_resource_name, + 'config %s' % current_locale, resource_value) + need_value = False + else: + m = _RE_AAPT_STRING_RESOURCE_HEADER.match(line) + if m: + current_resource_id = int(m.group(1), 16) + current_resource_name = m.group(2) + need_value = True + + return res_map + + +def main(args): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + 'input_file', + help='Input file path. This can be either an APK, or an app bundle.') + parser.add_argument('--output', help='Optional output file path.') + parser.add_argument( + '--omit-ids', + action='store_true', + help='Omit resource IDs in the output. This is useful ' + 'to compare the contents of two distinct builds of the ' + 'same APK.') + parser.add_argument( + '--aapt-path', + default=_AAPT_DEFAULT_PATH, + help='Path to aapt executable. Optional for APKs.') + parser.add_argument( + '--r-txt-path', + help='Path to an optional input R.txt file used to translate resource ' + 'IDs to string names. Useful when resources names in the input files ' + 'were obfuscated. NOTE: If ${INPUT_FILE}.R.txt exists, if will be used ' + 'automatically by this script.') + parser.add_argument( + '--bundletool-path', + default=_DEFAULT_BUNDLETOOL_PATH, + help='Path to alternate bundletool .jar file. Only used for bundles.') + parser.add_argument( + '--profile', action='store_true', help='Enable Python profiling.') + + options = parser.parse_args(args) + + # Create a {res_id -> res_name} map for unobfuscation, if needed. + res_id_name_map = {} + r_txt_path = options.r_txt_path + if not r_txt_path: + candidate_r_txt_path = options.input_file + '.R.txt' + if os.path.exists(candidate_r_txt_path): + r_txt_path = candidate_r_txt_path + + if r_txt_path: + res_id_name_map = ReadStringMapFromRTxt(r_txt_path) + + # Create a helper lambda that creates a new ResourceStringMap instance + # based on the input file's type. + if IsFilePathABundle(options.input_file): + if not options.bundletool_path: + parser.error( + '--bundletool-path is required to parse bundles.') + + # use bundletool to parse the bundle resources. + def create_string_map(): + return ParseBundleResources(options.bundletool_path, options.input_file) + + elif IsFilePathAnApk(options.input_file): + if not options.aapt_path: + parser.error('--aapt-path is required to parse APKs.') + + # Use aapt dump to parse the APK resources. + def create_string_map(): + return ParseApkResources(options.aapt_path, options.input_file) + + else: + parser.error('Unknown file format: %s' % options.input_file) + + # Print everything now. + with ManagedOutput(options.output) as output: + with ManagedPythonProfiling(options.profile): + res_map = create_string_map() + res_map.RemapResourceNames(res_id_name_map) + lines = AutoIndentStringList(res_map.ToStringList(options.omit_ids)) + for line in lines: + output.write(line) + output.write('\n') + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/emma_coverage_stats.py b/third_party/libwebrtc/build/android/emma_coverage_stats.py new file mode 100755 index 0000000000..9ea8baa321 --- /dev/null +++ b/third_party/libwebrtc/build/android/emma_coverage_stats.py @@ -0,0 +1,483 @@ +#!/usr/bin/env vpython3 +# 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 incremental code coverage reports for Java code in Chromium. + +Usage: + + build/android/emma_coverage_stats.py -v --out --emma-dir + --lines-for-coverage-file + + + Creates a JSON representation of the overall and file coverage stats and saves + this information to the specified output file. +""" + +import argparse +import collections +import json +import logging +import os +import re +import sys +from xml.etree import ElementTree + +import devil_chromium +from devil.utils import run_tests_helper + +NOT_EXECUTABLE = -1 +NOT_COVERED = 0 +COVERED = 1 +PARTIALLY_COVERED = 2 + +# Coverage information about a single line of code. +LineCoverage = collections.namedtuple( + 'LineCoverage', + ['lineno', 'source', 'covered_status', 'fractional_line_coverage']) + + +class _EmmaHtmlParser(object): + """Encapsulates HTML file parsing operations. + + This class contains all operations related to parsing HTML files that were + produced using the EMMA code coverage tool. + + Example HTML: + + Package links: + org.chromium.chrome + This is returned by the selector |XPATH_SELECT_PACKAGE_ELEMENTS|. + + Class links: + DoActivity.java + This is returned by the selector |XPATH_SELECT_CLASS_ELEMENTS|. + + Line coverage data: + + 108 + + if (index < 0 || index = mSelectors.size()) index = 0; + + + 109 + + + + 110 + if (mSelectors.get(index) != null) { + + + 111 + for (int i = 0; i < mSelectors.size(); i++) { + + Each element is returned by the selector |XPATH_SELECT_LOC|. + + We can parse this to get: + 1. Line number + 2. Line of source code + 3. Coverage status (c, z, or p) + 4. Fractional coverage value (% out of 100 if PARTIALLY_COVERED) + """ + # Selector to match all elements within the rows that are in the table + # that displays all of the different packages. + _XPATH_SELECT_PACKAGE_ELEMENTS = './/BODY/TABLE[4]/TR/TD/A' + + # Selector to match all elements within the rows that are in the table + # that displays all of the different classes within a package. + _XPATH_SELECT_CLASS_ELEMENTS = './/BODY/TABLE[3]/TR/TD/A' + + # Selector to match all elements within the table containing Java source + # code in an EMMA HTML file. + _XPATH_SELECT_LOC = './/BODY/TABLE[4]/TR' + + # Children of HTML elements are represented as a list in ElementTree. These + # constants represent list indices corresponding to relevant child elements. + + # Child 1 contains percentage covered for a line. + _ELEMENT_PERCENT_COVERED = 1 + + # Child 1 contains the original line of source code. + _ELEMENT_CONTAINING_SOURCE_CODE = 1 + + # Child 0 contains the line number. + _ELEMENT_CONTAINING_LINENO = 0 + + # Maps CSS class names to corresponding coverage constants. + _CSS_TO_STATUS = {'c': COVERED, 'p': PARTIALLY_COVERED, 'z': NOT_COVERED} + + # UTF-8 no break space. + _NO_BREAK_SPACE = '\xc2\xa0' + + def __init__(self, emma_file_base_dir): + """Initializes _EmmaHtmlParser. + + Args: + emma_file_base_dir: Path to the location where EMMA report files are + stored. Should be where index.html is stored. + """ + self._base_dir = emma_file_base_dir + self._emma_files_path = os.path.join(self._base_dir, '_files') + self._index_path = os.path.join(self._base_dir, 'index.html') + + def GetLineCoverage(self, emma_file_path): + """Returns a list of LineCoverage objects for the given EMMA HTML file. + + Args: + emma_file_path: String representing the path to the EMMA HTML file. + + Returns: + A list of LineCoverage objects. + """ + line_tr_elements = self._FindElements( + emma_file_path, self._XPATH_SELECT_LOC) + line_coverage = [] + for tr in line_tr_elements: + # Get the coverage status. + coverage_status = self._CSS_TO_STATUS.get(tr.get('CLASS'), NOT_EXECUTABLE) + # Get the fractional coverage value. + if coverage_status == PARTIALLY_COVERED: + title_attribute = (tr[self._ELEMENT_PERCENT_COVERED].get('TITLE')) + # Parse string that contains percent covered: "83% line coverage ...". + percent_covered = title_attribute.split('%')[0] + fractional_coverage = int(percent_covered) / 100.0 + else: + fractional_coverage = 1.0 + + # Get the line number. + lineno_element = tr[self._ELEMENT_CONTAINING_LINENO] + # Handles oddly formatted HTML (where there is an extra tag). + lineno = int(lineno_element.text or + lineno_element[self._ELEMENT_CONTAINING_LINENO].text) + # Get the original line of Java source code. + raw_source = tr[self._ELEMENT_CONTAINING_SOURCE_CODE].text + source = raw_source.replace(self._NO_BREAK_SPACE, ' ') + + line = LineCoverage(lineno, source, coverage_status, fractional_coverage) + line_coverage.append(line) + + return line_coverage + + def GetPackageNameToEmmaFileDict(self): + """Returns a dict mapping Java packages to EMMA HTML coverage files. + + Parses the EMMA index.html file to get a list of packages, then parses each + package HTML file to get a list of classes for that package, and creates + a dict with this info. + + Returns: + A dict mapping string representation of Java packages (with class + names appended) to the corresponding file paths of EMMA HTML files. + """ + # These elements contain each package name and the path of the file + # where all classes within said package are listed. + package_link_elements = self._FindElements( + self._index_path, self._XPATH_SELECT_PACKAGE_ELEMENTS) + # Maps file path of package directory (EMMA generated) to package name. + # Example: emma_dir/f.html: org.chromium.chrome. + package_links = { + os.path.join(self._base_dir, link.attrib['HREF']): link.text + for link in package_link_elements if 'HREF' in link.attrib + } + + package_to_emma = {} + for package_emma_file_path, package_name in package_links.items(): + # These elements contain each class name in the current package and + # the path of the file where the coverage info is stored for each class. + coverage_file_link_elements = self._FindElements( + package_emma_file_path, self._XPATH_SELECT_CLASS_ELEMENTS) + + for class_name_element in coverage_file_link_elements: + emma_coverage_file_path = os.path.join( + self._emma_files_path, class_name_element.attrib['HREF']) + full_package_name = '%s.%s' % (package_name, class_name_element.text) + package_to_emma[full_package_name] = emma_coverage_file_path + + return package_to_emma + + # pylint: disable=no-self-use + def _FindElements(self, file_path, xpath_selector): + """Reads a HTML file and performs an XPath match. + + Args: + file_path: String representing the path to the HTML file. + xpath_selector: String representing xpath search pattern. + + Returns: + A list of ElementTree.Elements matching the given XPath selector. + Returns an empty list if there is no match. + """ + with open(file_path) as f: + file_contents = f.read() + root = ElementTree.fromstring(file_contents) + return root.findall(xpath_selector) + + +class _EmmaCoverageStats(object): + """Computes code coverage stats for Java code using the coverage tool EMMA. + + This class provides an API that allows users to capture absolute code coverage + and code coverage on a subset of lines for each Java source file. Coverage + reports are generated in JSON format. + """ + # Regular expression to get package name from Java package statement. + RE_PACKAGE_MATCH_GROUP = 'package' + RE_PACKAGE = re.compile(r'package (?P<%s>[\w.]*);' % RE_PACKAGE_MATCH_GROUP) + + def __init__(self, emma_file_base_dir, files_for_coverage): + """Initialize _EmmaCoverageStats. + + Args: + emma_file_base_dir: String representing the path to the base directory + where EMMA HTML coverage files are stored, i.e. parent of index.html. + files_for_coverage: A list of Java source code file paths to get EMMA + coverage for. + """ + self._emma_parser = _EmmaHtmlParser(emma_file_base_dir) + self._source_to_emma = self._GetSourceFileToEmmaFileDict(files_for_coverage) + + def GetCoverageDict(self, lines_for_coverage): + """Returns a dict containing detailed coverage information. + + Gets detailed coverage stats for each file specified in the + |lines_for_coverage| dict and the total incremental number of lines covered + and executable for all files in |lines_for_coverage|. + + Args: + lines_for_coverage: A dict mapping Java source file paths to lists of line + numbers. + + Returns: + A dict containing coverage stats for the given dict of files and lines. + Contains absolute coverage stats for each file, coverage stats for each + file's lines specified in |lines_for_coverage|, line by line coverage + for each file, and overall coverage stats for the lines specified in + |lines_for_coverage|. + """ + file_coverage = {} + for file_path, line_numbers in lines_for_coverage.items(): + file_coverage_dict = self.GetCoverageDictForFile(file_path, line_numbers) + if file_coverage_dict: + file_coverage[file_path] = file_coverage_dict + else: + logging.warning( + 'No code coverage data for %s, skipping.', file_path) + + covered_statuses = [s['incremental'] for s in file_coverage.values()] + num_covered_lines = sum(s['covered'] for s in covered_statuses) + num_total_lines = sum(s['total'] for s in covered_statuses) + return { + 'files': file_coverage, + 'patch': { + 'incremental': { + 'covered': num_covered_lines, + 'total': num_total_lines + } + } + } + + def GetCoverageDictForFile(self, file_path, line_numbers): + """Returns a dict containing detailed coverage info for the given file. + + Args: + file_path: The path to the Java source file that we want to create the + coverage dict for. + line_numbers: A list of integer line numbers to retrieve additional stats + for. + + Returns: + A dict containing absolute, incremental, and line by line coverage for + a file. + """ + if file_path not in self._source_to_emma: + return None + emma_file = self._source_to_emma[file_path] + total_line_coverage = self._emma_parser.GetLineCoverage(emma_file) + incremental_line_coverage = [line for line in total_line_coverage + if line.lineno in line_numbers] + line_by_line_coverage = [ + { + 'line': line.source, + 'coverage': line.covered_status, + 'changed': line.lineno in line_numbers, + 'fractional_coverage': line.fractional_line_coverage, + } + for line in total_line_coverage + ] + total_covered_lines, total_lines = ( + self.GetSummaryStatsForLines(total_line_coverage)) + incremental_covered_lines, incremental_total_lines = ( + self.GetSummaryStatsForLines(incremental_line_coverage)) + + file_coverage_stats = { + 'absolute': { + 'covered': total_covered_lines, + 'total': total_lines + }, + 'incremental': { + 'covered': incremental_covered_lines, + 'total': incremental_total_lines + }, + 'source': line_by_line_coverage, + } + return file_coverage_stats + + # pylint: disable=no-self-use + def GetSummaryStatsForLines(self, line_coverage): + """Gets summary stats for a given list of LineCoverage objects. + + Args: + line_coverage: A list of LineCoverage objects. + + Returns: + A tuple containing the number of lines that are covered and the total + number of lines that are executable, respectively + """ + partially_covered_sum = 0 + covered_status_totals = {COVERED: 0, NOT_COVERED: 0, PARTIALLY_COVERED: 0} + for line in line_coverage: + status = line.covered_status + if status == NOT_EXECUTABLE: + continue + covered_status_totals[status] += 1 + if status == PARTIALLY_COVERED: + partially_covered_sum += line.fractional_line_coverage + + total_covered = covered_status_totals[COVERED] + partially_covered_sum + total_lines = sum(covered_status_totals.values()) + return total_covered, total_lines + + def _GetSourceFileToEmmaFileDict(self, files): + """Gets a dict used to correlate Java source files with EMMA HTML files. + + This method gathers the information needed to correlate EMMA HTML + files with Java source files. EMMA XML and plain text reports do not provide + line by line coverage data, so HTML reports must be used instead. + Unfortunately, the HTML files that are created are given garbage names + (i.e 1.html) so we need to manually correlate EMMA HTML files + with the original Java source files. + + Args: + files: A list of file names for which coverage information is desired. + + Returns: + A dict mapping Java source file paths to EMMA HTML file paths. + """ + # Maps Java source file paths to package names. + # Example: /usr/code/file.java -> org.chromium.file.java. + source_to_package = {} + for file_path in files: + package = self.GetPackageNameFromFile(file_path) + if package: + source_to_package[file_path] = package + else: + logging.warning("Skipping %s because it doesn\'t have a package " + "statement.", file_path) + + # Maps package names to EMMA report HTML files. + # Example: org.chromium.file.java -> out/coverage/1a.html. + package_to_emma = self._emma_parser.GetPackageNameToEmmaFileDict() + # Finally, we have a dict mapping Java file paths to EMMA report files. + # Example: /usr/code/file.java -> out/coverage/1a.html. + source_to_emma = { + source: package_to_emma[package] + for source, package in source_to_package.items() + if package in package_to_emma + } + return source_to_emma + + @staticmethod + def NeedsCoverage(file_path): + """Checks to see if the file needs to be analyzed for code coverage. + + Args: + file_path: A string representing path to the file. + + Returns: + True for Java files that exist, False for all others. + """ + if os.path.splitext(file_path)[1] == '.java' and os.path.exists(file_path): + return True + else: + logging.info('Skipping file %s, cannot compute code coverage.', file_path) + return False + + @staticmethod + def GetPackageNameFromFile(file_path): + """Gets the full package name including the file name for a given file path. + + Args: + file_path: String representing the path to the Java source file. + + Returns: + A string representing the full package name with file name appended or + None if there is no package statement in the file. + """ + with open(file_path) as f: + file_content = f.read() + package_match = re.search(_EmmaCoverageStats.RE_PACKAGE, file_content) + if package_match: + package = package_match.group(_EmmaCoverageStats.RE_PACKAGE_MATCH_GROUP) + file_name = os.path.basename(file_path) + return '%s.%s' % (package, file_name) + else: + return None + + +def GenerateCoverageReport(line_coverage_file, out_file_path, coverage_dir): + """Generates a coverage report for a given set of lines. + + Writes the results of the coverage analysis to the file specified by + |out_file_path|. + + Args: + line_coverage_file: The path to a file which contains a dict mapping file + names to lists of line numbers. Example: {file1: [1, 2, 3], ...} means + that we should compute coverage information on lines 1 - 3 for file1. + out_file_path: A string representing the location to write the JSON report. + coverage_dir: A string representing the file path where the EMMA + HTML coverage files are located (i.e. folder where index.html is located). + """ + with open(line_coverage_file) as f: + potential_files_for_coverage = json.load(f) + + files_for_coverage = { + f: lines + for f, lines in potential_files_for_coverage.items() + if _EmmaCoverageStats.NeedsCoverage(f) + } + + coverage_results = {} + if files_for_coverage: + code_coverage = _EmmaCoverageStats(coverage_dir, + list(files_for_coverage.keys())) + coverage_results = code_coverage.GetCoverageDict(files_for_coverage) + else: + logging.info('No Java files requiring coverage were included in %s.', + line_coverage_file) + + with open(out_file_path, 'w+') as out_status_file: + json.dump(coverage_results, out_status_file) + + +def main(): + argparser = argparse.ArgumentParser() + argparser.add_argument('--out', required=True, type=str, + help='Report output file path.') + argparser.add_argument('--emma-dir', required=True, type=str, + help='EMMA HTML report directory.') + argparser.add_argument('--lines-for-coverage-file', required=True, type=str, + help='File containing a JSON object. Should contain a ' + 'dict mapping file names to lists of line numbers of ' + 'code for which coverage information is desired.') + argparser.add_argument('-v', '--verbose', action='count', + help='Print verbose log information.') + args = argparser.parse_args() + run_tests_helper.SetLogLevel(args.verbose) + devil_chromium.Initialize() + GenerateCoverageReport(args.lines_for_coverage_file, args.out, args.emma_dir) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/emma_coverage_stats_test.py b/third_party/libwebrtc/build/android/emma_coverage_stats_test.py new file mode 100755 index 0000000000..94d2c5c0a9 --- /dev/null +++ b/third_party/libwebrtc/build/android/emma_coverage_stats_test.py @@ -0,0 +1,593 @@ +#!/usr/bin/env vpython3 +# 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=protected-access + +import sys +import unittest +from xml.etree import ElementTree + +import emma_coverage_stats + +import mock # pylint: disable=import-error + +EMPTY_COVERAGE_STATS_DICT = { + 'files': {}, + 'patch': { + 'incremental': { + 'covered': 0, 'total': 0 + } + } +} + + +class _EmmaHtmlParserTest(unittest.TestCase): + """Tests for _EmmaHtmlParser. + + Uses modified EMMA report HTML that contains only the subset of tags needed + for test verification. + """ + + def setUp(self): + self.emma_dir = 'fake/dir/' + self.parser = emma_coverage_stats._EmmaHtmlParser(self.emma_dir) + self.simple_html = 'Test HTML' + self.index_html = ( + '' + '' + '' + '
' + '' + '
' + '' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
nameclass, %method, %block, %line, %
org.chromium.chrome.browser0% (0/3)
org.chromium.chrome.browser.tabmodel0% (0/8)
' + '' + '
' + '' + '' + ) + self.package_1_class_list_html = ( + '' + '' + '' + '
' + '' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
nameclass, %method, %block, %line, %
IntentHelper.java0% (0/3)0% (0/9)0% (0/97)0% (0/26)
' + '' + '
' + '' + '' + ) + self.package_2_class_list_html = ( + '' + '' + '' + '
' + '' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
nameclass, %method, %block, %line, %
ContentSetting.java0% (0/1)
DevToolsServer.java
FileProviderHelper.java
ContextualMenuBar.java
AccessibilityUtil.java
NavigationPopup.java
' + '' + '
' + '' + '' + ) + self.partially_covered_tr_html = ( + '' + '108' + '' + 'if (index < 0 || index = mSelectors.size()) index = 0;' + '' + ) + self.covered_tr_html = ( + '' + '110' + ' if (mSelectors.get(index) != null) {' + '' + ) + self.not_executable_tr_html = ( + '' + '109' + ' ' + '' + ) + self.tr_with_extra_a_tag = ( + '' + '' + '
54' + '' + ' }' + '' + ) + + def testInit(self): + emma_dir = self.emma_dir + parser = emma_coverage_stats._EmmaHtmlParser(emma_dir) + self.assertEqual(parser._base_dir, emma_dir) + self.assertEqual(parser._emma_files_path, 'fake/dir/_files') + self.assertEqual(parser._index_path, 'fake/dir/index.html') + + def testFindElements_basic(self): + read_values = [self.simple_html] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TD') + self.assertIs(type(found), list) + self.assertIs(type(found[0]), ElementTree.Element) + self.assertEqual(found[0].text, 'Test HTML') + + def testFindElements_multipleElements(self): + multiple_trs = self.not_executable_tr_html + self.covered_tr_html + read_values = ['
' + multiple_trs + '
'] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TR') + self.assertEqual(2, len(found)) + + def testFindElements_noMatch(self): + read_values = [self.simple_html] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TR') + self.assertEqual(found, []) + + def testFindElements_badFilePath(self): + with self.assertRaises(IOError): + with mock.patch('os.path.exists', return_value=False): + self.parser._FindElements('fake', xpath_selector='//tr') + + def testGetPackageNameToEmmaFileDict_basic(self): + if sys.version_info.major < 3: + expected_dict = { + 'org.chromium.chrome.browser.AccessibilityUtil.java': + 'fake/dir/_files/23.html', + 'org.chromium.chrome.browser.ContextualMenuBar.java': + 'fake/dir/_files/22.html', + 'org.chromium.chrome.browser.tabmodel.IntentHelper.java': + 'fake/dir/_files/1e.html', + 'org.chromium.chrome.browser.ContentSetting.java': + 'fake/dir/_files/1f.html', + 'org.chromium.chrome.browser.DevToolsServer.java': + 'fake/dir/_files/20.html', + 'org.chromium.chrome.browser.NavigationPopup.java': + 'fake/dir/_files/24.html', + 'org.chromium.chrome.browser.FileProviderHelper.java': + 'fake/dir/_files/21.html' + } + else: + expected_dict = { + 'org.chromium.chrome.browser.IntentHelper.java': + 'fake/dir/_files/1e.html', + 'org.chromium.chrome.browser.tabmodel.AccessibilityUtil.java': + 'fake/dir/_files/23.html', + 'org.chromium.chrome.browser.tabmodel.ContextualMenuBar.java': + 'fake/dir/_files/22.html', + 'org.chromium.chrome.browser.tabmodel.ContentSetting.java': + 'fake/dir/_files/1f.html', + 'org.chromium.chrome.browser.tabmodel.DevToolsServer.java': + 'fake/dir/_files/20.html', + 'org.chromium.chrome.browser.tabmodel.NavigationPopup.java': + 'fake/dir/_files/24.html', + 'org.chromium.chrome.browser.tabmodel.FileProviderHelper.java': + 'fake/dir/_files/21.html' + } + read_values = [self.index_html, self.package_1_class_list_html, + self.package_2_class_list_html] + return_dict, mock_open = MockOpenForFunction( + self.parser.GetPackageNameToEmmaFileDict, read_values) + + self.assertDictEqual(return_dict, expected_dict) + self.assertEqual(mock_open.call_count, 3) + if sys.version_info.major < 3: + calls = [ + mock.call('fake/dir/index.html'), + mock.call('fake/dir/_files/1.html'), + mock.call('fake/dir/_files/0.html') + ] + else: + calls = [ + mock.call('fake/dir/index.html'), + mock.call('fake/dir/_files/0.html'), + mock.call('fake/dir/_files/1.html') + ] + mock_open.assert_has_calls(calls) + + def testGetPackageNameToEmmaFileDict_noPackageElements(self): + self.parser._FindElements = mock.Mock(return_value=[]) + return_dict = self.parser.GetPackageNameToEmmaFileDict() + self.assertDictEqual({}, return_dict) + + def testGetLineCoverage_status_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].covered_status, + emma_coverage_stats.COVERED) + + def testGetLineCoverage_status_statusMissing(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.not_executable_tr_html]) + self.assertEqual(line_coverage[0].covered_status, + emma_coverage_stats.NOT_EXECUTABLE) + + def testGetLineCoverage_fractionalCoverage_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].fractional_line_coverage, 1.0) + + def testGetLineCoverage_fractionalCoverage_partial(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.partially_covered_tr_html]) + self.assertEqual(line_coverage[0].fractional_line_coverage, 0.78) + + def testGetLineCoverage_lineno_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].lineno, 110) + + def testGetLineCoverage_lineno_withAlternativeHtml(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.tr_with_extra_a_tag]) + self.assertEqual(line_coverage[0].lineno, 54) + + def testGetLineCoverage_source(self): + self.parser._FindElements = mock.Mock( + return_value=[ElementTree.fromstring(self.covered_tr_html)]) + line_coverage = self.parser.GetLineCoverage('fake_path') + self.assertEqual(line_coverage[0].source, + ' if (mSelectors.get(index) != null) {') + + def testGetLineCoverage_multipleElements(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.covered_tr_html, self.partially_covered_tr_html, + self.tr_with_extra_a_tag]) + self.assertEqual(len(line_coverage), 3) + + def GetLineCoverageWithFakeElements(self, html_elements): + """Wraps GetLineCoverage so mock HTML can easily be used. + + Args: + html_elements: List of strings each representing an HTML element. + + Returns: + A list of LineCoverage objects. + """ + elements = [ElementTree.fromstring(string) for string in html_elements] + with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', + return_value=elements): + return self.parser.GetLineCoverage('fake_path') + + +class _EmmaCoverageStatsTest(unittest.TestCase): + """Tests for _EmmaCoverageStats.""" + + def setUp(self): + self.good_source_to_emma = { + '/path/to/1/File1.java': '/emma/1.html', + '/path/2/File2.java': '/emma/2.html', + '/path/2/File3.java': '/emma/3.html' + } + self.line_coverage = [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), + emma_coverage_stats.LineCoverage( + 4, '', emma_coverage_stats.NOT_COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 5, '', emma_coverage_stats.PARTIALLY_COVERED, 0.85), + emma_coverage_stats.LineCoverage( + 6, '', emma_coverage_stats.PARTIALLY_COVERED, 0.20) + ] + self.lines_for_coverage = [1, 3, 5, 6] + with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', + return_value=[]): + self.simple_coverage = emma_coverage_stats._EmmaCoverageStats( + 'fake_dir', {}) + + def testInit(self): + coverage_stats = self.simple_coverage + self.assertIsInstance(coverage_stats._emma_parser, + emma_coverage_stats._EmmaHtmlParser) + self.assertIsInstance(coverage_stats._source_to_emma, dict) + + def testNeedsCoverage_withExistingJavaFile(self): + test_file = '/path/to/file/File.java' + with mock.patch('os.path.exists', return_value=True): + self.assertTrue( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testNeedsCoverage_withNonJavaFile(self): + test_file = '/path/to/file/File.c' + with mock.patch('os.path.exists', return_value=True): + self.assertFalse( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testNeedsCoverage_fileDoesNotExist(self): + test_file = '/path/to/file/File.java' + with mock.patch('os.path.exists', return_value=False): + self.assertFalse( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testGetPackageNameFromFile_basic(self): + test_file_text = """// Test Copyright + package org.chromium.chrome.browser; + import android.graphics.RectF;""" + result_package, _ = MockOpenForFunction( + emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, + [test_file_text], file_path='/path/to/file/File.java') + self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java') + + def testGetPackageNameFromFile_noPackageStatement(self): + result_package, _ = MockOpenForFunction( + emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, + ['not a package statement'], file_path='/path/to/file/File.java') + self.assertIsNone(result_package) + + def testGetSummaryStatsForLines_basic(self): + covered, total = self.simple_coverage.GetSummaryStatsForLines( + self.line_coverage) + self.assertEqual(covered, 3.05) + self.assertEqual(total, 5) + + def testGetSourceFileToEmmaFileDict(self): + package_names = { + '/path/to/1/File1.java': 'org.fake.one.File1.java', + '/path/2/File2.java': 'org.fake.File2.java', + '/path/2/File3.java': 'org.fake.File3.java' + } + package_to_emma = { + 'org.fake.one.File1.java': '/emma/1.html', + 'org.fake.File2.java': '/emma/2.html', + 'org.fake.File3.java': '/emma/3.html' + } + with mock.patch('os.path.exists', return_value=True): + coverage_stats = self.simple_coverage + coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock( + return_value=package_to_emma) + coverage_stats.GetPackageNameFromFile = lambda x: package_names[x] + result_dict = coverage_stats._GetSourceFileToEmmaFileDict( + list(package_names.keys())) + self.assertDictEqual(result_dict, self.good_source_to_emma) + + def testGetCoverageDictForFile(self): + line_coverage = self.line_coverage + self.simple_coverage._emma_parser.GetLineCoverage = lambda x: line_coverage + self.simple_coverage._source_to_emma = {'/fake/src': 'fake/emma'} + lines = self.lines_for_coverage + expected_dict = { + 'absolute': { + 'covered': 3.05, + 'total': 5 + }, + 'incremental': { + 'covered': 2.05, + 'total': 3 + }, + 'source': [ + { + 'line': line_coverage[0].source, + 'coverage': line_coverage[0].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[0].fractional_line_coverage, + }, + { + 'line': line_coverage[1].source, + 'coverage': line_coverage[1].covered_status, + 'changed': False, + 'fractional_coverage': line_coverage[1].fractional_line_coverage, + }, + { + 'line': line_coverage[2].source, + 'coverage': line_coverage[2].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[2].fractional_line_coverage, + }, + { + 'line': line_coverage[3].source, + 'coverage': line_coverage[3].covered_status, + 'changed': False, + 'fractional_coverage': line_coverage[3].fractional_line_coverage, + }, + { + 'line': line_coverage[4].source, + 'coverage': line_coverage[4].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[4].fractional_line_coverage, + }, + { + 'line': line_coverage[5].source, + 'coverage': line_coverage[5].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[5].fractional_line_coverage, + } + ] + } + result_dict = self.simple_coverage.GetCoverageDictForFile( + '/fake/src', lines) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDictForFile_emptyCoverage(self): + expected_dict = { + 'absolute': {'covered': 0, 'total': 0}, + 'incremental': {'covered': 0, 'total': 0}, + 'source': [] + } + self.simple_coverage._emma_parser.GetLineCoverage = lambda x: [] + self.simple_coverage._source_to_emma = {'fake_dir': 'fake/emma'} + result_dict = self.simple_coverage.GetCoverageDictForFile('fake_dir', {}) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDictForFile_missingCoverage(self): + self.simple_coverage._source_to_emma = {} + result_dict = self.simple_coverage.GetCoverageDictForFile('fake_file', {}) + self.assertIsNone(result_dict) + + def testGetCoverageDict_basic(self): + files_for_coverage = { + '/path/to/1/File1.java': [1, 3, 4], + '/path/2/File2.java': [1, 2] + } + self.simple_coverage._source_to_emma = { + '/path/to/1/File1.java': 'emma_1', + '/path/2/File2.java': 'emma_2' + } + coverage_info = { + 'emma_1': [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.PARTIALLY_COVERED, 0.5), + emma_coverage_stats.LineCoverage( + 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), + emma_coverage_stats.LineCoverage( + 4, '', emma_coverage_stats.COVERED, 1.0) + ], + 'emma_2': [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.NOT_COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.COVERED, 1.0) + ] + } + expected_dict = { + 'files': { + '/path/2/File2.java': { + 'absolute': {'covered': 1, 'total': 2}, + 'incremental': {'covered': 1, 'total': 2}, + 'source': [{'changed': True, 'coverage': 0, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}] + }, + '/path/to/1/File1.java': { + 'absolute': {'covered': 2.5, 'total': 3}, + 'incremental': {'covered': 2, 'total': 2}, + 'source': [{'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': False, 'coverage': 2, + 'line': '', 'fractional_coverage': 0.5}, + {'changed': True, 'coverage': -1, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}] + } + }, + 'patch': {'incremental': {'covered': 3, 'total': 4}} + } + # Return the relevant coverage info for each file. + self.simple_coverage._emma_parser.GetLineCoverage = ( + lambda x: coverage_info[x]) + result_dict = self.simple_coverage.GetCoverageDict(files_for_coverage) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDict_noCoverage(self): + result_dict = self.simple_coverage.GetCoverageDict({}) + self.assertDictEqual(result_dict, EMPTY_COVERAGE_STATS_DICT) + + +class EmmaCoverageStatsGenerateCoverageReport(unittest.TestCase): + """Tests for GenerateCoverageReport.""" + + def testGenerateCoverageReport_missingJsonFile(self): + with self.assertRaises(IOError): + with mock.patch('os.path.exists', return_value=False): + emma_coverage_stats.GenerateCoverageReport('', '', '') + + def testGenerateCoverageReport_invalidJsonFile(self): + with self.assertRaises(ValueError): + with mock.patch('os.path.exists', return_value=True): + MockOpenForFunction(emma_coverage_stats.GenerateCoverageReport, [''], + line_coverage_file='', out_file_path='', + coverage_dir='') + + +def MockOpenForFunction(func, side_effects, **kwargs): + """Allows easy mock open and read for callables that open multiple files. + + Will mock the python open function in a way such that each time read() is + called on an open file, the next element in |side_effects| is returned. This + makes it easier to test functions that call open() multiple times. + + Args: + func: The callable to invoke once mock files are setup. + side_effects: A list of return values for each file to return once read. + Length of list should be equal to the number calls to open in |func|. + **kwargs: Keyword arguments to be passed to |func|. + + Returns: + A tuple containing the return value of |func| and the MagicMock object used + to mock all calls to open respectively. + """ + mock_open = mock.mock_open() + mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value + for side_effect in side_effects] + if sys.version_info.major < 3: + open_builtin_path = '__builtin__.open' + else: + open_builtin_path = 'builtins.open' + with mock.patch(open_builtin_path, mock_open): + return func(**kwargs), mock_open + + +if __name__ == '__main__': + # Suppress logging messages. + unittest.main(buffer=True) diff --git a/third_party/libwebrtc/build/android/envsetup.sh b/third_party/libwebrtc/build/android/envsetup.sh new file mode 100755 index 0000000000..7f549d9cf7 --- /dev/null +++ b/third_party/libwebrtc/build/android/envsetup.sh @@ -0,0 +1,35 @@ +#!/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. + +# Adds Android SDK tools and related helpers to PATH, useful for development. +# Not used on bots, nor required for any commands to succeed. +# Use like: source build/android/envsetup.sh + +# Make sure we're being sourced. +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 CHROME_SRC="$(readlink -f "${SCRIPT_DIR}/../../")" + + # Some tools expect these environmental variables. + export ANDROID_SDK_ROOT="${CHROME_SRC}/third_party/android_sdk/public" + # ANDROID_HOME is deprecated, but generally means the same thing as + # ANDROID_SDK_ROOT and shouldn't hurt to set it. + export ANDROID_HOME="$ANDROID_SDK_ROOT" + + # Set up PATH to point to SDK-provided (and other) tools, such as 'adb'. + export PATH=${CHROME_SRC}/build/android:$PATH + export PATH=${ANDROID_SDK_ROOT}/tools/:$PATH + export PATH=${ANDROID_SDK_ROOT}/platform-tools:$PATH +} +# In zsh, $0 is the name of the file being sourced. +android_envsetup_main "${BASH_SOURCE:-$0}" +unset -f android_envsetup_main diff --git a/third_party/libwebrtc/build/android/fast_local_dev_server.py b/third_party/libwebrtc/build/android/fast_local_dev_server.py new file mode 100755 index 0000000000..a35c5007e4 --- /dev/null +++ b/third_party/libwebrtc/build/android/fast_local_dev_server.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 server to offload non-critical-path GN targets.""" + +from __future__ import annotations + +import argparse +import json +import os +import queue +import shutil +import socket +import subprocess +import sys +import threading +from typing import Callable, Dict, List, Optional, Tuple + +sys.path.append(os.path.join(os.path.dirname(__file__), 'gyp')) +from util import server_utils + + +def log(msg: str, *, end: str = ''): + # Shrink the message (leaving a 2-char prefix and use the rest of the room + # for the suffix) according to terminal size so it is always one line. + width = shutil.get_terminal_size().columns + prefix = f'[{TaskStats.prefix()}] ' + max_msg_width = width - len(prefix) + if len(msg) > max_msg_width: + length_to_show = max_msg_width - 5 # Account for ellipsis and header. + msg = f'{msg[:2]}...{msg[-length_to_show:]}' + # \r to return the carriage to the beginning of line. + # \033[K to replace the normal \n to erase until the end of the line. + # Avoid the default line ending so the next \r overwrites the same line just + # like ninja's output. + print(f'\r{prefix}{msg}\033[K', end=end, flush=True) + + +class TaskStats: + """Class to keep track of aggregate stats for all tasks across threads.""" + _num_processes = 0 + _completed_tasks = 0 + _total_tasks = 0 + _lock = threading.Lock() + + @classmethod + def no_running_processes(cls): + return cls._num_processes == 0 + + @classmethod + def add_task(cls): + # Only the main thread calls this, so there is no need for locking. + cls._total_tasks += 1 + + @classmethod + def add_process(cls): + with cls._lock: + cls._num_processes += 1 + + @classmethod + def remove_process(cls): + with cls._lock: + cls._num_processes -= 1 + + @classmethod + def complete_task(cls): + with cls._lock: + cls._completed_tasks += 1 + + @classmethod + def prefix(cls): + # Ninja's prefix is: [205 processes, 6/734 @ 6.5/s : 0.922s ] + # Time taken and task completion rate are not important for the build server + # since it is always running in the background and uses idle priority for + # its tasks. + with cls._lock: + word = 'process' if cls._num_processes == 1 else 'processes' + return (f'{cls._num_processes} {word}, ' + f'{cls._completed_tasks}/{cls._total_tasks}') + + +class TaskManager: + """Class to encapsulate a threadsafe queue and handle deactivating it.""" + + def __init__(self): + self._queue: queue.SimpleQueue[Task] = queue.SimpleQueue() + self._deactivated = False + + def add_task(self, task: Task): + assert not self._deactivated + TaskStats.add_task() + self._queue.put(task) + log(f'QUEUED {task.name}') + self._maybe_start_tasks() + + def deactivate(self): + self._deactivated = True + while not self._queue.empty(): + try: + task = self._queue.get_nowait() + except queue.Empty: + return + task.terminate() + + @staticmethod + def _num_running_processes(): + with open('/proc/stat') as f: + for line in f: + if line.startswith('procs_running'): + return int(line.rstrip().split()[1]) + assert False, 'Could not read /proc/stat' + + def _maybe_start_tasks(self): + if self._deactivated: + return + # Include load avg so that a small dip in the number of currently running + # processes will not cause new tasks to be started while the overall load is + # heavy. + cur_load = max(self._num_running_processes(), os.getloadavg()[0]) + num_started = 0 + # Always start a task if we don't have any running, so that all tasks are + # eventually finished. Try starting up tasks when the overall load is light. + # Limit to at most 2 new tasks to prevent ramping up too fast. There is a + # chance where multiple threads call _maybe_start_tasks and each gets to + # spawn up to 2 new tasks, but since the only downside is some build tasks + # get worked on earlier rather than later, it is not worth mitigating. + while num_started < 2 and (TaskStats.no_running_processes() + or num_started + cur_load < os.cpu_count()): + try: + next_task = self._queue.get_nowait() + except queue.Empty: + return + num_started += next_task.start(self._maybe_start_tasks) + + +# TODO(wnwen): Break this into Request (encapsulating what ninja sends) and Task +# when a Request starts to be run. This would eliminate ambiguity +# about when and whether _proc/_thread are initialized. +class Task: + """Class to represent one task and operations on it.""" + + def __init__(self, name: str, cwd: str, cmd: List[str], stamp_file: str): + self.name = name + self.cwd = cwd + self.cmd = cmd + self.stamp_file = stamp_file + self._terminated = False + self._lock = threading.Lock() + self._proc: Optional[subprocess.Popen] = None + self._thread: Optional[threading.Thread] = None + self._return_code: Optional[int] = None + + @property + def key(self): + return (self.cwd, self.name) + + def start(self, on_complete_callback: Callable[[], None]) -> int: + """Starts the task if it has not already been terminated. + + Returns the number of processes that have been started. This is called at + most once when the task is popped off the task queue.""" + + # The environment variable forces the script to actually run in order to + # avoid infinite recursion. + env = os.environ.copy() + env[server_utils.BUILD_SERVER_ENV_VARIABLE] = '1' + + with self._lock: + if self._terminated: + return 0 + # Use os.nice(19) to ensure the lowest priority (idle) for these analysis + # tasks since we want to avoid slowing down the actual build. + # TODO(wnwen): Use ionice to reduce resource consumption. + TaskStats.add_process() + log(f'STARTING {self.name}') + self._proc = subprocess.Popen( + self.cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=self.cwd, + env=env, + text=True, + preexec_fn=lambda: os.nice(19), + ) + self._thread = threading.Thread( + target=self._complete_when_process_finishes, + args=(on_complete_callback, )) + self._thread.start() + return 1 + + def terminate(self): + """Can be called multiple times to cancel and ignore the task's output.""" + + with self._lock: + if self._terminated: + return + self._terminated = True + # It is safe to access _proc and _thread outside of _lock since they are + # only changed by self.start holding _lock when self._terminate is false. + # Since we have just set self._terminate to true inside of _lock, we know + # that neither _proc nor _thread will be changed from this point onwards. + if self._proc: + self._proc.terminate() + self._proc.wait() + # Ensure that self._complete is called either by the thread or by us. + if self._thread: + self._thread.join() + else: + self._complete() + + def _complete_when_process_finishes(self, + on_complete_callback: Callable[[], None]): + assert self._proc + # We know Popen.communicate will return a str and not a byte since it is + # constructed with text=True. + stdout: str = self._proc.communicate()[0] + self._return_code = self._proc.returncode + TaskStats.remove_process() + self._complete(stdout) + on_complete_callback() + + def _complete(self, stdout: str = ''): + """Update the user and ninja after the task has run or been terminated. + + This method should only be run once per task. Avoid modifying the task so + that this method does not need locking.""" + + TaskStats.complete_task() + failed = False + if self._terminated: + log(f'TERMINATED {self.name}') + # Ignore stdout as it is now outdated. + failed = True + else: + log(f'FINISHED {self.name}') + if stdout or self._return_code != 0: + failed = True + # An extra new line is needed since we want to preserve the previous + # _log line. Use a single print so that it is threadsafe. + # TODO(wnwen): Improve stdout display by parsing over it and moving the + # actual error to the bottom. Otherwise long command lines + # in the Traceback section obscure the actual error(s). + print('\n' + '\n'.join([ + f'FAILED: {self.name}', + f'Return code: {self._return_code}', + ' '.join(self.cmd), + stdout, + ])) + + if failed: + # Force ninja to consider failed targets as dirty. + try: + os.unlink(os.path.join(self.cwd, self.stamp_file)) + except FileNotFoundError: + pass + else: + # Ninja will rebuild targets when their inputs change even if their stamp + # file has a later modified time. Thus we do not need to worry about the + # script being run by the build server updating the mtime incorrectly. + pass + + +def _listen_for_request_data(sock: socket.socket): + while True: + conn = sock.accept()[0] + received = [] + with conn: + while True: + data = conn.recv(4096) + if not data: + break + received.append(data) + if received: + yield json.loads(b''.join(received)) + + +def _process_requests(sock: socket.socket): + # Since dicts in python can contain anything, explicitly type tasks to help + # make static type checking more useful. + tasks: Dict[Tuple[str, str], Task] = {} + task_manager = TaskManager() + try: + for data in _listen_for_request_data(sock): + task = Task(name=data['name'], + cwd=data['cwd'], + cmd=data['cmd'], + stamp_file=data['stamp_file']) + existing_task = tasks.get(task.key) + if existing_task: + existing_task.terminate() + tasks[task.key] = task + task_manager.add_task(task) + except KeyboardInterrupt: + log('STOPPING SERVER...', end='\n') + # Gracefully shut down the task manager, terminating all queued tasks. + task_manager.deactivate() + # Terminate all currently running tasks. + for task in tasks.values(): + task.terminate() + log('STOPPED', end='\n') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.parse_args() + with socket.socket(socket.AF_UNIX) as sock: + sock.bind(server_utils.SOCKET_ADDRESS) + sock.listen() + _process_requests(sock) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/generate_jacoco_report.py b/third_party/libwebrtc/build/android/generate_jacoco_report.py new file mode 100755 index 0000000000..da4a38e514 --- /dev/null +++ b/third_party/libwebrtc/build/android/generate_jacoco_report.py @@ -0,0 +1,274 @@ +#!/usr/bin/env vpython3 + +# 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 Jacoco coverage files to produce output.""" + +from __future__ import print_function + +import argparse +import fnmatch +import json +import os +import sys + +import devil_chromium +from devil.utils import cmd_helper +from pylib.constants import host_paths + +# Source paths should be passed to Jacoco in a way that the relative file paths +# reflect the class package name. +_PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium'] + +# The sources_json_file is generated by jacoco_instr.py with source directories +# and input path to non-instrumented jars. +# e.g. +# 'source_dirs': [ +# "chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom", +# "chrome/android/java/src/org/chromium/chrome/browser/ui/system", +# ...] +# 'input_path': +# '$CHROMIUM_OUTPUT_DIR/\ +# obj/chrome/android/features/tab_ui/java__process_prebuilt-filtered.jar' + +_SOURCES_JSON_FILES_SUFFIX = '__jacoco_sources.json' + + +def _CreateClassfileArgs(class_files, report_type, include_substr=None): + """Returns a filtered list of files with classfile option. + + Args: + class_files: A list of class files. + report_type: A string indicating if device or host files are desired. + include_substr: A substring that must be present to include the file. + + Returns: + A list of files that don't use the suffix. + """ + # These should match the jar class files generated in internal_rules.gni + search_jar_suffix = '%s.filter.jar' % report_type + result_class_files = [] + for f in class_files: + include_file = False + if f.endswith(search_jar_suffix): + include_file = True + + # If include_substr is specified, remove files that don't have the + # required substring. + if include_file and include_substr and include_substr not in f: + include_file = False + if include_file: + result_class_files += ['--classfiles', f] + + return result_class_files + + +def _GenerateReportOutputArgs(args, class_files, report_type): + cmd = _CreateClassfileArgs(class_files, report_type, + args.include_substr_filter) + if args.format == 'html': + report_dir = os.path.join(args.output_dir, report_type) + if not os.path.exists(report_dir): + os.makedirs(report_dir) + cmd += ['--html', report_dir] + elif args.format == 'xml': + cmd += ['--xml', args.output_file] + elif args.format == 'csv': + cmd += ['--csv', args.output_file] + + return cmd + + +def _GetFilesWithSuffix(root_dir, suffix): + """Gets all files with a given suffix. + + Args: + root_dir: Directory in which to search for files. + suffix: Suffix to look for. + + Returns: + A list of absolute paths to files that match. + """ + files = [] + for root, _, filenames in os.walk(root_dir): + basenames = fnmatch.filter(filenames, '*' + suffix) + files.extend([os.path.join(root, basename) for basename in basenames]) + + return files + + +def _GetExecFiles(root_dir, exclude_substr=None): + """ Gets all .exec files + + Args: + root_dir: Root directory in which to search for files. + exclude_substr: Substring which should be absent in filename. If None, all + files are selected. + + Returns: + A list of absolute paths to .exec files + + """ + all_exec_files = _GetFilesWithSuffix(root_dir, ".exec") + valid_exec_files = [] + for exec_file in all_exec_files: + if not exclude_substr or exclude_substr not in exec_file: + valid_exec_files.append(exec_file) + return valid_exec_files + + +def _ParseArguments(parser): + """Parses the command line arguments. + + Args: + parser: ArgumentParser object. + + Returns: + The parsed arguments. + """ + parser.add_argument( + '--format', + required=True, + choices=['html', 'xml', 'csv'], + help='Output report format. Choose one from html, xml and csv.') + parser.add_argument( + '--device-or-host', + choices=['device', 'host'], + help='Selection on whether to use the device classpath files or the ' + 'host classpath files. Host would typically be used for junit tests ' + ' and device for tests that run on the device. Only used for xml and csv' + ' reports.') + parser.add_argument('--include-substr-filter', + help='Substring that must be included in classjars.', + type=str, + default='') + parser.add_argument('--output-dir', help='html report output directory.') + parser.add_argument('--output-file', + help='xml file to write device coverage results.') + parser.add_argument( + '--coverage-dir', + required=True, + help='Root of the directory in which to search for ' + 'coverage data (.exec) files.') + parser.add_argument('--exec-filename-excludes', + required=False, + help='Excludes .exec files which contain a particular ' + 'substring in their name') + parser.add_argument( + '--sources-json-dir', + help='Root of the directory in which to search for ' + '*__jacoco_sources.json files.') + parser.add_argument( + '--class-files', + nargs='+', + help='Location of Java non-instrumented class files. ' + 'Use non-instrumented jars instead of instrumented jars. ' + 'e.g. use chrome_java__process_prebuilt_(host/device)_filter.jar instead' + 'of chrome_java__process_prebuilt-instrumented.jar') + parser.add_argument( + '--sources', + nargs='+', + help='Location of the source files. ' + 'Specified source folders must be the direct parent of the folders ' + 'that define the Java packages.' + 'e.g. /chrome/android/java/src/') + parser.add_argument( + '--cleanup', + action='store_true', + help='If set, removes coverage files generated at ' + 'runtime.') + args = parser.parse_args() + + if args.format == 'html' and not args.output_dir: + parser.error('--output-dir needed for report.') + if args.format in ('csv', 'xml'): + if not args.output_file: + parser.error('--output-file needed for xml/csv reports.') + if not args.device_or_host and args.sources_json_dir: + parser.error('--device-or-host selection needed with --sources-json-dir') + if not (args.sources_json_dir or args.class_files): + parser.error('At least either --sources-json-dir or --class-files needed.') + return args + + +def main(): + parser = argparse.ArgumentParser() + args = _ParseArguments(parser) + + devil_chromium.Initialize() + + coverage_files = _GetExecFiles(args.coverage_dir, args.exec_filename_excludes) + if not coverage_files: + parser.error('No coverage file found under %s' % args.coverage_dir) + print('Found coverage files: %s' % str(coverage_files)) + + class_files = [] + source_dirs = [] + if args.sources_json_dir: + sources_json_files = _GetFilesWithSuffix(args.sources_json_dir, + _SOURCES_JSON_FILES_SUFFIX) + for f in sources_json_files: + with open(f, 'r') as json_file: + data = json.load(json_file) + class_files.extend(data['input_path']) + source_dirs.extend(data['source_dirs']) + + # Fix source directories as direct parent of Java packages. + fixed_source_dirs = set() + for path in source_dirs: + for partial in _PARTIAL_PACKAGE_NAMES: + if partial in path: + fixed_dir = os.path.join(host_paths.DIR_SOURCE_ROOT, + path[:path.index(partial)]) + fixed_source_dirs.add(fixed_dir) + break + + if args.class_files: + class_files += args.class_files + if args.sources: + fixed_source_dirs.update(args.sources) + + cmd = [ + 'java', '-jar', + os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', 'jacoco', 'lib', + 'jacococli.jar'), 'report' + ] + coverage_files + + for source in fixed_source_dirs: + cmd += ['--sourcefiles', source] + + if args.format == 'html': + # Both reports are generated for html as the cq bot generates an html + # report and we wouldn't know which one a developer needed. + device_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'device') + host_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'host') + + device_exit_code = cmd_helper.RunCmd(device_cmd) + host_exit_code = cmd_helper.RunCmd(host_cmd) + exit_code = device_exit_code or host_exit_code + else: + cmd = cmd + _GenerateReportOutputArgs(args, class_files, + args.device_or_host) + exit_code = cmd_helper.RunCmd(cmd) + + if args.cleanup: + for f in coverage_files: + os.remove(f) + + # Command tends to exit with status 0 when it actually failed. + if not exit_code: + if args.format == 'html': + if not os.path.isdir(args.output_dir) or not os.listdir(args.output_dir): + print('No report generated at %s' % args.output_dir) + exit_code = 1 + elif not os.path.isfile(args.output_file): + print('No device coverage report generated at %s' % args.output_file) + exit_code = 1 + + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gradle/AndroidManifest.xml b/third_party/libwebrtc/build/android/gradle/AndroidManifest.xml new file mode 100644 index 0000000000..f3e50e0c93 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/third_party/libwebrtc/build/android/gradle/OWNERS b/third_party/libwebrtc/build/android/gradle/OWNERS new file mode 100644 index 0000000000..a0e0826972 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/OWNERS @@ -0,0 +1,2 @@ +agrieve@chromium.org +wnwen@chromium.org diff --git a/third_party/libwebrtc/build/android/gradle/android.jinja b/third_party/libwebrtc/build/android/gradle/android.jinja new file mode 100644 index 0000000000..40d4506306 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/android.jinja @@ -0,0 +1,114 @@ +{# 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. #} +{% macro expand_sourceset(variables, prefix) %} +{% if variables is defined %} + {{ prefix }} { +{% if variables.android_manifest is defined %} + manifest.srcFile "{{ variables.android_manifest }}" +{% endif %} +{% if variables.java_dirs is defined %} + java.srcDirs = [ +{% for path in variables.java_dirs %} + "{{ path }}", +{% endfor %} + ] +{% endif %} +{% if variables.java_excludes is defined %} + java.filter.exclude([ +{% for path in variables.java_excludes %} + "{{ path }}", +{% endfor %} + ]) +{% endif %} +{% if variables.jni_libs is defined %} + jniLibs.srcDirs = [ +{% for path in variables.jni_libs %} + "{{ path }}", +{% endfor %} + ] +{% endif %} +{% if variables.res_dirs is defined %} + res.srcDirs = [ +{% for path in variables.res_dirs %} + "{{ path }}", +{% endfor %} + ] +{% endif %} + } +{% endif %} +{% endmacro %} +// Generated by //build/android/generate_gradle.py + +{% if template_type in ('android_library', 'android_junit') %} +apply plugin: "com.android.library" +{% elif template_type == 'android_apk' %} +apply plugin: "com.android.application" +{% endif %} + +android { + compileSdkVersion "{{ compile_sdk_version }}" + + defaultConfig { + vectorDrawables.useSupportLibrary = true + minSdkVersion 21 + targetSdkVersion {{ target_sdk_version }} + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + +{% if native is defined %} + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +{% endif %} + + sourceSets { +{% for name in ['main', 'test', 'androidTest', 'debug', 'release'] %} + {{ name }} { + aidl.srcDirs = [] + assets.srcDirs = [] + java.srcDirs = [] + jni.srcDirs = [] + renderscript.srcDirs = [] + res.srcDirs = [] + resources.srcDirs = [] + } +{% endfor %} + +{{ expand_sourceset(main, 'main') }} +{{ expand_sourceset(test, 'test') }} +{% if android_test is defined %} +{% for t in android_test %} +{{ expand_sourceset(t, 'androidTest') }} +{% endfor %} +{% endif %} + } +} + +{% include 'dependencies.jinja' %} + +afterEvaluate { + def tasksToDisable = tasks.findAll { + return (it.name.equals('generateDebugSources') // causes unwanted AndroidManifest.java + || it.name.equals('generateReleaseSources') + || it.name.endsWith('BuildConfig') // causes unwanted BuildConfig.java + || it.name.equals('preDebugAndroidTestBuild') +{% if not use_gradle_process_resources %} + || it.name.endsWith('Assets') + || it.name.endsWith('Resources') + || it.name.endsWith('ResValues') +{% endif %} + || it.name.endsWith('Aidl') + || it.name.endsWith('Renderscript') + || it.name.endsWith('Shaders')) + } + tasksToDisable.each { Task task -> + task.enabled = false + } +} diff --git a/third_party/libwebrtc/build/android/gradle/cmake.jinja b/third_party/libwebrtc/build/android/gradle/cmake.jinja new file mode 100644 index 0000000000..b7273880cf --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/cmake.jinja @@ -0,0 +1,25 @@ +{# Copyright 2018 The Chromium Authors. All rights reserved. #} +{# Use of this source code is governed by a BSD-style license that can be #} +{# found in the LICENSE file. #} +# Generated by //build/android/generate_gradle.py + +cmake_minimum_required(VERSION 3.4.1) + +project(chrome C CXX) + +{% if native.includes is defined %} +include_directories( +{% for path in native.includes %} + {{ path }} +{% endfor %} +) +{% endif %} + +# Android studio will index faster when adding all sources into one library. +{% if native.sources is defined %} +add_library("chromium" +{% for path in native.sources %} + {{ path }} +{% endfor %} +) +{% endif %} diff --git a/third_party/libwebrtc/build/android/gradle/dependencies.jinja b/third_party/libwebrtc/build/android/gradle/dependencies.jinja new file mode 100644 index 0000000000..87bc312853 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/dependencies.jinja @@ -0,0 +1,28 @@ +{# 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. #} +{% macro expand_deps(variables, prefix) %} +{% if variables is defined %} +{% if variables.prebuilts is defined %} +{% for path in variables.prebuilts %} + {{ prefix }} files("{{ path }}") +{% endfor %} +{% endif %} +{% if variables.java_project_deps is defined %} +{% for proj in variables.java_project_deps %} + {{ prefix }} project(":{{ proj }}") +{% endfor %} +{% endif %} +{% if variables.android_project_deps is defined %} +{% for proj in variables.android_project_deps %} + {{ prefix }} project(path: ":{{ proj }}") +{% endfor %} +{% endif %} +{% endif %} +{% endmacro %} + +dependencies { +{{ expand_deps(main, 'implementation') }} +{{ expand_deps(test, 'testImplementation') }} +{{ expand_deps(android_test, 'androidTestImplementation') }} +} diff --git a/third_party/libwebrtc/build/android/gradle/generate_gradle.py b/third_party/libwebrtc/build/android/gradle/generate_gradle.py new file mode 100755 index 0000000000..8a5c0abb8e --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/generate_gradle.py @@ -0,0 +1,930 @@ +#!/usr/bin/env vpython3 +# 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. + +"""Generates an Android Studio project from a GN target.""" + +import argparse +import codecs +import collections +import glob +import json +import logging +import os +import re +import shutil +import subprocess +import sys + +_BUILD_ANDROID = os.path.join(os.path.dirname(__file__), os.pardir) +sys.path.append(_BUILD_ANDROID) +import devil_chromium +from devil.utils import run_tests_helper +from pylib import constants +from pylib.constants import host_paths + +sys.path.append(os.path.join(_BUILD_ANDROID, 'gyp')) +import jinja_template +from util import build_utils +from util import resource_utils + +sys.path.append(os.path.dirname(_BUILD_ANDROID)) +import gn_helpers + +_DEPOT_TOOLS_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', + 'depot_tools') +_DEFAULT_ANDROID_MANIFEST_PATH = os.path.join( + host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gradle', + 'AndroidManifest.xml') +_FILE_DIR = os.path.dirname(__file__) +_GENERATED_JAVA_SUBDIR = 'generated_java' +_JNI_LIBS_SUBDIR = 'symlinked-libs' +_ARMEABI_SUBDIR = 'armeabi' +_GRADLE_BUILD_FILE = 'build.gradle' +_CMAKE_FILE = 'CMakeLists.txt' +# This needs to come first alphabetically among all modules. +_MODULE_ALL = '_all' +_SRC_INTERNAL = os.path.join( + os.path.dirname(host_paths.DIR_SOURCE_ROOT), 'src-internal') +_INSTRUMENTATION_TARGET_SUFFIX = '_test_apk__test_apk__apk' + +_DEFAULT_TARGETS = [ + '//android_webview/test/embedded_test_server:aw_net_test_support_apk', + '//android_webview/test:webview_instrumentation_apk', + '//android_webview/test:webview_instrumentation_test_apk', + '//base:base_junit_tests', + '//chrome/android:chrome_junit_tests', + '//chrome/android:chrome_public_apk', + '//chrome/android:chrome_public_test_apk', + '//chrome/android:chrome_public_unit_test_apk', + '//content/public/android:content_junit_tests', + '//content/shell/android:content_shell_apk', + # Below must be included even with --all since they are libraries. + '//base/android/jni_generator:jni_processor', + '//tools/android/errorprone_plugin:errorprone_plugin_java', +] + +_EXCLUDED_PREBUILT_JARS = [ + # Android Studio already provides Desugar runtime. + # Including it would cause linking error because of a duplicate class. + 'lib.java/third_party/bazel/desugar/Desugar-runtime.jar' +] + + +def _TemplatePath(name): + return os.path.join(_FILE_DIR, '{}.jinja'.format(name)) + + +def _RebasePath(path_or_list, new_cwd=None, old_cwd=None): + """Makes the given path(s) relative to new_cwd, or absolute if not specified. + + If new_cwd is not specified, absolute paths are returned. + If old_cwd is not specified, constants.GetOutDirectory() is assumed. + """ + if path_or_list is None: + return [] + if not isinstance(path_or_list, str): + return [_RebasePath(p, new_cwd, old_cwd) for p in path_or_list] + if old_cwd is None: + old_cwd = constants.GetOutDirectory() + old_cwd = os.path.abspath(old_cwd) + if new_cwd: + new_cwd = os.path.abspath(new_cwd) + return os.path.relpath(os.path.join(old_cwd, path_or_list), new_cwd) + return os.path.abspath(os.path.join(old_cwd, path_or_list)) + + +def _IsSubpathOf(child, parent): + """Returns whether |child| is a subpath of |parent|.""" + return not os.path.relpath(child, parent).startswith(os.pardir) + + +def _WriteFile(path, data): + """Writes |data| to |path|, constucting parent directories if necessary.""" + logging.info('Writing %s', path) + dirname = os.path.dirname(path) + if not os.path.exists(dirname): + os.makedirs(dirname) + with codecs.open(path, 'w', 'utf-8') as output_file: + output_file.write(data) + + +def _RunGnGen(output_dir, args=None): + cmd = [os.path.join(_DEPOT_TOOLS_PATH, 'gn'), 'gen', output_dir] + if args: + cmd.extend(args) + logging.info('Running: %r', cmd) + subprocess.check_call(cmd) + + +def _RunNinja(output_dir, args): + # Don't use version within _DEPOT_TOOLS_PATH, since most devs don't use + # that one when building. + cmd = ['autoninja', '-C', output_dir] + cmd.extend(args) + logging.info('Running: %r', cmd) + subprocess.check_call(cmd) + + +def _QueryForAllGnTargets(output_dir): + cmd = [ + os.path.join(_BUILD_ANDROID, 'list_java_targets.py'), '--gn-labels', + '--nested', '--build', '--output-directory', output_dir + ] + logging.info('Running: %r', cmd) + return subprocess.check_output(cmd, encoding='UTF-8').splitlines() + + +class _ProjectEntry(object): + """Helper class for project entries.""" + + _cached_entries = {} + + def __init__(self, gn_target): + # Use _ProjectEntry.FromGnTarget instead for caching. + self._gn_target = gn_target + self._build_config = None + self._java_files = None + self._all_entries = None + self.android_test_entries = [] + + @classmethod + def FromGnTarget(cls, gn_target): + assert gn_target.startswith('//'), gn_target + if ':' not in gn_target: + gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) + if gn_target not in cls._cached_entries: + cls._cached_entries[gn_target] = cls(gn_target) + return cls._cached_entries[gn_target] + + @classmethod + def FromBuildConfigPath(cls, path): + prefix = 'gen/' + suffix = '.build_config.json' + assert path.startswith(prefix) and path.endswith(suffix), path + subdir = path[len(prefix):-len(suffix)] + gn_target = '//%s:%s' % (os.path.split(subdir)) + return cls.FromGnTarget(gn_target) + + def __hash__(self): + return hash(self._gn_target) + + def __eq__(self, other): + return self._gn_target == other.GnTarget() + + def GnTarget(self): + return self._gn_target + + def NinjaTarget(self): + return self._gn_target[2:] + + def GradleSubdir(self): + """Returns the output subdirectory.""" + ninja_target = self.NinjaTarget() + # Support targets at the root level. e.g. //:foo + if ninja_target[0] == ':': + ninja_target = ninja_target[1:] + return ninja_target.replace(':', os.path.sep) + + def GeneratedJavaSubdir(self): + return _RebasePath( + os.path.join('gen', self.GradleSubdir(), _GENERATED_JAVA_SUBDIR)) + + def ProjectName(self): + """Returns the Gradle project name.""" + return self.GradleSubdir().replace(os.path.sep, '.') + + def BuildConfig(self): + """Reads and returns the project's .build_config.json JSON.""" + if not self._build_config: + path = os.path.join('gen', self.GradleSubdir() + '.build_config.json') + with open(_RebasePath(path)) as jsonfile: + self._build_config = json.load(jsonfile) + return self._build_config + + def DepsInfo(self): + return self.BuildConfig()['deps_info'] + + def Gradle(self): + return self.BuildConfig()['gradle'] + + def Javac(self): + return self.BuildConfig()['javac'] + + def GetType(self): + """Returns the target type from its .build_config.""" + return self.DepsInfo()['type'] + + def IsValid(self): + return self.GetType() in ( + 'android_apk', + 'android_app_bundle_module', + 'java_library', + "java_annotation_processor", + 'java_binary', + 'junit_binary', + ) + + def ResSources(self): + return self.DepsInfo().get('lint_resource_sources', []) + + def JavaFiles(self): + if self._java_files is None: + java_sources_file = self.DepsInfo().get('java_sources_file') + java_files = [] + if java_sources_file: + java_sources_file = _RebasePath(java_sources_file) + java_files = build_utils.ReadSourcesList(java_sources_file) + self._java_files = java_files + return self._java_files + + def PrebuiltJars(self): + all_jars = self.Gradle().get('dependent_prebuilt_jars', []) + return [i for i in all_jars if i not in _EXCLUDED_PREBUILT_JARS] + + def AllEntries(self): + """Returns a list of all entries that the current entry depends on. + + This includes the entry itself to make iterating simpler.""" + if self._all_entries is None: + logging.debug('Generating entries for %s', self.GnTarget()) + deps = [_ProjectEntry.FromBuildConfigPath(p) + for p in self.Gradle()['dependent_android_projects']] + deps.extend(_ProjectEntry.FromBuildConfigPath(p) + for p in self.Gradle()['dependent_java_projects']) + all_entries = set() + for dep in deps: + all_entries.update(dep.AllEntries()) + all_entries.add(self) + self._all_entries = list(all_entries) + return self._all_entries + + +class _ProjectContextGenerator(object): + """Helper class to generate gradle build files""" + def __init__(self, project_dir, build_vars, use_gradle_process_resources, + jinja_processor, split_projects, channel): + self.project_dir = project_dir + self.build_vars = build_vars + self.use_gradle_process_resources = use_gradle_process_resources + self.jinja_processor = jinja_processor + self.split_projects = split_projects + self.channel = channel + self.processed_java_dirs = set() + self.processed_prebuilts = set() + self.processed_res_dirs = set() + + def _GenJniLibs(self, root_entry): + libraries = [] + for entry in self._GetEntries(root_entry): + libraries += entry.BuildConfig().get('native', {}).get('libraries', []) + if libraries: + return _CreateJniLibsDir(constants.GetOutDirectory(), + self.EntryOutputDir(root_entry), libraries) + return [] + + def _GenJavaDirs(self, root_entry): + java_files = [] + for entry in self._GetEntries(root_entry): + java_files += entry.JavaFiles() + java_dirs, excludes = _ComputeJavaSourceDirsAndExcludes( + constants.GetOutDirectory(), java_files) + return java_dirs, excludes + + def _GenCustomManifest(self, entry): + """Returns the path to the generated AndroidManifest.xml. + + Gradle uses package id from manifest when generating R.class. So, we need + to generate a custom manifest if we let gradle process resources. We cannot + simply set android.defaultConfig.applicationId because it is not supported + for library targets.""" + resource_packages = entry.Javac().get('resource_packages') + if not resource_packages: + logging.debug('Target ' + entry.GnTarget() + ' includes resources from ' + 'unknown package. Unable to process with gradle.') + return _DEFAULT_ANDROID_MANIFEST_PATH + elif len(resource_packages) > 1: + logging.debug('Target ' + entry.GnTarget() + ' includes resources from ' + 'multiple packages. Unable to process with gradle.') + return _DEFAULT_ANDROID_MANIFEST_PATH + + variables = {'package': resource_packages[0]} + data = self.jinja_processor.Render(_TemplatePath('manifest'), variables) + output_file = os.path.join( + self.EntryOutputDir(entry), 'AndroidManifest.xml') + _WriteFile(output_file, data) + + return output_file + + def _Relativize(self, entry, paths): + return _RebasePath(paths, self.EntryOutputDir(entry)) + + def _GetEntries(self, entry): + if self.split_projects: + return [entry] + return entry.AllEntries() + + def EntryOutputDir(self, entry): + return os.path.join(self.project_dir, entry.GradleSubdir()) + + def GeneratedInputs(self, root_entry): + generated_inputs = set() + for entry in self._GetEntries(root_entry): + generated_inputs.update(entry.PrebuiltJars()) + return generated_inputs + + def GenerateManifest(self, root_entry): + android_manifest = root_entry.DepsInfo().get('android_manifest') + if not android_manifest: + android_manifest = self._GenCustomManifest(root_entry) + return self._Relativize(root_entry, android_manifest) + + def Generate(self, root_entry): + # TODO(agrieve): Add an option to use interface jars and see if that speeds + # things up at all. + variables = {} + java_dirs, excludes = self._GenJavaDirs(root_entry) + java_dirs.extend( + e.GeneratedJavaSubdir() for e in self._GetEntries(root_entry)) + self.processed_java_dirs.update(java_dirs) + java_dirs.sort() + variables['java_dirs'] = self._Relativize(root_entry, java_dirs) + variables['java_excludes'] = excludes + variables['jni_libs'] = self._Relativize( + root_entry, set(self._GenJniLibs(root_entry))) + prebuilts = set( + p for e in self._GetEntries(root_entry) for p in e.PrebuiltJars()) + self.processed_prebuilts.update(prebuilts) + variables['prebuilts'] = self._Relativize(root_entry, prebuilts) + res_sources_files = _RebasePath( + set(p for e in self._GetEntries(root_entry) for p in e.ResSources())) + res_sources = [] + for res_sources_file in res_sources_files: + res_sources.extend(build_utils.ReadSourcesList(res_sources_file)) + res_dirs = resource_utils.DeduceResourceDirsFromFileList(res_sources) + # Do not add generated resources for the all module since it creates many + # duplicates, and currently resources are only used for editing. + self.processed_res_dirs.update(res_dirs) + variables['res_dirs'] = self._Relativize(root_entry, res_dirs) + if self.split_projects: + deps = [_ProjectEntry.FromBuildConfigPath(p) + for p in root_entry.Gradle()['dependent_android_projects']] + variables['android_project_deps'] = [d.ProjectName() for d in deps] + deps = [_ProjectEntry.FromBuildConfigPath(p) + for p in root_entry.Gradle()['dependent_java_projects']] + variables['java_project_deps'] = [d.ProjectName() for d in deps] + return variables + + +def _ComputeJavaSourceDirs(java_files): + """Returns a dictionary of source dirs with each given files in one.""" + found_roots = {} + for path in java_files: + path_root = path + # Recognize these tokens as top-level. + while True: + path_root = os.path.dirname(path_root) + basename = os.path.basename(path_root) + assert basename, 'Failed to find source dir for ' + path + if basename in ('java', 'src'): + break + if basename in ('javax', 'org', 'com'): + path_root = os.path.dirname(path_root) + break + if path_root not in found_roots: + found_roots[path_root] = [] + found_roots[path_root].append(path) + return found_roots + + +def _ComputeExcludeFilters(wanted_files, unwanted_files, parent_dir): + """Returns exclude patters to exclude unwanted files but keep wanted files. + + - Shortens exclude list by globbing if possible. + - Exclude patterns are relative paths from the parent directory. + """ + excludes = [] + files_to_include = set(wanted_files) + files_to_exclude = set(unwanted_files) + while files_to_exclude: + unwanted_file = files_to_exclude.pop() + target_exclude = os.path.join( + os.path.dirname(unwanted_file), '*.java') + found_files = set(glob.glob(target_exclude)) + valid_files = found_files & files_to_include + if valid_files: + excludes.append(os.path.relpath(unwanted_file, parent_dir)) + else: + excludes.append(os.path.relpath(target_exclude, parent_dir)) + files_to_exclude -= found_files + return excludes + + +def _ComputeJavaSourceDirsAndExcludes(output_dir, java_files): + """Computes the list of java source directories and exclude patterns. + + 1. Computes the root java source directories from the list of files. + 2. Compute exclude patterns that exclude all extra files only. + 3. Returns the list of java source directories and exclude patterns. + """ + java_dirs = [] + excludes = [] + if java_files: + java_files = _RebasePath(java_files) + computed_dirs = _ComputeJavaSourceDirs(java_files) + java_dirs = list(computed_dirs.keys()) + all_found_java_files = set() + + for directory, files in computed_dirs.items(): + found_java_files = build_utils.FindInDirectory(directory, '*.java') + all_found_java_files.update(found_java_files) + unwanted_java_files = set(found_java_files) - set(files) + if unwanted_java_files: + logging.debug('Directory requires excludes: %s', directory) + excludes.extend( + _ComputeExcludeFilters(files, unwanted_java_files, directory)) + + missing_java_files = set(java_files) - all_found_java_files + # Warn only about non-generated files that are missing. + missing_java_files = [p for p in missing_java_files + if not p.startswith(output_dir)] + if missing_java_files: + logging.warning( + 'Some java files were not found: %s', missing_java_files) + + return java_dirs, excludes + + +def _CreateRelativeSymlink(target_path, link_path): + link_dir = os.path.dirname(link_path) + relpath = os.path.relpath(target_path, link_dir) + logging.debug('Creating symlink %s -> %s', link_path, relpath) + os.symlink(relpath, link_path) + + +def _CreateJniLibsDir(output_dir, entry_output_dir, so_files): + """Creates directory with symlinked .so files if necessary. + + Returns list of JNI libs directories.""" + + if so_files: + symlink_dir = os.path.join(entry_output_dir, _JNI_LIBS_SUBDIR) + shutil.rmtree(symlink_dir, True) + abi_dir = os.path.join(symlink_dir, _ARMEABI_SUBDIR) + if not os.path.exists(abi_dir): + os.makedirs(abi_dir) + for so_file in so_files: + target_path = os.path.join(output_dir, so_file) + symlinked_path = os.path.join(abi_dir, so_file) + _CreateRelativeSymlink(target_path, symlinked_path) + + return [symlink_dir] + + return [] + + +def _GenerateLocalProperties(sdk_dir): + """Returns the data for local.properties as a string.""" + return '\n'.join([ + '# Generated by //build/android/gradle/generate_gradle.py', + 'sdk.dir=%s' % sdk_dir, + '', + ]) + + +def _GenerateGradleWrapperPropertiesCanary(): + """Returns the data for gradle-wrapper.properties as a string.""" + # Before May 2020, this wasn't necessary. Might not be necessary at some point + # in the future? + return '\n'.join([ + '# Generated by //build/android/gradle/generate_gradle.py', + ('distributionUrl=https\\://services.gradle.org/distributions/' + 'gradle-6.5-rc-1-all.zip\n'), + '', + ]) + + +def _GenerateGradleProperties(): + """Returns the data for gradle.properties as a string.""" + return '\n'.join([ + '# Generated by //build/android/gradle/generate_gradle.py', + '', + '# Tells Gradle to show warnings during project sync.', + 'org.gradle.warning.mode=all', + '', + ]) + + +def _GenerateBaseVars(generator, build_vars): + variables = {} + variables['compile_sdk_version'] = ( + 'android-%s' % build_vars['compile_sdk_version']) + target_sdk_version = build_vars['android_sdk_version'] + if str(target_sdk_version).isalpha(): + target_sdk_version = '"{}"'.format(target_sdk_version) + variables['target_sdk_version'] = target_sdk_version + variables['use_gradle_process_resources'] = ( + generator.use_gradle_process_resources) + variables['channel'] = generator.channel + return variables + + +def _GenerateGradleFile(entry, generator, build_vars, jinja_processor): + """Returns the data for a project's build.gradle.""" + deps_info = entry.DepsInfo() + variables = _GenerateBaseVars(generator, build_vars) + sourceSetName = 'main' + + if deps_info['type'] == 'android_apk': + target_type = 'android_apk' + elif deps_info['type'] in ('java_library', 'java_annotation_processor'): + is_prebuilt = deps_info.get('is_prebuilt', False) + gradle_treat_as_prebuilt = deps_info.get('gradle_treat_as_prebuilt', False) + if is_prebuilt or gradle_treat_as_prebuilt: + return None + elif deps_info['requires_android']: + target_type = 'android_library' + else: + target_type = 'java_library' + elif deps_info['type'] == 'java_binary': + target_type = 'java_binary' + variables['main_class'] = deps_info.get('main_class') + elif deps_info['type'] == 'junit_binary': + target_type = 'android_junit' + sourceSetName = 'test' + else: + return None + + variables['target_name'] = os.path.splitext(deps_info['name'])[0] + variables['template_type'] = target_type + variables['main'] = {} + variables[sourceSetName] = generator.Generate(entry) + variables['main']['android_manifest'] = generator.GenerateManifest(entry) + + if entry.android_test_entries: + variables['android_test'] = [] + for e in entry.android_test_entries: + test_entry = generator.Generate(e) + test_entry['android_manifest'] = generator.GenerateManifest(e) + variables['android_test'].append(test_entry) + for key, value in test_entry.items(): + if isinstance(value, list): + test_entry[key] = sorted(set(value) - set(variables['main'][key])) + + return jinja_processor.Render( + _TemplatePath(target_type.split('_')[0]), variables) + + +# Example: //chrome/android:monochrome +def _GetNative(relative_func, target_names): + """Returns an object containing native c++ sources list and its included path + + Iterate through all target_names and their deps to get the list of included + paths and sources.""" + out_dir = constants.GetOutDirectory() + with open(os.path.join(out_dir, 'project.json'), 'r') as project_file: + projects = json.load(project_file) + project_targets = projects['targets'] + root_dir = projects['build_settings']['root_path'] + includes = set() + processed_target = set() + targets_stack = list(target_names) + sources = [] + + while targets_stack: + target_name = targets_stack.pop() + if target_name in processed_target: + continue + processed_target.add(target_name) + target = project_targets[target_name] + includes.update(target.get('include_dirs', [])) + targets_stack.extend(target.get('deps', [])) + # Ignore generated files + sources.extend(f for f in target.get('sources', []) + if f.endswith('.cc') and not f.startswith('//out')) + + def process_paths(paths): + # Ignores leading // + return relative_func( + sorted(os.path.join(root_dir, path[2:]) for path in paths)) + + return { + 'sources': process_paths(sources), + 'includes': process_paths(includes), + } + + +def _GenerateModuleAll(gradle_output_dir, generator, build_vars, + jinja_processor, native_targets): + """Returns the data for a pseudo build.gradle of all dirs. + + See //docs/android_studio.md for more details.""" + variables = _GenerateBaseVars(generator, build_vars) + target_type = 'android_apk' + variables['target_name'] = _MODULE_ALL + variables['template_type'] = target_type + java_dirs = sorted(generator.processed_java_dirs) + prebuilts = sorted(generator.processed_prebuilts) + res_dirs = sorted(generator.processed_res_dirs) + def Relativize(paths): + return _RebasePath(paths, os.path.join(gradle_output_dir, _MODULE_ALL)) + + # As after clank modularization, the java and javatests code will live side by + # side in the same module, we will list both of them in the main target here. + main_java_dirs = [d for d in java_dirs if 'junit/' not in d] + junit_test_java_dirs = [d for d in java_dirs if 'junit/' in d] + variables['main'] = { + 'android_manifest': Relativize(_DEFAULT_ANDROID_MANIFEST_PATH), + 'java_dirs': Relativize(main_java_dirs), + 'prebuilts': Relativize(prebuilts), + 'java_excludes': ['**/*.java'], + 'res_dirs': Relativize(res_dirs), + } + variables['android_test'] = [{ + 'java_dirs': Relativize(junit_test_java_dirs), + 'java_excludes': ['**/*.java'], + }] + if native_targets: + variables['native'] = _GetNative( + relative_func=Relativize, target_names=native_targets) + data = jinja_processor.Render( + _TemplatePath(target_type.split('_')[0]), variables) + _WriteFile( + os.path.join(gradle_output_dir, _MODULE_ALL, _GRADLE_BUILD_FILE), data) + if native_targets: + cmake_data = jinja_processor.Render(_TemplatePath('cmake'), variables) + _WriteFile( + os.path.join(gradle_output_dir, _MODULE_ALL, _CMAKE_FILE), cmake_data) + + +def _GenerateRootGradle(jinja_processor, channel): + """Returns the data for the root project's build.gradle.""" + return jinja_processor.Render(_TemplatePath('root'), {'channel': channel}) + + +def _GenerateSettingsGradle(project_entries): + """Returns the data for settings.gradle.""" + project_name = os.path.basename(os.path.dirname(host_paths.DIR_SOURCE_ROOT)) + lines = [] + lines.append('// Generated by //build/android/gradle/generate_gradle.py') + lines.append('rootProject.name = "%s"' % project_name) + lines.append('rootProject.projectDir = settingsDir') + lines.append('') + for name, subdir in project_entries: + # Example target: + # android_webview:android_webview_java__build_config_crbug_908819 + lines.append('include ":%s"' % name) + lines.append('project(":%s").projectDir = new File(settingsDir, "%s")' % + (name, subdir)) + return '\n'.join(lines) + + +def _FindAllProjectEntries(main_entries): + """Returns the list of all _ProjectEntry instances given the root project.""" + found = set() + to_scan = list(main_entries) + while to_scan: + cur_entry = to_scan.pop() + if cur_entry in found: + continue + found.add(cur_entry) + sub_config_paths = cur_entry.DepsInfo()['deps_configs'] + to_scan.extend( + _ProjectEntry.FromBuildConfigPath(p) for p in sub_config_paths) + return list(found) + + +def _CombineTestEntries(entries): + """Combines test apks into the androidTest source set of their target. + + - Speeds up android studio + - Adds proper dependency between test and apk_under_test + - Doesn't work for junit yet due to resulting circular dependencies + - e.g. base_junit_tests > base_junit_test_support > base_java + """ + combined_entries = [] + android_test_entries = collections.defaultdict(list) + for entry in entries: + target_name = entry.GnTarget() + if (target_name.endswith(_INSTRUMENTATION_TARGET_SUFFIX) + and 'apk_under_test' in entry.Gradle()): + apk_name = entry.Gradle()['apk_under_test'] + android_test_entries[apk_name].append(entry) + else: + combined_entries.append(entry) + for entry in combined_entries: + target_name = entry.DepsInfo()['name'] + if target_name in android_test_entries: + entry.android_test_entries = android_test_entries[target_name] + del android_test_entries[target_name] + # Add unmatched test entries as individual targets. + combined_entries.extend(e for l in android_test_entries.values() for e in l) + return combined_entries + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--output-directory', + help='Path to the root build directory.') + parser.add_argument('-v', + '--verbose', + dest='verbose_count', + default=0, + action='count', + help='Verbose level') + parser.add_argument('--target', + dest='targets', + action='append', + help='GN target to generate project for. Replaces set of ' + 'default targets. May be repeated.') + parser.add_argument('--extra-target', + dest='extra_targets', + action='append', + help='GN target to generate project for, in addition to ' + 'the default ones. May be repeated.') + parser.add_argument('--project-dir', + help='Root of the output project.', + default=os.path.join('$CHROMIUM_OUTPUT_DIR', 'gradle')) + parser.add_argument('--all', + action='store_true', + help='Include all .java files reachable from any ' + 'apk/test/binary target. On by default unless ' + '--split-projects is used (--split-projects can ' + 'slow down Studio given too many targets).') + parser.add_argument('--use-gradle-process-resources', + action='store_true', + help='Have gradle generate R.java rather than ninja') + parser.add_argument('--split-projects', + action='store_true', + help='Split projects by their gn deps rather than ' + 'combining all the dependencies of each target') + parser.add_argument('--native-target', + dest='native_targets', + action='append', + help='GN native targets to generate for. May be ' + 'repeated.') + parser.add_argument('--compile-sdk-version', + type=int, + default=0, + help='Override compileSdkVersion for android sdk docs. ' + 'Useful when sources for android_sdk_version is ' + 'not available in Android Studio.') + parser.add_argument( + '--sdk-path', + default=os.path.expanduser('~/Android/Sdk'), + help='The path to use as the SDK root, overrides the ' + 'default at ~/Android/Sdk.') + version_group = parser.add_mutually_exclusive_group() + version_group.add_argument('--beta', + action='store_true', + help='Generate a project that is compatible with ' + 'Android Studio Beta.') + version_group.add_argument('--canary', + action='store_true', + help='Generate a project that is compatible with ' + 'Android Studio Canary.') + args = parser.parse_args() + if args.output_directory: + constants.SetOutputDirectory(args.output_directory) + constants.CheckOutputDirectory() + output_dir = constants.GetOutDirectory() + devil_chromium.Initialize(output_directory=output_dir) + run_tests_helper.SetLogLevel(args.verbose_count) + + if args.use_gradle_process_resources: + assert args.split_projects, ( + 'Gradle resources does not work without --split-projects.') + + _gradle_output_dir = os.path.abspath( + args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir)) + logging.warning('Creating project at: %s', _gradle_output_dir) + + # Generate for "all targets" by default when not using --split-projects (too + # slow), and when no --target has been explicitly set. "all targets" means all + # java targets that are depended on by an apk or java_binary (leaf + # java_library targets will not be included). + args.all = args.all or (not args.split_projects and not args.targets) + + targets_from_args = set(args.targets or _DEFAULT_TARGETS) + if args.extra_targets: + targets_from_args.update(args.extra_targets) + + if args.all: + if args.native_targets: + _RunGnGen(output_dir, ['--ide=json']) + elif not os.path.exists(os.path.join(output_dir, 'build.ninja')): + _RunGnGen(output_dir) + else: + # Faster than running "gn gen" in the no-op case. + _RunNinja(output_dir, ['build.ninja']) + # Query ninja for all __build_config_crbug_908819 targets. + targets = _QueryForAllGnTargets(output_dir) + else: + assert not args.native_targets, 'Native editing requires --all.' + targets = [ + re.sub(r'_test_apk$', _INSTRUMENTATION_TARGET_SUFFIX, t) + for t in targets_from_args + ] + # Necessary after "gn clean" + if not os.path.exists( + os.path.join(output_dir, gn_helpers.BUILD_VARS_FILENAME)): + _RunGnGen(output_dir) + + build_vars = gn_helpers.ReadBuildVars(output_dir) + jinja_processor = jinja_template.JinjaProcessor(_FILE_DIR) + if args.beta: + channel = 'beta' + elif args.canary: + channel = 'canary' + else: + channel = 'stable' + if args.compile_sdk_version: + build_vars['compile_sdk_version'] = args.compile_sdk_version + else: + build_vars['compile_sdk_version'] = build_vars['android_sdk_version'] + generator = _ProjectContextGenerator(_gradle_output_dir, build_vars, + args.use_gradle_process_resources, jinja_processor, args.split_projects, + channel) + + main_entries = [_ProjectEntry.FromGnTarget(t) for t in targets] + + if args.all: + # There are many unused libraries, so restrict to those that are actually + # used by apks/bundles/binaries/tests or that are explicitly mentioned in + # --targets. + BASE_TYPES = ('android_apk', 'android_app_bundle_module', 'java_binary', + 'junit_binary') + main_entries = [ + e for e in main_entries + if (e.GetType() in BASE_TYPES or e.GnTarget() in targets_from_args + or e.GnTarget().endswith(_INSTRUMENTATION_TARGET_SUFFIX)) + ] + + if args.split_projects: + main_entries = _FindAllProjectEntries(main_entries) + + logging.info('Generating for %d targets.', len(main_entries)) + + entries = [e for e in _CombineTestEntries(main_entries) if e.IsValid()] + logging.info('Creating %d projects for targets.', len(entries)) + + logging.warning('Writing .gradle files...') + project_entries = [] + # When only one entry will be generated we want it to have a valid + # build.gradle file with its own AndroidManifest. + for entry in entries: + data = _GenerateGradleFile(entry, generator, build_vars, jinja_processor) + if data and not args.all: + project_entries.append((entry.ProjectName(), entry.GradleSubdir())) + _WriteFile( + os.path.join(generator.EntryOutputDir(entry), _GRADLE_BUILD_FILE), + data) + if args.all: + project_entries.append((_MODULE_ALL, _MODULE_ALL)) + _GenerateModuleAll(_gradle_output_dir, generator, build_vars, + jinja_processor, args.native_targets) + + _WriteFile(os.path.join(generator.project_dir, _GRADLE_BUILD_FILE), + _GenerateRootGradle(jinja_processor, channel)) + + _WriteFile(os.path.join(generator.project_dir, 'settings.gradle'), + _GenerateSettingsGradle(project_entries)) + + # Ensure the Android Studio sdk is correctly initialized. + if not os.path.exists(args.sdk_path): + # Help first-time users avoid Android Studio forcibly changing back to + # the previous default due to not finding a valid sdk under this dir. + shutil.copytree(_RebasePath(build_vars['android_sdk_root']), args.sdk_path) + _WriteFile( + os.path.join(generator.project_dir, 'local.properties'), + _GenerateLocalProperties(args.sdk_path)) + _WriteFile(os.path.join(generator.project_dir, 'gradle.properties'), + _GenerateGradleProperties()) + + wrapper_properties = os.path.join(generator.project_dir, 'gradle', 'wrapper', + 'gradle-wrapper.properties') + if os.path.exists(wrapper_properties): + os.unlink(wrapper_properties) + if args.canary: + _WriteFile(wrapper_properties, _GenerateGradleWrapperPropertiesCanary()) + + generated_inputs = set() + for entry in entries: + entries_to_gen = [entry] + entries_to_gen.extend(entry.android_test_entries) + for entry_to_gen in entries_to_gen: + # Build all paths references by .gradle that exist within output_dir. + generated_inputs.update(generator.GeneratedInputs(entry_to_gen)) + if generated_inputs: + targets = _RebasePath(generated_inputs, output_dir) + _RunNinja(output_dir, targets) + + logging.warning('Generated files will only appear once you\'ve built them.') + logging.warning('Generated projects for Android Studio %s', channel) + logging.warning('For more tips: https://chromium.googlesource.com/chromium' + '/src.git/+/master/docs/android_studio.md') + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gradle/gn_to_cmake.py b/third_party/libwebrtc/build/android/gradle/gn_to_cmake.py new file mode 100755 index 0000000000..72898254e7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/gn_to_cmake.py @@ -0,0 +1,689 @@ +#!/usr/bin/env python3 +# 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. + +""" +Usage: gn_to_cmake.py + +gn gen out/config --ide=json --json-ide-script=../../gn/gn_to_cmake.py + +or + +gn gen out/config --ide=json +python gn/gn_to_cmake.py out/config/project.json + +The first is recommended, as it will auto-update. +""" + +from __future__ import print_function + +import functools +import json +import posixpath +import string +import sys + + +def CMakeStringEscape(a): + """Escapes the string 'a' for use inside a CMake string. + + This means escaping + '\' otherwise it may be seen as modifying the next character + '"' otherwise it will end the string + ';' otherwise the string becomes a list + + The following do not need to be escaped + '#' when the lexer is in string state, this does not start a comment + """ + return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"') + + +def CMakeTargetEscape(a): + """Escapes the string 'a' for use as a CMake target name. + + CMP0037 in CMake 3.0 restricts target names to "^[A-Za-z0-9_.:+-]+$" + The ':' is only allowed for imported targets. + """ + def Escape(c): + if c in string.ascii_letters or c in string.digits or c in '_.+-': + return c + else: + return '__' + return ''.join([Escape(c) for c in a]) + + +def SetVariable(out, variable_name, value): + """Sets a CMake variable.""" + out.write('set("') + out.write(CMakeStringEscape(variable_name)) + out.write('" "') + out.write(CMakeStringEscape(value)) + out.write('")\n') + + +def SetVariableList(out, variable_name, values): + """Sets a CMake variable to a list.""" + if not values: + return SetVariable(out, variable_name, "") + if len(values) == 1: + return SetVariable(out, variable_name, values[0]) + out.write('list(APPEND "') + out.write(CMakeStringEscape(variable_name)) + out.write('"\n "') + out.write('"\n "'.join([CMakeStringEscape(value) for value in values])) + out.write('")\n') + + +def SetFilesProperty(output, variable, property_name, values, sep): + """Given a set of source files, sets the given property on them.""" + output.write('set_source_files_properties(') + WriteVariable(output, variable) + output.write(' PROPERTIES ') + output.write(property_name) + output.write(' "') + for value in values: + output.write(CMakeStringEscape(value)) + output.write(sep) + output.write('")\n') + + +def SetCurrentTargetProperty(out, property_name, values, sep=''): + """Given a target, sets the given property.""" + out.write('set_target_properties("${target}" PROPERTIES ') + out.write(property_name) + out.write(' "') + for value in values: + out.write(CMakeStringEscape(value)) + out.write(sep) + out.write('")\n') + + +def WriteVariable(output, variable_name, prepend=None): + if prepend: + output.write(prepend) + output.write('${') + output.write(variable_name) + output.write('}') + + +# See GetSourceFileType in gn +source_file_types = { + '.cc': 'cxx', + '.cpp': 'cxx', + '.cxx': 'cxx', + '.c': 'c', + '.s': 'asm', + '.S': 'asm', + '.asm': 'asm', + '.o': 'obj', + '.obj': 'obj', +} + + +class CMakeTargetType(object): + def __init__(self, command, modifier, property_modifier, is_linkable): + self.command = command + self.modifier = modifier + self.property_modifier = property_modifier + self.is_linkable = is_linkable +CMakeTargetType.custom = CMakeTargetType('add_custom_target', 'SOURCES', + None, False) + +# See GetStringForOutputType in gn +cmake_target_types = { + 'unknown': CMakeTargetType.custom, + 'group': CMakeTargetType.custom, + 'executable': CMakeTargetType('add_executable', None, 'RUNTIME', True), + 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY', True), + 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY', True), + 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE', False), + 'source_set': CMakeTargetType('add_library', 'OBJECT', None, False), + 'copy': CMakeTargetType.custom, + 'action': CMakeTargetType.custom, + 'action_foreach': CMakeTargetType.custom, + 'bundle_data': CMakeTargetType.custom, + 'create_bundle': CMakeTargetType.custom, +} + + +def FindFirstOf(s, a): + return min(s.find(i) for i in a if i in s) + + +def GetCMakeTargetName(gn_target_name): + # See /src/tools/gn/label.cc#Resolve + # //base/test:test_support(//build/toolchain/win:msvc) + path_separator = FindFirstOf(gn_target_name, (':', '(')) + location = None + name = None + toolchain = None + if not path_separator: + location = gn_target_name[2:] + else: + location = gn_target_name[2:path_separator] + toolchain_separator = gn_target_name.find('(', path_separator) + if toolchain_separator == -1: + name = gn_target_name[path_separator + 1:] + else: + if toolchain_separator > path_separator: + name = gn_target_name[path_separator + 1:toolchain_separator] + assert gn_target_name.endswith(')') + toolchain = gn_target_name[toolchain_separator + 1:-1] + assert location or name + + cmake_target_name = None + if location.endswith('/' + name): + cmake_target_name = location + elif location: + cmake_target_name = location + '_' + name + else: + cmake_target_name = name + if toolchain: + cmake_target_name += '--' + toolchain + return CMakeTargetEscape(cmake_target_name) + + +class Project(object): + def __init__(self, project_json): + self.targets = project_json['targets'] + build_settings = project_json['build_settings'] + self.root_path = build_settings['root_path'] + self.build_path = posixpath.join(self.root_path, + build_settings['build_dir'][2:]) + self.object_source_deps = {} + + def GetAbsolutePath(self, path): + if path.startswith("//"): + return self.root_path + "/" + path[2:] + else: + return path + + def GetObjectSourceDependencies(self, gn_target_name, object_dependencies): + """All OBJECT libraries whose sources have not been absorbed.""" + if gn_target_name in self.object_source_deps: + object_dependencies.update(self.object_source_deps[gn_target_name]) + return + target_deps = set() + dependencies = self.targets[gn_target_name].get('deps', []) + for dependency in dependencies: + dependency_type = self.targets[dependency].get('type', None) + if dependency_type == 'source_set': + target_deps.add(dependency) + if dependency_type not in gn_target_types_that_absorb_objects: + self.GetObjectSourceDependencies(dependency, target_deps) + self.object_source_deps[gn_target_name] = target_deps + object_dependencies.update(target_deps) + + def GetObjectLibraryDependencies(self, gn_target_name, object_dependencies): + """All OBJECT libraries whose libraries have not been absorbed.""" + dependencies = self.targets[gn_target_name].get('deps', []) + for dependency in dependencies: + dependency_type = self.targets[dependency].get('type', None) + if dependency_type == 'source_set': + object_dependencies.add(dependency) + self.GetObjectLibraryDependencies(dependency, object_dependencies) + + +class Target(object): + def __init__(self, gn_target_name, project): + self.gn_name = gn_target_name + self.properties = project.targets[self.gn_name] + self.cmake_name = GetCMakeTargetName(self.gn_name) + self.gn_type = self.properties.get('type', None) + self.cmake_type = cmake_target_types.get(self.gn_type, None) + + +def WriteAction(out, target, project, sources, synthetic_dependencies): + outputs = [] + output_directories = set() + for output in target.properties.get('outputs', []): + output_abs_path = project.GetAbsolutePath(output) + outputs.append(output_abs_path) + output_directory = posixpath.dirname(output_abs_path) + if output_directory: + output_directories.add(output_directory) + outputs_name = '${target}__output' + SetVariableList(out, outputs_name, outputs) + + out.write('add_custom_command(OUTPUT ') + WriteVariable(out, outputs_name) + out.write('\n') + + if output_directories: + out.write(' COMMAND ${CMAKE_COMMAND} -E make_directory "') + out.write('" "'.join([CMakeStringEscape(d) for d in output_directories])) + out.write('"\n') + + script = target.properties['script'] + arguments = target.properties['args'] + out.write(' COMMAND python "') + out.write(CMakeStringEscape(project.GetAbsolutePath(script))) + out.write('"') + if arguments: + out.write('\n "') + out.write('"\n "'.join([CMakeStringEscape(a) for a in arguments])) + out.write('"') + out.write('\n') + + out.write(' DEPENDS ') + for sources_type_name in sources.values(): + WriteVariable(out, sources_type_name, ' ') + out.write('\n') + + #TODO: CMake 3.7 is introducing DEPFILE + + out.write(' WORKING_DIRECTORY "') + out.write(CMakeStringEscape(project.build_path)) + out.write('"\n') + + out.write(' COMMENT "Action: ${target}"\n') + + out.write(' VERBATIM)\n') + + synthetic_dependencies.add(outputs_name) + + +def ExpandPlaceholders(source, a): + source_dir, source_file_part = posixpath.split(source) + source_name_part, _ = posixpath.splitext(source_file_part) + #TODO: {{source_gen_dir}}, {{source_out_dir}}, {{response_file_name}} + return a.replace('{{source}}', source) \ + .replace('{{source_file_part}}', source_file_part) \ + .replace('{{source_name_part}}', source_name_part) \ + .replace('{{source_dir}}', source_dir) \ + .replace('{{source_root_relative_dir}}', source_dir) + + +def WriteActionForEach(out, target, project, sources, synthetic_dependencies): + all_outputs = target.properties.get('outputs', []) + inputs = target.properties.get('sources', []) + # TODO: consider expanding 'output_patterns' instead. + outputs_per_input = len(all_outputs) / len(inputs) + for count, source in enumerate(inputs): + source_abs_path = project.GetAbsolutePath(source) + + outputs = [] + output_directories = set() + for output in all_outputs[outputs_per_input * count: + outputs_per_input * (count+1)]: + output_abs_path = project.GetAbsolutePath(output) + outputs.append(output_abs_path) + output_directory = posixpath.dirname(output_abs_path) + if output_directory: + output_directories.add(output_directory) + outputs_name = '${target}__output_' + str(count) + SetVariableList(out, outputs_name, outputs) + + out.write('add_custom_command(OUTPUT ') + WriteVariable(out, outputs_name) + out.write('\n') + + if output_directories: + out.write(' COMMAND ${CMAKE_COMMAND} -E make_directory "') + out.write('" "'.join([CMakeStringEscape(d) for d in output_directories])) + out.write('"\n') + + script = target.properties['script'] + # TODO: need to expand {{xxx}} in arguments + arguments = target.properties['args'] + out.write(' COMMAND python "') + out.write(CMakeStringEscape(project.GetAbsolutePath(script))) + out.write('"') + if arguments: + out.write('\n "') + expand = functools.partial(ExpandPlaceholders, source_abs_path) + out.write('"\n "'.join( + [CMakeStringEscape(expand(a)) for a in arguments])) + out.write('"') + out.write('\n') + + out.write(' DEPENDS') + if 'input' in sources: + WriteVariable(out, sources['input'], ' ') + out.write(' "') + out.write(CMakeStringEscape(source_abs_path)) + out.write('"\n') + + #TODO: CMake 3.7 is introducing DEPFILE + + out.write(' WORKING_DIRECTORY "') + out.write(CMakeStringEscape(project.build_path)) + out.write('"\n') + + out.write(' COMMENT "Action ${target} on ') + out.write(CMakeStringEscape(source_abs_path)) + out.write('"\n') + + out.write(' VERBATIM)\n') + + synthetic_dependencies.add(outputs_name) + + +def WriteCopy(out, target, project, sources, synthetic_dependencies): + inputs = target.properties.get('sources', []) + raw_outputs = target.properties.get('outputs', []) + + # TODO: consider expanding 'output_patterns' instead. + outputs = [] + for output in raw_outputs: + output_abs_path = project.GetAbsolutePath(output) + outputs.append(output_abs_path) + outputs_name = '${target}__output' + SetVariableList(out, outputs_name, outputs) + + out.write('add_custom_command(OUTPUT ') + WriteVariable(out, outputs_name) + out.write('\n') + + for src, dst in zip(inputs, outputs): + out.write(' COMMAND ${CMAKE_COMMAND} -E copy "') + out.write(CMakeStringEscape(project.GetAbsolutePath(src))) + out.write('" "') + out.write(CMakeStringEscape(dst)) + out.write('"\n') + + out.write(' DEPENDS ') + for sources_type_name in sources.values(): + WriteVariable(out, sources_type_name, ' ') + out.write('\n') + + out.write(' WORKING_DIRECTORY "') + out.write(CMakeStringEscape(project.build_path)) + out.write('"\n') + + out.write(' COMMENT "Copy ${target}"\n') + + out.write(' VERBATIM)\n') + + synthetic_dependencies.add(outputs_name) + + +def WriteCompilerFlags(out, target, project, sources): + # Hack, set linker language to c if no c or cxx files present. + if not 'c' in sources and not 'cxx' in sources: + SetCurrentTargetProperty(out, 'LINKER_LANGUAGE', ['C']) + + # Mark uncompiled sources as uncompiled. + if 'input' in sources: + SetFilesProperty(out, sources['input'], 'HEADER_FILE_ONLY', ('True',), '') + if 'other' in sources: + SetFilesProperty(out, sources['other'], 'HEADER_FILE_ONLY', ('True',), '') + + # Mark object sources as linkable. + if 'obj' in sources: + SetFilesProperty(out, sources['obj'], 'EXTERNAL_OBJECT', ('True',), '') + + # TODO: 'output_name', 'output_dir', 'output_extension' + # This includes using 'source_outputs' to direct compiler output. + + # Includes + includes = target.properties.get('include_dirs', []) + if includes: + out.write('set_property(TARGET "${target}" ') + out.write('APPEND PROPERTY INCLUDE_DIRECTORIES') + for include_dir in includes: + out.write('\n "') + out.write(project.GetAbsolutePath(include_dir)) + out.write('"') + out.write(')\n') + + # Defines + defines = target.properties.get('defines', []) + if defines: + SetCurrentTargetProperty(out, 'COMPILE_DEFINITIONS', defines, ';') + + # Compile flags + # "arflags", "asmflags", "cflags", + # "cflags_c", "clfags_cc", "cflags_objc", "clfags_objcc" + # CMake does not have per target lang compile flags. + # TODO: $<$:cflags_cc style generator expression. + # http://public.kitware.com/Bug/view.php?id=14857 + flags = [] + flags.extend(target.properties.get('cflags', [])) + cflags_asm = target.properties.get('asmflags', []) + cflags_c = target.properties.get('cflags_c', []) + cflags_cxx = target.properties.get('cflags_cc', []) + if 'c' in sources and not any(k in sources for k in ('asm', 'cxx')): + flags.extend(cflags_c) + elif 'cxx' in sources and not any(k in sources for k in ('asm', 'c')): + flags.extend(cflags_cxx) + else: + # TODO: This is broken, one cannot generally set properties on files, + # as other targets may require different properties on the same files. + if 'asm' in sources and cflags_asm: + SetFilesProperty(out, sources['asm'], 'COMPILE_FLAGS', cflags_asm, ' ') + if 'c' in sources and cflags_c: + SetFilesProperty(out, sources['c'], 'COMPILE_FLAGS', cflags_c, ' ') + if 'cxx' in sources and cflags_cxx: + SetFilesProperty(out, sources['cxx'], 'COMPILE_FLAGS', cflags_cxx, ' ') + if flags: + SetCurrentTargetProperty(out, 'COMPILE_FLAGS', flags, ' ') + + # Linker flags + ldflags = target.properties.get('ldflags', []) + if ldflags: + SetCurrentTargetProperty(out, 'LINK_FLAGS', ldflags, ' ') + + +gn_target_types_that_absorb_objects = ( + 'executable', + 'loadable_module', + 'shared_library', + 'static_library' +) + + +def WriteSourceVariables(out, target, project): + # gn separates the sheep from the goats based on file extensions. + # A full separation is done here because of flag handing (see Compile flags). + source_types = {'cxx':[], 'c':[], 'asm':[], + 'obj':[], 'obj_target':[], 'input':[], 'other':[]} + + # TODO .def files on Windows + for source in target.properties.get('sources', []): + _, ext = posixpath.splitext(source) + source_abs_path = project.GetAbsolutePath(source) + source_types[source_file_types.get(ext, 'other')].append(source_abs_path) + + for input_path in target.properties.get('inputs', []): + input_abs_path = project.GetAbsolutePath(input_path) + source_types['input'].append(input_abs_path) + + # OBJECT library dependencies need to be listed as sources. + # Only executables and non-OBJECT libraries may reference an OBJECT library. + # https://gitlab.kitware.com/cmake/cmake/issues/14778 + if target.gn_type in gn_target_types_that_absorb_objects: + object_dependencies = set() + project.GetObjectSourceDependencies(target.gn_name, object_dependencies) + for dependency in object_dependencies: + cmake_dependency_name = GetCMakeTargetName(dependency) + obj_target_sources = '$' + source_types['obj_target'].append(obj_target_sources) + + sources = {} + for source_type, sources_of_type in source_types.items(): + if sources_of_type: + sources[source_type] = '${target}__' + source_type + '_srcs' + SetVariableList(out, sources[source_type], sources_of_type) + return sources + + +def WriteTarget(out, target, project): + out.write('\n#') + out.write(target.gn_name) + out.write('\n') + + if target.cmake_type is None: + print('Target {} has unknown target type {}, skipping.'.format( + target.gn_name, target.gn_type)) + return + + SetVariable(out, 'target', target.cmake_name) + + sources = WriteSourceVariables(out, target, project) + + synthetic_dependencies = set() + if target.gn_type == 'action': + WriteAction(out, target, project, sources, synthetic_dependencies) + if target.gn_type == 'action_foreach': + WriteActionForEach(out, target, project, sources, synthetic_dependencies) + if target.gn_type == 'copy': + WriteCopy(out, target, project, sources, synthetic_dependencies) + + out.write(target.cmake_type.command) + out.write('("${target}"') + if target.cmake_type.modifier is not None: + out.write(' ') + out.write(target.cmake_type.modifier) + for sources_type_name in sources.values(): + WriteVariable(out, sources_type_name, ' ') + if synthetic_dependencies: + out.write(' DEPENDS') + for synthetic_dependencie in synthetic_dependencies: + WriteVariable(out, synthetic_dependencie, ' ') + out.write(')\n') + + if target.cmake_type.command != 'add_custom_target': + WriteCompilerFlags(out, target, project, sources) + + libraries = set() + nonlibraries = set() + + dependencies = set(target.properties.get('deps', [])) + # Transitive OBJECT libraries are in sources. + # Those sources are dependent on the OBJECT library dependencies. + # Those sources cannot bring in library dependencies. + object_dependencies = set() + if target.gn_type != 'source_set': + project.GetObjectLibraryDependencies(target.gn_name, object_dependencies) + for object_dependency in object_dependencies: + dependencies.update(project.targets.get(object_dependency).get('deps', [])) + + for dependency in dependencies: + gn_dependency_type = project.targets.get(dependency, {}).get('type', None) + cmake_dependency_type = cmake_target_types.get(gn_dependency_type, None) + cmake_dependency_name = GetCMakeTargetName(dependency) + if cmake_dependency_type.command != 'add_library': + nonlibraries.add(cmake_dependency_name) + elif cmake_dependency_type.modifier != 'OBJECT': + if target.cmake_type.is_linkable: + libraries.add(cmake_dependency_name) + else: + nonlibraries.add(cmake_dependency_name) + + # Non-library dependencies. + if nonlibraries: + out.write('add_dependencies("${target}"') + for nonlibrary in nonlibraries: + out.write('\n "') + out.write(nonlibrary) + out.write('"') + out.write(')\n') + + # Non-OBJECT library dependencies. + external_libraries = target.properties.get('libs', []) + if target.cmake_type.is_linkable and (external_libraries or libraries): + library_dirs = target.properties.get('lib_dirs', []) + if library_dirs: + SetVariableList(out, '${target}__library_directories', library_dirs) + + system_libraries = [] + for external_library in external_libraries: + if '/' in external_library: + libraries.add(project.GetAbsolutePath(external_library)) + else: + if external_library.endswith('.framework'): + external_library = external_library[:-len('.framework')] + system_library = 'library__' + external_library + if library_dirs: + system_library = system_library + '__for_${target}' + out.write('find_library("') + out.write(CMakeStringEscape(system_library)) + out.write('" "') + out.write(CMakeStringEscape(external_library)) + out.write('"') + if library_dirs: + out.write(' PATHS "') + WriteVariable(out, '${target}__library_directories') + out.write('"') + out.write(')\n') + system_libraries.append(system_library) + out.write('target_link_libraries("${target}"') + for library in libraries: + out.write('\n "') + out.write(CMakeStringEscape(library)) + out.write('"') + for system_library in system_libraries: + WriteVariable(out, system_library, '\n "') + out.write('"') + out.write(')\n') + + +def WriteProject(project): + out = open(posixpath.join(project.build_path, 'CMakeLists.txt'), 'w+') + out.write('# Generated by gn_to_cmake.py.\n') + out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') + out.write('cmake_policy(VERSION 2.8.8)\n\n') + + # Update the gn generated ninja build. + # If a build file has changed, this will update CMakeLists.ext if + # gn gen out/config --ide=json --json-ide-script=../../gn/gn_to_cmake.py + # style was used to create this config. + out.write('execute_process(COMMAND ninja -C "') + out.write(CMakeStringEscape(project.build_path)) + out.write('" build.ninja)\n') + + out.write('include(CMakeLists.ext)\n') + out.close() + + out = open(posixpath.join(project.build_path, 'CMakeLists.ext'), 'w+') + out.write('# Generated by gn_to_cmake.py.\n') + out.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') + out.write('cmake_policy(VERSION 2.8.8)\n') + + # The following appears to be as-yet undocumented. + # http://public.kitware.com/Bug/view.php?id=8392 + out.write('enable_language(ASM)\n\n') + # ASM-ATT does not support .S files. + # output.write('enable_language(ASM-ATT)\n') + + # Current issues with automatic re-generation: + # The gn generated build.ninja target uses build.ninja.d + # but build.ninja.d does not contain the ide or gn. + # Currently the ide is not run if the project.json file is not changed + # but the ide needs to be run anyway if it has itself changed. + # This can be worked around by deleting the project.json file. + out.write('file(READ "') + gn_deps_file = posixpath.join(project.build_path, 'build.ninja.d') + out.write(CMakeStringEscape(gn_deps_file)) + out.write('" "gn_deps_string" OFFSET ') + out.write(str(len('build.ninja: '))) + out.write(')\n') + # One would think this would need to worry about escaped spaces + # but gn doesn't escape spaces here (it generates invalid .d files). + out.write('string(REPLACE " " ";" "gn_deps" ${gn_deps_string})\n') + out.write('foreach("gn_dep" ${gn_deps})\n') + out.write(' configure_file(${gn_dep} "CMakeLists.devnull" COPYONLY)\n') + out.write('endforeach("gn_dep")\n') + + for target_name in project.targets.keys(): + out.write('\n') + WriteTarget(out, Target(target_name, project), project) + + +def main(): + if len(sys.argv) != 2: + print('Usage: ' + sys.argv[0] + ' ') + exit(1) + + json_path = sys.argv[1] + project = None + with open(json_path, 'r') as json_file: + project = json.loads(json_file.read()) + + WriteProject(Project(project)) + + +if __name__ == "__main__": + main() diff --git a/third_party/libwebrtc/build/android/gradle/java.jinja b/third_party/libwebrtc/build/android/gradle/java.jinja new file mode 100644 index 0000000000..7626f61f7a --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/java.jinja @@ -0,0 +1,41 @@ +{# 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. #} +// Generated by //build/android/generate_gradle.py + +apply plugin: "java" +{% if template_type == 'java_binary' %} +apply plugin: "application" +{% endif %} + +sourceSets { + main { + java.srcDirs = [ +{% for path in main.java_dirs %} + "{{ path }}", +{% endfor %} + ] +{% if main.java_excludes is defined %} + java.filter.exclude([ +{% for path in main.java_excludes %} + "{{ path }}", +{% endfor %} + ]) +{% endif %} + } +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +{% if template_type == 'java_binary' %} +applicationName = "{{ target_name }}" +{% if main_class %} +mainClassName = "{{ main_class }}" +{% endif %} +{% endif %} +{% if template_type in ('java_binary', 'java_library') %} +archivesBaseName = "{{ target_name }}" +{% endif %} + +{% include 'dependencies.jinja' %} diff --git a/third_party/libwebrtc/build/android/gradle/manifest.jinja b/third_party/libwebrtc/build/android/gradle/manifest.jinja new file mode 100644 index 0000000000..dea7071eb6 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/manifest.jinja @@ -0,0 +1,7 @@ +{# Copyright 2017 The Chromium 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/third_party/libwebrtc/build/android/gradle/root.jinja b/third_party/libwebrtc/build/android/gradle/root.jinja new file mode 100644 index 0000000000..15b5e10184 --- /dev/null +++ b/third_party/libwebrtc/build/android/gradle/root.jinja @@ -0,0 +1,26 @@ +{# 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. #} +// Generated by //build/android/generate_gradle.py + +buildscript { + repositories { + google() + jcenter() +{% if channel == 'canary' %} + // Workaround for http://b/144885480. + //maven() { + // url "http://dl.bintray.com/kotlin/kotlin-eap" + //} +{% endif %} + } + dependencies { +{% if channel == 'canary' %} + classpath "com.android.tools.build:gradle:4.1.0-beta01" +{% elif channel == 'beta' %} + classpath "com.android.tools.build:gradle:4.0.0-rc01" +{% else %} + classpath "com.android.tools.build:gradle:4.0.1" +{% endif %} + } +} diff --git a/third_party/libwebrtc/build/android/gtest_apk/BUILD.gn b/third_party/libwebrtc/build/android/gtest_apk/BUILD.gn new file mode 100644 index 0000000000..2a72bc47ed --- /dev/null +++ b/third_party/libwebrtc/build/android/gtest_apk/BUILD.gn @@ -0,0 +1,15 @@ +# Copyright 2020 The Chromium 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") + +android_library("native_test_instrumentation_test_runner_java") { + testonly = true + sources = [ + "java/src/org/chromium/build/gtest_apk/NativeTestInstrumentationTestRunner.java", + "java/src/org/chromium/build/gtest_apk/NativeTestIntent.java", + "java/src/org/chromium/build/gtest_apk/TestStatusIntent.java", + "java/src/org/chromium/build/gtest_apk/TestStatusReceiver.java", + ] +} diff --git a/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestInstrumentationTestRunner.java b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestInstrumentationTestRunner.java new file mode 100644 index 0000000000..652333bdd8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestInstrumentationTestRunner.java @@ -0,0 +1,281 @@ +// 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.build.gtest_apk; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Instrumentation; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Process; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An Instrumentation that runs tests based on NativeTest. + */ +public class NativeTestInstrumentationTestRunner extends Instrumentation { + private static final String EXTRA_NATIVE_TEST_ACTIVITY = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity"; + private static final String EXTRA_SHARD_NANO_TIMEOUT = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout"; + private static final String EXTRA_SHARD_SIZE_LIMIT = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardSizeLimit"; + private static final String EXTRA_STDOUT_FILE = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.StdoutFile"; + private static final String EXTRA_TEST_LIST_FILE = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.TestList"; + private static final String EXTRA_TEST = + "org.chromium.native_test.NativeTestInstrumentationTestRunner.Test"; + + private static final String TAG = "NativeTest"; + + private static final long DEFAULT_SHARD_NANO_TIMEOUT = 60 * 1000000000L; + // Default to no size limit. + private static final int DEFAULT_SHARD_SIZE_LIMIT = 0; + + private Handler mHandler = new Handler(); + private Bundle mLogBundle = new Bundle(); + private SparseArray mMonitors = new SparseArray(); + private String mNativeTestActivity; + private TestStatusReceiver mReceiver; + private Queue mShards = new ArrayDeque(); + private long mShardNanoTimeout = DEFAULT_SHARD_NANO_TIMEOUT; + private int mShardSizeLimit = DEFAULT_SHARD_SIZE_LIMIT; + private File mStdoutFile; + private Bundle mTransparentArguments; + + @Override + public void onCreate(Bundle arguments) { + Context context = getContext(); + mTransparentArguments = new Bundle(arguments); + + mNativeTestActivity = arguments.getString(EXTRA_NATIVE_TEST_ACTIVITY); + if (mNativeTestActivity == null) { + Log.e(TAG, + "Unable to find org.chromium.native_test.NativeUnitTestActivity extra on " + + "NativeTestInstrumentationTestRunner launch intent."); + finish(Activity.RESULT_CANCELED, new Bundle()); + return; + } + mTransparentArguments.remove(EXTRA_NATIVE_TEST_ACTIVITY); + + String shardNanoTimeout = arguments.getString(EXTRA_SHARD_NANO_TIMEOUT); + if (shardNanoTimeout != null) mShardNanoTimeout = Long.parseLong(shardNanoTimeout); + mTransparentArguments.remove(EXTRA_SHARD_NANO_TIMEOUT); + + String shardSizeLimit = arguments.getString(EXTRA_SHARD_SIZE_LIMIT); + if (shardSizeLimit != null) mShardSizeLimit = Integer.parseInt(shardSizeLimit); + mTransparentArguments.remove(EXTRA_SHARD_SIZE_LIMIT); + + String stdoutFile = arguments.getString(EXTRA_STDOUT_FILE); + if (stdoutFile != null) { + mStdoutFile = new File(stdoutFile); + } else { + try { + mStdoutFile = File.createTempFile( + ".temp_stdout_", ".txt", Environment.getExternalStorageDirectory()); + Log.i(TAG, "stdout file created: " + mStdoutFile.getAbsolutePath()); + } catch (IOException e) { + Log.e(TAG, "Unable to create temporary stdout file.", e); + finish(Activity.RESULT_CANCELED, new Bundle()); + return; + } + } + + mTransparentArguments.remove(EXTRA_STDOUT_FILE); + + String singleTest = arguments.getString(EXTRA_TEST); + if (singleTest != null) { + mShards.add(singleTest); + } + + String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE); + if (testListFilePath != null) { + File testListFile = new File(testListFilePath); + try { + BufferedReader testListFileReader = + new BufferedReader(new FileReader(testListFile)); + + String test; + ArrayList workingShard = new ArrayList(); + while ((test = testListFileReader.readLine()) != null) { + workingShard.add(test); + if (workingShard.size() == mShardSizeLimit) { + mShards.add(TextUtils.join(":", workingShard)); + workingShard = new ArrayList(); + } + } + + if (!workingShard.isEmpty()) { + mShards.add(TextUtils.join(":", workingShard)); + } + + testListFileReader.close(); + } catch (IOException e) { + Log.e(TAG, "Error reading " + testListFile.getAbsolutePath(), e); + } + } + mTransparentArguments.remove(EXTRA_TEST_LIST_FILE); + + start(); + } + + @Override + @SuppressLint("DefaultLocale") + public void onStart() { + super.onStart(); + + mReceiver = new TestStatusReceiver(); + mReceiver.register(getContext()); + mReceiver.registerCallback(new TestStatusReceiver.TestRunCallback() { + @Override + public void testRunStarted(int pid) { + if (pid != Process.myPid()) { + ShardMonitor m = new ShardMonitor(pid, System.nanoTime() + mShardNanoTimeout); + mMonitors.put(pid, m); + mHandler.post(m); + } + } + + @Override + public void testRunFinished(int pid) { + ShardMonitor m = mMonitors.get(pid); + if (m != null) { + m.stopped(); + mMonitors.remove(pid); + } + mHandler.post(new ShardEnder(pid)); + } + + @Override + public void uncaughtException(int pid, String stackTrace) { + mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("Uncaught exception in test process (pid: %d)%n%s%n", pid, + stackTrace)); + sendStatus(0, mLogBundle); + } + }); + + mHandler.post(new ShardStarter()); + } + + /** Monitors a test shard's execution. */ + private class ShardMonitor implements Runnable { + private static final int MONITOR_FREQUENCY_MS = 1000; + + private long mExpirationNanoTime; + private int mPid; + private AtomicBoolean mStopped; + + public ShardMonitor(int pid, long expirationNanoTime) { + mPid = pid; + mExpirationNanoTime = expirationNanoTime; + mStopped = new AtomicBoolean(false); + } + + public void stopped() { + mStopped.set(true); + } + + @Override + public void run() { + if (mStopped.get()) { + return; + } + + if (isAppProcessAlive(getContext(), mPid)) { + if (System.nanoTime() > mExpirationNanoTime) { + Log.e(TAG, String.format("Test process %d timed out.", mPid)); + mHandler.post(new ShardEnder(mPid)); + return; + } else { + mHandler.postDelayed(this, MONITOR_FREQUENCY_MS); + return; + } + } + + Log.e(TAG, String.format("Test process %d died unexpectedly.", mPid)); + mHandler.post(new ShardEnder(mPid)); + } + } + + private static boolean isAppProcessAlive(Context context, int pid) { + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningAppProcessInfo processInfo : + activityManager.getRunningAppProcesses()) { + if (processInfo.pid == pid) return true; + } + return false; + } + + protected Intent createShardMainIntent() { + Intent i = new Intent(Intent.ACTION_MAIN); + i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity)); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + i.putExtras(mTransparentArguments); + if (mShards != null && !mShards.isEmpty()) { + String gtestFilter = mShards.remove(); + i.putExtra(NativeTestIntent.EXTRA_GTEST_FILTER, gtestFilter); + } + i.putExtra(NativeTestIntent.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath()); + return i; + } + + /** + * Starts the NativeTest Activity. + */ + private class ShardStarter implements Runnable { + @Override + public void run() { + getContext().startActivity(createShardMainIntent()); + } + } + + private class ShardEnder implements Runnable { + private static final int WAIT_FOR_DEATH_MILLIS = 10; + + private int mPid; + + public ShardEnder(int pid) { + mPid = pid; + } + + @Override + public void run() { + if (mPid != Process.myPid()) { + Process.killProcess(mPid); + try { + while (isAppProcessAlive(getContext(), mPid)) { + Thread.sleep(WAIT_FOR_DEATH_MILLIS); + } + } catch (InterruptedException e) { + Log.e(TAG, String.format("%d may still be alive.", mPid), e); + } + } + if (mShards != null && !mShards.isEmpty()) { + mHandler.post(new ShardStarter()); + } else { + finish(Activity.RESULT_OK, new Bundle()); + } + } + } +} diff --git a/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestIntent.java b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestIntent.java new file mode 100644 index 0000000000..a875e9740e --- /dev/null +++ b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/NativeTestIntent.java @@ -0,0 +1,22 @@ +// Copyright 2020 The Chromium 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.build.gtest_apk; + +/** + * Extras for intent sent by NativeTestInstrumentationTestRunner. + */ +public class NativeTestIntent { + public static final String EXTRA_COMMAND_LINE_FILE = + "org.chromium.native_test.NativeTest.CommandLineFile"; + public static final String EXTRA_COMMAND_LINE_FLAGS = + "org.chromium.native_test.NativeTest.CommandLineFlags"; + public static final String EXTRA_RUN_IN_SUB_THREAD = + "org.chromium.native_test.NativeTest.RunInSubThread"; + public static final String EXTRA_GTEST_FILTER = + "org.chromium.native_test.NativeTest.GtestFilter"; + public static final String EXTRA_STDOUT_FILE = "org.chromium.native_test.NativeTest.StdoutFile"; + public static final String EXTRA_COVERAGE_DEVICE_FILE = + "org.chromium.native_test.NativeTest.CoverageDeviceFile"; +} diff --git a/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusIntent.java b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusIntent.java new file mode 100644 index 0000000000..520b7485b7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusIntent.java @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium 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.build.gtest_apk; + +/** + * Intent action and extras of broadcasts intercepted by TestStatusReceiver. + */ +public class TestStatusIntent { + public static final String ACTION_TEST_RUN_STARTED = + "org.chromium.test.reporter.TestStatusReporter.TEST_RUN_STARTED"; + public static final String ACTION_TEST_RUN_FINISHED = + "org.chromium.test.reporter.TestStatusReporter.TEST_RUN_FINISHED"; + public static final String ACTION_UNCAUGHT_EXCEPTION = + "org.chromium.test.reporter.TestStatusReporter.UNCAUGHT_EXCEPTION"; + public static final String DATA_TYPE_RESULT = "org.chromium.test.reporter/result"; + public static final String EXTRA_PID = "org.chromium.test.reporter.TestStatusReporter.PID"; + public static final String EXTRA_STACK_TRACE = + "org.chromium.test.reporter.TestStatusReporter.STACK_TRACE"; +} diff --git a/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusReceiver.java b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusReceiver.java new file mode 100644 index 0000000000..e53900944e --- /dev/null +++ b/third_party/libwebrtc/build/android/gtest_apk/java/src/org/chromium/build/gtest_apk/TestStatusReceiver.java @@ -0,0 +1,89 @@ +// 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.build.gtest_apk; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + Receives test status broadcasts sent from + {@link org.chromium.test.reporter.TestStatusReporter}. + */ +public class TestStatusReceiver extends BroadcastReceiver { + private static final String TAG = "test_reporter"; + + private final List mTestRunCallbacks = new ArrayList(); + + /** An IntentFilter that matches the intents that this class can receive. */ + private static final IntentFilter INTENT_FILTER; + static { + IntentFilter filter = new IntentFilter(); + filter.addAction(TestStatusIntent.ACTION_TEST_RUN_STARTED); + filter.addAction(TestStatusIntent.ACTION_TEST_RUN_FINISHED); + filter.addAction(TestStatusIntent.ACTION_UNCAUGHT_EXCEPTION); + try { + filter.addDataType(TestStatusIntent.DATA_TYPE_RESULT); + } catch (IntentFilter.MalformedMimeTypeException e) { + Log.wtf(TAG, "Invalid MIME type", e); + } + INTENT_FILTER = filter; + } + + /** A callback used when a test run has started or finished. */ + public interface TestRunCallback { + void testRunStarted(int pid); + void testRunFinished(int pid); + void uncaughtException(int pid, String stackTrace); + } + + /** Register a callback for when a test run has started or finished. */ + public void registerCallback(TestRunCallback c) { + mTestRunCallbacks.add(c); + } + + /** Register this receiver using the provided context. */ + public void register(Context c) { + c.registerReceiver(this, INTENT_FILTER); + } + + /** + * Receive a broadcast intent. + * + * @param context The Context in which the receiver is running. + * @param intent The intent received. + */ + @Override + public void onReceive(Context context, Intent intent) { + int pid = intent.getIntExtra(TestStatusIntent.EXTRA_PID, 0); + String stackTrace = intent.getStringExtra(TestStatusIntent.EXTRA_STACK_TRACE); + + switch (intent.getAction()) { + case TestStatusIntent.ACTION_TEST_RUN_STARTED: + for (TestRunCallback c : mTestRunCallbacks) { + c.testRunStarted(pid); + } + break; + case TestStatusIntent.ACTION_TEST_RUN_FINISHED: + for (TestRunCallback c : mTestRunCallbacks) { + c.testRunFinished(pid); + } + break; + case TestStatusIntent.ACTION_UNCAUGHT_EXCEPTION: + for (TestRunCallback c : mTestRunCallbacks) { + c.uncaughtException(pid, stackTrace); + } + break; + default: + Log.e(TAG, "Unrecognized intent received: " + intent.toString()); + break; + } + } +} diff --git a/third_party/libwebrtc/build/android/gyp/OWNERS b/third_party/libwebrtc/build/android/gyp/OWNERS new file mode 100644 index 0000000000..25557e1fc5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/OWNERS @@ -0,0 +1,4 @@ +agrieve@chromium.org +digit@chromium.org +smaier@chromium.org +wnwen@chromium.org diff --git a/third_party/libwebrtc/build/android/gyp/aar.py b/third_party/libwebrtc/build/android/gyp/aar.py new file mode 100755 index 0000000000..b157cd816f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/aar.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# +# 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. + +"""Processes an Android AAR file.""" + +import argparse +import os +import posixpath +import re +import shutil +import sys +from xml.etree import ElementTree +import zipfile + +from util import build_utils + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir))) +import gn_helpers + + +_PROGUARD_TXT = 'proguard.txt' + + +def _GetManifestPackage(doc): + """Returns the package specified in the manifest. + + Args: + doc: an XML tree parsed by ElementTree + + Returns: + String representing the package name. + """ + return doc.attrib['package'] + + +def _IsManifestEmpty(doc): + """Decides whether the given manifest has merge-worthy elements. + + E.g.: , , etc. + + Args: + doc: an XML tree parsed by ElementTree + + Returns: + Whether the manifest has merge-worthy elements. + """ + for node in doc: + if node.tag == 'application': + if list(node): + return False + elif node.tag != 'uses-sdk': + return False + + return True + + +def _CreateInfo(aar_file): + """Extracts and return .info data from an .aar file. + + Args: + aar_file: Path to an input .aar file. + + Returns: + A dict containing .info data. + """ + data = {} + data['aidl'] = [] + data['assets'] = [] + data['resources'] = [] + data['subjars'] = [] + data['subjar_tuples'] = [] + data['has_classes_jar'] = False + data['has_proguard_flags'] = False + data['has_native_libraries'] = False + data['has_r_text_file'] = False + with zipfile.ZipFile(aar_file) as z: + manifest_xml = ElementTree.fromstring(z.read('AndroidManifest.xml')) + data['is_manifest_empty'] = _IsManifestEmpty(manifest_xml) + manifest_package = _GetManifestPackage(manifest_xml) + if manifest_package: + data['manifest_package'] = manifest_package + + for name in z.namelist(): + if name.endswith('/'): + continue + if name.startswith('aidl/'): + data['aidl'].append(name) + elif name.startswith('res/'): + data['resources'].append(name) + elif name.startswith('libs/') and name.endswith('.jar'): + label = posixpath.basename(name)[:-4] + label = re.sub(r'[^a-zA-Z0-9._]', '_', label) + data['subjars'].append(name) + data['subjar_tuples'].append([label, name]) + elif name.startswith('assets/'): + data['assets'].append(name) + elif name.startswith('jni/'): + data['has_native_libraries'] = True + if 'native_libraries' in data: + data['native_libraries'].append(name) + else: + data['native_libraries'] = [name] + elif name == 'classes.jar': + data['has_classes_jar'] = True + elif name == _PROGUARD_TXT: + data['has_proguard_flags'] = True + elif name == 'R.txt': + # Some AARs, e.g. gvr_controller_java, have empty R.txt. Such AARs + # have no resources as well. We treat empty R.txt as having no R.txt. + data['has_r_text_file'] = bool(z.read('R.txt').strip()) + + return data + + +def _PerformExtract(aar_file, output_dir, name_allowlist): + with build_utils.TempDir() as tmp_dir: + tmp_dir = os.path.join(tmp_dir, 'staging') + os.mkdir(tmp_dir) + build_utils.ExtractAll( + aar_file, path=tmp_dir, predicate=name_allowlist.__contains__) + # Write a breadcrumb so that SuperSize can attribute files back to the .aar. + with open(os.path.join(tmp_dir, 'source.info'), 'w') as f: + f.write('source={}\n'.format(aar_file)) + + shutil.rmtree(output_dir, ignore_errors=True) + shutil.move(tmp_dir, output_dir) + + +def _AddCommonArgs(parser): + parser.add_argument( + 'aar_file', help='Path to the AAR file.', type=os.path.normpath) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + command_parsers = parser.add_subparsers(dest='command') + subp = command_parsers.add_parser( + 'list', help='Output a GN scope describing the contents of the .aar.') + _AddCommonArgs(subp) + subp.add_argument('--output', help='Output file.', default='-') + + subp = command_parsers.add_parser('extract', help='Extracts the .aar') + _AddCommonArgs(subp) + subp.add_argument( + '--output-dir', + help='Output directory for the extracted files.', + required=True, + type=os.path.normpath) + subp.add_argument( + '--assert-info-file', + help='Path to .info file. Asserts that it matches what ' + '"list" would output.', + type=argparse.FileType('r')) + subp.add_argument( + '--ignore-resources', + action='store_true', + help='Whether to skip extraction of res/') + + args = parser.parse_args() + + aar_info = _CreateInfo(args.aar_file) + formatted_info = """\ +# Generated by //build/android/gyp/aar.py +# To regenerate, use "update_android_aar_prebuilts = true" and run "gn gen". + +""" + gn_helpers.ToGNString(aar_info, pretty=True) + + if args.command == 'extract': + if args.assert_info_file: + cached_info = args.assert_info_file.read() + if formatted_info != cached_info: + raise Exception('android_aar_prebuilt() cached .info file is ' + 'out-of-date. Run gn gen with ' + 'update_android_aar_prebuilts=true to update it.') + + with zipfile.ZipFile(args.aar_file) as zf: + names = zf.namelist() + if args.ignore_resources: + names = [n for n in names if not n.startswith('res')] + + _PerformExtract(args.aar_file, args.output_dir, set(names)) + + elif args.command == 'list': + aar_output_present = args.output != '-' and os.path.isfile(args.output) + if aar_output_present: + # Some .info files are read-only, for examples the cipd-controlled ones + # under third_party/android_deps/repositoty. To deal with these, first + # that its content is correct, and if it is, exit without touching + # the file system. + file_info = open(args.output, 'r').read() + if file_info == formatted_info: + return + + # Try to write the file. This may fail for read-only ones that were + # not updated. + try: + with open(args.output, 'w') as f: + f.write(formatted_info) + except IOError as e: + if not aar_output_present: + raise e + raise Exception('Could not update output file: %s\n%s\n' % + (args.output, e)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/aar.pydeps b/third_party/libwebrtc/build/android/gyp/aar.pydeps new file mode 100644 index 0000000000..7e2924b34c --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/aar.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/aar.pydeps build/android/gyp/aar.py +../../gn_helpers.py +aar.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/aidl.py b/third_party/libwebrtc/build/android/gyp/aidl.py new file mode 100755 index 0000000000..b8099aaecd --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/aidl.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# +# 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 re +import sys +import zipfile + +from util import build_utils + + +def main(argv): + option_parser = optparse.OptionParser() + 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.') + build_utils.AddDepfileOption(option_parser) + options, args = option_parser.parse_args(argv[1:]) + + options.includes = build_utils.ParseGnList(options.includes) + + 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.ParseGnList(options.imports) + ] + aidl_cmd += ['-I' + s for s in options.includes] + aidl_cmd += [ + f, + output + ] + build_utils.CheckOutput(aidl_cmd) + + with build_utils.AtomicOutput(options.srcjar) as f: + with zipfile.ZipFile(f, 'w') as srcjar: + for path in build_utils.FindInDirectory(temp_dir, '*.java'): + with open(path) as fileobj: + data = fileobj.read() + pkg_name = re.search(r'^\s*package\s+(.*?)\s*;', data, re.M).group(1) + arcname = '%s/%s' % ( + pkg_name.replace('.', '/'), os.path.basename(path)) + build_utils.AddToZipHermetic(srcjar, arcname, data=data) + + if options.depfile: + include_files = [] + for include_dir in options.includes: + include_files += build_utils.FindInDirectory(include_dir, '*.java') + build_utils.WriteDepfile(options.depfile, options.srcjar, include_files) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/third_party/libwebrtc/build/android/gyp/aidl.pydeps b/third_party/libwebrtc/build/android/gyp/aidl.pydeps new file mode 100644 index 0000000000..11c55ed4b6 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/aidl.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/aidl.pydeps build/android/gyp/aidl.py +../../gn_helpers.py +aidl.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/allot_native_libraries.py b/third_party/libwebrtc/build/android/gyp/allot_native_libraries.py new file mode 100755 index 0000000000..978b173403 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/allot_native_libraries.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Allots libraries to modules to be packaged into. + +All libraries that are depended on by a single module will be allotted to this +module. All other libraries will be allotted to the closest ancestor. + +Example: + Given the module dependency structure + + c + / \ + b d + / \ + a e + + and libraries assignment + + a: ['lib1.so'] + e: ['lib2.so', 'lib1.so'] + + will make the allotment decision + + c: ['lib1.so'] + e: ['lib2.so'] + + The above example is invoked via: + + ./allot_native_libraries \ + --libraries 'a,["1.so"]' \ + --libraries 'e,["2.so", "1.so"]' \ + --dep c:b \ + --dep b:a \ + --dep c:d \ + --dep d:e \ + --output +""" + +import argparse +import collections +import json +import sys + +from util import build_utils + + +def _ModuleLibrariesPair(arg): + pos = arg.find(',') + assert pos > 0 + return (arg[:pos], arg[pos + 1:]) + + +def _DepPair(arg): + parent, child = arg.split(':') + return (parent, child) + + +def _PathFromRoot(module_tree, module): + """Computes path from root to a module. + + Parameters: + module_tree: Dictionary mapping each module to its parent. + module: Module to which to compute the path. + + Returns: + Path from root the the module. + """ + path = [module] + while module_tree.get(module): + module = module_tree[module] + path = [module] + path + return path + + +def _ClosestCommonAncestor(module_tree, modules): + """Computes the common ancestor of a set of modules. + + Parameters: + module_tree: Dictionary mapping each module to its parent. + modules: Set of modules for which to find the closest common ancestor. + + Returns: + The closest common ancestor. + """ + paths = [_PathFromRoot(module_tree, m) for m in modules] + assert len(paths) > 0 + ancestor = None + for level in zip(*paths): + if len(set(level)) != 1: + return ancestor + ancestor = level[0] + return ancestor + + +def _AllotLibraries(module_tree, libraries_map): + """Allot all libraries to a module. + + Parameters: + module_tree: Dictionary mapping each module to its parent. Modules can map + to None, which is considered the root of the tree. + libraries_map: Dictionary mapping each library to a set of modules, which + depend on the library. + + Returns: + A dictionary mapping mapping each module name to a set of libraries allotted + to the module such that libraries with multiple dependees are allotted to + the closest ancestor. + + Raises: + Exception if some libraries can only be allotted to the None root. + """ + allotment_map = collections.defaultdict(set) + for library, modules in libraries_map.items(): + ancestor = _ClosestCommonAncestor(module_tree, modules) + if not ancestor: + raise Exception('Cannot allot libraries for given dependency tree') + allotment_map[ancestor].add(library) + return allotment_map + + +def main(args): + parser = argparse.ArgumentParser() + parser.add_argument( + '--libraries', + action='append', + type=_ModuleLibrariesPair, + required=True, + help='A pair of module name and GN list of libraries a module depends ' + 'on. Can be specified multiple times.') + parser.add_argument( + '--output', + required=True, + help='A JSON file with a key for each module mapping to a list of ' + 'libraries, which should be packaged into this module.') + parser.add_argument( + '--dep', + action='append', + type=_DepPair, + dest='deps', + default=[], + help='A pair of parent module name and child module name ' + '(format: ":"). Can be specified multiple times.') + options = parser.parse_args(build_utils.ExpandFileArgs(args)) + options.libraries = [(m, build_utils.ParseGnList(l)) + for m, l in options.libraries] + + # Parse input creating libraries and dependency tree. + libraries_map = collections.defaultdict(set) # Maps each library to its + # dependee modules. + module_tree = {} # Maps each module name to its parent. + for module, libraries in options.libraries: + module_tree[module] = None + for library in libraries: + libraries_map[library].add(module) + for parent, child in options.deps: + if module_tree.get(child): + raise Exception('%s cannot have multiple parents' % child) + module_tree[child] = parent + module_tree[parent] = module_tree.get(parent) + + # Allot all libraries to a module such that libraries with multiple dependees + # are allotted to the closest ancestor. + allotment_map = _AllotLibraries(module_tree, libraries_map) + + # The build system expects there to be a set of libraries even for the modules + # that don't have any libraries allotted. + for module in module_tree: + # Creates missing sets because of defaultdict. + allotment_map[module] = allotment_map[module] + + with open(options.output, 'w') as f: + # Write native libraries config and ensure the output is deterministic. + json.dump({m: sorted(l) + for m, l in allotment_map.items()}, + f, + sort_keys=True, + indent=2) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/allot_native_libraries.pydeps b/third_party/libwebrtc/build/android/gyp/allot_native_libraries.pydeps new file mode 100644 index 0000000000..d8b10cd3da --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/allot_native_libraries.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/allot_native_libraries.pydeps build/android/gyp/allot_native_libraries.py +../../gn_helpers.py +allot_native_libraries.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/apkbuilder.py b/third_party/libwebrtc/build/android/gyp/apkbuilder.py new file mode 100755 index 0000000000..c355fdf88f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/apkbuilder.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 +# +# 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. + +"""Adds the code parts to a resource APK.""" + +import argparse +import logging +import os +import shutil +import sys +import tempfile +import zipfile +import zlib + +import finalize_apk + +from util import build_utils +from util import diff_utils +from util import zipalign + +# Input dex.jar files are zipaligned. +zipalign.ApplyZipFileZipAlignFix() + + +# Taken from aapt's Package.cpp: +_NO_COMPRESS_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.gif', '.wav', '.mp2', + '.mp3', '.ogg', '.aac', '.mpg', '.mpeg', '.mid', + '.midi', '.smf', '.jet', '.rtttl', '.imy', '.xmf', + '.mp4', '.m4a', '.m4v', '.3gp', '.3gpp', '.3g2', + '.3gpp2', '.amr', '.awb', '.wma', '.wmv', '.webm') + + +def _ParseArgs(args): + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument( + '--assets', + help='GYP-list of files to add as assets in the form ' + '"srcPath:zipPath", where ":zipPath" is optional.') + parser.add_argument( + '--java-resources', help='GYP-list of java_resources JARs to include.') + parser.add_argument('--write-asset-list', + action='store_true', + help='Whether to create an assets/assets_list file.') + parser.add_argument( + '--uncompressed-assets', + help='Same as --assets, except disables compression.') + parser.add_argument('--resource-apk', + help='An .ap_ file built using aapt', + required=True) + parser.add_argument('--output-apk', + help='Path to the output file', + required=True) + parser.add_argument('--format', choices=['apk', 'bundle-module'], + default='apk', help='Specify output format.') + parser.add_argument('--dex-file', + help='Path to the classes.dex to use') + parser.add_argument( + '--jdk-libs-dex-file', + help='Path to classes.dex created by dex_jdk_libs.py') + parser.add_argument('--uncompress-dex', action='store_true', + help='Store .dex files uncompressed in the APK') + parser.add_argument('--native-libs', + action='append', + help='GYP-list of native libraries to include. ' + 'Can be specified multiple times.', + default=[]) + parser.add_argument('--secondary-native-libs', + action='append', + help='GYP-list of native libraries for secondary ' + 'android-abi. Can be specified multiple times.', + default=[]) + parser.add_argument('--android-abi', + help='Android architecture to use for native libraries') + parser.add_argument('--secondary-android-abi', + help='The secondary Android architecture to use for' + 'secondary native libraries') + parser.add_argument( + '--is-multi-abi', + action='store_true', + help='Will add a placeholder for the missing ABI if no native libs or ' + 'placeholders are set for either the primary or secondary ABI. Can only ' + 'be set if both --android-abi and --secondary-android-abi are set.') + parser.add_argument( + '--native-lib-placeholders', + help='GYP-list of native library placeholders to add.') + parser.add_argument( + '--secondary-native-lib-placeholders', + help='GYP-list of native library placeholders to add ' + 'for the secondary ABI') + parser.add_argument('--uncompress-shared-libraries', default='False', + choices=['true', 'True', 'false', 'False'], + help='Whether to uncompress native shared libraries. Argument must be ' + 'a boolean value.') + parser.add_argument( + '--apksigner-jar', help='Path to the apksigner executable.') + parser.add_argument('--zipalign-path', + help='Path to the zipalign executable.') + parser.add_argument('--key-path', + help='Path to keystore for signing.') + parser.add_argument('--key-passwd', + help='Keystore password') + parser.add_argument('--key-name', + help='Keystore name') + parser.add_argument( + '--min-sdk-version', required=True, help='Value of APK\'s minSdkVersion') + parser.add_argument( + '--best-compression', + action='store_true', + help='Use zip -9 rather than zip -1') + parser.add_argument( + '--library-always-compress', + action='append', + help='The list of library files that we always compress.') + parser.add_argument( + '--library-renames', + action='append', + help='The list of library files that we prepend crazy. to their names.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + diff_utils.AddCommandLineFlags(parser) + options = parser.parse_args(args) + options.assets = build_utils.ParseGnList(options.assets) + options.uncompressed_assets = build_utils.ParseGnList( + options.uncompressed_assets) + options.native_lib_placeholders = build_utils.ParseGnList( + options.native_lib_placeholders) + options.secondary_native_lib_placeholders = build_utils.ParseGnList( + options.secondary_native_lib_placeholders) + options.java_resources = build_utils.ParseGnList(options.java_resources) + options.native_libs = build_utils.ParseGnList(options.native_libs) + options.secondary_native_libs = build_utils.ParseGnList( + options.secondary_native_libs) + options.library_always_compress = build_utils.ParseGnList( + options.library_always_compress) + options.library_renames = build_utils.ParseGnList(options.library_renames) + + # --apksigner-jar, --zipalign-path, --key-xxx arguments are + # required when building an APK, but not a bundle module. + if options.format == 'apk': + required_args = [ + 'apksigner_jar', 'zipalign_path', 'key_path', 'key_passwd', 'key_name' + ] + for required in required_args: + if not vars(options)[required]: + raise Exception('Argument --%s is required for APKs.' % ( + required.replace('_', '-'))) + + options.uncompress_shared_libraries = \ + options.uncompress_shared_libraries in [ 'true', 'True' ] + + if not options.android_abi and (options.native_libs or + options.native_lib_placeholders): + raise Exception('Must specify --android-abi with --native-libs') + if not options.secondary_android_abi and (options.secondary_native_libs or + options.secondary_native_lib_placeholders): + raise Exception('Must specify --secondary-android-abi with' + ' --secondary-native-libs') + if options.is_multi_abi and not (options.android_abi + and options.secondary_android_abi): + raise Exception('Must specify --is-multi-abi with both --android-abi ' + 'and --secondary-android-abi.') + return options + + +def _SplitAssetPath(path): + """Returns (src, dest) given an asset path in the form src[:dest].""" + path_parts = path.split(':') + src_path = path_parts[0] + if len(path_parts) > 1: + dest_path = path_parts[1] + else: + dest_path = os.path.basename(src_path) + return src_path, dest_path + + +def _ExpandPaths(paths): + """Converts src:dst into tuples and enumerates files within directories. + + Args: + paths: Paths in the form "src_path:dest_path" + + Returns: + A list of (src_path, dest_path) tuples sorted by dest_path (for stable + ordering within output .apk). + """ + ret = [] + for path in paths: + src_path, dest_path = _SplitAssetPath(path) + if os.path.isdir(src_path): + for f in build_utils.FindInDirectory(src_path, '*'): + ret.append((f, os.path.join(dest_path, f[len(src_path) + 1:]))) + else: + ret.append((src_path, dest_path)) + ret.sort(key=lambda t:t[1]) + return ret + + +def _GetAssetsToAdd(path_tuples, + fast_align, + disable_compression=False, + allow_reads=True): + """Returns the list of file_detail tuples for assets in the apk. + + Args: + path_tuples: List of src_path, dest_path tuples to add. + fast_align: Whether to perform alignment in python zipfile (alternatively + alignment can be done using the zipalign utility out of band). + disable_compression: Whether to disable compression. + allow_reads: If false, we do not try to read the files from disk (to find + their size for example). + + Returns: A list of (src_path, apk_path, compress, alignment) tuple + representing what and how assets are added. + """ + assets_to_add = [] + + # Group all uncompressed assets together in the hope that it will increase + # locality of mmap'ed files. + for target_compress in (False, True): + for src_path, dest_path in path_tuples: + compress = not disable_compression and ( + os.path.splitext(src_path)[1] not in _NO_COMPRESS_EXTENSIONS) + + if target_compress == compress: + # AddToZipHermetic() uses this logic to avoid growing small files. + # We need it here in order to set alignment correctly. + if allow_reads and compress and os.path.getsize(src_path) < 16: + compress = False + + apk_path = 'assets/' + dest_path + alignment = 0 if compress and not fast_align else 4 + assets_to_add.append((apk_path, src_path, compress, alignment)) + return assets_to_add + + +def _AddFiles(apk, details): + """Adds files to the apk. + + Args: + apk: path to APK to add to. + details: A list of file detail tuples (src_path, apk_path, compress, + alignment) representing what and how files are added to the APK. + """ + for apk_path, src_path, compress, alignment in details: + # This check is only relevant for assets, but it should not matter if it is + # checked for the whole list of files. + try: + apk.getinfo(apk_path) + # Should never happen since write_build_config.py handles merging. + raise Exception( + 'Multiple targets specified the asset path: %s' % apk_path) + except KeyError: + zipalign.AddToZipHermetic( + apk, + apk_path, + src_path=src_path, + compress=compress, + alignment=alignment) + + +def _GetNativeLibrariesToAdd(native_libs, android_abi, uncompress, fast_align, + lib_always_compress, lib_renames): + """Returns the list of file_detail tuples for native libraries in the apk. + + Returns: A list of (src_path, apk_path, compress, alignment) tuple + representing what and how native libraries are added. + """ + libraries_to_add = [] + + + for path in native_libs: + basename = os.path.basename(path) + compress = not uncompress or any(lib_name in basename + for lib_name in lib_always_compress) + rename = any(lib_name in basename for lib_name in lib_renames) + if rename: + basename = 'crazy.' + basename + + lib_android_abi = android_abi + if path.startswith('android_clang_arm64_hwasan/'): + lib_android_abi = 'arm64-v8a-hwasan' + + apk_path = 'lib/%s/%s' % (lib_android_abi, basename) + alignment = 0 if compress and not fast_align else 0x1000 + libraries_to_add.append((apk_path, path, compress, alignment)) + + return libraries_to_add + + +def _CreateExpectationsData(native_libs, assets): + """Creates list of native libraries and assets.""" + native_libs = sorted(native_libs) + assets = sorted(assets) + + ret = [] + for apk_path, _, compress, alignment in native_libs + assets: + ret.append('apk_path=%s, compress=%s, alignment=%s\n' % + (apk_path, compress, alignment)) + return ''.join(ret) + + +def main(args): + build_utils.InitLogging('APKBUILDER_DEBUG') + args = build_utils.ExpandFileArgs(args) + options = _ParseArgs(args) + + # Until Python 3.7, there's no better way to set compression level. + # The default is 6. + if options.best_compression: + # Compresses about twice as slow as the default. + zlib.Z_DEFAULT_COMPRESSION = 9 + else: + # Compresses about twice as fast as the default. + zlib.Z_DEFAULT_COMPRESSION = 1 + + # Manually align only when alignment is necessary. + # Python's zip implementation duplicates file comments in the central + # directory, whereas zipalign does not, so use zipalign for official builds. + fast_align = options.format == 'apk' and not options.best_compression + + native_libs = sorted(options.native_libs) + + # Include native libs in the depfile_deps since GN doesn't know about the + # dependencies when is_component_build=true. + depfile_deps = list(native_libs) + + # For targets that depend on static library APKs, dex paths are created by + # the static library's dexsplitter target and GN doesn't know about these + # paths. + if options.dex_file: + depfile_deps.append(options.dex_file) + + secondary_native_libs = [] + if options.secondary_native_libs: + secondary_native_libs = sorted(options.secondary_native_libs) + depfile_deps += secondary_native_libs + + if options.java_resources: + # Included via .build_config.json, so need to write it to depfile. + depfile_deps.extend(options.java_resources) + + assets = _ExpandPaths(options.assets) + uncompressed_assets = _ExpandPaths(options.uncompressed_assets) + + # Included via .build_config.json, so need to write it to depfile. + depfile_deps.extend(x[0] for x in assets) + depfile_deps.extend(x[0] for x in uncompressed_assets) + depfile_deps.append(options.resource_apk) + + # Bundle modules have a structure similar to APKs, except that resources + # are compiled in protobuf format (instead of binary xml), and that some + # files are located into different top-level directories, e.g.: + # AndroidManifest.xml -> manifest/AndroidManifest.xml + # classes.dex -> dex/classes.dex + # res/ -> res/ (unchanged) + # assets/ -> assets/ (unchanged) + # -> root/ + # + # Hence, the following variables are used to control the location of files in + # the final archive. + if options.format == 'bundle-module': + apk_manifest_dir = 'manifest/' + apk_root_dir = 'root/' + apk_dex_dir = 'dex/' + else: + apk_manifest_dir = '' + apk_root_dir = '' + apk_dex_dir = '' + + def _GetAssetDetails(assets, uncompressed_assets, fast_align, allow_reads): + ret = _GetAssetsToAdd(assets, + fast_align, + disable_compression=False, + allow_reads=allow_reads) + ret.extend( + _GetAssetsToAdd(uncompressed_assets, + fast_align, + disable_compression=True, + allow_reads=allow_reads)) + return ret + + libs_to_add = _GetNativeLibrariesToAdd( + native_libs, options.android_abi, options.uncompress_shared_libraries, + fast_align, options.library_always_compress, options.library_renames) + if options.secondary_android_abi: + libs_to_add.extend( + _GetNativeLibrariesToAdd( + secondary_native_libs, options.secondary_android_abi, + options.uncompress_shared_libraries, fast_align, + options.library_always_compress, options.library_renames)) + + if options.expected_file: + # We compute expectations without reading the files. This allows us to check + # expectations for different targets by just generating their build_configs + # and not have to first generate all the actual files and all their + # dependencies (for example by just passing --only-verify-expectations). + asset_details = _GetAssetDetails(assets, + uncompressed_assets, + fast_align, + allow_reads=False) + + actual_data = _CreateExpectationsData(libs_to_add, asset_details) + diff_utils.CheckExpectations(actual_data, options) + + if options.only_verify_expectations: + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.actual_file, + inputs=depfile_deps) + return + + # If we are past this point, we are going to actually create the final apk so + # we should recompute asset details again but maybe perform some optimizations + # based on the size of the files on disk. + assets_to_add = _GetAssetDetails( + assets, uncompressed_assets, fast_align, allow_reads=True) + + # Targets generally do not depend on apks, so no need for only_if_changed. + with build_utils.AtomicOutput(options.output_apk, only_if_changed=False) as f: + with zipfile.ZipFile(options.resource_apk) as resource_apk, \ + zipfile.ZipFile(f, 'w') as out_apk: + + def add_to_zip(zip_path, data, compress=True, alignment=4): + zipalign.AddToZipHermetic( + out_apk, + zip_path, + data=data, + compress=compress, + alignment=0 if compress and not fast_align else alignment) + + def copy_resource(zipinfo, out_dir=''): + add_to_zip( + out_dir + zipinfo.filename, + resource_apk.read(zipinfo.filename), + compress=zipinfo.compress_type != zipfile.ZIP_STORED) + + # Make assets come before resources in order to maintain the same file + # ordering as GYP / aapt. http://crbug.com/561862 + resource_infos = resource_apk.infolist() + + # 1. AndroidManifest.xml + logging.debug('Adding AndroidManifest.xml') + copy_resource( + resource_apk.getinfo('AndroidManifest.xml'), out_dir=apk_manifest_dir) + + # 2. Assets + logging.debug('Adding assets/') + _AddFiles(out_apk, assets_to_add) + + # 3. Dex files + logging.debug('Adding classes.dex') + if options.dex_file: + with open(options.dex_file, 'rb') as dex_file_obj: + if options.dex_file.endswith('.dex'): + max_dex_number = 1 + # This is the case for incremental_install=true. + add_to_zip( + apk_dex_dir + 'classes.dex', + dex_file_obj.read(), + compress=not options.uncompress_dex) + else: + max_dex_number = 0 + with zipfile.ZipFile(dex_file_obj) as dex_zip: + for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')): + max_dex_number += 1 + add_to_zip( + apk_dex_dir + dex, + dex_zip.read(dex), + compress=not options.uncompress_dex) + + if options.jdk_libs_dex_file: + with open(options.jdk_libs_dex_file, 'rb') as dex_file_obj: + add_to_zip( + apk_dex_dir + 'classes{}.dex'.format(max_dex_number + 1), + dex_file_obj.read(), + compress=not options.uncompress_dex) + + # 4. Native libraries. + logging.debug('Adding lib/') + _AddFiles(out_apk, libs_to_add) + + # Add a placeholder lib if the APK should be multi ABI but is missing libs + # for one of the ABIs. + native_lib_placeholders = options.native_lib_placeholders + secondary_native_lib_placeholders = ( + options.secondary_native_lib_placeholders) + if options.is_multi_abi: + if ((secondary_native_libs or secondary_native_lib_placeholders) + and not native_libs and not native_lib_placeholders): + native_lib_placeholders += ['libplaceholder.so'] + if ((native_libs or native_lib_placeholders) + and not secondary_native_libs + and not secondary_native_lib_placeholders): + secondary_native_lib_placeholders += ['libplaceholder.so'] + + # Add placeholder libs. + for name in sorted(native_lib_placeholders): + # Note: Empty libs files are ignored by md5check (can cause issues + # with stale builds when the only change is adding/removing + # placeholders). + apk_path = 'lib/%s/%s' % (options.android_abi, name) + add_to_zip(apk_path, '', alignment=0x1000) + + for name in sorted(secondary_native_lib_placeholders): + # Note: Empty libs files are ignored by md5check (can cause issues + # with stale builds when the only change is adding/removing + # placeholders). + apk_path = 'lib/%s/%s' % (options.secondary_android_abi, name) + add_to_zip(apk_path, '', alignment=0x1000) + + # 5. Resources + logging.debug('Adding res/') + for info in sorted(resource_infos, key=lambda i: i.filename): + if info.filename != 'AndroidManifest.xml': + copy_resource(info) + + # 6. Java resources that should be accessible via + # Class.getResourceAsStream(), in particular parts of Emma jar. + # Prebuilt jars may contain class files which we shouldn't include. + logging.debug('Adding Java resources') + for java_resource in options.java_resources: + with zipfile.ZipFile(java_resource, 'r') as java_resource_jar: + for apk_path in sorted(java_resource_jar.namelist()): + apk_path_lower = apk_path.lower() + + if apk_path_lower.startswith('meta-inf/'): + continue + if apk_path_lower.endswith('/'): + continue + if apk_path_lower.endswith('.class'): + continue + + add_to_zip(apk_root_dir + apk_path, + java_resource_jar.read(apk_path)) + + if options.format == 'apk': + zipalign_path = None if fast_align else options.zipalign_path + finalize_apk.FinalizeApk(options.apksigner_jar, + zipalign_path, + f.name, + f.name, + options.key_path, + options.key_passwd, + options.key_name, + int(options.min_sdk_version), + warnings_as_errors=options.warnings_as_errors) + logging.debug('Moving file into place') + + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.output_apk, + inputs=depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/apkbuilder.pydeps b/third_party/libwebrtc/build/android/gyp/apkbuilder.pydeps new file mode 100644 index 0000000000..e6122edd2f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/apkbuilder.pydeps @@ -0,0 +1,9 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/apkbuilder.pydeps build/android/gyp/apkbuilder.py +../../gn_helpers.py +apkbuilder.py +finalize_apk.py +util/__init__.py +util/build_utils.py +util/diff_utils.py +util/zipalign.py diff --git a/third_party/libwebrtc/build/android/gyp/assert_static_initializers.py b/third_party/libwebrtc/build/android/gyp/assert_static_initializers.py new file mode 100755 index 0000000000..9af5e2b825 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/assert_static_initializers.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Chromium 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 number of static initializers in an APK's library.""" + +from __future__ import print_function + +import argparse +import os +import re +import subprocess +import sys +import tempfile +import zipfile + +from util import build_utils + +_DUMP_STATIC_INITIALIZERS_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT, + 'tools', 'linux', + 'dump-static-initializers.py') + + +def _RunReadelf(so_path, options, tool_prefix=''): + return subprocess.check_output([tool_prefix + 'readelf'] + options + + [so_path]).decode('utf8') + + +def _ParseLibBuildId(so_path, tool_prefix): + """Returns the Build ID of the given native library.""" + stdout = _RunReadelf(so_path, ['-n'], tool_prefix) + match = re.search(r'Build ID: (\w+)', stdout) + return match.group(1) if match else None + + +def _VerifyLibBuildIdsMatch(tool_prefix, *so_files): + if len(set(_ParseLibBuildId(f, tool_prefix) for f in so_files)) > 1: + raise Exception('Found differing build ids in output directory and apk. ' + 'Your output directory is likely stale.') + + +def _GetStaticInitializers(so_path, tool_prefix): + output = subprocess.check_output( + [_DUMP_STATIC_INITIALIZERS_PATH, '-d', so_path, '-t', tool_prefix], + encoding='utf-8') + summary = re.search(r'Found \d+ static initializers in (\d+) files.', output) + return output.splitlines()[:-1], int(summary.group(1)) + + +def _PrintDumpSIsCount(apk_so_name, unzipped_so, out_dir, tool_prefix): + lib_name = os.path.basename(apk_so_name).replace('crazy.', '') + so_with_symbols_path = os.path.join(out_dir, 'lib.unstripped', lib_name) + if not os.path.exists(so_with_symbols_path): + raise Exception('Unstripped .so not found. Looked here: %s', + so_with_symbols_path) + _VerifyLibBuildIdsMatch(tool_prefix, unzipped_so, so_with_symbols_path) + sis, _ = _GetStaticInitializers(so_with_symbols_path, tool_prefix) + for si in sis: + print(si) + + +# Mostly copied from //infra/scripts/legacy/scripts/slave/chromium/sizes.py. +def _ReadInitArray(so_path, tool_prefix, expect_no_initializers): + stdout = _RunReadelf(so_path, ['-SW'], tool_prefix) + # Matches: .init_array INIT_ARRAY 000000000516add0 5169dd0 000010 00 WA 0 0 8 + match = re.search(r'\.init_array.*$', stdout, re.MULTILINE) + if expect_no_initializers: + if match: + raise Exception( + 'Expected no initializers for %s, yet some were found' % so_path) + else: + return 0 + elif not match: + raise Exception('Did not find section: .init_array in {}:\n{}'.format( + so_path, stdout)) + size_str = re.split(r'\W+', match.group(0))[5] + return int(size_str, 16) + + +def _CountStaticInitializers(so_path, tool_prefix, expect_no_initializers): + # Find the number of files with at least one static initializer. + # First determine if we're 32 or 64 bit + stdout = _RunReadelf(so_path, ['-h'], tool_prefix) + elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0) + elf_class = re.split(r'\W+', elf_class_line)[1] + if elf_class == 'ELF32': + word_size = 4 + else: + word_size = 8 + + # Then find the number of files with global static initializers. + # NOTE: this is very implementation-specific and makes assumptions + # about how compiler and linker implement global static initializers. + init_array_size = _ReadInitArray(so_path, tool_prefix, expect_no_initializers) + return init_array_size / word_size + + +def _AnalyzeStaticInitializers(apk_or_aab, tool_prefix, dump_sis, out_dir, + ignored_libs, no_initializers_libs): + # Static initializer counting mostly copies logic in + # infra/scripts/legacy/scripts/slave/chromium/sizes.py. + with zipfile.ZipFile(apk_or_aab) as z: + so_files = [ + f for f in z.infolist() if f.filename.endswith('.so') + and f.file_size > 0 and os.path.basename(f.filename) not in ignored_libs + ] + # Skip checking static initializers for secondary abi libs. They will be + # checked by 32-bit bots. This avoids the complexity of finding 32 bit .so + # files in the output directory in 64 bit builds. + has_64 = any('64' in f.filename for f in so_files) + files_to_check = [f for f in so_files if not has_64 or '64' in f.filename] + + # Do not check partitioned libs. They have no ".init_array" section since + # all SIs are considered "roots" by the linker, and so end up in the base + # module. + files_to_check = [ + f for f in files_to_check if not f.filename.endswith('_partition.so') + ] + + si_count = 0 + for f in files_to_check: + lib_basename = os.path.basename(f.filename) + expect_no_initializers = lib_basename in no_initializers_libs + with tempfile.NamedTemporaryFile(prefix=lib_basename) as temp: + temp.write(z.read(f)) + temp.flush() + si_count += _CountStaticInitializers(temp.name, tool_prefix, + expect_no_initializers) + if dump_sis: + # Print count and list of SIs reported by dump-static-initializers.py. + # Doesn't work well on all archs (particularly arm), which is why + # the readelf method is used for tracking SI counts. + _PrintDumpSIsCount(f.filename, temp.name, out_dir, tool_prefix) + return si_count + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--touch', help='File to touch upon success') + parser.add_argument('--tool-prefix', required=True, + help='Prefix for nm and friends') + parser.add_argument('--expected-count', required=True, type=int, + help='Fail if number of static initializers is not ' + 'equal to this value.') + parser.add_argument('apk_or_aab', help='Path to .apk or .aab file.') + args = parser.parse_args() + + # TODO(crbug.com/838414): add support for files included via loadable_modules. + ignored_libs = { + 'libarcore_sdk_c.so', 'libcrashpad_handler_trampoline.so', + 'libsketchology_native.so' + } + # The chromium linker doesn't have static initializers, which makes the + # regular check throw. It should not have any. + no_initializers_libs = ['libchromium_android_linker.so'] + + si_count = _AnalyzeStaticInitializers(args.apk_or_aab, args.tool_prefix, + False, '.', ignored_libs, + no_initializers_libs) + if si_count != args.expected_count: + print('Expected {} static initializers, but found {}.'.format( + args.expected_count, si_count)) + if args.expected_count > si_count: + print('You have removed one or more static initializers. Thanks!') + print('To fix the build, update the expectation in:') + print(' //chrome/android/static_initializers.gni') + else: + print('Dumping static initializers via dump-static-initializers.py:') + sys.stdout.flush() + _AnalyzeStaticInitializers(args.apk_or_aab, args.tool_prefix, True, '.', + ignored_libs, no_initializers_libs) + print() + print('If the above list is not useful, consider listing them with:') + print(' //tools/binary_size/diagnose_bloat.py') + print() + print('For more information:') + print(' https://chromium.googlesource.com/chromium/src/+/main/docs/' + 'static_initializers.md') + sys.exit(1) + + if args.touch: + open(args.touch, 'w') + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/assert_static_initializers.pydeps b/third_party/libwebrtc/build/android/gyp/assert_static_initializers.pydeps new file mode 100644 index 0000000000..b574d817a1 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/assert_static_initializers.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/assert_static_initializers.pydeps build/android/gyp/assert_static_initializers.py +../../gn_helpers.py +assert_static_initializers.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/bundletool.py b/third_party/libwebrtc/build/android/gyp/bundletool.py new file mode 100755 index 0000000000..372e55226d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/bundletool.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 wrapper around the bundletool tool. + +Bundletool is distributed as a versioned jar file. This script abstracts the +location and version of this jar file, as well as the JVM invokation.""" + +import logging +import os +import sys + +from util import build_utils + +# Assume this is stored under build/android/gyp/ +BUNDLETOOL_DIR = os.path.abspath(os.path.join( + __file__, '..', '..', '..', '..', 'third_party', 'android_build_tools', + 'bundletool')) + +BUNDLETOOL_VERSION = '1.8.0' + +BUNDLETOOL_JAR_PATH = os.path.join( + BUNDLETOOL_DIR, 'bundletool-all-%s.jar' % BUNDLETOOL_VERSION) + + +def RunBundleTool(args, warnings_as_errors=(), print_stdout=False): + # Use () instead of None because command-line flags are None by default. + verify = warnings_as_errors == () or warnings_as_errors + # ASAN builds failed with the default of 1GB (crbug.com/1120202). + # Bug for bundletool: https://issuetracker.google.com/issues/165911616 + cmd = build_utils.JavaCmd(verify, xmx='4G') + cmd += ['-jar', BUNDLETOOL_JAR_PATH] + cmd += args + logging.debug(' '.join(cmd)) + return build_utils.CheckOutput( + cmd, + print_stdout=print_stdout, + print_stderr=True, + fail_on_output=False, + stderr_filter=build_utils.FilterReflectiveAccessJavaWarnings) + + +if __name__ == '__main__': + RunBundleTool(sys.argv[1:], print_stdout=True) diff --git a/third_party/libwebrtc/build/android/gyp/bytecode_processor.py b/third_party/libwebrtc/build/android/gyp/bytecode_processor.py new file mode 100755 index 0000000000..d77f159d82 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/bytecode_processor.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Wraps bin/helper/bytecode_processor and expands @FileArgs.""" + +import argparse +import sys + +from util import build_utils +from util import server_utils + + +def _AddSwitch(parser, val): + parser.add_argument( + val, action='store_const', default='--disabled', const=val) + + +def main(argv): + argv = build_utils.ExpandFileArgs(argv[1:]) + parser = argparse.ArgumentParser() + parser.add_argument('--target-name', help='Fully qualified GN target name.') + parser.add_argument('--script', required=True, + help='Path to the java binary wrapper script.') + parser.add_argument('--gn-target', required=True) + parser.add_argument('--input-jar', required=True) + parser.add_argument('--direct-classpath-jars') + parser.add_argument('--sdk-classpath-jars') + parser.add_argument('--full-classpath-jars') + parser.add_argument('--full-classpath-gn-targets') + parser.add_argument('--stamp') + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('--missing-classes-allowlist') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + _AddSwitch(parser, '--is-prebuilt') + args = parser.parse_args(argv) + + if server_utils.MaybeRunCommand(name=args.target_name, + argv=sys.argv, + stamp_file=args.stamp): + return + + args.sdk_classpath_jars = build_utils.ParseGnList(args.sdk_classpath_jars) + args.direct_classpath_jars = build_utils.ParseGnList( + args.direct_classpath_jars) + args.full_classpath_jars = build_utils.ParseGnList(args.full_classpath_jars) + args.full_classpath_gn_targets = build_utils.ParseGnList( + args.full_classpath_gn_targets) + args.missing_classes_allowlist = build_utils.ParseGnList( + args.missing_classes_allowlist) + + verbose = '--verbose' if args.verbose else '--not-verbose' + + cmd = [args.script, args.gn_target, args.input_jar, verbose, args.is_prebuilt] + cmd += [str(len(args.missing_classes_allowlist))] + cmd += args.missing_classes_allowlist + cmd += [str(len(args.sdk_classpath_jars))] + cmd += args.sdk_classpath_jars + cmd += [str(len(args.direct_classpath_jars))] + cmd += args.direct_classpath_jars + cmd += [str(len(args.full_classpath_jars))] + cmd += args.full_classpath_jars + cmd += [str(len(args.full_classpath_gn_targets))] + cmd += args.full_classpath_gn_targets + build_utils.CheckOutput(cmd, + print_stdout=True, + fail_func=None, + fail_on_output=args.warnings_as_errors) + + if args.stamp: + build_utils.Touch(args.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/third_party/libwebrtc/build/android/gyp/bytecode_processor.pydeps b/third_party/libwebrtc/build/android/gyp/bytecode_processor.pydeps new file mode 100644 index 0000000000..6105d934da --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/bytecode_processor.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/bytecode_processor.pydeps build/android/gyp/bytecode_processor.py +../../gn_helpers.py +bytecode_processor.py +util/__init__.py +util/build_utils.py +util/server_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.py b/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.py new file mode 100755 index 0000000000..ad232df038 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium 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 script around ByteCodeRewriter subclass scripts.""" + +import argparse +import sys + +from util import build_utils + + +def main(argv): + argv = build_utils.ExpandFileArgs(argv[1:]) + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--script', + required=True, + help='Path to the java binary wrapper script.') + parser.add_argument('--classpath', action='append', nargs='+') + parser.add_argument('--input-jar', required=True) + parser.add_argument('--output-jar', required=True) + args = parser.parse_args(argv) + + classpath = build_utils.ParseGnList(args.classpath) + build_utils.WriteDepfile(args.depfile, args.output_jar, inputs=classpath) + + classpath.append(args.input_jar) + cmd = [ + args.script, '--classpath', ':'.join(classpath), args.input_jar, + args.output_jar + ] + build_utils.CheckOutput(cmd, print_stdout=True) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.pydeps b/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.pydeps new file mode 100644 index 0000000000..b8f304a783 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/bytecode_rewriter.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/bytecode_rewriter.pydeps build/android/gyp/bytecode_rewriter.py +../../gn_helpers.py +bytecode_rewriter.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/check_flag_expectations.py b/third_party/libwebrtc/build/android/gyp/check_flag_expectations.py new file mode 100755 index 0000000000..22da211f36 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/check_flag_expectations.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 + +from util import build_utils +from util import diff_utils + +IGNORE_FLAG_PREFIXES = [ + # For cflags. + '-DANDROID_NDK_VERSION_ROLL', + '-DCR_LIBCXX_REVISION', + '-I', + '-g', + '-fcrash-diagnostics-dir=', + '-fprofile', + '--no-system-header-prefix', + '--system-header-prefix', + '-isystem', + '-iquote', + '-fmodule-map', + '-frandom-seed', + '-c ', + '-o ', + '-fmodule-name=', + '--sysroot=', + '-fcolor-diagnostics', + '-MF ', + '-MD', + + # For ldflags. + '-Wl,--thinlto-cache-dir', + '-Wl,--thinlto-cache-policy', + '-Wl,--thinlto-jobs', + '-Wl,--start-lib', + '-Wl,--end-lib', + '-Wl,-whole-archive', + '-Wl,-no-whole-archive', + '-l', + '-L', + '-Wl,-soname', + '-Wl,-version-script', + '-Wl,--version-script', + '-fdiagnostics-color', + '-Wl,--color-diagnostics', + '-B', + '-Wl,--dynamic-linker', + '-DCR_CLANG_REVISION=', +] + +FLAGS_WITH_PARAMS = ( + '-Xclang', + '-mllvm', + '-Xclang -fdebug-compilation-dir', + '-Xclang -add-plugin', +) + + +def KeepFlag(flag): + return not any(flag.startswith(prefix) for prefix in IGNORE_FLAG_PREFIXES) + + +def MergeFlags(flags): + flags = _MergeFlagsHelper(flags) + # For double params eg: -Xclang -fdebug-compilation-dir + flags = _MergeFlagsHelper(flags) + return flags + + +def _MergeFlagsHelper(flags): + merged_flags = [] + while flags: + current_flag = flags.pop(0) + if flags: + next_flag = flags[0] + else: + next_flag = None + merge_flags = False + + # Special case some flags that always come with params. + if current_flag in FLAGS_WITH_PARAMS: + merge_flags = True + # Assume flags without '-' are a param. + if next_flag and not next_flag.startswith('-'): + merge_flags = True + # Special case -plugin-arg prefix because it has the plugin name. + if current_flag.startswith('-Xclang -plugin-arg'): + merge_flags = True + if merge_flags: + merged_flag = '{} {}'.format(current_flag, next_flag) + merged_flags.append(merged_flag) + flags.pop(0) + else: + merged_flags.append(current_flag) + return merged_flags + + +def ParseFlags(flag_file_path): + flags = [] + with open(flag_file_path) as f: + for flag in f.read().splitlines(): + if KeepFlag(flag): + flags.append(flag) + return flags + + +def main(): + """Compare the flags with the checked in list.""" + parser = argparse.ArgumentParser() + diff_utils.AddCommandLineFlags(parser) + parser.add_argument('--current-flags', + help='Path to flags to check against expectations.') + options = parser.parse_args() + + flags = ParseFlags(options.current_flags) + flags = MergeFlags(flags) + + msg = """ +This expectation file is meant to inform the build team about changes to +flags used when building native libraries in chrome (most importantly any +that relate to security). This is to ensure the flags are replicated when +building native libraries outside of the repo. Please update the .expected +files and a WATCHLIST entry will alert the build team to your change.""" + diff_utils.CheckExpectations('\n'.join(sorted(flags)), + options, + custom_msg=msg) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/check_flag_expectations.pydeps b/third_party/libwebrtc/build/android/gyp/check_flag_expectations.pydeps new file mode 100644 index 0000000000..d8c394a04c --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/check_flag_expectations.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/check_flag_expectations.pydeps build/android/gyp/check_flag_expectations.py +../../gn_helpers.py +check_flag_expectations.py +util/__init__.py +util/build_utils.py +util/diff_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/compile_java.py b/third_party/libwebrtc/build/android/gyp/compile_java.py new file mode 100755 index 0000000000..b11665e2a7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/compile_java.py @@ -0,0 +1,787 @@ +#!/usr/bin/env python3 +# +# 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 functools +import logging +import multiprocessing +import optparse +import os +import re +import shutil +import sys +import time +import zipfile + +import javac_output_processor +from util import build_utils +from util import md5_check +from util import jar_info_utils +from util import server_utils + +_JAVAC_EXTRACTOR = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', + 'android_prebuilts', 'build_tools', 'common', + 'framework', 'javac_extractor.jar') + +# Add a check here to cause the suggested fix to be applied while compiling. +# Use this when trying to enable more checks. +ERRORPRONE_CHECKS_TO_APPLY = [] + +# Full list of checks: https://errorprone.info/bugpatterns +ERRORPRONE_WARNINGS_TO_DISABLE = [ + # The following are super useful, but existing issues need to be fixed first + # before they can start failing the build on new errors. + 'InvalidParam', + 'InvalidLink', + 'InvalidInlineTag', + 'EmptyBlockTag', + 'PublicConstructorForAbstractClass', + 'InvalidBlockTag', + 'StaticAssignmentInConstructor', + 'MutablePublicArray', + 'UnescapedEntity', + 'NonCanonicalType', + 'AlmostJavadoc', + # The following are added for errorprone update: https://crbug.com/1216032 + 'InlineMeSuggester', + 'DoNotClaimAnnotations', + 'JavaUtilDate', + 'IdentityHashMapUsage', + 'UnnecessaryMethodReference', + 'LongFloatConversion', + 'CharacterGetNumericValue', + 'ErroneousThreadPoolConstructorChecker', + 'StaticMockMember', + 'MissingSuperCall', + 'ToStringReturnsNull', + # TODO(crbug.com/834807): Follow steps in bug + 'DoubleBraceInitialization', + # TODO(crbug.com/834790): Follow steps in bug. + 'CatchAndPrintStackTrace', + # TODO(crbug.com/801210): Follow steps in bug. + 'SynchronizeOnNonFinalField', + # TODO(crbug.com/802073): Follow steps in bug. + 'TypeParameterUnusedInFormals', + # TODO(crbug.com/803484): Follow steps in bug. + 'CatchFail', + # TODO(crbug.com/803485): Follow steps in bug. + 'JUnitAmbiguousTestClass', + # Android platform default is always UTF-8. + # https://developer.android.com/reference/java/nio/charset/Charset.html#defaultCharset() + 'DefaultCharset', + # Low priority since there are lots of tags that don't fit this check. + 'UnrecognisedJavadocTag', + # Low priority since the alternatives still work. + 'JdkObsolete', + # We don't use that many lambdas. + 'FunctionalInterfaceClash', + # There are lots of times when we just want to post a task. + 'FutureReturnValueIgnored', + # Nice to be explicit about operators, but not necessary. + 'OperatorPrecedence', + # Just false positives in our code. + 'ThreadJoinLoop', + # Low priority corner cases with String.split. + # Linking Guava and using Splitter was rejected + # in the https://chromium-review.googlesource.com/c/chromium/src/+/871630. + 'StringSplitter', + # Preferred to use another method since it propagates exceptions better. + 'ClassNewInstance', + # Nice to have static inner classes but not necessary. + 'ClassCanBeStatic', + # Explicit is better than implicit. + 'FloatCast', + # Results in false positives. + 'ThreadLocalUsage', + # Also just false positives. + 'Finally', + # False positives for Chromium. + 'FragmentNotInstantiable', + # Low priority to fix. + 'HidingField', + # Low priority. + 'IntLongMath', + # Low priority. + 'BadComparable', + # Low priority. + 'EqualsHashCode', + # Nice to fix but low priority. + 'TypeParameterShadowing', + # Good to have immutable enums, also low priority. + 'ImmutableEnumChecker', + # False positives for testing. + 'InputStreamSlowMultibyteRead', + # Nice to have better primitives. + 'BoxedPrimitiveConstructor', + # Not necessary for tests. + 'OverrideThrowableToString', + # Nice to have better type safety. + 'CollectionToArraySafeParameter', + # Makes logcat debugging more difficult, and does not provide obvious + # benefits in the Chromium codebase. + 'ObjectToString', + # Triggers on private methods that are @CalledByNative. + 'UnusedMethod', + # Triggers on generated R.java files. + 'UnusedVariable', + # Not that useful. + 'UnsafeReflectiveConstructionCast', + # Not that useful. + 'MixedMutabilityReturnType', + # Nice to have. + 'EqualsGetClass', + # A lot of false-positives from CharSequence.equals(). + 'UndefinedEquals', + # Nice to have. + 'ExtendingJUnitAssert', + # Nice to have. + 'SystemExitOutsideMain', + # Nice to have. + 'TypeParameterNaming', + # Nice to have. + 'UnusedException', + # Nice to have. + 'UngroupedOverloads', + # Nice to have. + 'FunctionalInterfaceClash', + # Nice to have. + 'InconsistentOverloads', + # Dagger generated code triggers this. + 'SameNameButDifferent', + # Nice to have. + 'UnnecessaryLambda', + # Nice to have. + 'UnnecessaryAnonymousClass', + # Nice to have. + 'LiteProtoToString', + # Nice to have. + 'MissingSummary', + # Nice to have. + 'ReturnFromVoid', + # Nice to have. + 'EmptyCatch', + # Nice to have. + 'BadImport', + # Nice to have. + 'UseCorrectAssertInTests', + # Nice to have. + 'InlineFormatString', + # Nice to have. + 'DefaultPackage', + # Must be off since we are now passing in annotation processor generated + # code as a source jar (deduplicating work with turbine). + 'RefersToDaggerCodegen', + # We already have presubmit checks for this. Not necessary to warn on + # every build. + 'RemoveUnusedImports', + # We do not care about unnecessary parenthesis enough to check for them. + 'UnnecessaryParentheses', +] + +# Full list of checks: https://errorprone.info/bugpatterns +# Only those marked as "experimental" need to be listed here in order to be +# enabled. +ERRORPRONE_WARNINGS_TO_ENABLE = [ + 'BinderIdentityRestoredDangerously', + 'EmptyIf', + 'EqualsBrokenForNull', + 'InvalidThrows', + 'LongLiteralLowerCaseSuffix', + 'MultiVariableDeclaration', + 'RedundantOverride', + 'StaticQualifiedUsingExpression', + 'StringEquality', + 'TimeUnitMismatch', + 'UnnecessaryStaticImport', + 'UseBinds', + 'WildcardImport', +] + + +def ProcessJavacOutput(output, target_name): + # These warnings cannot be suppressed even for third party code. Deprecation + # warnings especially do not help since we must support older android version. + deprecated_re = re.compile( + r'(Note: .* uses? or overrides? a deprecated API.)$') + unchecked_re = re.compile( + r'(Note: .* uses? unchecked or unsafe operations.)$') + recompile_re = re.compile(r'(Note: Recompile with -Xlint:.* for details.)$') + + activity_re = re.compile(r'^(?P\s*location: )class Activity$') + + def ApplyFilters(line): + return not (deprecated_re.match(line) or unchecked_re.match(line) + or recompile_re.match(line)) + + def Elaborate(line): + if activity_re.match(line): + prefix = ' ' * activity_re.match(line).end('prefix') + return '{}\n{}Expecting a FragmentActivity? See {}'.format( + line, prefix, 'docs/ui/android/bytecode_rewriting.md') + return line + + output = build_utils.FilterReflectiveAccessJavaWarnings(output) + + lines = (l for l in output.split('\n') if ApplyFilters(l)) + lines = (Elaborate(l) for l in lines) + + output_processor = javac_output_processor.JavacOutputProcessor(target_name) + lines = output_processor.Process(lines) + + return '\n'.join(lines) + + +def _ParsePackageAndClassNames(java_file): + package_name = '' + class_names = [] + with open(java_file) as f: + for l in f: + # Strip unindented comments. + # Considers a leading * as a continuation of a multi-line comment (our + # linter doesn't enforce a space before it like there should be). + l = re.sub(r'^(?://.*|/?\*.*?(?:\*/\s*|$))', '', l) + + m = re.match(r'package\s+(.*?);', l) + if m and not package_name: + package_name = m.group(1) + + # Not exactly a proper parser, but works for sources that Chrome uses. + # In order to not match nested classes, it just checks for lack of indent. + m = re.match(r'(?:\S.*?)?(?:class|@?interface|enum)\s+(.+?)\b', l) + if m: + class_names.append(m.group(1)) + return package_name, class_names + + +def _ProcessJavaFileForInfo(java_file): + package_name, class_names = _ParsePackageAndClassNames(java_file) + return java_file, package_name, class_names + + +class _InfoFileContext(object): + """Manages the creation of the class->source file .info file.""" + + def __init__(self, chromium_code, excluded_globs): + self._chromium_code = chromium_code + self._excluded_globs = excluded_globs + # Map of .java path -> .srcjar/nested/path.java. + self._srcjar_files = {} + # List of generators from pool.imap_unordered(). + self._results = [] + # Lazily created multiprocessing.Pool. + self._pool = None + + def AddSrcJarSources(self, srcjar_path, extracted_paths, parent_dir): + for path in extracted_paths: + # We want the path inside the srcjar so the viewer can have a tree + # structure. + self._srcjar_files[path] = '{}/{}'.format( + srcjar_path, os.path.relpath(path, parent_dir)) + + def SubmitFiles(self, java_files): + if self._pool is None: + # Restrict to just one process to not slow down compiling. Compiling + # is always slower. + self._pool = multiprocessing.Pool(1) + logging.info('Submitting %d files for info', len(java_files)) + self._results.append( + self._pool.imap_unordered( + _ProcessJavaFileForInfo, java_files, chunksize=1000)) + + def _CheckPathMatchesClassName(self, java_file, package_name, class_name): + parts = package_name.split('.') + [class_name + '.java'] + expected_path_suffix = os.path.sep.join(parts) + if not java_file.endswith(expected_path_suffix): + raise Exception(('Java package+class name do not match its path.\n' + 'Actual path: %s\nExpected path: %s') % + (java_file, expected_path_suffix)) + + def _ProcessInfo(self, java_file, package_name, class_names, source): + for class_name in class_names: + yield '{}.{}'.format(package_name, class_name) + # Skip aidl srcjars since they don't indent code correctly. + if '_aidl.srcjar' in source: + continue + assert not self._chromium_code or len(class_names) == 1, ( + 'Chromium java files must only have one class: {}'.format(source)) + if self._chromium_code: + # This check is not necessary but nice to check this somewhere. + self._CheckPathMatchesClassName(java_file, package_name, class_names[0]) + + def _ShouldIncludeInJarInfo(self, fully_qualified_name): + name_as_class_glob = fully_qualified_name.replace('.', '/') + '.class' + return not build_utils.MatchesGlob(name_as_class_glob, self._excluded_globs) + + def _Collect(self): + if self._pool is None: + return {} + ret = {} + for result in self._results: + for java_file, package_name, class_names in result: + source = self._srcjar_files.get(java_file, java_file) + for fully_qualified_name in self._ProcessInfo(java_file, package_name, + class_names, source): + if self._ShouldIncludeInJarInfo(fully_qualified_name): + ret[fully_qualified_name] = java_file + self._pool.terminate() + return ret + + def __del__(self): + # Work around for Python 2.x bug with multiprocessing and daemon threads: + # https://bugs.python.org/issue4106 + if self._pool is not None: + logging.info('Joining multiprocessing.Pool') + self._pool.terminate() + self._pool.join() + logging.info('Done.') + + def Commit(self, output_path): + """Writes a .jar.info file. + + Maps fully qualified names for classes to either the java file that they + are defined in or the path of the srcjar that they came from. + """ + logging.info('Collecting info file entries') + entries = self._Collect() + + logging.info('Writing info file: %s', output_path) + with build_utils.AtomicOutput(output_path, mode='wb') as f: + jar_info_utils.WriteJarInfoFile(f, entries, self._srcjar_files) + logging.info('Completed info file: %s', output_path) + + +def _CreateJarFile(jar_path, service_provider_configuration_dir, + additional_jar_files, classes_dir): + logging.info('Start creating jar file: %s', jar_path) + with build_utils.AtomicOutput(jar_path) as f: + with zipfile.ZipFile(f.name, 'w') as z: + build_utils.ZipDir(z, classes_dir) + if service_provider_configuration_dir: + config_files = build_utils.FindInDirectory( + service_provider_configuration_dir) + for config_file in config_files: + zip_path = os.path.relpath(config_file, + service_provider_configuration_dir) + build_utils.AddToZipHermetic(z, zip_path, src_path=config_file) + + if additional_jar_files: + for src_path, zip_path in additional_jar_files: + build_utils.AddToZipHermetic(z, zip_path, src_path=src_path) + logging.info('Completed jar file: %s', jar_path) + + +def _OnStaleMd5(changes, options, javac_cmd, javac_args, java_files): + logging.info('Starting _OnStaleMd5') + if options.enable_kythe_annotations: + # Kythe requires those env variables to be set and compile_java.py does the + # same + if not os.environ.get('KYTHE_ROOT_DIRECTORY') or \ + not os.environ.get('KYTHE_OUTPUT_DIRECTORY'): + raise Exception('--enable-kythe-annotations requires ' + 'KYTHE_ROOT_DIRECTORY and KYTHE_OUTPUT_DIRECTORY ' + 'environment variables to be set.') + javac_extractor_cmd = build_utils.JavaCmd() + [ + '-jar', + _JAVAC_EXTRACTOR, + ] + try: + # _RunCompiler()'s partial javac implementation does not support + # generating outputs in $KYTHE_OUTPUT_DIRECTORY. + _RunCompiler(changes, + options, + javac_extractor_cmd + javac_args, + java_files, + options.jar_path + '.javac_extractor', + enable_partial_javac=False) + except build_utils.CalledProcessError as e: + # Having no index for particular target is better than failing entire + # codesearch. Log and error and move on. + logging.error('Could not generate kzip: %s', e) + + intermediates_out_dir = None + jar_info_path = None + if not options.enable_errorprone: + # Delete any stale files in the generated directory. The purpose of + # options.generated_dir is for codesearch. + shutil.rmtree(options.generated_dir, True) + intermediates_out_dir = options.generated_dir + + jar_info_path = options.jar_path + '.info' + + # Compiles with Error Prone take twice as long to run as pure javac. Thus GN + # rules run both in parallel, with Error Prone only used for checks. + _RunCompiler(changes, + options, + javac_cmd + javac_args, + java_files, + options.jar_path, + jar_info_path=jar_info_path, + intermediates_out_dir=intermediates_out_dir, + enable_partial_javac=True) + logging.info('Completed all steps in _OnStaleMd5') + + +def _RunCompiler(changes, + options, + javac_cmd, + java_files, + jar_path, + jar_info_path=None, + intermediates_out_dir=None, + enable_partial_javac=False): + """Runs java compiler. + + Args: + changes: md5_check.Changes object. + options: Object with command line flags. + javac_cmd: Command to execute. + java_files: List of java files passed from command line. + jar_path: Path of output jar file. + jar_info_path: Path of the .info file to generate. + If None, .info file will not be generated. + intermediates_out_dir: Directory for saving intermediate outputs. + If None a temporary directory is used. + enable_partial_javac: Enables compiling only Java files which have changed + in the special case that no method signatures have changed. This is + useful for large GN targets. + Not supported if compiling generates outputs other than |jar_path| and + |jar_info_path|. + """ + logging.info('Starting _RunCompiler') + + java_files = java_files.copy() + java_srcjars = options.java_srcjars + save_info_file = jar_info_path is not None + + # Use jar_path's directory to ensure paths are relative (needed for goma). + temp_dir = jar_path + '.staging' + shutil.rmtree(temp_dir, True) + os.makedirs(temp_dir) + try: + classes_dir = os.path.join(temp_dir, 'classes') + service_provider_configuration = os.path.join( + temp_dir, 'service_provider_configuration') + + if java_files: + os.makedirs(classes_dir) + + if enable_partial_javac: + all_changed_paths_are_java = all( + [p.endswith(".java") for p in changes.IterChangedPaths()]) + if (all_changed_paths_are_java and not changes.HasStringChanges() + and os.path.exists(jar_path) + and (jar_info_path is None or os.path.exists(jar_info_path))): + # Log message is used by tests to determine whether partial javac + # optimization was used. + logging.info('Using partial javac optimization for %s compile' % + (jar_path)) + + # Header jar corresponding to |java_files| did not change. + # As a build speed optimization (crbug.com/1170778), re-compile only + # java files which have changed. Re-use old jar .info file. + java_files = list(changes.IterChangedPaths()) + java_srcjars = None + + # Reuse old .info file. + save_info_file = False + + build_utils.ExtractAll(jar_path, classes_dir) + + if save_info_file: + info_file_context = _InfoFileContext(options.chromium_code, + options.jar_info_exclude_globs) + + if intermediates_out_dir is None: + input_srcjars_dir = os.path.join(temp_dir, 'input_srcjars') + else: + input_srcjars_dir = os.path.join(intermediates_out_dir, 'input_srcjars') + + if java_srcjars: + logging.info('Extracting srcjars to %s', input_srcjars_dir) + build_utils.MakeDirectory(input_srcjars_dir) + for srcjar in options.java_srcjars: + extracted_files = build_utils.ExtractAll( + srcjar, no_clobber=True, path=input_srcjars_dir, pattern='*.java') + java_files.extend(extracted_files) + if save_info_file: + info_file_context.AddSrcJarSources(srcjar, extracted_files, + input_srcjars_dir) + logging.info('Done extracting srcjars') + + if options.header_jar: + logging.info('Extracting service provider configs') + # Extract META-INF/services/* so that it can be copied into the output + # .jar + build_utils.ExtractAll(options.header_jar, + no_clobber=True, + path=service_provider_configuration, + pattern='META-INF/services/*') + logging.info('Done extracting service provider configs') + + if save_info_file and java_files: + info_file_context.SubmitFiles(java_files) + + if java_files: + # Don't include the output directory in the initial set of args since it + # being in a temp dir makes it unstable (breaks md5 stamping). + cmd = list(javac_cmd) + cmd += ['-d', classes_dir] + + if options.classpath: + cmd += ['-classpath', ':'.join(options.classpath)] + + # Pass source paths as response files to avoid extremely long command + # lines that are tedius to debug. + java_files_rsp_path = os.path.join(temp_dir, 'files_list.txt') + with open(java_files_rsp_path, 'w') as f: + f.write(' '.join(java_files)) + cmd += ['@' + java_files_rsp_path] + + process_javac_output_partial = functools.partial( + ProcessJavacOutput, target_name=options.target_name) + + logging.debug('Build command %s', cmd) + start = time.time() + build_utils.CheckOutput(cmd, + print_stdout=options.chromium_code, + stdout_filter=process_javac_output_partial, + stderr_filter=process_javac_output_partial, + fail_on_output=options.warnings_as_errors) + end = time.time() - start + logging.info('Java compilation took %ss', end) + + _CreateJarFile(jar_path, service_provider_configuration, + options.additional_jar_files, classes_dir) + + if save_info_file: + info_file_context.Commit(jar_info_path) + + logging.info('Completed all steps in _RunCompiler') + finally: + shutil.rmtree(temp_dir) + + +def _ParseOptions(argv): + parser = optparse.OptionParser() + build_utils.AddDepfileOption(parser) + + parser.add_option('--target-name', help='Fully qualified GN target name.') + parser.add_option('--skip-build-server', + action='store_true', + help='Avoid using the build server.') + parser.add_option( + '--java-srcjars', + action='append', + default=[], + help='List of srcjars to include in compilation.') + parser.add_option( + '--generated-dir', + help='Subdirectory within target_gen_dir to place extracted srcjars and ' + 'annotation processor output for codesearch to find.') + 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( + '--java-version', + help='Java language version to use in -source and -target args to javac.') + parser.add_option('--classpath', action='append', help='Classpath to use.') + parser.add_option( + '--processorpath', + action='append', + help='GN list of jars that comprise the classpath used for Annotation ' + 'Processors.') + parser.add_option( + '--processor-arg', + dest='processor_args', + action='append', + help='key=value arguments for the annotation processors.') + parser.add_option( + '--additional-jar-file', + dest='additional_jar_files', + action='append', + help='Additional files to package into jar. By default, only Java .class ' + 'files are packaged into the jar. Files should be specified in ' + 'format :.') + parser.add_option( + '--jar-info-exclude-globs', + help='GN list of exclude globs to filter from generated .info files.') + parser.add_option( + '--chromium-code', + type='int', + help='Whether code being compiled should be built with stricter ' + 'warnings for chromium code.') + parser.add_option( + '--gomacc-path', help='When set, prefix javac command with gomacc') + parser.add_option( + '--errorprone-path', help='Use the Errorprone compiler at this path.') + parser.add_option( + '--enable-errorprone', + action='store_true', + help='Enable errorprone checks') + parser.add_option( + '--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + parser.add_option('--jar-path', help='Jar output path.') + parser.add_option( + '--javac-arg', + action='append', + default=[], + help='Additional arguments to pass to javac.') + parser.add_option( + '--enable-kythe-annotations', + action='store_true', + help='Enable generation of Kythe kzip, used for codesearch. Ensure ' + 'proper environment variables are set before using this flag.') + parser.add_option( + '--header-jar', + help='This is the header jar for the current target that contains ' + 'META-INF/services/* files to be included in the output jar.') + + options, args = parser.parse_args(argv) + build_utils.CheckOptions(options, parser, required=('jar_path', )) + + options.bootclasspath = build_utils.ParseGnList(options.bootclasspath) + options.classpath = build_utils.ParseGnList(options.classpath) + options.processorpath = build_utils.ParseGnList(options.processorpath) + options.java_srcjars = build_utils.ParseGnList(options.java_srcjars) + options.jar_info_exclude_globs = build_utils.ParseGnList( + options.jar_info_exclude_globs) + + additional_jar_files = [] + for arg in options.additional_jar_files or []: + filepath, jar_filepath = arg.split(':') + additional_jar_files.append((filepath, jar_filepath)) + options.additional_jar_files = additional_jar_files + + java_files = [] + for arg in args: + # Interpret a path prefixed with @ as a file containing a list of sources. + if arg.startswith('@'): + java_files.extend(build_utils.ReadSourcesList(arg[1:])) + else: + java_files.append(arg) + + return options, java_files + + +def main(argv): + build_utils.InitLogging('JAVAC_DEBUG') + argv = build_utils.ExpandFileArgs(argv) + options, java_files = _ParseOptions(argv) + + # Only use the build server for errorprone runs. + if (options.enable_errorprone and not options.skip_build_server + and server_utils.MaybeRunCommand(name=options.target_name, + argv=sys.argv, + stamp_file=options.jar_path)): + return + + javac_cmd = [] + if options.gomacc_path: + javac_cmd.append(options.gomacc_path) + javac_cmd.append(build_utils.JAVAC_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', + # Prevent compiler from compiling .java files not listed as inputs. + # See: http://blog.ltgt.net/most-build-tools-misuse-javac/ + '-sourcepath', + ':', + ] + + if options.enable_errorprone: + # All errorprone args are passed space-separated in a single arg. + errorprone_flags = ['-Xplugin:ErrorProne'] + # Make everything a warning so that when treat_warnings_as_errors is false, + # they do not fail the build. + errorprone_flags += ['-XepAllErrorsAsWarnings'] + # Don't check generated files. + errorprone_flags += ['-XepDisableWarningsInGeneratedCode'] + errorprone_flags.extend('-Xep:{}:OFF'.format(x) + for x in ERRORPRONE_WARNINGS_TO_DISABLE) + errorprone_flags.extend('-Xep:{}:WARN'.format(x) + for x in ERRORPRONE_WARNINGS_TO_ENABLE) + + if ERRORPRONE_CHECKS_TO_APPLY: + errorprone_flags += [ + '-XepPatchLocation:IN_PLACE', + '-XepPatchChecks:,' + ','.join(ERRORPRONE_CHECKS_TO_APPLY) + ] + + javac_args += ['-XDcompilePolicy=simple', ' '.join(errorprone_flags)] + + # This flag quits errorprone after checks and before code generation, since + # we do not need errorprone outputs, this speeds up errorprone by 4 seconds + # for chrome_java. + if not ERRORPRONE_CHECKS_TO_APPLY: + javac_args += ['-XDshould-stop.ifNoError=FLOW'] + + if options.java_version: + javac_args.extend([ + '-source', + options.java_version, + '-target', + options.java_version, + ]) + if options.java_version == '1.8': + # Android's boot jar doesn't contain all java 8 classes. + options.bootclasspath.append(build_utils.RT_JAR_PATH) + + # This effectively disables all annotation processors, even including + # annotation processors in service provider configuration files named + # META-INF/. See the following link for reference: + # https://docs.oracle.com/en/java/javase/11/tools/javac.html + javac_args.extend(['-proc:none']) + + if options.bootclasspath: + javac_args.extend(['-bootclasspath', ':'.join(options.bootclasspath)]) + + if options.processorpath: + javac_args.extend(['-processorpath', ':'.join(options.processorpath)]) + if options.processor_args: + for arg in options.processor_args: + javac_args.extend(['-A%s' % arg]) + + javac_args.extend(options.javac_arg) + + classpath_inputs = ( + options.bootclasspath + options.classpath + options.processorpath) + + depfile_deps = classpath_inputs + # Files that are already inputs in GN should go in input_paths. + input_paths = depfile_deps + options.java_srcjars + java_files + if options.header_jar: + input_paths.append(options.header_jar) + input_paths += [x[0] for x in options.additional_jar_files] + + output_paths = [options.jar_path] + if not options.enable_errorprone: + output_paths += [options.jar_path + '.info'] + + input_strings = javac_cmd + javac_args + options.classpath + java_files + [ + options.warnings_as_errors, options.jar_info_exclude_globs + ] + + # Use md5_check for |pass_changes| feature. + md5_check.CallAndWriteDepfileIfStale(lambda changes: _OnStaleMd5( + changes, options, javac_cmd, javac_args, java_files), + options, + depfile_deps=depfile_deps, + input_paths=input_paths, + input_strings=input_strings, + output_paths=output_paths, + pass_changes=True) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/compile_java.pydeps b/third_party/libwebrtc/build/android/gyp/compile_java.pydeps new file mode 100644 index 0000000000..c1c7d5fd56 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/compile_java.pydeps @@ -0,0 +1,30 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/compile_java.pydeps build/android/gyp/compile_java.py +../../../third_party/catapult/devil/devil/__init__.py +../../../third_party/catapult/devil/devil/android/__init__.py +../../../third_party/catapult/devil/devil/android/constants/__init__.py +../../../third_party/catapult/devil/devil/android/constants/chrome.py +../../../third_party/catapult/devil/devil/android/sdk/__init__.py +../../../third_party/catapult/devil/devil/android/sdk/keyevent.py +../../../third_party/catapult/devil/devil/android/sdk/version_codes.py +../../../third_party/catapult/devil/devil/constants/__init__.py +../../../third_party/catapult/devil/devil/constants/exit_codes.py +../../../third_party/colorama/src/colorama/__init__.py +../../../third_party/colorama/src/colorama/ansi.py +../../../third_party/colorama/src/colorama/ansitowin32.py +../../../third_party/colorama/src/colorama/initialise.py +../../../third_party/colorama/src/colorama/win32.py +../../../third_party/colorama/src/colorama/winterm.py +../../../tools/android/modularization/convenience/lookup_dep.py +../../gn_helpers.py +../../print_python_deps.py +../list_java_targets.py +../pylib/__init__.py +../pylib/constants/__init__.py +compile_java.py +javac_output_processor.py +util/__init__.py +util/build_utils.py +util/jar_info_utils.py +util/md5_check.py +util/server_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/compile_resources.py b/third_party/libwebrtc/build/android/gyp/compile_resources.py new file mode 100755 index 0000000000..9add95aed8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/compile_resources.py @@ -0,0 +1,1032 @@ +#!/usr/bin/env python3 +# +# 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. + +"""Compile Android resources into an intermediate APK. + +This can also generate an R.txt, and an .srcjar file containing the proper +final R.java class for all resource packages the APK depends on. + +This will crunch images with aapt2. +""" + +import argparse +import collections +import contextlib +import filecmp +import hashlib +import logging +import os +import re +import shutil +import subprocess +import sys +import tempfile +import textwrap +import zipfile +from xml.etree import ElementTree + +from util import build_utils +from util import diff_utils +from util import manifest_utils +from util import parallel +from util import protoresources +from util import resource_utils + + +# Pngs that we shouldn't convert to webp. Please add rationale when updating. +_PNG_WEBP_EXCLUSION_PATTERN = re.compile('|'.join([ + # Crashes on Galaxy S5 running L (https://crbug.com/807059). + r'.*star_gray\.png', + # Android requires pngs for 9-patch images. + r'.*\.9\.png', + # Daydream requires pngs for icon files. + r'.*daydream_icon_.*\.png' +])) + + +def _ParseArgs(args): + """Parses command line options. + + Returns: + An options object as from argparse.ArgumentParser.parse_args() + """ + parser, input_opts, output_opts = resource_utils.ResourceArgsParser() + + input_opts.add_argument( + '--aapt2-path', required=True, help='Path to the Android aapt2 tool.') + input_opts.add_argument( + '--android-manifest', required=True, help='AndroidManifest.xml path.') + input_opts.add_argument( + '--r-java-root-package-name', + default='base', + help='Short package name for this target\'s root R java file (ex. ' + 'input of "base" would become gen.base_module). Defaults to "base".') + group = input_opts.add_mutually_exclusive_group() + group.add_argument( + '--shared-resources', + action='store_true', + help='Make all resources in R.java non-final and allow the resource IDs ' + 'to be reset to a different package index when the apk is loaded by ' + 'another application at runtime.') + group.add_argument( + '--app-as-shared-lib', + action='store_true', + help='Same as --shared-resources, but also ensures all resource IDs are ' + 'directly usable from the APK loaded as an application.') + + input_opts.add_argument( + '--package-id', + type=int, + help='Decimal integer representing custom package ID for resources ' + '(instead of 127==0x7f). Cannot be used with --shared-resources.') + + input_opts.add_argument( + '--package-name', + help='Package name that will be used to create R class.') + + input_opts.add_argument( + '--rename-manifest-package', help='Package name to force AAPT to use.') + + input_opts.add_argument( + '--arsc-package-name', + help='Package name to set in manifest of resources.arsc file. This is ' + 'only used for apks under test.') + + input_opts.add_argument( + '--shared-resources-allowlist', + help='An R.txt file acting as a allowlist for resources that should be ' + 'non-final and have their package ID changed at runtime in R.java. ' + 'Implies and overrides --shared-resources.') + + input_opts.add_argument( + '--shared-resources-allowlist-locales', + default='[]', + help='Optional GN-list of locales. If provided, all strings corresponding' + ' to this locale list will be kept in the final output for the ' + 'resources identified through --shared-resources-allowlist, even ' + 'if --locale-allowlist is being used.') + + input_opts.add_argument( + '--use-resource-ids-path', + help='Use resource IDs generated by aapt --emit-ids.') + + input_opts.add_argument( + '--extra-main-r-text-files', + help='Additional R.txt files that will be added to the root R.java file, ' + 'but not packaged in the generated resources.arsc. If these resources ' + 'entries contain duplicate resources with the generated R.txt file, they ' + 'must be identical.') + + input_opts.add_argument( + '--debuggable', + action='store_true', + help='Whether to add android:debuggable="true".') + + input_opts.add_argument('--version-code', help='Version code for apk.') + input_opts.add_argument('--version-name', help='Version name for apk.') + input_opts.add_argument( + '--min-sdk-version', required=True, help='android:minSdkVersion for APK.') + input_opts.add_argument( + '--target-sdk-version', + required=True, + help="android:targetSdkVersion for APK.") + input_opts.add_argument( + '--max-sdk-version', + help="android:maxSdkVersion expected in AndroidManifest.xml.") + input_opts.add_argument( + '--manifest-package', help='Package name of the AndroidManifest.xml.') + + input_opts.add_argument( + '--locale-allowlist', + default='[]', + help='GN list of languages to include. All other language configs will ' + 'be stripped out. List may include a combination of Android locales ' + 'or Chrome locales.') + input_opts.add_argument( + '--resource-exclusion-regex', + default='', + help='File-based filter for resources (applied before compiling)') + input_opts.add_argument( + '--resource-exclusion-exceptions', + default='[]', + help='GN list of globs that say which files to include even ' + 'when --resource-exclusion-regex is set.') + + input_opts.add_argument( + '--dependencies-res-zip-overlays', + help='GN list with subset of --dependencies-res-zips to use overlay ' + 'semantics for.') + + input_opts.add_argument( + '--values-filter-rules', + help='GN list of source_glob:regex for filtering resources after they ' + 'are compiled. Use this to filter out entries within values/ files.') + + input_opts.add_argument('--png-to-webp', action='store_true', + help='Convert png files to webp format.') + + input_opts.add_argument('--webp-binary', default='', + help='Path to the cwebp binary.') + input_opts.add_argument( + '--webp-cache-dir', help='The directory to store webp image cache.') + + input_opts.add_argument( + '--no-xml-namespaces', + action='store_true', + help='Whether to strip xml namespaces from processed xml resources.') + + output_opts.add_argument('--arsc-path', help='Apk output for arsc format.') + output_opts.add_argument('--proto-path', help='Apk output for proto format.') + group = input_opts.add_mutually_exclusive_group() + + output_opts.add_argument( + '--info-path', help='Path to output info file for the partial apk.') + + output_opts.add_argument( + '--srcjar-out', + required=True, + help='Path to srcjar to contain generated R.java.') + + output_opts.add_argument('--r-text-out', + help='Path to store the generated R.txt file.') + + output_opts.add_argument( + '--proguard-file', help='Path to proguard.txt generated file.') + + output_opts.add_argument( + '--proguard-file-main-dex', + help='Path to proguard.txt generated file for main dex.') + + output_opts.add_argument( + '--emit-ids-out', help='Path to file produced by aapt2 --emit-ids.') + + input_opts.add_argument( + '--is-bundle-module', + action='store_true', + help='Whether resources are being generated for a bundle module.') + + input_opts.add_argument( + '--uses-split', + help='Value to set uses-split to in the AndroidManifest.xml.') + + input_opts.add_argument( + '--extra-verification-manifest', + help='Path to AndroidManifest.xml which should be merged into base ' + 'manifest when performing verification.') + + diff_utils.AddCommandLineFlags(parser) + options = parser.parse_args(args) + + resource_utils.HandleCommonOptions(options) + + options.locale_allowlist = build_utils.ParseGnList(options.locale_allowlist) + options.shared_resources_allowlist_locales = build_utils.ParseGnList( + options.shared_resources_allowlist_locales) + options.resource_exclusion_exceptions = build_utils.ParseGnList( + options.resource_exclusion_exceptions) + options.dependencies_res_zip_overlays = build_utils.ParseGnList( + options.dependencies_res_zip_overlays) + options.values_filter_rules = build_utils.ParseGnList( + options.values_filter_rules) + options.extra_main_r_text_files = build_utils.ParseGnList( + options.extra_main_r_text_files) + + if not options.arsc_path and not options.proto_path: + parser.error('One of --arsc-path or --proto-path is required.') + + if options.package_id and options.shared_resources: + parser.error('--package-id and --shared-resources are mutually exclusive') + + return options + + +def _IterFiles(root_dir): + for root, _, files in os.walk(root_dir): + for f in files: + yield os.path.join(root, f) + + +def _RenameLocaleResourceDirs(resource_dirs, path_info): + """Rename locale resource directories into standard names when necessary. + + This is necessary to deal with the fact that older Android releases only + support ISO 639-1 two-letter codes, and sometimes even obsolete versions + of them. + + In practice it means: + * 3-letter ISO 639-2 qualifiers are renamed under a corresponding + 2-letter one. E.g. for Filipino, strings under values-fil/ will be moved + to a new corresponding values-tl/ sub-directory. + + * Modern ISO 639-1 codes will be renamed to their obsolete variant + for Indonesian, Hebrew and Yiddish (e.g. 'values-in/ -> values-id/). + + * Norwegian macrolanguage strings will be renamed to Bokmal (main + Norway language). See http://crbug.com/920960. In practice this + means that 'values-no/ -> values-nb/' unless 'values-nb/' already + exists. + + * BCP 47 langauge tags will be renamed to an equivalent ISO 639-1 + locale qualifier if possible (e.g. 'values-b+en+US/ -> values-en-rUS'). + + Args: + resource_dirs: list of top-level resource directories. + """ + for resource_dir in resource_dirs: + ignore_dirs = {} + for path in _IterFiles(resource_dir): + locale = resource_utils.FindLocaleInStringResourceFilePath(path) + if not locale: + continue + cr_locale = resource_utils.ToChromiumLocaleName(locale) + if not cr_locale: + continue # Unsupported Android locale qualifier!? + locale2 = resource_utils.ToAndroidLocaleName(cr_locale) + if locale != locale2: + path2 = path.replace('/values-%s/' % locale, '/values-%s/' % locale2) + if path == path2: + raise Exception('Could not substitute locale %s for %s in %s' % + (locale, locale2, path)) + + # Ignore rather than rename when the destination resources config + # already exists. + # e.g. some libraries provide both values-nb/ and values-no/. + # e.g. material design provides: + # * res/values-rUS/values-rUS.xml + # * res/values-b+es+419/values-b+es+419.xml + config_dir = os.path.dirname(path2) + already_has_renamed_config = ignore_dirs.get(config_dir) + if already_has_renamed_config is None: + # Cache the result of the first time the directory is encountered + # since subsequent encounters will find the directory already exists + # (due to the rename). + already_has_renamed_config = os.path.exists(config_dir) + ignore_dirs[config_dir] = already_has_renamed_config + if already_has_renamed_config: + continue + + build_utils.MakeDirectory(os.path.dirname(path2)) + shutil.move(path, path2) + path_info.RegisterRename( + os.path.relpath(path, resource_dir), + os.path.relpath(path2, resource_dir)) + + +def _ToAndroidLocales(locale_allowlist): + """Converts the list of Chrome locales to Android config locale qualifiers. + + Args: + locale_allowlist: A list of Chromium locale names. + Returns: + A set of matching Android config locale qualifier names. + """ + ret = set() + for locale in locale_allowlist: + locale = resource_utils.ToAndroidLocaleName(locale) + if locale is None or ('-' in locale and '-r' not in locale): + raise Exception('Unsupported Chromium locale name: %s' % locale) + ret.add(locale) + # Always keep non-regional fall-backs. + language = locale.split('-')[0] + ret.add(language) + + return ret + + +def _MoveImagesToNonMdpiFolders(res_root, path_info): + """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 os.path.splitext(src_file_name)[1] in ('.png', '.webp', ''): + 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) + path_info.RegisterRename( + os.path.relpath(src_file, res_root), + os.path.relpath(dst_file, res_root)) + + +def _FixManifest(options, temp_dir, extra_manifest=None): + """Fix the APK's AndroidManifest.xml. + + This adds any missing namespaces for 'android' and 'tools', and + sets certains elements like 'platformBuildVersionCode' or + 'android:debuggable' depending on the content of |options|. + + Args: + options: The command-line arguments tuple. + temp_dir: A temporary directory where the fixed manifest will be written to. + extra_manifest: Path to an AndroidManifest.xml file which will get merged + into the application node of the base manifest. + Returns: + Tuple of: + * Manifest path within |temp_dir|. + * Original package_name. + * Manifest package name. + """ + def maybe_extract_version(j): + try: + return resource_utils.ExtractBinaryManifestValues(options.aapt2_path, j) + except build_utils.CalledProcessError: + return None + + android_sdk_jars = [j for j in options.include_resources + if os.path.basename(j) in ('android.jar', + 'android_system.jar')] + extract_all = [maybe_extract_version(j) for j in android_sdk_jars] + successful_extractions = [x for x in extract_all if x] + if len(successful_extractions) == 0: + raise Exception( + 'Unable to find android SDK jar among candidates: %s' + % ', '.join(android_sdk_jars)) + elif len(successful_extractions) > 1: + raise Exception( + 'Found multiple android SDK jars among candidates: %s' + % ', '.join(android_sdk_jars)) + version_code, version_name = successful_extractions.pop()[:2] + + debug_manifest_path = os.path.join(temp_dir, 'AndroidManifest.xml') + doc, manifest_node, app_node = manifest_utils.ParseManifest( + options.android_manifest) + + if extra_manifest: + _, extra_manifest_node, extra_app_node = manifest_utils.ParseManifest( + extra_manifest) + for node in extra_app_node: + app_node.append(node) + for node in extra_manifest_node: + # DFM manifests have a bunch of tags we don't care about inside + # , so only take . + if node.tag == 'queries': + manifest_node.append(node) + + manifest_utils.AssertUsesSdk(manifest_node, options.min_sdk_version, + options.target_sdk_version) + # We explicitly check that maxSdkVersion is set in the manifest since we don't + # add it later like minSdkVersion and targetSdkVersion. + manifest_utils.AssertUsesSdk( + manifest_node, + max_sdk_version=options.max_sdk_version, + fail_if_not_exist=True) + manifest_utils.AssertPackage(manifest_node, options.manifest_package) + + manifest_node.set('platformBuildVersionCode', version_code) + manifest_node.set('platformBuildVersionName', version_name) + + orig_package = manifest_node.get('package') + fixed_package = orig_package + if options.arsc_package_name: + manifest_node.set('package', options.arsc_package_name) + fixed_package = options.arsc_package_name + + if options.debuggable: + app_node.set('{%s}%s' % (manifest_utils.ANDROID_NAMESPACE, 'debuggable'), + 'true') + + if options.uses_split: + uses_split = ElementTree.SubElement(manifest_node, 'uses-split') + uses_split.set('{%s}name' % manifest_utils.ANDROID_NAMESPACE, + options.uses_split) + + # Make sure the min-sdk condition is not less than the min-sdk of the bundle. + for min_sdk_node in manifest_node.iter('{%s}min-sdk' % + manifest_utils.DIST_NAMESPACE): + dist_value = '{%s}value' % manifest_utils.DIST_NAMESPACE + if int(min_sdk_node.get(dist_value)) < int(options.min_sdk_version): + min_sdk_node.set(dist_value, options.min_sdk_version) + + manifest_utils.SaveManifest(doc, debug_manifest_path) + return debug_manifest_path, orig_package, fixed_package + + +def _CreateKeepPredicate(resource_exclusion_regex, + resource_exclusion_exceptions): + """Return a predicate lambda to determine which resource files to keep. + + Args: + resource_exclusion_regex: A regular expression describing all resources + to exclude, except if they are mip-maps, or if they are listed + in |resource_exclusion_exceptions|. + resource_exclusion_exceptions: A list of glob patterns corresponding + to exceptions to the |resource_exclusion_regex|. + Returns: + A lambda that takes a path, and returns true if the corresponding file + must be kept. + """ + predicate = lambda path: os.path.basename(path)[0] != '.' + if resource_exclusion_regex == '': + # Do not extract dotfiles (e.g. ".gitkeep"). aapt ignores them anyways. + return predicate + + # A simple predicate that only removes (returns False for) paths covered by + # the exclusion regex or listed as exceptions. + return lambda path: ( + not re.search(resource_exclusion_regex, path) or + build_utils.MatchesGlob(path, resource_exclusion_exceptions)) + + +def _ComputeSha1(path): + with open(path, 'rb') as f: + data = f.read() + return hashlib.sha1(data).hexdigest() + + +def _ConvertToWebPSingle(png_path, cwebp_binary, cwebp_version, webp_cache_dir): + sha1_hash = _ComputeSha1(png_path) + + # The set of arguments that will appear in the cache key. + quality_args = ['-m', '6', '-q', '100', '-lossless'] + + webp_cache_path = os.path.join( + webp_cache_dir, '{}-{}-{}'.format(sha1_hash, cwebp_version, + ''.join(quality_args))) + # No need to add .webp. Android can load images fine without them. + webp_path = os.path.splitext(png_path)[0] + + cache_hit = os.path.exists(webp_cache_path) + if cache_hit: + os.link(webp_cache_path, webp_path) + else: + # We place the generated webp image to webp_path, instead of in the + # webp_cache_dir to avoid concurrency issues. + args = [cwebp_binary, png_path, '-o', webp_path, '-quiet'] + quality_args + subprocess.check_call(args) + + try: + os.link(webp_path, webp_cache_path) + except OSError: + # Because of concurrent run, a webp image may already exists in + # webp_cache_path. + pass + + os.remove(png_path) + original_dir = os.path.dirname(os.path.dirname(png_path)) + rename_tuple = (os.path.relpath(png_path, original_dir), + os.path.relpath(webp_path, original_dir)) + return rename_tuple, cache_hit + + +def _ConvertToWebP(cwebp_binary, png_paths, path_info, webp_cache_dir): + cwebp_version = subprocess.check_output([cwebp_binary, '-version']).rstrip() + shard_args = [(f, ) for f in png_paths + if not _PNG_WEBP_EXCLUSION_PATTERN.match(f)] + + build_utils.MakeDirectory(webp_cache_dir) + results = parallel.BulkForkAndCall(_ConvertToWebPSingle, + shard_args, + cwebp_binary=cwebp_binary, + cwebp_version=cwebp_version, + webp_cache_dir=webp_cache_dir) + total_cache_hits = 0 + for rename_tuple, cache_hit in results: + path_info.RegisterRename(*rename_tuple) + total_cache_hits += int(cache_hit) + + logging.debug('png->webp cache: %d/%d', total_cache_hits, len(shard_args)) + + +def _RemoveImageExtensions(directory, path_info): + """Remove extensions from image files in the passed directory. + + This reduces binary size but does not affect android's ability to load the + images. + """ + for f in _IterFiles(directory): + if (f.endswith('.png') or f.endswith('.webp')) and not f.endswith('.9.png'): + path_with_extension = f + path_no_extension = os.path.splitext(path_with_extension)[0] + if path_no_extension != path_with_extension: + shutil.move(path_with_extension, path_no_extension) + path_info.RegisterRename( + os.path.relpath(path_with_extension, directory), + os.path.relpath(path_no_extension, directory)) + + +def _CompileSingleDep(index, dep_subdir, keep_predicate, aapt2_path, + partials_dir): + unique_name = '{}_{}'.format(index, os.path.basename(dep_subdir)) + partial_path = os.path.join(partials_dir, '{}.zip'.format(unique_name)) + + compile_command = [ + aapt2_path, + 'compile', + # TODO(wnwen): Turn this on once aapt2 forces 9-patch to be crunched. + # '--no-crunch', + '--dir', + dep_subdir, + '-o', + partial_path + ] + + # There are resources targeting API-versions lower than our minapi. For + # various reasons it's easier to let aapt2 ignore these than for us to + # remove them from our build (e.g. it's from a 3rd party library). + build_utils.CheckOutput( + compile_command, + stderr_filter=lambda output: build_utils.FilterLines( + output, r'ignoring configuration .* for (styleable|attribute)')) + + # Filtering these files is expensive, so only apply filters to the partials + # that have been explicitly targeted. + if keep_predicate: + logging.debug('Applying .arsc filtering to %s', dep_subdir) + protoresources.StripUnwantedResources(partial_path, keep_predicate) + return partial_path + + +def _CreateValuesKeepPredicate(exclusion_rules, dep_subdir): + patterns = [ + x[1] for x in exclusion_rules + if build_utils.MatchesGlob(dep_subdir, [x[0]]) + ] + if not patterns: + return None + + regexes = [re.compile(p) for p in patterns] + return lambda x: not any(r.search(x) for r in regexes) + + +def _CompileDeps(aapt2_path, dep_subdirs, dep_subdir_overlay_set, temp_dir, + exclusion_rules): + partials_dir = os.path.join(temp_dir, 'partials') + build_utils.MakeDirectory(partials_dir) + + job_params = [(i, dep_subdir, + _CreateValuesKeepPredicate(exclusion_rules, dep_subdir)) + for i, dep_subdir in enumerate(dep_subdirs)] + + # Filtering is slow, so ensure jobs with keep_predicate are started first. + job_params.sort(key=lambda x: not x[2]) + partials = list( + parallel.BulkForkAndCall(_CompileSingleDep, + job_params, + aapt2_path=aapt2_path, + partials_dir=partials_dir)) + + partials_cmd = list() + for i, partial in enumerate(partials): + dep_subdir = job_params[i][1] + if dep_subdir in dep_subdir_overlay_set: + partials_cmd += ['-R'] + partials_cmd += [partial] + return partials_cmd + + +def _CreateResourceInfoFile(path_info, info_path, dependencies_res_zips): + for zip_file in dependencies_res_zips: + zip_info_file_path = zip_file + '.info' + if os.path.exists(zip_info_file_path): + path_info.MergeInfoFile(zip_info_file_path) + path_info.Write(info_path) + + +def _RemoveUnwantedLocalizedStrings(dep_subdirs, options): + """Remove localized strings that should not go into the final output. + + Args: + dep_subdirs: List of resource dependency directories. + options: Command-line options namespace. + """ + # Collect locale and file paths from the existing subdirs. + # The following variable maps Android locale names to + # sets of corresponding xml file paths. + locale_to_files_map = collections.defaultdict(set) + for directory in dep_subdirs: + for f in _IterFiles(directory): + locale = resource_utils.FindLocaleInStringResourceFilePath(f) + if locale: + locale_to_files_map[locale].add(f) + + all_locales = set(locale_to_files_map) + + # Set A: wanted locales, either all of them or the + # list provided by --locale-allowlist. + wanted_locales = all_locales + if options.locale_allowlist: + wanted_locales = _ToAndroidLocales(options.locale_allowlist) + + # Set B: shared resources locales, which is either set A + # or the list provided by --shared-resources-allowlist-locales + shared_resources_locales = wanted_locales + shared_names_allowlist = set() + if options.shared_resources_allowlist_locales: + shared_names_allowlist = set( + resource_utils.GetRTxtStringResourceNames( + options.shared_resources_allowlist)) + + shared_resources_locales = _ToAndroidLocales( + options.shared_resources_allowlist_locales) + + # Remove any file that belongs to a locale not covered by + # either A or B. + removable_locales = (all_locales - wanted_locales - shared_resources_locales) + for locale in removable_locales: + for path in locale_to_files_map[locale]: + os.remove(path) + + # For any locale in B but not in A, only keep the shared + # resource strings in each file. + for locale in shared_resources_locales - wanted_locales: + for path in locale_to_files_map[locale]: + resource_utils.FilterAndroidResourceStringsXml( + path, lambda x: x in shared_names_allowlist) + + # For any locale in A but not in B, only keep the strings + # that are _not_ from shared resources in the file. + for locale in wanted_locales - shared_resources_locales: + for path in locale_to_files_map[locale]: + resource_utils.FilterAndroidResourceStringsXml( + path, lambda x: x not in shared_names_allowlist) + + +def _FilterResourceFiles(dep_subdirs, keep_predicate): + # Create a function that selects which resource files should be packaged + # into the final output. Any file that does not pass the predicate will + # be removed below. + png_paths = [] + for directory in dep_subdirs: + for f in _IterFiles(directory): + if not keep_predicate(f): + os.remove(f) + elif f.endswith('.png'): + png_paths.append(f) + + return png_paths + + +def _PackageApk(options, build): + """Compile and link resources with aapt2. + + Args: + options: The command-line options. + build: BuildContext object. + Returns: + The manifest package name for the APK. + """ + logging.debug('Extracting resource .zips') + dep_subdirs = [] + dep_subdir_overlay_set = set() + for dependency_res_zip in options.dependencies_res_zips: + extracted_dep_subdirs = resource_utils.ExtractDeps([dependency_res_zip], + build.deps_dir) + dep_subdirs += extracted_dep_subdirs + if dependency_res_zip in options.dependencies_res_zip_overlays: + dep_subdir_overlay_set.update(extracted_dep_subdirs) + + logging.debug('Applying locale transformations') + path_info = resource_utils.ResourceInfoFile() + _RenameLocaleResourceDirs(dep_subdirs, path_info) + + logging.debug('Applying file-based exclusions') + keep_predicate = _CreateKeepPredicate(options.resource_exclusion_regex, + options.resource_exclusion_exceptions) + png_paths = _FilterResourceFiles(dep_subdirs, keep_predicate) + + if options.locale_allowlist or options.shared_resources_allowlist_locales: + logging.debug('Applying locale-based string exclusions') + _RemoveUnwantedLocalizedStrings(dep_subdirs, options) + + if png_paths and options.png_to_webp: + logging.debug('Converting png->webp') + _ConvertToWebP(options.webp_binary, png_paths, path_info, + options.webp_cache_dir) + logging.debug('Applying drawable transformations') + for directory in dep_subdirs: + _MoveImagesToNonMdpiFolders(directory, path_info) + _RemoveImageExtensions(directory, path_info) + + logging.debug('Running aapt2 compile') + exclusion_rules = [x.split(':', 1) for x in options.values_filter_rules] + partials = _CompileDeps(options.aapt2_path, dep_subdirs, + dep_subdir_overlay_set, build.temp_dir, + exclusion_rules) + + link_command = [ + options.aapt2_path, + 'link', + '--auto-add-overlay', + '--no-version-vectors', + # Set SDK versions in case they are not set in the Android manifest. + '--min-sdk-version', + options.min_sdk_version, + '--target-sdk-version', + options.target_sdk_version, + '--output-text-symbols', + build.r_txt_path, + ] + + for j in options.include_resources: + link_command += ['-I', j] + if options.version_code: + link_command += ['--version-code', options.version_code] + if options.version_name: + link_command += ['--version-name', options.version_name] + if options.proguard_file: + link_command += ['--proguard', build.proguard_path] + link_command += ['--proguard-minimal-keep-rules'] + if options.proguard_file_main_dex: + link_command += ['--proguard-main-dex', build.proguard_main_dex_path] + if options.emit_ids_out: + link_command += ['--emit-ids', build.emit_ids_path] + + # Note: only one of --proto-format, --shared-lib or --app-as-shared-lib + # can be used with recent versions of aapt2. + if options.shared_resources: + link_command.append('--shared-lib') + + if options.no_xml_namespaces: + link_command.append('--no-xml-namespaces') + + if options.package_id: + link_command += [ + '--package-id', + hex(options.package_id), + '--allow-reserved-package-id', + ] + + fixed_manifest, desired_manifest_package_name, fixed_manifest_package = ( + _FixManifest(options, build.temp_dir)) + if options.rename_manifest_package: + desired_manifest_package_name = options.rename_manifest_package + + link_command += [ + '--manifest', fixed_manifest, '--rename-manifest-package', + desired_manifest_package_name + ] + + # Creates a .zip with AndroidManifest.xml, resources.arsc, res/* + # Also creates R.txt + if options.use_resource_ids_path: + _CreateStableIdsFile(options.use_resource_ids_path, build.stable_ids_path, + fixed_manifest_package) + link_command += ['--stable-ids', build.stable_ids_path] + + link_command += partials + + # We always create a binary arsc file first, then convert to proto, so flags + # such as --shared-lib can be supported. + link_command += ['-o', build.arsc_path] + + logging.debug('Starting: aapt2 link') + link_proc = subprocess.Popen(link_command) + + # Create .res.info file in parallel. + _CreateResourceInfoFile(path_info, build.info_path, + options.dependencies_res_zips) + logging.debug('Created .res.info file') + + exit_code = link_proc.wait() + logging.debug('Finished: aapt2 link') + if exit_code: + raise subprocess.CalledProcessError(exit_code, link_command) + + if options.proguard_file and (options.shared_resources + or options.app_as_shared_lib): + # Make sure the R class associated with the manifest package does not have + # its onResourcesLoaded method obfuscated or removed, so that the framework + # can call it in the case where the APK is being loaded as a library. + with open(build.proguard_path, 'a') as proguard_file: + keep_rule = ''' + -keep,allowoptimization class {package}.R {{ + public static void onResourcesLoaded(int); + }} + '''.format(package=desired_manifest_package_name) + proguard_file.write(textwrap.dedent(keep_rule)) + + logging.debug('Running aapt2 convert') + build_utils.CheckOutput([ + options.aapt2_path, 'convert', '--output-format', 'proto', '-o', + build.proto_path, build.arsc_path + ]) + + # Workaround for b/147674078. This is only needed for WebLayer and does not + # affect WebView usage, since WebView does not used dynamic attributes. + if options.shared_resources: + logging.debug('Hardcoding dynamic attributes') + protoresources.HardcodeSharedLibraryDynamicAttributes( + build.proto_path, options.is_bundle_module, + options.shared_resources_allowlist) + + build_utils.CheckOutput([ + options.aapt2_path, 'convert', '--output-format', 'binary', '-o', + build.arsc_path, build.proto_path + ]) + + return desired_manifest_package_name + + +@contextlib.contextmanager +def _CreateStableIdsFile(in_path, out_path, package_name): + """Transforms a file generated by --emit-ids from another package. + + --stable-ids is generally meant to be used by different versions of the same + package. To make it work for other packages, we need to transform the package + name references to match the package that resources are being generated for. + + Note: This will fail if the package ID of the resources in + |options.use_resource_ids_path| does not match the package ID of the + resources being linked. + """ + with open(in_path) as stable_ids_file: + with open(out_path, 'w') as output_ids_file: + output_stable_ids = re.sub( + r'^.*?:', + package_name + ':', + stable_ids_file.read(), + flags=re.MULTILINE) + output_ids_file.write(output_stable_ids) + + +def _WriteOutputs(options, build): + possible_outputs = [ + (options.srcjar_out, build.srcjar_path), + (options.r_text_out, build.r_txt_path), + (options.arsc_path, build.arsc_path), + (options.proto_path, build.proto_path), + (options.proguard_file, build.proguard_path), + (options.proguard_file_main_dex, build.proguard_main_dex_path), + (options.emit_ids_out, build.emit_ids_path), + (options.info_path, build.info_path), + ] + + for final, temp in possible_outputs: + # Write file only if it's changed. + if final and not (os.path.exists(final) and filecmp.cmp(final, temp)): + shutil.move(temp, final) + + +def _CreateNormalizedManifestForVerification(options): + with build_utils.TempDir() as tempdir: + fixed_manifest, _, _ = _FixManifest( + options, tempdir, extra_manifest=options.extra_verification_manifest) + with open(fixed_manifest) as f: + return manifest_utils.NormalizeManifest(f.read()) + + +def main(args): + build_utils.InitLogging('RESOURCE_DEBUG') + args = build_utils.ExpandFileArgs(args) + options = _ParseArgs(args) + + if options.expected_file: + actual_data = _CreateNormalizedManifestForVerification(options) + diff_utils.CheckExpectations(actual_data, options) + if options.only_verify_expectations: + return + + path = options.arsc_path or options.proto_path + debug_temp_resources_dir = os.environ.get('TEMP_RESOURCES_DIR') + if debug_temp_resources_dir: + path = os.path.join(debug_temp_resources_dir, os.path.basename(path)) + else: + # Use a deterministic temp directory since .pb files embed the absolute + # path of resources: crbug.com/939984 + path = path + '.tmpdir' + build_utils.DeleteDirectory(path) + + with resource_utils.BuildContext( + temp_dir=path, keep_files=bool(debug_temp_resources_dir)) as build: + + manifest_package_name = _PackageApk(options, build) + + # If --shared-resources-allowlist is used, all the resources listed in the + # corresponding R.txt file will be non-final, and an onResourcesLoaded() + # will be generated to adjust them at runtime. + # + # Otherwise, if --shared-resources is used, the all resources will be + # non-final, and an onResourcesLoaded() method will be generated too. + # + # Otherwise, all resources will be final, and no method will be generated. + # + rjava_build_options = resource_utils.RJavaBuildOptions() + if options.shared_resources_allowlist: + rjava_build_options.ExportSomeResources( + options.shared_resources_allowlist) + rjava_build_options.GenerateOnResourcesLoaded() + if options.shared_resources: + # The final resources will only be used in WebLayer, so hardcode the + # package ID to be what WebLayer expects. + rjava_build_options.SetFinalPackageId( + protoresources.SHARED_LIBRARY_HARDCODED_ID) + elif options.shared_resources or options.app_as_shared_lib: + rjava_build_options.ExportAllResources() + rjava_build_options.GenerateOnResourcesLoaded() + + custom_root_package_name = options.r_java_root_package_name + grandparent_custom_package_name = None + + # Always generate an R.java file for the package listed in + # AndroidManifest.xml because this is where Android framework looks to find + # onResourcesLoaded() for shared library apks. While not actually necessary + # for application apks, it also doesn't hurt. + apk_package_name = manifest_package_name + + if options.package_name and not options.arsc_package_name: + # Feature modules have their own custom root package name and should + # inherit from the appropriate base module package. This behaviour should + # not be present for test apks with an apk under test. Thus, + # arsc_package_name is used as it is only defined for test apks with an + # apk under test. + custom_root_package_name = options.package_name + grandparent_custom_package_name = options.r_java_root_package_name + # Feature modules have the same manifest package as the base module but + # they should not create an R.java for said manifest package because it + # will be created in the base module. + apk_package_name = None + + logging.debug('Creating R.srcjar') + resource_utils.CreateRJavaFiles( + build.srcjar_dir, apk_package_name, build.r_txt_path, + options.extra_res_packages, rjava_build_options, options.srcjar_out, + custom_root_package_name, grandparent_custom_package_name, + options.extra_main_r_text_files) + build_utils.ZipDir(build.srcjar_path, build.srcjar_dir) + + # Sanity check that the created resources have the expected package ID. + logging.debug('Performing sanity check') + if options.package_id: + expected_id = options.package_id + elif options.shared_resources: + expected_id = 0 + else: + expected_id = 127 # == '0x7f'. + _, package_id = resource_utils.ExtractArscPackage( + options.aapt2_path, + build.arsc_path if options.arsc_path else build.proto_path) + # When there are no resources, ExtractArscPackage returns (None, None), in + # this case there is no need to check for matching package ID. + if package_id is not None and package_id != expected_id: + raise Exception( + 'Invalid package ID 0x%x (expected 0x%x)' % (package_id, expected_id)) + + logging.debug('Copying outputs') + _WriteOutputs(options, build) + + if options.depfile: + depfile_deps = (options.dependencies_res_zips + + options.dependencies_res_zip_overlays + + options.extra_main_r_text_files + options.include_resources) + build_utils.WriteDepfile(options.depfile, options.srcjar_out, depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/compile_resources.pydeps b/third_party/libwebrtc/build/android/gyp/compile_resources.pydeps new file mode 100644 index 0000000000..907601422d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/compile_resources.pydeps @@ -0,0 +1,39 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/compile_resources.pydeps build/android/gyp/compile_resources.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../../third_party/six/src/six.py +../../gn_helpers.py +compile_resources.py +proto/Configuration_pb2.py +proto/Resources_pb2.py +proto/__init__.py +util/__init__.py +util/build_utils.py +util/diff_utils.py +util/manifest_utils.py +util/parallel.py +util/protoresources.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/copy_ex.py b/third_party/libwebrtc/build/android/gyp/copy_ex.py new file mode 100755 index 0000000000..41604c4627 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/copy_ex.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# 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.""" + +from __future__ import print_function + +import filecmp +import itertools +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 CopyFile(f, dest, deps): + """Copy file or directory and update deps.""" + if os.path.isdir(f): + shutil.copytree(f, os.path.join(dest, os.path.basename(f))) + deps.extend(_get_all_files(f)) + else: + if os.path.isfile(os.path.join(dest, os.path.basename(f))): + dest = os.path.join(dest, os.path.basename(f)) + + deps.append(f) + + if os.path.isfile(dest): + if filecmp.cmp(dest, f, shallow=False): + return + # The shutil.copy() below would fail if the file does not have write + # permissions. Deleting the file has similar costs to modifying the + # permissions. + os.unlink(dest) + + shutil.copy(f, dest) + +def DoCopy(options, deps): + """Copy files or directories given in options.files and update deps.""" + files = list(itertools.chain.from_iterable(build_utils.ParseGnList(f) + for f in options.files)) + + for f in files: + if os.path.isdir(f) and not options.clear: + print('To avoid stale files you must use --clear when copying ' + 'directories') + sys.exit(-1) + CopyFile(f, options.dest, deps) + +def DoRenaming(options, deps): + """Copy and rename files given in options.renaming_sources and update deps.""" + src_files = list(itertools.chain.from_iterable( + build_utils.ParseGnList(f) + for f in options.renaming_sources)) + + dest_files = list(itertools.chain.from_iterable( + build_utils.ParseGnList(f) + for f in options.renaming_destinations)) + + if (len(src_files) != len(dest_files)): + print('Renaming source and destination files not match.') + sys.exit(-1) + + for src, dest in zip(src_files, dest_files): + if os.path.isdir(src): + print('renaming diretory is not supported.') + sys.exit(-1) + else: + CopyFile(src, os.path.join(options.dest, dest), deps) + +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.') + parser.add_option('--renaming-sources', + action='append', + help='List of files need to be renamed while being ' + 'copied to dest directory') + parser.add_option('--renaming-destinations', + action='append', + help='List of destination file name without path, the ' + 'number of elements must match rename-sources.') + + options, _ = parser.parse_args(args) + + if options.clear: + build_utils.DeleteDirectory(options.dest) + build_utils.MakeDirectory(options.dest) + + deps = [] + + if options.files: + DoCopy(options, deps) + + if options.renaming_sources: + DoRenaming(options, deps) + + if options.depfile: + build_utils.WriteDepfile(options.depfile, options.stamp, deps) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/copy_ex.pydeps b/third_party/libwebrtc/build/android/gyp/copy_ex.pydeps new file mode 100644 index 0000000000..37352512be --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/copy_ex.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/copy_ex.pydeps build/android/gyp/copy_ex.py +../../gn_helpers.py +copy_ex.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.py b/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.py new file mode 100755 index 0000000000..a5a5b6658a --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Chromium 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 string +import sys + +from util import build_utils + +SCRIPT_TEMPLATE = string.Template("""\ +#!/usr/bin/env python3 +# +# This file was generated by build/android/gyp/create_apk_operations_script.py + +import os +import sys + +def main(): + script_directory = os.path.dirname(__file__) + resolve = lambda p: p if p is None else os.path.abspath(os.path.join( + script_directory, p)) + sys.path.append(resolve(${APK_OPERATIONS_DIR})) + import apk_operations + output_dir = resolve(${OUTPUT_DIR}) + apk_operations.Run( + output_dir, + resolve(${APK_PATH}), + [resolve(p) for p in ${ADDITIONAL_APK_PATHS}], + resolve(${INC_JSON_PATH}), + ${FLAGS_FILE}, + ${TARGET_CPU}, + resolve(${MAPPING_PATH})) + + +if __name__ == '__main__': + sys.exit(main()) +""") + + +def main(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + parser.add_argument('--script-output-path', + help='Output path for executable script.') + parser.add_argument('--apk-path') + parser.add_argument('--incremental-install-json-path') + parser.add_argument('--command-line-flags-file') + parser.add_argument('--target-cpu') + parser.add_argument( + '--additional-apk-path', + action='append', + dest='additional_apk_paths', + default=[], + help='Paths to APKs to be installed prior to --apk-path.') + parser.add_argument('--proguard-mapping-path') + args = parser.parse_args(args) + + def relativize(path): + """Returns the path relative to the output script directory.""" + if path is None: + return path + return os.path.relpath(path, os.path.dirname(args.script_output_path)) + apk_operations_dir = os.path.join(os.path.dirname(__file__), os.path.pardir) + apk_operations_dir = relativize(apk_operations_dir) + + with open(args.script_output_path, 'w') as script: + script_dict = { + 'APK_OPERATIONS_DIR': repr(apk_operations_dir), + 'OUTPUT_DIR': repr(relativize('.')), + 'APK_PATH': repr(relativize(args.apk_path)), + 'ADDITIONAL_APK_PATHS': + [relativize(p) for p in args.additional_apk_paths], + 'INC_JSON_PATH': repr(relativize(args.incremental_install_json_path)), + 'MAPPING_PATH': repr(relativize(args.proguard_mapping_path)), + 'FLAGS_FILE': repr(args.command_line_flags_file), + 'TARGET_CPU': repr(args.target_cpu), + } + script.write(SCRIPT_TEMPLATE.substitute(script_dict)) + os.chmod(args.script_output_path, 0o750) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.pydeps b/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.pydeps new file mode 100644 index 0000000000..e09bb7244c --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_apk_operations_script.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_apk_operations_script.pydeps build/android/gyp/create_apk_operations_script.py +../../gn_helpers.py +create_apk_operations_script.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_app_bundle.py b/third_party/libwebrtc/build/android/gyp/create_app_bundle.py new file mode 100755 index 0000000000..8d03f08c34 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_app_bundle.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 The Chromium 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 an Android application bundle from one or more bundle modules.""" + +import argparse +import json +import os +import shutil +import sys +import zipfile + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from pylib.utils import dexdump + +from util import build_utils +from util import manifest_utils +from util import resource_utils +from xml.etree import ElementTree + +import bundletool + +# Location of language-based assets in bundle modules. +_LOCALES_SUBDIR = 'assets/locales/' + +# The fallback locale should always have its .pak file included in +# the base apk, i.e. not use language-based asset targetting. This ensures +# that Chrome won't crash on startup if its bundle is installed on a device +# with an unsupported system locale (e.g. fur-rIT). +_FALLBACK_LOCALE = 'en-US' + +# List of split dimensions recognized by this tool. +_ALL_SPLIT_DIMENSIONS = [ 'ABI', 'SCREEN_DENSITY', 'LANGUAGE' ] + +# Due to historical reasons, certain languages identified by Chromium with a +# 3-letters ISO 639-2 code, are mapped to a nearly equivalent 2-letters +# ISO 639-1 code instead (due to the fact that older Android releases only +# supported the latter when matching resources). +# +# the same conversion as for Java resources. +_SHORTEN_LANGUAGE_CODE_MAP = { + 'fil': 'tl', # Filipino to Tagalog. +} + +# A list of extensions corresponding to files that should never be compressed +# in the bundle. This used to be handled by bundletool automatically until +# release 0.8.0, which required that this be passed to the BundleConfig +# file instead. +# +# This is the original list, which was taken from aapt2, with 'webp' added to +# it (which curiously was missing from the list). +_UNCOMPRESSED_FILE_EXTS = [ + '3g2', '3gp', '3gpp', '3gpp2', 'aac', 'amr', 'awb', 'git', 'imy', 'jet', + 'jpeg', 'jpg', 'm4a', 'm4v', 'mid', 'midi', 'mkv', 'mp2', 'mp3', 'mp4', + 'mpeg', 'mpg', 'ogg', 'png', 'rtttl', 'smf', 'wav', 'webm', 'webp', 'wmv', + 'xmf' +] + + +def _ParseArgs(args): + parser = argparse.ArgumentParser() + parser.add_argument('--out-bundle', required=True, + help='Output bundle zip archive.') + parser.add_argument('--module-zips', required=True, + help='GN-list of module zip archives.') + parser.add_argument( + '--pathmap-in-paths', + action='append', + help='List of module pathmap files.') + parser.add_argument( + '--module-name', + action='append', + dest='module_names', + help='List of module names.') + parser.add_argument( + '--pathmap-out-path', help='Path to combined pathmap file for bundle.') + parser.add_argument( + '--rtxt-in-paths', action='append', help='GN-list of module R.txt files.') + parser.add_argument( + '--rtxt-out-path', help='Path to combined R.txt file for bundle.') + parser.add_argument('--uncompressed-assets', action='append', + help='GN-list of uncompressed assets.') + parser.add_argument( + '--compress-shared-libraries', + action='store_true', + help='Whether to store native libraries compressed.') + parser.add_argument('--compress-dex', + action='store_true', + help='Compress .dex files') + parser.add_argument('--split-dimensions', + help="GN-list of split dimensions to support.") + parser.add_argument( + '--base-module-rtxt-path', + help='Optional path to the base module\'s R.txt file, only used with ' + 'language split dimension.') + parser.add_argument( + '--base-allowlist-rtxt-path', + help='Optional path to an R.txt file, string resources ' + 'listed there _and_ in --base-module-rtxt-path will ' + 'be kept in the base bundle module, even if language' + ' splitting is enabled.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + + parser.add_argument( + '--validate-services', + action='store_true', + help='Check if services are in base module if isolatedSplits is enabled.') + + options = parser.parse_args(args) + options.module_zips = build_utils.ParseGnList(options.module_zips) + options.rtxt_in_paths = build_utils.ParseGnList(options.rtxt_in_paths) + options.pathmap_in_paths = build_utils.ParseGnList(options.pathmap_in_paths) + + if len(options.module_zips) == 0: + raise Exception('The module zip list cannot be empty.') + + # Merge all uncompressed assets into a set. + uncompressed_list = [] + if options.uncompressed_assets: + for l in options.uncompressed_assets: + for entry in build_utils.ParseGnList(l): + # Each entry has the following format: 'zipPath' or 'srcPath:zipPath' + pos = entry.find(':') + if pos >= 0: + uncompressed_list.append(entry[pos + 1:]) + else: + uncompressed_list.append(entry) + + options.uncompressed_assets = set(uncompressed_list) + + # Check that all split dimensions are valid + if options.split_dimensions: + options.split_dimensions = build_utils.ParseGnList(options.split_dimensions) + for dim in options.split_dimensions: + if dim.upper() not in _ALL_SPLIT_DIMENSIONS: + parser.error('Invalid split dimension "%s" (expected one of: %s)' % ( + dim, ', '.join(x.lower() for x in _ALL_SPLIT_DIMENSIONS))) + + # As a special case, --base-allowlist-rtxt-path can be empty to indicate + # that the module doesn't need such a allowlist. That's because it is easier + # to check this condition here than through GN rules :-( + if options.base_allowlist_rtxt_path == '': + options.base_module_rtxt_path = None + + # Check --base-module-rtxt-path and --base-allowlist-rtxt-path usage. + if options.base_module_rtxt_path: + if not options.base_allowlist_rtxt_path: + parser.error( + '--base-module-rtxt-path requires --base-allowlist-rtxt-path') + if 'language' not in options.split_dimensions: + parser.error('--base-module-rtxt-path is only valid with ' + 'language-based splits.') + + return options + + +def _MakeSplitDimension(value, enabled): + """Return dict modelling a BundleConfig splitDimension entry.""" + return {'value': value, 'negate': not enabled} + + +def _GenerateBundleConfigJson(uncompressed_assets, compress_dex, + compress_shared_libraries, split_dimensions, + base_master_resource_ids): + """Generate a dictionary that can be written to a JSON BuildConfig. + + Args: + uncompressed_assets: A list or set of file paths under assets/ that always + be stored uncompressed. + compressed_dex: Boolean, whether to compress .dex. + compress_shared_libraries: Boolean, whether to compress native libs. + split_dimensions: list of split dimensions. + base_master_resource_ids: Optional list of 32-bit resource IDs to keep + inside the base module, even when split dimensions are enabled. + Returns: + A dictionary that can be written as a json file. + """ + # Compute splitsConfig list. Each item is a dictionary that can have + # the following keys: + # 'value': One of ['LANGUAGE', 'DENSITY', 'ABI'] + # 'negate': Boolean, True to indicate that the bundle should *not* be + # split (unused at the moment by this script). + + split_dimensions = [ _MakeSplitDimension(dim, dim in split_dimensions) + for dim in _ALL_SPLIT_DIMENSIONS ] + + # Native libraries loaded by the crazy linker. + # Whether other .so files are compressed is controlled by + # "uncompressNativeLibraries". + uncompressed_globs = ['lib/*/crazy.*'] + # Locale-specific pak files stored in bundle splits need not be compressed. + uncompressed_globs.extend( + ['assets/locales#lang_*/*.pak', 'assets/fallback-locales/*.pak']) + uncompressed_globs.extend('assets/' + x for x in uncompressed_assets) + # NOTE: Use '**' instead of '*' to work through directories! + uncompressed_globs.extend('**.' + ext for ext in _UNCOMPRESSED_FILE_EXTS) + if not compress_dex: + # Explicit glob required only when using bundletool. Play Store looks for + # "uncompressDexFiles" set below. + uncompressed_globs.extend('classes*.dex') + + data = { + 'optimizations': { + 'splitsConfig': { + 'splitDimension': split_dimensions, + }, + 'uncompressNativeLibraries': { + 'enabled': not compress_shared_libraries, + }, + 'uncompressDexFiles': { + 'enabled': True, # Applies only for P+. + } + }, + 'compression': { + 'uncompressedGlob': sorted(uncompressed_globs), + }, + } + + if base_master_resource_ids: + data['master_resources'] = { + 'resource_ids': list(base_master_resource_ids), + } + + return json.dumps(data, indent=2) + + +def _RewriteLanguageAssetPath(src_path): + """Rewrite the destination path of a locale asset for language-based splits. + + Should only be used when generating bundles with language-based splits. + This will rewrite paths that look like locales/.pak into + locales#/.pak, where is the language code + from the locale. + + Returns new path. + """ + if not src_path.startswith(_LOCALES_SUBDIR) or not src_path.endswith('.pak'): + return [src_path] + + locale = src_path[len(_LOCALES_SUBDIR):-4] + android_locale = resource_utils.ToAndroidLocaleName(locale) + + # The locale format is - or or BCP-47 (e.g b+sr+Latn). + # Extract the language. + pos = android_locale.find('-') + if android_locale.startswith('b+'): + # If locale is in BCP-47 the language is the second tag (e.g. b+sr+Latn) + android_language = android_locale.split('+')[1] + elif pos >= 0: + android_language = android_locale[:pos] + else: + android_language = android_locale + + if locale == _FALLBACK_LOCALE: + # Fallback locale .pak files must be placed in a different directory + # to ensure they are always stored in the base module. + result_path = 'assets/fallback-locales/%s.pak' % locale + else: + # Other language .pak files go into a language-specific asset directory + # that bundletool will store in separate split APKs. + result_path = 'assets/locales#lang_%s/%s.pak' % (android_language, locale) + + return result_path + + +def _SplitModuleForAssetTargeting(src_module_zip, tmp_dir, split_dimensions): + """Splits assets in a module if needed. + + Args: + src_module_zip: input zip module path. + tmp_dir: Path to temporary directory, where the new output module might + be written to. + split_dimensions: list of split dimensions. + + Returns: + If the module doesn't need asset targeting, doesn't do anything and + returns src_module_zip. Otherwise, create a new module zip archive under + tmp_dir with the same file name, but which contains assets paths targeting + the proper dimensions. + """ + split_language = 'LANGUAGE' in split_dimensions + if not split_language: + # Nothing to target, so return original module path. + return src_module_zip + + with zipfile.ZipFile(src_module_zip, 'r') as src_zip: + language_files = [ + f for f in src_zip.namelist() if f.startswith(_LOCALES_SUBDIR)] + + if not language_files: + # Not language-based assets to split in this module. + return src_module_zip + + tmp_zip = os.path.join(tmp_dir, os.path.basename(src_module_zip)) + with zipfile.ZipFile(tmp_zip, 'w') as dst_zip: + for info in src_zip.infolist(): + src_path = info.filename + is_compressed = info.compress_type != zipfile.ZIP_STORED + + dst_path = src_path + if src_path in language_files: + dst_path = _RewriteLanguageAssetPath(src_path) + + build_utils.AddToZipHermetic( + dst_zip, + dst_path, + data=src_zip.read(src_path), + compress=is_compressed) + + return tmp_zip + + +def _GenerateBaseResourcesAllowList(base_module_rtxt_path, + base_allowlist_rtxt_path): + """Generate a allowlist of base master resource ids. + + Args: + base_module_rtxt_path: Path to base module R.txt file. + base_allowlist_rtxt_path: Path to base allowlist R.txt file. + Returns: + list of resource ids. + """ + ids_map = resource_utils.GenerateStringResourcesAllowList( + base_module_rtxt_path, base_allowlist_rtxt_path) + return ids_map.keys() + + +def _ConcatTextFiles(in_paths, out_path): + """Concatenate the contents of multiple text files into one. + + The each file contents is preceded by a line containing the original filename. + + Args: + in_paths: List of input file paths. + out_path: Path to output file. + """ + with open(out_path, 'w') as out_file: + for in_path in in_paths: + if not os.path.exists(in_path): + continue + with open(in_path, 'r') as in_file: + out_file.write('-- Contents of {}\n'.format(os.path.basename(in_path))) + out_file.write(in_file.read()) + + +def _LoadPathmap(pathmap_path): + """Load the pathmap of obfuscated resource paths. + + Returns: A dict mapping from obfuscated paths to original paths or an + empty dict if passed a None |pathmap_path|. + """ + if pathmap_path is None: + return {} + + pathmap = {} + with open(pathmap_path, 'r') as f: + for line in f: + line = line.strip() + if line.startswith('--') or line == '': + continue + original, renamed = line.split(' -> ') + pathmap[renamed] = original + return pathmap + + +def _WriteBundlePathmap(module_pathmap_paths, module_names, + bundle_pathmap_path): + """Combine the contents of module pathmaps into a bundle pathmap. + + This rebases the resource paths inside the module pathmap before adding them + to the bundle pathmap. So res/a.xml inside the base module pathmap would be + base/res/a.xml in the bundle pathmap. + """ + with open(bundle_pathmap_path, 'w') as bundle_pathmap_file: + for module_pathmap_path, module_name in zip(module_pathmap_paths, + module_names): + if not os.path.exists(module_pathmap_path): + continue + module_pathmap = _LoadPathmap(module_pathmap_path) + for short_path, long_path in module_pathmap.items(): + rebased_long_path = '{}/{}'.format(module_name, long_path) + rebased_short_path = '{}/{}'.format(module_name, short_path) + line = '{} -> {}\n'.format(rebased_long_path, rebased_short_path) + bundle_pathmap_file.write(line) + + +def _GetManifestForModule(bundle_path, module_name): + return ElementTree.fromstring( + bundletool.RunBundleTool([ + 'dump', 'manifest', '--bundle', bundle_path, '--module', module_name + ])) + + +def _GetComponentNames(manifest, tag_name): + android_name = '{%s}name' % manifest_utils.ANDROID_NAMESPACE + return [s.attrib.get(android_name) for s in manifest.iter(tag_name)] + + +def _MaybeCheckServicesAndProvidersPresentInBase(bundle_path, module_zips): + """Checks bundles with isolated splits define all services in the base module. + + Due to b/169196314, service classes are not found if they are not present in + the base module. Providers are also checked because they are loaded early in + startup, and keeping them in the base module gives more time for the chrome + split to load. + """ + base_manifest = _GetManifestForModule(bundle_path, 'base') + isolated_splits = base_manifest.get('{%s}isolatedSplits' % + manifest_utils.ANDROID_NAMESPACE) + if isolated_splits != 'true': + return + + # Collect service names from all split manifests. + base_zip = None + service_names = _GetComponentNames(base_manifest, 'service') + provider_names = _GetComponentNames(base_manifest, 'provider') + for module_zip in module_zips: + name = os.path.basename(module_zip)[:-len('.zip')] + if name == 'base': + base_zip = module_zip + else: + service_names.extend( + _GetComponentNames(_GetManifestForModule(bundle_path, name), + 'service')) + module_providers = _GetComponentNames( + _GetManifestForModule(bundle_path, name), 'provider') + if module_providers: + raise Exception("Providers should all be declared in the base manifest." + " '%s' module declared: %s" % (name, module_providers)) + + # Extract classes from the base module's dex. + classes = set() + base_package_name = manifest_utils.GetPackage(base_manifest) + for package in dexdump.Dump(base_zip): + for name, package_dict in package.items(): + if not name: + name = base_package_name + classes.update('%s.%s' % (name, c) + for c in package_dict['classes'].keys()) + + ignored_service_names = { + # Defined in the chime DFM manifest, but unused. + # org.chromium.chrome.browser.chime.ScheduledTaskService is used instead. + ("com.google.android.libraries.notifications.entrypoints.scheduled." + "ScheduledTaskService"), + + # Defined in the chime DFM manifest, only used pre-O (where isolated + # splits are not supported). + ("com.google.android.libraries.notifications.executor.impl.basic." + "ChimeExecutorApiService"), + } + + # Ensure all services are present in base module. + for service_name in service_names: + if service_name not in classes: + if service_name in ignored_service_names: + continue + raise Exception("Service %s should be present in the base module's dex." + " See b/169196314 for more details." % service_name) + + # Ensure all providers are present in base module. + for provider_name in provider_names: + if provider_name not in classes: + raise Exception( + "Provider %s should be present in the base module's dex." % + provider_name) + + +def main(args): + args = build_utils.ExpandFileArgs(args) + options = _ParseArgs(args) + + split_dimensions = [] + if options.split_dimensions: + split_dimensions = [x.upper() for x in options.split_dimensions] + + + with build_utils.TempDir() as tmp_dir: + module_zips = [ + _SplitModuleForAssetTargeting(module, tmp_dir, split_dimensions) \ + for module in options.module_zips] + + base_master_resource_ids = None + if options.base_module_rtxt_path: + base_master_resource_ids = _GenerateBaseResourcesAllowList( + options.base_module_rtxt_path, options.base_allowlist_rtxt_path) + + bundle_config = _GenerateBundleConfigJson(options.uncompressed_assets, + options.compress_dex, + options.compress_shared_libraries, + split_dimensions, + base_master_resource_ids) + + tmp_bundle = os.path.join(tmp_dir, 'tmp_bundle') + + # Important: bundletool requires that the bundle config file is + # named with a .pb.json extension. + tmp_bundle_config = tmp_bundle + '.BundleConfig.pb.json' + + with open(tmp_bundle_config, 'w') as f: + f.write(bundle_config) + + cmd_args = build_utils.JavaCmd(options.warnings_as_errors) + [ + '-jar', + bundletool.BUNDLETOOL_JAR_PATH, + 'build-bundle', + '--modules=' + ','.join(module_zips), + '--output=' + tmp_bundle, + '--config=' + tmp_bundle_config, + ] + + build_utils.CheckOutput( + cmd_args, + print_stdout=True, + print_stderr=True, + stderr_filter=build_utils.FilterReflectiveAccessJavaWarnings, + fail_on_output=options.warnings_as_errors) + + if options.validate_services: + # TODO(crbug.com/1126301): This step takes 0.4s locally for bundles with + # isolated splits disabled and 2s for bundles with isolated splits + # enabled. Consider making this run in parallel or move into a separate + # step before enabling isolated splits by default. + _MaybeCheckServicesAndProvidersPresentInBase(tmp_bundle, module_zips) + + shutil.move(tmp_bundle, options.out_bundle) + + if options.rtxt_out_path: + _ConcatTextFiles(options.rtxt_in_paths, options.rtxt_out_path) + + if options.pathmap_out_path: + _WriteBundlePathmap(options.pathmap_in_paths, options.module_names, + options.pathmap_out_path) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/create_app_bundle.pydeps b/third_party/libwebrtc/build/android/gyp/create_app_bundle.pydeps new file mode 100644 index 0000000000..503dfb0dc5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_app_bundle.pydeps @@ -0,0 +1,49 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_app_bundle.pydeps build/android/gyp/create_app_bundle.py +../../../third_party/catapult/devil/devil/__init__.py +../../../third_party/catapult/devil/devil/android/__init__.py +../../../third_party/catapult/devil/devil/android/constants/__init__.py +../../../third_party/catapult/devil/devil/android/constants/chrome.py +../../../third_party/catapult/devil/devil/android/sdk/__init__.py +../../../third_party/catapult/devil/devil/android/sdk/keyevent.py +../../../third_party/catapult/devil/devil/android/sdk/version_codes.py +../../../third_party/catapult/devil/devil/base_error.py +../../../third_party/catapult/devil/devil/constants/__init__.py +../../../third_party/catapult/devil/devil/constants/exit_codes.py +../../../third_party/catapult/devil/devil/utils/__init__.py +../../../third_party/catapult/devil/devil/utils/cmd_helper.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +../pylib/__init__.py +../pylib/constants/__init__.py +../pylib/utils/__init__.py +../pylib/utils/dexdump.py +bundletool.py +create_app_bundle.py +util/__init__.py +util/build_utils.py +util/manifest_utils.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.py b/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.py new file mode 100755 index 0000000000..059b4dd8af --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Chromium 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 .apks from an .aab.""" + +import argparse +import os +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from pylib.utils import app_bundle_utils + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--bundle', required=True, help='Path to input .aab file.') + parser.add_argument( + '--output', required=True, help='Path to output .apks file.') + parser.add_argument('--aapt2-path', required=True, help='Path to aapt2.') + parser.add_argument( + '--keystore-path', required=True, help='Path to keystore.') + parser.add_argument( + '--keystore-password', required=True, help='Keystore password.') + parser.add_argument( + '--keystore-name', required=True, help='Key name within keystore') + parser.add_argument( + '--minimal', + action='store_true', + help='Create APKs archive with minimal language support.') + parser.add_argument('--local-testing', + action='store_true', + help='Create APKs archive with local testing support.') + + args = parser.parse_args() + + app_bundle_utils.GenerateBundleApks(args.bundle, + args.output, + args.aapt2_path, + args.keystore_path, + args.keystore_password, + args.keystore_name, + local_testing=args.local_testing, + minimal=args.minimal, + check_for_noop=False) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.pydeps b/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.pydeps new file mode 100644 index 0000000000..5e04dae1d9 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_app_bundle_apks.pydeps @@ -0,0 +1,37 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_app_bundle_apks.pydeps build/android/gyp/create_app_bundle_apks.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +../../print_python_deps.py +../pylib/__init__.py +../pylib/utils/__init__.py +../pylib/utils/app_bundle_utils.py +bundletool.py +create_app_bundle_apks.py +util/__init__.py +util/build_utils.py +util/md5_check.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.py b/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.py new file mode 100755 index 0000000000..1bdb7670d3 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 wrapper script to manage an Android App Bundle.""" + +import argparse +import os +import string +import sys + +from util import build_utils + +SCRIPT_TEMPLATE = string.Template("""\ +#!/usr/bin/env python3 +# +# This file was generated by build/android/gyp/create_bundle_wrapper_script.py + +import os +import sys + +def main(): + script_directory = os.path.dirname(__file__) + resolve = lambda p: p if p is None else os.path.abspath(os.path.join( + script_directory, p)) + sys.path.append(resolve(${WRAPPED_SCRIPT_DIR})) + import apk_operations + + additional_apk_paths = [resolve(p) for p in ${ADDITIONAL_APK_PATHS}] + apk_operations.RunForBundle(output_directory=resolve(${OUTPUT_DIR}), + bundle_path=resolve(${BUNDLE_PATH}), + bundle_apks_path=resolve(${BUNDLE_APKS_PATH}), + additional_apk_paths=additional_apk_paths, + aapt2_path=resolve(${AAPT2_PATH}), + keystore_path=resolve(${KEYSTORE_PATH}), + keystore_password=${KEYSTORE_PASSWORD}, + keystore_alias=${KEY_NAME}, + package_name=${PACKAGE_NAME}, + command_line_flags_file=${FLAGS_FILE}, + proguard_mapping_path=resolve(${MAPPING_PATH}), + target_cpu=${TARGET_CPU}, + system_image_locales=${SYSTEM_IMAGE_LOCALES}, + default_modules=${DEFAULT_MODULES}) + +if __name__ == '__main__': + sys.exit(main()) +""") + +def main(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + parser.add_argument('--script-output-path', required=True, + help='Output path for executable script.') + parser.add_argument('--bundle-path', required=True) + parser.add_argument('--bundle-apks-path', required=True) + parser.add_argument( + '--additional-apk-path', + action='append', + dest='additional_apk_paths', + default=[], + help='Paths to APKs to be installed prior to --apk-path.') + parser.add_argument('--package-name', required=True) + parser.add_argument('--aapt2-path', required=True) + parser.add_argument('--keystore-path', required=True) + parser.add_argument('--keystore-password', required=True) + parser.add_argument('--key-name', required=True) + parser.add_argument('--command-line-flags-file') + parser.add_argument('--proguard-mapping-path') + parser.add_argument('--target-cpu') + parser.add_argument('--system-image-locales') + parser.add_argument('--default-modules', nargs='*', default=[]) + args = parser.parse_args(args) + + def relativize(path): + """Returns the path relative to the output script directory.""" + if path is None: + return path + return os.path.relpath(path, os.path.dirname(args.script_output_path)) + + wrapped_script_dir = os.path.join(os.path.dirname(__file__), os.path.pardir) + wrapped_script_dir = relativize(wrapped_script_dir) + with open(args.script_output_path, 'w') as script: + script_dict = { + 'WRAPPED_SCRIPT_DIR': + repr(wrapped_script_dir), + 'OUTPUT_DIR': + repr(relativize('.')), + 'BUNDLE_PATH': + repr(relativize(args.bundle_path)), + 'BUNDLE_APKS_PATH': + repr(relativize(args.bundle_apks_path)), + 'ADDITIONAL_APK_PATHS': + [relativize(p) for p in args.additional_apk_paths], + 'PACKAGE_NAME': + repr(args.package_name), + 'AAPT2_PATH': + repr(relativize(args.aapt2_path)), + 'KEYSTORE_PATH': + repr(relativize(args.keystore_path)), + 'KEYSTORE_PASSWORD': + repr(args.keystore_password), + 'KEY_NAME': + repr(args.key_name), + 'MAPPING_PATH': + repr(relativize(args.proguard_mapping_path)), + 'FLAGS_FILE': + repr(args.command_line_flags_file), + 'TARGET_CPU': + repr(args.target_cpu), + 'SYSTEM_IMAGE_LOCALES': + repr(build_utils.ParseGnList(args.system_image_locales)), + 'DEFAULT_MODULES': + repr(args.default_modules), + } + script.write(SCRIPT_TEMPLATE.substitute(script_dict)) + os.chmod(args.script_output_path, 0o750) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.pydeps b/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.pydeps new file mode 100644 index 0000000000..7758ed6272 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_bundle_wrapper_script.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_bundle_wrapper_script.pydeps build/android/gyp/create_bundle_wrapper_script.py +../../gn_helpers.py +create_bundle_wrapper_script.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_java_binary_script.py b/third_party/libwebrtc/build/android/gyp/create_java_binary_script.py new file mode 100755 index 0000000000..91fe600ea8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_java_binary_script.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# +# 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 python3 +# +# This file was generated by build/android/gyp/create_java_binary_script.py + +import argparse +import os +import sys + +self_dir = os.path.dirname(__file__) +classpath = [{classpath}] +extra_program_args = {extra_program_args} +java_path = {java_path} +if os.getcwd() != self_dir: + offset = os.path.relpath(self_dir, os.getcwd()) + fix_path = lambda p: os.path.normpath(os.path.join(offset, p)) + classpath = [fix_path(p) for p in classpath] + java_path = fix_path(java_path) +java_cmd = [java_path] +# This is a simple argparser for jvm, jar, and classpath arguments. +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument('--jar-args') +parser.add_argument('--jvm-args') +parser.add_argument('--classpath') +# Test_runner parses the classpath for sharding junit tests. +parser.add_argument('--print-classpath', action='store_true', + help='Prints the classpass. Used by test_runner.') +known_args, unknown_args = parser.parse_known_args(sys.argv[1:]) + +if known_args.print_classpath: + sys.stdout.write(':'.join(classpath)) + sys.exit(0) + +if known_args.jvm_args: + jvm_arguments = known_args.jvm_args.strip('"').split() + java_cmd.extend(jvm_arguments) +if known_args.jar_args: + jar_arguments = known_args.jar_args.strip('"').split() + if unknown_args: + raise Exception('There are unknown arguments') +else: + jar_arguments = unknown_args + +if known_args.classpath: + classpath += [known_args.classpath] + +{extra_flags} +java_cmd.extend( + ['-classpath', ':'.join(classpath), '-enableassertions', \"{main_class}\"]) +java_cmd.extend(extra_program_args) +java_cmd.extend(jar_arguments) +os.execvp(java_cmd[0], java_cmd) +""" + +def main(argv): + argv = build_utils.ExpandFileArgs(argv) + parser = optparse.OptionParser() + parser.add_option('--output', help='Output path for executable script.') + parser.add_option('--main-class', + help='Name of the java class with the "main" entry point.') + parser.add_option('--classpath', action='append', default=[], + help='Classpath for running the jar.') + parser.add_option('--noverify', action='store_true', + help='JVM flag: noverify.') + parser.add_option('--tiered-stop-at-level-one', + action='store_true', + help='JVM flag: -XX:TieredStopAtLevel=1.') + + options, extra_program_args = parser.parse_args(argv) + + extra_flags = [] + if options.noverify: + extra_flags.append('java_cmd.append("-noverify")') + if options.tiered_stop_at_level_one: + extra_flags.append('java_cmd.append("-XX:TieredStopAtLevel=1")') + + classpath = [] + for cp_arg in options.classpath: + classpath += build_utils.ParseGnList(cp_arg) + + run_dir = os.path.dirname(options.output) + classpath = [os.path.relpath(p, run_dir) for p in classpath] + java_path = os.path.relpath( + os.path.join(build_utils.JAVA_HOME, 'bin', 'java'), run_dir) + + with build_utils.AtomicOutput(options.output, mode='w') as script: + script.write( + script_template.format(classpath=('"%s"' % '", "'.join(classpath)), + java_path=repr(java_path), + main_class=options.main_class, + extra_program_args=repr(extra_program_args), + extra_flags='\n'.join(extra_flags))) + + os.chmod(options.output, 0o750) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/create_java_binary_script.pydeps b/third_party/libwebrtc/build/android/gyp/create_java_binary_script.pydeps new file mode 100644 index 0000000000..6bc21fa7e2 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_java_binary_script.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_java_binary_script.pydeps build/android/gyp/create_java_binary_script.py +../../gn_helpers.py +create_java_binary_script.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_r_java.py b/third_party/libwebrtc/build/android/gyp/create_r_java.py new file mode 100755 index 0000000000..97e512d2f8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_r_java.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium 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 dummy R.java file from a list of R.txt files.""" + +import argparse +import sys + +from util import build_utils +from util import resource_utils + + +def _ConcatRTxts(rtxt_in_paths, combined_out_path): + all_lines = set() + for rtxt_in_path in rtxt_in_paths: + with open(rtxt_in_path) as rtxt_in: + all_lines.update(rtxt_in.read().splitlines()) + with open(combined_out_path, 'w') as combined_out: + combined_out.write('\n'.join(sorted(all_lines))) + + +def _CreateRJava(rtxts, package_name, srcjar_out): + with resource_utils.BuildContext() as build: + _ConcatRTxts(rtxts, build.r_txt_path) + rjava_build_options = resource_utils.RJavaBuildOptions() + rjava_build_options.ExportAllResources() + rjava_build_options.ExportAllStyleables() + rjava_build_options.GenerateOnResourcesLoaded(fake=True) + resource_utils.CreateRJavaFiles(build.srcjar_dir, + package_name, + build.r_txt_path, + extra_res_packages=[], + rjava_build_options=rjava_build_options, + srcjar_out=srcjar_out, + ignore_mismatched_values=True) + build_utils.ZipDir(srcjar_out, build.srcjar_dir) + + +def main(args): + parser = argparse.ArgumentParser(description='Create an R.java srcjar.') + build_utils.AddDepfileOption(parser) + parser.add_argument('--srcjar-out', + required=True, + help='Path to output srcjar.') + parser.add_argument('--deps-rtxts', + required=True, + help='List of rtxts of resource dependencies.') + parser.add_argument('--r-package', + required=True, + help='R.java package to use.') + options = parser.parse_args(build_utils.ExpandFileArgs(args)) + options.deps_rtxts = build_utils.ParseGnList(options.deps_rtxts) + + _CreateRJava(options.deps_rtxts, options.r_package, options.srcjar_out) + build_utils.WriteDepfile(options.depfile, + options.srcjar_out, + inputs=options.deps_rtxts) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/create_r_java.pydeps b/third_party/libwebrtc/build/android/gyp/create_r_java.pydeps new file mode 100644 index 0000000000..b259751ced --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_r_java.pydeps @@ -0,0 +1,31 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_r_java.pydeps build/android/gyp/create_r_java.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +create_r_java.py +util/__init__.py +util/build_utils.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_r_txt.py b/third_party/libwebrtc/build/android/gyp/create_r_txt.py new file mode 100755 index 0000000000..2adde5dfb9 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_r_txt.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium 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 dummy R.txt file from a resource zip.""" + +import argparse +import sys + +from util import build_utils +from util import resource_utils +from util import resources_parser + + +def main(args): + parser = argparse.ArgumentParser( + description='Create an R.txt from resources.') + parser.add_argument('--resources-zip-path', + required=True, + help='Path to input resources zip.') + parser.add_argument('--rtxt-path', + required=True, + help='Path to output R.txt file.') + options = parser.parse_args(build_utils.ExpandFileArgs(args)) + with build_utils.TempDir() as temp: + dep_subdirs = resource_utils.ExtractDeps([options.resources_zip_path], temp) + resources_parser.RTxtGenerator(dep_subdirs).WriteRTxtFile(options.rtxt_path) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/create_r_txt.pydeps b/third_party/libwebrtc/build/android/gyp/create_r_txt.pydeps new file mode 100644 index 0000000000..54e5670eb0 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_r_txt.pydeps @@ -0,0 +1,32 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_r_txt.pydeps build/android/gyp/create_r_txt.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +create_r_txt.py +util/__init__.py +util/build_utils.py +util/resource_utils.py +util/resources_parser.py diff --git a/third_party/libwebrtc/build/android/gyp/create_size_info_files.py b/third_party/libwebrtc/build/android/gyp/create_size_info_files.py new file mode 100755 index 0000000000..c60b02d7c8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_size_info_files.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 + +# Copyright 2018 The Chromium 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 size-info/*.info files used by SuperSize.""" + +import argparse +import collections +import os +import re +import sys +import zipfile + +from util import build_utils +from util import jar_info_utils + + +_AAR_VERSION_PATTERN = re.compile(r'/[^/]*?(\.aar/|\.jar/)') + + +def _RemoveDuplicatesFromList(source_list): + return collections.OrderedDict.fromkeys(source_list).keys() + + +def _TransformAarPaths(path): + # .aar files within //third_party/android_deps have a version suffix. + # The suffix changes each time .aar files are updated, which makes size diffs + # hard to compare (since the before/after have different source paths). + # Rather than changing how android_deps works, we employ this work-around + # to normalize the paths. + # From: .../androidx_appcompat_appcompat/appcompat-1.1.0.aar/res/... + # To: .../androidx_appcompat_appcompat.aar/res/... + # https://crbug.com/1056455 + if 'android_deps' not in path: + return path + return _AAR_VERSION_PATTERN.sub(r'\1', path) + + +def _MergeResInfoFiles(res_info_path, info_paths): + # Concatenate them all. + # only_if_changed=False since no build rules depend on this as an input. + with build_utils.AtomicOutput(res_info_path, only_if_changed=False, + mode='w+') as dst: + for p in info_paths: + with open(p) as src: + dst.writelines(_TransformAarPaths(l) for l in src) + + +def _PakInfoPathsForAssets(assets): + return [f.split(':')[0] + '.info' for f in assets if f.endswith('.pak')] + + +def _MergePakInfoFiles(merged_path, pak_infos): + info_lines = set() + for pak_info_path in pak_infos: + with open(pak_info_path, 'r') as src_info_file: + info_lines.update(_TransformAarPaths(x) for x in src_info_file) + # only_if_changed=False since no build rules depend on this as an input. + with build_utils.AtomicOutput(merged_path, only_if_changed=False, + mode='w+') as f: + f.writelines(sorted(info_lines)) + + +def _FullJavaNameFromClassFilePath(path): + # Input: base/android/java/src/org/chromium/Foo.class + # Output: base.android.java.src.org.chromium.Foo + if not path.endswith('.class'): + return '' + path = os.path.splitext(path)[0] + parts = [] + while path: + # Use split to be platform independent. + head, tail = os.path.split(path) + path = head + parts.append(tail) + parts.reverse() # Package comes first + return '.'.join(parts) + + +def _MergeJarInfoFiles(output, inputs): + """Merge several .jar.info files to generate an .apk.jar.info. + + Args: + output: output file path. + inputs: List of .jar.info or .jar files. + """ + info_data = dict() + for path in inputs: + # For non-prebuilts: .jar.info files are written by compile_java.py and map + # .class files to .java source paths. + # + # For prebuilts: No .jar.info file exists, we scan the .jar files here and + # map .class files to the .jar. + # + # For .aar files: We look for a "source.info" file in the containing + # directory in order to map classes back to the .aar (rather than mapping + # them to the extracted .jar file). + if path.endswith('.info'): + info_data.update(jar_info_utils.ParseJarInfoFile(path)) + else: + attributed_path = path + if not path.startswith('..'): + parent_path = os.path.dirname(path) + # See if it's an sub-jar within the .aar. + if os.path.basename(parent_path) == 'libs': + parent_path = os.path.dirname(parent_path) + aar_source_info_path = os.path.join(parent_path, 'source.info') + # source.info files exist only for jars from android_aar_prebuilt(). + # E.g. Could have an java_prebuilt() pointing to a generated .jar. + if os.path.exists(aar_source_info_path): + attributed_path = jar_info_utils.ReadAarSourceInfo( + aar_source_info_path) + + with zipfile.ZipFile(path) as zip_info: + for name in zip_info.namelist(): + fully_qualified_name = _FullJavaNameFromClassFilePath(name) + if fully_qualified_name: + info_data[fully_qualified_name] = _TransformAarPaths('{}/{}'.format( + attributed_path, name)) + + # only_if_changed=False since no build rules depend on this as an input. + with build_utils.AtomicOutput(output, only_if_changed=False) as f: + jar_info_utils.WriteJarInfoFile(f, info_data) + + +def _FindJarInputs(jar_paths): + ret = [] + for jar_path in jar_paths: + jar_info_path = jar_path + '.info' + if os.path.exists(jar_info_path): + ret.append(jar_info_path) + else: + ret.append(jar_path) + return ret + + +def main(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser(description=__doc__) + build_utils.AddDepfileOption(parser) + parser.add_argument( + '--jar-info-path', required=True, help='Output .jar.info file') + parser.add_argument( + '--pak-info-path', required=True, help='Output .pak.info file') + parser.add_argument( + '--res-info-path', required=True, help='Output .res.info file') + parser.add_argument( + '--jar-files', + required=True, + action='append', + help='GN-list of .jar file paths') + parser.add_argument( + '--assets', + required=True, + action='append', + help='GN-list of files to add as assets in the form ' + '"srcPath:zipPath", where ":zipPath" is optional.') + parser.add_argument( + '--uncompressed-assets', + required=True, + action='append', + help='Same as --assets, except disables compression.') + parser.add_argument( + '--in-res-info-path', + required=True, + action='append', + help='Paths to .ap_.info files') + + options = parser.parse_args(args) + + options.jar_files = build_utils.ParseGnList(options.jar_files) + options.assets = build_utils.ParseGnList(options.assets) + options.uncompressed_assets = build_utils.ParseGnList( + options.uncompressed_assets) + + jar_inputs = _FindJarInputs(_RemoveDuplicatesFromList(options.jar_files)) + pak_inputs = _PakInfoPathsForAssets(options.assets + + options.uncompressed_assets) + res_inputs = options.in_res_info_path + + # Just create the info files every time. See https://crbug.com/1045024 + _MergeJarInfoFiles(options.jar_info_path, jar_inputs) + _MergePakInfoFiles(options.pak_info_path, pak_inputs) + _MergeResInfoFiles(options.res_info_path, res_inputs) + + all_inputs = jar_inputs + pak_inputs + res_inputs + build_utils.WriteDepfile(options.depfile, + options.jar_info_path, + inputs=all_inputs) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/create_size_info_files.pydeps b/third_party/libwebrtc/build/android/gyp/create_size_info_files.pydeps new file mode 100644 index 0000000000..1a69c553d7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_size_info_files.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_size_info_files.pydeps build/android/gyp/create_size_info_files.py +../../gn_helpers.py +create_size_info_files.py +util/__init__.py +util/build_utils.py +util/jar_info_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.py b/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.py new file mode 100755 index 0000000000..772dab7709 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generate a zip archive containing localized locale name Android resource +strings! + +This script takes a list of input Chrome-specific locale names, as well as an +output zip file path. + +Each output file will contain the definition of a single string resource, +named 'current_locale', whose value will be the matching Chromium locale name. +E.g. values-en-rUS/strings.xml will define 'current_locale' as 'en-US'. +""" + +import argparse +import os +import sys +import zipfile + +sys.path.insert( + 0, + os.path.join( + os.path.dirname(__file__), '..', '..', '..', 'build', 'android', 'gyp')) + +from util import build_utils +from util import resource_utils + +# A small string template for the content of each strings.xml file. +# NOTE: The name is chosen to avoid any conflicts with other string defined +# by other resource archives. +_TEMPLATE = """\ + + + {resource_text} + +""" + +# The default Chrome locale value. +_DEFAULT_CHROME_LOCALE = 'en-US' + + +def _GenerateLocaleStringsXml(locale): + return _TEMPLATE.format(resource_text=locale) + + +def _AddLocaleResourceFileToZip(out_zip, android_locale, locale): + locale_data = _GenerateLocaleStringsXml(locale) + if android_locale: + zip_path = 'values-%s/strings.xml' % android_locale + else: + zip_path = 'values/strings.xml' + build_utils.AddToZipHermetic( + out_zip, zip_path, data=locale_data, compress=False) + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument( + '--locale-list', + required=True, + help='GN-list of Chrome-specific locale names.') + parser.add_argument( + '--output-zip', required=True, help='Output zip archive path.') + + args = parser.parse_args() + + locale_list = build_utils.ParseGnList(args.locale_list) + if not locale_list: + raise Exception('Locale list cannot be empty!') + + with build_utils.AtomicOutput(args.output_zip) as tmp_file: + with zipfile.ZipFile(tmp_file, 'w') as out_zip: + # First, write the default value, since aapt requires one. + _AddLocaleResourceFileToZip(out_zip, '', _DEFAULT_CHROME_LOCALE) + + for locale in locale_list: + android_locale = resource_utils.ToAndroidLocaleName(locale) + _AddLocaleResourceFileToZip(out_zip, android_locale, locale) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.pydeps b/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.pydeps new file mode 100644 index 0000000000..a147237677 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/create_ui_locale_resources.pydeps @@ -0,0 +1,31 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_ui_locale_resources.pydeps build/android/gyp/create_ui_locale_resources.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +create_ui_locale_resources.py +util/__init__.py +util/build_utils.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/desugar.py b/third_party/libwebrtc/build/android/gyp/desugar.py new file mode 100755 index 0000000000..87eb1590a5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/desugar.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright 2017 The Chromium 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 sys + +from util import build_utils + + +def main(): + args = build_utils.ExpandFileArgs(sys.argv[1:]) + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--desugar-jar', required=True, + help='Path to Desugar.jar.') + parser.add_argument('--input-jar', required=True, + help='Jar input path to include .class files from.') + parser.add_argument('--output-jar', required=True, + help='Jar output path.') + parser.add_argument('--classpath', + action='append', + required=True, + help='Classpath.') + parser.add_argument('--bootclasspath', required=True, + help='Path to javac bootclasspath interface jar.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + options = parser.parse_args(args) + + options.bootclasspath = build_utils.ParseGnList(options.bootclasspath) + options.classpath = build_utils.ParseGnList(options.classpath) + + cmd = build_utils.JavaCmd(options.warnings_as_errors) + [ + '-jar', + options.desugar_jar, + '--input', + options.input_jar, + '--output', + options.output_jar, + '--generate_base_classes_for_default_methods', + # Don't include try-with-resources files in every .jar. Instead, they + # are included via //third_party/bazel/desugar:desugar_runtime_java. + '--desugar_try_with_resources_omit_runtime_classes', + ] + for path in options.bootclasspath: + cmd += ['--bootclasspath_entry', path] + for path in options.classpath: + cmd += ['--classpath_entry', path] + build_utils.CheckOutput( + cmd, + print_stdout=False, + stderr_filter=build_utils.FilterReflectiveAccessJavaWarnings, + fail_on_output=options.warnings_as_errors) + + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.output_jar, + inputs=options.bootclasspath + options.classpath) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/desugar.pydeps b/third_party/libwebrtc/build/android/gyp/desugar.pydeps new file mode 100644 index 0000000000..3e5c9ea231 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/desugar.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/desugar.pydeps build/android/gyp/desugar.py +../../gn_helpers.py +desugar.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/dex.py b/third_party/libwebrtc/build/android/gyp/dex.py new file mode 100755 index 0000000000..79304a6392 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dex.py @@ -0,0 +1,650 @@ +#!/usr/bin/env python3 +# +# 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 argparse +import collections +import logging +import os +import re +import shutil +import sys +import tempfile +import zipfile + +from util import build_utils +from util import md5_check +from util import zipalign + +sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.path.pardir)) + +import convert_dex_profile + + +_DEX_XMX = '2G' # Increase this when __final_dex OOMs. + +_IGNORE_WARNINGS = ( + # Caused by Play Services: + r'Type `libcore.io.Memory` was not found', + # Caused by flogger supporting these as fallbacks. Not needed at runtime. + r'Type `dalvik.system.VMStack` was not found', + r'Type `sun.misc.JavaLangAccess` was not found', + r'Type `sun.misc.SharedSecrets` was not found', + # Caused by jacoco code coverage: + r'Type `java.lang.management.ManagementFactory` was not found', + # TODO(wnwen): Remove this after R8 version 3.0.26-dev: + r'Missing class sun.misc.Unsafe', + # Caused when the test apk and the apk under test do not having native libs. + r'Missing class org.chromium.build.NativeLibraries', + # Caused by internal annotation: https://crbug.com/1180222 + r'Missing class com.google.errorprone.annotations.RestrictedInheritance', + # Caused by internal protobuf package: https://crbug.com/1183971 + r'referenced from: com.google.protobuf.GeneratedMessageLite$GeneratedExtension', # pylint: disable=line-too-long + # Caused by using Bazel desugar instead of D8 for desugar, since Bazel + # desugar doesn't preserve interfaces in the same way. This should be + # removed when D8 is used for desugaring. + r'Warning: Cannot emulate interface ', + # Only relevant for R8 when optimizing an app that doesn't use proto. + r'Ignoring -shrinkunusedprotofields since the protobuf-lite runtime is', +) + + +def _ParseArgs(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + + build_utils.AddDepfileOption(parser) + parser.add_argument('--output', required=True, help='Dex output path.') + parser.add_argument( + '--class-inputs', + action='append', + help='GN-list of .jars with .class files.') + parser.add_argument( + '--class-inputs-filearg', + action='append', + help='GN-list of .jars with .class files (added to depfile).') + parser.add_argument( + '--dex-inputs', action='append', help='GN-list of .jars with .dex files.') + parser.add_argument( + '--dex-inputs-filearg', + action='append', + help='GN-list of .jars with .dex files (added to depfile).') + parser.add_argument( + '--incremental-dir', + help='Path of directory to put intermediate dex files.') + parser.add_argument('--main-dex-rules-path', + action='append', + help='Path to main dex rules for multidex.') + parser.add_argument( + '--multi-dex', + action='store_true', + help='Allow multiple dex files within output.') + parser.add_argument('--library', + action='store_true', + help='Allow numerous dex files within output.') + parser.add_argument('--r8-jar-path', required=True, help='Path to R8 jar.') + parser.add_argument('--skip-custom-d8', + action='store_true', + help='When rebuilding the CustomD8 jar, this may be ' + 'necessary to avoid incompatibility with the new r8 ' + 'jar.') + parser.add_argument('--custom-d8-jar-path', + required=True, + help='Path to our customized d8 jar.') + parser.add_argument('--desugar-dependencies', + help='Path to store desugar dependencies.') + parser.add_argument('--desugar', action='store_true') + parser.add_argument( + '--bootclasspath', + action='append', + help='GN-list of bootclasspath. Needed for --desugar') + parser.add_argument( + '--desugar-jdk-libs-json', help='Path to desugar_jdk_libs.json.') + parser.add_argument('--show-desugar-default-interface-warnings', + action='store_true', + help='Enable desugaring warnings.') + parser.add_argument( + '--classpath', + action='append', + help='GN-list of full classpath. Needed for --desugar') + parser.add_argument( + '--release', + action='store_true', + help='Run D8 in release mode. Release mode maximises main dex and ' + 'deletes non-essential line number information (vs debug which minimizes ' + 'main dex and keeps all line number information, and then some.') + parser.add_argument( + '--min-api', help='Minimum Android API level compatibility.') + parser.add_argument('--force-enable-assertions', + action='store_true', + help='Forcefully enable javac generated assertion code.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + parser.add_argument('--dump-inputs', + action='store_true', + help='Use when filing D8 bugs to capture inputs.' + ' Stores inputs to d8inputs.zip') + + group = parser.add_argument_group('Dexlayout') + group.add_argument( + '--dexlayout-profile', + help=('Text profile for dexlayout. If present, a dexlayout ' + 'pass will happen')) + group.add_argument( + '--profman-path', + help=('Path to ART profman binary. There should be a lib/ directory at ' + 'the same path with shared libraries (shared with dexlayout).')) + group.add_argument( + '--dexlayout-path', + help=('Path to ART dexlayout binary. There should be a lib/ directory at ' + 'the same path with shared libraries (shared with dexlayout).')) + group.add_argument('--dexdump-path', help='Path to dexdump binary.') + group.add_argument( + '--proguard-mapping-path', + help=('Path to proguard map from obfuscated symbols in the jar to ' + 'unobfuscated symbols present in the code. If not present, the jar ' + 'is assumed not to be obfuscated.')) + + options = parser.parse_args(args) + + if options.dexlayout_profile: + build_utils.CheckOptions( + options, + parser, + required=('profman_path', 'dexlayout_path', 'dexdump_path')) + elif options.proguard_mapping_path is not None: + parser.error('Unexpected proguard mapping without dexlayout') + + if options.main_dex_rules_path and not options.multi_dex: + parser.error('--main-dex-rules-path is unused if multidex is not enabled') + + options.class_inputs = build_utils.ParseGnList(options.class_inputs) + options.class_inputs_filearg = build_utils.ParseGnList( + options.class_inputs_filearg) + options.bootclasspath = build_utils.ParseGnList(options.bootclasspath) + options.classpath = build_utils.ParseGnList(options.classpath) + options.dex_inputs = build_utils.ParseGnList(options.dex_inputs) + options.dex_inputs_filearg = build_utils.ParseGnList( + options.dex_inputs_filearg) + + return options + + +def CreateStderrFilter(show_desugar_default_interface_warnings): + def filter_stderr(output): + patterns = list(_IGNORE_WARNINGS) + + # When using Bazel's Desugar tool to desugar lambdas and interface methods, + # we do not provide D8 with a classpath, which causes a lot of warnings from + # D8's default interface desugaring pass. Not having a classpath makes + # incremental dexing much more effective. D8 still does backported method + # desugaring. + # These warnings are also turned off when bytecode checks are turned off. + if not show_desugar_default_interface_warnings: + patterns += ['default or static interface methods'] + + combined_pattern = '|'.join(re.escape(p) for p in patterns) + output = build_utils.FilterLines(output, combined_pattern) + + # Each warning has a prefix line of the file it's from. If we've filtered + # out the warning, then also filter out the file header. + # E.g.: + # Warning in path/to/Foo.class: + # Error message #1 indented here. + # Error message #2 indented here. + output = re.sub(r'^Warning in .*?:\n(?! )', '', output, flags=re.MULTILINE) + return output + + return filter_stderr + + +def _RunD8(dex_cmd, input_paths, output_path, warnings_as_errors, + show_desugar_default_interface_warnings): + dex_cmd = dex_cmd + ['--output', output_path] + input_paths + + stderr_filter = CreateStderrFilter(show_desugar_default_interface_warnings) + + with tempfile.NamedTemporaryFile(mode='w') as flag_file: + # Chosen arbitrarily. Needed to avoid command-line length limits. + MAX_ARGS = 50 + if len(dex_cmd) > MAX_ARGS: + flag_file.write('\n'.join(dex_cmd[MAX_ARGS:])) + flag_file.flush() + dex_cmd = dex_cmd[:MAX_ARGS] + dex_cmd.append('@' + flag_file.name) + + # stdout sometimes spams with things like: + # Stripped invalid locals information from 1 method. + build_utils.CheckOutput(dex_cmd, + stderr_filter=stderr_filter, + fail_on_output=warnings_as_errors) + + +def _EnvWithArtLibPath(binary_path): + """Return an environment dictionary for ART host shared libraries. + + Args: + binary_path: the path to an ART host binary. + + Returns: + An environment dictionary where LD_LIBRARY_PATH has been augmented with the + shared library path for the binary. This assumes that there is a lib/ + directory in the same location as the binary. + """ + lib_path = os.path.join(os.path.dirname(binary_path), 'lib') + env = os.environ.copy() + libraries = [l for l in env.get('LD_LIBRARY_PATH', '').split(':') if l] + libraries.append(lib_path) + env['LD_LIBRARY_PATH'] = ':'.join(libraries) + return env + + +def _CreateBinaryProfile(text_profile, input_dex, profman_path, temp_dir): + """Create a binary profile for dexlayout. + + Args: + text_profile: The ART text profile that will be converted to a binary + profile. + input_dex: The input dex file to layout. + profman_path: Path to the profman binary. + temp_dir: Directory to work in. + + Returns: + The name of the binary profile, which will live in temp_dir. + """ + binary_profile = os.path.join( + temp_dir, 'binary_profile-for-' + os.path.basename(text_profile)) + open(binary_profile, 'w').close() # Touch binary_profile. + profman_cmd = [profman_path, + '--apk=' + input_dex, + '--dex-location=' + input_dex, + '--create-profile-from=' + text_profile, + '--reference-profile-file=' + binary_profile] + build_utils.CheckOutput( + profman_cmd, + env=_EnvWithArtLibPath(profman_path), + stderr_filter=lambda output: + build_utils.FilterLines(output, '|'.join( + [r'Could not find (method_id|proto_id|name):', + r'Could not create type list']))) + return binary_profile + + +def _LayoutDex(binary_profile, input_dex, dexlayout_path, temp_dir): + """Layout a dexfile using a profile. + + Args: + binary_profile: An ART binary profile, eg output from _CreateBinaryProfile. + input_dex: The dex file used to create the binary profile. + dexlayout_path: Path to the dexlayout binary. + temp_dir: Directory to work in. + + Returns: + List of output files produced by dexlayout. This will be one if the input + was a single dexfile, or multiple files if the input was a multidex + zip. These output files are located in temp_dir. + """ + dexlayout_output_dir = os.path.join(temp_dir, 'dexlayout_output') + os.mkdir(dexlayout_output_dir) + dexlayout_cmd = [ dexlayout_path, + '-u', # Update checksum + '-p', binary_profile, + '-w', dexlayout_output_dir, + input_dex ] + build_utils.CheckOutput( + dexlayout_cmd, + env=_EnvWithArtLibPath(dexlayout_path), + stderr_filter=lambda output: + build_utils.FilterLines(output, + r'Can.t mmap dex file.*please zipalign')) + output_files = os.listdir(dexlayout_output_dir) + if not output_files: + raise Exception('dexlayout unexpectedly produced no output') + return sorted([os.path.join(dexlayout_output_dir, f) for f in output_files]) + + +def _ZipMultidex(file_dir, dex_files): + """Zip dex files into a multidex. + + Args: + file_dir: The directory into which to write the output. + dex_files: The dexfiles forming the multizip. Their names must end with + classes.dex, classes2.dex, ... + + Returns: + The name of the multidex file, which will live in file_dir. + """ + ordered_files = [] # List of (archive name, file name) + for f in dex_files: + if f.endswith('dex.jar'): + ordered_files.append(('classes.dex', f)) + break + if not ordered_files: + raise Exception('Could not find classes.dex multidex file in %s', + dex_files) + for dex_idx in range(2, len(dex_files) + 1): + archive_name = 'classes%d.dex' % dex_idx + for f in dex_files: + if f.endswith(archive_name): + ordered_files.append((archive_name, f)) + break + else: + raise Exception('Could not find classes%d.dex multidex file in %s', + dex_files) + if len(set(f[1] for f in ordered_files)) != len(ordered_files): + raise Exception('Unexpected clashing filenames for multidex in %s', + dex_files) + + zip_name = os.path.join(file_dir, 'multidex_classes.zip') + build_utils.DoZip(((archive_name, os.path.join(file_dir, file_name)) + for archive_name, file_name in ordered_files), + zip_name) + return zip_name + + +def _ZipAligned(dex_files, output_path): + """Creates a .dex.jar with 4-byte aligned files. + + Args: + dex_files: List of dex files. + output_path: The output file in which to write the zip. + """ + with zipfile.ZipFile(output_path, 'w') as z: + for i, dex_file in enumerate(dex_files): + name = 'classes{}.dex'.format(i + 1 if i > 0 else '') + zipalign.AddToZipHermetic(z, name, src_path=dex_file, alignment=4) + + +def _PerformDexlayout(tmp_dir, tmp_dex_output, options): + if options.proguard_mapping_path is not None: + matching_profile = os.path.join(tmp_dir, 'obfuscated_profile') + convert_dex_profile.ObfuscateProfile( + options.dexlayout_profile, tmp_dex_output, + options.proguard_mapping_path, options.dexdump_path, matching_profile) + else: + logging.warning('No obfuscation for %s', options.dexlayout_profile) + matching_profile = options.dexlayout_profile + binary_profile = _CreateBinaryProfile(matching_profile, tmp_dex_output, + options.profman_path, tmp_dir) + output_files = _LayoutDex(binary_profile, tmp_dex_output, + options.dexlayout_path, tmp_dir) + if len(output_files) > 1: + return _ZipMultidex(tmp_dir, output_files) + + if zipfile.is_zipfile(output_files[0]): + return output_files[0] + + final_output = os.path.join(tmp_dir, 'dex_classes.zip') + _ZipAligned(output_files, final_output) + return final_output + + +def _CreateFinalDex(d8_inputs, output, tmp_dir, dex_cmd, options=None): + tmp_dex_output = os.path.join(tmp_dir, 'tmp_dex_output.zip') + needs_dexing = not all(f.endswith('.dex') for f in d8_inputs) + needs_dexmerge = output.endswith('.dex') or not (options and options.library) + if needs_dexing or needs_dexmerge: + if options and options.main_dex_rules_path: + for main_dex_rule in options.main_dex_rules_path: + dex_cmd = dex_cmd + ['--main-dex-rules', main_dex_rule] + + tmp_dex_dir = os.path.join(tmp_dir, 'tmp_dex_dir') + os.mkdir(tmp_dex_dir) + + _RunD8(dex_cmd, d8_inputs, tmp_dex_dir, + (not options or options.warnings_as_errors), + (options and options.show_desugar_default_interface_warnings)) + logging.debug('Performed dex merging') + + dex_files = [os.path.join(tmp_dex_dir, f) for f in os.listdir(tmp_dex_dir)] + + if output.endswith('.dex'): + if len(dex_files) > 1: + raise Exception('%d files created, expected 1' % len(dex_files)) + tmp_dex_output = dex_files[0] + else: + _ZipAligned(sorted(dex_files), tmp_dex_output) + else: + # Skip dexmerger. Just put all incrementals into the .jar individually. + _ZipAligned(sorted(d8_inputs), tmp_dex_output) + logging.debug('Quick-zipped %d files', len(d8_inputs)) + + if options and options.dexlayout_profile: + tmp_dex_output = _PerformDexlayout(tmp_dir, tmp_dex_output, options) + + # The dex file is complete and can be moved out of tmp_dir. + shutil.move(tmp_dex_output, output) + + +def _IntermediateDexFilePathsFromInputJars(class_inputs, incremental_dir): + """Returns a list of all intermediate dex file paths.""" + dex_files = [] + for jar in class_inputs: + with zipfile.ZipFile(jar, 'r') as z: + for subpath in z.namelist(): + if subpath.endswith('.class'): + subpath = subpath[:-5] + 'dex' + dex_files.append(os.path.join(incremental_dir, subpath)) + return dex_files + + +def _DeleteStaleIncrementalDexFiles(dex_dir, dex_files): + """Deletes intermediate .dex files that are no longer needed.""" + all_files = build_utils.FindInDirectory(dex_dir) + desired_files = set(dex_files) + for path in all_files: + if path not in desired_files: + os.unlink(path) + + +def _ParseDesugarDeps(desugar_dependencies_file): + dependents_from_dependency = collections.defaultdict(set) + if desugar_dependencies_file and os.path.exists(desugar_dependencies_file): + with open(desugar_dependencies_file, 'r') as f: + for line in f: + dependent, dependency = line.rstrip().split(' -> ') + dependents_from_dependency[dependency].add(dependent) + return dependents_from_dependency + + +def _ComputeRequiredDesugarClasses(changes, desugar_dependencies_file, + class_inputs, classpath): + dependents_from_dependency = _ParseDesugarDeps(desugar_dependencies_file) + required_classes = set() + # Gather classes that need to be re-desugared from changes in the classpath. + for jar in classpath: + for subpath in changes.IterChangedSubpaths(jar): + dependency = '{}:{}'.format(jar, subpath) + required_classes.update(dependents_from_dependency[dependency]) + + for jar in class_inputs: + for subpath in changes.IterChangedSubpaths(jar): + required_classes.update(dependents_from_dependency[subpath]) + + return required_classes + + +def _ExtractClassFiles(changes, tmp_dir, class_inputs, required_classes_set): + classes_list = [] + for jar in class_inputs: + if changes: + changed_class_list = (set(changes.IterChangedSubpaths(jar)) + | required_classes_set) + predicate = lambda x: x in changed_class_list and x.endswith('.class') + else: + predicate = lambda x: x.endswith('.class') + + classes_list.extend( + build_utils.ExtractAll(jar, path=tmp_dir, predicate=predicate)) + return classes_list + + +def _CreateIntermediateDexFiles(changes, options, tmp_dir, dex_cmd): + # Create temporary directory for classes to be extracted to. + tmp_extract_dir = os.path.join(tmp_dir, 'tmp_extract_dir') + os.mkdir(tmp_extract_dir) + + # Do a full rebuild when changes occur in non-input files. + allowed_changed = set(options.class_inputs) + allowed_changed.update(options.dex_inputs) + allowed_changed.update(options.classpath) + strings_changed = changes.HasStringChanges() + non_direct_input_changed = next( + (p for p in changes.IterChangedPaths() if p not in allowed_changed), None) + + if strings_changed or non_direct_input_changed: + logging.debug('Full dex required: strings_changed=%s path_changed=%s', + strings_changed, non_direct_input_changed) + changes = None + + if changes: + required_desugar_classes_set = _ComputeRequiredDesugarClasses( + changes, options.desugar_dependencies, options.class_inputs, + options.classpath) + logging.debug('Class files needing re-desugar: %d', + len(required_desugar_classes_set)) + else: + required_desugar_classes_set = set() + class_files = _ExtractClassFiles(changes, tmp_extract_dir, + options.class_inputs, + required_desugar_classes_set) + logging.debug('Extracted class files: %d', len(class_files)) + + # If the only change is deleting a file, class_files will be empty. + if class_files: + # Dex necessary classes into intermediate dex files. + dex_cmd = dex_cmd + ['--intermediate', '--file-per-class-file'] + if options.desugar_dependencies and not options.skip_custom_d8: + dex_cmd += ['--file-tmp-prefix', tmp_extract_dir] + _RunD8(dex_cmd, class_files, options.incremental_dir, + options.warnings_as_errors, + options.show_desugar_default_interface_warnings) + logging.debug('Dexed class files.') + + +def _OnStaleMd5(changes, options, final_dex_inputs, dex_cmd): + logging.debug('_OnStaleMd5') + with build_utils.TempDir() as tmp_dir: + if options.incremental_dir: + # Create directory for all intermediate dex files. + if not os.path.exists(options.incremental_dir): + os.makedirs(options.incremental_dir) + + _DeleteStaleIncrementalDexFiles(options.incremental_dir, final_dex_inputs) + logging.debug('Stale files deleted') + _CreateIntermediateDexFiles(changes, options, tmp_dir, dex_cmd) + + _CreateFinalDex( + final_dex_inputs, options.output, tmp_dir, dex_cmd, options=options) + + +def MergeDexForIncrementalInstall(r8_jar_path, src_paths, dest_dex_jar, + min_api): + dex_cmd = build_utils.JavaCmd(verify=False, xmx=_DEX_XMX) + [ + '-cp', + r8_jar_path, + 'com.android.tools.r8.D8', + '--min-api', + min_api, + ] + with build_utils.TempDir() as tmp_dir: + _CreateFinalDex(src_paths, dest_dex_jar, tmp_dir, dex_cmd) + + +def main(args): + build_utils.InitLogging('DEX_DEBUG') + options = _ParseArgs(args) + + options.class_inputs += options.class_inputs_filearg + options.dex_inputs += options.dex_inputs_filearg + + input_paths = options.class_inputs + options.dex_inputs + input_paths.append(options.r8_jar_path) + input_paths.append(options.custom_d8_jar_path) + if options.main_dex_rules_path: + input_paths.extend(options.main_dex_rules_path) + + depfile_deps = options.class_inputs_filearg + options.dex_inputs_filearg + + output_paths = [options.output] + + track_subpaths_allowlist = [] + if options.incremental_dir: + final_dex_inputs = _IntermediateDexFilePathsFromInputJars( + options.class_inputs, options.incremental_dir) + output_paths += final_dex_inputs + track_subpaths_allowlist += options.class_inputs + else: + final_dex_inputs = list(options.class_inputs) + final_dex_inputs += options.dex_inputs + + dex_cmd = build_utils.JavaCmd(options.warnings_as_errors, xmx=_DEX_XMX) + + if options.dump_inputs: + dex_cmd += ['-Dcom.android.tools.r8.dumpinputtofile=d8inputs.zip'] + + if not options.skip_custom_d8: + dex_cmd += [ + '-cp', + '{}:{}'.format(options.r8_jar_path, options.custom_d8_jar_path), + 'org.chromium.build.CustomD8', + ] + else: + dex_cmd += [ + '-cp', + options.r8_jar_path, + 'com.android.tools.r8.D8', + ] + + if options.release: + dex_cmd += ['--release'] + if options.min_api: + dex_cmd += ['--min-api', options.min_api] + + if not options.desugar: + dex_cmd += ['--no-desugaring'] + elif options.classpath: + # The classpath is used by D8 to for interface desugaring. + if options.desugar_dependencies and not options.skip_custom_d8: + dex_cmd += ['--desugar-dependencies', options.desugar_dependencies] + if track_subpaths_allowlist: + track_subpaths_allowlist += options.classpath + depfile_deps += options.classpath + input_paths += options.classpath + # Still pass the entire classpath in case a new dependency is needed by + # desugar, so that desugar_dependencies will be updated for the next build. + for path in options.classpath: + dex_cmd += ['--classpath', path] + + if options.classpath or options.main_dex_rules_path: + # --main-dex-rules requires bootclasspath. + dex_cmd += ['--lib', build_utils.JAVA_HOME] + for path in options.bootclasspath: + dex_cmd += ['--lib', path] + depfile_deps += options.bootclasspath + input_paths += options.bootclasspath + + + if options.desugar_jdk_libs_json: + dex_cmd += ['--desugared-lib', options.desugar_jdk_libs_json] + if options.force_enable_assertions: + dex_cmd += ['--force-enable-assertions'] + + # The changes feature from md5_check allows us to only re-dex the class files + # that have changed and the class files that need to be re-desugared by D8. + md5_check.CallAndWriteDepfileIfStale( + lambda changes: _OnStaleMd5(changes, options, final_dex_inputs, dex_cmd), + options, + input_paths=input_paths, + input_strings=dex_cmd + [bool(options.incremental_dir)], + output_paths=output_paths, + pass_changes=True, + track_subpaths_allowlist=track_subpaths_allowlist, + depfile_deps=depfile_deps) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/dex.pydeps b/third_party/libwebrtc/build/android/gyp/dex.pydeps new file mode 100644 index 0000000000..23856f3c84 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dex.pydeps @@ -0,0 +1,10 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dex.pydeps build/android/gyp/dex.py +../../gn_helpers.py +../../print_python_deps.py +../convert_dex_profile.py +dex.py +util/__init__.py +util/build_utils.py +util/md5_check.py +util/zipalign.py diff --git a/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.py b/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.py new file mode 100755 index 0000000000..6304779104 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 The Chromium 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 shutil +import subprocess +import sys +import zipfile + +from util import build_utils + + +def _ParseArgs(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + + parser.add_argument('--output', required=True, help='Dex output path.') + parser.add_argument('--r8-path', required=True, help='Path to R8 jar.') + parser.add_argument( + '--desugar-jdk-libs-json', help='Path to desugar_jdk_libs.json.') + parser.add_argument( + '--desugar-jdk-libs-jar', help='Path to desugar_jdk_libs.jar.') + parser.add_argument('--desugar-jdk-libs-configuration-jar', + help='Path to desugar_jdk_libs_configuration.jar.') + parser.add_argument('--min-api', help='minSdkVersion', required=True) + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + options = parser.parse_args(args) + return options + + +def DexJdkLibJar(r8_path, + min_api, + desugar_jdk_libs_json, + desugar_jdk_libs_jar, + desugar_jdk_libs_configuration_jar, + output, + warnings_as_errors, + config_paths=None): + # TODO(agrieve): Spews a lot of stderr about missing classes. + with build_utils.TempDir() as tmp_dir: + cmd = build_utils.JavaCmd(warnings_as_errors) + [ + '-cp', + r8_path, + 'com.android.tools.r8.L8', + '--min-api', + min_api, + '--lib', + build_utils.JAVA_HOME, + '--desugared-lib', + desugar_jdk_libs_json, + ] + + # If no desugaring is required, no keep rules are generated, and the keep + # file will not be created. + if config_paths is not None: + for path in config_paths: + cmd += ['--pg-conf', path] + + cmd += [ + '--output', tmp_dir, desugar_jdk_libs_jar, + desugar_jdk_libs_configuration_jar + ] + + build_utils.CheckOutput(cmd, + print_stdout=True, + fail_on_output=warnings_as_errors) + if os.path.exists(os.path.join(tmp_dir, 'classes2.dex')): + raise Exception('Achievement unlocked: desugar_jdk_libs is multidex!') + + # classes.dex might not exists if the "desugar_jdk_libs_jar" is not used + # at all. + if os.path.exists(os.path.join(tmp_dir, 'classes.dex')): + shutil.move(os.path.join(tmp_dir, 'classes.dex'), output) + return True + return False + + +def main(args): + options = _ParseArgs(args) + DexJdkLibJar(options.r8_path, options.min_api, options.desugar_jdk_libs_json, + options.desugar_jdk_libs_jar, + options.desugar_jdk_libs_configuration_jar, options.output, + options.warnings_as_errors) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.pydeps b/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.pydeps new file mode 100644 index 0000000000..28d181f528 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dex_jdk_libs.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dex_jdk_libs.pydeps build/android/gyp/dex_jdk_libs.py +../../gn_helpers.py +dex_jdk_libs.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/dexsplitter.py b/third_party/libwebrtc/build/android/gyp/dexsplitter.py new file mode 100755 index 0000000000..80b49c7f8e --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dexsplitter.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 The Chromium 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 shutil +import sys +import zipfile + +from util import build_utils + + +def _ParseOptions(args): + parser = argparse.ArgumentParser() + parser.add_argument('--depfile', help='Path to the depfile to write to.') + parser.add_argument('--stamp', help='Path to stamp to mark when finished.') + parser.add_argument('--r8-path', help='Path to the r8.jar to use.') + parser.add_argument( + '--input-dex-zip', help='Path to dex files in zip being split.') + parser.add_argument( + '--proguard-mapping-file', help='Path to proguard mapping file.') + parser.add_argument( + '--feature-name', + action='append', + dest='feature_names', + help='The name of the feature module.') + parser.add_argument( + '--feature-jars', + action='append', + help='GN list of path to jars which compirse the corresponding feature.') + parser.add_argument( + '--dex-dest', + action='append', + dest='dex_dests', + help='Destination for dex file of the corresponding feature.') + options = parser.parse_args(args) + + assert len(options.feature_names) == len(options.feature_jars) and len( + options.feature_names) == len(options.dex_dests) + options.features = {} + for i, name in enumerate(options.feature_names): + options.features[name] = build_utils.ParseGnList(options.feature_jars[i]) + + return options + + +def _RunDexsplitter(options, output_dir): + cmd = build_utils.JavaCmd() + [ + '-cp', + options.r8_path, + 'com.android.tools.r8.dexsplitter.DexSplitter', + '--output', + output_dir, + '--proguard-map', + options.proguard_mapping_file, + ] + + for base_jar in options.features['base']: + cmd += ['--base-jar', base_jar] + + base_jars_lookup = set(options.features['base']) + for feature in options.features: + if feature == 'base': + continue + for feature_jar in options.features[feature]: + if feature_jar not in base_jars_lookup: + cmd += ['--feature-jar', feature_jar + ':' + feature] + + with build_utils.TempDir() as temp_dir: + unzipped_files = build_utils.ExtractAll(options.input_dex_zip, temp_dir) + for file_name in unzipped_files: + cmd += ['--input', file_name] + build_utils.CheckOutput(cmd) + + +def main(args): + args = build_utils.ExpandFileArgs(args) + options = _ParseOptions(args) + + input_paths = [options.input_dex_zip] + for feature_jars in options.features.values(): + for feature_jar in feature_jars: + input_paths.append(feature_jar) + + with build_utils.TempDir() as dexsplitter_output_dir: + curr_location_to_dest = [] + if len(options.features) == 1: + # Don't run dexsplitter since it needs at least 1 feature module. + curr_location_to_dest.append((options.input_dex_zip, + options.dex_dests[0])) + else: + _RunDexsplitter(options, dexsplitter_output_dir) + + for i, dest in enumerate(options.dex_dests): + module_dex_file = os.path.join(dexsplitter_output_dir, + options.feature_names[i], 'classes.dex') + if os.path.exists(module_dex_file): + curr_location_to_dest.append((module_dex_file, dest)) + else: + module_dex_file += '.jar' + assert os.path.exists( + module_dex_file), 'Dexsplitter tool output not found.' + curr_location_to_dest.append((module_dex_file + '.jar', dest)) + + for curr_location, dest in curr_location_to_dest: + with build_utils.AtomicOutput(dest) as f: + if curr_location.endswith('.jar'): + if dest.endswith('.jar'): + shutil.copy(curr_location, f.name) + else: + with zipfile.ZipFile(curr_location, 'r') as z: + namelist = z.namelist() + assert len(namelist) == 1, ( + 'Unzipping to single dex file, but not single dex file in ' + + options.input_dex_zip) + z.extract(namelist[0], f.name) + else: + if dest.endswith('.jar'): + build_utils.ZipDir( + f.name, os.path.abspath(os.path.join(curr_location, os.pardir))) + else: + shutil.move(curr_location, f.name) + + build_utils.Touch(options.stamp) + build_utils.WriteDepfile(options.depfile, options.stamp, inputs=input_paths) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/dexsplitter.pydeps b/third_party/libwebrtc/build/android/gyp/dexsplitter.pydeps new file mode 100644 index 0000000000..cefc5722d5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dexsplitter.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dexsplitter.pydeps build/android/gyp/dexsplitter.py +../../gn_helpers.py +dexsplitter.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/dist_aar.py b/third_party/libwebrtc/build/android/gyp/dist_aar.py new file mode 100755 index 0000000000..6bf0573f51 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dist_aar.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# +# Copyright 2017 The Chromium 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 Android .aar file.""" + +import argparse +import os +import posixpath +import shutil +import sys +import tempfile +import zipfile + +import filter_zip +from util import build_utils + + +_ANDROID_BUILD_DIR = os.path.dirname(os.path.dirname(__file__)) + + +def _MergeRTxt(r_paths, include_globs): + """Merging the given R.txt files and returns them as a string.""" + all_lines = set() + for r_path in r_paths: + if include_globs and not build_utils.MatchesGlob(r_path, include_globs): + continue + with open(r_path) as f: + all_lines.update(f.readlines()) + return ''.join(sorted(all_lines)) + + +def _MergeProguardConfigs(proguard_configs): + """Merging the given proguard config files and returns them as a string.""" + ret = [] + for config in proguard_configs: + ret.append('# FROM: {}'.format(config)) + with open(config) as f: + ret.append(f.read()) + return '\n'.join(ret) + + +def _AddResources(aar_zip, resource_zips, include_globs): + """Adds all resource zips to the given aar_zip. + + Ensures all res/values/* files have unique names by prefixing them. + """ + for i, path in enumerate(resource_zips): + if include_globs and not build_utils.MatchesGlob(path, include_globs): + continue + with zipfile.ZipFile(path) as res_zip: + for info in res_zip.infolist(): + data = res_zip.read(info) + dirname, basename = posixpath.split(info.filename) + if 'values' in dirname: + root, ext = os.path.splitext(basename) + basename = '{}_{}{}'.format(root, i, ext) + info.filename = posixpath.join(dirname, basename) + info.filename = posixpath.join('res', info.filename) + aar_zip.writestr(info, data) + + +def main(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--output', required=True, help='Path to output aar.') + parser.add_argument('--jars', required=True, help='GN list of jar inputs.') + parser.add_argument('--dependencies-res-zips', required=True, + help='GN list of resource zips') + parser.add_argument('--r-text-files', required=True, + help='GN list of R.txt files to merge') + parser.add_argument('--proguard-configs', required=True, + help='GN list of ProGuard flag files to merge.') + parser.add_argument( + '--android-manifest', + help='Path to AndroidManifest.xml to include.', + default=os.path.join(_ANDROID_BUILD_DIR, 'AndroidManifest.xml')) + parser.add_argument('--native-libraries', default='', + help='GN list of native libraries. If non-empty then ' + 'ABI must be specified.') + parser.add_argument('--abi', + help='ABI (e.g. armeabi-v7a) for native libraries.') + parser.add_argument( + '--jar-excluded-globs', + help='GN-list of globs for paths to exclude in jar.') + parser.add_argument( + '--jar-included-globs', + help='GN-list of globs for paths to include in jar.') + parser.add_argument( + '--resource-included-globs', + help='GN-list of globs for paths to include in R.txt and resources zips.') + + options = parser.parse_args(args) + + if options.native_libraries and not options.abi: + parser.error('You must provide --abi if you have native libs') + + options.jars = build_utils.ParseGnList(options.jars) + options.dependencies_res_zips = build_utils.ParseGnList( + options.dependencies_res_zips) + options.r_text_files = build_utils.ParseGnList(options.r_text_files) + options.proguard_configs = build_utils.ParseGnList(options.proguard_configs) + options.native_libraries = build_utils.ParseGnList(options.native_libraries) + options.jar_excluded_globs = build_utils.ParseGnList( + options.jar_excluded_globs) + options.jar_included_globs = build_utils.ParseGnList( + options.jar_included_globs) + options.resource_included_globs = build_utils.ParseGnList( + options.resource_included_globs) + + with tempfile.NamedTemporaryFile(delete=False) as staging_file: + try: + with zipfile.ZipFile(staging_file.name, 'w') as z: + build_utils.AddToZipHermetic( + z, 'AndroidManifest.xml', src_path=options.android_manifest) + + path_transform = filter_zip.CreatePathTransform( + options.jar_excluded_globs, options.jar_included_globs) + with tempfile.NamedTemporaryFile() as jar_file: + build_utils.MergeZips( + jar_file.name, options.jars, path_transform=path_transform) + build_utils.AddToZipHermetic(z, 'classes.jar', src_path=jar_file.name) + + build_utils.AddToZipHermetic( + z, + 'R.txt', + data=_MergeRTxt(options.r_text_files, + options.resource_included_globs)) + build_utils.AddToZipHermetic(z, 'public.txt', data='') + + if options.proguard_configs: + build_utils.AddToZipHermetic( + z, 'proguard.txt', + data=_MergeProguardConfigs(options.proguard_configs)) + + _AddResources(z, options.dependencies_res_zips, + options.resource_included_globs) + + for native_library in options.native_libraries: + libname = os.path.basename(native_library) + build_utils.AddToZipHermetic( + z, os.path.join('jni', options.abi, libname), + src_path=native_library) + except: + os.unlink(staging_file.name) + raise + shutil.move(staging_file.name, options.output) + + if options.depfile: + all_inputs = (options.jars + options.dependencies_res_zips + + options.r_text_files + options.proguard_configs) + build_utils.WriteDepfile(options.depfile, options.output, all_inputs) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/dist_aar.pydeps b/third_party/libwebrtc/build/android/gyp/dist_aar.pydeps new file mode 100644 index 0000000000..3182580af7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/dist_aar.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dist_aar.pydeps build/android/gyp/dist_aar.py +../../gn_helpers.py +dist_aar.py +filter_zip.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/extract_unwind_tables.py b/third_party/libwebrtc/build/android/gyp/extract_unwind_tables.py new file mode 100755 index 0000000000..65c2db441d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/extract_unwind_tables.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 the unwind tables in from breakpad symbol files + +Runs dump_syms on the given binary file and extracts the CFI data into the +given output file. +The output file is a binary file containing CFI rows ordered based on function +address. The output file only contains rows that match the most popular rule +type in CFI table, to reduce the output size and specify data in compact format. +See doc https://github.com/google/breakpad/blob/master/docs/symbol_files.md. +1. The CFA rules should be of postfix form "SP +". +2. The RA rules should be of postfix form "CFA + ^". +Note: breakpad represents dereferencing address with '^' operator. + +The output file has 2 tables UNW_INDEX and UNW_DATA, inspired from ARM EHABI +format. The first table contains function addresses and an index into the +UNW_DATA table. The second table contains one or more rows for the function +unwind information. + +The output file starts with 4 bytes counting the number of entries in UNW_INDEX. +Then UNW_INDEX table and UNW_DATA table. + +UNW_INDEX contains two columns of N rows each, where N is the number of +functions. + 1. First column 4 byte rows of all the function start address as offset from + start of the binary, in sorted order. + 2. For each function addr, the second column contains 2 byte indices in order. + The indices are offsets (in count of 2 bytes) of the CFI data from start of + UNW_DATA. +The last entry in the table always contains CANT_UNWIND index to specify the +end address of the last function. + +UNW_DATA contains data of all the functions. Each function data contains N rows. +The data found at the address pointed from UNW_INDEX will be: + 2 bytes: N - number of rows that belong to current function. + N * 4 bytes: N rows of data. 16 bits : Address offset from function start. + 14 bits : CFA offset / 4. + 2 bits : RA offset / 4. + +The function is not added to the unwind table in following conditions: +C1. If length of the function code (number of instructions) is greater than + 0xFFFF (2 byte address span). This is because we use 16 bits to refer to + offset of instruction from start of the address. +C2. If the function moves the SP by more than 0xFFFF bytes. This is because we + use 14 bits to denote CFA offset (last 2 bits are 0). +C3. If the Return Address is stored at an offset >= 16 from the CFA. Some + functions which have variable arguments can have offset upto 16. + TODO(ssid): We can actually store offset 16 by subtracting 1 from RA/4 since + we never have 0. +C4: Some functions do not have unwind information defined in dwarf info. These + functions have index value CANT_UNWIND(0xFFFF) in UNW_INDEX table. + + +Usage: + extract_unwind_tables.py --input_path [root path to unstripped chrome.so] + --output_path [output path] --dump_syms_path [path to dump_syms binary] +""" + +import argparse +import re +import struct +import subprocess +import sys +import tempfile + + +_CFA_REG = '.cfa' +_RA_REG = '.ra' + +_ADDR_ENTRY = 0 +_LENGTH_ENTRY = 1 + +_CANT_UNWIND = 0xFFFF + + +def _Write4Bytes(output_file, val): + """Writes a 32 bit unsigned integer to the given output file.""" + output_file.write(struct.pack(' R2: ". + """ + out = [] + found_register = False + for part in cfi_row: + if found_register: + if part[-1] == ':': + break + out.append(part) + elif part == reg + ':': + found_register = True + return ' '.join(out) + + +def _GetCfaAndRaOffset(cfi_row): + """Returns a tuple with 2 numbers (cfa_offset, ra_offset). + + Returns right values if rule matches the predefined criteria. Returns (0, 0) + otherwise. The criteria for CFA rule is postfix form "SP +" and RA rule + is postfix form "CFA - + ^". + """ + cfa_offset = 0 + ra_offset = 0 + cfa_rule = _FindRuleForRegister(cfi_row, _CFA_REG) + ra_rule = _FindRuleForRegister(cfi_row, _RA_REG) + if cfa_rule and re.match(r'sp [0-9]+ \+', cfa_rule): + cfa_offset = int(cfa_rule.split()[1], 10) + if ra_rule: + if not re.match(r'.cfa -[0-9]+ \+ \^', ra_rule): + return (0, 0) + ra_offset = -1 * int(ra_rule.split()[1], 10) + return (cfa_offset, ra_offset) + + +def _GetAllCfiRows(symbol_file): + """Returns parsed CFI data from given symbol_file. + + Each entry in the cfi data dictionary returned is a map from function start + address to array of function rows, starting with FUNCTION type, followed by + one or more CFI rows. + """ + cfi_data = {} + current_func = [] + for line in symbol_file: + line = line.decode('utf8') + if 'STACK CFI' not in line: + continue + + parts = line.split() + data = {} + if parts[2] == 'INIT': + # Add the previous function to the output + if len(current_func) > 1: + cfi_data[current_func[0][_ADDR_ENTRY]] = current_func + current_func = [] + + # The function line is of format "STACK CFI INIT ..." + data[_ADDR_ENTRY] = int(parts[3], 16) + data[_LENGTH_ENTRY] = int(parts[4], 16) + + # Condition C1: Skip if length is large. + if data[_LENGTH_ENTRY] == 0 or data[_LENGTH_ENTRY] > 0xffff: + continue # Skip the current function. + else: + # The current function is skipped. + if len(current_func) == 0: + continue + + # The CFI row is of format "STACK CFI .cfa: .ra: ..." + data[_ADDR_ENTRY] = int(parts[2], 16) + (data[_CFA_REG], data[_RA_REG]) = _GetCfaAndRaOffset(parts) + + # Condition C2 and C3: Skip based on limits on offsets. + if data[_CFA_REG] == 0 or data[_RA_REG] >= 16 or data[_CFA_REG] > 0xffff: + current_func = [] + continue + assert data[_CFA_REG] % 4 == 0 + # Since we skipped functions with code size larger than 0xffff, we should + # have no function offset larger than the same value. + assert data[_ADDR_ENTRY] - current_func[0][_ADDR_ENTRY] < 0xffff + + if data[_ADDR_ENTRY] == 0: + # Skip current function, delete all previous entries. + current_func = [] + continue + assert data[_ADDR_ENTRY] % 2 == 0 + current_func.append(data) + + # Condition C4: Skip function without CFI rows. + if len(current_func) > 1: + cfi_data[current_func[0][_ADDR_ENTRY]] = current_func + return cfi_data + + +def _WriteCfiData(cfi_data, out_file): + """Writes the CFI data in defined format to out_file.""" + # Stores the final data that will be written to UNW_DATA table, in order + # with 2 byte items. + unw_data = [] + + # Represent all the CFI data of functions as set of numbers and map them to an + # index in the |unw_data|. This index is later written to the UNW_INDEX table + # for each function. This map is used to find index of the data for functions. + data_to_index = {} + # Store mapping between the functions to the index. + func_addr_to_index = {} + previous_func_end = 0 + for addr, function in sorted(cfi_data.items()): + # Add an empty function entry when functions CFIs are missing between 2 + # functions. + if previous_func_end != 0 and addr - previous_func_end > 4: + func_addr_to_index[previous_func_end + 2] = _CANT_UNWIND + previous_func_end = addr + cfi_data[addr][0][_LENGTH_ENTRY] + + assert len(function) > 1 + func_data_arr = [] + func_data = 0 + # The first row contains the function address and length. The rest of the + # rows have CFI data. Create function data array as given in the format. + for row in function[1:]: + addr_offset = row[_ADDR_ENTRY] - addr + cfa_offset = (row[_CFA_REG]) | (row[_RA_REG] // 4) + + func_data_arr.append(addr_offset) + func_data_arr.append(cfa_offset) + + # Consider all the rows in the data as one large integer and add it as a key + # to the |data_to_index|. + for data in func_data_arr: + func_data = (func_data << 16) | data + + row_count = len(func_data_arr) // 2 + if func_data not in data_to_index: + # When data is not found, create a new index = len(unw_data), and write + # the data to |unw_data|. + index = len(unw_data) + data_to_index[func_data] = index + unw_data.append(row_count) + for row in func_data_arr: + unw_data.append(row) + else: + # If the data was found, then use the same index for the function. + index = data_to_index[func_data] + assert row_count == unw_data[index] + func_addr_to_index[addr] = data_to_index[func_data] + + # Mark the end end of last function entry. + func_addr_to_index[previous_func_end + 2] = _CANT_UNWIND + + # Write the size of UNW_INDEX file in bytes. + _Write4Bytes(out_file, len(func_addr_to_index)) + + # Write the UNW_INDEX table. First list of addresses and then indices. + sorted_unw_index = sorted(func_addr_to_index.items()) + for addr, index in sorted_unw_index: + _Write4Bytes(out_file, addr) + for addr, index in sorted_unw_index: + _Write2Bytes(out_file, index) + + # Write the UNW_DATA table. + for data in unw_data: + _Write2Bytes(out_file, data) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--input_path', required=True, + help='The input path of the unstripped binary') + parser.add_argument( + '--output_path', required=True, + help='The path of the output file') + parser.add_argument( + '--dump_syms_path', required=True, + help='The path of the dump_syms binary') + + args = parser.parse_args() + cmd = ['./' + args.dump_syms_path, args.input_path, '-v'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + cfi_data = _GetAllCfiRows(proc.stdout) + if proc.wait(): + sys.stderr.write('dump_syms exited with code {} after {} symbols\n'.format( + proc.returncode, len(cfi_data))) + sys.exit(proc.returncode) + with open(args.output_path, 'wb') as out_file: + _WriteCfiData(cfi_data, out_file) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/extract_unwind_tables_tests.py b/third_party/libwebrtc/build/android/gyp/extract_unwind_tables_tests.py new file mode 100755 index 0000000000..59436ff2cd --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/extract_unwind_tables_tests.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 extract_unwind_tables.py + +This test suite contains various tests for extracting CFI tables from breakpad +symbol files. +""" + +import optparse +import os +import struct +import sys +import tempfile +import unittest + +import extract_unwind_tables + +sys.path.append(os.path.join(os.path.dirname(__file__), "gyp")) +from util import build_utils + + +class TestExtractUnwindTables(unittest.TestCase): + def testExtractCfi(self): + with tempfile.NamedTemporaryFile() as output_file: + test_data_lines = """ +MODULE Linux arm CDE12FE1DF2B37A9C6560B4CBEE056420 lib_chrome.so +INFO CODE_ID E12FE1CD2BDFA937C6560B4CBEE05642 +FILE 0 ../../base/allocator/allocator_check.cc +FILE 1 ../../base/allocator/allocator_extension.cc +FILE 2 ../../base/allocator/allocator_shim.cc +FUNC 1adcb60 54 0 i2d_name_canon +1adcb60 1a 509 17054 +3b94c70 2 69 40 +PUBLIC e17001 0 assist_ranker::(anonymous namespace)::FakePredict::Initialize() +PUBLIC e17005 0 (anonymous namespace)::FileDeleter(base::File) +STACK CFI INIT e17000 4 .cfa: sp 0 + .ra: lr +STACK CFI INIT 0 4 .cfa: sp 0 + .ra: lr +STACK CFI 2 .cfa: sp 4 + +STACK CFI 4 .cfa: sp 12 + .ra: .cfa -8 + ^ r7: .cfa -12 + ^ +STACK CFI 6 .cfa: sp 16 + +STACK CFI INIT e1a96e 20 .cfa: sp 0 + .ra: lr +STACK CFI e1a970 .cfa: sp 4 + +STACK CFI e1a972 .cfa: sp 12 + .ra: .cfa -8 + ^ r7: .cfa -12 + ^ +STACK CFI e1a974 .cfa: sp 16 + +STACK CFI INIT e1a1e4 b0 .cfa: sp 0 + .ra: lr +STACK CFI e1a1e6 .cfa: sp 16 + .ra: .cfa -4 + ^ r4: .cfa -16 + ^ r5: .cfa -12 + +STACK CFI e1a1e8 .cfa: sp 80 + +STACK CFI INIT 0 4 .cfa: sp 0 + .ra: lr +STACK CFI INIT 3b92e24 3c .cfa: sp 0 + .ra: lr +STACK CFI 3b92e4c .cfa: sp 16 + .ra: .cfa -12 + ^ +STACK CFI INIT e17004 0 .cfa: sp 0 + .ra: lr +STACK CFI e17004 2 .cfa: sp 0 + .ra: lr +STACK CFI INIT 3b92e70 38 .cfa: sp 0 + .ra: lr +STACK CFI 3b92e74 .cfa: sp 8 + .ra: .cfa -4 + ^ r4: .cfa -8 + ^ +STACK CFI 3b92e90 .cfa: sp 0 + .ra: .ra r4: r4 +STACK CFI INIT 3b93114 6c .cfa: sp 0 + .ra: lr +STACK CFI 3b93118 .cfa: r7 16 + .ra: .cfa -4 + ^ +STACK CFI INIT 3b92114 6c .cfa: sp 0 + .ra: lr +STACK CFI 3b92118 .cfa: r7 16 + .ra: .cfa -20 + ^ +STACK CFI INIT 3b93214 fffff .cfa: sp 0 + .ra: lr +STACK CFI 3b93218 .cfa: r7 16 + .ra: .cfa -4 + ^ +""".splitlines() + extract_unwind_tables._ParseCfiData( + [l.encode('utf8') for l in test_data_lines], output_file.name) + + expected_cfi_data = { + 0xe1a1e4 : [0x2, 0x11, 0x4, 0x50], + 0xe1a296 : [], + 0xe1a96e : [0x2, 0x4, 0x4, 0xe, 0x6, 0x10], + 0xe1a990 : [], + 0x3b92e24: [0x28, 0x13], + 0x3b92e62: [], + } + expected_function_count = len(expected_cfi_data) + + actual_output = [] + with open(output_file.name, 'rb') as f: + while True: + read = f.read(2) + if not read: + break + actual_output.append(struct.unpack('H', read)[0]) + + # First value is size of unw_index table. + unw_index_size = actual_output[1] << 16 | actual_output[0] + # |unw_index_size| should match entry count. + self.assertEqual(expected_function_count, unw_index_size) + # |actual_output| is in blocks of 2 bytes. Skip first 4 bytes representing + # size. + unw_index_start = 2 + unw_index_addr_end = unw_index_start + expected_function_count * 2 + unw_index_end = unw_index_addr_end + expected_function_count + unw_index_addr_col = actual_output[unw_index_start : unw_index_addr_end] + unw_index_index_col = actual_output[unw_index_addr_end : unw_index_end] + + unw_data_start = unw_index_end + unw_data = actual_output[unw_data_start:] + + for func_iter in range(0, expected_function_count): + func_addr = (unw_index_addr_col[func_iter * 2 + 1] << 16 | + unw_index_addr_col[func_iter * 2]) + index = unw_index_index_col[func_iter] + # If index is CANT_UNWIND then invalid function. + if index == 0xFFFF: + self.assertEqual(expected_cfi_data[func_addr], []) + continue + + func_start = index + 1 + func_end = func_start + unw_data[index] * 2 + self.assertEqual(len(expected_cfi_data[func_addr]), + func_end - func_start) + func_cfi = unw_data[func_start : func_end] + self.assertEqual(expected_cfi_data[func_addr], func_cfi) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/filter_zip.py b/third_party/libwebrtc/build/android/gyp/filter_zip.py new file mode 100755 index 0000000000..caa26eb690 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/filter_zip.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 The Chromium 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 shutil +import sys + +from util import build_utils + + +def CreatePathTransform(exclude_globs, include_globs): + """Returns a function to strip paths for the given patterns. + + Args: + exclude_globs: List of globs that if matched should be excluded. + include_globs: List of globs that if not matched should be excluded. + + Returns: + * None if no filters are needed. + * A function "(path) -> path" that returns None when |path| should be + stripped, or |path| otherwise. + """ + if not (exclude_globs or include_globs): + return None + exclude_globs = list(exclude_globs or []) + def path_transform(path): + # Exclude filters take precidence over include filters. + if build_utils.MatchesGlob(path, exclude_globs): + return None + if include_globs and not build_utils.MatchesGlob(path, include_globs): + return None + return path + + return path_transform + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--input', required=True, + help='Input zip file.') + parser.add_argument('--output', required=True, + help='Output zip file') + parser.add_argument('--exclude-globs', + help='GN list of exclude globs') + parser.add_argument('--include-globs', + help='GN list of include globs') + argv = build_utils.ExpandFileArgs(sys.argv[1:]) + args = parser.parse_args(argv) + + args.exclude_globs = build_utils.ParseGnList(args.exclude_globs) + args.include_globs = build_utils.ParseGnList(args.include_globs) + + path_transform = CreatePathTransform(args.exclude_globs, args.include_globs) + with build_utils.AtomicOutput(args.output) as f: + if path_transform: + build_utils.MergeZips(f.name, [args.input], path_transform=path_transform) + else: + shutil.copy(args.input, f.name) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/filter_zip.pydeps b/third_party/libwebrtc/build/android/gyp/filter_zip.pydeps new file mode 100644 index 0000000000..f561e05c45 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/filter_zip.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/filter_zip.pydeps build/android/gyp/filter_zip.py +../../gn_helpers.py +filter_zip.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/finalize_apk.py b/third_party/libwebrtc/build/android/gyp/finalize_apk.py new file mode 100644 index 0000000000..b465f713db --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/finalize_apk.py @@ -0,0 +1,78 @@ +# 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 aligns an APK.""" + +import argparse +import logging +import shutil +import subprocess +import sys +import tempfile + +from util import build_utils + + +def FinalizeApk(apksigner_path, + zipalign_path, + unsigned_apk_path, + final_apk_path, + key_path, + key_passwd, + key_name, + min_sdk_version, + warnings_as_errors=False): + # Use a tempfile so that Ctrl-C does not leave the file with a fresh mtime + # and a corrupted state. + with tempfile.NamedTemporaryFile() as staging_file: + if zipalign_path: + # v2 signing requires that zipalign happen first. + logging.debug('Running zipalign') + zipalign_cmd = [ + zipalign_path, '-p', '-f', '4', unsigned_apk_path, staging_file.name + ] + build_utils.CheckOutput(zipalign_cmd, + print_stdout=True, + fail_on_output=warnings_as_errors) + signer_input_path = staging_file.name + else: + signer_input_path = unsigned_apk_path + + sign_cmd = build_utils.JavaCmd(warnings_as_errors) + [ + '-jar', + apksigner_path, + 'sign', + '--in', + signer_input_path, + '--out', + staging_file.name, + '--ks', + key_path, + '--ks-key-alias', + key_name, + '--ks-pass', + 'pass:' + key_passwd, + ] + # V3 signing adds security niceties, which are irrelevant for local builds. + sign_cmd += ['--v3-signing-enabled', 'false'] + + if min_sdk_version >= 24: + # Disable v1 signatures when v2 signing can be used (it's much faster). + # By default, both v1 and v2 signing happen. + sign_cmd += ['--v1-signing-enabled', 'false'] + else: + # Force SHA-1 (makes signing faster; insecure is fine for local builds). + # Leave v2 signing enabled since it verifies faster on device when + # supported. + sign_cmd += ['--min-sdk-version', '1'] + + logging.debug('Signing apk') + build_utils.CheckOutput(sign_cmd, + print_stdout=True, + fail_on_output=warnings_as_errors) + shutil.move(staging_file.name, final_apk_path) + # TODO(crbug.com/1174969): Remove this once Python2 is obsoleted. + if sys.version_info.major == 2: + staging_file.delete = False + else: + staging_file._closer.delete = False diff --git a/third_party/libwebrtc/build/android/gyp/find.py b/third_party/libwebrtc/build/android/gyp/find.py new file mode 100755 index 0000000000..b05874bfb7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/find.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# 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. +""" + +from __future__ import print_function + +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('%s does not exist' % d, file=sys.stderr) + 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/third_party/libwebrtc/build/android/gyp/gcc_preprocess.py b/third_party/libwebrtc/build/android/gyp/gcc_preprocess.py new file mode 100755 index 0000000000..70ae10fc13 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/gcc_preprocess.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# 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 argparse +import os +import posixpath +import re +import sys +import zipfile + +from util import build_utils + + +def _ParsePackageName(data): + m = re.match(r'^\s*package\s+(.*?)\s*;', data, re.MULTILINE) + return m.group(1) if m else '' + + +def main(args): + args = build_utils.ExpandFileArgs(args) + + parser = argparse.ArgumentParser() + parser.add_argument('--include-dirs', help='GN list of include directories.') + parser.add_argument('--output', help='Path for .srcjar.') + parser.add_argument('--define', + action='append', + dest='defines', + help='List of -D args') + parser.add_argument('templates', nargs='+', help='Template files.') + options = parser.parse_args(args) + + options.defines = build_utils.ParseGnList(options.defines) + options.include_dirs = build_utils.ParseGnList(options.include_dirs) + + gcc_cmd = [ + 'gcc', + '-E', # stop after preprocessing. + '-DANDROID', # Specify ANDROID define for pre-processor. + '-x', + 'c-header', # treat sources as C header files + '-P', # disable line markers, i.e. '#line 309' + ] + gcc_cmd.extend('-D' + x for x in options.defines) + gcc_cmd.extend('-I' + x for x in options.include_dirs) + + with build_utils.AtomicOutput(options.output) as f: + with zipfile.ZipFile(f, 'w') as z: + for template in options.templates: + data = build_utils.CheckOutput(gcc_cmd + [template]) + package_name = _ParsePackageName(data) + if not package_name: + raise Exception('Could not find java package of ' + template) + zip_path = posixpath.join( + package_name.replace('.', '/'), + os.path.splitext(os.path.basename(template))[0]) + '.java' + build_utils.AddToZipHermetic(z, zip_path, data=data) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/gcc_preprocess.pydeps b/third_party/libwebrtc/build/android/gyp/gcc_preprocess.pydeps new file mode 100644 index 0000000000..39e56f7008 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/gcc_preprocess.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/gcc_preprocess.pydeps build/android/gyp/gcc_preprocess.py +../../gn_helpers.py +gcc_preprocess.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/generate_android_wrapper.py b/third_party/libwebrtc/build/android/gyp/generate_android_wrapper.py new file mode 100755 index 0000000000..c8b762c754 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/generate_android_wrapper.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright 2019 The Chromium 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 sys + +from util import build_utils + +sys.path.append( + os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', 'util'))) + +import generate_wrapper + +_WRAPPED_PATH_LIST_RE = re.compile(r'@WrappedPathList\(([^,]+), ([^)]+)\)') + + +def ExpandWrappedPathLists(args): + expanded_args = [] + for arg in args: + m = _WRAPPED_PATH_LIST_RE.match(arg) + if m: + for p in build_utils.ParseGnList(m.group(2)): + expanded_args.extend([m.group(1), '@WrappedPath(%s)' % p]) + else: + expanded_args.append(arg) + return expanded_args + + +def main(raw_args): + parser = generate_wrapper.CreateArgumentParser() + expanded_raw_args = build_utils.ExpandFileArgs(raw_args) + expanded_raw_args = ExpandWrappedPathLists(expanded_raw_args) + args = parser.parse_args(expanded_raw_args) + return generate_wrapper.Wrap(args) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.py b/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.py new file mode 100755 index 0000000000..995fcd7b88 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generate linker version scripts for Chrome on Android shared libraries.""" + +import argparse +import os + +from util import build_utils + +_SCRIPT_HEADER = """\ +# AUTO-GENERATED FILE. DO NOT MODIFY. +# +# See: %s + +{ + global: +""" % os.path.relpath(__file__, build_utils.DIR_SOURCE_ROOT) + +_SCRIPT_FOOTER = """\ + local: + *; +}; +""" + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--output', + required=True, + help='Path to output linker version script file.') + parser.add_argument( + '--export-java-symbols', + action='store_true', + help='Export Java_* JNI methods') + parser.add_argument( + '--export-symbol-allowlist-file', + action='append', + default=[], + dest='allowlists', + help='Path to an input file containing an allowlist of extra symbols to ' + 'export, one symbol per line. Multiple files may be specified.') + parser.add_argument( + '--export-feature-registrations', + action='store_true', + help='Export JNI_OnLoad_* methods') + options = parser.parse_args() + + # JNI_OnLoad is always exported. + # CrashpadHandlerMain() is the entry point to the Crashpad handler, required + # for libcrashpad_handler_trampoline.so. + symbol_list = ['CrashpadHandlerMain', 'JNI_OnLoad'] + + if options.export_java_symbols: + symbol_list.append('Java_*') + + if options.export_feature_registrations: + symbol_list.append('JNI_OnLoad_*') + + for allowlist in options.allowlists: + with open(allowlist, 'rt') as f: + for line in f: + line = line.strip() + if not line or line[0] == '#': + continue + symbol_list.append(line) + + script_content = [_SCRIPT_HEADER] + for symbol in symbol_list: + script_content.append(' %s;\n' % symbol) + script_content.append(_SCRIPT_FOOTER) + + script = ''.join(script_content) + + with build_utils.AtomicOutput(options.output, mode='w') as f: + f.write(script) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.pydeps b/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.pydeps new file mode 100644 index 0000000000..de9fa56a95 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/generate_linker_version_script.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/generate_linker_version_script.pydeps build/android/gyp/generate_linker_version_script.py +../../gn_helpers.py +generate_linker_version_script.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/ijar.py b/third_party/libwebrtc/build/android/gyp/ijar.py new file mode 100755 index 0000000000..45413f62fd --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/ijar.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 The Chromium 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 subprocess +import sys + +from util import build_utils + + +# python -c "import zipfile; zipfile.ZipFile('test.jar', 'w')" +# du -b test.jar +_EMPTY_JAR_SIZE = 22 + + +def main(): + # The point of this wrapper is to use AtomicOutput so that output timestamps + # are not updated when outputs are unchanged. + ijar_bin, in_jar, out_jar = sys.argv[1:] + with build_utils.AtomicOutput(out_jar) as f: + # ijar fails on empty jars: https://github.com/bazelbuild/bazel/issues/10162 + if os.path.getsize(in_jar) <= _EMPTY_JAR_SIZE: + with open(in_jar, 'rb') as in_f: + f.write(in_f.read()) + else: + build_utils.CheckOutput([ijar_bin, in_jar, f.name]) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/ijar.pydeps b/third_party/libwebrtc/build/android/gyp/ijar.pydeps new file mode 100644 index 0000000000..e9ecb6636d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/ijar.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/ijar.pydeps build/android/gyp/ijar.py +../../gn_helpers.py +ijar.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/jacoco_instr.py b/third_party/libwebrtc/build/android/gyp/jacoco_instr.py new file mode 100755 index 0000000000..8e5f29c9cd --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jacoco_instr.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# +# 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 'jacoco_instr' action in the Java build process. +Depending on whether jacoco_instrument is set, the 'jacoco_instr' action will +call the instrument command which accepts a jar and instruments it using +jacococli.jar. + +""" + +from __future__ import print_function + +import argparse +import json +import os +import shutil +import sys +import tempfile +import zipfile + +from util import build_utils + + +def _AddArguments(parser): + """Adds arguments related to instrumentation to parser. + + Args: + parser: ArgumentParser object. + """ + parser.add_argument( + '--input-path', + required=True, + help='Path to input file(s). Either the classes ' + 'directory, or the path to a jar.') + parser.add_argument( + '--output-path', + required=True, + help='Path to output final file(s) to. Either the ' + 'final classes directory, or the directory in ' + 'which to place the instrumented/copied jar.') + parser.add_argument( + '--sources-json-file', + required=True, + help='File to create with the list of source directories ' + 'and input path.') + parser.add_argument( + '--java-sources-file', + required=True, + help='File containing newline-separated .java paths') + parser.add_argument( + '--jacococli-jar', required=True, help='Path to jacococli.jar.') + parser.add_argument( + '--files-to-instrument', + help='Path to a file containing which source files are affected.') + + +def _GetSourceDirsFromSourceFiles(source_files): + """Returns list of directories for the files in |source_files|. + + Args: + source_files: List of source files. + + Returns: + List of source directories. + """ + return list(set(os.path.dirname(source_file) for source_file in source_files)) + + +def _CreateSourcesJsonFile(source_dirs, input_path, sources_json_file, + src_root): + """Adds all normalized source directories and input path to + |sources_json_file|. + + Args: + source_dirs: List of source directories. + input_path: The input path to non-instrumented class files. + sources_json_file: File into which to write the list of source directories + and input path. + src_root: Root which sources added to the file should be relative to. + + Returns: + An exit code. + """ + src_root = os.path.abspath(src_root) + relative_sources = [] + for s in source_dirs: + 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) + + data = {} + data['source_dirs'] = relative_sources + data['input_path'] = [] + if input_path: + data['input_path'].append(os.path.abspath(input_path)) + with open(sources_json_file, 'w') as f: + json.dump(data, f) + + +def _GetAffectedClasses(jar_file, source_files): + """Gets affected classes by affected source files to a jar. + + Args: + jar_file: The jar file to get all members. + source_files: The list of affected source files. + + Returns: + A tuple of affected classes and unaffected members. + """ + with zipfile.ZipFile(jar_file) as f: + members = f.namelist() + + affected_classes = [] + unaffected_members = [] + + for member in members: + if not member.endswith('.class'): + unaffected_members.append(member) + continue + + is_affected = False + index = member.find('$') + if index == -1: + index = member.find('.class') + for source_file in source_files: + if source_file.endswith(member[:index] + '.java'): + affected_classes.append(member) + is_affected = True + break + if not is_affected: + unaffected_members.append(member) + + return affected_classes, unaffected_members + + +def _InstrumentClassFiles(instrument_cmd, + input_path, + output_path, + temp_dir, + affected_source_files=None): + """Instruments class files from input jar. + + Args: + instrument_cmd: JaCoCo instrument command. + input_path: The input path to non-instrumented jar. + output_path: The output path to instrumented jar. + temp_dir: The temporary directory. + affected_source_files: The affected source file paths to input jar. + Default is None, which means instrumenting all class files in jar. + """ + affected_classes = None + unaffected_members = None + if affected_source_files: + affected_classes, unaffected_members = _GetAffectedClasses( + input_path, affected_source_files) + + # Extract affected class files. + with zipfile.ZipFile(input_path) as f: + f.extractall(temp_dir, affected_classes) + + instrumented_dir = os.path.join(temp_dir, 'instrumented') + + # Instrument extracted class files. + instrument_cmd.extend([temp_dir, '--dest', instrumented_dir]) + build_utils.CheckOutput(instrument_cmd) + + if affected_source_files and unaffected_members: + # Extract unaffected members to instrumented_dir. + with zipfile.ZipFile(input_path) as f: + f.extractall(instrumented_dir, unaffected_members) + + # Zip all files to output_path + build_utils.ZipDir(output_path, instrumented_dir) + + +def _RunInstrumentCommand(parser): + """Instruments class or Jar files using JaCoCo. + + Args: + parser: ArgumentParser object. + + Returns: + An exit code. + """ + args = parser.parse_args() + + source_files = [] + if args.java_sources_file: + source_files.extend(build_utils.ReadSourcesList(args.java_sources_file)) + + with build_utils.TempDir() as temp_dir: + instrument_cmd = build_utils.JavaCmd() + [ + '-jar', args.jacococli_jar, 'instrument' + ] + + if not args.files_to_instrument: + _InstrumentClassFiles(instrument_cmd, args.input_path, args.output_path, + temp_dir) + else: + affected_files = build_utils.ReadSourcesList(args.files_to_instrument) + source_set = set(source_files) + affected_source_files = [f for f in affected_files if f in source_set] + + # Copy input_path to output_path and return if no source file affected. + if not affected_source_files: + shutil.copyfile(args.input_path, args.output_path) + # Create a dummy sources_json_file. + _CreateSourcesJsonFile([], None, args.sources_json_file, + build_utils.DIR_SOURCE_ROOT) + return 0 + else: + _InstrumentClassFiles(instrument_cmd, args.input_path, args.output_path, + temp_dir, affected_source_files) + + source_dirs = _GetSourceDirsFromSourceFiles(source_files) + # TODO(GYP): In GN, we are passed the list of sources, detecting source + # directories, then walking them to re-establish the list of sources. + # This can obviously be simplified! + _CreateSourcesJsonFile(source_dirs, args.input_path, args.sources_json_file, + build_utils.DIR_SOURCE_ROOT) + + return 0 + + +def main(): + parser = argparse.ArgumentParser() + _AddArguments(parser) + _RunInstrumentCommand(parser) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/jacoco_instr.pydeps b/third_party/libwebrtc/build/android/gyp/jacoco_instr.pydeps new file mode 100644 index 0000000000..d7fec19fde --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jacoco_instr.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/jacoco_instr.pydeps build/android/gyp/jacoco_instr.py +../../gn_helpers.py +jacoco_instr.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_enum.py b/third_party/libwebrtc/build/android/gyp/java_cpp_enum.py new file mode 100755 index 0000000000..08a381a968 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_enum.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python3 +# +# 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 +from datetime import date +import re +import optparse +import os +from string import Template +import sys +import textwrap +import zipfile + +from util import build_utils +from util import java_cpp_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_ALLOWLIST = [ + '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, comments=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.comments = collections.OrderedDict(comments 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 + + def AppendEntryComment(self, key, value): + if key in self.comments: + raise Exception('Multiple definitions of key %s found.' % key) + self.comments[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() + self._NormalizeNames() + + 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_ALLOWLIST: + raise Exception('Fixed type %s for enum %s not in allowlist.' % + (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.items(): + 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: + shout_case = self.original_enum_name + shout_case = re.sub('(?!^)([A-Z]+)', r'_\1', shout_case).upper() + shout_case += '_' + + prefixes = [shout_case, self.original_enum_name, + 'k' + self.original_enum_name] + + for prefix in prefixes: + if all([w.startswith(prefix) for w in self.entries.keys()]): + prefix_to_strip = prefix + break + else: + prefix_to_strip = '' + + def StripEntries(entries): + ret = collections.OrderedDict() + for k, v in entries.items(): + stripped_key = k.replace(prefix_to_strip, '', 1) + if isinstance(v, str): + stripped_value = v.replace(prefix_to_strip, '') + else: + stripped_value = v + ret[stripped_key] = stripped_value + + return ret + + self.entries = StripEntries(self.entries) + self.comments = StripEntries(self.comments) + + def _NormalizeNames(self): + self.entries = _TransformKeys(self.entries, java_cpp_utils.KCamelToShouty) + self.comments = _TransformKeys(self.comments, java_cpp_utils.KCamelToShouty) + + +def _TransformKeys(d, func): + """Normalize keys in |d| and update references to old keys in |d| values.""" + keys_map = {k: func(k) for k in d} + ret = collections.OrderedDict() + for k, v in d.items(): + # Need to transform values as well when the entry value was explicitly set + # (since it could contain references to other enum entry values). + if isinstance(v, str): + # First check if a full replacement is available. This avoids issues when + # one key is a substring of another. + if v in d: + v = keys_map[v] + else: + for old_key, new_key in keys_map.items(): + v = v.replace(old_key, new_key) + ret[keys_map[k]] = v + return ret + + +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*//\s*([^\n]*)') + 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_error_re = re.compile(r'^\s*//\s+GENERATED_JAVA_(\w+)\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*') + enum_single_line_re = re.compile( + r'^\s*(?:\[cpp.*\])?\s*enum.*{(?P.*)}.*$') + + def __init__(self, lines, path=''): + self._lines = lines + self._path = path + self._enum_definitions = [] + self._in_enum = False + self._current_definition = None + self._current_comments = [] + self._generator_directives = DirectiveSet() + self._multi_line_generator_directive = None + self._current_enum_entry = '' + + 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.multi_line_comment_start_re.match(line): + raise Exception('Multi-line comments in enums are not supported in ' + + self._path) + + enum_comment = HeaderParser.single_line_comment_re.match(line) + if enum_comment: + comment = enum_comment.groups()[0] + if comment: + self._current_comments.append(comment) + elif HeaderParser.enum_end_re.match(line): + self._FinalizeCurrentEnumDefinition() + else: + self._AddToCurrentEnumEntry(line) + if ',' in line: + self._ParseCurrentEnumEntry() + + def _ParseSingleLineEnum(self, line): + for entry in line.split(','): + self._AddToCurrentEnumEntry(entry) + self._ParseCurrentEnumEntry() + + self._FinalizeCurrentEnumDefinition() + + def _ParseCurrentEnumEntry(self): + if not self._current_enum_entry: + return + + enum_entry = HeaderParser.enum_line_re.match(self._current_enum_entry) + if not enum_entry: + raise Exception('Unexpected error while attempting to parse %s as enum ' + 'entry.' % self._current_enum_entry) + + enum_key = enum_entry.groups()[0] + enum_value = enum_entry.groups()[2] + self._current_definition.AppendEntry(enum_key, enum_value) + if self._current_comments: + self._current_definition.AppendEntryComment( + enum_key, ' '.join(self._current_comments)) + self._current_comments = [] + self._current_enum_entry = '' + + def _AddToCurrentEnumEntry(self, line): + self._current_enum_entry += ' ' + line.strip() + + def _FinalizeCurrentEnumDefinition(self): + if self._current_enum_entry: + self._ParseCurrentEnumEntry() + self._ApplyGeneratorDirectives() + self._current_definition.Finalize() + self._enum_definitions.append(self._current_definition) + self._current_definition = None + self._in_enum = False + + 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_error = HeaderParser.generator_error_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)) + single_line_enum = HeaderParser.enum_single_line_re.match(line) + + if generator_directive_error: + raise Exception('Malformed directive declaration in ' + self._path + + '. Use () for multi-line directives. E.g.\n' + + '// GENERATED_JAVA_ENUM_PACKAGE: (\n' + + '// foo.package)') + elif 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 or single_line_enum: + 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 + if single_line_enum: + self._ParseSingleLineEnum(single_line_enum.group('enum_entries')) + + +def DoGenerate(source_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: + output_path = java_cpp_utils.GetJavaFilePath(enum_definition.enum_package, + enum_definition.class_name) + output = GenerateOutput(source_path, enum_definition) + yield output_path, output + + +def DoParseHeaderFile(path): + with open(path) as f: + return HeaderParser(f.readlines(), path).ParseDefinitions() + + +def GenerateOutput(source_path, enum_definition): + template = Template(""" +// Copyright ${YEAR} The Chromium 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}; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({ +${INT_DEF} +}) +@Retention(RetentionPolicy.SOURCE) +public @interface ${CLASS_NAME} { +${ENUM_ENTRIES} +} +""") + + enum_template = Template(' int ${NAME} = ${VALUE};') + enum_entries_string = [] + enum_names = [] + for enum_name, enum_value in enum_definition.entries.items(): + values = { + 'NAME': enum_name, + 'VALUE': enum_value, + } + enum_comments = enum_definition.comments.get(enum_name) + if enum_comments: + enum_comments_indent = ' * ' + comments_line_wrapper = textwrap.TextWrapper( + initial_indent=enum_comments_indent, + subsequent_indent=enum_comments_indent, + width=100) + enum_entries_string.append(' /**') + enum_entries_string.append('\n'.join( + comments_line_wrapper.wrap(enum_comments))) + enum_entries_string.append(' */') + enum_entries_string.append(enum_template.substitute(values)) + if enum_name != "NUM_ENTRIES": + enum_names.append(enum_definition.class_name + '.' + enum_name) + enum_entries_string = '\n'.join(enum_entries_string) + + enum_names_indent = ' ' * 4 + wrapper = textwrap.TextWrapper(initial_indent = enum_names_indent, + subsequent_indent = enum_names_indent, + width = 100) + enum_names_string = '\n'.join(wrapper.wrap(', '.join(enum_names))) + + values = { + 'CLASS_NAME': enum_definition.class_name, + 'ENUM_ENTRIES': enum_entries_string, + 'PACKAGE': enum_definition.enum_package, + 'INT_DEF': enum_names_string, + 'SCRIPT_NAME': java_cpp_utils.GetScriptName(), + 'SOURCE_PATH': source_path, + 'YEAR': str(date.today().year) + } + return template.substitute(values) + + +def DoMain(argv): + usage = 'usage: %prog [options] [output_dir] input_file(s)...' + parser = optparse.OptionParser(usage=usage) + + parser.add_option('--srcjar', + help='When specified, a .srcjar at the given path is ' + 'created instead of individual .java files.') + + options, args = parser.parse_args(argv) + + if not args: + parser.error('Need to specify at least one input file') + input_paths = args + + with build_utils.AtomicOutput(options.srcjar) as f: + with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar: + for output_path, data in DoGenerate(input_paths): + build_utils.AddToZipHermetic(srcjar, output_path, data=data) + + +if __name__ == '__main__': + DoMain(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_enum.pydeps b/third_party/libwebrtc/build/android/gyp/java_cpp_enum.pydeps new file mode 100644 index 0000000000..e6aaeb7b1f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_enum.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_enum.pydeps build/android/gyp/java_cpp_enum.py +../../gn_helpers.py +java_cpp_enum.py +util/__init__.py +util/build_utils.py +util/java_cpp_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_enum_tests.py b/third_party/libwebrtc/build/android/gyp/java_cpp_enum_tests.py new file mode 100755 index 0000000000..6d5f150fa0 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_enum_tests.py @@ -0,0 +1,783 @@ +#!/usr/bin/env python3 +# 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 contains various tests for the C++ -> Java enum generator. +""" + +import collections +from datetime import date +import unittest + +import java_cpp_enum +from java_cpp_enum import EnumDefinition, GenerateOutput +from java_cpp_enum import HeaderParser +from util import java_cpp_utils + + +class TestPreprocess(unittest.TestCase): + def testOutput(self): + definition = EnumDefinition(original_enum_name='ClassName', + enum_package='some.package', + entries=[('E1', 1), ('E2', '2 << 2')], + comments=[('E2', 'This is a comment.'), + ('E1', 'This is a multiple line ' + 'comment that is really long. ' + 'This is a multiple line ' + 'comment that is really ' + 'really long.')]) + output = GenerateOutput('path/to/file', definition) + expected = """ +// Copyright %d The Chromium 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; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({ + ClassName.E1, ClassName.E2 +}) +@Retention(RetentionPolicy.SOURCE) +public @interface ClassName { + /** + * %s + * really really long. + */ + int E1 = 1; + /** + * This is a comment. + */ + int E2 = 2 << 2; +} +""" + long_comment = ('This is a multiple line comment that is really long. ' + 'This is a multiple line comment that is') + self.assertEqual( + expected % (date.today().year, java_cpp_utils.GetScriptName(), + long_comment), 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, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum EnumName { + ENUM_NAME_ZERO = 1 << 0, + ENUM_NAME_ONE = 1 << 1, + ENUM_NAME_TWO = ENUM_NAME_ZERO | ENUM_NAME_ONE, + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(2, 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) + + definition = definitions[1] + expected_entries = collections.OrderedDict([ + ('ZERO', '1 << 0'), + ('ONE', '1 << 1'), + ('TWO', 'ZERO | ONE')]) + self.assertEqual(expected_entries, definition.entries) + + def testParseMultilineEnumEntry(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: bar.namespace + enum Foo { + VALUE_ZERO = 1 << 0, + VALUE_ONE = + SymbolKey | FnKey | AltGrKey | MetaKey | AltKey | ControlKey, + VALUE_TWO = 1 << 18, + }; + """.split('\n') + expected_entries = collections.OrderedDict([ + ('VALUE_ZERO', '1 << 0'), + ('VALUE_ONE', 'SymbolKey | FnKey | AltGrKey | MetaKey | AltKey | ' + 'ControlKey'), + ('VALUE_TWO', '1 << 18')]) + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(1, len(definitions)) + definition = definitions[0] + self.assertEqual('Foo', definition.class_name) + self.assertEqual('bar.namespace', definition.enum_package) + self.assertEqual(expected_entries, definition.entries) + + def testParseEnumEntryWithTrailingMultilineEntry(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: bar.namespace + enum Foo { + VALUE_ZERO = 1, + VALUE_ONE = + SymbolKey | FnKey | AltGrKey | MetaKey | + AltKey | ControlKey | ShiftKey, + }; + """.split('\n') + expected_entries = collections.OrderedDict([ + ('VALUE_ZERO', '1'), + ('VALUE_ONE', 'SymbolKey | FnKey | AltGrKey | MetaKey | AltKey | ' + 'ControlKey | ShiftKey')]) + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(1, len(definitions)) + definition = definitions[0] + self.assertEqual('Foo', definition.class_name) + self.assertEqual('bar.namespace', definition.enum_package) + self.assertEqual(expected_entries, definition.entries) + + def testParseNoCommaAfterLastEntry(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: bar.namespace + enum Foo { + VALUE_ZERO = 1, + + // This is a multiline + // + // comment with an empty line. + VALUE_ONE = 2 + }; + """.split('\n') + expected_entries = collections.OrderedDict([ + ('VALUE_ZERO', '1'), + ('VALUE_ONE', '2')]) + expected_comments = collections.OrderedDict([ + ('VALUE_ONE', 'This is a multiline comment with an empty line.')]) + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(1, len(definitions)) + definition = definitions[0] + self.assertEqual('Foo', definition.class_name) + self.assertEqual('bar.namespace', definition.enum_package) + self.assertEqual(expected_entries, definition.entries) + self.assertEqual(expected_comments, definition.comments) + + 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 testParsePreservesCommentsWhenPrefixStripping(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, + // This comment spans + // two lines. + 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) + self.assertEqual(collections.OrderedDict([('B', 'Comment there')]), + definition.comments) + definition = definitions[1] + self.assertEqual('EnumTwo', definition.class_name) + self.assertEqual('other.package', definition.enum_package) + self.assertEqual(collections.OrderedDict( + [('B', 'This comment spans two lines.')]), definition.comments) + self.assertEqual(collections.OrderedDict([('A', 0), + ('B', 1)]), + definition.entries) + + def testParseTwoEnums(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum AnEnum { + ENUM_ONE_A = 1, + ENUM_ONE_B = A, + }; + + enum EnumIgnore { + C, D, E + }; + + // GENERATED_JAVA_ENUM_PACKAGE: other.package + enum EnumTwo { + P_A, + P_B + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(2, len(definitions)) + definition = definitions[0] + self.assertEqual('AnEnum', definition.class_name) + self.assertEqual('test.namespace', definition.enum_package) + self.assertEqual(collections.OrderedDict([('ENUM_ONE_A', '1'), + ('ENUM_ONE_B', 'A')]), + definition.entries) + definition = definitions[1] + self.assertEqual('EnumTwo', definition.class_name) + self.assertEqual('other.package', definition.enum_package) + self.assertEqual(collections.OrderedDict([('P_A', 0), + ('P_B', 1)]), + definition.entries) + + def testParseSingleLineEnum(self): + test_data = """ + // 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() + definition = definitions[0] + self.assertEqual('EnumTwo', definition.class_name) + self.assertEqual('other.package', definition.enum_package) + self.assertEqual(collections.OrderedDict([('A', 0), + ('B', 1)]), + definition.entries) + + def testParseWithStrippingAndRelativeReferences(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: other.package + // GENERATED_JAVA_PREFIX_TO_STRIP: P_ + enum EnumTwo { + P_A = 1, + // P_A is old-don't use P_A. + P_B = P_A, + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + definition = definitions[0] + self.assertEqual('EnumTwo', definition.class_name) + self.assertEqual('other.package', definition.enum_package) + self.assertEqual(collections.OrderedDict([('A', '1'), + ('B', 'A')]), + definition.entries) + self.assertEqual(collections.OrderedDict([('B', 'A is old-don\'t use A.')]), + definition.comments) + + def testParseSingleLineAndRegularEnum(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum EnumOne { + ENUM_ONE_A = 1, + // Comment there + ENUM_ONE_B = A, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: other.package + enum EnumTwo { P_A, P_B }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OverrideName + enum EnumName { + ENUM_NAME_FOO + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + definition = definitions[0] + self.assertEqual( + collections.OrderedDict([('A', '1'), ('B', 'A')]), definition.entries) + self.assertEqual(collections.OrderedDict([('B', 'Comment there')]), + definition.comments) + + self.assertEqual(3, len(definitions)) + definition = definitions[1] + self.assertEqual( + collections.OrderedDict([('P_A', 0), ('P_B', 1)]), definition.entries) + + definition = definitions[2] + self.assertEqual(collections.OrderedDict([('FOO', 0)]), definition.entries) + + def testParseWithCamelCaseNames(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum EnumTest { + EnumTestA = 1, + // comment for EnumTestB. + EnumTestB = 2, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + // GENERATED_JAVA_PREFIX_TO_STRIP: Test + enum AnEnum { + TestHTTPOption, + TestHTTPSOption, + }; + + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + definition = definitions[0] + self.assertEqual( + collections.OrderedDict([('A', '1'), ('B', '2')]), + definition.entries) + self.assertEqual( + collections.OrderedDict([('B', 'comment for B.')]), + definition.comments) + + definition = definitions[1] + self.assertEqual( + collections.OrderedDict([('HTTP_OPTION', 0), ('HTTPS_OPTION', 1)]), + definition.entries) + + def testParseWithKCamelCaseNames(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum EnumOne { + kEnumOne = 1, + // comment for kEnumTwo. + kEnumTwo = 2, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OverrideName + enum EnumName { + kEnumNameFoo, + kEnumNameBar + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum EnumName { + kEnumNameFoo, + kEnumBar, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum Keys { + kSymbolKey = 1 << 0, + kAltKey = 1 << 1, + kUpKey = 1 << 2, + kKeyModifiers = kSymbolKey | kAltKey | kUpKey | kKeyModifiers, + }; + + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum Mixed { + kTestVal, + kCodecMPEG2 + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + definition = definitions[0] + self.assertEqual( + collections.OrderedDict([('ENUM_ONE', '1'), ('ENUM_TWO', '2')]), + definition.entries) + self.assertEqual( + collections.OrderedDict([('ENUM_TWO', 'comment for ENUM_TWO.')]), + definition.comments) + + definition = definitions[1] + self.assertEqual( + collections.OrderedDict([('FOO', 0), ('BAR', 1)]), + definition.entries) + + definition = definitions[2] + self.assertEqual( + collections.OrderedDict([('ENUM_NAME_FOO', 0), ('ENUM_BAR', 1)]), + definition.entries) + + definition = definitions[3] + expected_entries = collections.OrderedDict([ + ('SYMBOL_KEY', '1 << 0'), + ('ALT_KEY', '1 << 1'), + ('UP_KEY', '1 << 2'), + ('KEY_MODIFIERS', 'SYMBOL_KEY | ALT_KEY | UP_KEY | KEY_MODIFIERS')]) + self.assertEqual(expected_entries, definition.entries) + + definition = definitions[4] + self.assertEqual( + collections.OrderedDict([('TEST_VAL', 0), ('CODEC_MPEG2', 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 testParseEnumClassOneValueSubstringOfAnother(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: test.namespace + enum class SafeBrowsingStatus { + kChecking = 0, + kEnabled = 1, + kDisabled = 2, + kDisabledByAdmin = 3, + kDisabledByExtension = 4, + kEnabledStandard = 5, + kEnabledEnhanced = 6, + // New enum values must go above here. + kMaxValue = kEnabledEnhanced, + }; + """.split('\n') + definitions = HeaderParser(test_data).ParseDefinitions() + self.assertEqual(1, len(definitions)) + definition = definitions[0] + self.assertEqual('SafeBrowsingStatus', definition.class_name) + self.assertEqual('test.namespace', definition.enum_package) + self.assertEqual( + collections.OrderedDict([ + ('CHECKING', '0'), + ('ENABLED', '1'), + ('DISABLED', '2'), + ('DISABLED_BY_ADMIN', '3'), + ('DISABLED_BY_EXTENSION', '4'), + ('ENABLED_STANDARD', '5'), + ('ENABLED_ENHANCED', '6'), + ('MAX_VALUE', 'ENABLED_ENHANCED'), + ]), definition.entries) + self.assertEqual( + collections.OrderedDict([ + ('MAX_VALUE', 'New enum values must go above here.') + ]), definition.comments) + + 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 testParseMalformedMultiLineDirectiveMissingBrackets(self): + test_data = """ + // GENERATED_JAVA_ENUM_PACKAGE: + // test.namespace + 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'], list(definition.entries.keys())) + + def testGenerateThrowsOnEmptyInput(self): + with self.assertRaises(Exception): + original_do_parse = java_cpp_enum.DoParseHeaderFile + try: + java_cpp_enum.DoParseHeaderFile = lambda _: [] + for _ in java_cpp_enum.DoGenerate(['file']): + pass + finally: + java_cpp_enum.DoParseHeaderFile = original_do_parse + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_features.py b/third_party/libwebrtc/build/android/gyp/java_cpp_features.py new file mode 100755 index 0000000000..8e7c2440d7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_features.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 The Chromium 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 +import zipfile + +from util import build_utils +from util import java_cpp_utils + + +class FeatureParserDelegate(java_cpp_utils.CppConstantParser.Delegate): + # Ex. 'const base::Feature kConstantName{"StringNameOfTheFeature", ...};' + # would parse as: + # ExtractConstantName() -> 'ConstantName' + # ExtractValue() -> '"StringNameOfTheFeature"' + FEATURE_RE = re.compile(r'\s*const (?:base::)?Feature\s+k(\w+)\s*(?:=\s*)?{') + VALUE_RE = re.compile(r'\s*("(?:\"|[^"])*")\s*,') + + def ExtractConstantName(self, line): + match = FeatureParserDelegate.FEATURE_RE.match(line) + return match.group(1) if match else None + + def ExtractValue(self, line): + match = FeatureParserDelegate.VALUE_RE.search(line) + return match.group(1) if match else None + + def CreateJavaConstant(self, name, value, comments): + return java_cpp_utils.JavaString(name, value, comments) + + +def _GenerateOutput(template, source_paths, template_path, features): + description_template = """ + // This following string constants were inserted by + // {SCRIPT_NAME} + // From + // {SOURCE_PATHS} + // Into + // {TEMPLATE_PATH} + +""" + values = { + 'SCRIPT_NAME': java_cpp_utils.GetScriptName(), + 'SOURCE_PATHS': ',\n // '.join(source_paths), + 'TEMPLATE_PATH': template_path, + } + description = description_template.format(**values) + native_features = '\n\n'.join(x.Format() for x in features) + + values = { + 'NATIVE_FEATURES': description + native_features, + } + return template.format(**values) + + +def _ParseFeatureFile(path): + with open(path) as f: + feature_file_parser = java_cpp_utils.CppConstantParser( + FeatureParserDelegate(), f.readlines()) + return feature_file_parser.Parse() + + +def _Generate(source_paths, template_path): + with open(template_path) as f: + lines = f.readlines() + + template = ''.join(lines) + package, class_name = java_cpp_utils.ParseTemplateFile(lines) + output_path = java_cpp_utils.GetJavaFilePath(package, class_name) + + features = [] + for source_path in source_paths: + features.extend(_ParseFeatureFile(source_path)) + + output = _GenerateOutput(template, source_paths, template_path, features) + return output, output_path + + +def _Main(argv): + parser = argparse.ArgumentParser() + + parser.add_argument('--srcjar', + required=True, + help='The path at which to generate the .srcjar file') + + parser.add_argument('--template', + required=True, + help='The template file with which to generate the Java ' + 'class. Must have "{NATIVE_FEATURES}" somewhere in ' + 'the template.') + + parser.add_argument('inputs', + nargs='+', + help='Input file(s)', + metavar='INPUTFILE') + args = parser.parse_args(argv) + + with build_utils.AtomicOutput(args.srcjar) as f: + with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar: + data, path = _Generate(args.inputs, args.template) + build_utils.AddToZipHermetic(srcjar, path, data=data) + + +if __name__ == '__main__': + _Main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_features.pydeps b/third_party/libwebrtc/build/android/gyp/java_cpp_features.pydeps new file mode 100644 index 0000000000..acffae2bb9 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_features.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_features.pydeps build/android/gyp/java_cpp_features.py +../../gn_helpers.py +java_cpp_features.py +util/__init__.py +util/build_utils.py +util/java_cpp_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_features_tests.py b/third_party/libwebrtc/build/android/gyp/java_cpp_features_tests.py new file mode 100755 index 0000000000..5dcdcd8b8c --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_features_tests.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +# Copyright 2020 The Chromium 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 java_cpp_features.py. + +This test suite contains various tests for the C++ -> Java base::Feature +generator. +""" + +import unittest + +import java_cpp_features +from util import java_cpp_utils + + +class _TestFeaturesParser(unittest.TestCase): + def testParseComments(self): + test_data = """ +/** + * This should be ignored as well. + */ + +// Comment followed by a blank line. + +// Comment followed by unrelated code. +int foo() { return 3; } + +// Real comment. +const base::Feature kSomeFeature{"SomeFeature", + base::FEATURE_DISABLED_BY_DEFAULT}; + +// Real comment that spans +// multiple lines. +const base::Feature kSomeOtherFeature{"SomeOtherFeature", + base::FEATURE_ENABLED_BY_DEFAULT}; + +// Comment followed by nothing. +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual(2, len(features)) + self.assertEqual('SOME_FEATURE', features[0].name) + self.assertEqual('"SomeFeature"', features[0].value) + self.assertEqual(1, len(features[0].comments.split('\n'))) + self.assertEqual('SOME_OTHER_FEATURE', features[1].name) + self.assertEqual('"SomeOtherFeature"', features[1].value) + self.assertEqual(2, len(features[1].comments.split('\n'))) + + def testWhitespace(self): + test_data = """ +// 1 line +const base::Feature kShort{"Short", base::FEATURE_DISABLED_BY_DEFAULT}; + +// 2 lines +const base::Feature kTwoLineFeatureA{"TwoLineFeatureA", + base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kTwoLineFeatureB{ + "TwoLineFeatureB", base::FEATURE_DISABLED_BY_DEFAULT}; + +// 3 lines +const base::Feature kFeatureWithAVeryLongNameThatWillHaveToWrap{ + "FeatureWithAVeryLongNameThatWillHaveToWrap", + base::FEATURE_DISABLED_BY_DEFAULT}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual(4, len(features)) + self.assertEqual('SHORT', features[0].name) + self.assertEqual('"Short"', features[0].value) + self.assertEqual('TWO_LINE_FEATURE_A', features[1].name) + self.assertEqual('"TwoLineFeatureA"', features[1].value) + self.assertEqual('TWO_LINE_FEATURE_B', features[2].name) + self.assertEqual('"TwoLineFeatureB"', features[2].value) + self.assertEqual('FEATURE_WITH_A_VERY_LONG_NAME_THAT_WILL_HAVE_TO_WRAP', + features[3].name) + self.assertEqual('"FeatureWithAVeryLongNameThatWillHaveToWrap"', + features[3].value) + + def testCppSyntax(self): + test_data = """ +// Mismatched name +const base::Feature kMismatchedFeature{"MismatchedName", + base::FEATURE_DISABLED_BY_DEFAULT}; + +namespace myfeature { +// In a namespace +const base::Feature kSomeFeature{"SomeFeature", + base::FEATURE_DISABLED_BY_DEFAULT}; +} + +// Defined with equals sign +const base::Feature kFoo = {"Foo", base::FEATURE_DISABLED_BY_DEFAULT}; + +// Build config-specific base::Feature +#if defined(OS_ANDROID) +const base::Feature kAndroidOnlyFeature{"AndroidOnlyFeature", + base::FEATURE_DISABLED_BY_DEFAULT}; +#endif + +// Value depends on build config +const base::Feature kMaybeEnabled{"MaybeEnabled", +#if defined(OS_ANDROID) + base::FEATURE_DISABLED_BY_DEFAULT +#else + base::FEATURE_ENABLED_BY_DEFAULT +#endif +}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual(5, len(features)) + self.assertEqual('MISMATCHED_FEATURE', features[0].name) + self.assertEqual('"MismatchedName"', features[0].value) + self.assertEqual('SOME_FEATURE', features[1].name) + self.assertEqual('"SomeFeature"', features[1].value) + self.assertEqual('FOO', features[2].name) + self.assertEqual('"Foo"', features[2].value) + self.assertEqual('ANDROID_ONLY_FEATURE', features[3].name) + self.assertEqual('"AndroidOnlyFeature"', features[3].value) + self.assertEqual('MAYBE_ENABLED', features[4].name) + self.assertEqual('"MaybeEnabled"', features[4].value) + + def testNotYetSupported(self): + # Negative test for cases we don't yet support, to ensure we don't misparse + # these until we intentionally add proper support. + test_data = """ +// Not currently supported: name depends on C++ directive +const base::Feature kNameDependsOnOs{ +#if defined(OS_ANDROID) + "MaybeName1", +#else + "MaybeName2", +#endif + base::FEATURE_DISABLED_BY_DEFAULT}; + +// Not currently supported: feature named with a constant instead of literal +const base::Feature kNamedAfterConstant{kNamedStringConstant, + base::FEATURE_DISABLED_BY_DEFAULT}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual(0, len(features)) + + def testTreatWebViewLikeOneWord(self): + test_data = """ +const base::Feature kSomeWebViewFeature{"SomeWebViewFeature", + base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kWebViewOtherFeature{"WebViewOtherFeature", + base::FEATURE_ENABLED_BY_DEFAULT}; +const base::Feature kFeatureWithPluralWebViews{ + "FeatureWithPluralWebViews", + base::FEATURE_ENABLED_BY_DEFAULT}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual('SOME_WEBVIEW_FEATURE', features[0].name) + self.assertEqual('"SomeWebViewFeature"', features[0].value) + self.assertEqual('WEBVIEW_OTHER_FEATURE', features[1].name) + self.assertEqual('"WebViewOtherFeature"', features[1].value) + self.assertEqual('FEATURE_WITH_PLURAL_WEBVIEWS', features[2].name) + self.assertEqual('"FeatureWithPluralWebViews"', features[2].value) + + def testSpecialCharacters(self): + test_data = r""" +const base::Feature kFeatureWithEscapes{"Weird\tfeature\"name\n", + base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kFeatureWithEscapes2{ + "Weird\tfeature\"name\n", + base::FEATURE_ENABLED_BY_DEFAULT}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual('FEATURE_WITH_ESCAPES', features[0].name) + self.assertEqual(r'"Weird\tfeature\"name\n"', features[0].value) + self.assertEqual('FEATURE_WITH_ESCAPES2', features[1].name) + self.assertEqual(r'"Weird\tfeature\"name\n"', features[1].value) + + def testNoBaseNamespacePrefix(self): + test_data = """ +const Feature kSomeFeature{"SomeFeature", FEATURE_DISABLED_BY_DEFAULT}; +""".split('\n') + feature_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_features.FeatureParserDelegate(), test_data) + features = feature_file_parser.Parse() + self.assertEqual('SOME_FEATURE', features[0].name) + self.assertEqual('"SomeFeature"', features[0].value) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_strings.py b/third_party/libwebrtc/build/android/gyp/java_cpp_strings.py new file mode 100755 index 0000000000..d713599793 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_strings.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Chromium 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 +import zipfile + +from util import build_utils +from util import java_cpp_utils + + +class StringParserDelegate(java_cpp_utils.CppConstantParser.Delegate): + STRING_RE = re.compile(r'\s*const char k(.*)\[\]\s*=') + VALUE_RE = re.compile(r'\s*("(?:\"|[^"])*")\s*;') + + def ExtractConstantName(self, line): + match = StringParserDelegate.STRING_RE.match(line) + return match.group(1) if match else None + + def ExtractValue(self, line): + match = StringParserDelegate.VALUE_RE.search(line) + return match.group(1) if match else None + + def CreateJavaConstant(self, name, value, comments): + return java_cpp_utils.JavaString(name, value, comments) + + +def _GenerateOutput(template, source_paths, template_path, strings): + description_template = """ + // This following string constants were inserted by + // {SCRIPT_NAME} + // From + // {SOURCE_PATHS} + // Into + // {TEMPLATE_PATH} + +""" + values = { + 'SCRIPT_NAME': java_cpp_utils.GetScriptName(), + 'SOURCE_PATHS': ',\n // '.join(source_paths), + 'TEMPLATE_PATH': template_path, + } + description = description_template.format(**values) + native_strings = '\n\n'.join(x.Format() for x in strings) + + values = { + 'NATIVE_STRINGS': description + native_strings, + } + return template.format(**values) + + +def _ParseStringFile(path): + with open(path) as f: + string_file_parser = java_cpp_utils.CppConstantParser( + StringParserDelegate(), f.readlines()) + return string_file_parser.Parse() + + +def _Generate(source_paths, template_path): + with open(template_path) as f: + lines = f.readlines() + + template = ''.join(lines) + package, class_name = java_cpp_utils.ParseTemplateFile(lines) + output_path = java_cpp_utils.GetJavaFilePath(package, class_name) + strings = [] + for source_path in source_paths: + strings.extend(_ParseStringFile(source_path)) + + output = _GenerateOutput(template, source_paths, template_path, strings) + return output, output_path + + +def _Main(argv): + parser = argparse.ArgumentParser() + + parser.add_argument('--srcjar', + required=True, + help='The path at which to generate the .srcjar file') + + parser.add_argument('--template', + required=True, + help='The template file with which to generate the Java ' + 'class. Must have "{NATIVE_STRINGS}" somewhere in ' + 'the template.') + + parser.add_argument( + 'inputs', nargs='+', help='Input file(s)', metavar='INPUTFILE') + args = parser.parse_args(argv) + + with build_utils.AtomicOutput(args.srcjar) as f: + with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar: + data, path = _Generate(args.inputs, args.template) + build_utils.AddToZipHermetic(srcjar, path, data=data) + + +if __name__ == '__main__': + _Main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_strings.pydeps b/third_party/libwebrtc/build/android/gyp/java_cpp_strings.pydeps new file mode 100644 index 0000000000..0a821f4469 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_strings.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_strings.pydeps build/android/gyp/java_cpp_strings.py +../../gn_helpers.py +java_cpp_strings.py +util/__init__.py +util/build_utils.py +util/java_cpp_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/java_cpp_strings_tests.py b/third_party/libwebrtc/build/android/gyp/java_cpp_strings_tests.py new file mode 100755 index 0000000000..4cb1eeeae7 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_cpp_strings_tests.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 + +# Copyright 2019 The Chromium 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 java_cpp_strings.py. + +This test suite contains various tests for the C++ -> Java string generator. +""" + +import unittest + +import java_cpp_strings +from util import java_cpp_utils + + +class _TestStringsParser(unittest.TestCase): + + def testParseComments(self): + test_data = """ +/** + * This should be ignored as well. + */ + +// Comment followed by a blank line. + +// Comment followed by unrelated code. +int foo() { return 3; } + +// Real comment. +const char kASwitch[] = "a-value"; + +// Real comment that spans +// multiple lines. +const char kAnotherSwitch[] = "another-value"; + +// Comment followed by nothing. +""".split('\n') + string_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_strings.StringParserDelegate(), test_data) + strings = string_file_parser.Parse() + self.assertEqual(2, len(strings)) + self.assertEqual('A_SWITCH', strings[0].name) + self.assertEqual('"a-value"', strings[0].value) + self.assertEqual(1, len(strings[0].comments.split('\n'))) + self.assertEqual('ANOTHER_SWITCH', strings[1].name) + self.assertEqual('"another-value"', strings[1].value) + self.assertEqual(2, len(strings[1].comments.split('\n'))) + + def testStringValues(self): + test_data = r""" +// Single line string constants. +const char kAString[] = "a-value"; +const char kNoComment[] = "no-comment"; + +namespace myfeature { +const char kMyFeatureNoComment[] = "myfeature.no-comment"; +} + +// Single line switch with a big space. +const char kAStringWithSpace[] = "a-value"; + +// Wrapped constant definition. +const char kAStringWithAVeryLongNameThatWillHaveToWrap[] = + "a-string-with-a-very-long-name-that-will-have-to-wrap"; + +// This one has no comment before it. + +const char kAStringWithAVeryLongNameThatWillHaveToWrap2[] = + "a-string-with-a-very-long-name-that-will-have-to-wrap2"; + +const char kStringWithEscapes[] = "tab\tquote\"newline\n"; +const char kStringWithEscapes2[] = + "tab\tquote\"newline\n"; + +const char kEmptyString[] = ""; + +// These are valid C++ but not currently supported by the script. +const char kInvalidLineBreak[] = + + "invalid-line-break"; + +const char kConcatenateMultipleStringLiterals[] = + "first line" + "second line"; +""".split('\n') + string_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_strings.StringParserDelegate(), test_data) + strings = string_file_parser.Parse() + self.assertEqual(9, len(strings)) + self.assertEqual('A_STRING', strings[0].name) + self.assertEqual('"a-value"', strings[0].value) + self.assertEqual('NO_COMMENT', strings[1].name) + self.assertEqual('"no-comment"', strings[1].value) + self.assertEqual('MY_FEATURE_NO_COMMENT', strings[2].name) + self.assertEqual('"myfeature.no-comment"', strings[2].value) + self.assertEqual('A_STRING_WITH_SPACE', strings[3].name) + self.assertEqual('"a-value"', strings[3].value) + self.assertEqual('A_STRING_WITH_A_VERY_LONG_NAME_THAT_WILL_HAVE_TO_WRAP', + strings[4].name) + self.assertEqual('"a-string-with-a-very-long-name-that-will-have-to-wrap"', + strings[4].value) + self.assertEqual('A_STRING_WITH_A_VERY_LONG_NAME_THAT_WILL_HAVE_TO_WRAP2', + strings[5].name) + self.assertEqual('"a-string-with-a-very-long-name-that-will-have-to-wrap2"', + strings[5].value) + self.assertEqual('STRING_WITH_ESCAPES', strings[6].name) + self.assertEqual(r'"tab\tquote\"newline\n"', strings[6].value) + self.assertEqual('STRING_WITH_ESCAPES2', strings[7].name) + self.assertEqual(r'"tab\tquote\"newline\n"', strings[7].value) + self.assertEqual('EMPTY_STRING', strings[8].name) + self.assertEqual('""', strings[8].value) + + def testTreatWebViewLikeOneWord(self): + test_data = """ +const char kSomeWebViewSwitch[] = "some-webview-switch"; +const char kWebViewOtherSwitch[] = "webview-other-switch"; +const char kSwitchWithPluralWebViews[] = "switch-with-plural-webviews"; +""".split('\n') + string_file_parser = java_cpp_utils.CppConstantParser( + java_cpp_strings.StringParserDelegate(), test_data) + strings = string_file_parser.Parse() + self.assertEqual('SOME_WEBVIEW_SWITCH', strings[0].name) + self.assertEqual('"some-webview-switch"', strings[0].value) + self.assertEqual('WEBVIEW_OTHER_SWITCH', strings[1].name) + self.assertEqual('"webview-other-switch"', strings[1].value) + self.assertEqual('SWITCH_WITH_PLURAL_WEBVIEWS', strings[2].name) + self.assertEqual('"switch-with-plural-webviews"', strings[2].value) + + def testTemplateParsing(self): + test_data = """ +// Copyright 2019 The Chromium 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 my.java.package; + +public any sort of class MyClass {{ + +{NATIVE_STRINGS} + +}} +""".split('\n') + package, class_name = java_cpp_utils.ParseTemplateFile(test_data) + self.assertEqual('my.java.package', package) + self.assertEqual('MyClass', class_name) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/java_google_api_keys.py b/third_party/libwebrtc/build/android/gyp/java_google_api_keys.py new file mode 100755 index 0000000000..a58628a78f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_google_api_keys.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# 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 Java file with API keys. + +import argparse +import os +import string +import sys +import zipfile + +from util import build_utils + +sys.path.append( + os.path.abspath(os.path.join(sys.path[0], '../../../google_apis'))) +import google_api_keys + + +PACKAGE = 'org.chromium.chrome' +CLASSNAME = 'GoogleAPIKeys' + + +def GetScriptName(): + return os.path.relpath(__file__, build_utils.DIR_SOURCE_ROOT) + + +def GenerateOutput(constant_definitions): + template = string.Template(""" +// 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 autogenerated by +// ${SCRIPT_NAME} +// From +// ${SOURCE_PATH} + +package ${PACKAGE}; + +public class ${CLASS_NAME} { +${CONSTANT_ENTRIES} +} +""") + + constant_template = string.Template( + ' public static final String ${NAME} = "${VALUE}";') + constant_entries_list = [] + for constant_name, constant_value in constant_definitions.items(): + values = { + 'NAME': constant_name, + 'VALUE': constant_value, + } + constant_entries_list.append(constant_template.substitute(values)) + constant_entries_string = '\n'.join(constant_entries_list) + + values = { + 'CLASS_NAME': CLASSNAME, + 'CONSTANT_ENTRIES': constant_entries_string, + 'PACKAGE': PACKAGE, + 'SCRIPT_NAME': GetScriptName(), + 'SOURCE_PATH': 'google_api_keys/google_api_keys.h', + } + return template.substitute(values) + + +def _DoWriteJavaOutput(output_path, constant_definition): + folder = os.path.dirname(output_path) + if folder and not os.path.exists(folder): + os.makedirs(folder) + with open(output_path, 'w') as out_file: + out_file.write(GenerateOutput(constant_definition)) + + +def _DoWriteJarOutput(output_path, constant_definition): + folder = os.path.dirname(output_path) + if folder and not os.path.exists(folder): + os.makedirs(folder) + with zipfile.ZipFile(output_path, 'w') as srcjar: + path = '%s/%s' % (PACKAGE.replace('.', '/'), CLASSNAME + '.java') + data = GenerateOutput(constant_definition) + build_utils.AddToZipHermetic(srcjar, path, data=data) + + +def _DoMain(argv): + parser = argparse.ArgumentParser() + parser.add_argument("--out", help="Path for java output.") + parser.add_argument("--srcjar", help="Path for srcjar output.") + options = parser.parse_args(argv) + if not options.out and not options.srcjar: + parser.print_help() + sys.exit(-1) + + values = {} + values['GOOGLE_API_KEY'] = google_api_keys.GetAPIKey() + values['GOOGLE_API_KEY_PHYSICAL_WEB_TEST'] = (google_api_keys. + GetAPIKeyPhysicalWebTest()) + values['GOOGLE_CLIENT_ID_MAIN'] = google_api_keys.GetClientID('MAIN') + values['GOOGLE_CLIENT_SECRET_MAIN'] = google_api_keys.GetClientSecret('MAIN') + values['GOOGLE_CLIENT_ID_CLOUD_PRINT'] = google_api_keys.GetClientID( + 'CLOUD_PRINT') + values['GOOGLE_CLIENT_SECRET_CLOUD_PRINT'] = google_api_keys.GetClientSecret( + 'CLOUD_PRINT') + values['GOOGLE_CLIENT_ID_REMOTING'] = google_api_keys.GetClientID('REMOTING') + values['GOOGLE_CLIENT_SECRET_REMOTING'] = google_api_keys.GetClientSecret( + 'REMOTING') + values['GOOGLE_CLIENT_ID_REMOTING_HOST'] = google_api_keys.GetClientID( + 'REMOTING_HOST') + values['GOOGLE_CLIENT_SECRET_REMOTING_HOST'] = (google_api_keys. + GetClientSecret('REMOTING_HOST')) + values['GOOGLE_CLIENT_ID_REMOTING_IDENTITY_API'] = (google_api_keys. + GetClientID('REMOTING_IDENTITY_API')) + + if options.out: + _DoWriteJavaOutput(options.out, values) + if options.srcjar: + _DoWriteJarOutput(options.srcjar, values) + + +if __name__ == '__main__': + _DoMain(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/java_google_api_keys.pydeps b/third_party/libwebrtc/build/android/gyp/java_google_api_keys.pydeps new file mode 100644 index 0000000000..ebb717273f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_google_api_keys.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_google_api_keys.pydeps build/android/gyp/java_google_api_keys.py +../../../google_apis/google_api_keys.py +../../gn_helpers.py +java_google_api_keys.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/java_google_api_keys_tests.py b/third_party/libwebrtc/build/android/gyp/java_google_api_keys_tests.py new file mode 100755 index 0000000000..e00e86cb74 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/java_google_api_keys_tests.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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. + +"""Tests for java_google_api_keys.py. + +This test suite contains various tests for the C++ -> Java Google API Keys +generator. +""" + +import unittest + +import java_google_api_keys + + +class TestJavaGoogleAPIKeys(unittest.TestCase): + def testOutput(self): + definition = {'E1': 'abc', 'E2': 'defgh'} + output = java_google_api_keys.GenerateOutput(definition) + expected = """ +// 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 autogenerated by +// %s +// From +// google_api_keys/google_api_keys.h + +package org.chromium.chrome; + +public class GoogleAPIKeys { + public static final String E1 = "abc"; + public static final String E2 = "defgh"; +} +""" + self.assertEqual(expected % java_google_api_keys.GetScriptName(), output) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/javac_output_processor.py b/third_party/libwebrtc/build/android/gyp/javac_output_processor.py new file mode 100755 index 0000000000..298c12573b --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/javac_output_processor.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Contains helper class for processing javac output.""" + +import os +import pathlib +import re +import sys + +from util import build_utils + +sys.path.insert( + 0, + os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src')) +import colorama +sys.path.insert( + 0, + os.path.join(build_utils.DIR_SOURCE_ROOT, 'tools', 'android', + 'modularization', 'convenience')) +import lookup_dep + + +class JavacOutputProcessor: + def __init__(self, target_name): + self._target_name = target_name + + # Example: ../../ui/android/java/src/org/chromium/ui/base/Clipboard.java:45: + fileline_prefix = ( + r'(?P(?P[-.\w/\\]+.java):(?P[0-9]+):)') + + self._warning_re = re.compile( + fileline_prefix + r'(?P warning: (?P.*))$') + self._error_re = re.compile(fileline_prefix + + r'(?P (?P.*))$') + self._marker_re = re.compile(r'\s*(?P\^)\s*$') + + # Matches output modification performed by _ElaborateLineForUnknownSymbol() + # so that it can be colorized. + # Example: org.chromium.base.Log found in dep //base:base_java. + self._please_add_dep_re = re.compile( + r'(?PPlease add //[\w/:]+ dep to //[\w/:]+.*)$') + + # First element in pair is bool which indicates whether the missing + # class/package is part of the error message. + self._symbol_not_found_re_list = [ + # Example: + # error: package org.chromium.components.url_formatter does not exist + (True, + re.compile(fileline_prefix + + r'( error: package [\w.]+ does not exist)$')), + # Example: error: cannot find symbol + (False, re.compile(fileline_prefix + r'( error: cannot find symbol)$')), + # Example: error: symbol not found org.chromium.url.GURL + (True, + re.compile(fileline_prefix + r'( error: symbol not found [\w.]+)$')), + ] + + # Example: import org.chromium.url.GURL; + self._import_re = re.compile(r'\s*import (?P[\w\.]+);$') + + self._warning_color = [ + 'full_message', colorama.Fore.YELLOW + colorama.Style.DIM + ] + self._error_color = [ + 'full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT + ] + self._marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT] + + self._class_lookup_index = None + + colorama.init() + + def Process(self, lines): + """ Processes javac output. + + - Applies colors to output. + - Suggests GN dep to add for 'unresolved symbol in Java import' errors. + """ + lines = self._ElaborateLinesForUnknownSymbol(iter(lines)) + return (self._ApplyColors(l) for l in lines) + + def _ElaborateLinesForUnknownSymbol(self, lines): + """ Elaborates passed-in javac output for unresolved symbols. + + Looks for unresolved symbols in imports. + Adds: + - Line with GN target which cannot compile. + - Mention of unresolved class if not present in error message. + - Line with suggestion of GN dep to add. + + Args: + lines: Generator with javac input. + Returns: + Generator with processed output. + """ + previous_line = next(lines, None) + line = next(lines, None) + while previous_line != None: + elaborated_lines = self._ElaborateLineForUnknownSymbol( + previous_line, line) + for elaborated_line in elaborated_lines: + yield elaborated_line + + previous_line = line + line = next(lines, None) + + def _ApplyColors(self, line): + """Adds colors to passed-in line and returns processed line.""" + if self._warning_re.match(line): + line = self._Colorize(line, self._warning_re, self._warning_color) + elif self._error_re.match(line): + line = self._Colorize(line, self._error_re, self._error_color) + elif self._please_add_dep_re.match(line): + line = self._Colorize(line, self._please_add_dep_re, self._error_color) + elif self._marker_re.match(line): + line = self._Colorize(line, self._marker_re, self._marker_color) + return line + + def _ElaborateLineForUnknownSymbol(self, line, next_line): + if not next_line: + return [line] + + import_re_match = self._import_re.match(next_line) + if not import_re_match: + return [line] + + symbol_missing = False + has_missing_symbol_in_error_msg = False + for symbol_in_error_msg, regex in self._symbol_not_found_re_list: + if regex.match(line): + symbol_missing = True + has_missing_symbol_in_error_msg = symbol_in_error_msg + break + + if not symbol_missing: + return [line] + + class_to_lookup = import_re_match.group('imported_class') + if self._class_lookup_index == None: + self._class_lookup_index = lookup_dep.ClassLookupIndex(pathlib.Path( + os.getcwd()), + should_build=False) + suggested_deps = self._class_lookup_index.match(class_to_lookup) + + if len(suggested_deps) != 1: + suggested_deps = self._FindFactoryDep(suggested_deps) + if len(suggested_deps) != 1: + return [line] + + suggested_target = suggested_deps[0].target + + target_name = self._RemoveSuffixesIfPresent( + ["__compile_java", "__errorprone", "__header"], self._target_name) + if not has_missing_symbol_in_error_msg: + line = "{} {}".format(line, class_to_lookup) + + return [ + line, + "Please add {} dep to {}. ".format(suggested_target, target_name) + + "File a crbug if this suggestion is incorrect.", + ] + + @staticmethod + def _FindFactoryDep(class_entries): + """Find the android_library_factory() GN target.""" + if len(class_entries) != 2: + return [] + + # android_library_factory() targets set low_classpath_priority=true. + # This logic is correct if GN targets other than android_library_factory() + # set low_classpath_priority=true. low_classpath_priority=true indicates + # that the target is depended on (and overridden) by other targets which + # contain the same class. We want to recommend the leaf target. + if class_entries[0].low_classpath_priority == class_entries[ + 1].low_classpath_priority: + return [] + + if class_entries[0].low_classpath_priority: + return [class_entries[0]] + return [class_entries[1]] + + @staticmethod + def _RemoveSuffixesIfPresent(suffixes, text): + for suffix in suffixes: + if text.endswith(suffix): + return text[:-len(suffix)] + return text + + @staticmethod + 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:]) diff --git a/third_party/libwebrtc/build/android/gyp/jetify_jar.py b/third_party/libwebrtc/build/android/gyp/jetify_jar.py new file mode 100755 index 0000000000..e97ad97d99 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jetify_jar.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Chromium 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 print_function + +import argparse +import os +import subprocess +import sys + +from util import build_utils + + +def _AddArguments(parser): + """Adds arguments related to jetifying to parser. + + Args: + parser: ArgumentParser object. + """ + parser.add_argument( + '--input-path', + required=True, + help='Path to input file(s). Either the classes ' + 'directory, or the path to a jar.') + parser.add_argument( + '--output-path', + required=True, + help='Path to output final file(s) to. Either the ' + 'final classes directory, or the directory in ' + 'which to place the instrumented/copied jar.') + parser.add_argument( + '--jetify-path', required=True, help='Path to jetify bin.') + parser.add_argument( + '--jetify-config-path', required=True, help='Path to jetify config file.') + + +def _RunJetifyCommand(parser): + args = parser.parse_args() + cmd = [ + args.jetify_path, + '-i', + args.input_path, + '-o', + args.output_path, + # Need to suppress a lot of warning output when jar doesn't have + # any references rewritten. + '-l', + 'error' + ] + if args.jetify_config_path: + cmd.extend(['-c', args.jetify_config_path]) + # Must wait for jetify command to complete to prevent race condition. + env = os.environ.copy() + env['JAVA_HOME'] = build_utils.JAVA_HOME + subprocess.check_call(cmd, env=env) + + +def main(): + parser = argparse.ArgumentParser() + _AddArguments(parser) + _RunJetifyCommand(parser) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/jetify_jar.pydeps b/third_party/libwebrtc/build/android/gyp/jetify_jar.pydeps new file mode 100644 index 0000000000..6a1a589a7d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jetify_jar.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/jetify_jar.pydeps build/android/gyp/jetify_jar.py +../../gn_helpers.py +jetify_jar.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/jinja_template.py b/third_party/libwebrtc/build/android/gyp/jinja_template.py new file mode 100755 index 0000000000..d42189ba38 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jinja_template.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# +# 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 argparse +import os +import sys + +from util import build_utils +from util import resource_utils + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +from pylib.constants import host_paths + +# Import jinja2 from third_party/jinja2 +sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party')) +import jinja2 # pylint: disable=F0401 + + +class _RecordingFileSystemLoader(jinja2.FileSystemLoader): + 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 + + +class JinjaProcessor(object): + """Allows easy rendering of jinja templates with input file tracking.""" + def __init__(self, loader_base_dir, variables=None): + self.loader_base_dir = loader_base_dir + self.variables = variables or {} + self.loader = _RecordingFileSystemLoader(loader_base_dir) + self.env = jinja2.Environment(loader=self.loader) + self.env.undefined = jinja2.StrictUndefined + self.env.line_comment_prefix = '##' + self.env.trim_blocks = True + self.env.lstrip_blocks = True + self._template_cache = {} # Map of path -> Template + + def Render(self, input_filename, variables=None): + input_rel_path = os.path.relpath(input_filename, self.loader_base_dir) + template = self._template_cache.get(input_rel_path) + if not template: + template = self.env.get_template(input_rel_path) + self._template_cache[input_rel_path] = template + return template.render(variables or self.variables) + + def GetLoadedTemplates(self): + return list(self.loader.loaded_templates) + + +def _ProcessFile(processor, input_filename, output_filename): + output = processor.Render(input_filename) + + # If |output| is same with the file content, we skip update and + # ninja's restat will avoid rebuilding things that depend on it. + if os.path.isfile(output_filename): + with codecs.open(output_filename, 'r', 'utf-8') as f: + if f.read() == output: + return + + with codecs.open(output_filename, 'w', 'utf-8') as output_file: + output_file.write(output) + + +def _ProcessFiles(processor, input_filenames, inputs_base_dir, outputs_zip): + with build_utils.TempDir() as temp_dir: + path_info = resource_utils.ResourceInfoFile() + 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(processor, input_filename, output_filename) + path_info.AddMapping(relpath, input_filename) + + path_info.Write(outputs_zip + '.info') + build_utils.ZipDir(outputs_zip, temp_dir) + + +def _ParseVariables(variables_arg, error_func): + variables = {} + for v in build_utils.ParseGnList(variables_arg): + if '=' not in v: + error_func('--variables argument must contain "=": ' + v) + name, _, value = v.partition('=') + variables[name] = value + return variables + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--inputs', required=True, + help='GN-list of template files to process.') + parser.add_argument('--includes', default='', + help="GN-list of files that get {% include %}'ed.") + parser.add_argument('--output', help='The output file to generate. Valid ' + 'only if there is a single input.') + parser.add_argument('--outputs-zip', help='A zip file for the processed ' + 'templates. Required if there are multiple inputs.') + parser.add_argument('--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_argument('--loader-base-dir', help='Base path used by the ' + 'template loader. Must be a common ancestor directory of ' + 'the inputs. Defaults to DIR_SOURCE_ROOT.', + default=host_paths.DIR_SOURCE_ROOT) + parser.add_argument('--variables', help='Variables to be made available in ' + 'the template processing environment, as a GYP list ' + '(e.g. --variables "channel=beta mstone=39")', default='') + parser.add_argument('--check-includes', action='store_true', + help='Enable inputs and includes checks.') + options = parser.parse_args() + + inputs = build_utils.ParseGnList(options.inputs) + includes = build_utils.ParseGnList(options.includes) + + 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') + + variables = _ParseVariables(options.variables, parser.error) + processor = JinjaProcessor(options.loader_base_dir, variables=variables) + + if options.output: + _ProcessFile(processor, inputs[0], options.output) + else: + _ProcessFiles(processor, inputs, options.inputs_base_dir, + options.outputs_zip) + + if options.check_includes: + all_inputs = set(processor.GetLoadedTemplates()) + all_inputs.difference_update(inputs) + all_inputs.difference_update(includes) + if all_inputs: + raise Exception('Found files not listed via --includes:\n' + + '\n'.join(sorted(all_inputs))) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/jinja_template.pydeps b/third_party/libwebrtc/build/android/gyp/jinja_template.pydeps new file mode 100644 index 0000000000..98de9329b3 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/jinja_template.pydeps @@ -0,0 +1,43 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/jinja_template.pydeps build/android/gyp/jinja_template.py +../../../third_party/catapult/devil/devil/__init__.py +../../../third_party/catapult/devil/devil/android/__init__.py +../../../third_party/catapult/devil/devil/android/constants/__init__.py +../../../third_party/catapult/devil/devil/android/constants/chrome.py +../../../third_party/catapult/devil/devil/android/sdk/__init__.py +../../../third_party/catapult/devil/devil/android/sdk/keyevent.py +../../../third_party/catapult/devil/devil/android/sdk/version_codes.py +../../../third_party/catapult/devil/devil/constants/__init__.py +../../../third_party/catapult/devil/devil/constants/exit_codes.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +../pylib/__init__.py +../pylib/constants/__init__.py +../pylib/constants/host_paths.py +jinja_template.py +util/__init__.py +util/build_utils.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/lint.py b/third_party/libwebrtc/build/android/gyp/lint.py new file mode 100755 index 0000000000..61763c1624 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/lint.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +# +# 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.""" + +from __future__ import print_function + +import argparse +import functools +import logging +import os +import re +import shutil +import sys +import time +import traceback +from xml.dom import minidom +from xml.etree import ElementTree + +from util import build_utils +from util import manifest_utils +from util import server_utils + +_LINT_MD_URL = 'https://chromium.googlesource.com/chromium/src/+/main/build/android/docs/lint.md' # pylint: disable=line-too-long + +# These checks are not useful for chromium. +_DISABLED_ALWAYS = [ + "AppCompatResource", # Lint does not correctly detect our appcompat lib. + "Assert", # R8 --force-enable-assertions is used to enable java asserts. + "InflateParams", # Null is ok when inflating views for dialogs. + "InlinedApi", # Constants are copied so they are always available. + "LintBaseline", # Don't warn about using baseline.xml files. + "MissingApplicationIcon", # False positive for non-production targets. + "SwitchIntDef", # Many C++ enums are not used at all in java. + "UniqueConstants", # Chromium enums allow aliases. + "UnusedAttribute", # Chromium apks have various minSdkVersion values. + "ObsoleteLintCustomCheck", # We have no control over custom lint checks. +] + +# These checks are not useful for test targets and adds an unnecessary burden +# to suppress them. +_DISABLED_FOR_TESTS = [ + # We should not require test strings.xml files to explicitly add + # translatable=false since they are not translated and not used in + # production. + "MissingTranslation", + # Test strings.xml files often have simple names and are not translatable, + # so it may conflict with a production string and cause this error. + "Untranslatable", + # Test targets often use the same strings target and resources target as the + # production targets but may not use all of them. + "UnusedResources", + # TODO(wnwen): Turn this back on since to crash it would require running on + # a device with all the various minSdkVersions. + # Real NewApi violations crash the app, so the only ones that lint catches + # but tests still succeed are false positives. + "NewApi", + # Tests should be allowed to access these methods/classes. + "VisibleForTests", +] + +_RES_ZIP_DIR = 'RESZIPS' +_SRCJAR_DIR = 'SRCJARS' +_AAR_DIR = 'AARS' + + +def _SrcRelative(path): + """Returns relative path to top-level src dir.""" + return os.path.relpath(path, build_utils.DIR_SOURCE_ROOT) + + +def _GenerateProjectFile(android_manifest, + android_sdk_root, + cache_dir, + sources=None, + classpath=None, + srcjar_sources=None, + resource_sources=None, + custom_lint_jars=None, + custom_annotation_zips=None, + android_sdk_version=None): + project = ElementTree.Element('project') + root = ElementTree.SubElement(project, 'root') + # Run lint from output directory: crbug.com/1115594 + root.set('dir', os.getcwd()) + sdk = ElementTree.SubElement(project, 'sdk') + # Lint requires that the sdk path be an absolute path. + sdk.set('dir', os.path.abspath(android_sdk_root)) + cache = ElementTree.SubElement(project, 'cache') + cache.set('dir', cache_dir) + main_module = ElementTree.SubElement(project, 'module') + main_module.set('name', 'main') + main_module.set('android', 'true') + main_module.set('library', 'false') + if android_sdk_version: + main_module.set('compile_sdk_version', android_sdk_version) + manifest = ElementTree.SubElement(main_module, 'manifest') + manifest.set('file', android_manifest) + if srcjar_sources: + for srcjar_file in srcjar_sources: + src = ElementTree.SubElement(main_module, 'src') + src.set('file', srcjar_file) + if sources: + for source in sources: + src = ElementTree.SubElement(main_module, 'src') + src.set('file', source) + if classpath: + for file_path in classpath: + classpath_element = ElementTree.SubElement(main_module, 'classpath') + classpath_element.set('file', file_path) + if resource_sources: + for resource_file in resource_sources: + resource = ElementTree.SubElement(main_module, 'resource') + resource.set('file', resource_file) + if custom_lint_jars: + for lint_jar in custom_lint_jars: + lint = ElementTree.SubElement(main_module, 'lint-checks') + lint.set('file', lint_jar) + if custom_annotation_zips: + for annotation_zip in custom_annotation_zips: + annotation = ElementTree.SubElement(main_module, 'annotations') + annotation.set('file', annotation_zip) + return project + + +def _RetrieveBackportedMethods(backported_methods_path): + with open(backported_methods_path) as f: + methods = f.read().splitlines() + # Methods look like: + # java/util/Set#of(Ljava/lang/Object;)Ljava/util/Set; + # But error message looks like: + # Call requires API level R (current min is 21): java.util.Set#of [NewApi] + methods = (m.replace('/', '\\.') for m in methods) + methods = (m[:m.index('(')] for m in methods) + return sorted(set(methods)) + + +def _GenerateConfigXmlTree(orig_config_path, backported_methods): + if orig_config_path: + root_node = ElementTree.parse(orig_config_path).getroot() + else: + root_node = ElementTree.fromstring('') + + issue_node = ElementTree.SubElement(root_node, 'issue') + issue_node.attrib['id'] = 'NewApi' + ignore_node = ElementTree.SubElement(issue_node, 'ignore') + ignore_node.attrib['regexp'] = '|'.join(backported_methods) + return root_node + + +def _GenerateAndroidManifest(original_manifest_path, extra_manifest_paths, + min_sdk_version, android_sdk_version): + # Set minSdkVersion in the manifest to the correct value. + doc, manifest, app_node = manifest_utils.ParseManifest(original_manifest_path) + + # TODO(crbug.com/1126301): Should this be done using manifest merging? + # Add anything in the application node of the extra manifests to the main + # manifest to prevent unused resource errors. + for path in extra_manifest_paths: + _, _, extra_app_node = manifest_utils.ParseManifest(path) + for node in extra_app_node: + app_node.append(node) + + if app_node.find( + '{%s}allowBackup' % manifest_utils.ANDROID_NAMESPACE) is None: + # Assume no backup is intended, appeases AllowBackup lint check and keeping + # it working for manifests that do define android:allowBackup. + app_node.set('{%s}allowBackup' % manifest_utils.ANDROID_NAMESPACE, 'false') + + uses_sdk = manifest.find('./uses-sdk') + if uses_sdk is None: + uses_sdk = ElementTree.Element('uses-sdk') + manifest.insert(0, uses_sdk) + uses_sdk.set('{%s}minSdkVersion' % manifest_utils.ANDROID_NAMESPACE, + min_sdk_version) + uses_sdk.set('{%s}targetSdkVersion' % manifest_utils.ANDROID_NAMESPACE, + android_sdk_version) + return doc + + +def _WriteXmlFile(root, path): + logging.info('Writing xml file %s', path) + build_utils.MakeDirectory(os.path.dirname(path)) + with build_utils.AtomicOutput(path) as f: + # Although we can write it just with ElementTree.tostring, using minidom + # makes it a lot easier to read as a human (also on code search). + f.write( + minidom.parseString(ElementTree.tostring( + root, encoding='utf-8')).toprettyxml(indent=' ').encode('utf-8')) + + +def _RunLint(lint_binary_path, + backported_methods_path, + config_path, + manifest_path, + extra_manifest_paths, + sources, + classpath, + cache_dir, + android_sdk_version, + aars, + srcjars, + min_sdk_version, + resource_sources, + resource_zips, + android_sdk_root, + lint_gen_dir, + baseline, + testonly_target=False, + warnings_as_errors=False): + logging.info('Lint starting') + + cmd = [ + lint_binary_path, + '--quiet', # Silences lint's "." progress updates. + '--disable', + ','.join(_DISABLED_ALWAYS), + ] + + if baseline: + cmd.extend(['--baseline', baseline]) + if testonly_target: + cmd.extend(['--disable', ','.join(_DISABLED_FOR_TESTS)]) + + if not manifest_path: + manifest_path = os.path.join(build_utils.DIR_SOURCE_ROOT, 'build', + 'android', 'AndroidManifest.xml') + + logging.info('Generating config.xml') + backported_methods = _RetrieveBackportedMethods(backported_methods_path) + config_xml_node = _GenerateConfigXmlTree(config_path, backported_methods) + generated_config_path = os.path.join(lint_gen_dir, 'config.xml') + _WriteXmlFile(config_xml_node, generated_config_path) + cmd.extend(['--config', generated_config_path]) + + logging.info('Generating Android manifest file') + android_manifest_tree = _GenerateAndroidManifest(manifest_path, + extra_manifest_paths, + min_sdk_version, + android_sdk_version) + # Include the rebased manifest_path in the lint generated path so that it is + # clear in error messages where the original AndroidManifest.xml came from. + lint_android_manifest_path = os.path.join(lint_gen_dir, manifest_path) + _WriteXmlFile(android_manifest_tree.getroot(), lint_android_manifest_path) + + resource_root_dir = os.path.join(lint_gen_dir, _RES_ZIP_DIR) + # These are zip files with generated resources (e. g. strings from GRD). + logging.info('Extracting resource zips') + for resource_zip in resource_zips: + # Use a consistent root and name rather than a temporary file so that + # suppressions can be local to the lint target and the resource target. + resource_dir = os.path.join(resource_root_dir, resource_zip) + shutil.rmtree(resource_dir, True) + os.makedirs(resource_dir) + resource_sources.extend( + build_utils.ExtractAll(resource_zip, path=resource_dir)) + + logging.info('Extracting aars') + aar_root_dir = os.path.join(lint_gen_dir, _AAR_DIR) + custom_lint_jars = [] + custom_annotation_zips = [] + if aars: + for aar in aars: + # androidx custom lint checks require a newer version of lint. Disable + # until we update see https://crbug.com/1225326 + if 'androidx' in aar: + continue + # Use relative source for aar files since they are not generated. + aar_dir = os.path.join(aar_root_dir, + os.path.splitext(_SrcRelative(aar))[0]) + shutil.rmtree(aar_dir, True) + os.makedirs(aar_dir) + aar_files = build_utils.ExtractAll(aar, path=aar_dir) + for f in aar_files: + if f.endswith('lint.jar'): + custom_lint_jars.append(f) + elif f.endswith('annotations.zip'): + custom_annotation_zips.append(f) + + logging.info('Extracting srcjars') + srcjar_root_dir = os.path.join(lint_gen_dir, _SRCJAR_DIR) + srcjar_sources = [] + if srcjars: + for srcjar in srcjars: + # Use path without extensions since otherwise the file name includes + # .srcjar and lint treats it as a srcjar. + srcjar_dir = os.path.join(srcjar_root_dir, os.path.splitext(srcjar)[0]) + shutil.rmtree(srcjar_dir, True) + os.makedirs(srcjar_dir) + # Sadly lint's srcjar support is broken since it only considers the first + # srcjar. Until we roll a lint version with that fixed, we need to extract + # it ourselves. + srcjar_sources.extend(build_utils.ExtractAll(srcjar, path=srcjar_dir)) + + logging.info('Generating project file') + project_file_root = _GenerateProjectFile(lint_android_manifest_path, + android_sdk_root, cache_dir, sources, + classpath, srcjar_sources, + resource_sources, custom_lint_jars, + custom_annotation_zips, + android_sdk_version) + + project_xml_path = os.path.join(lint_gen_dir, 'project.xml') + _WriteXmlFile(project_file_root, project_xml_path) + cmd += ['--project', project_xml_path] + + logging.info('Preparing environment variables') + env = os.environ.copy() + # It is important that lint uses the checked-in JDK11 as it is almost 50% + # faster than JDK8. + env['JAVA_HOME'] = build_utils.JAVA_HOME + # This is necessary so that lint errors print stack traces in stdout. + env['LINT_PRINT_STACKTRACE'] = 'true' + if baseline and not os.path.exists(baseline): + # Generating new baselines is only done locally, and requires more memory to + # avoid OOMs. + env['LINT_OPTS'] = '-Xmx4g' + else: + # The default set in the wrapper script is 1g, but it seems not enough :( + env['LINT_OPTS'] = '-Xmx2g' + + # This filter is necessary for JDK11. + stderr_filter = build_utils.FilterReflectiveAccessJavaWarnings + stdout_filter = lambda x: build_utils.FilterLines(x, 'No issues found') + + start = time.time() + logging.debug('Lint command %s', ' '.join(cmd)) + failed = True + try: + failed = bool( + build_utils.CheckOutput(cmd, + env=env, + print_stdout=True, + stdout_filter=stdout_filter, + stderr_filter=stderr_filter, + fail_on_output=warnings_as_errors)) + finally: + # When not treating warnings as errors, display the extra footer. + is_debug = os.environ.get('LINT_DEBUG', '0') != '0' + + if failed: + print('- For more help with lint in Chrome:', _LINT_MD_URL) + if is_debug: + print('- DEBUG MODE: Here is the project.xml: {}'.format( + _SrcRelative(project_xml_path))) + else: + print('- Run with LINT_DEBUG=1 to enable lint configuration debugging') + + end = time.time() - start + logging.info('Lint command took %ss', end) + if not is_debug: + shutil.rmtree(aar_root_dir, ignore_errors=True) + shutil.rmtree(resource_root_dir, ignore_errors=True) + shutil.rmtree(srcjar_root_dir, ignore_errors=True) + os.unlink(project_xml_path) + + logging.info('Lint completed') + + +def _ParseArgs(argv): + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--target-name', help='Fully qualified GN target name.') + parser.add_argument('--skip-build-server', + action='store_true', + help='Avoid using the build server.') + parser.add_argument('--lint-binary-path', + required=True, + help='Path to lint executable.') + parser.add_argument('--backported-methods', + help='Path to backported methods file created by R8.') + parser.add_argument('--cache-dir', + required=True, + help='Path to the directory in which the android cache ' + 'directory tree should be stored.') + parser.add_argument('--config-path', help='Path to lint suppressions file.') + parser.add_argument('--lint-gen-dir', + required=True, + help='Path to store generated xml files.') + parser.add_argument('--stamp', help='Path to stamp upon success.') + parser.add_argument('--android-sdk-version', + help='Version (API level) of the Android SDK used for ' + 'building.') + parser.add_argument('--min-sdk-version', + required=True, + help='Minimal SDK version to lint against.') + parser.add_argument('--android-sdk-root', + required=True, + help='Lint needs an explicit path to the android sdk.') + parser.add_argument('--testonly', + action='store_true', + help='If set, some checks like UnusedResources will be ' + 'disabled since they are not helpful for test ' + 'targets.') + parser.add_argument('--create-cache', + action='store_true', + help='Whether this invocation is just warming the cache.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + parser.add_argument('--java-sources', + help='File containing a list of java sources files.') + parser.add_argument('--aars', help='GN list of included aars.') + parser.add_argument('--srcjars', help='GN list of included srcjars.') + parser.add_argument('--manifest-path', + help='Path to original AndroidManifest.xml') + parser.add_argument('--extra-manifest-paths', + action='append', + help='GYP-list of manifest paths to merge into the ' + 'original AndroidManifest.xml') + parser.add_argument('--resource-sources', + default=[], + action='append', + help='GYP-list of resource sources files, similar to ' + 'java sources files, but for resource files.') + parser.add_argument('--resource-zips', + default=[], + action='append', + help='GYP-list of resource zips, zip files of generated ' + 'resource files.') + parser.add_argument('--classpath', + help='List of jars to add to the classpath.') + parser.add_argument('--baseline', + help='Baseline file to ignore existing errors and fail ' + 'on new errors.') + + args = parser.parse_args(build_utils.ExpandFileArgs(argv)) + args.java_sources = build_utils.ParseGnList(args.java_sources) + args.aars = build_utils.ParseGnList(args.aars) + args.srcjars = build_utils.ParseGnList(args.srcjars) + args.resource_sources = build_utils.ParseGnList(args.resource_sources) + args.extra_manifest_paths = build_utils.ParseGnList(args.extra_manifest_paths) + args.resource_zips = build_utils.ParseGnList(args.resource_zips) + args.classpath = build_utils.ParseGnList(args.classpath) + return args + + +def main(): + build_utils.InitLogging('LINT_DEBUG') + args = _ParseArgs(sys.argv[1:]) + + # TODO(wnwen): Consider removing lint cache now that there are only two lint + # invocations. + # Avoid parallelizing cache creation since lint runs without the cache defeat + # the purpose of creating the cache in the first place. + if (not args.create_cache and not args.skip_build_server + and server_utils.MaybeRunCommand( + name=args.target_name, argv=sys.argv, stamp_file=args.stamp)): + return + + sources = [] + for java_sources_file in args.java_sources: + sources.extend(build_utils.ReadSourcesList(java_sources_file)) + resource_sources = [] + for resource_sources_file in args.resource_sources: + resource_sources.extend(build_utils.ReadSourcesList(resource_sources_file)) + + possible_depfile_deps = (args.srcjars + args.resource_zips + sources + + resource_sources + [ + args.baseline, + args.manifest_path, + ]) + depfile_deps = [p for p in possible_depfile_deps if p] + + _RunLint(args.lint_binary_path, + args.backported_methods, + args.config_path, + args.manifest_path, + args.extra_manifest_paths, + sources, + args.classpath, + args.cache_dir, + args.android_sdk_version, + args.aars, + args.srcjars, + args.min_sdk_version, + resource_sources, + args.resource_zips, + args.android_sdk_root, + args.lint_gen_dir, + args.baseline, + testonly_target=args.testonly, + warnings_as_errors=args.warnings_as_errors) + logging.info('Creating stamp file') + build_utils.Touch(args.stamp) + + if args.depfile: + build_utils.WriteDepfile(args.depfile, args.stamp, depfile_deps) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/lint.pydeps b/third_party/libwebrtc/build/android/gyp/lint.pydeps new file mode 100644 index 0000000000..0994e19a4a --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/lint.pydeps @@ -0,0 +1,8 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/lint.pydeps build/android/gyp/lint.py +../../gn_helpers.py +lint.py +util/__init__.py +util/build_utils.py +util/manifest_utils.py +util/server_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/merge_manifest.py b/third_party/libwebrtc/build/android/gyp/merge_manifest.py new file mode 100755 index 0000000000..d0a93a8c78 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/merge_manifest.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +# Copyright 2017 The Chromium 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 dependency Android manifests into a root manifest.""" + +import argparse +import contextlib +import os +import sys +import tempfile +import xml.etree.ElementTree as ElementTree + +from util import build_utils +from util import manifest_utils + +_MANIFEST_MERGER_MAIN_CLASS = 'com.android.manifmerger.Merger' +_MANIFEST_MERGER_JARS = [ + os.path.join('build-system', 'manifest-merger.jar'), + os.path.join('common', 'common.jar'), + os.path.join('sdk-common', 'sdk-common.jar'), + os.path.join('sdklib', 'sdklib.jar'), + os.path.join('external', 'com', 'google', 'guava', 'guava', '28.1-jre', + 'guava-28.1-jre.jar'), + os.path.join('external', 'kotlin-plugin-ij', 'Kotlin', 'kotlinc', 'lib', + 'kotlin-stdlib.jar'), + os.path.join('external', 'com', 'google', 'code', 'gson', 'gson', '2.8.6', + 'gson-2.8.6.jar'), +] + + +@contextlib.contextmanager +def _ProcessManifest(manifest_path, min_sdk_version, target_sdk_version, + max_sdk_version, manifest_package): + """Patches an Android manifest's package and performs assertions to ensure + correctness for the manifest. + """ + doc, manifest, _ = manifest_utils.ParseManifest(manifest_path) + manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version, + max_sdk_version) + assert manifest_utils.GetPackage(manifest) or manifest_package, \ + 'Must set manifest package in GN or in AndroidManifest.xml' + manifest_utils.AssertPackage(manifest, manifest_package) + if manifest_package: + manifest.set('package', manifest_package) + tmp_prefix = os.path.basename(manifest_path) + with tempfile.NamedTemporaryFile(prefix=tmp_prefix) as patched_manifest: + manifest_utils.SaveManifest(doc, patched_manifest.name) + yield patched_manifest.name, manifest_utils.GetPackage(manifest) + + +def _BuildManifestMergerClasspath(android_sdk_cmdline_tools): + return ':'.join([ + os.path.join(android_sdk_cmdline_tools, 'lib', jar) + for jar in _MANIFEST_MERGER_JARS + ]) + + +def main(argv): + argv = build_utils.ExpandFileArgs(argv) + parser = argparse.ArgumentParser(description=__doc__) + build_utils.AddDepfileOption(parser) + parser.add_argument( + '--android-sdk-cmdline-tools', + help='Path to SDK\'s cmdline-tools folder.', + required=True) + parser.add_argument('--root-manifest', + help='Root manifest which to merge into', + required=True) + parser.add_argument('--output', help='Output manifest path', required=True) + parser.add_argument('--extras', + help='GN list of additional manifest to merge') + parser.add_argument( + '--min-sdk-version', + required=True, + help='android:minSdkVersion for merging.') + parser.add_argument( + '--target-sdk-version', + required=True, + help='android:targetSdkVersion for merging.') + parser.add_argument( + '--max-sdk-version', help='android:maxSdkVersion for merging.') + parser.add_argument( + '--manifest-package', + help='Package name of the merged AndroidManifest.xml.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + args = parser.parse_args(argv) + + classpath = _BuildManifestMergerClasspath(args.android_sdk_cmdline_tools) + + with build_utils.AtomicOutput(args.output) as output: + cmd = build_utils.JavaCmd(args.warnings_as_errors) + [ + '-cp', + classpath, + _MANIFEST_MERGER_MAIN_CLASS, + '--out', + output.name, + '--property', + 'MIN_SDK_VERSION=' + args.min_sdk_version, + '--property', + 'TARGET_SDK_VERSION=' + args.target_sdk_version, + ] + + if args.max_sdk_version: + cmd += [ + '--property', + 'MAX_SDK_VERSION=' + args.max_sdk_version, + ] + + extras = build_utils.ParseGnList(args.extras) + if extras: + cmd += ['--libs', ':'.join(extras)] + + with _ProcessManifest(args.root_manifest, args.min_sdk_version, + args.target_sdk_version, args.max_sdk_version, + args.manifest_package) as tup: + root_manifest, package = tup + cmd += [ + '--main', + root_manifest, + '--property', + 'PACKAGE=' + package, + '--remove-tools-declarations', + ] + build_utils.CheckOutput( + cmd, + # https://issuetracker.google.com/issues/63514300: + # The merger doesn't set a nonzero exit code for failures. + fail_func=lambda returncode, stderr: returncode != 0 or build_utils. + IsTimeStale(output.name, [root_manifest] + extras), + fail_on_output=args.warnings_as_errors) + + # Check for correct output. + _, manifest, _ = manifest_utils.ParseManifest(output.name) + manifest_utils.AssertUsesSdk(manifest, args.min_sdk_version, + args.target_sdk_version) + manifest_utils.AssertPackage(manifest, package) + + if args.depfile: + inputs = extras + classpath.split(':') + build_utils.WriteDepfile(args.depfile, args.output, inputs=inputs) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/merge_manifest.pydeps b/third_party/libwebrtc/build/android/gyp/merge_manifest.pydeps new file mode 100644 index 0000000000..ef9bb34047 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/merge_manifest.pydeps @@ -0,0 +1,7 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/merge_manifest.pydeps build/android/gyp/merge_manifest.py +../../gn_helpers.py +merge_manifest.py +util/__init__.py +util/build_utils.py +util/manifest_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/native_libraries_template.py b/third_party/libwebrtc/build/android/gyp/native_libraries_template.py new file mode 100644 index 0000000000..cf336ecf49 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/native_libraries_template.py @@ -0,0 +1,39 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +NATIVE_LIBRARIES_TEMPLATE = """\ +// This file is autogenerated by +// build/android/gyp/write_native_libraries_java.py +// Please do not change its content. + +package org.chromium.build; + +public class NativeLibraries {{ + public static final int CPU_FAMILY_UNKNOWN = 0; + public static final int CPU_FAMILY_ARM = 1; + public static final int CPU_FAMILY_MIPS = 2; + public static final int CPU_FAMILY_X86 = 3; + + // Set to true to enable the use of the Chromium Linker. + public static {MAYBE_FINAL}boolean sUseLinker{USE_LINKER}; + public static {MAYBE_FINAL}boolean sUseLibraryInZipFile{USE_LIBRARY_IN_ZIP_FILE}; + public static {MAYBE_FINAL}boolean sUseModernLinker{USE_MODERN_LINKER}; + + // This is the list of native libraries to be loaded (in the correct order) + // by LibraryLoader.java. + // TODO(cjhopman): This is public since it is referenced by NativeTestActivity.java + // directly. The two ways of library loading should be refactored into one. + public static {MAYBE_FINAL}String[] LIBRARIES = {{{LIBRARIES}}}; + + // This is the expected version of the 'main' native library, which is the one that + // implements the initial set of base JNI functions including + // base::android::nativeGetVersionName() + // TODO(torne): This is public to work around classloader issues in Trichrome + // where NativeLibraries is not in the same dex as LibraryLoader. + // We should instead split up Java code along package boundaries. + public static {MAYBE_FINAL}String sVersionNumber = {VERSION_NUMBER}; + + public static {MAYBE_FINAL}int sCpuFamily = {CPU_FAMILY}; +}} +""" diff --git a/third_party/libwebrtc/build/android/gyp/nocompile_test.py b/third_party/libwebrtc/build/android/gyp/nocompile_test.py new file mode 100755 index 0000000000..69fb395067 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/nocompile_test.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium 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 that compiling targets in BUILD.gn file fails.""" + +import argparse +import json +import os +import subprocess +import re +import sys +from util import build_utils + +_CHROMIUM_SRC = os.path.normpath(os.path.join(__file__, '..', '..', '..', '..')) +_NINJA_PATH = os.path.join(_CHROMIUM_SRC, 'third_party', 'depot_tools', 'ninja') + +# Relative to _CHROMIUM_SRC +_GN_SRC_REL_PATH = os.path.join('third_party', 'depot_tools', 'gn') + +# Regex for determining whether compile failed because 'gn gen' needs to be run. +_GN_GEN_REGEX = re.compile(r'ninja: (error|fatal):') + + +def _raise_command_exception(args, returncode, output): + """Raises an exception whose message describes a command failure. + + Args: + args: shell command-line (as passed to subprocess.Popen()) + returncode: status code. + output: command output. + Raises: + a new Exception. + """ + message = 'Command failed with status {}: {}\n' \ + 'Output:-----------------------------------------\n{}\n' \ + '------------------------------------------------\n'.format( + returncode, args, output) + raise Exception(message) + + +def _run_command(args, cwd=None): + """Runs shell command. Raises exception if command fails.""" + p = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=cwd) + pout, _ = p.communicate() + if p.returncode != 0: + _raise_command_exception(args, p.returncode, pout) + + +def _run_command_get_failure_output(args): + """Runs shell command. + + Returns: + Command output if command fails, None if command succeeds. + """ + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + pout, _ = p.communicate() + + if p.returncode == 0: + return None + + # For Python3 only: + if isinstance(pout, bytes) and sys.version_info >= (3, ): + pout = pout.decode('utf-8') + return '' if pout is None else pout + + +def _copy_and_append_gn_args(src_args_path, dest_args_path, extra_args): + """Copies args.gn. + + Args: + src_args_path: args.gn file to copy. + dest_args_path: Copy file destination. + extra_args: Text to append to args.gn after copy. + """ + with open(src_args_path) as f_in, open(dest_args_path, 'w') as f_out: + f_out.write(f_in.read()) + f_out.write('\n') + f_out.write('\n'.join(extra_args)) + + +def _find_regex_in_test_failure_output(test_output, regex): + """Searches for regex in test output. + + Args: + test_output: test output. + regex: regular expression to search for. + Returns: + Whether the regular expression was found in the part of the test output + after the 'FAILED' message. + + If the regex does not contain '\n': + the first 5 lines after the 'FAILED' message (including the text on the + line after the 'FAILED' message) is searched. + Otherwise: + the entire test output after the 'FAILED' message is searched. + """ + if test_output is None: + return False + + failed_index = test_output.find('FAILED') + if failed_index < 0: + return False + + failure_message = test_output[failed_index:] + if regex.find('\n') >= 0: + return re.search(regex, failure_message) + + return _search_regex_in_list(failure_message.split('\n')[:5], regex) + + +def _search_regex_in_list(value, regex): + for line in value: + if re.search(regex, line): + return True + return False + + +def _do_build_get_failure_output(gn_path, gn_cmd, options): + # Extract directory from test target. As all of the test targets are declared + # in the same BUILD.gn file, it does not matter which test target is used. + target_dir = gn_path.rsplit(':', 1)[0] + + if gn_cmd is not None: + gn_args = [ + _GN_SRC_REL_PATH, '--root-target=' + target_dir, gn_cmd, + os.path.relpath(options.out_dir, _CHROMIUM_SRC) + ] + _run_command(gn_args, cwd=_CHROMIUM_SRC) + + ninja_args = [_NINJA_PATH, '-C', options.out_dir, gn_path] + return _run_command_get_failure_output(ninja_args) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--gn-args-path', + required=True, + help='Path to args.gn file.') + parser.add_argument('--test-configs-path', + required=True, + help='Path to file with test configurations') + parser.add_argument('--out-dir', + required=True, + help='Path to output directory to use for compilation.') + parser.add_argument('--stamp', help='Path to touch.') + options = parser.parse_args() + + with open(options.test_configs_path) as f: + # Escape '\' in '\.' now. This avoids having to do the escaping in the test + # specification. + config_text = f.read().replace(r'\.', r'\\.') + test_configs = json.loads(config_text) + + if not os.path.exists(options.out_dir): + os.makedirs(options.out_dir) + + out_gn_args_path = os.path.join(options.out_dir, 'args.gn') + extra_gn_args = [ + 'enable_android_nocompile_tests = true', + 'treat_warnings_as_errors = true', + # GOMA does not work with non-standard output directories. + 'use_goma = false', + ] + _copy_and_append_gn_args(options.gn_args_path, out_gn_args_path, + extra_gn_args) + + ran_gn_gen = False + did_clean_build = False + error_messages = [] + for config in test_configs: + # Strip leading '//' + gn_path = config['target'][2:] + expect_regex = config['expect_regex'] + + test_output = _do_build_get_failure_output(gn_path, None, options) + + # 'gn gen' takes > 1s to run. Only run 'gn gen' if it is needed for compile. + if (test_output + and _search_regex_in_list(test_output.split('\n'), _GN_GEN_REGEX)): + assert not ran_gn_gen + ran_gn_gen = True + test_output = _do_build_get_failure_output(gn_path, 'gen', options) + + if (not _find_regex_in_test_failure_output(test_output, expect_regex) + and not did_clean_build): + # Ensure the failure is not due to incremental build. + did_clean_build = True + test_output = _do_build_get_failure_output(gn_path, 'clean', options) + + if not _find_regex_in_test_failure_output(test_output, expect_regex): + if test_output is None: + # Purpose of quotes at beginning of message is to make it clear that + # "Compile successful." is not a compiler log message. + test_output = '""\nCompile successful.' + error_message = '//{} failed.\nExpected compile output pattern:\n'\ + '{}\nActual compile output:\n{}'.format( + gn_path, expect_regex, test_output) + error_messages.append(error_message) + + if error_messages: + raise Exception('\n'.join(error_messages)) + + if options.stamp: + build_utils.Touch(options.stamp) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/optimize_resources.py b/third_party/libwebrtc/build/android/gyp/optimize_resources.py new file mode 100755 index 0000000000..d3b11636f5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/optimize_resources.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 The Chromium 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 sys + +from util import build_utils + + +def _ParseArgs(args): + """Parses command line options. + + Returns: + An options object as from argparse.ArgumentParser.parse_args() + """ + parser = argparse.ArgumentParser() + parser.add_argument('--aapt2-path', + required=True, + help='Path to the Android aapt2 tool.') + parser.add_argument( + '--short-resource-paths', + action='store_true', + help='Whether to shorten resource paths inside the apk or module.') + parser.add_argument( + '--strip-resource-names', + action='store_true', + help='Whether to strip resource names from the resource table of the apk ' + 'or module.') + parser.add_argument('--proto-path', + required=True, + help='Input proto format resources APK.') + parser.add_argument('--resources-config-paths', + default='[]', + help='GN list of paths to aapt2 resources config files.') + parser.add_argument('--r-text-in', + required=True, + help='Path to R.txt. Used to exclude id/ resources.') + parser.add_argument( + '--resources-path-map-out-path', + help='Path to file produced by aapt2 that maps original resource paths ' + 'to shortened resource paths inside the apk or module.') + parser.add_argument('--optimized-proto-path', + required=True, + help='Output for `aapt2 optimize`.') + options = parser.parse_args(args) + + options.resources_config_paths = build_utils.ParseGnList( + options.resources_config_paths) + + if options.resources_path_map_out_path and not options.short_resource_paths: + parser.error( + '--resources-path-map-out-path requires --short-resource-paths') + return options + + +def _CombineResourceConfigs(resources_config_paths, out_config_path): + with open(out_config_path, 'w') as out_config: + for config_path in resources_config_paths: + with open(config_path) as config: + out_config.write(config.read()) + out_config.write('\n') + + +def _ExtractNonCollapsableResources(rtxt_path): + """Extract resources that should not be collapsed from the R.txt file + + Resources of type ID are references to UI elements/views. They are used by + UI automation testing frameworks. They are kept in so that they don't break + tests, even though they may not actually be used during runtime. See + https://crbug.com/900993 + App icons (aka mipmaps) are sometimes referenced by other apps by name so must + be keps as well. See https://b/161564466 + + Args: + rtxt_path: Path to R.txt file with all the resources + Returns: + List of resources in the form of / + """ + resources = [] + _NO_COLLAPSE_TYPES = ['id', 'mipmap'] + with open(rtxt_path) as rtxt: + for line in rtxt: + for resource_type in _NO_COLLAPSE_TYPES: + if ' {} '.format(resource_type) in line: + resource_name = line.split()[2] + resources.append('{}/{}'.format(resource_type, resource_name)) + return resources + + +def _OptimizeApk(output, options, temp_dir, unoptimized_path, r_txt_path): + """Optimize intermediate .ap_ file with aapt2. + + Args: + output: Path to write to. + options: The command-line options. + temp_dir: A temporary directory. + unoptimized_path: path of the apk to optimize. + r_txt_path: path to the R.txt file of the unoptimized apk. + """ + optimize_command = [ + options.aapt2_path, + 'optimize', + unoptimized_path, + '-o', + output, + ] + + # Optimize the resources.pb file by obfuscating resource names and only + # allow usage via R.java constant. + if options.strip_resource_names: + no_collapse_resources = _ExtractNonCollapsableResources(r_txt_path) + gen_config_path = os.path.join(temp_dir, 'aapt2.config') + if options.resources_config_paths: + _CombineResourceConfigs(options.resources_config_paths, gen_config_path) + with open(gen_config_path, 'a') as config: + for resource in no_collapse_resources: + config.write('{}#no_collapse\n'.format(resource)) + + optimize_command += [ + '--collapse-resource-names', + '--resources-config-path', + gen_config_path, + ] + + if options.short_resource_paths: + optimize_command += ['--shorten-resource-paths'] + if options.resources_path_map_out_path: + optimize_command += [ + '--resource-path-shortening-map', options.resources_path_map_out_path + ] + + logging.debug('Running aapt2 optimize') + build_utils.CheckOutput(optimize_command, + print_stdout=False, + print_stderr=False) + + +def main(args): + options = _ParseArgs(args) + with build_utils.TempDir() as temp_dir: + _OptimizeApk(options.optimized_proto_path, options, temp_dir, + options.proto_path, options.r_text_in) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/optimize_resources.pydeps b/third_party/libwebrtc/build/android/gyp/optimize_resources.pydeps new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/android/gyp/prepare_resources.py b/third_party/libwebrtc/build/android/gyp/prepare_resources.py new file mode 100755 index 0000000000..ba75afaee3 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/prepare_resources.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# +# 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 resource directories to generate .resources.zip and R.txt +files.""" + +import argparse +import os +import shutil +import sys +import zipfile + +from util import build_utils +from util import jar_info_utils +from util import md5_check +from util import resources_parser +from util import resource_utils + + +def _ParseArgs(args): + """Parses command line options. + + Returns: + An options object as from argparse.ArgumentParser.parse_args() + """ + parser = argparse.ArgumentParser(description=__doc__) + build_utils.AddDepfileOption(parser) + + parser.add_argument('--res-sources-path', + required=True, + help='Path to a list of input resources for this target.') + + parser.add_argument( + '--r-text-in', + help='Path to pre-existing R.txt. Its resource IDs override those found ' + 'in the generated R.txt when generating R.java.') + + parser.add_argument( + '--allow-missing-resources', + action='store_true', + help='Do not fail if some resources exist in the res/ dir but are not ' + 'listed in the sources.') + + parser.add_argument( + '--resource-zip-out', + help='Path to a zip archive containing all resources from ' + '--resource-dirs, merged into a single directory tree.') + + parser.add_argument('--r-text-out', + help='Path to store the generated R.txt file.') + + parser.add_argument('--strip-drawables', + action="store_true", + help='Remove drawables from the resources.') + + options = parser.parse_args(args) + + with open(options.res_sources_path) as f: + options.sources = f.read().splitlines() + options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList( + options.sources) + + return options + + +def _CheckAllFilesListed(resource_files, resource_dirs): + resource_files = set(resource_files) + missing_files = [] + for path, _ in resource_utils.IterResourceFilesInDirectories(resource_dirs): + if path not in resource_files: + missing_files.append(path) + + if missing_files: + sys.stderr.write('Error: Found files not listed in the sources list of ' + 'the BUILD.gn target:\n') + for path in missing_files: + sys.stderr.write('{}\n'.format(path)) + sys.exit(1) + + +def _ZipResources(resource_dirs, zip_path, ignore_pattern): + # ignore_pattern is a string of ':' delimited list of globs used to ignore + # files that should not be part of the final resource zip. + files_to_zip = [] + path_info = resource_utils.ResourceInfoFile() + for index, resource_dir in enumerate(resource_dirs): + attributed_aar = None + if not resource_dir.startswith('..'): + aar_source_info_path = os.path.join( + os.path.dirname(resource_dir), 'source.info') + if os.path.exists(aar_source_info_path): + attributed_aar = jar_info_utils.ReadAarSourceInfo(aar_source_info_path) + + for path, archive_path in resource_utils.IterResourceFilesInDirectories( + [resource_dir], ignore_pattern): + attributed_path = path + if attributed_aar: + attributed_path = os.path.join(attributed_aar, 'res', + path[len(resource_dir) + 1:]) + # Use the non-prefixed archive_path in the .info file. + path_info.AddMapping(archive_path, attributed_path) + + resource_dir_name = os.path.basename(resource_dir) + archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path) + files_to_zip.append((archive_path, path)) + + path_info.Write(zip_path + '.info') + + with zipfile.ZipFile(zip_path, 'w') as z: + # This magic comment signals to resource_utils.ExtractDeps that this zip is + # not just the contents of a single res dir, without the encapsulating res/ + # (like the outputs of android_generated_resources targets), but instead has + # the contents of possibly multiple res/ dirs each within an encapsulating + # directory within the zip. + z.comment = resource_utils.MULTIPLE_RES_MAGIC_STRING + build_utils.DoZip(files_to_zip, z) + + +def _GenerateRTxt(options, r_txt_path): + """Generate R.txt file. + + Args: + options: The command-line options tuple. + r_txt_path: Locates where the R.txt file goes. + """ + ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN + if options.strip_drawables: + ignore_pattern += ':*drawable*' + + resources_parser.RTxtGenerator(options.resource_dirs, + ignore_pattern).WriteRTxtFile(r_txt_path) + + +def _OnStaleMd5(options): + with resource_utils.BuildContext() as build: + if options.sources and not options.allow_missing_resources: + _CheckAllFilesListed(options.sources, options.resource_dirs) + if options.r_text_in: + r_txt_path = options.r_text_in + else: + _GenerateRTxt(options, build.r_txt_path) + r_txt_path = build.r_txt_path + + if options.r_text_out: + shutil.copyfile(r_txt_path, options.r_text_out) + + if options.resource_zip_out: + ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN + if options.strip_drawables: + ignore_pattern += ':*drawable*' + _ZipResources(options.resource_dirs, options.resource_zip_out, + ignore_pattern) + + +def main(args): + args = build_utils.ExpandFileArgs(args) + options = _ParseArgs(args) + + # Order of these must match order specified in GN so that the correct one + # appears first in the depfile. + output_paths = [ + options.resource_zip_out, + options.resource_zip_out + '.info', + options.r_text_out, + ] + + input_paths = [options.res_sources_path] + if options.r_text_in: + input_paths += [options.r_text_in] + + # Resource files aren't explicitly listed in GN. Listing them in the depfile + # ensures the target will be marked stale when resource files are removed. + depfile_deps = [] + resource_names = [] + for resource_dir in options.resource_dirs: + for resource_file in build_utils.FindInDirectory(resource_dir, '*'): + # Don't list the empty .keep file in depfile. Since it doesn't end up + # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors + # if ever moved. + if not resource_file.endswith(os.path.join('empty', '.keep')): + input_paths.append(resource_file) + depfile_deps.append(resource_file) + resource_names.append(os.path.relpath(resource_file, resource_dir)) + + # Resource filenames matter to the output, so add them to strings as well. + # This matters if a file is renamed but not changed (http://crbug.com/597126). + input_strings = sorted(resource_names) + [ + options.strip_drawables, + ] + + # Since android_resources targets like *__all_dfm_resources depend on java + # targets that they do not need (in reality it only needs the transitive + # resource targets that those java targets depend on), md5_check is used to + # prevent outputs from being re-written when real inputs have not changed. + md5_check.CallAndWriteDepfileIfStale(lambda: _OnStaleMd5(options), + options, + input_paths=input_paths, + input_strings=input_strings, + output_paths=output_paths, + depfile_deps=depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/prepare_resources.pydeps b/third_party/libwebrtc/build/android/gyp/prepare_resources.pydeps new file mode 100644 index 0000000000..8136e733ef --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/prepare_resources.pydeps @@ -0,0 +1,35 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/prepare_resources.pydeps build/android/gyp/prepare_resources.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +../../print_python_deps.py +prepare_resources.py +util/__init__.py +util/build_utils.py +util/jar_info_utils.py +util/md5_check.py +util/resource_utils.py +util/resources_parser.py diff --git a/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.py b/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.py new file mode 100755 index 0000000000..52645d9b16 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 The Chromium 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 shutil +import sys + +from util import build_utils + + +def main(args): + parser = argparse.ArgumentParser(args) + parser.add_argument('--strip-path', required=True, help='') + parser.add_argument('--input-path', required=True, help='') + parser.add_argument('--stripped-output-path', required=True, help='') + parser.add_argument('--unstripped-output-path', required=True, help='') + options = parser.parse_args(args) + + # eu-strip's output keeps mode from source file which might not be writable + # thus it fails to override its output on the next run. AtomicOutput fixes + # the issue. + with build_utils.AtomicOutput(options.stripped_output_path) as out: + cmd = [ + options.strip_path, + options.input_path, + '-o', + out.name, + ] + build_utils.CheckOutput(cmd) + shutil.copyfile(options.input_path, options.unstripped_output_path) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.pydeps b/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.pydeps new file mode 100644 index 0000000000..8e2012aceb --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/process_native_prebuilt.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/process_native_prebuilt.pydeps build/android/gyp/process_native_prebuilt.py +../../gn_helpers.py +process_native_prebuilt.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/proguard.py b/third_party/libwebrtc/build/android/gyp/proguard.py new file mode 100755 index 0000000000..9da100e42d --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/proguard.py @@ -0,0 +1,710 @@ +#!/usr/bin/env python3 +# +# 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 argparse +from collections import defaultdict +import logging +import os +import re +import shutil +import sys +import tempfile +import zipfile + +import dex +import dex_jdk_libs +from pylib.dex import dex_parser +from util import build_utils +from util import diff_utils + +_API_LEVEL_VERSION_CODE = [ + (21, 'L'), + (22, 'LollipopMR1'), + (23, 'M'), + (24, 'N'), + (25, 'NMR1'), + (26, 'O'), + (27, 'OMR1'), + (28, 'P'), + (29, 'Q'), + (30, 'R'), + (31, 'S'), +] + + +def _ParseOptions(): + args = build_utils.ExpandFileArgs(sys.argv[1:]) + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--r8-path', + required=True, + help='Path to the R8.jar to use.') + parser.add_argument( + '--desugar-jdk-libs-json', help='Path to desugar_jdk_libs.json.') + parser.add_argument('--input-paths', + action='append', + required=True, + help='GN-list of .jar files to optimize.') + parser.add_argument('--desugar-jdk-libs-jar', + help='Path to desugar_jdk_libs.jar.') + parser.add_argument('--desugar-jdk-libs-configuration-jar', + help='Path to desugar_jdk_libs_configuration.jar.') + parser.add_argument('--output-path', help='Path to the generated .jar file.') + parser.add_argument( + '--proguard-configs', + action='append', + required=True, + help='GN-list of configuration files.') + parser.add_argument( + '--apply-mapping', help='Path to ProGuard mapping to apply.') + parser.add_argument( + '--mapping-output', + required=True, + help='Path for ProGuard to output mapping file to.') + parser.add_argument( + '--extra-mapping-output-paths', + help='GN-list of additional paths to copy output mapping file to.') + parser.add_argument( + '--classpath', + action='append', + help='GN-list of .jar files to include as libraries.') + parser.add_argument('--main-dex-rules-path', + action='append', + help='Path to main dex rules for multidex.') + parser.add_argument( + '--min-api', help='Minimum Android API level compatibility.') + parser.add_argument('--enable-obfuscation', + action='store_true', + help='Minify symbol names') + parser.add_argument( + '--verbose', '-v', action='store_true', help='Print all ProGuard output') + parser.add_argument( + '--repackage-classes', help='Package all optimized classes are put in.') + parser.add_argument( + '--disable-outlining', + action='store_true', + help='Disable the outlining optimization provided by R8.') + parser.add_argument( + '--disable-checks', + action='store_true', + help='Disable -checkdiscard directives and missing symbols check') + parser.add_argument('--sourcefile', help='Value for source file attribute') + parser.add_argument( + '--force-enable-assertions', + action='store_true', + help='Forcefully enable javac generated assertion code.') + parser.add_argument( + '--feature-jars', + action='append', + help='GN list of path to jars which comprise the corresponding feature.') + parser.add_argument( + '--dex-dest', + action='append', + dest='dex_dests', + help='Destination for dex file of the corresponding feature.') + parser.add_argument( + '--feature-name', + action='append', + dest='feature_names', + help='The name of the feature module.') + parser.add_argument( + '--uses-split', + action='append', + help='List of name pairs separated by : mapping a feature module to a ' + 'dependent feature module.') + parser.add_argument( + '--keep-rules-targets-regex', + metavar='KEEP_RULES_REGEX', + help='If passed outputs keep rules for references from all other inputs ' + 'to the subset of inputs that satisfy the KEEP_RULES_REGEX.') + parser.add_argument( + '--keep-rules-output-path', + help='Output path to the keep rules for references to the ' + '--keep-rules-targets-regex inputs from the rest of the inputs.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + parser.add_argument('--show-desugar-default-interface-warnings', + action='store_true', + help='Enable desugaring warnings.') + parser.add_argument('--dump-inputs', + action='store_true', + help='Use when filing R8 bugs to capture inputs.' + ' Stores inputs to r8inputs.zip') + parser.add_argument( + '--stamp', + help='File to touch upon success. Mutually exclusive with --output-path') + parser.add_argument('--desugared-library-keep-rule-output', + help='Path to desugared library keep rule output file.') + + diff_utils.AddCommandLineFlags(parser) + options = parser.parse_args(args) + + if options.feature_names: + if options.output_path: + parser.error('Feature splits cannot specify an output in GN.') + if not options.actual_file and not options.stamp: + parser.error('Feature splits require a stamp file as output.') + elif not options.output_path: + parser.error('Output path required when feature splits aren\'t used') + + if bool(options.keep_rules_targets_regex) != bool( + options.keep_rules_output_path): + raise Exception('You must path both --keep-rules-targets-regex and ' + '--keep-rules-output-path') + + options.classpath = build_utils.ParseGnList(options.classpath) + options.proguard_configs = build_utils.ParseGnList(options.proguard_configs) + options.input_paths = build_utils.ParseGnList(options.input_paths) + options.extra_mapping_output_paths = build_utils.ParseGnList( + options.extra_mapping_output_paths) + + if options.feature_names: + if 'base' not in options.feature_names: + parser.error('"base" feature required when feature arguments are used.') + if len(options.feature_names) != len(options.feature_jars) or len( + options.feature_names) != len(options.dex_dests): + parser.error('Invalid feature argument lengths.') + + options.feature_jars = [ + build_utils.ParseGnList(x) for x in options.feature_jars + ] + + split_map = {} + if options.uses_split: + for split_pair in options.uses_split: + child, parent = split_pair.split(':') + for name in (child, parent): + if name not in options.feature_names: + parser.error('"%s" referenced in --uses-split not present.' % name) + split_map[child] = parent + options.uses_split = split_map + + return options + + +class _SplitContext(object): + def __init__(self, name, output_path, input_jars, work_dir, parent_name=None): + self.name = name + self.parent_name = parent_name + self.input_jars = set(input_jars) + self.final_output_path = output_path + self.staging_dir = os.path.join(work_dir, name) + os.mkdir(self.staging_dir) + + def CreateOutput(self, has_imported_lib=False, keep_rule_output=None): + found_files = build_utils.FindInDirectory(self.staging_dir) + if not found_files: + raise Exception('Missing dex outputs in {}'.format(self.staging_dir)) + + if self.final_output_path.endswith('.dex'): + if has_imported_lib: + raise Exception( + 'Trying to create a single .dex file, but a dependency requires ' + 'JDK Library Desugaring (which necessitates a second file).' + 'Refer to %s to see what desugaring was required' % + keep_rule_output) + if len(found_files) != 1: + raise Exception('Expected exactly 1 dex file output, found: {}'.format( + '\t'.join(found_files))) + shutil.move(found_files[0], self.final_output_path) + return + + # Add to .jar using Python rather than having R8 output to a .zip directly + # in order to disable compression of the .jar, saving ~500ms. + tmp_jar_output = self.staging_dir + '.jar' + build_utils.DoZip(found_files, tmp_jar_output, base_dir=self.staging_dir) + shutil.move(tmp_jar_output, self.final_output_path) + + +def _DeDupeInputJars(split_contexts_by_name): + """Moves jars used by multiple splits into common ancestors. + + Updates |input_jars| for each _SplitContext. + """ + + def count_ancestors(split_context): + ret = 0 + if split_context.parent_name: + ret += 1 + ret += count_ancestors(split_contexts_by_name[split_context.parent_name]) + return ret + + base_context = split_contexts_by_name['base'] + # Sort by tree depth so that ensure children are visited before their parents. + sorted_contexts = list(split_contexts_by_name.values()) + sorted_contexts.remove(base_context) + sorted_contexts.sort(key=count_ancestors, reverse=True) + + # If a jar is present in multiple siblings, promote it to their parent. + seen_jars_by_parent = defaultdict(set) + for split_context in sorted_contexts: + seen_jars = seen_jars_by_parent[split_context.parent_name] + new_dupes = seen_jars.intersection(split_context.input_jars) + parent_context = split_contexts_by_name[split_context.parent_name] + parent_context.input_jars.update(new_dupes) + seen_jars.update(split_context.input_jars) + + def ancestor_jars(parent_name, dest=None): + dest = dest or set() + if not parent_name: + return dest + parent_context = split_contexts_by_name[parent_name] + dest.update(parent_context.input_jars) + return ancestor_jars(parent_context.parent_name, dest) + + # Now that jars have been moved up the tree, remove those that appear in + # ancestors. + for split_context in sorted_contexts: + split_context.input_jars -= ancestor_jars(split_context.parent_name) + + +def _OptimizeWithR8(options, + config_paths, + libraries, + dynamic_config_data, + print_stdout=False): + with build_utils.TempDir() as tmp_dir: + if dynamic_config_data: + dynamic_config_path = os.path.join(tmp_dir, 'dynamic_config.flags') + with open(dynamic_config_path, 'w') as f: + f.write(dynamic_config_data) + config_paths = config_paths + [dynamic_config_path] + + tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt') + # If there is no output (no classes are kept), this prevents this script + # from failing. + build_utils.Touch(tmp_mapping_path) + + tmp_output = os.path.join(tmp_dir, 'r8out') + os.mkdir(tmp_output) + + split_contexts_by_name = {} + if options.feature_names: + for name, dest_dex, input_jars in zip(options.feature_names, + options.dex_dests, + options.feature_jars): + parent_name = options.uses_split.get(name) + if parent_name is None and name != 'base': + parent_name = 'base' + split_context = _SplitContext(name, + dest_dex, + input_jars, + tmp_output, + parent_name=parent_name) + split_contexts_by_name[name] = split_context + else: + # Base context will get populated via "extra_jars" below. + split_contexts_by_name['base'] = _SplitContext('base', + options.output_path, [], + tmp_output) + base_context = split_contexts_by_name['base'] + + # R8 OOMs with the default xmx=1G. + cmd = build_utils.JavaCmd(options.warnings_as_errors, xmx='2G') + [ + '-Dcom.android.tools.r8.allowTestProguardOptions=1', + '-Dcom.android.tools.r8.disableHorizontalClassMerging=1', + ] + if options.disable_outlining: + cmd += ['-Dcom.android.tools.r8.disableOutlining=1'] + if options.dump_inputs: + cmd += ['-Dcom.android.tools.r8.dumpinputtofile=r8inputs.zip'] + cmd += [ + '-cp', + options.r8_path, + 'com.android.tools.r8.R8', + '--no-data-resources', + '--output', + base_context.staging_dir, + '--pg-map-output', + tmp_mapping_path, + ] + + if options.disable_checks: + # Info level priority logs are not printed by default. + cmd += ['--map-diagnostics:CheckDiscardDiagnostic', 'error', 'info'] + + if options.desugar_jdk_libs_json: + cmd += [ + '--desugared-lib', + options.desugar_jdk_libs_json, + '--desugared-lib-pg-conf-output', + options.desugared_library_keep_rule_output, + ] + + if options.min_api: + cmd += ['--min-api', options.min_api] + + if options.force_enable_assertions: + cmd += ['--force-enable-assertions'] + + for lib in libraries: + cmd += ['--lib', lib] + + for config_file in config_paths: + cmd += ['--pg-conf', config_file] + + if options.main_dex_rules_path: + for main_dex_rule in options.main_dex_rules_path: + cmd += ['--main-dex-rules', main_dex_rule] + + _DeDupeInputJars(split_contexts_by_name) + + # Add any extra inputs to the base context (e.g. desugar runtime). + extra_jars = set(options.input_paths) + for split_context in split_contexts_by_name.values(): + extra_jars -= split_context.input_jars + base_context.input_jars.update(extra_jars) + + for split_context in split_contexts_by_name.values(): + if split_context is base_context: + continue + for in_jar in sorted(split_context.input_jars): + cmd += ['--feature', in_jar, split_context.staging_dir] + + cmd += sorted(base_context.input_jars) + + try: + stderr_filter = dex.CreateStderrFilter( + options.show_desugar_default_interface_warnings) + logging.debug('Running R8') + build_utils.CheckOutput(cmd, + print_stdout=print_stdout, + stderr_filter=stderr_filter, + fail_on_output=options.warnings_as_errors) + except build_utils.CalledProcessError: + # Python will print the original exception as well. + raise Exception( + 'R8 failed. Please see ' + 'https://chromium.googlesource.com/chromium/src/+/HEAD/build/' + 'android/docs/java_optimization.md#Debugging-common-failures') + + base_has_imported_lib = False + if options.desugar_jdk_libs_json: + logging.debug('Running L8') + existing_files = build_utils.FindInDirectory(base_context.staging_dir) + jdk_dex_output = os.path.join(base_context.staging_dir, + 'classes%d.dex' % (len(existing_files) + 1)) + # Use -applymapping to avoid name collisions. + l8_dynamic_config_path = os.path.join(tmp_dir, 'l8_dynamic_config.flags') + with open(l8_dynamic_config_path, 'w') as f: + f.write("-applymapping '{}'\n".format(tmp_mapping_path)) + # Pass the dynamic config so that obfuscation options are picked up. + l8_config_paths = [dynamic_config_path, l8_dynamic_config_path] + if os.path.exists(options.desugared_library_keep_rule_output): + l8_config_paths.append(options.desugared_library_keep_rule_output) + + base_has_imported_lib = dex_jdk_libs.DexJdkLibJar( + options.r8_path, options.min_api, options.desugar_jdk_libs_json, + options.desugar_jdk_libs_jar, + options.desugar_jdk_libs_configuration_jar, jdk_dex_output, + options.warnings_as_errors, l8_config_paths) + if int(options.min_api) >= 24 and base_has_imported_lib: + with open(jdk_dex_output, 'rb') as f: + dexfile = dex_parser.DexFile(bytearray(f.read())) + for m in dexfile.IterMethodSignatureParts(): + print('{}#{}'.format(m[0], m[2])) + assert False, ( + 'Desugared JDK libs are disabled on Monochrome and newer - see ' + 'crbug.com/1159984 for details, and see above list for desugared ' + 'classes and methods.') + + logging.debug('Collecting ouputs') + base_context.CreateOutput(base_has_imported_lib, + options.desugared_library_keep_rule_output) + for split_context in split_contexts_by_name.values(): + if split_context is not base_context: + split_context.CreateOutput() + + with open(options.mapping_output, 'w') as out_file, \ + open(tmp_mapping_path) as in_file: + # Mapping files generated by R8 include comments that may break + # some of our tooling so remove those (specifically: apkanalyzer). + out_file.writelines(l for l in in_file if not l.startswith('#')) + return base_context + + +def _OutputKeepRules(r8_path, input_paths, classpath, targets_re_string, + keep_rules_output): + cmd = build_utils.JavaCmd(False) + [ + '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences', + '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning', + '--keep-rules', '--output', keep_rules_output + ] + targets_re = re.compile(targets_re_string) + for path in input_paths: + if targets_re.search(path): + cmd += ['--target', path] + else: + cmd += ['--source', path] + for path in classpath: + cmd += ['--lib', path] + + build_utils.CheckOutput(cmd, print_stderr=False, fail_on_output=False) + + +def _CheckForMissingSymbols(r8_path, dex_files, classpath, warnings_as_errors, + error_title): + cmd = build_utils.JavaCmd(warnings_as_errors) + [ + '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences', + '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning', + '--check' + ] + + for path in classpath: + cmd += ['--lib', path] + for path in dex_files: + cmd += ['--source', path] + + def stderr_filter(stderr): + ignored_lines = [ + # Summary contains warning count, which our filtering makes wrong. + 'Warning: Tracereferences found', + + # TODO(agrieve): Create interface jars for these missing classes rather + # than allowlisting here. + 'dalvik.system', + 'libcore.io', + 'sun.misc.Unsafe', + + # Found in: com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper + 'android.text.StaticLayout.', + + # Explicictly guarded by try (NoClassDefFoundError) in Flogger's + # PlatformProvider. + 'com.google.common.flogger.backend.google.GooglePlatform', + 'com.google.common.flogger.backend.system.DefaultPlatform', + + # trichrome_webview_google_bundle contains this missing reference. + # TODO(crbug.com/1142530): Fix this missing reference properly. + 'org.chromium.build.NativeLibraries', + + # TODO(agrieve): Exclude these only when use_jacoco_coverage=true. + 'java.lang.instrument.ClassFileTransformer', + 'java.lang.instrument.IllegalClassFormatException', + 'java.lang.instrument.Instrumentation', + 'java.lang.management.ManagementFactory', + 'javax.management.MBeanServer', + 'javax.management.ObjectInstance', + 'javax.management.ObjectName', + 'javax.management.StandardMBean', + + # Explicitly guarded by try (NoClassDefFoundError) in Firebase's + # KotlinDetector: com.google.firebase.platforminfo.KotlinDetector. + 'kotlin.KotlinVersion', + ] + + had_unfiltered_items = ' ' in stderr + stderr = build_utils.FilterLines( + stderr, '|'.join(re.escape(x) for x in ignored_lines)) + if stderr: + if ' ' in stderr: + stderr = error_title + """ +Tip: Build with: + is_java_debug=false + treat_warnings_as_errors=false + enable_proguard_obfuscation=false + and then use dexdump to see which class(s) reference them. + + E.g.: + third_party/android_sdk/public/build-tools/*/dexdump -d \ +out/Release/apks/YourApk.apk > dex.txt +""" + stderr + + if 'FragmentActivity' in stderr: + stderr += """ +You may need to update build configs to run FragmentActivityReplacer for +additional targets. See +https://chromium.googlesource.com/chromium/src.git/+/main/docs/ui/android/bytecode_rewriting.md. +""" + elif had_unfiltered_items: + # Left only with empty headings. All indented items filtered out. + stderr = '' + return stderr + + logging.debug('cmd: %s', ' '.join(cmd)) + build_utils.CheckOutput(cmd, + print_stdout=True, + stderr_filter=stderr_filter, + fail_on_output=warnings_as_errors) + + +def _CombineConfigs(configs, dynamic_config_data, exclude_generated=False): + ret = [] + + # Sort in this way so //clank versions of the same libraries will sort + # to the same spot in the file. + def sort_key(path): + return tuple(reversed(path.split(os.path.sep))) + + for config in sorted(configs, key=sort_key): + if exclude_generated and config.endswith('.resources.proguard.txt'): + continue + + with open(config) as config_file: + contents = config_file.read().rstrip() + + if not contents.strip(): + # Ignore empty files. + continue + + # Fix up line endings (third_party configs can have windows endings). + contents = contents.replace('\r', '') + # Remove numbers from generated rule comments to make file more + # diff'able. + contents = re.sub(r' #generated:\d+', '', contents) + ret.append('# File: ' + config) + ret.append(contents) + ret.append('') + + if dynamic_config_data: + ret.append('# File: //build/android/gyp/proguard.py (generated rules)') + ret.append(dynamic_config_data) + ret.append('') + return '\n'.join(ret) + + +def _CreateDynamicConfig(options): + # Our scripts already fail on output. Adding -ignorewarnings makes R8 output + # warnings rather than throw exceptions so we can selectively ignore them via + # dex.py's ignore list. Context: https://crbug.com/1180222 + ret = ["-ignorewarnings"] + + if options.sourcefile: + ret.append("-renamesourcefileattribute '%s' # OMIT FROM EXPECTATIONS" % + options.sourcefile) + + if options.enable_obfuscation: + ret.append("-repackageclasses ''") + else: + ret.append("-dontobfuscate") + + if options.apply_mapping: + ret.append("-applymapping '%s'" % options.apply_mapping) + + _min_api = int(options.min_api) if options.min_api else 0 + for api_level, version_code in _API_LEVEL_VERSION_CODE: + annotation_name = 'org.chromium.base.annotations.VerifiesOn' + version_code + if api_level > _min_api: + ret.append('-keep @interface %s' % annotation_name) + ret.append("""\ +-if @%s class * { + *** *(...); +} +-keep,allowobfuscation class <1> { + *** <2>(...); +}""" % annotation_name) + ret.append("""\ +-keepclassmembers,allowobfuscation class ** { + @%s ; +}""" % annotation_name) + return '\n'.join(ret) + + +def _VerifyNoEmbeddedConfigs(jar_paths): + failed = False + for jar_path in jar_paths: + with zipfile.ZipFile(jar_path) as z: + for name in z.namelist(): + if name.startswith('META-INF/proguard/'): + failed = True + sys.stderr.write("""\ +Found embedded proguard config within {}. +Embedded configs are not permitted (https://crbug.com/989505) +""".format(jar_path)) + break + if failed: + sys.exit(1) + + +def _ContainsDebuggingConfig(config_str): + debugging_configs = ('-whyareyoukeeping', '-whyareyounotinlining') + return any(config in config_str for config in debugging_configs) + + +def _MaybeWriteStampAndDepFile(options, inputs): + output = options.output_path + if options.stamp: + build_utils.Touch(options.stamp) + output = options.stamp + if options.depfile: + build_utils.WriteDepfile(options.depfile, output, inputs=inputs) + + +def main(): + build_utils.InitLogging('PROGUARD_DEBUG') + options = _ParseOptions() + + logging.debug('Preparing configs') + proguard_configs = options.proguard_configs + + # ProGuard configs that are derived from flags. + dynamic_config_data = _CreateDynamicConfig(options) + + # ProGuard configs that are derived from flags. + merged_configs = _CombineConfigs( + proguard_configs, dynamic_config_data, exclude_generated=True) + print_stdout = _ContainsDebuggingConfig(merged_configs) or options.verbose + + if options.expected_file: + diff_utils.CheckExpectations(merged_configs, options) + if options.only_verify_expectations: + build_utils.WriteDepfile(options.depfile, + options.actual_file, + inputs=options.proguard_configs) + return + + logging.debug('Looking for embedded configs') + libraries = [] + for p in options.classpath: + # TODO(bjoyce): Remove filter once old android support libraries are gone. + # Fix for having Library class extend program class dependency problem. + if 'com_android_support' in p or 'android_support_test' in p: + continue + # If a jar is part of input no need to include it as library jar. + if p not in libraries and p not in options.input_paths: + libraries.append(p) + _VerifyNoEmbeddedConfigs(options.input_paths + libraries) + if options.keep_rules_output_path: + _OutputKeepRules(options.r8_path, options.input_paths, options.classpath, + options.keep_rules_targets_regex, + options.keep_rules_output_path) + return + + base_context = _OptimizeWithR8(options, proguard_configs, libraries, + dynamic_config_data, print_stdout) + + if not options.disable_checks: + logging.debug('Running tracereferences') + all_dex_files = [] + if options.output_path: + all_dex_files.append(options.output_path) + if options.dex_dests: + all_dex_files.extend(options.dex_dests) + error_title = 'DEX contains references to non-existent symbols after R8.' + _CheckForMissingSymbols(options.r8_path, all_dex_files, options.classpath, + options.warnings_as_errors, error_title) + # Also ensure that base module doesn't have any references to child dex + # symbols. + # TODO(agrieve): Remove this check once r8 desugaring is fixed to not put + # synthesized classes in the base module. + error_title = 'Base module DEX contains references symbols within DFMs.' + _CheckForMissingSymbols(options.r8_path, [base_context.final_output_path], + options.classpath, options.warnings_as_errors, + error_title) + + for output in options.extra_mapping_output_paths: + shutil.copy(options.mapping_output, output) + + inputs = options.proguard_configs + options.input_paths + libraries + if options.apply_mapping: + inputs.append(options.apply_mapping) + + _MaybeWriteStampAndDepFile(options, inputs) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/gyp/proguard.pydeps b/third_party/libwebrtc/build/android/gyp/proguard.pydeps new file mode 100644 index 0000000000..c1de73b57e --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/proguard.pydeps @@ -0,0 +1,16 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/proguard.pydeps build/android/gyp/proguard.py +../../gn_helpers.py +../../print_python_deps.py +../convert_dex_profile.py +../pylib/__init__.py +../pylib/dex/__init__.py +../pylib/dex/dex_parser.py +dex.py +dex_jdk_libs.py +proguard.py +util/__init__.py +util/build_utils.py +util/diff_utils.py +util/md5_check.py +util/zipalign.py diff --git a/third_party/libwebrtc/build/android/gyp/proto/Configuration_pb2.py b/third_party/libwebrtc/build/android/gyp/proto/Configuration_pb2.py new file mode 100644 index 0000000000..859183089a --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/proto/Configuration_pb2.py @@ -0,0 +1,697 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: frameworks/base/tools/aapt2/Configuration.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='frameworks/base/tools/aapt2/Configuration.proto', + package='aapt.pb', + syntax='proto3', + serialized_options=_b('\n\020com.android.aapt'), + serialized_pb=_b('\n/frameworks/base/tools/aapt2/Configuration.proto\x12\x07\x61\x61pt.pb\"\xd9\x14\n\rConfiguration\x12\x0b\n\x03mcc\x18\x01 \x01(\r\x12\x0b\n\x03mnc\x18\x02 \x01(\r\x12\x0e\n\x06locale\x18\x03 \x01(\t\x12@\n\x10layout_direction\x18\x04 \x01(\x0e\x32&.aapt.pb.Configuration.LayoutDirection\x12\x14\n\x0cscreen_width\x18\x05 \x01(\r\x12\x15\n\rscreen_height\x18\x06 \x01(\r\x12\x17\n\x0fscreen_width_dp\x18\x07 \x01(\r\x12\x18\n\x10screen_height_dp\x18\x08 \x01(\r\x12 \n\x18smallest_screen_width_dp\x18\t \x01(\r\x12\x43\n\x12screen_layout_size\x18\n \x01(\x0e\x32\'.aapt.pb.Configuration.ScreenLayoutSize\x12\x43\n\x12screen_layout_long\x18\x0b \x01(\x0e\x32\'.aapt.pb.Configuration.ScreenLayoutLong\x12\x38\n\x0cscreen_round\x18\x0c \x01(\x0e\x32\".aapt.pb.Configuration.ScreenRound\x12?\n\x10wide_color_gamut\x18\r \x01(\x0e\x32%.aapt.pb.Configuration.WideColorGamut\x12\'\n\x03hdr\x18\x0e \x01(\x0e\x32\x1a.aapt.pb.Configuration.Hdr\x12\x37\n\x0borientation\x18\x0f \x01(\x0e\x32\".aapt.pb.Configuration.Orientation\x12\x37\n\x0cui_mode_type\x18\x10 \x01(\x0e\x32!.aapt.pb.Configuration.UiModeType\x12\x39\n\rui_mode_night\x18\x11 \x01(\x0e\x32\".aapt.pb.Configuration.UiModeNight\x12\x0f\n\x07\x64\x65nsity\x18\x12 \x01(\r\x12\x37\n\x0btouchscreen\x18\x13 \x01(\x0e\x32\".aapt.pb.Configuration.Touchscreen\x12\x36\n\x0bkeys_hidden\x18\x14 \x01(\x0e\x32!.aapt.pb.Configuration.KeysHidden\x12\x31\n\x08keyboard\x18\x15 \x01(\x0e\x32\x1f.aapt.pb.Configuration.Keyboard\x12\x34\n\nnav_hidden\x18\x16 \x01(\x0e\x32 .aapt.pb.Configuration.NavHidden\x12\x35\n\nnavigation\x18\x17 \x01(\x0e\x32!.aapt.pb.Configuration.Navigation\x12\x13\n\x0bsdk_version\x18\x18 \x01(\r\x12\x0f\n\x07product\x18\x19 \x01(\t\"a\n\x0fLayoutDirection\x12\x1a\n\x16LAYOUT_DIRECTION_UNSET\x10\x00\x12\x18\n\x14LAYOUT_DIRECTION_LTR\x10\x01\x12\x18\n\x14LAYOUT_DIRECTION_RTL\x10\x02\"\xaa\x01\n\x10ScreenLayoutSize\x12\x1c\n\x18SCREEN_LAYOUT_SIZE_UNSET\x10\x00\x12\x1c\n\x18SCREEN_LAYOUT_SIZE_SMALL\x10\x01\x12\x1d\n\x19SCREEN_LAYOUT_SIZE_NORMAL\x10\x02\x12\x1c\n\x18SCREEN_LAYOUT_SIZE_LARGE\x10\x03\x12\x1d\n\x19SCREEN_LAYOUT_SIZE_XLARGE\x10\x04\"m\n\x10ScreenLayoutLong\x12\x1c\n\x18SCREEN_LAYOUT_LONG_UNSET\x10\x00\x12\x1b\n\x17SCREEN_LAYOUT_LONG_LONG\x10\x01\x12\x1e\n\x1aSCREEN_LAYOUT_LONG_NOTLONG\x10\x02\"X\n\x0bScreenRound\x12\x16\n\x12SCREEN_ROUND_UNSET\x10\x00\x12\x16\n\x12SCREEN_ROUND_ROUND\x10\x01\x12\x19\n\x15SCREEN_ROUND_NOTROUND\x10\x02\"h\n\x0eWideColorGamut\x12\x1a\n\x16WIDE_COLOR_GAMUT_UNSET\x10\x00\x12\x1b\n\x17WIDE_COLOR_GAMUT_WIDECG\x10\x01\x12\x1d\n\x19WIDE_COLOR_GAMUT_NOWIDECG\x10\x02\"3\n\x03Hdr\x12\r\n\tHDR_UNSET\x10\x00\x12\x0e\n\nHDR_HIGHDR\x10\x01\x12\r\n\tHDR_LOWDR\x10\x02\"h\n\x0bOrientation\x12\x15\n\x11ORIENTATION_UNSET\x10\x00\x12\x14\n\x10ORIENTATION_PORT\x10\x01\x12\x14\n\x10ORIENTATION_LAND\x10\x02\x12\x16\n\x12ORIENTATION_SQUARE\x10\x03\"\xd7\x01\n\nUiModeType\x12\x16\n\x12UI_MODE_TYPE_UNSET\x10\x00\x12\x17\n\x13UI_MODE_TYPE_NORMAL\x10\x01\x12\x15\n\x11UI_MODE_TYPE_DESK\x10\x02\x12\x14\n\x10UI_MODE_TYPE_CAR\x10\x03\x12\x1b\n\x17UI_MODE_TYPE_TELEVISION\x10\x04\x12\x1a\n\x16UI_MODE_TYPE_APPLIANCE\x10\x05\x12\x16\n\x12UI_MODE_TYPE_WATCH\x10\x06\x12\x1a\n\x16UI_MODE_TYPE_VRHEADSET\x10\x07\"[\n\x0bUiModeNight\x12\x17\n\x13UI_MODE_NIGHT_UNSET\x10\x00\x12\x17\n\x13UI_MODE_NIGHT_NIGHT\x10\x01\x12\x1a\n\x16UI_MODE_NIGHT_NOTNIGHT\x10\x02\"m\n\x0bTouchscreen\x12\x15\n\x11TOUCHSCREEN_UNSET\x10\x00\x12\x17\n\x13TOUCHSCREEN_NOTOUCH\x10\x01\x12\x16\n\x12TOUCHSCREEN_STYLUS\x10\x02\x12\x16\n\x12TOUCHSCREEN_FINGER\x10\x03\"v\n\nKeysHidden\x12\x15\n\x11KEYS_HIDDEN_UNSET\x10\x00\x12\x1b\n\x17KEYS_HIDDEN_KEYSEXPOSED\x10\x01\x12\x1a\n\x16KEYS_HIDDEN_KEYSHIDDEN\x10\x02\x12\x18\n\x14KEYS_HIDDEN_KEYSSOFT\x10\x03\"`\n\x08Keyboard\x12\x12\n\x0eKEYBOARD_UNSET\x10\x00\x12\x13\n\x0fKEYBOARD_NOKEYS\x10\x01\x12\x13\n\x0fKEYBOARD_QWERTY\x10\x02\x12\x16\n\x12KEYBOARD_TWELVEKEY\x10\x03\"V\n\tNavHidden\x12\x14\n\x10NAV_HIDDEN_UNSET\x10\x00\x12\x19\n\x15NAV_HIDDEN_NAVEXPOSED\x10\x01\x12\x18\n\x14NAV_HIDDEN_NAVHIDDEN\x10\x02\"}\n\nNavigation\x12\x14\n\x10NAVIGATION_UNSET\x10\x00\x12\x14\n\x10NAVIGATION_NONAV\x10\x01\x12\x13\n\x0fNAVIGATION_DPAD\x10\x02\x12\x18\n\x14NAVIGATION_TRACKBALL\x10\x03\x12\x14\n\x10NAVIGATION_WHEEL\x10\x04\x42\x12\n\x10\x63om.android.aaptb\x06proto3') +) + + + +_CONFIGURATION_LAYOUTDIRECTION = _descriptor.EnumDescriptor( + name='LayoutDirection', + full_name='aapt.pb.Configuration.LayoutDirection', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='LAYOUT_DIRECTION_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='LAYOUT_DIRECTION_LTR', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='LAYOUT_DIRECTION_RTL', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1119, + serialized_end=1216, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_LAYOUTDIRECTION) + +_CONFIGURATION_SCREENLAYOUTSIZE = _descriptor.EnumDescriptor( + name='ScreenLayoutSize', + full_name='aapt.pb.Configuration.ScreenLayoutSize', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_SIZE_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_SIZE_SMALL', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_SIZE_NORMAL', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_SIZE_LARGE', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_SIZE_XLARGE', index=4, number=4, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1219, + serialized_end=1389, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_SCREENLAYOUTSIZE) + +_CONFIGURATION_SCREENLAYOUTLONG = _descriptor.EnumDescriptor( + name='ScreenLayoutLong', + full_name='aapt.pb.Configuration.ScreenLayoutLong', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_LONG_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_LONG_LONG', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_LAYOUT_LONG_NOTLONG', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1391, + serialized_end=1500, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_SCREENLAYOUTLONG) + +_CONFIGURATION_SCREENROUND = _descriptor.EnumDescriptor( + name='ScreenRound', + full_name='aapt.pb.Configuration.ScreenRound', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='SCREEN_ROUND_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_ROUND_ROUND', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SCREEN_ROUND_NOTROUND', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1502, + serialized_end=1590, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_SCREENROUND) + +_CONFIGURATION_WIDECOLORGAMUT = _descriptor.EnumDescriptor( + name='WideColorGamut', + full_name='aapt.pb.Configuration.WideColorGamut', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='WIDE_COLOR_GAMUT_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WIDE_COLOR_GAMUT_WIDECG', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WIDE_COLOR_GAMUT_NOWIDECG', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1592, + serialized_end=1696, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_WIDECOLORGAMUT) + +_CONFIGURATION_HDR = _descriptor.EnumDescriptor( + name='Hdr', + full_name='aapt.pb.Configuration.Hdr', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='HDR_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='HDR_HIGHDR', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='HDR_LOWDR', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1698, + serialized_end=1749, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_HDR) + +_CONFIGURATION_ORIENTATION = _descriptor.EnumDescriptor( + name='Orientation', + full_name='aapt.pb.Configuration.Orientation', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='ORIENTATION_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ORIENTATION_PORT', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ORIENTATION_LAND', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ORIENTATION_SQUARE', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1751, + serialized_end=1855, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_ORIENTATION) + +_CONFIGURATION_UIMODETYPE = _descriptor.EnumDescriptor( + name='UiModeType', + full_name='aapt.pb.Configuration.UiModeType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_NORMAL', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_DESK', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_CAR', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_TELEVISION', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_APPLIANCE', index=5, number=5, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_WATCH', index=6, number=6, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_TYPE_VRHEADSET', index=7, number=7, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1858, + serialized_end=2073, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_UIMODETYPE) + +_CONFIGURATION_UIMODENIGHT = _descriptor.EnumDescriptor( + name='UiModeNight', + full_name='aapt.pb.Configuration.UiModeNight', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UI_MODE_NIGHT_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_NIGHT_NIGHT', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UI_MODE_NIGHT_NOTNIGHT', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2075, + serialized_end=2166, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_UIMODENIGHT) + +_CONFIGURATION_TOUCHSCREEN = _descriptor.EnumDescriptor( + name='Touchscreen', + full_name='aapt.pb.Configuration.Touchscreen', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='TOUCHSCREEN_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TOUCHSCREEN_NOTOUCH', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TOUCHSCREEN_STYLUS', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TOUCHSCREEN_FINGER', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2168, + serialized_end=2277, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_TOUCHSCREEN) + +_CONFIGURATION_KEYSHIDDEN = _descriptor.EnumDescriptor( + name='KeysHidden', + full_name='aapt.pb.Configuration.KeysHidden', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='KEYS_HIDDEN_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYS_HIDDEN_KEYSEXPOSED', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYS_HIDDEN_KEYSHIDDEN', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYS_HIDDEN_KEYSSOFT', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2279, + serialized_end=2397, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_KEYSHIDDEN) + +_CONFIGURATION_KEYBOARD = _descriptor.EnumDescriptor( + name='Keyboard', + full_name='aapt.pb.Configuration.Keyboard', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='KEYBOARD_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYBOARD_NOKEYS', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYBOARD_QWERTY', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='KEYBOARD_TWELVEKEY', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2399, + serialized_end=2495, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_KEYBOARD) + +_CONFIGURATION_NAVHIDDEN = _descriptor.EnumDescriptor( + name='NavHidden', + full_name='aapt.pb.Configuration.NavHidden', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NAV_HIDDEN_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAV_HIDDEN_NAVEXPOSED', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAV_HIDDEN_NAVHIDDEN', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2497, + serialized_end=2583, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_NAVHIDDEN) + +_CONFIGURATION_NAVIGATION = _descriptor.EnumDescriptor( + name='Navigation', + full_name='aapt.pb.Configuration.Navigation', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NAVIGATION_UNSET', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAVIGATION_NONAV', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAVIGATION_DPAD', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAVIGATION_TRACKBALL', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NAVIGATION_WHEEL', index=4, number=4, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2585, + serialized_end=2710, +) +_sym_db.RegisterEnumDescriptor(_CONFIGURATION_NAVIGATION) + + +_CONFIGURATION = _descriptor.Descriptor( + name='Configuration', + full_name='aapt.pb.Configuration', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mcc', full_name='aapt.pb.Configuration.mcc', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mnc', full_name='aapt.pb.Configuration.mnc', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='locale', full_name='aapt.pb.Configuration.locale', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='layout_direction', full_name='aapt.pb.Configuration.layout_direction', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_width', full_name='aapt.pb.Configuration.screen_width', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_height', full_name='aapt.pb.Configuration.screen_height', index=5, + number=6, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_width_dp', full_name='aapt.pb.Configuration.screen_width_dp', index=6, + number=7, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_height_dp', full_name='aapt.pb.Configuration.screen_height_dp', index=7, + number=8, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='smallest_screen_width_dp', full_name='aapt.pb.Configuration.smallest_screen_width_dp', index=8, + number=9, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_layout_size', full_name='aapt.pb.Configuration.screen_layout_size', index=9, + number=10, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_layout_long', full_name='aapt.pb.Configuration.screen_layout_long', index=10, + number=11, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='screen_round', full_name='aapt.pb.Configuration.screen_round', index=11, + number=12, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='wide_color_gamut', full_name='aapt.pb.Configuration.wide_color_gamut', index=12, + number=13, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='hdr', full_name='aapt.pb.Configuration.hdr', index=13, + number=14, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='orientation', full_name='aapt.pb.Configuration.orientation', index=14, + number=15, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ui_mode_type', full_name='aapt.pb.Configuration.ui_mode_type', index=15, + number=16, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ui_mode_night', full_name='aapt.pb.Configuration.ui_mode_night', index=16, + number=17, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='density', full_name='aapt.pb.Configuration.density', index=17, + number=18, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='touchscreen', full_name='aapt.pb.Configuration.touchscreen', index=18, + number=19, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='keys_hidden', full_name='aapt.pb.Configuration.keys_hidden', index=19, + number=20, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='keyboard', full_name='aapt.pb.Configuration.keyboard', index=20, + number=21, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='nav_hidden', full_name='aapt.pb.Configuration.nav_hidden', index=21, + number=22, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='navigation', full_name='aapt.pb.Configuration.navigation', index=22, + number=23, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sdk_version', full_name='aapt.pb.Configuration.sdk_version', index=23, + number=24, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='product', full_name='aapt.pb.Configuration.product', index=24, + number=25, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _CONFIGURATION_LAYOUTDIRECTION, + _CONFIGURATION_SCREENLAYOUTSIZE, + _CONFIGURATION_SCREENLAYOUTLONG, + _CONFIGURATION_SCREENROUND, + _CONFIGURATION_WIDECOLORGAMUT, + _CONFIGURATION_HDR, + _CONFIGURATION_ORIENTATION, + _CONFIGURATION_UIMODETYPE, + _CONFIGURATION_UIMODENIGHT, + _CONFIGURATION_TOUCHSCREEN, + _CONFIGURATION_KEYSHIDDEN, + _CONFIGURATION_KEYBOARD, + _CONFIGURATION_NAVHIDDEN, + _CONFIGURATION_NAVIGATION, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=61, + serialized_end=2710, +) + +_CONFIGURATION.fields_by_name['layout_direction'].enum_type = _CONFIGURATION_LAYOUTDIRECTION +_CONFIGURATION.fields_by_name['screen_layout_size'].enum_type = _CONFIGURATION_SCREENLAYOUTSIZE +_CONFIGURATION.fields_by_name['screen_layout_long'].enum_type = _CONFIGURATION_SCREENLAYOUTLONG +_CONFIGURATION.fields_by_name['screen_round'].enum_type = _CONFIGURATION_SCREENROUND +_CONFIGURATION.fields_by_name['wide_color_gamut'].enum_type = _CONFIGURATION_WIDECOLORGAMUT +_CONFIGURATION.fields_by_name['hdr'].enum_type = _CONFIGURATION_HDR +_CONFIGURATION.fields_by_name['orientation'].enum_type = _CONFIGURATION_ORIENTATION +_CONFIGURATION.fields_by_name['ui_mode_type'].enum_type = _CONFIGURATION_UIMODETYPE +_CONFIGURATION.fields_by_name['ui_mode_night'].enum_type = _CONFIGURATION_UIMODENIGHT +_CONFIGURATION.fields_by_name['touchscreen'].enum_type = _CONFIGURATION_TOUCHSCREEN +_CONFIGURATION.fields_by_name['keys_hidden'].enum_type = _CONFIGURATION_KEYSHIDDEN +_CONFIGURATION.fields_by_name['keyboard'].enum_type = _CONFIGURATION_KEYBOARD +_CONFIGURATION.fields_by_name['nav_hidden'].enum_type = _CONFIGURATION_NAVHIDDEN +_CONFIGURATION.fields_by_name['navigation'].enum_type = _CONFIGURATION_NAVIGATION +_CONFIGURATION_LAYOUTDIRECTION.containing_type = _CONFIGURATION +_CONFIGURATION_SCREENLAYOUTSIZE.containing_type = _CONFIGURATION +_CONFIGURATION_SCREENLAYOUTLONG.containing_type = _CONFIGURATION +_CONFIGURATION_SCREENROUND.containing_type = _CONFIGURATION +_CONFIGURATION_WIDECOLORGAMUT.containing_type = _CONFIGURATION +_CONFIGURATION_HDR.containing_type = _CONFIGURATION +_CONFIGURATION_ORIENTATION.containing_type = _CONFIGURATION +_CONFIGURATION_UIMODETYPE.containing_type = _CONFIGURATION +_CONFIGURATION_UIMODENIGHT.containing_type = _CONFIGURATION +_CONFIGURATION_TOUCHSCREEN.containing_type = _CONFIGURATION +_CONFIGURATION_KEYSHIDDEN.containing_type = _CONFIGURATION +_CONFIGURATION_KEYBOARD.containing_type = _CONFIGURATION +_CONFIGURATION_NAVHIDDEN.containing_type = _CONFIGURATION +_CONFIGURATION_NAVIGATION.containing_type = _CONFIGURATION +DESCRIPTOR.message_types_by_name['Configuration'] = _CONFIGURATION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Configuration = _reflection.GeneratedProtocolMessageType('Configuration', (_message.Message,), { + 'DESCRIPTOR' : _CONFIGURATION, + '__module__' : 'frameworks.base.tools.aapt2.Configuration_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Configuration) + }) +_sym_db.RegisterMessage(Configuration) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/third_party/libwebrtc/build/android/gyp/proto/README.md b/third_party/libwebrtc/build/android/gyp/proto/README.md new file mode 100644 index 0000000000..685041087a --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/proto/README.md @@ -0,0 +1,13 @@ +# Protos +These protos are generated from Resources.proto and Configuration.proto from the +Android repo. They are found in the frameworks/base/tools/aapt2/ directory. To +regenerate these if there are changes, run this command from the root of an +Android checkout: + + protoc --python_out=some_dir frameworks/base/tools/aapt2/Resources.proto \ + frameworks/base/tools/aapt2/Configuration.proto + +Then copy the resulting \*pb2.py files from some_dir here. To make sure +Resources_pb2.py is able to import Configuration_pb2.py, replace the +"from frameworks.base.tools.aapt2" portion of the import statement with +"from ." so it will instead be imported from the current directory. diff --git a/third_party/libwebrtc/build/android/gyp/proto/Resources_pb2.py b/third_party/libwebrtc/build/android/gyp/proto/Resources_pb2.py new file mode 100644 index 0000000000..3bbd7028b5 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/proto/Resources_pb2.py @@ -0,0 +1,2779 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: frameworks/base/tools/aapt2/Resources.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import Configuration_pb2 as frameworks_dot_base_dot_tools_dot_aapt2_dot_Configuration__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='frameworks/base/tools/aapt2/Resources.proto', + package='aapt.pb', + syntax='proto3', + serialized_options=_b('\n\020com.android.aapt'), + serialized_pb=_b('\n+frameworks/base/tools/aapt2/Resources.proto\x12\x07\x61\x61pt.pb\x1a/frameworks/base/tools/aapt2/Configuration.proto\"\x1a\n\nStringPool\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"<\n\x0eSourcePosition\x12\x13\n\x0bline_number\x18\x01 \x01(\r\x12\x15\n\rcolumn_number\x18\x02 \x01(\r\"E\n\x06Source\x12\x10\n\x08path_idx\x18\x01 \x01(\r\x12)\n\x08position\x18\x02 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"0\n\x0fToolFingerprint\x12\x0c\n\x04tool\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\"\xbb\x01\n\rResourceTable\x12(\n\x0bsource_pool\x18\x01 \x01(\x0b\x32\x13.aapt.pb.StringPool\x12!\n\x07package\x18\x02 \x03(\x0b\x32\x10.aapt.pb.Package\x12)\n\x0boverlayable\x18\x03 \x03(\x0b\x32\x14.aapt.pb.Overlayable\x12\x32\n\x10tool_fingerprint\x18\x04 \x03(\x0b\x32\x18.aapt.pb.ToolFingerprint\"\x17\n\tPackageId\x12\n\n\x02id\x18\x01 \x01(\r\"d\n\x07Package\x12&\n\npackage_id\x18\x01 \x01(\x0b\x32\x12.aapt.pb.PackageId\x12\x14\n\x0cpackage_name\x18\x02 \x01(\t\x12\x1b\n\x04type\x18\x03 \x03(\x0b\x32\r.aapt.pb.Type\"\x14\n\x06TypeId\x12\n\n\x02id\x18\x01 \x01(\r\"U\n\x04Type\x12 \n\x07type_id\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.TypeId\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1d\n\x05\x65ntry\x18\x03 \x03(\x0b\x32\x0e.aapt.pb.Entry\"\x97\x01\n\nVisibility\x12(\n\x05level\x18\x01 \x01(\x0e\x32\x19.aapt.pb.Visibility.Level\x12\x1f\n\x06source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x03 \x01(\t\"-\n\x05Level\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07PRIVATE\x10\x01\x12\n\n\x06PUBLIC\x10\x02\"<\n\x08\x41llowNew\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\"K\n\x0bOverlayable\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1f\n\x06source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\r\n\x05\x61\x63tor\x18\x03 \x01(\t\"\xf3\x01\n\x0fOverlayableItem\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12/\n\x06policy\x18\x03 \x03(\x0e\x32\x1f.aapt.pb.OverlayableItem.Policy\x12\x17\n\x0foverlayable_idx\x18\x04 \x01(\r\"d\n\x06Policy\x12\x08\n\x04NONE\x10\x00\x12\n\n\x06PUBLIC\x10\x01\x12\n\n\x06SYSTEM\x10\x02\x12\n\n\x06VENDOR\x10\x03\x12\x0b\n\x07PRODUCT\x10\x04\x12\r\n\tSIGNATURE\x10\x05\x12\x07\n\x03ODM\x10\x06\x12\x07\n\x03OEM\x10\x07\"\x15\n\x07\x45ntryId\x12\n\n\x02id\x18\x01 \x01(\r\"\xe8\x01\n\x05\x45ntry\x12\"\n\x08\x65ntry_id\x18\x01 \x01(\x0b\x32\x10.aapt.pb.EntryId\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\'\n\nvisibility\x18\x03 \x01(\x0b\x32\x13.aapt.pb.Visibility\x12$\n\tallow_new\x18\x04 \x01(\x0b\x32\x11.aapt.pb.AllowNew\x12\x32\n\x10overlayable_item\x18\x05 \x01(\x0b\x32\x18.aapt.pb.OverlayableItem\x12*\n\x0c\x63onfig_value\x18\x06 \x03(\x0b\x32\x14.aapt.pb.ConfigValue\"T\n\x0b\x43onfigValue\x12&\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x16.aapt.pb.Configuration\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.Value\"\xa1\x01\n\x05Value\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x0c\n\x04weak\x18\x03 \x01(\x08\x12\x1d\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.ItemH\x00\x12\x30\n\x0e\x63ompound_value\x18\x05 \x01(\x0b\x32\x16.aapt.pb.CompoundValueH\x00\x42\x07\n\x05value\"\x8d\x02\n\x04Item\x12!\n\x03ref\x18\x01 \x01(\x0b\x32\x12.aapt.pb.ReferenceH\x00\x12\x1e\n\x03str\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.StringH\x00\x12%\n\x07raw_str\x18\x03 \x01(\x0b\x32\x12.aapt.pb.RawStringH\x00\x12+\n\nstyled_str\x18\x04 \x01(\x0b\x32\x15.aapt.pb.StyledStringH\x00\x12&\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x16.aapt.pb.FileReferenceH\x00\x12\x19\n\x02id\x18\x06 \x01(\x0b\x32\x0b.aapt.pb.IdH\x00\x12\"\n\x04prim\x18\x07 \x01(\x0b\x32\x12.aapt.pb.PrimitiveH\x00\x42\x07\n\x05value\"\xca\x01\n\rCompoundValue\x12\"\n\x04\x61ttr\x18\x01 \x01(\x0b\x32\x12.aapt.pb.AttributeH\x00\x12\x1f\n\x05style\x18\x02 \x01(\x0b\x32\x0e.aapt.pb.StyleH\x00\x12\'\n\tstyleable\x18\x03 \x01(\x0b\x32\x12.aapt.pb.StyleableH\x00\x12\x1f\n\x05\x61rray\x18\x04 \x01(\x0b\x32\x0e.aapt.pb.ArrayH\x00\x12!\n\x06plural\x18\x05 \x01(\x0b\x32\x0f.aapt.pb.PluralH\x00\x42\x07\n\x05value\"\x18\n\x07\x42oolean\x12\r\n\x05value\x18\x01 \x01(\x08\"\xa9\x01\n\tReference\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.aapt.pb.Reference.Type\x12\n\n\x02id\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0f\n\x07private\x18\x04 \x01(\x08\x12$\n\nis_dynamic\x18\x05 \x01(\x0b\x32\x10.aapt.pb.Boolean\"$\n\x04Type\x12\r\n\tREFERENCE\x10\x00\x12\r\n\tATTRIBUTE\x10\x01\"\x04\n\x02Id\"\x17\n\x06String\x12\r\n\x05value\x18\x01 \x01(\t\"\x1a\n\tRawString\x12\r\n\x05value\x18\x01 \x01(\t\"\x83\x01\n\x0cStyledString\x12\r\n\x05value\x18\x01 \x01(\t\x12(\n\x04span\x18\x02 \x03(\x0b\x32\x1a.aapt.pb.StyledString.Span\x1a:\n\x04Span\x12\x0b\n\x03tag\x18\x01 \x01(\t\x12\x12\n\nfirst_char\x18\x02 \x01(\r\x12\x11\n\tlast_char\x18\x03 \x01(\r\"\x85\x01\n\rFileReference\x12\x0c\n\x04path\x18\x01 \x01(\t\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x1b.aapt.pb.FileReference.Type\";\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03PNG\x10\x01\x12\x0e\n\nBINARY_XML\x10\x02\x12\r\n\tPROTO_XML\x10\x03\"\x83\x04\n\tPrimitive\x12\x31\n\nnull_value\x18\x01 \x01(\x0b\x32\x1b.aapt.pb.Primitive.NullTypeH\x00\x12\x33\n\x0b\x65mpty_value\x18\x02 \x01(\x0b\x32\x1c.aapt.pb.Primitive.EmptyTypeH\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x19\n\x0f\x64imension_value\x18\r \x01(\rH\x00\x12\x18\n\x0e\x66raction_value\x18\x0e \x01(\rH\x00\x12\x1b\n\x11int_decimal_value\x18\x06 \x01(\x05H\x00\x12\x1f\n\x15int_hexadecimal_value\x18\x07 \x01(\rH\x00\x12\x17\n\rboolean_value\x18\x08 \x01(\x08H\x00\x12\x1b\n\x11\x63olor_argb8_value\x18\t \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb8_value\x18\n \x01(\rH\x00\x12\x1b\n\x11\x63olor_argb4_value\x18\x0b \x01(\rH\x00\x12\x1a\n\x10\x63olor_rgb4_value\x18\x0c \x01(\rH\x00\x12(\n\x1a\x64imension_value_deprecated\x18\x04 \x01(\x02\x42\x02\x18\x01H\x00\x12\'\n\x19\x66raction_value_deprecated\x18\x05 \x01(\x02\x42\x02\x18\x01H\x00\x1a\n\n\x08NullType\x1a\x0b\n\tEmptyTypeB\r\n\x0boneof_value\"\x90\x03\n\tAttribute\x12\x14\n\x0c\x66ormat_flags\x18\x01 \x01(\r\x12\x0f\n\x07min_int\x18\x02 \x01(\x05\x12\x0f\n\x07max_int\x18\x03 \x01(\x05\x12)\n\x06symbol\x18\x04 \x03(\x0b\x32\x19.aapt.pb.Attribute.Symbol\x1ay\n\x06Symbol\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04name\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\r\n\x05value\x18\x04 \x01(\r\x12\x0c\n\x04type\x18\x05 \x01(\r\"\xa4\x01\n\x0b\x46ormatFlags\x12\x08\n\x04NONE\x10\x00\x12\t\n\x03\x41NY\x10\xff\xff\x03\x12\r\n\tREFERENCE\x10\x01\x12\n\n\x06STRING\x10\x02\x12\x0b\n\x07INTEGER\x10\x04\x12\x0b\n\x07\x42OOLEAN\x10\x08\x12\t\n\x05\x43OLOR\x10\x10\x12\t\n\x05\x46LOAT\x10 \x12\r\n\tDIMENSION\x10@\x12\r\n\x08\x46RACTION\x10\x80\x01\x12\n\n\x04\x45NUM\x10\x80\x80\x04\x12\x0b\n\x05\x46LAGS\x10\x80\x80\x08\"\xf1\x01\n\x05Style\x12\"\n\x06parent\x18\x01 \x01(\x0b\x32\x12.aapt.pb.Reference\x12&\n\rparent_source\x18\x02 \x01(\x0b\x32\x0f.aapt.pb.Source\x12#\n\x05\x65ntry\x18\x03 \x03(\x0b\x32\x14.aapt.pb.Style.Entry\x1aw\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1f\n\x03key\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"\x91\x01\n\tStyleable\x12\'\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x18.aapt.pb.Styleable.Entry\x1a[\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12 \n\x04\x61ttr\x18\x03 \x01(\x0b\x32\x12.aapt.pb.Reference\"\x8a\x01\n\x05\x41rray\x12\'\n\x07\x65lement\x18\x01 \x03(\x0b\x32\x16.aapt.pb.Array.Element\x1aX\n\x07\x45lement\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12\x1b\n\x04item\x18\x03 \x01(\x0b\x32\r.aapt.pb.Item\"\xef\x01\n\x06Plural\x12$\n\x05\x65ntry\x18\x01 \x03(\x0b\x32\x15.aapt.pb.Plural.Entry\x1a|\n\x05\x45ntry\x12\x1f\n\x06source\x18\x01 \x01(\x0b\x32\x0f.aapt.pb.Source\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\x12$\n\x05\x61rity\x18\x03 \x01(\x0e\x32\x15.aapt.pb.Plural.Arity\x12\x1b\n\x04item\x18\x04 \x01(\x0b\x32\r.aapt.pb.Item\"A\n\x05\x41rity\x12\x08\n\x04ZERO\x10\x00\x12\x07\n\x03ONE\x10\x01\x12\x07\n\x03TWO\x10\x02\x12\x07\n\x03\x46\x45W\x10\x03\x12\x08\n\x04MANY\x10\x04\x12\t\n\x05OTHER\x10\x05\"r\n\x07XmlNode\x12&\n\x07\x65lement\x18\x01 \x01(\x0b\x32\x13.aapt.pb.XmlElementH\x00\x12\x0e\n\x04text\x18\x02 \x01(\tH\x00\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePositionB\x06\n\x04node\"\xb2\x01\n\nXmlElement\x12\x34\n\x15namespace_declaration\x18\x01 \x03(\x0b\x32\x15.aapt.pb.XmlNamespace\x12\x15\n\rnamespace_uri\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12(\n\tattribute\x18\x04 \x03(\x0b\x32\x15.aapt.pb.XmlAttribute\x12\x1f\n\x05\x63hild\x18\x05 \x03(\x0b\x32\x10.aapt.pb.XmlNode\"T\n\x0cXmlNamespace\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\'\n\x06source\x18\x03 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\"\xa6\x01\n\x0cXmlAttribute\x12\x15\n\rnamespace_uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\'\n\x06source\x18\x04 \x01(\x0b\x32\x17.aapt.pb.SourcePosition\x12\x13\n\x0bresource_id\x18\x05 \x01(\r\x12$\n\rcompiled_item\x18\x06 \x01(\x0b\x32\r.aapt.pb.ItemB\x12\n\x10\x63om.android.aaptb\x06proto3') + , + dependencies=[frameworks_dot_base_dot_tools_dot_aapt2_dot_Configuration__pb2.DESCRIPTOR,]) + + + +_VISIBILITY_LEVEL = _descriptor.EnumDescriptor( + name='Level', + full_name='aapt.pb.Visibility.Level', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UNKNOWN', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PRIVATE', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PUBLIC', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=849, + serialized_end=894, +) +_sym_db.RegisterEnumDescriptor(_VISIBILITY_LEVEL) + +_OVERLAYABLEITEM_POLICY = _descriptor.EnumDescriptor( + name='Policy', + full_name='aapt.pb.OverlayableItem.Policy', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PUBLIC', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SYSTEM', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VENDOR', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PRODUCT', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SIGNATURE', index=5, number=5, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ODM', index=6, number=6, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='OEM', index=7, number=7, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1179, + serialized_end=1279, +) +_sym_db.RegisterEnumDescriptor(_OVERLAYABLEITEM_POLICY) + +_REFERENCE_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='aapt.pb.Reference.Type', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='REFERENCE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ATTRIBUTE', index=1, number=1, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2426, + serialized_end=2462, +) +_sym_db.RegisterEnumDescriptor(_REFERENCE_TYPE) + +_FILEREFERENCE_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='aapt.pb.FileReference.Type', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UNKNOWN', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PNG', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BINARY_XML', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PROTO_XML', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=2732, + serialized_end=2791, +) +_sym_db.RegisterEnumDescriptor(_FILEREFERENCE_TYPE) + +_ATTRIBUTE_FORMATFLAGS = _descriptor.EnumDescriptor( + name='FormatFlags', + full_name='aapt.pb.Attribute.FormatFlags', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ANY', index=1, number=65535, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='REFERENCE', index=2, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STRING', index=3, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='INTEGER', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BOOLEAN', index=5, number=8, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='COLOR', index=6, number=16, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FLOAT', index=7, number=32, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DIMENSION', index=8, number=64, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FRACTION', index=9, number=128, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ENUM', index=10, number=65536, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FLAGS', index=11, number=131072, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=3548, + serialized_end=3712, +) +_sym_db.RegisterEnumDescriptor(_ATTRIBUTE_FORMATFLAGS) + +_PLURAL_ARITY = _descriptor.EnumDescriptor( + name='Arity', + full_name='aapt.pb.Plural.Arity', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='ZERO', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ONE', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TWO', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FEW', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MANY', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='OTHER', index=5, number=5, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=4422, + serialized_end=4487, +) +_sym_db.RegisterEnumDescriptor(_PLURAL_ARITY) + + +_STRINGPOOL = _descriptor.Descriptor( + name='StringPool', + full_name='aapt.pb.StringPool', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='data', full_name='aapt.pb.StringPool.data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=105, + serialized_end=131, +) + + +_SOURCEPOSITION = _descriptor.Descriptor( + name='SourcePosition', + full_name='aapt.pb.SourcePosition', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='line_number', full_name='aapt.pb.SourcePosition.line_number', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='column_number', full_name='aapt.pb.SourcePosition.column_number', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=133, + serialized_end=193, +) + + +_SOURCE = _descriptor.Descriptor( + name='Source', + full_name='aapt.pb.Source', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='path_idx', full_name='aapt.pb.Source.path_idx', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='position', full_name='aapt.pb.Source.position', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=195, + serialized_end=264, +) + + +_TOOLFINGERPRINT = _descriptor.Descriptor( + name='ToolFingerprint', + full_name='aapt.pb.ToolFingerprint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='tool', full_name='aapt.pb.ToolFingerprint.tool', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='version', full_name='aapt.pb.ToolFingerprint.version', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=266, + serialized_end=314, +) + + +_RESOURCETABLE = _descriptor.Descriptor( + name='ResourceTable', + full_name='aapt.pb.ResourceTable', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source_pool', full_name='aapt.pb.ResourceTable.source_pool', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='package', full_name='aapt.pb.ResourceTable.package', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='overlayable', full_name='aapt.pb.ResourceTable.overlayable', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='tool_fingerprint', full_name='aapt.pb.ResourceTable.tool_fingerprint', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=317, + serialized_end=504, +) + + +_PACKAGEID = _descriptor.Descriptor( + name='PackageId', + full_name='aapt.pb.PackageId', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='aapt.pb.PackageId.id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=506, + serialized_end=529, +) + + +_PACKAGE = _descriptor.Descriptor( + name='Package', + full_name='aapt.pb.Package', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='package_id', full_name='aapt.pb.Package.package_id', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='package_name', full_name='aapt.pb.Package.package_name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='aapt.pb.Package.type', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=531, + serialized_end=631, +) + + +_TYPEID = _descriptor.Descriptor( + name='TypeId', + full_name='aapt.pb.TypeId', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='aapt.pb.TypeId.id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=633, + serialized_end=653, +) + + +_TYPE = _descriptor.Descriptor( + name='Type', + full_name='aapt.pb.Type', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type_id', full_name='aapt.pb.Type.type_id', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.Type.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='entry', full_name='aapt.pb.Type.entry', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=655, + serialized_end=740, +) + + +_VISIBILITY = _descriptor.Descriptor( + name='Visibility', + full_name='aapt.pb.Visibility', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='level', full_name='aapt.pb.Visibility.level', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Visibility.source', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Visibility.comment', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _VISIBILITY_LEVEL, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=743, + serialized_end=894, +) + + +_ALLOWNEW = _descriptor.Descriptor( + name='AllowNew', + full_name='aapt.pb.AllowNew', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.AllowNew.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.AllowNew.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=896, + serialized_end=956, +) + + +_OVERLAYABLE = _descriptor.Descriptor( + name='Overlayable', + full_name='aapt.pb.Overlayable', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.Overlayable.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Overlayable.source', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='actor', full_name='aapt.pb.Overlayable.actor', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=958, + serialized_end=1033, +) + + +_OVERLAYABLEITEM = _descriptor.Descriptor( + name='OverlayableItem', + full_name='aapt.pb.OverlayableItem', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.OverlayableItem.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.OverlayableItem.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='policy', full_name='aapt.pb.OverlayableItem.policy', index=2, + number=3, type=14, cpp_type=8, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='overlayable_idx', full_name='aapt.pb.OverlayableItem.overlayable_idx', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _OVERLAYABLEITEM_POLICY, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1036, + serialized_end=1279, +) + + +_ENTRYID = _descriptor.Descriptor( + name='EntryId', + full_name='aapt.pb.EntryId', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='aapt.pb.EntryId.id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1281, + serialized_end=1302, +) + + +_ENTRY = _descriptor.Descriptor( + name='Entry', + full_name='aapt.pb.Entry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='entry_id', full_name='aapt.pb.Entry.entry_id', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.Entry.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='visibility', full_name='aapt.pb.Entry.visibility', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='allow_new', full_name='aapt.pb.Entry.allow_new', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='overlayable_item', full_name='aapt.pb.Entry.overlayable_item', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config_value', full_name='aapt.pb.Entry.config_value', index=5, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1305, + serialized_end=1537, +) + + +_CONFIGVALUE = _descriptor.Descriptor( + name='ConfigValue', + full_name='aapt.pb.ConfigValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='config', full_name='aapt.pb.ConfigValue.config', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.ConfigValue.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1539, + serialized_end=1623, +) + + +_VALUE = _descriptor.Descriptor( + name='Value', + full_name='aapt.pb.Value', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Value.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Value.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='weak', full_name='aapt.pb.Value.weak', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='item', full_name='aapt.pb.Value.item', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='compound_value', full_name='aapt.pb.Value.compound_value', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='aapt.pb.Value.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=1626, + serialized_end=1787, +) + + +_ITEM = _descriptor.Descriptor( + name='Item', + full_name='aapt.pb.Item', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='ref', full_name='aapt.pb.Item.ref', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='str', full_name='aapt.pb.Item.str', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='raw_str', full_name='aapt.pb.Item.raw_str', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='styled_str', full_name='aapt.pb.Item.styled_str', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='file', full_name='aapt.pb.Item.file', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='aapt.pb.Item.id', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='prim', full_name='aapt.pb.Item.prim', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='aapt.pb.Item.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=1790, + serialized_end=2059, +) + + +_COMPOUNDVALUE = _descriptor.Descriptor( + name='CompoundValue', + full_name='aapt.pb.CompoundValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='attr', full_name='aapt.pb.CompoundValue.attr', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='style', full_name='aapt.pb.CompoundValue.style', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='styleable', full_name='aapt.pb.CompoundValue.styleable', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='array', full_name='aapt.pb.CompoundValue.array', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='plural', full_name='aapt.pb.CompoundValue.plural', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='aapt.pb.CompoundValue.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=2062, + serialized_end=2264, +) + + +_BOOLEAN = _descriptor.Descriptor( + name='Boolean', + full_name='aapt.pb.Boolean', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.Boolean.value', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2266, + serialized_end=2290, +) + + +_REFERENCE = _descriptor.Descriptor( + name='Reference', + full_name='aapt.pb.Reference', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='aapt.pb.Reference.type', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='aapt.pb.Reference.id', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.Reference.name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='private', full_name='aapt.pb.Reference.private', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='is_dynamic', full_name='aapt.pb.Reference.is_dynamic', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _REFERENCE_TYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2293, + serialized_end=2462, +) + + +_ID = _descriptor.Descriptor( + name='Id', + full_name='aapt.pb.Id', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2464, + serialized_end=2468, +) + + +_STRING = _descriptor.Descriptor( + name='String', + full_name='aapt.pb.String', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.String.value', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2470, + serialized_end=2493, +) + + +_RAWSTRING = _descriptor.Descriptor( + name='RawString', + full_name='aapt.pb.RawString', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.RawString.value', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2495, + serialized_end=2521, +) + + +_STYLEDSTRING_SPAN = _descriptor.Descriptor( + name='Span', + full_name='aapt.pb.StyledString.Span', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='tag', full_name='aapt.pb.StyledString.Span.tag', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='first_char', full_name='aapt.pb.StyledString.Span.first_char', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='last_char', full_name='aapt.pb.StyledString.Span.last_char', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2597, + serialized_end=2655, +) + +_STYLEDSTRING = _descriptor.Descriptor( + name='StyledString', + full_name='aapt.pb.StyledString', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.StyledString.value', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='span', full_name='aapt.pb.StyledString.span', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_STYLEDSTRING_SPAN, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2524, + serialized_end=2655, +) + + +_FILEREFERENCE = _descriptor.Descriptor( + name='FileReference', + full_name='aapt.pb.FileReference', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='aapt.pb.FileReference.path', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='aapt.pb.FileReference.type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FILEREFERENCE_TYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2658, + serialized_end=2791, +) + + +_PRIMITIVE_NULLTYPE = _descriptor.Descriptor( + name='NullType', + full_name='aapt.pb.Primitive.NullType', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3271, + serialized_end=3281, +) + +_PRIMITIVE_EMPTYTYPE = _descriptor.Descriptor( + name='EmptyType', + full_name='aapt.pb.Primitive.EmptyType', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3283, + serialized_end=3294, +) + +_PRIMITIVE = _descriptor.Descriptor( + name='Primitive', + full_name='aapt.pb.Primitive', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='null_value', full_name='aapt.pb.Primitive.null_value', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='empty_value', full_name='aapt.pb.Primitive.empty_value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='float_value', full_name='aapt.pb.Primitive.float_value', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dimension_value', full_name='aapt.pb.Primitive.dimension_value', index=3, + number=13, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='fraction_value', full_name='aapt.pb.Primitive.fraction_value', index=4, + number=14, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='int_decimal_value', full_name='aapt.pb.Primitive.int_decimal_value', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='int_hexadecimal_value', full_name='aapt.pb.Primitive.int_hexadecimal_value', index=6, + number=7, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='boolean_value', full_name='aapt.pb.Primitive.boolean_value', index=7, + number=8, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='color_argb8_value', full_name='aapt.pb.Primitive.color_argb8_value', index=8, + number=9, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='color_rgb8_value', full_name='aapt.pb.Primitive.color_rgb8_value', index=9, + number=10, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='color_argb4_value', full_name='aapt.pb.Primitive.color_argb4_value', index=10, + number=11, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='color_rgb4_value', full_name='aapt.pb.Primitive.color_rgb4_value', index=11, + number=12, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dimension_value_deprecated', full_name='aapt.pb.Primitive.dimension_value_deprecated', index=12, + number=4, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=_b('\030\001'), file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='fraction_value_deprecated', full_name='aapt.pb.Primitive.fraction_value_deprecated', index=13, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=_b('\030\001'), file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_PRIMITIVE_NULLTYPE, _PRIMITIVE_EMPTYTYPE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='oneof_value', full_name='aapt.pb.Primitive.oneof_value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=2794, + serialized_end=3309, +) + + +_ATTRIBUTE_SYMBOL = _descriptor.Descriptor( + name='Symbol', + full_name='aapt.pb.Attribute.Symbol', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Attribute.Symbol.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Attribute.Symbol.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.Attribute.Symbol.name', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.Attribute.Symbol.value', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='aapt.pb.Attribute.Symbol.type', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3424, + serialized_end=3545, +) + +_ATTRIBUTE = _descriptor.Descriptor( + name='Attribute', + full_name='aapt.pb.Attribute', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='format_flags', full_name='aapt.pb.Attribute.format_flags', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='min_int', full_name='aapt.pb.Attribute.min_int', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='max_int', full_name='aapt.pb.Attribute.max_int', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='symbol', full_name='aapt.pb.Attribute.symbol', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_ATTRIBUTE_SYMBOL, ], + enum_types=[ + _ATTRIBUTE_FORMATFLAGS, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3312, + serialized_end=3712, +) + + +_STYLE_ENTRY = _descriptor.Descriptor( + name='Entry', + full_name='aapt.pb.Style.Entry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Style.Entry.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Style.Entry.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='key', full_name='aapt.pb.Style.Entry.key', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='item', full_name='aapt.pb.Style.Entry.item', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3837, + serialized_end=3956, +) + +_STYLE = _descriptor.Descriptor( + name='Style', + full_name='aapt.pb.Style', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parent', full_name='aapt.pb.Style.parent', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='parent_source', full_name='aapt.pb.Style.parent_source', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='entry', full_name='aapt.pb.Style.entry', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_STYLE_ENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3715, + serialized_end=3956, +) + + +_STYLEABLE_ENTRY = _descriptor.Descriptor( + name='Entry', + full_name='aapt.pb.Styleable.Entry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Styleable.Entry.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Styleable.Entry.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='attr', full_name='aapt.pb.Styleable.Entry.attr', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4013, + serialized_end=4104, +) + +_STYLEABLE = _descriptor.Descriptor( + name='Styleable', + full_name='aapt.pb.Styleable', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='entry', full_name='aapt.pb.Styleable.entry', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_STYLEABLE_ENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3959, + serialized_end=4104, +) + + +_ARRAY_ELEMENT = _descriptor.Descriptor( + name='Element', + full_name='aapt.pb.Array.Element', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Array.Element.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Array.Element.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='item', full_name='aapt.pb.Array.Element.item', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4157, + serialized_end=4245, +) + +_ARRAY = _descriptor.Descriptor( + name='Array', + full_name='aapt.pb.Array', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='element', full_name='aapt.pb.Array.element', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_ARRAY_ELEMENT, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4107, + serialized_end=4245, +) + + +_PLURAL_ENTRY = _descriptor.Descriptor( + name='Entry', + full_name='aapt.pb.Plural.Entry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.Plural.Entry.source', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='comment', full_name='aapt.pb.Plural.Entry.comment', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='arity', full_name='aapt.pb.Plural.Entry.arity', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='item', full_name='aapt.pb.Plural.Entry.item', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4296, + serialized_end=4420, +) + +_PLURAL = _descriptor.Descriptor( + name='Plural', + full_name='aapt.pb.Plural', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='entry', full_name='aapt.pb.Plural.entry', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_PLURAL_ENTRY, ], + enum_types=[ + _PLURAL_ARITY, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4248, + serialized_end=4487, +) + + +_XMLNODE = _descriptor.Descriptor( + name='XmlNode', + full_name='aapt.pb.XmlNode', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='element', full_name='aapt.pb.XmlNode.element', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='text', full_name='aapt.pb.XmlNode.text', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.XmlNode.source', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='node', full_name='aapt.pb.XmlNode.node', + index=0, containing_type=None, fields=[]), + ], + serialized_start=4489, + serialized_end=4603, +) + + +_XMLELEMENT = _descriptor.Descriptor( + name='XmlElement', + full_name='aapt.pb.XmlElement', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='namespace_declaration', full_name='aapt.pb.XmlElement.namespace_declaration', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='namespace_uri', full_name='aapt.pb.XmlElement.namespace_uri', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.XmlElement.name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='attribute', full_name='aapt.pb.XmlElement.attribute', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='child', full_name='aapt.pb.XmlElement.child', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4606, + serialized_end=4784, +) + + +_XMLNAMESPACE = _descriptor.Descriptor( + name='XmlNamespace', + full_name='aapt.pb.XmlNamespace', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prefix', full_name='aapt.pb.XmlNamespace.prefix', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='uri', full_name='aapt.pb.XmlNamespace.uri', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.XmlNamespace.source', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4786, + serialized_end=4870, +) + + +_XMLATTRIBUTE = _descriptor.Descriptor( + name='XmlAttribute', + full_name='aapt.pb.XmlAttribute', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='namespace_uri', full_name='aapt.pb.XmlAttribute.namespace_uri', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='aapt.pb.XmlAttribute.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='aapt.pb.XmlAttribute.value', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='aapt.pb.XmlAttribute.source', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resource_id', full_name='aapt.pb.XmlAttribute.resource_id', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='compiled_item', full_name='aapt.pb.XmlAttribute.compiled_item', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4873, + serialized_end=5039, +) + +_SOURCE.fields_by_name['position'].message_type = _SOURCEPOSITION +_RESOURCETABLE.fields_by_name['source_pool'].message_type = _STRINGPOOL +_RESOURCETABLE.fields_by_name['package'].message_type = _PACKAGE +_RESOURCETABLE.fields_by_name['overlayable'].message_type = _OVERLAYABLE +_RESOURCETABLE.fields_by_name['tool_fingerprint'].message_type = _TOOLFINGERPRINT +_PACKAGE.fields_by_name['package_id'].message_type = _PACKAGEID +_PACKAGE.fields_by_name['type'].message_type = _TYPE +_TYPE.fields_by_name['type_id'].message_type = _TYPEID +_TYPE.fields_by_name['entry'].message_type = _ENTRY +_VISIBILITY.fields_by_name['level'].enum_type = _VISIBILITY_LEVEL +_VISIBILITY.fields_by_name['source'].message_type = _SOURCE +_VISIBILITY_LEVEL.containing_type = _VISIBILITY +_ALLOWNEW.fields_by_name['source'].message_type = _SOURCE +_OVERLAYABLE.fields_by_name['source'].message_type = _SOURCE +_OVERLAYABLEITEM.fields_by_name['source'].message_type = _SOURCE +_OVERLAYABLEITEM.fields_by_name['policy'].enum_type = _OVERLAYABLEITEM_POLICY +_OVERLAYABLEITEM_POLICY.containing_type = _OVERLAYABLEITEM +_ENTRY.fields_by_name['entry_id'].message_type = _ENTRYID +_ENTRY.fields_by_name['visibility'].message_type = _VISIBILITY +_ENTRY.fields_by_name['allow_new'].message_type = _ALLOWNEW +_ENTRY.fields_by_name['overlayable_item'].message_type = _OVERLAYABLEITEM +_ENTRY.fields_by_name['config_value'].message_type = _CONFIGVALUE +_CONFIGVALUE.fields_by_name['config'].message_type = frameworks_dot_base_dot_tools_dot_aapt2_dot_Configuration__pb2._CONFIGURATION +_CONFIGVALUE.fields_by_name['value'].message_type = _VALUE +_VALUE.fields_by_name['source'].message_type = _SOURCE +_VALUE.fields_by_name['item'].message_type = _ITEM +_VALUE.fields_by_name['compound_value'].message_type = _COMPOUNDVALUE +_VALUE.oneofs_by_name['value'].fields.append( + _VALUE.fields_by_name['item']) +_VALUE.fields_by_name['item'].containing_oneof = _VALUE.oneofs_by_name['value'] +_VALUE.oneofs_by_name['value'].fields.append( + _VALUE.fields_by_name['compound_value']) +_VALUE.fields_by_name['compound_value'].containing_oneof = _VALUE.oneofs_by_name['value'] +_ITEM.fields_by_name['ref'].message_type = _REFERENCE +_ITEM.fields_by_name['str'].message_type = _STRING +_ITEM.fields_by_name['raw_str'].message_type = _RAWSTRING +_ITEM.fields_by_name['styled_str'].message_type = _STYLEDSTRING +_ITEM.fields_by_name['file'].message_type = _FILEREFERENCE +_ITEM.fields_by_name['id'].message_type = _ID +_ITEM.fields_by_name['prim'].message_type = _PRIMITIVE +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['ref']) +_ITEM.fields_by_name['ref'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['str']) +_ITEM.fields_by_name['str'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['raw_str']) +_ITEM.fields_by_name['raw_str'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['styled_str']) +_ITEM.fields_by_name['styled_str'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['file']) +_ITEM.fields_by_name['file'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['id']) +_ITEM.fields_by_name['id'].containing_oneof = _ITEM.oneofs_by_name['value'] +_ITEM.oneofs_by_name['value'].fields.append( + _ITEM.fields_by_name['prim']) +_ITEM.fields_by_name['prim'].containing_oneof = _ITEM.oneofs_by_name['value'] +_COMPOUNDVALUE.fields_by_name['attr'].message_type = _ATTRIBUTE +_COMPOUNDVALUE.fields_by_name['style'].message_type = _STYLE +_COMPOUNDVALUE.fields_by_name['styleable'].message_type = _STYLEABLE +_COMPOUNDVALUE.fields_by_name['array'].message_type = _ARRAY +_COMPOUNDVALUE.fields_by_name['plural'].message_type = _PLURAL +_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( + _COMPOUNDVALUE.fields_by_name['attr']) +_COMPOUNDVALUE.fields_by_name['attr'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] +_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( + _COMPOUNDVALUE.fields_by_name['style']) +_COMPOUNDVALUE.fields_by_name['style'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] +_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( + _COMPOUNDVALUE.fields_by_name['styleable']) +_COMPOUNDVALUE.fields_by_name['styleable'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] +_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( + _COMPOUNDVALUE.fields_by_name['array']) +_COMPOUNDVALUE.fields_by_name['array'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] +_COMPOUNDVALUE.oneofs_by_name['value'].fields.append( + _COMPOUNDVALUE.fields_by_name['plural']) +_COMPOUNDVALUE.fields_by_name['plural'].containing_oneof = _COMPOUNDVALUE.oneofs_by_name['value'] +_REFERENCE.fields_by_name['type'].enum_type = _REFERENCE_TYPE +_REFERENCE.fields_by_name['is_dynamic'].message_type = _BOOLEAN +_REFERENCE_TYPE.containing_type = _REFERENCE +_STYLEDSTRING_SPAN.containing_type = _STYLEDSTRING +_STYLEDSTRING.fields_by_name['span'].message_type = _STYLEDSTRING_SPAN +_FILEREFERENCE.fields_by_name['type'].enum_type = _FILEREFERENCE_TYPE +_FILEREFERENCE_TYPE.containing_type = _FILEREFERENCE +_PRIMITIVE_NULLTYPE.containing_type = _PRIMITIVE +_PRIMITIVE_EMPTYTYPE.containing_type = _PRIMITIVE +_PRIMITIVE.fields_by_name['null_value'].message_type = _PRIMITIVE_NULLTYPE +_PRIMITIVE.fields_by_name['empty_value'].message_type = _PRIMITIVE_EMPTYTYPE +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['null_value']) +_PRIMITIVE.fields_by_name['null_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['empty_value']) +_PRIMITIVE.fields_by_name['empty_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['float_value']) +_PRIMITIVE.fields_by_name['float_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['dimension_value']) +_PRIMITIVE.fields_by_name['dimension_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['fraction_value']) +_PRIMITIVE.fields_by_name['fraction_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['int_decimal_value']) +_PRIMITIVE.fields_by_name['int_decimal_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['int_hexadecimal_value']) +_PRIMITIVE.fields_by_name['int_hexadecimal_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['boolean_value']) +_PRIMITIVE.fields_by_name['boolean_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['color_argb8_value']) +_PRIMITIVE.fields_by_name['color_argb8_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['color_rgb8_value']) +_PRIMITIVE.fields_by_name['color_rgb8_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['color_argb4_value']) +_PRIMITIVE.fields_by_name['color_argb4_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['color_rgb4_value']) +_PRIMITIVE.fields_by_name['color_rgb4_value'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['dimension_value_deprecated']) +_PRIMITIVE.fields_by_name['dimension_value_deprecated'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_PRIMITIVE.oneofs_by_name['oneof_value'].fields.append( + _PRIMITIVE.fields_by_name['fraction_value_deprecated']) +_PRIMITIVE.fields_by_name['fraction_value_deprecated'].containing_oneof = _PRIMITIVE.oneofs_by_name['oneof_value'] +_ATTRIBUTE_SYMBOL.fields_by_name['source'].message_type = _SOURCE +_ATTRIBUTE_SYMBOL.fields_by_name['name'].message_type = _REFERENCE +_ATTRIBUTE_SYMBOL.containing_type = _ATTRIBUTE +_ATTRIBUTE.fields_by_name['symbol'].message_type = _ATTRIBUTE_SYMBOL +_ATTRIBUTE_FORMATFLAGS.containing_type = _ATTRIBUTE +_STYLE_ENTRY.fields_by_name['source'].message_type = _SOURCE +_STYLE_ENTRY.fields_by_name['key'].message_type = _REFERENCE +_STYLE_ENTRY.fields_by_name['item'].message_type = _ITEM +_STYLE_ENTRY.containing_type = _STYLE +_STYLE.fields_by_name['parent'].message_type = _REFERENCE +_STYLE.fields_by_name['parent_source'].message_type = _SOURCE +_STYLE.fields_by_name['entry'].message_type = _STYLE_ENTRY +_STYLEABLE_ENTRY.fields_by_name['source'].message_type = _SOURCE +_STYLEABLE_ENTRY.fields_by_name['attr'].message_type = _REFERENCE +_STYLEABLE_ENTRY.containing_type = _STYLEABLE +_STYLEABLE.fields_by_name['entry'].message_type = _STYLEABLE_ENTRY +_ARRAY_ELEMENT.fields_by_name['source'].message_type = _SOURCE +_ARRAY_ELEMENT.fields_by_name['item'].message_type = _ITEM +_ARRAY_ELEMENT.containing_type = _ARRAY +_ARRAY.fields_by_name['element'].message_type = _ARRAY_ELEMENT +_PLURAL_ENTRY.fields_by_name['source'].message_type = _SOURCE +_PLURAL_ENTRY.fields_by_name['arity'].enum_type = _PLURAL_ARITY +_PLURAL_ENTRY.fields_by_name['item'].message_type = _ITEM +_PLURAL_ENTRY.containing_type = _PLURAL +_PLURAL.fields_by_name['entry'].message_type = _PLURAL_ENTRY +_PLURAL_ARITY.containing_type = _PLURAL +_XMLNODE.fields_by_name['element'].message_type = _XMLELEMENT +_XMLNODE.fields_by_name['source'].message_type = _SOURCEPOSITION +_XMLNODE.oneofs_by_name['node'].fields.append( + _XMLNODE.fields_by_name['element']) +_XMLNODE.fields_by_name['element'].containing_oneof = _XMLNODE.oneofs_by_name['node'] +_XMLNODE.oneofs_by_name['node'].fields.append( + _XMLNODE.fields_by_name['text']) +_XMLNODE.fields_by_name['text'].containing_oneof = _XMLNODE.oneofs_by_name['node'] +_XMLELEMENT.fields_by_name['namespace_declaration'].message_type = _XMLNAMESPACE +_XMLELEMENT.fields_by_name['attribute'].message_type = _XMLATTRIBUTE +_XMLELEMENT.fields_by_name['child'].message_type = _XMLNODE +_XMLNAMESPACE.fields_by_name['source'].message_type = _SOURCEPOSITION +_XMLATTRIBUTE.fields_by_name['source'].message_type = _SOURCEPOSITION +_XMLATTRIBUTE.fields_by_name['compiled_item'].message_type = _ITEM +DESCRIPTOR.message_types_by_name['StringPool'] = _STRINGPOOL +DESCRIPTOR.message_types_by_name['SourcePosition'] = _SOURCEPOSITION +DESCRIPTOR.message_types_by_name['Source'] = _SOURCE +DESCRIPTOR.message_types_by_name['ToolFingerprint'] = _TOOLFINGERPRINT +DESCRIPTOR.message_types_by_name['ResourceTable'] = _RESOURCETABLE +DESCRIPTOR.message_types_by_name['PackageId'] = _PACKAGEID +DESCRIPTOR.message_types_by_name['Package'] = _PACKAGE +DESCRIPTOR.message_types_by_name['TypeId'] = _TYPEID +DESCRIPTOR.message_types_by_name['Type'] = _TYPE +DESCRIPTOR.message_types_by_name['Visibility'] = _VISIBILITY +DESCRIPTOR.message_types_by_name['AllowNew'] = _ALLOWNEW +DESCRIPTOR.message_types_by_name['Overlayable'] = _OVERLAYABLE +DESCRIPTOR.message_types_by_name['OverlayableItem'] = _OVERLAYABLEITEM +DESCRIPTOR.message_types_by_name['EntryId'] = _ENTRYID +DESCRIPTOR.message_types_by_name['Entry'] = _ENTRY +DESCRIPTOR.message_types_by_name['ConfigValue'] = _CONFIGVALUE +DESCRIPTOR.message_types_by_name['Value'] = _VALUE +DESCRIPTOR.message_types_by_name['Item'] = _ITEM +DESCRIPTOR.message_types_by_name['CompoundValue'] = _COMPOUNDVALUE +DESCRIPTOR.message_types_by_name['Boolean'] = _BOOLEAN +DESCRIPTOR.message_types_by_name['Reference'] = _REFERENCE +DESCRIPTOR.message_types_by_name['Id'] = _ID +DESCRIPTOR.message_types_by_name['String'] = _STRING +DESCRIPTOR.message_types_by_name['RawString'] = _RAWSTRING +DESCRIPTOR.message_types_by_name['StyledString'] = _STYLEDSTRING +DESCRIPTOR.message_types_by_name['FileReference'] = _FILEREFERENCE +DESCRIPTOR.message_types_by_name['Primitive'] = _PRIMITIVE +DESCRIPTOR.message_types_by_name['Attribute'] = _ATTRIBUTE +DESCRIPTOR.message_types_by_name['Style'] = _STYLE +DESCRIPTOR.message_types_by_name['Styleable'] = _STYLEABLE +DESCRIPTOR.message_types_by_name['Array'] = _ARRAY +DESCRIPTOR.message_types_by_name['Plural'] = _PLURAL +DESCRIPTOR.message_types_by_name['XmlNode'] = _XMLNODE +DESCRIPTOR.message_types_by_name['XmlElement'] = _XMLELEMENT +DESCRIPTOR.message_types_by_name['XmlNamespace'] = _XMLNAMESPACE +DESCRIPTOR.message_types_by_name['XmlAttribute'] = _XMLATTRIBUTE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +StringPool = _reflection.GeneratedProtocolMessageType('StringPool', (_message.Message,), { + 'DESCRIPTOR' : _STRINGPOOL, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.StringPool) + }) +_sym_db.RegisterMessage(StringPool) + +SourcePosition = _reflection.GeneratedProtocolMessageType('SourcePosition', (_message.Message,), { + 'DESCRIPTOR' : _SOURCEPOSITION, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.SourcePosition) + }) +_sym_db.RegisterMessage(SourcePosition) + +Source = _reflection.GeneratedProtocolMessageType('Source', (_message.Message,), { + 'DESCRIPTOR' : _SOURCE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Source) + }) +_sym_db.RegisterMessage(Source) + +ToolFingerprint = _reflection.GeneratedProtocolMessageType('ToolFingerprint', (_message.Message,), { + 'DESCRIPTOR' : _TOOLFINGERPRINT, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.ToolFingerprint) + }) +_sym_db.RegisterMessage(ToolFingerprint) + +ResourceTable = _reflection.GeneratedProtocolMessageType('ResourceTable', (_message.Message,), { + 'DESCRIPTOR' : _RESOURCETABLE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.ResourceTable) + }) +_sym_db.RegisterMessage(ResourceTable) + +PackageId = _reflection.GeneratedProtocolMessageType('PackageId', (_message.Message,), { + 'DESCRIPTOR' : _PACKAGEID, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.PackageId) + }) +_sym_db.RegisterMessage(PackageId) + +Package = _reflection.GeneratedProtocolMessageType('Package', (_message.Message,), { + 'DESCRIPTOR' : _PACKAGE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Package) + }) +_sym_db.RegisterMessage(Package) + +TypeId = _reflection.GeneratedProtocolMessageType('TypeId', (_message.Message,), { + 'DESCRIPTOR' : _TYPEID, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.TypeId) + }) +_sym_db.RegisterMessage(TypeId) + +Type = _reflection.GeneratedProtocolMessageType('Type', (_message.Message,), { + 'DESCRIPTOR' : _TYPE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Type) + }) +_sym_db.RegisterMessage(Type) + +Visibility = _reflection.GeneratedProtocolMessageType('Visibility', (_message.Message,), { + 'DESCRIPTOR' : _VISIBILITY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Visibility) + }) +_sym_db.RegisterMessage(Visibility) + +AllowNew = _reflection.GeneratedProtocolMessageType('AllowNew', (_message.Message,), { + 'DESCRIPTOR' : _ALLOWNEW, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.AllowNew) + }) +_sym_db.RegisterMessage(AllowNew) + +Overlayable = _reflection.GeneratedProtocolMessageType('Overlayable', (_message.Message,), { + 'DESCRIPTOR' : _OVERLAYABLE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Overlayable) + }) +_sym_db.RegisterMessage(Overlayable) + +OverlayableItem = _reflection.GeneratedProtocolMessageType('OverlayableItem', (_message.Message,), { + 'DESCRIPTOR' : _OVERLAYABLEITEM, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.OverlayableItem) + }) +_sym_db.RegisterMessage(OverlayableItem) + +EntryId = _reflection.GeneratedProtocolMessageType('EntryId', (_message.Message,), { + 'DESCRIPTOR' : _ENTRYID, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.EntryId) + }) +_sym_db.RegisterMessage(EntryId) + +Entry = _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { + 'DESCRIPTOR' : _ENTRY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Entry) + }) +_sym_db.RegisterMessage(Entry) + +ConfigValue = _reflection.GeneratedProtocolMessageType('ConfigValue', (_message.Message,), { + 'DESCRIPTOR' : _CONFIGVALUE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.ConfigValue) + }) +_sym_db.RegisterMessage(ConfigValue) + +Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), { + 'DESCRIPTOR' : _VALUE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Value) + }) +_sym_db.RegisterMessage(Value) + +Item = _reflection.GeneratedProtocolMessageType('Item', (_message.Message,), { + 'DESCRIPTOR' : _ITEM, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Item) + }) +_sym_db.RegisterMessage(Item) + +CompoundValue = _reflection.GeneratedProtocolMessageType('CompoundValue', (_message.Message,), { + 'DESCRIPTOR' : _COMPOUNDVALUE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.CompoundValue) + }) +_sym_db.RegisterMessage(CompoundValue) + +Boolean = _reflection.GeneratedProtocolMessageType('Boolean', (_message.Message,), { + 'DESCRIPTOR' : _BOOLEAN, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Boolean) + }) +_sym_db.RegisterMessage(Boolean) + +Reference = _reflection.GeneratedProtocolMessageType('Reference', (_message.Message,), { + 'DESCRIPTOR' : _REFERENCE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Reference) + }) +_sym_db.RegisterMessage(Reference) + +Id = _reflection.GeneratedProtocolMessageType('Id', (_message.Message,), { + 'DESCRIPTOR' : _ID, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Id) + }) +_sym_db.RegisterMessage(Id) + +String = _reflection.GeneratedProtocolMessageType('String', (_message.Message,), { + 'DESCRIPTOR' : _STRING, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.String) + }) +_sym_db.RegisterMessage(String) + +RawString = _reflection.GeneratedProtocolMessageType('RawString', (_message.Message,), { + 'DESCRIPTOR' : _RAWSTRING, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.RawString) + }) +_sym_db.RegisterMessage(RawString) + +StyledString = _reflection.GeneratedProtocolMessageType('StyledString', (_message.Message,), { + + 'Span' : _reflection.GeneratedProtocolMessageType('Span', (_message.Message,), { + 'DESCRIPTOR' : _STYLEDSTRING_SPAN, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.StyledString.Span) + }) + , + 'DESCRIPTOR' : _STYLEDSTRING, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.StyledString) + }) +_sym_db.RegisterMessage(StyledString) +_sym_db.RegisterMessage(StyledString.Span) + +FileReference = _reflection.GeneratedProtocolMessageType('FileReference', (_message.Message,), { + 'DESCRIPTOR' : _FILEREFERENCE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.FileReference) + }) +_sym_db.RegisterMessage(FileReference) + +Primitive = _reflection.GeneratedProtocolMessageType('Primitive', (_message.Message,), { + + 'NullType' : _reflection.GeneratedProtocolMessageType('NullType', (_message.Message,), { + 'DESCRIPTOR' : _PRIMITIVE_NULLTYPE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Primitive.NullType) + }) + , + + 'EmptyType' : _reflection.GeneratedProtocolMessageType('EmptyType', (_message.Message,), { + 'DESCRIPTOR' : _PRIMITIVE_EMPTYTYPE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Primitive.EmptyType) + }) + , + 'DESCRIPTOR' : _PRIMITIVE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Primitive) + }) +_sym_db.RegisterMessage(Primitive) +_sym_db.RegisterMessage(Primitive.NullType) +_sym_db.RegisterMessage(Primitive.EmptyType) + +Attribute = _reflection.GeneratedProtocolMessageType('Attribute', (_message.Message,), { + + 'Symbol' : _reflection.GeneratedProtocolMessageType('Symbol', (_message.Message,), { + 'DESCRIPTOR' : _ATTRIBUTE_SYMBOL, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Attribute.Symbol) + }) + , + 'DESCRIPTOR' : _ATTRIBUTE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Attribute) + }) +_sym_db.RegisterMessage(Attribute) +_sym_db.RegisterMessage(Attribute.Symbol) + +Style = _reflection.GeneratedProtocolMessageType('Style', (_message.Message,), { + + 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { + 'DESCRIPTOR' : _STYLE_ENTRY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Style.Entry) + }) + , + 'DESCRIPTOR' : _STYLE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Style) + }) +_sym_db.RegisterMessage(Style) +_sym_db.RegisterMessage(Style.Entry) + +Styleable = _reflection.GeneratedProtocolMessageType('Styleable', (_message.Message,), { + + 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { + 'DESCRIPTOR' : _STYLEABLE_ENTRY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Styleable.Entry) + }) + , + 'DESCRIPTOR' : _STYLEABLE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Styleable) + }) +_sym_db.RegisterMessage(Styleable) +_sym_db.RegisterMessage(Styleable.Entry) + +Array = _reflection.GeneratedProtocolMessageType('Array', (_message.Message,), { + + 'Element' : _reflection.GeneratedProtocolMessageType('Element', (_message.Message,), { + 'DESCRIPTOR' : _ARRAY_ELEMENT, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Array.Element) + }) + , + 'DESCRIPTOR' : _ARRAY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Array) + }) +_sym_db.RegisterMessage(Array) +_sym_db.RegisterMessage(Array.Element) + +Plural = _reflection.GeneratedProtocolMessageType('Plural', (_message.Message,), { + + 'Entry' : _reflection.GeneratedProtocolMessageType('Entry', (_message.Message,), { + 'DESCRIPTOR' : _PLURAL_ENTRY, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Plural.Entry) + }) + , + 'DESCRIPTOR' : _PLURAL, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.Plural) + }) +_sym_db.RegisterMessage(Plural) +_sym_db.RegisterMessage(Plural.Entry) + +XmlNode = _reflection.GeneratedProtocolMessageType('XmlNode', (_message.Message,), { + 'DESCRIPTOR' : _XMLNODE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.XmlNode) + }) +_sym_db.RegisterMessage(XmlNode) + +XmlElement = _reflection.GeneratedProtocolMessageType('XmlElement', (_message.Message,), { + 'DESCRIPTOR' : _XMLELEMENT, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.XmlElement) + }) +_sym_db.RegisterMessage(XmlElement) + +XmlNamespace = _reflection.GeneratedProtocolMessageType('XmlNamespace', (_message.Message,), { + 'DESCRIPTOR' : _XMLNAMESPACE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.XmlNamespace) + }) +_sym_db.RegisterMessage(XmlNamespace) + +XmlAttribute = _reflection.GeneratedProtocolMessageType('XmlAttribute', (_message.Message,), { + 'DESCRIPTOR' : _XMLATTRIBUTE, + '__module__' : 'frameworks.base.tools.aapt2.Resources_pb2' + # @@protoc_insertion_point(class_scope:aapt.pb.XmlAttribute) + }) +_sym_db.RegisterMessage(XmlAttribute) + + +DESCRIPTOR._options = None +_PRIMITIVE.fields_by_name['dimension_value_deprecated']._options = None +_PRIMITIVE.fields_by_name['fraction_value_deprecated']._options = None +# @@protoc_insertion_point(module_scope) diff --git a/third_party/libwebrtc/build/android/gyp/proto/__init__.py b/third_party/libwebrtc/build/android/gyp/proto/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/android/gyp/test/BUILD.gn b/third_party/libwebrtc/build/android/gyp/test/BUILD.gn new file mode 100644 index 0000000000..301a220d03 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/test/BUILD.gn @@ -0,0 +1,11 @@ +import("//build/config/android/rules.gni") + +java_library("hello_world_java") { + sources = [ "java/org/chromium/helloworld/HelloWorldPrinter.java" ] +} + +java_binary("hello_world") { + deps = [ ":hello_world_java" ] + sources = [ "java/org/chromium/helloworld/HelloWorldMain.java" ] + main_class = "org.chromium.helloworld.HelloWorldMain" +} diff --git a/third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java b/third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldMain.java new file mode 100644 index 0000000000..10860d8332 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java b/third_party/libwebrtc/build/android/gyp/test/java/org/chromium/helloworld/HelloWorldPrinter.java new file mode 100644 index 0000000000..b09673e21f --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/gyp/turbine.py b/third_party/libwebrtc/build/android/gyp/turbine.py new file mode 100755 index 0000000000..074550e047 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/turbine.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Wraps the turbine jar and expands @FileArgs.""" + +import argparse +import functools +import logging +import os +import shutil +import sys +import time + +import javac_output_processor +from util import build_utils + + +def ProcessJavacOutput(output, target_name): + output_processor = javac_output_processor.JavacOutputProcessor(target_name) + lines = output_processor.Process(output.split('\n')) + return '\n'.join(lines) + + +def main(argv): + build_utils.InitLogging('TURBINE_DEBUG') + argv = build_utils.ExpandFileArgs(argv[1:]) + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--target-name', help='Fully qualified GN target name.') + parser.add_argument( + '--turbine-jar-path', required=True, help='Path to the turbine jar file.') + parser.add_argument( + '--java-srcjars', + action='append', + default=[], + help='List of srcjars to include in compilation.') + parser.add_argument( + '--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_argument( + '--java-version', + help='Java language version to use in -source and -target args to javac.') + parser.add_argument('--classpath', action='append', help='Classpath to use.') + parser.add_argument( + '--processors', + action='append', + help='GN list of annotation processor main classes.') + parser.add_argument( + '--processorpath', + action='append', + help='GN list of jars that comprise the classpath used for Annotation ' + 'Processors.') + parser.add_argument( + '--processor-args', + action='append', + help='key=value arguments for the annotation processors.') + parser.add_argument('--jar-path', help='Jar output path.', required=True) + parser.add_argument( + '--generated-jar-path', + required=True, + help='Output path for generated source files.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + options, unknown_args = parser.parse_known_args(argv) + + options.bootclasspath = build_utils.ParseGnList(options.bootclasspath) + options.classpath = build_utils.ParseGnList(options.classpath) + options.processorpath = build_utils.ParseGnList(options.processorpath) + options.processors = build_utils.ParseGnList(options.processors) + options.java_srcjars = build_utils.ParseGnList(options.java_srcjars) + + files = [] + for arg in unknown_args: + # Interpret a path prefixed with @ as a file containing a list of sources. + if arg.startswith('@'): + files.extend(build_utils.ReadSourcesList(arg[1:])) + + cmd = build_utils.JavaCmd(options.warnings_as_errors) + [ + '-classpath', options.turbine_jar_path, 'com.google.turbine.main.Main' + ] + javac_cmd = [] + + # Turbine reads lists from command line args by consuming args until one + # starts with double dash (--). Thus command line args should be grouped + # together and passed in together. + if options.processors: + cmd += ['--processors'] + cmd += options.processors + + if options.java_version: + javac_cmd.extend([ + '-source', + options.java_version, + '-target', + options.java_version, + ]) + if options.java_version == '1.8': + # Android's boot jar doesn't contain all java 8 classes. + options.bootclasspath.append(build_utils.RT_JAR_PATH) + + if options.bootclasspath: + cmd += ['--bootclasspath'] + for bootclasspath in options.bootclasspath: + cmd += bootclasspath.split(':') + + if options.processorpath: + cmd += ['--processorpath'] + cmd += options.processorpath + + if options.processor_args: + for arg in options.processor_args: + javac_cmd.extend(['-A%s' % arg]) + + if options.classpath: + cmd += ['--classpath'] + cmd += options.classpath + + if options.java_srcjars: + cmd += ['--source_jars'] + cmd += options.java_srcjars + + if files: + # Use jar_path to ensure paths are relative (needed for goma). + files_rsp_path = options.jar_path + '.files_list.txt' + with open(files_rsp_path, 'w') as f: + f.write(' '.join(files)) + # Pass source paths as response files to avoid extremely long command lines + # that are tedius to debug. + cmd += ['--sources'] + cmd += ['@' + files_rsp_path] + + if javac_cmd: + cmd.append('--javacopts') + cmd += javac_cmd + cmd.append('--') # Terminate javacopts + + # Use AtomicOutput so that output timestamps are not updated when outputs + # are not changed. + with build_utils.AtomicOutput(options.jar_path) as output_jar, \ + build_utils.AtomicOutput(options.generated_jar_path) as generated_jar: + cmd += ['--output', output_jar.name, '--gensrc_output', generated_jar.name] + + process_javac_output_partial = functools.partial( + ProcessJavacOutput, target_name=options.target_name) + + logging.debug('Command: %s', cmd) + start = time.time() + build_utils.CheckOutput(cmd, + print_stdout=True, + stdout_filter=process_javac_output_partial, + stderr_filter=process_javac_output_partial, + fail_on_output=options.warnings_as_errors) + end = time.time() - start + logging.info('Header compilation took %ss', end) + + if options.depfile: + # GN already knows of the java files, so avoid listing individual java files + # in the depfile. + depfile_deps = (options.bootclasspath + options.classpath + + options.processorpath + options.java_srcjars) + build_utils.WriteDepfile(options.depfile, options.jar_path, depfile_deps) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/third_party/libwebrtc/build/android/gyp/turbine.pydeps b/third_party/libwebrtc/build/android/gyp/turbine.pydeps new file mode 100644 index 0000000000..f0b2411e58 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/turbine.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/turbine.pydeps build/android/gyp/turbine.py +../../gn_helpers.py +turbine.py +util/__init__.py +util/build_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/unused_resources.py b/third_party/libwebrtc/build/android/gyp/unused_resources.py new file mode 100755 index 0000000000..cdaf4cf5b1 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/unused_resources.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# Copyright (c) 2021 The Chromium 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 sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from util import build_utils +from util import resource_utils + + +def main(args): + parser = argparse.ArgumentParser() + + build_utils.AddDepfileOption(parser) + parser.add_argument('--script', + required=True, + help='Path to the unused resources detector script.') + parser.add_argument( + '--dependencies-res-zips', + required=True, + action='append', + help='Resources zip archives to investigate for unused resources.') + parser.add_argument('--dexes', + action='append', + required=True, + help='Path to dex file, or zip with dex files.') + parser.add_argument( + '--proguard-mapping', + help='Path to proguard mapping file for the optimized dex.') + parser.add_argument('--r-text', required=True, help='Path to R.txt') + parser.add_argument('--android-manifests', + action='append', + required=True, + help='Path to AndroidManifest') + parser.add_argument('--output-config', + required=True, + help='Path to output the aapt2 config to.') + args = build_utils.ExpandFileArgs(args) + options = parser.parse_args(args) + options.dependencies_res_zips = (build_utils.ParseGnList( + options.dependencies_res_zips)) + + # in case of no resources, short circuit early. + if not options.dependencies_res_zips: + build_utils.Touch(options.output_config) + return + + with build_utils.TempDir() as temp_dir: + dep_subdirs = [] + for dependency_res_zip in options.dependencies_res_zips: + dep_subdirs += resource_utils.ExtractDeps([dependency_res_zip], temp_dir) + + cmd = [ + options.script, + '--rtxts', + options.r_text, + '--manifests', + ':'.join(options.android_manifests), + '--resourceDirs', + ':'.join(dep_subdirs), + '--dexes', + ':'.join(options.dexes), + '--outputConfig', + options.output_config, + ] + if options.proguard_mapping: + cmd += [ + '--mapping', + options.proguard_mapping, + ] + build_utils.CheckOutput(cmd) + + if options.depfile: + depfile_deps = (options.dependencies_res_zips + options.android_manifests + + options.dexes) + [options.r_text] + if options.proguard_mapping: + depfile_deps.append(options.proguard_mapping) + build_utils.WriteDepfile(options.depfile, options.output_config, + depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/unused_resources.pydeps b/third_party/libwebrtc/build/android/gyp/unused_resources.pydeps new file mode 100644 index 0000000000..b821d70614 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/unused_resources.pydeps @@ -0,0 +1,31 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/unused_resources.pydeps build/android/gyp/unused_resources.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +unused_resources.py +util/__init__.py +util/build_utils.py +util/resource_utils.py diff --git a/third_party/libwebrtc/build/android/gyp/util/__init__.py b/third_party/libwebrtc/build/android/gyp/util/__init__.py new file mode 100644 index 0000000000..96196cffb2 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/__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/third_party/libwebrtc/build/android/gyp/util/build_utils.py b/third_party/libwebrtc/build/android/gyp/util/build_utils.py new file mode 100644 index 0000000000..6469f762cc --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/build_utils.py @@ -0,0 +1,725 @@ +# 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. + +"""Contains common helpers for GN action()s.""" + +import atexit +import collections +import contextlib +import filecmp +import fnmatch +import json +import logging +import os +import pipes +import re +import shutil +import stat +import subprocess +import sys +import tempfile +import time +import zipfile + +sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir)) +import gn_helpers + +# Use relative paths to improved hermetic property of build scripts. +DIR_SOURCE_ROOT = os.path.relpath( + os.environ.get( + 'CHECKOUT_SOURCE_ROOT', + os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, + os.pardir))) +JAVA_HOME = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current') +JAVAC_PATH = os.path.join(JAVA_HOME, 'bin', 'javac') +JAVAP_PATH = os.path.join(JAVA_HOME, 'bin', 'javap') +RT_JAR_PATH = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'extras', + 'java_8', 'jre', 'lib', 'rt.jar') + +try: + string_types = basestring +except NameError: + string_types = (str, bytes) + + +def JavaCmd(verify=True, xmx='1G'): + ret = [os.path.join(JAVA_HOME, 'bin', 'java')] + # Limit heap to avoid Java not GC'ing when it should, and causing + # bots to OOM when many java commands are runnig at the same time + # https://crbug.com/1098333 + ret += ['-Xmx' + xmx] + + # Disable bytecode verification for local builds gives a ~2% speed-up. + if not verify: + ret += ['-noverify'] + + return ret + + +@contextlib.contextmanager +def TempDir(**kwargs): + dirname = tempfile.mkdtemp(**kwargs) + 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 ParseGnList(value): + """Converts a "GN-list" command-line parameter into a list. + + Conversions handled: + * None -> [] + * '' -> [] + * 'asdf' -> ['asdf'] + * '["a", "b"]' -> ['a', 'b'] + * ['["a", "b"]', 'c'] -> ['a', 'b', 'c'] (flattened list) + + The common use for this behavior is in the Android build where things can + take lists of @FileArg references that are expanded via ExpandFileArgs. + """ + # Convert None to []. + if not value: + return [] + # Convert a list of GN lists to a flattened list. + if isinstance(value, list): + ret = [] + for arg in value: + ret.extend(ParseGnList(arg)) + return ret + # Convert normal GN list. + if value.startswith('['): + return gn_helpers.GNValueParser(value).ParseList() + # Convert a single string value to a list. + return [value] + + +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) + + +@contextlib.contextmanager +def AtomicOutput(path, only_if_changed=True, mode='w+b'): + """Helper to prevent half-written outputs. + + Args: + path: Path to the final output file, which will be written atomically. + only_if_changed: If True (the default), do not touch the filesystem + if the content has not changed. + mode: The mode to open the file in (str). + Returns: + A python context manager that yelds a NamedTemporaryFile instance + that must be used by clients to write the data to. On exit, the + manager will try to replace the final output file with the + temporary one if necessary. The temporary file is always destroyed + on exit. + Example: + with build_utils.AtomicOutput(output_path) as tmp_file: + subprocess.check_call(['prog', '--output', tmp_file.name]) + """ + # Create in same directory to ensure same filesystem when moving. + dirname = os.path.dirname(path) + if not os.path.exists(dirname): + MakeDirectory(dirname) + with tempfile.NamedTemporaryFile( + mode, suffix=os.path.basename(path), dir=dirname, delete=False) as f: + try: + yield f + + # file should be closed before comparison/move. + f.close() + if not (only_if_changed and os.path.exists(path) and + filecmp.cmp(f.name, path)): + shutil.move(f.name, path) + finally: + if os.path.exists(f.name): + os.unlink(f.name) + + +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) + + +def FilterLines(output, filter_string): + """Output filter from build_utils.CheckOutput. + + Args: + output: Executable output as from build_utils.CheckOutput. + filter_string: An RE string that will filter (remove) matching + lines from |output|. + + Returns: + The filtered output, as a single string. + """ + re_filter = re.compile(filter_string) + return '\n'.join( + line for line in output.split('\n') if not re_filter.search(line)) + + +def FilterReflectiveAccessJavaWarnings(output): + """Filters out warnings about illegal reflective access operation. + + These warnings were introduced in Java 9, and generally mean that dependencies + need to be updated. + """ + # WARNING: An illegal reflective access operation has occurred + # WARNING: Illegal reflective access by ... + # WARNING: Please consider reporting this to the maintainers of ... + # WARNING: Use --illegal-access=warn to enable warnings of further ... + # WARNING: All illegal access operations will be denied in a future release + return FilterLines( + output, r'WARNING: (' + 'An illegal reflective|' + 'Illegal reflective access|' + 'Please consider reporting this to|' + 'Use --illegal-access=warn|' + 'All illegal access operations)') + + +# 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, + env=None, + print_stdout=False, + print_stderr=True, + stdout_filter=None, + stderr_filter=None, + fail_on_output=True, + fail_func=lambda returncode, stderr: returncode != 0): + if not cwd: + cwd = os.getcwd() + + logging.info('CheckOutput: %s', ' '.join(args)) + child = subprocess.Popen(args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + stdout, stderr = child.communicate() + + # For Python3 only: + if isinstance(stdout, bytes) and sys.version_info >= (3, ): + stdout = stdout.decode('utf-8') + stderr = stderr.decode('utf-8') + + if stdout_filter is not None: + stdout = stdout_filter(stdout) + + if stderr_filter is not None: + stderr = stderr_filter(stderr) + + if fail_func and 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) + + has_stdout = print_stdout and stdout + has_stderr = print_stderr and stderr + if fail_on_output and (has_stdout or has_stderr): + MSG = """\ +Command failed because it wrote to {}. +You can often set treat_warnings_as_errors=false to not treat output as \ +failure (useful when developing locally).""" + if has_stdout and has_stderr: + stream_string = 'stdout and stderr' + elif has_stdout: + stream_string = 'stdout' + else: + stream_string = 'stderr' + raise CalledProcessError(cwd, args, MSG.format(stream_string)) + + 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 _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 _IsSymlink(zip_file, name): + zi = zip_file.getinfo(name) + + # The two high-order bytes of ZipInfo.external_attr represent + # UNIX permissions and file type bits. + return stat.S_ISLNK(zi.external_attr >> 16) + + +def ExtractAll(zip_path, path=None, no_clobber=True, pattern=None, + predicate=None): + if path is None: + path = os.getcwd() + elif not os.path.exists(path): + MakeDirectory(path) + + if not zipfile.is_zipfile(zip_path): + raise Exception('Invalid zip file: %s' % zip_path) + + extracted = [] + with zipfile.ZipFile(zip_path) as z: + for name in z.namelist(): + if name.endswith('/'): + MakeDirectory(os.path.join(path, name)) + continue + if pattern is not None: + if not fnmatch.fnmatch(name, pattern): + continue + if predicate and not predicate(name): + 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)) + if _IsSymlink(z, name): + dest = os.path.join(path, name) + MakeDirectory(os.path.dirname(dest)) + os.symlink(z.read(name), dest) + extracted.append(dest) + else: + z.extract(name, path) + extracted.append(os.path.join(path, name)) + + return extracted + + +def HermeticDateTime(timestamp=None): + """Returns a constant ZipInfo.date_time tuple. + + Args: + timestamp: Unix timestamp to use for files in the archive. + + Returns: + A ZipInfo.date_time tuple for Jan 1, 2001, or the given timestamp. + """ + if not timestamp: + return (2001, 1, 1, 0, 0, 0) + utc_time = time.gmtime(timestamp) + return (utc_time.tm_year, utc_time.tm_mon, utc_time.tm_mday, utc_time.tm_hour, + utc_time.tm_min, utc_time.tm_sec) + + +def HermeticZipInfo(*args, **kwargs): + """Creates a zipfile.ZipInfo with a constant timestamp and external_attr. + + If a date_time value is not provided in the positional or keyword arguments, + the default value from HermeticDateTime is used. + + Args: + See zipfile.ZipInfo. + + Returns: + A zipfile.ZipInfo. + """ + # The caller may have provided a date_time either as a positional parameter + # (args[1]) or as a keyword parameter. Use the default hermetic date_time if + # none was provided. + date_time = None + if len(args) >= 2: + date_time = args[1] + elif 'date_time' in kwargs: + date_time = kwargs['date_time'] + if not date_time: + kwargs['date_time'] = HermeticDateTime() + ret = zipfile.ZipInfo(*args, **kwargs) + ret.external_attr = (0o644 << 16) + return ret + + +def AddToZipHermetic(zip_file, + zip_path, + src_path=None, + data=None, + compress=None, + date_time=None): + """Adds a file to the given ZipFile with a hard-coded modified time. + + Args: + zip_file: ZipFile instance to add the file to. + zip_path: Destination path within the zip file (or ZipInfo instance). + src_path: Path of the source file. Mutually exclusive with |data|. + data: File data as a string. + compress: Whether to enable compression. Default is taken from ZipFile + constructor. + date_time: The last modification date and time for the archive member. + """ + assert (src_path is None) != (data is None), ( + '|src_path| and |data| are mutually exclusive.') + if isinstance(zip_path, zipfile.ZipInfo): + zipinfo = zip_path + zip_path = zipinfo.filename + else: + zipinfo = HermeticZipInfo(filename=zip_path, date_time=date_time) + + _CheckZipPath(zip_path) + + if src_path and os.path.islink(src_path): + zipinfo.filename = zip_path + zipinfo.external_attr |= stat.S_IFLNK << 16 # mark as a symlink + zip_file.writestr(zipinfo, os.readlink(src_path)) + return + + # zipfile.write() does + # external_attr = (os.stat(src_path)[0] & 0xFFFF) << 16 + # but we want to use _HERMETIC_FILE_ATTR, so manually set + # the few attr bits we care about. + if src_path: + st = os.stat(src_path) + for mode in (stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH): + if st.st_mode & mode: + zipinfo.external_attr |= mode << 16 + + if src_path: + with open(src_path, 'rb') as f: + data = f.read() + + # zipfile will deflate even when it makes the file bigger. To avoid + # growing files, disable compression at an arbitrary cut off point. + if len(data) < 16: + compress = False + + # None converts to ZIP_STORED, when passed explicitly rather than the + # default passed to the ZipFile constructor. + compress_type = zip_file.compression + if compress is not None: + compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + zip_file.writestr(zipinfo, data, compress_type) + + +def DoZip(inputs, + output, + base_dir=None, + compress_fn=None, + zip_prefix_path=None, + timestamp=None): + """Creates a zip file from a list of files. + + Args: + inputs: A list of paths to zip, or a list of (zip_path, fs_path) tuples. + output: Path, fileobj, or ZipFile instance to add files to. + base_dir: Prefix to strip from inputs. + compress_fn: Applied to each input to determine whether or not to compress. + By default, items will be |zipfile.ZIP_STORED|. + zip_prefix_path: Path prepended to file path in zip file. + timestamp: Unix timestamp to use for files in the archive. + """ + if base_dir is None: + base_dir = '.' + input_tuples = [] + for tup in inputs: + if isinstance(tup, string_types): + tup = (os.path.relpath(tup, base_dir), tup) + if tup[0].startswith('..'): + raise Exception('Invalid zip_path: ' + tup[0]) + input_tuples.append(tup) + + # Sort by zip path to ensure stable zip ordering. + input_tuples.sort(key=lambda tup: tup[0]) + + out_zip = output + if not isinstance(output, zipfile.ZipFile): + out_zip = zipfile.ZipFile(output, 'w') + + date_time = HermeticDateTime(timestamp) + try: + for zip_path, fs_path in input_tuples: + if zip_prefix_path: + zip_path = os.path.join(zip_prefix_path, zip_path) + compress = compress_fn(zip_path) if compress_fn else None + AddToZipHermetic(out_zip, + zip_path, + src_path=fs_path, + compress=compress, + date_time=date_time) + finally: + if output is not out_zip: + out_zip.close() + + +def ZipDir(output, base_dir, compress_fn=None, zip_prefix_path=None): + """Creates a zip file from a directory.""" + inputs = [] + for root, _, files in os.walk(base_dir): + for f in files: + inputs.append(os.path.join(root, f)) + + if isinstance(output, zipfile.ZipFile): + DoZip( + inputs, + output, + base_dir, + compress_fn=compress_fn, + zip_prefix_path=zip_prefix_path) + else: + with AtomicOutput(output) as f: + DoZip( + inputs, + f, + base_dir, + compress_fn=compress_fn, + zip_prefix_path=zip_prefix_path) + + +def MatchesGlob(path, filters): + """Returns whether the given path matches any of the given glob patterns.""" + return filters and any(fnmatch.fnmatch(path, f) for f in filters) + + +def MergeZips(output, input_zips, path_transform=None, compress=None): + """Combines all files from |input_zips| into |output|. + + Args: + output: Path, fileobj, or ZipFile instance to add files to. + input_zips: Iterable of paths to zip files to merge. + path_transform: Called for each entry path. Returns a new path, or None to + skip the file. + compress: Overrides compression setting from origin zip entries. + """ + path_transform = path_transform or (lambda p: p) + added_names = set() + + out_zip = output + if not isinstance(output, zipfile.ZipFile): + out_zip = zipfile.ZipFile(output, 'w') + + try: + for in_file in input_zips: + with zipfile.ZipFile(in_file, 'r') as in_zip: + for info in in_zip.infolist(): + # Ignore directories. + if info.filename[-1] == '/': + continue + dst_name = path_transform(info.filename) + if not dst_name: + continue + already_added = dst_name in added_names + if not already_added: + if compress is not None: + compress_entry = compress + else: + compress_entry = info.compress_type != zipfile.ZIP_STORED + AddToZipHermetic( + out_zip, + dst_name, + data=in_zip.read(info), + compress=compress_entry) + added_names.add(dst_name) + finally: + if output is not out_zip: + out_zip.close() + + +def GetSortedTransitiveDependencies(top, deps_func): + """Gets the list of all transitive dependencies in sorted order. + + There should be no cycles in the dependency graph (crashes if cycles exist). + + Args: + top: A list of the top level nodes + deps_func: A function that takes a node and returns a list of 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). + """ + # Find all deps depth-first, maintaining original order in the case of ties. + deps_map = collections.OrderedDict() + def discover(nodes): + for node in nodes: + if node in deps_map: + continue + deps = deps_func(node) + discover(deps) + deps_map[node] = deps + + discover(top) + return list(deps_map) + + +def InitLogging(enabling_env): + logging.basicConfig( + level=logging.DEBUG if os.environ.get(enabling_env) else logging.WARNING, + format='%(levelname).1s %(process)d %(relativeCreated)6d %(message)s') + script_name = os.path.basename(sys.argv[0]) + logging.info('Started (%s)', script_name) + + my_pid = os.getpid() + + def log_exit(): + # Do not log for fork'ed processes. + if os.getpid() == my_pid: + logging.info("Job's done (%s)", script_name) + + atexit.register(log_exit) + + +def AddDepfileOption(parser): + # TODO(agrieve): Get rid of this once we've moved to argparse. + if hasattr(parser, 'add_option'): + func = parser.add_option + else: + func = parser.add_argument + func('--depfile', + help='Path to depfile (refer to `gn help depfile`)') + + +def WriteDepfile(depfile_path, first_gn_output, inputs=None): + assert depfile_path != first_gn_output # http://crbug.com/646165 + assert not isinstance(inputs, string_types) # Easy mistake to make + inputs = inputs or [] + MakeDirectory(os.path.dirname(depfile_path)) + # Ninja does not support multiple outputs in depfiles. + with open(depfile_path, 'w') as depfile: + depfile.write(first_gn_output.replace(' ', '\\ ')) + depfile.write(': \\\n ') + depfile.write(' \\\n '.join(i.replace(' ', '\\ ') for i in inputs)) + 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]. If a key has a '[]' + suffix the (intermediate) value will be interpreted as a single item list and + the single item will be returned or used for further traversal. + + 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 + + def get_key(key): + if key.endswith('[]'): + return key[:-2], True + return key, False + + lookup_path = match.group(1).split(':') + file_path, _ = get_key(lookup_path[0]) + if not file_path in file_jsons: + with open(file_path) as f: + file_jsons[file_path] = json.load(f) + + expansion = file_jsons + for k in lookup_path: + k, flatten = get_key(k) + expansion = expansion[k] + if flatten: + if not isinstance(expansion, list) or not len(expansion) == 1: + raise Exception('Expected single item list but got %s' % expansion) + expansion = expansion[0] + + # This should match ParseGnList. The output is either a GN-formatted list + # or a literal (with no quotes). + if isinstance(expansion, list): + new_args[i] = (arg[:match.start()] + gn_helpers.ToGNString(expansion) + + arg[match.end():]) + else: + new_args[i] = arg[:match.start()] + str(expansion) + arg[match.end():] + + return new_args + + +def ReadSourcesList(sources_list_file_name): + """Reads a GN-written file containing list of file names and returns a list. + + Note that this function should not be used to parse response files. + """ + with open(sources_list_file_name) as f: + return [file_name.strip() for file_name in f] diff --git a/third_party/libwebrtc/build/android/gyp/util/build_utils_test.py b/third_party/libwebrtc/build/android/gyp/util/build_utils_test.py new file mode 100755 index 0000000000..008ea11748 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/build_utils_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 os +import sys +import unittest + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from util import build_utils + +_DEPS = collections.OrderedDict() +_DEPS['a'] = [] +_DEPS['b'] = [] +_DEPS['c'] = ['a'] +_DEPS['d'] = ['a'] +_DEPS['e'] = ['f'] +_DEPS['f'] = ['a', 'd'] +_DEPS['g'] = [] +_DEPS['h'] = ['d', 'b', 'f'] +_DEPS['i'] = ['f'] + + +class BuildUtilsTest(unittest.TestCase): + def testGetSortedTransitiveDependencies_all(self): + TOP = _DEPS.keys() + EXPECTED = ['a', 'b', 'c', 'd', 'f', 'e', 'g', 'h', 'i'] + actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get) + self.assertEqual(EXPECTED, actual) + + def testGetSortedTransitiveDependencies_leaves(self): + TOP = ['c', 'e', 'g', 'h', 'i'] + EXPECTED = ['a', 'c', 'd', 'f', 'e', 'g', 'b', 'h', 'i'] + actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get) + self.assertEqual(EXPECTED, actual) + + def testGetSortedTransitiveDependencies_leavesReverse(self): + TOP = ['i', 'h', 'g', 'e', 'c'] + EXPECTED = ['a', 'd', 'f', 'i', 'b', 'h', 'g', 'e', 'c'] + actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get) + self.assertEqual(EXPECTED, actual) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/util/diff_utils.py b/third_party/libwebrtc/build/android/gyp/util/diff_utils.py new file mode 100644 index 0000000000..530a688191 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/diff_utils.py @@ -0,0 +1,127 @@ +# Copyright 2019 The Chromium 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 difflib +from util import build_utils + + +def _SkipOmitted(line): + """ + Skip lines that are to be intentionally omitted from the expectations file. + + This is required when the file to be compared against expectations contains + a line that changes from build to build because - for instance - it contains + version information. + """ + if line.rstrip().endswith('# OMIT FROM EXPECTATIONS'): + return '# THIS LINE WAS OMITTED\n' + return line + + +def _GenerateDiffWithOnlyAdditons(expected_path, actual_data): + """Generate a diff that only contains additions""" + # Ignore blank lines when creating the diff to cut down on whitespace-only + # lines in the diff. Also remove trailing whitespaces and add the new lines + # manually (ndiff expects new lines but we don't care about trailing + # whitespace). + with open(expected_path) as expected: + expected_lines = [l for l in expected.readlines() if l.strip()] + actual_lines = [ + '{}\n'.format(l.rstrip()) for l in actual_data.splitlines() if l.strip() + ] + + diff = difflib.ndiff(expected_lines, actual_lines) + filtered_diff = (l for l in diff if l.startswith('+')) + return ''.join(filtered_diff) + + +def _DiffFileContents(expected_path, actual_data): + """Check file contents for equality and return the diff or None.""" + # Remove all trailing whitespace and add it explicitly in the end. + with open(expected_path) as f_expected: + expected_lines = [l.rstrip() for l in f_expected.readlines()] + actual_lines = [ + _SkipOmitted(line).rstrip() for line in actual_data.splitlines() + ] + + if expected_lines == actual_lines: + return None + + expected_path = os.path.relpath(expected_path, build_utils.DIR_SOURCE_ROOT) + + diff = difflib.unified_diff( + expected_lines, + actual_lines, + fromfile=os.path.join('before', expected_path), + tofile=os.path.join('after', expected_path), + n=0, + lineterm='', + ) + + return '\n'.join(diff) + + +def AddCommandLineFlags(parser): + group = parser.add_argument_group('Expectations') + group.add_argument( + '--expected-file', + help='Expected contents for the check. If --expected-file-base is set, ' + 'this is a diff of --actual-file and --expected-file-base.') + group.add_argument( + '--expected-file-base', + help='File to diff against before comparing to --expected-file.') + group.add_argument('--actual-file', + help='Path to write actual file (for reference).') + group.add_argument('--failure-file', + help='Write to this file if expectations fail.') + group.add_argument('--fail-on-expectations', + action="store_true", + help='Fail on expectation mismatches.') + group.add_argument('--only-verify-expectations', + action='store_true', + help='Verify the expectation and exit.') + + +def CheckExpectations(actual_data, options, custom_msg=''): + if options.actual_file: + with build_utils.AtomicOutput(options.actual_file) as f: + f.write(actual_data.encode('utf8')) + if options.expected_file_base: + actual_data = _GenerateDiffWithOnlyAdditons(options.expected_file_base, + actual_data) + diff_text = _DiffFileContents(options.expected_file, actual_data) + + if not diff_text: + fail_msg = '' + else: + fail_msg = """ +Expectations need updating: +https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/expectations/README.md + +LogDog tip: Use "Raw log" or "Switch to lite mode" before copying: +https://bugs.chromium.org/p/chromium/issues/detail?id=984616 + +{} + +To update expectations, run: +########### START ########### + patch -p1 <<'END_DIFF' +{} +END_DIFF +############ END ############ +""".format(custom_msg, diff_text) + + sys.stderr.write(fail_msg) + + if fail_msg and options.fail_on_expectations: + # Don't write failure file when failing on expectations or else the target + # will not be re-run on subsequent ninja invocations. + sys.exit(1) + + if options.failure_file: + with open(options.failure_file, 'w') as f: + f.write(fail_msg) diff --git a/third_party/libwebrtc/build/android/gyp/util/jar_info_utils.py b/third_party/libwebrtc/build/android/gyp/util/jar_info_utils.py new file mode 100644 index 0000000000..975945510e --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/jar_info_utils.py @@ -0,0 +1,59 @@ +# Copyright 2018 The Chromium 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 + +# Utilities to read and write .jar.info files. +# +# A .jar.info file contains a simple mapping from fully-qualified Java class +# names to the source file that actually defines it. +# +# For APKs, the .jar.info maps the class names to the .jar file that which +# contains its .class definition instead. + + +def ReadAarSourceInfo(info_path): + """Returns the source= path from an .aar's source.info file.""" + # The .info looks like: "source=path/to/.aar\n". + with open(info_path) as f: + return f.read().rstrip().split('=', 1)[1] + + +def ParseJarInfoFile(info_path): + """Parse a given .jar.info file as a dictionary. + + Args: + info_path: input .jar.info file path. + Returns: + A new dictionary mapping fully-qualified Java class names to file paths. + """ + info_data = dict() + if os.path.exists(info_path): + with open(info_path, 'r') as info_file: + for line in info_file: + line = line.strip() + if line: + fully_qualified_name, path = line.split(',', 1) + info_data[fully_qualified_name] = path + return info_data + + +def WriteJarInfoFile(output_obj, info_data, source_file_map=None): + """Generate a .jar.info file from a given dictionary. + + Args: + output_obj: output file object. + info_data: a mapping of fully qualified Java class names to filepaths. + source_file_map: an optional mapping from java source file paths to the + corresponding source .srcjar. This is because info_data may contain the + path of Java source files that where extracted from an .srcjar into a + temporary location. + """ + for fully_qualified_name, path in sorted(info_data.items()): + if source_file_map and path in source_file_map: + path = source_file_map[path] + assert not path.startswith('/tmp'), ( + 'Java file path should not be in temp dir: {}'.format(path)) + output_obj.write(('{},{}\n'.format(fully_qualified_name, + path)).encode('utf8')) diff --git a/third_party/libwebrtc/build/android/gyp/util/java_cpp_utils.py b/third_party/libwebrtc/build/android/gyp/util/java_cpp_utils.py new file mode 100644 index 0000000000..5180400d61 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/java_cpp_utils.py @@ -0,0 +1,194 @@ +# Copyright 2019 The Chromium 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 sys + + +def GetScriptName(): + return os.path.basename(os.path.abspath(sys.argv[0])) + + +def GetJavaFilePath(java_package, class_name): + package_path = java_package.replace('.', os.path.sep) + file_name = class_name + '.java' + return os.path.join(package_path, file_name) + + +def KCamelToShouty(s): + """Convert |s| from kCamelCase or CamelCase to SHOUTY_CASE. + + kFooBar -> FOO_BAR + FooBar -> FOO_BAR + FooBAR9 -> FOO_BAR9 + FooBARBaz -> FOO_BAR_BAZ + """ + if not re.match(r'^k?([A-Z][^A-Z]+|[A-Z0-9]+)+$', s): + return s + # Strip the leading k. + s = re.sub(r'^k', '', s) + # Treat "WebView" like one word. + s = re.sub(r'WebView', r'Webview', s) + # Add _ between title words and anything else. + s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s) + # Add _ between lower -> upper transitions. + s = re.sub(r'([^A-Z_0-9])([A-Z])', r'\1_\2', s) + return s.upper() + + +class JavaString(object): + def __init__(self, name, value, comments): + self.name = KCamelToShouty(name) + self.value = value + self.comments = '\n'.join(' ' + x for x in comments) + + def Format(self): + return '%s\n public static final String %s = %s;' % ( + self.comments, self.name, self.value) + + +def ParseTemplateFile(lines): + package_re = re.compile(r'^package (.*);') + class_re = re.compile(r'.*class (.*) {') + package = '' + class_name = '' + for line in lines: + package_line = package_re.match(line) + if package_line: + package = package_line.groups()[0] + class_line = class_re.match(line) + if class_line: + class_name = class_line.groups()[0] + break + return package, class_name + + +# TODO(crbug.com/937282): Work will be needed if we want to annotate specific +# constants in the file to be parsed. +class CppConstantParser(object): + """Parses C++ constants, retaining their comments. + + The Delegate subclass is responsible for matching and extracting the + constant's variable name and value, as well as generating an object to + represent the Java representation of this value. + """ + SINGLE_LINE_COMMENT_RE = re.compile(r'\s*(// [^\n]*)') + + class Delegate(object): + def ExtractConstantName(self, line): + """Extracts a constant's name from line or None if not a match.""" + raise NotImplementedError() + + def ExtractValue(self, line): + """Extracts a constant's value from line or None if not a match.""" + raise NotImplementedError() + + def CreateJavaConstant(self, name, value, comments): + """Creates an object representing the Java analog of a C++ constant. + + CppConstantParser will not interact with the object created by this + method. Instead, it will store this value in a list and return a list of + all objects from the Parse() method. In this way, the caller may define + whatever class suits their need. + + Args: + name: the constant's variable name, as extracted by + ExtractConstantName() + value: the constant's value, as extracted by ExtractValue() + comments: the code comments describing this constant + """ + raise NotImplementedError() + + def __init__(self, delegate, lines): + self._delegate = delegate + self._lines = lines + self._in_variable = False + self._in_comment = False + self._package = '' + self._current_comments = [] + self._current_name = '' + self._current_value = '' + self._constants = [] + + def _ExtractVariable(self, line): + match = StringFileParser.STRING_RE.match(line) + return match.group(1) if match else None + + def _ExtractValue(self, line): + match = StringFileParser.VALUE_RE.search(line) + return match.group(1) if match else None + + def _Reset(self): + self._current_comments = [] + self._current_name = '' + self._current_value = '' + self._in_variable = False + self._in_comment = False + + def _AppendConstant(self): + self._constants.append( + self._delegate.CreateJavaConstant(self._current_name, + self._current_value, + self._current_comments)) + self._Reset() + + def _ParseValue(self, line): + current_value = self._delegate.ExtractValue(line) + if current_value is not None: + self._current_value = current_value + self._AppendConstant() + else: + self._Reset() + + def _ParseComment(self, line): + comment_line = CppConstantParser.SINGLE_LINE_COMMENT_RE.match(line) + if comment_line: + self._current_comments.append(comment_line.groups()[0]) + self._in_comment = True + self._in_variable = True + return True + else: + self._in_comment = False + return False + + def _ParseVariable(self, line): + current_name = self._delegate.ExtractConstantName(line) + if current_name is not None: + self._current_name = current_name + current_value = self._delegate.ExtractValue(line) + if current_value is not None: + self._current_value = current_value + self._AppendConstant() + else: + self._in_variable = True + return True + else: + self._in_variable = False + return False + + def _ParseLine(self, line): + if not self._in_variable: + if not self._ParseVariable(line): + self._ParseComment(line) + return + + if self._in_comment: + if self._ParseComment(line): + return + if not self._ParseVariable(line): + self._Reset() + return + + if self._in_variable: + self._ParseValue(line) + + def Parse(self): + """Returns a list of objects representing C++ constants. + + Each object in the list was created by Delegate.CreateJavaValue(). + """ + for line in self._lines: + self._ParseLine(line) + return self._constants diff --git a/third_party/libwebrtc/build/android/gyp/util/manifest_utils.py b/third_party/libwebrtc/build/android/gyp/util/manifest_utils.py new file mode 100644 index 0000000000..a517708b59 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/manifest_utils.py @@ -0,0 +1,321 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Contains common helpers for working with Android manifests.""" + +import hashlib +import os +import re +import shlex +import sys +import xml.dom.minidom as minidom + +from util import build_utils +from xml.etree import ElementTree + +ANDROID_NAMESPACE = 'http://schemas.android.com/apk/res/android' +TOOLS_NAMESPACE = 'http://schemas.android.com/tools' +DIST_NAMESPACE = 'http://schemas.android.com/apk/distribution' +EMPTY_ANDROID_MANIFEST_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', 'AndroidManifest.xml')) +# When normalizing for expectation matching, wrap these tags when they are long +# or else they become very hard to read. +_WRAP_CANDIDATES = ( + ' node. + app_node: the node. + """ + _RegisterElementTreeNamespaces() + doc = ElementTree.parse(path) + # ElementTree.find does not work if the required tag is the root. + if doc.getroot().tag == 'manifest': + manifest_node = doc.getroot() + else: + manifest_node = doc.find('manifest') + + app_node = doc.find('application') + if app_node is None: + app_node = ElementTree.SubElement(manifest_node, 'application') + + return doc, manifest_node, app_node + + +def SaveManifest(doc, path): + with build_utils.AtomicOutput(path) as f: + f.write(ElementTree.tostring(doc.getroot(), encoding='UTF-8')) + + +def GetPackage(manifest_node): + return manifest_node.get('package') + + +def AssertUsesSdk(manifest_node, + min_sdk_version=None, + target_sdk_version=None, + max_sdk_version=None, + fail_if_not_exist=False): + """Asserts values of attributes of element. + + Unless |fail_if_not_exist| is true, will only assert if both the passed value + is not None and the value of attribute exist. If |fail_if_not_exist| is true + will fail if passed value is not None but attribute does not exist. + """ + uses_sdk_node = manifest_node.find('./uses-sdk') + if uses_sdk_node is None: + return + for prefix, sdk_version in (('min', min_sdk_version), ('target', + target_sdk_version), + ('max', max_sdk_version)): + value = uses_sdk_node.get('{%s}%sSdkVersion' % (ANDROID_NAMESPACE, prefix)) + if fail_if_not_exist and not value and sdk_version: + assert False, ( + '%sSdkVersion in Android manifest does not exist but we expect %s' % + (prefix, sdk_version)) + if not value or not sdk_version: + continue + assert value == sdk_version, ( + '%sSdkVersion in Android manifest is %s but we expect %s' % + (prefix, value, sdk_version)) + + +def AssertPackage(manifest_node, package): + """Asserts that manifest package has desired value. + + Will only assert if both |package| is not None and the package is set in the + manifest. + """ + package_value = GetPackage(manifest_node) + if package_value is None or package is None: + return + assert package_value == package, ( + 'Package in Android manifest is %s but we expect %s' % (package_value, + package)) + + +def _SortAndStripElementTree(root): + # Sort alphabetically with two exceptions: + # 1) Put node last (since it's giant). + # 2) Put android:name before other attributes. + def element_sort_key(node): + if node.tag == 'application': + return 'z' + ret = ElementTree.tostring(node) + # ElementTree.tostring inserts namespace attributes for any that are needed + # for the node or any of its descendants. Remove them so as to prevent a + # change to a child that adds/removes a namespace usage from changing sort + # order. + return re.sub(r' xmlns:.*?".*?"', '', ret.decode('utf8')) + + name_attr = '{%s}name' % ANDROID_NAMESPACE + + def attribute_sort_key(tup): + return ('', '') if tup[0] == name_attr else tup + + def helper(node): + for child in node: + if child.text and child.text.isspace(): + child.text = None + helper(child) + + # Sort attributes (requires Python 3.8+). + node.attrib = dict(sorted(node.attrib.items(), key=attribute_sort_key)) + + # Sort nodes + node[:] = sorted(node, key=element_sort_key) + + helper(root) + + +def _SplitElement(line): + """Parses a one-line xml node into ('').""" + + # Shlex splits nicely, but removes quotes. Need to put them back. + def restore_quotes(value): + return value.replace('=', '="', 1) + '"' + + # Simplify restore_quotes by separating />. + assert line.endswith('>'), line + end_tag = '>' + if line.endswith('/>'): + end_tag = '/>' + line = line[:-len(end_tag)] + + # Use shlex to avoid having to re-encode ", etc. + parts = shlex.split(line) + start_tag = parts[0] + attrs = parts[1:] + + return start_tag, [restore_quotes(x) for x in attrs], end_tag + + +def _CreateNodeHash(lines): + """Computes a hash (md5) for the first XML node found in |lines|. + + Args: + lines: List of strings containing pretty-printed XML. + + Returns: + Positive 32-bit integer hash of the node (including children). + """ + target_indent = lines[0].find('<') + tag_closed = False + for i, l in enumerate(lines[1:]): + cur_indent = l.find('<') + if cur_indent != -1 and cur_indent <= target_indent: + tag_lines = lines[:i + 1] + break + elif not tag_closed and 'android:name="' in l: + # To reduce noise of node tags changing, use android:name as the + # basis the hash since they usually unique. + tag_lines = [l] + break + tag_closed = tag_closed or '>' in l + else: + assert False, 'Did not find end of node:\n' + '\n'.join(lines) + + # Insecure and truncated hash as it only needs to be unique vs. its neighbors. + return hashlib.md5(('\n'.join(tag_lines)).encode('utf8')).hexdigest()[:8] + + +def _IsSelfClosing(lines): + """Given pretty-printed xml, returns whether first node is self-closing.""" + for l in lines: + idx = l.find('>') + if idx != -1: + return l[idx - 1] == '/' + assert False, 'Did not find end of tag:\n' + '\n'.join(lines) + + +def _AddDiffTags(lines): + # When multiple identical tags appear sequentially, XML diffs can look like: + # + + # + + # rather than: + # + + # + + # To reduce confusion, add hashes to tags. + # This also ensures changed tags show up with outer elements rather than + # showing only changed attributes. + hash_stack = [] + for i, l in enumerate(lines): + stripped = l.lstrip() + # Ignore non-indented tags and lines that are not the start/end of a node. + if l[0] != ' ' or stripped[0] != '<': + continue + # Ignore self-closing nodes that fit on one line. + if l[-2:] == '/>': + continue + # Ignore since diff tag changes with basically any change. + if stripped.lstrip('). + if stripped[1] != '/': + cur_hash = _CreateNodeHash(lines[i:]) + if not _IsSelfClosing(lines[i:]): + hash_stack.append(cur_hash) + else: + cur_hash = hash_stack.pop() + lines[i] += ' # DIFF-ANCHOR: {}'.format(cur_hash) + assert not hash_stack, 'hash_stack was not empty:\n' + '\n'.join(hash_stack) + + +def NormalizeManifest(manifest_contents): + _RegisterElementTreeNamespaces() + # This also strips comments and sorts node attributes alphabetically. + root = ElementTree.fromstring(manifest_contents) + package = GetPackage(root) + + app_node = root.find('application') + if app_node is not None: + # android:debuggable is added when !is_official_build. Strip it out to avoid + # expectation diffs caused by not adding is_official_build. Play store + # blocks uploading apps with it set, so there's no risk of it slipping in. + debuggable_name = '{%s}debuggable' % ANDROID_NAMESPACE + if debuggable_name in app_node.attrib: + del app_node.attrib[debuggable_name] + + # Trichrome's static library version number is updated daily. To avoid + # frequent manifest check failures, we remove the exact version number + # during normalization. + for node in app_node: + if (node.tag in ['uses-static-library', 'static-library'] + and '{%s}version' % ANDROID_NAMESPACE in node.keys() + and '{%s}name' % ANDROID_NAMESPACE in node.keys()): + node.set('{%s}version' % ANDROID_NAMESPACE, '$VERSION_NUMBER') + + # We also remove the exact package name (except the one at the root level) + # to avoid noise during manifest comparison. + def blur_package_name(node): + for key in node.keys(): + node.set(key, node.get(key).replace(package, '$PACKAGE')) + + for child in node: + blur_package_name(child) + + # We only blur the package names of non-root nodes because they generate a lot + # of diffs when doing manifest checks for upstream targets. We still want to + # have 1 piece of package name not blurred just in case the package name is + # mistakenly changed. + for child in root: + blur_package_name(child) + + _SortAndStripElementTree(root) + + # Fix up whitespace/indentation. + dom = minidom.parseString(ElementTree.tostring(root)) + out_lines = [] + for l in dom.toprettyxml(indent=' ').splitlines(): + if not l or l.isspace(): + continue + if len(l) > _WRAP_LINE_LENGTH and any(x in l for x in _WRAP_CANDIDATES): + indent = ' ' * l.find('<') + start_tag, attrs, end_tag = _SplitElement(l) + out_lines.append('{}{}'.format(indent, start_tag)) + for attribute in attrs: + out_lines.append('{} {}'.format(indent, attribute)) + out_lines[-1] += '>' + # Heuristic: Do not allow multi-line tags to be self-closing since these + # can generally be allowed to have nested elements. When diffing, it adds + # noise if the base file is self-closing and the non-base file is not + # self-closing. + if end_tag == '/>': + out_lines.append('{}{}>'.format(indent, start_tag.replace('<', ' + + + + + + + + + + {extra_intent_filter_elem} + + + + + + + + + +""" + +_TEST_MANIFEST_NORMALIZED = """\ + + + + + + + # DIFF-ANCHOR: {intent_filter_diff_anchor} + {extra_intent_filter_elem}\ + + + + # DIFF-ANCHOR: {intent_filter_diff_anchor} + # DIFF-ANCHOR: {activity_diff_anchor} + + # DIFF-ANCHOR: ddab3320 + + +""" + +_ACTIVITY_DIFF_ANCHOR = '32b3a641' +_INTENT_FILTER_DIFF_ANCHOR = '4ee601b7' + + +def _CreateTestData(intent_filter_diff_anchor=_INTENT_FILTER_DIFF_ANCHOR, + extra_activity_attr='', + extra_intent_filter_elem=''): + if extra_activity_attr: + extra_activity_attr += '\n ' + if extra_intent_filter_elem: + extra_intent_filter_elem += '\n ' + test_manifest = _TEST_MANIFEST.format( + extra_activity_attr=extra_activity_attr, + extra_intent_filter_elem=extra_intent_filter_elem) + expected = _TEST_MANIFEST_NORMALIZED.format( + activity_diff_anchor=_ACTIVITY_DIFF_ANCHOR, + intent_filter_diff_anchor=intent_filter_diff_anchor, + extra_activity_attr=extra_activity_attr, + extra_intent_filter_elem=extra_intent_filter_elem) + return test_manifest, expected + + +class ManifestUtilsTest(unittest.TestCase): + # Enable diff output. + maxDiff = None + + def testNormalizeManifest_golden(self): + test_manifest, expected = _CreateTestData() + actual = manifest_utils.NormalizeManifest(test_manifest) + self.assertMultiLineEqual(expected, actual) + + def testNormalizeManifest_nameUsedForActivity(self): + test_manifest, expected = _CreateTestData(extra_activity_attr='a="b"') + actual = manifest_utils.NormalizeManifest(test_manifest) + # Checks that the DIFF-ANCHOR does not change with the added attribute. + self.assertMultiLineEqual(expected, actual) + + def testNormalizeManifest_nameNotUsedForIntentFilter(self): + test_manifest, expected = _CreateTestData( + extra_intent_filter_elem='', intent_filter_diff_anchor='5f5c8a70') + actual = manifest_utils.NormalizeManifest(test_manifest) + # Checks that the DIFF-ANCHOR does change with the added element despite + # having a nested element with an android:name set. + self.assertMultiLineEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/util/md5_check.py b/third_party/libwebrtc/build/android/gyp/util/md5_check.py new file mode 100644 index 0000000000..87ee723c85 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/md5_check.py @@ -0,0 +1,471 @@ +# 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 __future__ import print_function + +import difflib +import hashlib +import itertools +import json +import os +import sys +import zipfile + +from util import build_utils + +sys.path.insert(1, os.path.join(build_utils.DIR_SOURCE_ROOT, 'build')) +import print_python_deps + +# When set and a difference is detected, a diff of what changed is printed. +PRINT_EXPLANATIONS = int(os.environ.get('PRINT_BUILD_EXPLANATIONS', 0)) + +# An escape hatch that causes all targets to be rebuilt. +_FORCE_REBUILD = int(os.environ.get('FORCE_REBUILD', 0)) + + +def CallAndWriteDepfileIfStale(on_stale_md5, + options, + record_path=None, + input_paths=None, + input_strings=None, + output_paths=None, + force=False, + pass_changes=False, + track_subpaths_allowlist=None, + depfile_deps=None): + """Wraps CallAndRecordIfStale() and writes a depfile if applicable. + + Depfiles are automatically added to output_paths when present in the |options| + argument. They are then created after |on_stale_md5| is called. + + By default, only python dependencies are added to the depfile. If there are + other input paths that are not captured by GN deps, then they should be listed + in depfile_deps. It's important to write paths to the depfile that are already + captured by GN deps since GN args can cause GN deps to change, and such + changes are not immediately reflected in depfiles (http://crbug.com/589311). + """ + if not output_paths: + raise Exception('At least one output_path must be specified.') + input_paths = list(input_paths or []) + input_strings = list(input_strings or []) + output_paths = list(output_paths or []) + + input_paths += print_python_deps.ComputePythonDependencies() + + CallAndRecordIfStale( + on_stale_md5, + record_path=record_path, + input_paths=input_paths, + input_strings=input_strings, + output_paths=output_paths, + force=force, + pass_changes=pass_changes, + track_subpaths_allowlist=track_subpaths_allowlist) + + # Write depfile even when inputs have not changed to ensure build correctness + # on bots that build with & without patch, and the patch changes the depfile + # location. + if hasattr(options, 'depfile') and options.depfile: + build_utils.WriteDepfile(options.depfile, output_paths[0], depfile_deps) + + +def CallAndRecordIfStale(function, + record_path=None, + input_paths=None, + input_strings=None, + output_paths=None, + force=False, + pass_changes=False, + track_subpaths_allowlist=None): + """Calls function if outputs are stale. + + Outputs are considered stale if: + - any output_paths are missing, or + - the contents of any file within input_paths has changed, or + - the contents of input_strings has changed. + + To debug which files are out-of-date, set the environment variable: + PRINT_MD5_DIFFS=1 + + Args: + function: The function to call. + record_path: Path to record metadata. + Defaults to output_paths[0] + '.md5.stamp' + input_paths: List of paths to calcualte an md5 sum on. + input_strings: List of strings to record verbatim. + output_paths: List of output paths. + force: Whether to treat outputs as missing regardless of whether they + actually are. + pass_changes: Whether to pass a Changes instance to |function|. + track_subpaths_allowlist: Relevant only when pass_changes=True. List of .zip + files from |input_paths| to make subpath information available for. + """ + assert record_path or output_paths + input_paths = input_paths or [] + input_strings = input_strings or [] + output_paths = output_paths or [] + record_path = record_path or output_paths[0] + '.md5.stamp' + + assert record_path.endswith('.stamp'), ( + 'record paths must end in \'.stamp\' so that they are easy to find ' + 'and delete') + + new_metadata = _Metadata(track_entries=pass_changes or PRINT_EXPLANATIONS) + new_metadata.AddStrings(input_strings) + + zip_allowlist = set(track_subpaths_allowlist or []) + for path in input_paths: + # It's faster to md5 an entire zip file than it is to just locate & hash + # its central directory (which is what this used to do). + if path in zip_allowlist: + entries = _ExtractZipEntries(path) + new_metadata.AddZipFile(path, entries) + else: + new_metadata.AddFile(path, _ComputeTagForPath(path)) + + old_metadata = None + force = force or _FORCE_REBUILD + missing_outputs = [x for x in output_paths if force or not os.path.exists(x)] + too_new = [] + # When outputs are missing, don't bother gathering change information. + if not missing_outputs and os.path.exists(record_path): + record_mtime = os.path.getmtime(record_path) + # Outputs newer than the change information must have been modified outside + # of the build, and should be considered stale. + too_new = [x for x in output_paths if os.path.getmtime(x) > record_mtime] + if not too_new: + with open(record_path, 'r') as jsonfile: + try: + old_metadata = _Metadata.FromFile(jsonfile) + except: # pylint: disable=bare-except + pass # Not yet using new file format. + + changes = Changes(old_metadata, new_metadata, force, missing_outputs, too_new) + if not changes.HasChanges(): + return + + if PRINT_EXPLANATIONS: + print('=' * 80) + print('Target is stale: %s' % record_path) + print(changes.DescribeDifference()) + print('=' * 80) + + args = (changes,) if pass_changes else () + function(*args) + + with open(record_path, 'w') as f: + new_metadata.ToFile(f) + + +class Changes(object): + """Provides and API for querying what changed between runs.""" + + def __init__(self, old_metadata, new_metadata, force, missing_outputs, + too_new): + self.old_metadata = old_metadata + self.new_metadata = new_metadata + self.force = force + self.missing_outputs = missing_outputs + self.too_new = too_new + + def _GetOldTag(self, path, subpath=None): + return self.old_metadata and self.old_metadata.GetTag(path, subpath) + + def HasChanges(self): + """Returns whether any changes exist.""" + return (self.HasStringChanges() + or self.old_metadata.FilesMd5() != self.new_metadata.FilesMd5()) + + def HasStringChanges(self): + """Returns whether string metadata changed.""" + return (self.force or not self.old_metadata + or self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5()) + + def AddedOrModifiedOnly(self): + """Returns whether the only changes were from added or modified (sub)files. + + No missing outputs, no removed paths/subpaths. + """ + if self.HasStringChanges(): + return False + if any(self.IterRemovedPaths()): + return False + for path in self.IterModifiedPaths(): + if any(self.IterRemovedSubpaths(path)): + return False + return True + + def IterAllPaths(self): + """Generator for paths.""" + return self.new_metadata.IterPaths(); + + def IterAllSubpaths(self, path): + """Generator for subpaths.""" + return self.new_metadata.IterSubpaths(path); + + def IterAddedPaths(self): + """Generator for paths that were added.""" + for path in self.new_metadata.IterPaths(): + if self._GetOldTag(path) is None: + yield path + + def IterAddedSubpaths(self, path): + """Generator for paths that were added within the given zip file.""" + for subpath in self.new_metadata.IterSubpaths(path): + if self._GetOldTag(path, subpath) is None: + yield subpath + + def IterRemovedPaths(self): + """Generator for paths that were removed.""" + if self.old_metadata: + for path in self.old_metadata.IterPaths(): + if self.new_metadata.GetTag(path) is None: + yield path + + def IterRemovedSubpaths(self, path): + """Generator for paths that were removed within the given zip file.""" + if self.old_metadata: + for subpath in self.old_metadata.IterSubpaths(path): + if self.new_metadata.GetTag(path, subpath) is None: + yield subpath + + def IterModifiedPaths(self): + """Generator for paths whose contents have changed.""" + for path in self.new_metadata.IterPaths(): + old_tag = self._GetOldTag(path) + new_tag = self.new_metadata.GetTag(path) + if old_tag is not None and old_tag != new_tag: + yield path + + def IterModifiedSubpaths(self, path): + """Generator for paths within a zip file whose contents have changed.""" + for subpath in self.new_metadata.IterSubpaths(path): + old_tag = self._GetOldTag(path, subpath) + new_tag = self.new_metadata.GetTag(path, subpath) + if old_tag is not None and old_tag != new_tag: + yield subpath + + def IterChangedPaths(self): + """Generator for all changed paths (added/removed/modified).""" + return itertools.chain(self.IterRemovedPaths(), + self.IterModifiedPaths(), + self.IterAddedPaths()) + + def IterChangedSubpaths(self, path): + """Generator for paths within a zip that were added/removed/modified.""" + return itertools.chain(self.IterRemovedSubpaths(path), + self.IterModifiedSubpaths(path), + self.IterAddedSubpaths(path)) + + def DescribeDifference(self): + """Returns a human-readable description of what changed.""" + if self.force: + return 'force=True' + elif self.missing_outputs: + return 'Outputs do not exist:\n ' + '\n '.join(self.missing_outputs) + elif self.too_new: + return 'Outputs newer than stamp file:\n ' + '\n '.join(self.too_new) + elif self.old_metadata is None: + return 'Previous stamp file not found.' + + if self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5(): + ndiff = difflib.ndiff(self.old_metadata.GetStrings(), + self.new_metadata.GetStrings()) + changed = [s for s in ndiff if not s.startswith(' ')] + return 'Input strings changed:\n ' + '\n '.join(changed) + + if self.old_metadata.FilesMd5() == self.new_metadata.FilesMd5(): + return "There's no difference." + + lines = [] + lines.extend('Added: ' + p for p in self.IterAddedPaths()) + lines.extend('Removed: ' + p for p in self.IterRemovedPaths()) + for path in self.IterModifiedPaths(): + lines.append('Modified: ' + path) + lines.extend(' -> Subpath added: ' + p + for p in self.IterAddedSubpaths(path)) + lines.extend(' -> Subpath removed: ' + p + for p in self.IterRemovedSubpaths(path)) + lines.extend(' -> Subpath modified: ' + p + for p in self.IterModifiedSubpaths(path)) + if lines: + return 'Input files changed:\n ' + '\n '.join(lines) + return 'I have no idea what changed (there is a bug).' + + +class _Metadata(object): + """Data model for tracking change metadata. + + Args: + track_entries: Enables per-file change tracking. Slower, but required for + Changes functionality. + """ + # Schema: + # { + # "files-md5": "VALUE", + # "strings-md5": "VALUE", + # "input-files": [ + # { + # "path": "path.jar", + # "tag": "{MD5 of entries}", + # "entries": [ + # { "path": "org/chromium/base/Foo.class", "tag": "{CRC32}" }, ... + # ] + # }, { + # "path": "path.txt", + # "tag": "{MD5}", + # } + # ], + # "input-strings": ["a", "b", ...], + # } + def __init__(self, track_entries=False): + self._track_entries = track_entries + self._files_md5 = None + self._strings_md5 = None + self._files = [] + self._strings = [] + # Map of (path, subpath) -> entry. Created upon first call to _GetEntry(). + self._file_map = None + + @classmethod + def FromFile(cls, fileobj): + """Returns a _Metadata initialized from a file object.""" + ret = cls() + obj = json.load(fileobj) + ret._files_md5 = obj['files-md5'] + ret._strings_md5 = obj['strings-md5'] + ret._files = obj.get('input-files', []) + ret._strings = obj.get('input-strings', []) + return ret + + def ToFile(self, fileobj): + """Serializes metadata to the given file object.""" + obj = { + 'files-md5': self.FilesMd5(), + 'strings-md5': self.StringsMd5(), + } + if self._track_entries: + obj['input-files'] = sorted(self._files, key=lambda e: e['path']) + obj['input-strings'] = self._strings + + json.dump(obj, fileobj, indent=2) + + def _AssertNotQueried(self): + assert self._files_md5 is None + assert self._strings_md5 is None + assert self._file_map is None + + def AddStrings(self, values): + self._AssertNotQueried() + self._strings.extend(str(v) for v in values) + + def AddFile(self, path, tag): + """Adds metadata for a non-zip file. + + Args: + path: Path to the file. + tag: A short string representative of the file contents. + """ + self._AssertNotQueried() + self._files.append({ + 'path': path, + 'tag': tag, + }) + + def AddZipFile(self, path, entries): + """Adds metadata for a zip file. + + Args: + path: Path to the file. + entries: List of (subpath, tag) tuples for entries within the zip. + """ + self._AssertNotQueried() + tag = _ComputeInlineMd5(itertools.chain((e[0] for e in entries), + (e[1] for e in entries))) + self._files.append({ + 'path': path, + 'tag': tag, + 'entries': [{"path": e[0], "tag": e[1]} for e in entries], + }) + + def GetStrings(self): + """Returns the list of input strings.""" + return self._strings + + def FilesMd5(self): + """Lazily computes and returns the aggregate md5 of input files.""" + if self._files_md5 is None: + # Omit paths from md5 since temporary files have random names. + self._files_md5 = _ComputeInlineMd5( + self.GetTag(p) for p in sorted(self.IterPaths())) + return self._files_md5 + + def StringsMd5(self): + """Lazily computes and returns the aggregate md5 of input strings.""" + if self._strings_md5 is None: + self._strings_md5 = _ComputeInlineMd5(self._strings) + return self._strings_md5 + + def _GetEntry(self, path, subpath=None): + """Returns the JSON entry for the given path / subpath.""" + if self._file_map is None: + self._file_map = {} + for entry in self._files: + self._file_map[(entry['path'], None)] = entry + for subentry in entry.get('entries', ()): + self._file_map[(entry['path'], subentry['path'])] = subentry + return self._file_map.get((path, subpath)) + + def GetTag(self, path, subpath=None): + """Returns the tag for the given path / subpath.""" + ret = self._GetEntry(path, subpath) + return ret and ret['tag'] + + def IterPaths(self): + """Returns a generator for all top-level paths.""" + return (e['path'] for e in self._files) + + def IterSubpaths(self, path): + """Returns a generator for all subpaths in the given zip. + + If the given path is not a zip file or doesn't exist, returns an empty + iterable. + """ + outer_entry = self._GetEntry(path) + if not outer_entry: + return () + subentries = outer_entry.get('entries', []) + return (entry['path'] for entry in subentries) + + +def _ComputeTagForPath(path): + stat = os.stat(path) + if stat.st_size > 1 * 1024 * 1024: + # Fallback to mtime for large files so that md5_check does not take too long + # to run. + return stat.st_mtime + md5 = hashlib.md5() + with open(path, 'rb') as f: + md5.update(f.read()) + return md5.hexdigest() + + +def _ComputeInlineMd5(iterable): + """Computes the md5 of the concatenated parameters.""" + md5 = hashlib.md5() + for item in iterable: + md5.update(str(item).encode('ascii')) + return md5.hexdigest() + + +def _ExtractZipEntries(path): + """Returns a list of (path, CRC32) of all files within |path|.""" + entries = [] + with zipfile.ZipFile(path) as zip_file: + for zip_info in zip_file.infolist(): + # Skip directories and empty files. + if zip_info.CRC: + entries.append( + (zip_info.filename, zip_info.CRC + zip_info.compress_type)) + return entries diff --git a/third_party/libwebrtc/build/android/gyp/util/md5_check_test.py b/third_party/libwebrtc/build/android/gyp/util/md5_check_test.py new file mode 100755 index 0000000000..e11bbd50ed --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/md5_check_test.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# 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 os +import sys +import tempfile +import unittest +import zipfile + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from util import md5_check + + +def _WriteZipFile(path, entries): + with zipfile.ZipFile(path, 'w') as zip_file: + for subpath, data in entries: + zip_file.writestr(subpath, data) + + +class TestMd5Check(unittest.TestCase): + def setUp(self): + self.called = False + self.changes = None + + def testCallAndRecordIfStale(self): + input_strings = ['string1', 'string2'] + input_file1 = tempfile.NamedTemporaryFile(suffix='.txt') + input_file2 = tempfile.NamedTemporaryFile(suffix='.zip') + file1_contents = b'input file 1' + input_file1.write(file1_contents) + input_file1.flush() + # Test out empty zip file to start. + _WriteZipFile(input_file2.name, []) + input_files = [input_file1.name, input_file2.name] + zip_paths = [input_file2.name] + + record_path = tempfile.NamedTemporaryFile(suffix='.stamp') + + def CheckCallAndRecord(should_call, + message, + force=False, + outputs_specified=False, + outputs_missing=False, + expected_changes=None, + added_or_modified_only=None, + track_subentries=False, + output_newer_than_record=False): + output_paths = None + if outputs_specified: + output_file1 = tempfile.NamedTemporaryFile() + if outputs_missing: + output_file1.close() # Gets deleted on close(). + output_paths = [output_file1.name] + if output_newer_than_record: + output_mtime = os.path.getmtime(output_file1.name) + os.utime(record_path.name, (output_mtime - 1, output_mtime - 1)) + else: + # touch the record file so it doesn't look like it's older that + # the output we've just created + os.utime(record_path.name, None) + + self.called = False + self.changes = None + if expected_changes or added_or_modified_only is not None: + def MarkCalled(changes): + self.called = True + self.changes = changes + else: + def MarkCalled(): + self.called = True + + md5_check.CallAndRecordIfStale( + MarkCalled, + record_path=record_path.name, + input_paths=input_files, + input_strings=input_strings, + output_paths=output_paths, + force=force, + pass_changes=(expected_changes or added_or_modified_only) is not None, + track_subpaths_allowlist=zip_paths if track_subentries else None) + self.assertEqual(should_call, self.called, message) + if expected_changes: + description = self.changes.DescribeDifference() + self.assertTrue(fnmatch.fnmatch(description, expected_changes), + 'Expected %s to match %s' % ( + repr(description), repr(expected_changes))) + if should_call and added_or_modified_only is not None: + self.assertEqual(added_or_modified_only, + self.changes.AddedOrModifiedOnly()) + + CheckCallAndRecord(True, 'should call when record doesn\'t exist', + expected_changes='Previous stamp file not found.', + added_or_modified_only=False) + CheckCallAndRecord(False, 'should not call when nothing changed') + input_files = input_files[::-1] + CheckCallAndRecord(False, 'reordering of inputs shouldn\'t trigger call') + + CheckCallAndRecord(False, 'should not call when nothing changed #2', + outputs_specified=True, outputs_missing=False) + CheckCallAndRecord(True, 'should call when output missing', + outputs_specified=True, outputs_missing=True, + expected_changes='Outputs do not exist:*', + added_or_modified_only=False) + CheckCallAndRecord(True, + 'should call when output is newer than record', + expected_changes='Outputs newer than stamp file:*', + outputs_specified=True, + outputs_missing=False, + added_or_modified_only=False, + output_newer_than_record=True) + CheckCallAndRecord(True, force=True, message='should call when forced', + expected_changes='force=True', + added_or_modified_only=False) + + input_file1.write(b'some more input') + input_file1.flush() + CheckCallAndRecord(True, 'changed input file should trigger call', + expected_changes='*Modified: %s' % input_file1.name, + added_or_modified_only=True) + + input_files = input_files[:1] + CheckCallAndRecord(True, 'removing file should trigger call', + expected_changes='*Removed: %s' % input_file1.name, + added_or_modified_only=False) + + input_files.append(input_file1.name) + CheckCallAndRecord(True, 'added input file should trigger call', + expected_changes='*Added: %s' % input_file1.name, + added_or_modified_only=True) + + input_strings[0] = input_strings[0] + ' a bit longer' + CheckCallAndRecord(True, 'changed input string should trigger call', + expected_changes='*Input strings changed*', + added_or_modified_only=False) + + input_strings = input_strings[::-1] + CheckCallAndRecord(True, 'reordering of string inputs should trigger call', + expected_changes='*Input strings changed*') + + 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', + added_or_modified_only=False) + + _WriteZipFile(input_file2.name, [('path/1.txt', '1')]) + CheckCallAndRecord( + True, + 'added subpath should trigger call', + expected_changes='*Modified: %s*Subpath added: %s' % (input_file2.name, + 'path/1.txt'), + added_or_modified_only=True, + track_subentries=True) + _WriteZipFile(input_file2.name, [('path/1.txt', '2')]) + CheckCallAndRecord( + True, + 'changed subpath should trigger call', + expected_changes='*Modified: %s*Subpath modified: %s' % + (input_file2.name, 'path/1.txt'), + added_or_modified_only=True, + track_subentries=True) + + _WriteZipFile(input_file2.name, []) + CheckCallAndRecord(True, 'removed subpath should trigger call', + expected_changes='*Modified: %s*Subpath removed: %s' % ( + input_file2.name, 'path/1.txt'), + added_or_modified_only=False) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/util/parallel.py b/third_party/libwebrtc/build/android/gyp/util/parallel.py new file mode 100644 index 0000000000..c26875a71c --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/parallel.py @@ -0,0 +1,214 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Helpers related to multiprocessing. + +Based on: //tools/binary_size/libsupersize/parallel.py +""" + +import atexit +import logging +import multiprocessing +import os +import sys +import threading +import traceback + +DISABLE_ASYNC = os.environ.get('DISABLE_ASYNC') == '1' +if DISABLE_ASYNC: + logging.warning('Running in synchronous mode.') + +_all_pools = None +_is_child_process = False +_silence_exceptions = False + +# Used to pass parameters to forked processes without pickling. +_fork_params = None +_fork_kwargs = None + + +class _ImmediateResult(object): + def __init__(self, value): + self._value = value + + def get(self): + return self._value + + def wait(self): + pass + + def ready(self): + return True + + def successful(self): + return True + + +class _ExceptionWrapper(object): + """Used to marshal exception messages back to main process.""" + + def __init__(self, msg, exception_type=None): + self.msg = msg + self.exception_type = exception_type + + def MaybeThrow(self): + if self.exception_type: + raise getattr(__builtins__, + self.exception_type)('Originally caused by: ' + self.msg) + + +class _FuncWrapper(object): + """Runs on the fork()'ed side to catch exceptions and spread *args.""" + + def __init__(self, func): + global _is_child_process + _is_child_process = True + self._func = func + + def __call__(self, index, _=None): + try: + return self._func(*_fork_params[index], **_fork_kwargs) + except Exception as e: + # Only keep the exception type for builtin exception types or else risk + # further marshalling exceptions. + exception_type = None + if hasattr(__builtins__, type(e).__name__): + exception_type = type(e).__name__ + # multiprocessing is supposed to catch and return exceptions automatically + # but it doesn't seem to work properly :(. + return _ExceptionWrapper(traceback.format_exc(), exception_type) + except: # pylint: disable=bare-except + return _ExceptionWrapper(traceback.format_exc()) + + +class _WrappedResult(object): + """Allows for host-side logic to be run after child process has terminated. + + * Unregisters associated pool _all_pools. + * Raises exception caught by _FuncWrapper. + """ + + def __init__(self, result, pool=None): + self._result = result + self._pool = pool + + def get(self): + self.wait() + value = self._result.get() + _CheckForException(value) + return value + + def wait(self): + self._result.wait() + if self._pool: + _all_pools.remove(self._pool) + self._pool = None + + def ready(self): + return self._result.ready() + + def successful(self): + return self._result.successful() + + +def _TerminatePools(): + """Calls .terminate() on all active process pools. + + Not supposed to be necessary according to the docs, but seems to be required + when child process throws an exception or Ctrl-C is hit. + """ + global _silence_exceptions + _silence_exceptions = True + # Child processes cannot have pools, but atexit runs this function because + # it was registered before fork()ing. + if _is_child_process: + return + + def close_pool(pool): + try: + pool.terminate() + except: # pylint: disable=bare-except + pass + + for i, pool in enumerate(_all_pools): + # Without calling terminate() on a separate thread, the call can block + # forever. + thread = threading.Thread(name='Pool-Terminate-{}'.format(i), + target=close_pool, + args=(pool, )) + thread.daemon = True + thread.start() + + +def _CheckForException(value): + if isinstance(value, _ExceptionWrapper): + global _silence_exceptions + if not _silence_exceptions: + value.MaybeThrow() + _silence_exceptions = True + logging.error('Subprocess raised an exception:\n%s', value.msg) + sys.exit(1) + + +def _MakeProcessPool(job_params, **job_kwargs): + global _all_pools + global _fork_params + global _fork_kwargs + assert _fork_params is None + assert _fork_kwargs is None + pool_size = min(len(job_params), multiprocessing.cpu_count()) + _fork_params = job_params + _fork_kwargs = job_kwargs + ret = multiprocessing.Pool(pool_size) + _fork_params = None + _fork_kwargs = None + if _all_pools is None: + _all_pools = [] + atexit.register(_TerminatePools) + _all_pools.append(ret) + return ret + + +def ForkAndCall(func, args): + """Runs |func| in a fork'ed process. + + Returns: + A Result object (call .get() to get the return value) + """ + if DISABLE_ASYNC: + pool = None + result = _ImmediateResult(func(*args)) + else: + pool = _MakeProcessPool([args]) # Omit |kwargs|. + result = pool.apply_async(_FuncWrapper(func), (0, )) + pool.close() + return _WrappedResult(result, pool=pool) + + +def BulkForkAndCall(func, arg_tuples, **kwargs): + """Calls |func| in a fork'ed process for each set of args within |arg_tuples|. + + Args: + kwargs: Common keyword arguments to be passed to |func|. + + Yields the return values in order. + """ + arg_tuples = list(arg_tuples) + if not arg_tuples: + return + + if DISABLE_ASYNC: + for args in arg_tuples: + yield func(*args, **kwargs) + return + + pool = _MakeProcessPool(arg_tuples, **kwargs) + wrapped_func = _FuncWrapper(func) + try: + for result in pool.imap(wrapped_func, range(len(arg_tuples))): + _CheckForException(result) + yield result + finally: + pool.close() + pool.join() + _all_pools.remove(pool) diff --git a/third_party/libwebrtc/build/android/gyp/util/protoresources.py b/third_party/libwebrtc/build/android/gyp/util/protoresources.py new file mode 100644 index 0000000000..272574f117 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/protoresources.py @@ -0,0 +1,308 @@ +# Copyright 2020 The Chromium 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 modify resources in protobuf format. + +Format reference: +https://cs.android.com/android/platform/superproject/+/master:frameworks/base/tools/aapt2/Resources.proto +""" + +import logging +import os +import struct +import sys +import zipfile + +from util import build_utils +from util import resource_utils + +sys.path[1:1] = [ + # `Resources_pb2` module imports `descriptor`, which imports `six`. + os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'six', 'src'), + # Make sure the pb2 files are able to import google.protobuf + os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'protobuf', + 'python'), +] + +from proto import Resources_pb2 + +# First bytes in an .flat.arsc file. +# uint32: Magic ("ARSC"), version (1), num_entries (1), type (0) +_FLAT_ARSC_HEADER = b'AAPT\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' + +# The package ID hardcoded for shared libraries. See +# _HardcodeSharedLibraryDynamicAttributes() for more details. If this value +# changes make sure to change REQUIRED_PACKAGE_IDENTIFIER in WebLayerImpl.java. +SHARED_LIBRARY_HARDCODED_ID = 36 + + +def _ProcessZip(zip_path, process_func): + """Filters a .zip file via: new_bytes = process_func(filename, data).""" + has_changes = False + zip_entries = [] + with zipfile.ZipFile(zip_path) as src_zip: + for info in src_zip.infolist(): + data = src_zip.read(info) + new_data = process_func(info.filename, data) + if new_data is not data: + has_changes = True + data = new_data + zip_entries.append((info, data)) + + # Overwrite the original zip file. + if has_changes: + with zipfile.ZipFile(zip_path, 'w') as f: + for info, data in zip_entries: + f.writestr(info, data) + + +def _ProcessProtoItem(item): + if not item.HasField('ref'): + return + + # If this is a dynamic attribute (type ATTRIBUTE, package ID 0), hardcode + # the package to SHARED_LIBRARY_HARDCODED_ID. + if item.ref.type == Resources_pb2.Reference.ATTRIBUTE and not (item.ref.id + & 0xff000000): + item.ref.id |= (0x01000000 * SHARED_LIBRARY_HARDCODED_ID) + item.ref.ClearField('is_dynamic') + + +def _ProcessProtoValue(value): + if value.HasField('item'): + _ProcessProtoItem(value.item) + return + + compound_value = value.compound_value + if compound_value.HasField('style'): + for entry in compound_value.style.entry: + _ProcessProtoItem(entry.item) + elif compound_value.HasField('array'): + for element in compound_value.array.element: + _ProcessProtoItem(element.item) + elif compound_value.HasField('plural'): + for entry in compound_value.plural.entry: + _ProcessProtoItem(entry.item) + + +def _ProcessProtoXmlNode(xml_node): + if not xml_node.HasField('element'): + return + + for attribute in xml_node.element.attribute: + _ProcessProtoItem(attribute.compiled_item) + + for child in xml_node.element.child: + _ProcessProtoXmlNode(child) + + +def _SplitLocaleResourceType(_type, allowed_resource_names): + """Splits locale specific resources out of |_type| and returns them. + + Any locale specific resources will be removed from |_type|, and a new + Resources_pb2.Type value will be returned which contains those resources. + + Args: + _type: A Resources_pb2.Type value + allowed_resource_names: Names of locale resources that should be kept in the + main type. + """ + locale_entries = [] + for entry in _type.entry: + if entry.name in allowed_resource_names: + continue + + # First collect all resources values with a locale set. + config_values_with_locale = [] + for config_value in entry.config_value: + if config_value.config.locale: + config_values_with_locale.append(config_value) + + if config_values_with_locale: + # Remove the locale resources from the original entry + for value in config_values_with_locale: + entry.config_value.remove(value) + + # Add locale resources to a new Entry, and save for later. + locale_entry = Resources_pb2.Entry() + locale_entry.CopyFrom(entry) + del locale_entry.config_value[:] + locale_entry.config_value.extend(config_values_with_locale) + locale_entries.append(locale_entry) + + if not locale_entries: + return None + + # Copy the original type and replace the entries with |locale_entries|. + locale_type = Resources_pb2.Type() + locale_type.CopyFrom(_type) + del locale_type.entry[:] + locale_type.entry.extend(locale_entries) + return locale_type + + +def _HardcodeInTable(table, is_bundle_module, shared_resources_allowlist): + translations_package = None + if is_bundle_module: + # A separate top level package will be added to the resources, which + # contains only locale specific resources. The package ID of the locale + # resources is hardcoded to SHARED_LIBRARY_HARDCODED_ID. This causes + # resources in locale splits to all get assigned + # SHARED_LIBRARY_HARDCODED_ID as their package ID, which prevents a bug + # in shared library bundles where each split APK gets a separate dynamic + # ID, and cannot be accessed by the main APK. + translations_package = Resources_pb2.Package() + translations_package.package_id.id = SHARED_LIBRARY_HARDCODED_ID + translations_package.package_name = (table.package[0].package_name + + '_translations') + + # These resources are allowed in the base resources, since they are needed + # by WebView. + allowed_resource_names = set() + if shared_resources_allowlist: + allowed_resource_names = set( + resource_utils.GetRTxtStringResourceNames(shared_resources_allowlist)) + + for package in table.package: + for _type in package.type: + for entry in _type.entry: + for config_value in entry.config_value: + _ProcessProtoValue(config_value.value) + + if translations_package is not None: + locale_type = _SplitLocaleResourceType(_type, allowed_resource_names) + if locale_type: + translations_package.type.add().CopyFrom(locale_type) + + if translations_package is not None: + table.package.add().CopyFrom(translations_package) + + +def HardcodeSharedLibraryDynamicAttributes(zip_path, + is_bundle_module, + shared_resources_allowlist=None): + """Hardcodes the package IDs of dynamic attributes and locale resources. + + Hardcoding dynamic attribute package IDs is a workaround for b/147674078, + which affects Android versions pre-N. Hardcoding locale resource package IDs + is a workaround for b/155437035, which affects resources built with + --shared-lib on all Android versions + + Args: + zip_path: Path to proto APK file. + is_bundle_module: True for bundle modules. + shared_resources_allowlist: Set of resource names to not extract out of the + main package. + """ + + def process_func(filename, data): + if filename == 'resources.pb': + table = Resources_pb2.ResourceTable() + table.ParseFromString(data) + _HardcodeInTable(table, is_bundle_module, shared_resources_allowlist) + data = table.SerializeToString() + elif filename.endswith('.xml') and not filename.startswith('res/raw'): + xml_node = Resources_pb2.XmlNode() + xml_node.ParseFromString(data) + _ProcessProtoXmlNode(xml_node) + data = xml_node.SerializeToString() + return data + + _ProcessZip(zip_path, process_func) + + +class _ResourceStripper(object): + def __init__(self, partial_path, keep_predicate): + self.partial_path = partial_path + self.keep_predicate = keep_predicate + self._has_changes = False + + @staticmethod + def _IterStyles(entry): + for config_value in entry.config_value: + value = config_value.value + if value.HasField('compound_value'): + compound_value = value.compound_value + if compound_value.HasField('style'): + yield compound_value.style + + def _StripStyles(self, entry, type_and_name): + # Strip style entries that refer to attributes that have been stripped. + for style in self._IterStyles(entry): + entries = style.entry + new_entries = [] + for entry in entries: + full_name = '{}/{}'.format(type_and_name, entry.key.name) + if not self.keep_predicate(full_name): + logging.debug('Stripped %s/%s', self.partial_path, full_name) + else: + new_entries.append(entry) + + if len(new_entries) != len(entries): + self._has_changes = True + del entries[:] + entries.extend(new_entries) + + def _StripEntries(self, entries, type_name): + new_entries = [] + for entry in entries: + type_and_name = '{}/{}'.format(type_name, entry.name) + if not self.keep_predicate(type_and_name): + logging.debug('Stripped %s/%s', self.partial_path, type_and_name) + else: + new_entries.append(entry) + self._StripStyles(entry, type_and_name) + + if len(new_entries) != len(entries): + self._has_changes = True + del entries[:] + entries.extend(new_entries) + + def StripTable(self, table): + self._has_changes = False + for package in table.package: + for _type in package.type: + self._StripEntries(_type.entry, _type.name) + return self._has_changes + + +def _TableFromFlatBytes(data): + # https://cs.android.com/android/platform/superproject/+/master:frameworks/base/tools/aapt2/format/Container.cpp + size_idx = len(_FLAT_ARSC_HEADER) + proto_idx = size_idx + 8 + if data[:size_idx] != _FLAT_ARSC_HEADER: + raise Exception('Error parsing {} in {}'.format(info.filename, zip_path)) + # Size is stored as uint64. + size = struct.unpack('' or '-' + # where is a 2 or 3 letter language code (ISO 639-1 or 639-2) + # and region is a capitalized locale region name. + lang, _, region = chromium_locale.partition('-') + if not region: + return lang + + # Translate newer language tags into obsolete ones. Only necessary if + # region is not None (e.g. 'he-IL' -> 'iw-rIL') + lang = _CHROME_TO_ANDROID_LOCALE_MAP.get(lang, lang) + + # Using '-r' is now acceptable as a locale name for all + # versions of Android. + return '%s-r%s' % (lang, region) + + +# ISO 639 language code + optional ("-r" + capitalized region code). +# Note that before Android 5.0/Lollipop, only 2-letter ISO 639-1 codes +# are supported. +_RE_ANDROID_LOCALE_QUALIFIER_1 = re.compile(r'^([a-z]{2,3})(\-r([A-Z]+))?$') + +# Starting with Android 7.0/Nougat, BCP 47 codes are supported but must +# be prefixed with 'b+', and may include optional tags. +# e.g. 'b+en+US', 'b+ja+Latn', 'b+ja+Latn+JP' +_RE_ANDROID_LOCALE_QUALIFIER_2 = re.compile(r'^b\+([a-z]{2,3})(\+.+)?$') + + +def ToChromiumLocaleName(android_locale): + """Convert an Android locale name into a Chromium one.""" + lang = None + region = None + script = None + m = _RE_ANDROID_LOCALE_QUALIFIER_1.match(android_locale) + if m: + lang = m.group(1) + if m.group(2): + region = m.group(3) + elif _RE_ANDROID_LOCALE_QUALIFIER_2.match(android_locale): + # Split an Android BCP-47 locale (e.g. b+sr+Latn+RS) + tags = android_locale.split('+') + + # The Lang tag is always the first tag. + lang = tags[1] + + # The optional region tag is 2ALPHA or 3DIGIT tag in pos 1 or 2. + # The optional script tag is 4ALPHA and always in pos 1. + optional_tags = iter(tags[2:]) + + next_tag = next(optional_tags, None) + if next_tag and len(next_tag) == 4: + script = next_tag + next_tag = next(optional_tags, None) + if next_tag and len(next_tag) < 4: + region = next_tag + + if not lang: + return None + + # Special case for es-rUS -> es-419 + if lang == 'es' and region == 'US': + return 'es-419' + + lang = _ANDROID_TO_CHROMIUM_LANGUAGE_MAP.get(lang, lang) + + if script: + lang = '%s-%s' % (lang, script) + + if not region: + return lang + + return '%s-%s' % (lang, region) + + +def IsAndroidLocaleQualifier(string): + """Returns true if |string| is a valid Android resource locale qualifier.""" + return (_RE_ANDROID_LOCALE_QUALIFIER_1.match(string) + or _RE_ANDROID_LOCALE_QUALIFIER_2.match(string)) + + +def FindLocaleInStringResourceFilePath(file_path): + """Return Android locale name of a string resource file path. + + Args: + file_path: A file path. + Returns: + If |file_path| is of the format '.../values-/.xml', return + the value of (and Android locale qualifier). Otherwise return None. + """ + if not file_path.endswith('.xml'): + return None + prefix = 'values-' + dir_name = os.path.basename(os.path.dirname(file_path)) + if not dir_name.startswith(prefix): + return None + qualifier = dir_name[len(prefix):] + return qualifier if IsAndroidLocaleQualifier(qualifier) else None + + +def ToAndroidLocaleList(locale_list): + """Convert a list of Chromium locales into the corresponding Android list.""" + return sorted(ToAndroidLocaleName(locale) for locale in locale_list) + +# Represents a line from a R.txt file. +_TextSymbolEntry = collections.namedtuple('RTextEntry', + ('java_type', 'resource_type', 'name', 'value')) + + +def _GenerateGlobs(pattern): + # This function processes the aapt ignore assets pattern into a list of globs + # to be used to exclude files using build_utils.MatchesGlob. It removes the + # '!', which is used by aapt to mean 'not chatty' so it does not output if the + # file is ignored (we dont output anyways, so it is not required). This + # function does not handle the and prefixes used by aapt and are + # assumed not to be included in the pattern string. + return pattern.replace('!', '').split(':') + + +def DeduceResourceDirsFromFileList(resource_files): + """Return a list of resource directories from a list of resource files.""" + # Directory list order is important, cannot use set or other data structures + # that change order. This is because resource files of the same name in + # multiple res/ directories ellide one another (the last one passed is used). + # Thus the order must be maintained to prevent non-deterministic and possibly + # flakey builds. + resource_dirs = [] + for resource_path in resource_files: + # Resources are always 1 directory deep under res/. + res_dir = os.path.dirname(os.path.dirname(resource_path)) + if res_dir not in resource_dirs: + resource_dirs.append(res_dir) + + # Check if any resource_dirs are children of other ones. This indicates that a + # file was listed that is not exactly 1 directory deep under res/. + # E.g.: + # sources = ["java/res/values/foo.xml", "java/res/README.md"] + # ^^ This will cause "java" to be detected as resource directory. + for a, b in itertools.permutations(resource_dirs, 2): + if not os.path.relpath(a, b).startswith('..'): + bad_sources = (s for s in resource_files + if os.path.dirname(os.path.dirname(s)) == b) + msg = """\ +Resource(s) found that are not in a proper directory structure: + {} +All resource files must follow a structure of "$ROOT/$SUBDIR/$FILE".""" + raise Exception(msg.format('\n '.join(bad_sources))) + + return resource_dirs + + +def IterResourceFilesInDirectories(directories, + ignore_pattern=AAPT_IGNORE_PATTERN): + globs = _GenerateGlobs(ignore_pattern) + for d in directories: + for root, _, files in os.walk(d): + for f in files: + archive_path = f + parent_dir = os.path.relpath(root, d) + if parent_dir != '.': + archive_path = os.path.join(parent_dir, f) + path = os.path.join(root, f) + if build_utils.MatchesGlob(archive_path, globs): + continue + yield path, archive_path + + +class ResourceInfoFile(object): + """Helper for building up .res.info files.""" + + def __init__(self): + # Dict of archive_path -> source_path for the current target. + self._entries = {} + # List of (old_archive_path, new_archive_path) tuples. + self._renames = [] + # We don't currently support using both AddMapping and MergeInfoFile. + self._add_mapping_was_called = False + + def AddMapping(self, archive_path, source_path): + """Adds a single |archive_path| -> |source_path| entry.""" + self._add_mapping_was_called = True + # "values/" files do not end up in the apk except through resources.arsc. + if archive_path.startswith('values'): + return + source_path = os.path.normpath(source_path) + new_value = self._entries.setdefault(archive_path, source_path) + if new_value != source_path: + raise Exception('Duplicate AddMapping for "{}". old={} new={}'.format( + archive_path, new_value, source_path)) + + def RegisterRename(self, old_archive_path, new_archive_path): + """Records an archive_path rename. + + |old_archive_path| does not need to currently exist in the mappings. Renames + are buffered and replayed only when Write() is called. + """ + if not old_archive_path.startswith('values'): + self._renames.append((old_archive_path, new_archive_path)) + + def MergeInfoFile(self, info_file_path): + """Merges the mappings from |info_file_path| into this object. + + Any existing entries are overridden. + """ + assert not self._add_mapping_was_called + # Allows clobbering, which is used when overriding resources. + with open(info_file_path) as f: + self._entries.update(l.rstrip().split('\t') for l in f) + + def _ApplyRenames(self): + applied_renames = set() + ret = self._entries + for rename_tup in self._renames: + # Duplicate entries happen for resource overrides. + # Use a "seen" set to ensure we still error out if multiple renames + # happen for the same old_archive_path with different new_archive_paths. + if rename_tup in applied_renames: + continue + applied_renames.add(rename_tup) + old_archive_path, new_archive_path = rename_tup + ret[new_archive_path] = ret[old_archive_path] + del ret[old_archive_path] + + self._entries = None + self._renames = None + return ret + + def Write(self, info_file_path): + """Applies renames and writes out the file. + + No other methods may be called after this. + """ + entries = self._ApplyRenames() + lines = [] + for archive_path, source_path in entries.items(): + lines.append('{}\t{}\n'.format(archive_path, source_path)) + with open(info_file_path, 'w') as info_file: + info_file.writelines(sorted(lines)) + + +def _ParseTextSymbolsFile(path, fix_package_ids=False): + """Given an R.txt file, returns a list of _TextSymbolEntry. + + Args: + path: Input file path. + fix_package_ids: if True, 0x00 and 0x02 package IDs read from the file + will be fixed to 0x7f. + Returns: + A list of _TextSymbolEntry instances. + Raises: + Exception: An unexpected line was detected in the input. + """ + ret = [] + with open(path) 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() + if fix_package_ids: + value = _FixPackageIds(value) + ret.append(_TextSymbolEntry(java_type, resource_type, name, value)) + return ret + + +def _FixPackageIds(resource_value): + # Resource IDs for resources belonging to regular APKs have their first byte + # as 0x7f (package id). However with webview, since it is not a regular apk + # but used as a shared library, aapt is passed the --shared-resources flag + # which changes some of the package ids to 0x00. This function normalises + # these (0x00) package ids to 0x7f, which the generated code in R.java changes + # to the correct package id at runtime. resource_value is a string with + # either, a single value '0x12345678', or an array of values like '{ + # 0xfedcba98, 0x01234567, 0x56789abc }' + return resource_value.replace('0x00', '0x7f') + + +def _GetRTxtResourceNames(r_txt_path): + """Parse an R.txt file and extract the set of resource names from it.""" + return {entry.name for entry in _ParseTextSymbolsFile(r_txt_path)} + + +def GetRTxtStringResourceNames(r_txt_path): + """Parse an R.txt file and the list of its string resource names.""" + return sorted({ + entry.name + for entry in _ParseTextSymbolsFile(r_txt_path) + if entry.resource_type == 'string' + }) + + +def GenerateStringResourcesAllowList(module_r_txt_path, allowlist_r_txt_path): + """Generate a allowlist of string resource IDs. + + Args: + module_r_txt_path: Input base module R.txt path. + allowlist_r_txt_path: Input allowlist R.txt path. + Returns: + A dictionary mapping numerical resource IDs to the corresponding + string resource names. The ID values are taken from string resources in + |module_r_txt_path| that are also listed by name in |allowlist_r_txt_path|. + """ + allowlisted_names = { + entry.name + for entry in _ParseTextSymbolsFile(allowlist_r_txt_path) + if entry.resource_type == 'string' + } + return { + int(entry.value, 0): entry.name + for entry in _ParseTextSymbolsFile(module_r_txt_path) + if entry.resource_type == 'string' and entry.name in allowlisted_names + } + + +class RJavaBuildOptions: + """A class used to model the various ways to build an R.java file. + + This is used to control which resource ID variables will be final or + non-final, and whether an onResourcesLoaded() method will be generated + to adjust the non-final ones, when the corresponding library is loaded + at runtime. + + Note that by default, all resources are final, and there is no + method generated, which corresponds to calling ExportNoResources(). + """ + def __init__(self): + self.has_constant_ids = True + self.resources_allowlist = None + self.has_on_resources_loaded = False + self.export_const_styleable = False + self.final_package_id = None + self.fake_on_resources_loaded = False + + def ExportNoResources(self): + """Make all resource IDs final, and don't generate a method.""" + self.has_constant_ids = True + self.resources_allowlist = None + self.has_on_resources_loaded = False + self.export_const_styleable = False + + def ExportAllResources(self): + """Make all resource IDs non-final in the R.java file.""" + self.has_constant_ids = False + self.resources_allowlist = None + + def ExportSomeResources(self, r_txt_file_path): + """Only select specific resource IDs to be non-final. + + Args: + r_txt_file_path: The path to an R.txt file. All resources named + int it will be non-final in the generated R.java file, all others + will be final. + """ + self.has_constant_ids = True + self.resources_allowlist = _GetRTxtResourceNames(r_txt_file_path) + + def ExportAllStyleables(self): + """Make all styleable constants non-final, even non-resources ones. + + Resources that are styleable but not of int[] type are not actually + resource IDs but constants. By default they are always final. Call this + method to make them non-final anyway in the final R.java file. + """ + self.export_const_styleable = True + + def GenerateOnResourcesLoaded(self, fake=False): + """Generate an onResourcesLoaded() method. + + This Java method will be called at runtime by the framework when + the corresponding library (which includes the R.java source file) + will be loaded at runtime. This corresponds to the --shared-resources + or --app-as-shared-lib flags of 'aapt package'. + + if |fake|, then the method will be empty bodied to compile faster. This + useful for dummy R.java files that will eventually be replaced by real + ones. + """ + self.has_on_resources_loaded = True + self.fake_on_resources_loaded = fake + + def SetFinalPackageId(self, package_id): + """Sets a package ID to be used for resources marked final.""" + self.final_package_id = package_id + + def _MaybeRewriteRTxtPackageIds(self, r_txt_path): + """Rewrites package IDs in the R.txt file if necessary. + + If SetFinalPackageId() was called, some of the resource IDs may have had + their package ID changed. This function rewrites the R.txt file to match + those changes. + """ + if self.final_package_id is None: + return + + entries = _ParseTextSymbolsFile(r_txt_path) + with open(r_txt_path, 'w') as f: + for entry in entries: + value = entry.value + if self._IsResourceFinal(entry): + value = re.sub(r'0x(?:00|7f)', + '0x{:02x}'.format(self.final_package_id), value) + f.write('{} {} {} {}\n'.format(entry.java_type, entry.resource_type, + entry.name, value)) + + def _IsResourceFinal(self, entry): + """Determines whether a resource should be final or not. + + Args: + entry: A _TextSymbolEntry instance. + Returns: + True iff the corresponding entry should be final. + """ + if entry.resource_type == 'styleable' and entry.java_type != 'int[]': + # A styleable constant may be exported as non-final after all. + return not self.export_const_styleable + elif not self.has_constant_ids: + # Every resource is non-final + return False + elif not self.resources_allowlist: + # No allowlist means all IDs are non-final. + return True + else: + # Otherwise, only those in the + return entry.name not in self.resources_allowlist + + +def CreateRJavaFiles(srcjar_dir, + package, + main_r_txt_file, + extra_res_packages, + rjava_build_options, + srcjar_out, + custom_root_package_name=None, + grandparent_custom_package_name=None, + extra_main_r_text_files=None, + ignore_mismatched_values=False): + """Create all R.java files for a set of packages and R.txt files. + + Args: + srcjar_dir: The top-level output directory for the generated files. + package: Package name for R java source files which will inherit + from the root R java file. + main_r_txt_file: The main R.txt file containing the valid values + of _all_ resource IDs. + extra_res_packages: A list of extra package names. + rjava_build_options: An RJavaBuildOptions instance that controls how + exactly the R.java file is generated. + srcjar_out: Path of desired output srcjar. + custom_root_package_name: Custom package name for module root R.java file, + (eg. vr for gen.vr package). + grandparent_custom_package_name: Custom root package name for the root + R.java file to inherit from. DFM root R.java files will have "base" + as the grandparent_custom_package_name. The format of this package name + is identical to custom_root_package_name. + (eg. for vr grandparent_custom_package_name would be "base") + extra_main_r_text_files: R.txt files to be added to the root R.java file. + ignore_mismatched_values: If True, ignores if a resource appears multiple + times with different entry values (useful when all the values are + dummy anyways). + Raises: + Exception if a package name appears several times in |extra_res_packages| + """ + rjava_build_options._MaybeRewriteRTxtPackageIds(main_r_txt_file) + + packages = list(extra_res_packages) + + if package and package not in packages: + # Sometimes, an apk target and a resources target share the same + # AndroidManifest.xml and thus |package| will already be in |packages|. + packages.append(package) + + # Map of (resource_type, name) -> Entry. + # Contains the correct values for resources. + all_resources = {} + all_resources_by_type = collections.defaultdict(list) + + main_r_text_files = [main_r_txt_file] + if extra_main_r_text_files: + main_r_text_files.extend(extra_main_r_text_files) + for r_txt_file in main_r_text_files: + for entry in _ParseTextSymbolsFile(r_txt_file, fix_package_ids=True): + entry_key = (entry.resource_type, entry.name) + if entry_key in all_resources: + if not ignore_mismatched_values: + assert entry == all_resources[entry_key], ( + 'Input R.txt %s provided a duplicate resource with a different ' + 'entry value. Got %s, expected %s.' % + (r_txt_file, entry, all_resources[entry_key])) + else: + all_resources[entry_key] = entry + all_resources_by_type[entry.resource_type].append(entry) + assert entry.resource_type in _ALL_RESOURCE_TYPES, ( + 'Unknown resource type: %s, add to _ALL_RESOURCE_TYPES!' % + entry.resource_type) + + if custom_root_package_name: + # Custom package name is available, thus use it for root_r_java_package. + root_r_java_package = GetCustomPackagePath(custom_root_package_name) + else: + # Create a unique name using srcjar_out. Underscores are added to ensure + # no reserved keywords are used for directory names. + root_r_java_package = re.sub('[^\w\.]', '', srcjar_out.replace('/', '._')) + + root_r_java_dir = os.path.join(srcjar_dir, *root_r_java_package.split('.')) + build_utils.MakeDirectory(root_r_java_dir) + root_r_java_path = os.path.join(root_r_java_dir, 'R.java') + root_java_file_contents = _RenderRootRJavaSource( + root_r_java_package, all_resources_by_type, rjava_build_options, + grandparent_custom_package_name) + with open(root_r_java_path, 'w') as f: + f.write(root_java_file_contents) + + for package in packages: + _CreateRJavaSourceFile(srcjar_dir, package, root_r_java_package, + rjava_build_options) + + +def _CreateRJavaSourceFile(srcjar_dir, package, root_r_java_package, + rjava_build_options): + """Generates an R.java source file.""" + package_r_java_dir = os.path.join(srcjar_dir, *package.split('.')) + build_utils.MakeDirectory(package_r_java_dir) + package_r_java_path = os.path.join(package_r_java_dir, 'R.java') + java_file_contents = _RenderRJavaSource(package, root_r_java_package, + rjava_build_options) + with open(package_r_java_path, 'w') as f: + f.write(java_file_contents) + + +# Resource IDs inside resource arrays are sorted. Application resource IDs start +# with 0x7f but system resource IDs start with 0x01 thus system resource ids are +# always at the start of the array. This function finds the index of the first +# non system resource id to be used for package ID rewriting (we should not +# rewrite system resource ids). +def _GetNonSystemIndex(entry): + """Get the index of the first application resource ID within a resource + array.""" + res_ids = re.findall(r'0x[0-9a-f]{8}', entry.value) + for i, res_id in enumerate(res_ids): + if res_id.startswith('0x7f'): + return i + return len(res_ids) + + +def _RenderRJavaSource(package, root_r_java_package, rjava_build_options): + """Generates the contents of a R.java file.""" + template = Template( + """/* AUTO-GENERATED FILE. DO NOT MODIFY. */ + +package {{ package }}; + +public final class R { + {% for resource_type in resource_types %} + public static final class {{ resource_type }} extends + {{ root_package }}.R.{{ resource_type }} {} + {% endfor %} + {% if has_on_resources_loaded %} + public static void onResourcesLoaded(int packageId) { + {{ root_package }}.R.onResourcesLoaded(packageId); + } + {% endif %} +} +""", + trim_blocks=True, + lstrip_blocks=True) + + return template.render( + package=package, + resource_types=sorted(_ALL_RESOURCE_TYPES), + root_package=root_r_java_package, + has_on_resources_loaded=rjava_build_options.has_on_resources_loaded) + + +def GetCustomPackagePath(package_name): + return 'gen.' + package_name + '_module' + + +def _RenderRootRJavaSource(package, all_resources_by_type, rjava_build_options, + grandparent_custom_package_name): + """Render an R.java source file. See _CreateRJaveSourceFile for args info.""" + final_resources_by_type = collections.defaultdict(list) + non_final_resources_by_type = collections.defaultdict(list) + for res_type, resources in all_resources_by_type.items(): + for entry in resources: + # Entries in stylable that are not int[] are not actually resource ids + # but constants. + if rjava_build_options._IsResourceFinal(entry): + final_resources_by_type[res_type].append(entry) + else: + non_final_resources_by_type[res_type].append(entry) + + # Here we diverge from what aapt does. Because we have so many + # resources, the onResourcesLoaded method was exceeding the 64KB limit that + # Java imposes. For this reason we split onResourcesLoaded into different + # methods for each resource type. + extends_string = '' + dep_path = '' + if grandparent_custom_package_name: + extends_string = 'extends {{ parent_path }}.R.{{ resource_type }} ' + dep_path = GetCustomPackagePath(grandparent_custom_package_name) + + template = Template("""/* AUTO-GENERATED FILE. DO NOT MODIFY. */ + +package {{ package }}; + +public final class R { + {% for resource_type in resource_types %} + public static class {{ resource_type }} """ + extends_string + """ { + {% for e in final_resources[resource_type] %} + public static final {{ e.java_type }} {{ e.name }} = {{ e.value }}; + {% endfor %} + {% for e in non_final_resources[resource_type] %} + {% if e.value != '0' %} + public static {{ e.java_type }} {{ e.name }} = {{ e.value }}; + {% else %} + public static {{ e.java_type }} {{ e.name }}; + {% endif %} + {% endfor %} + } + {% endfor %} + {% if has_on_resources_loaded %} + {% if fake_on_resources_loaded %} + public static void onResourcesLoaded(int packageId) { + } + {% else %} + private static boolean sResourcesDidLoad; + + private static void patchArray( + int[] arr, int startIndex, int packageIdTransform) { + for (int i = startIndex; i < arr.length; ++i) { + arr[i] ^= packageIdTransform; + } + } + + public static void onResourcesLoaded(int packageId) { + if (sResourcesDidLoad) { + return; + } + sResourcesDidLoad = true; + int packageIdTransform = (packageId ^ 0x7f) << 24; + {# aapt2 makes int[] resources refer to other resources by reference + rather than by value. Thus, need to transform the int[] resources + first, before the referenced resources are transformed in order to + ensure the transform applies exactly once. + See https://crbug.com/1237059 for context. + #} + {% for resource_type in resource_types %} + {% for e in non_final_resources[resource_type] %} + {% if e.java_type == 'int[]' %} + patchArray({{ e.resource_type }}.{{ e.name }}, {{ startIndex(e) }}, \ +packageIdTransform); + {% endif %} + {% endfor %} + {% endfor %} + {% for resource_type in resource_types %} + onResourcesLoaded{{ resource_type|title }}(packageIdTransform); + {% endfor %} + } + {% for res_type in resource_types %} + private static void onResourcesLoaded{{ res_type|title }} ( + int packageIdTransform) { + {% for e in non_final_resources[res_type] %} + {% if res_type != 'styleable' and e.java_type != 'int[]' %} + {{ e.resource_type }}.{{ e.name }} ^= packageIdTransform; + {% endif %} + {% endfor %} + } + {% endfor %} + {% endif %} + {% endif %} +} +""", + trim_blocks=True, + lstrip_blocks=True) + return template.render( + package=package, + resource_types=sorted(_ALL_RESOURCE_TYPES), + has_on_resources_loaded=rjava_build_options.has_on_resources_loaded, + fake_on_resources_loaded=rjava_build_options.fake_on_resources_loaded, + final_resources=final_resources_by_type, + non_final_resources=non_final_resources_by_type, + startIndex=_GetNonSystemIndex, + parent_path=dep_path) + + +def ExtractBinaryManifestValues(aapt2_path, apk_path): + """Returns (version_code, version_name, package_name) for the given apk.""" + output = subprocess.check_output([ + aapt2_path, 'dump', 'xmltree', apk_path, '--file', 'AndroidManifest.xml' + ]).decode('utf-8') + version_code = re.search(r'versionCode.*?=(\d*)', output).group(1) + version_name = re.search(r'versionName.*?="(.*?)"', output).group(1) + package_name = re.search(r'package.*?="(.*?)"', output).group(1) + return version_code, version_name, package_name + + +def ExtractArscPackage(aapt2_path, apk_path): + """Returns (package_name, package_id) of resources.arsc from apk_path. + + When the apk does not have any entries in its resources file, in recent aapt2 + versions it will not contain a "Package" line. The package is not even in the + actual resources.arsc/resources.pb file (which itself is mostly empty). Thus + return (None, None) when dump succeeds and there are no errors to indicate + that the package name does not exist in the resources file. + """ + proc = subprocess.Popen([aapt2_path, 'dump', 'resources', apk_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + for line in proc.stdout: + line = line.decode('utf-8') + # Package name=org.chromium.webview_shell id=7f + if line.startswith('Package'): + proc.kill() + parts = line.split() + package_name = parts[1].split('=')[1] + package_id = parts[2][3:] + return package_name, int(package_id, 16) + + # aapt2 currently crashes when dumping webview resources, but not until after + # it prints the "Package" line (b/130553900). + stderr_output = proc.stderr.read().decode('utf-8') + if stderr_output: + sys.stderr.write(stderr_output) + raise Exception('Failed to find arsc package name') + return None, None + + +def _RenameSubdirsWithPrefix(dir_path, prefix): + subdirs = [ + d for d in os.listdir(dir_path) + if os.path.isdir(os.path.join(dir_path, d)) + ] + renamed_subdirs = [] + for d in subdirs: + old_path = os.path.join(dir_path, d) + new_path = os.path.join(dir_path, '{}_{}'.format(prefix, d)) + renamed_subdirs.append(new_path) + os.rename(old_path, new_path) + return renamed_subdirs + + +def _HasMultipleResDirs(zip_path): + """Checks for magic comment set by prepare_resources.py + + Returns: True iff the zipfile has the magic comment that means it contains + multiple res/ dirs inside instead of just contents of a single res/ dir + (without a wrapping res/). + """ + with zipfile.ZipFile(zip_path) as z: + return z.comment == MULTIPLE_RES_MAGIC_STRING + + +def ExtractDeps(dep_zips, deps_dir): + """Extract a list of resource dependency zip files. + + Args: + dep_zips: A list of zip file paths, each one will be extracted to + a subdirectory of |deps_dir|, named after the zip file's path (e.g. + '/some/path/foo.zip' -> '{deps_dir}/some_path_foo/'). + deps_dir: Top-level extraction directory. + Returns: + The list of all sub-directory paths, relative to |deps_dir|. + Raises: + Exception: If a sub-directory already exists with the same name before + extraction. + """ + dep_subdirs = [] + for z in dep_zips: + subdirname = z.replace(os.path.sep, '_') + subdir = os.path.join(deps_dir, subdirname) + if os.path.exists(subdir): + raise Exception('Resource zip name conflict: ' + subdirname) + build_utils.ExtractAll(z, path=subdir) + if _HasMultipleResDirs(z): + # basename of the directory is used to create a zip during resource + # compilation, include the path in the basename to help blame errors on + # the correct target. For example directory 0_res may be renamed + # chrome_android_chrome_app_java_resources_0_res pointing to the name and + # path of the android_resources target from whence it came. + subdir_subdirs = _RenameSubdirsWithPrefix(subdir, subdirname) + dep_subdirs.extend(subdir_subdirs) + else: + dep_subdirs.append(subdir) + return dep_subdirs + + +class _ResourceBuildContext(object): + """A temporary directory for packaging and compiling Android resources. + + Args: + temp_dir: Optional root build directory path. If None, a temporary + directory will be created, and removed in Close(). + """ + + def __init__(self, temp_dir=None, keep_files=False): + """Initialized the context.""" + # The top-level temporary directory. + if temp_dir: + self.temp_dir = temp_dir + os.makedirs(temp_dir) + else: + self.temp_dir = tempfile.mkdtemp() + self.remove_on_exit = not keep_files + + # A location to store resources extracted form dependency zip files. + self.deps_dir = os.path.join(self.temp_dir, 'deps') + os.mkdir(self.deps_dir) + # A location to place aapt-generated files. + self.gen_dir = os.path.join(self.temp_dir, 'gen') + os.mkdir(self.gen_dir) + # A location to place generated R.java files. + self.srcjar_dir = os.path.join(self.temp_dir, 'java') + os.mkdir(self.srcjar_dir) + # Temporary file locacations. + self.r_txt_path = os.path.join(self.gen_dir, 'R.txt') + self.srcjar_path = os.path.join(self.temp_dir, 'R.srcjar') + self.info_path = os.path.join(self.temp_dir, 'size.info') + self.stable_ids_path = os.path.join(self.temp_dir, 'in_ids.txt') + self.emit_ids_path = os.path.join(self.temp_dir, 'out_ids.txt') + self.proguard_path = os.path.join(self.temp_dir, 'keeps.flags') + self.proguard_main_dex_path = os.path.join(self.temp_dir, 'maindex.flags') + self.arsc_path = os.path.join(self.temp_dir, 'out.ap_') + self.proto_path = os.path.join(self.temp_dir, 'out.proto.ap_') + self.optimized_arsc_path = os.path.join(self.temp_dir, 'out.opt.ap_') + self.optimized_proto_path = os.path.join(self.temp_dir, 'out.opt.proto.ap_') + + def Close(self): + """Close the context and destroy all temporary files.""" + if self.remove_on_exit: + shutil.rmtree(self.temp_dir) + + +@contextlib.contextmanager +def BuildContext(temp_dir=None, keep_files=False): + """Generator for a _ResourceBuildContext instance.""" + context = None + try: + context = _ResourceBuildContext(temp_dir, keep_files) + yield context + finally: + if context: + context.Close() + + +def ResourceArgsParser(): + """Create an argparse.ArgumentParser instance with common argument groups. + + Returns: + A tuple of (parser, in_group, out_group) corresponding to the parser + instance, and the input and output argument groups for it, respectively. + """ + parser = argparse.ArgumentParser(description=__doc__) + + input_opts = parser.add_argument_group('Input options') + output_opts = parser.add_argument_group('Output options') + + build_utils.AddDepfileOption(output_opts) + + input_opts.add_argument('--include-resources', required=True, action="append", + help='Paths to arsc resource files used to link ' + 'against. Can be specified multiple times.') + + input_opts.add_argument('--dependencies-res-zips', required=True, + help='Resources zip archives from dependents. Required to ' + 'resolve @type/foo references into dependent ' + 'libraries.') + + input_opts.add_argument( + '--extra-res-packages', + help='Additional package names to generate R.java files for.') + + return (parser, input_opts, output_opts) + + +def HandleCommonOptions(options): + """Handle common command-line options after parsing. + + Args: + options: the result of parse_args() on the parser returned by + ResourceArgsParser(). This function updates a few common fields. + """ + options.include_resources = [build_utils.ParseGnList(r) for r in + options.include_resources] + # Flatten list of include resources list to make it easier to use. + options.include_resources = [r for resources in options.include_resources + for r in resources] + + options.dependencies_res_zips = ( + build_utils.ParseGnList(options.dependencies_res_zips)) + + # Don't use [] as default value since some script explicitly pass "". + if options.extra_res_packages: + options.extra_res_packages = ( + build_utils.ParseGnList(options.extra_res_packages)) + else: + options.extra_res_packages = [] + + +def ParseAndroidResourceStringsFromXml(xml_data): + """Parse and Android xml resource file and extract strings from it. + + Args: + xml_data: XML file data. + Returns: + A (dict, namespaces) tuple, where |dict| maps string names to their UTF-8 + encoded value, and |namespaces| is a dictionary mapping prefixes to URLs + corresponding to namespaces declared in the element. + """ + # NOTE: This uses regular expression matching because parsing with something + # like ElementTree makes it tedious to properly parse some of the structured + # text found in string resources, e.g.: + # \ + # "Condividi tramite %s"\ + # + result = {} + + # Find start tag and extract namespaces from it. + m = re.search(']*)>', xml_data, re.MULTILINE) + if not m: + raise Exception(' start tag expected: ' + xml_data) + input_data = xml_data[m.end():] + resource_attrs = m.group(1) + re_namespace = re.compile('\s*(xmlns:(\w+)="([^"]+)")') + namespaces = {} + while resource_attrs: + m = re_namespace.match(resource_attrs) + if not m: + break + namespaces[m.group(2)] = m.group(3) + resource_attrs = resource_attrs[m.end(1):] + + # Find each string element now. + re_string_element_start = re.compile(']* )?name="([^">]+)"[^>]*>') + re_string_element_end = re.compile('') + while input_data: + m = re_string_element_start.search(input_data) + if not m: + break + name = m.group(2) + input_data = input_data[m.end():] + m2 = re_string_element_end.search(input_data) + if not m2: + raise Exception('Expected closing string tag: ' + input_data) + text = input_data[:m2.start()] + input_data = input_data[m2.end():] + if len(text) and text[0] == '"' and text[-1] == '"': + text = text[1:-1] + result[name] = text + + return result, namespaces + + +def GenerateAndroidResourceStringsXml(names_to_utf8_text, namespaces=None): + """Generate an XML text corresponding to an Android resource strings map. + + Args: + names_to_text: A dictionary mapping resource names to localized + text (encoded as UTF-8). + namespaces: A map of namespace prefix to URL. + Returns: + New non-Unicode string containing an XML data structure describing the + input as an Android resource .xml file. + """ + result = '\n' + result += ' element should be kept in the file. + + Args: + xml_file_path: Android resource strings xml file path. + string_predicate: A predicate function which will receive the string name + and shal + """ + with open(xml_file_path) as f: + xml_data = f.read() + strings_map, namespaces = ParseAndroidResourceStringsFromXml(xml_data) + + string_deletion = False + for name in list(strings_map.keys()): + if not string_predicate(name): + del strings_map[name] + string_deletion = True + + if string_deletion: + new_xml_data = GenerateAndroidResourceStringsXml(strings_map, namespaces) + with open(xml_file_path, 'wb') as f: + f.write(new_xml_data) diff --git a/third_party/libwebrtc/build/android/gyp/util/resource_utils_test.py b/third_party/libwebrtc/build/android/gyp/util/resource_utils_test.py new file mode 100755 index 0000000000..62d5b431e9 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/resource_utils_test.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# coding: utf-8 +# Copyright 2018 The Chromium 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 os +import sys +import unittest + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +from util import build_utils + +# Required because the following import needs build/android/gyp in the +# Python path to import util.build_utils. +_BUILD_ANDROID_GYP_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir)) +sys.path.insert(1, _BUILD_ANDROID_GYP_ROOT) + +import resource_utils # pylint: disable=relative-import + +# pylint: disable=line-too-long + +_TEST_XML_INPUT_1 = ''' + +"Lõikelauale kopeerimine ebaõnnestus" +"Eelmist toimingut ei saa vähese mälu tõttu lõpetada" +"Valit. faili avamine ebaõnnestus" +"This is %s" + +''' + +_TEST_XML_OUTPUT_2 = ''' + +"Eelmist toimingut ei saa vähese mälu tõttu lõpetada" +"This is %s" + +''' + +# pylint: enable=line-too-long + +_TEST_XML_OUTPUT_EMPTY = ''' + + + +''' + +_TEST_RESOURCES_MAP_1 = { + 'low_memory_error': 'Eelmist toimingut ei saa vähese mälu tõttu lõpetada', + 'opening_file_error': 'Valit. faili avamine ebaõnnestus', + 'copy_to_clipboard_failure_message': 'Lõikelauale kopeerimine ebaõnnestus', + 'structured_text': 'This is %s', +} + +_TEST_NAMESPACES_1 = {'android': 'http://schemas.android.com/apk/res/android'} + +_TEST_RESOURCES_ALLOWLIST_1 = ['low_memory_error', 'structured_text'] + +# Extracted from one generated Chromium R.txt file, with string resource +# names shuffled randomly. +_TEST_R_TXT = r'''int anim abc_fade_in 0x7f050000 +int anim abc_fade_out 0x7f050001 +int anim abc_grow_fade_in_from_bottom 0x7f050002 +int array DefaultCookiesSettingEntries 0x7f120002 +int array DefaultCookiesSettingValues 0x7f120003 +int array DefaultGeolocationSettingEntries 0x7f120004 +int attr actionBarDivider 0x7f0100e7 +int attr actionBarStyle 0x7f0100e2 +int string AllowedDomainsForAppsDesc 0x7f0c0105 +int string AlternateErrorPagesEnabledDesc 0x7f0c0107 +int string AuthAndroidNegotiateAccountTypeDesc 0x7f0c0109 +int string AllowedDomainsForAppsTitle 0x7f0c0104 +int string AlternateErrorPagesEnabledTitle 0x7f0c0106 +int[] styleable SnackbarLayout { 0x0101011f, 0x7f010076, 0x7f0100ba } +int styleable SnackbarLayout_android_maxWidth 0 +int styleable SnackbarLayout_elevation 2 +''' + +# Test allowlist R.txt file. Note that AlternateErrorPagesEnabledTitle is +# listed as an 'anim' and should thus be skipped. Similarly the string +# 'ThisStringDoesNotAppear' should not be in the final result. +_TEST_ALLOWLIST_R_TXT = r'''int anim AlternateErrorPagesEnabledTitle 0x7f0eeeee +int string AllowedDomainsForAppsDesc 0x7f0c0105 +int string AlternateErrorPagesEnabledDesc 0x7f0c0107 +int string ThisStringDoesNotAppear 0x7f0fffff +''' + +_TEST_R_TEXT_RESOURCES_IDS = { + 0x7f0c0105: 'AllowedDomainsForAppsDesc', + 0x7f0c0107: 'AlternateErrorPagesEnabledDesc', +} + +# Names of string resources in _TEST_R_TXT, should be sorted! +_TEST_R_TXT_STRING_RESOURCE_NAMES = sorted([ + 'AllowedDomainsForAppsDesc', + 'AllowedDomainsForAppsTitle', + 'AlternateErrorPagesEnabledDesc', + 'AlternateErrorPagesEnabledTitle', + 'AuthAndroidNegotiateAccountTypeDesc', +]) + + +def _CreateTestFile(tmp_dir, file_name, file_data): + file_path = os.path.join(tmp_dir, file_name) + with open(file_path, 'wt') as f: + f.write(file_data) + return file_path + + + +class ResourceUtilsTest(unittest.TestCase): + + def test_GetRTxtStringResourceNames(self): + with build_utils.TempDir() as tmp_dir: + tmp_file = _CreateTestFile(tmp_dir, "test_R.txt", _TEST_R_TXT) + self.assertListEqual( + resource_utils.GetRTxtStringResourceNames(tmp_file), + _TEST_R_TXT_STRING_RESOURCE_NAMES) + + def test_GenerateStringResourcesAllowList(self): + with build_utils.TempDir() as tmp_dir: + tmp_module_rtxt_file = _CreateTestFile(tmp_dir, "test_R.txt", _TEST_R_TXT) + tmp_allowlist_rtxt_file = _CreateTestFile(tmp_dir, "test_allowlist_R.txt", + _TEST_ALLOWLIST_R_TXT) + self.assertDictEqual( + resource_utils.GenerateStringResourcesAllowList( + tmp_module_rtxt_file, tmp_allowlist_rtxt_file), + _TEST_R_TEXT_RESOURCES_IDS) + + def test_IsAndroidLocaleQualifier(self): + good_locales = [ + 'en', + 'en-rUS', + 'fil', + 'fil-rPH', + 'iw', + 'iw-rIL', + 'b+en', + 'b+en+US', + 'b+ja+Latn', + 'b+ja+JP+Latn', + 'b+cmn+Hant-TW', + ] + bad_locales = [ + 'e', 'english', 'en-US', 'en_US', 'en-rus', 'b+e', 'b+english', 'b+ja+' + ] + for locale in good_locales: + self.assertTrue( + resource_utils.IsAndroidLocaleQualifier(locale), + msg="'%s' should be a good locale!" % locale) + + for locale in bad_locales: + self.assertFalse( + resource_utils.IsAndroidLocaleQualifier(locale), + msg="'%s' should be a bad locale!" % locale) + + def test_ToAndroidLocaleName(self): + _TEST_CHROMIUM_TO_ANDROID_LOCALE_MAP = { + 'en': 'en', + 'en-US': 'en-rUS', + 'en-FOO': 'en-rFOO', + 'fil': 'tl', + 'tl': 'tl', + 'he': 'iw', + 'he-IL': 'iw-rIL', + 'id': 'in', + 'id-BAR': 'in-rBAR', + 'nb': 'nb', + 'yi': 'ji' + } + for chromium_locale, android_locale in \ + _TEST_CHROMIUM_TO_ANDROID_LOCALE_MAP.items(): + result = resource_utils.ToAndroidLocaleName(chromium_locale) + self.assertEqual(result, android_locale) + + def test_ToChromiumLocaleName(self): + _TEST_ANDROID_TO_CHROMIUM_LOCALE_MAP = { + 'foo': 'foo', + 'foo-rBAR': 'foo-BAR', + 'b+lll': 'lll', + 'b+ll+Extra': 'll', + 'b+ll+RR': 'll-RR', + 'b+lll+RR+Extra': 'lll-RR', + 'b+ll+RRR+Extra': 'll-RRR', + 'b+ll+Ssss': 'll-Ssss', + 'b+ll+Ssss+Extra': 'll-Ssss', + 'b+ll+Ssss+RR': 'll-Ssss-RR', + 'b+ll+Ssss+RRR': 'll-Ssss-RRR', + 'b+ll+Ssss+RRR+Extra': 'll-Ssss-RRR', + 'b+ll+Whatever': 'll', + 'en': 'en', + 'en-rUS': 'en-US', + 'en-US': None, + 'en-FOO': None, + 'en-rFOO': 'en-FOO', + 'es-rES': 'es-ES', + 'es-rUS': 'es-419', + 'tl': 'fil', + 'fil': 'fil', + 'iw': 'he', + 'iw-rIL': 'he-IL', + 'b+iw+IL': 'he-IL', + 'in': 'id', + 'in-rBAR': 'id-BAR', + 'id-rBAR': 'id-BAR', + 'nb': 'nb', + 'no': 'nb', # http://crbug.com/920960 + } + for android_locale, chromium_locale in \ + _TEST_ANDROID_TO_CHROMIUM_LOCALE_MAP.items(): + result = resource_utils.ToChromiumLocaleName(android_locale) + self.assertEqual(result, chromium_locale) + + def test_FindLocaleInStringResourceFilePath(self): + self.assertEqual( + None, + resource_utils.FindLocaleInStringResourceFilePath( + 'res/values/whatever.xml')) + self.assertEqual( + 'foo', + resource_utils.FindLocaleInStringResourceFilePath( + 'res/values-foo/whatever.xml')) + self.assertEqual( + 'foo-rBAR', + resource_utils.FindLocaleInStringResourceFilePath( + 'res/values-foo-rBAR/whatever.xml')) + self.assertEqual( + None, + resource_utils.FindLocaleInStringResourceFilePath( + 'res/values-foo/ignore-subdirs/whatever.xml')) + + def test_ParseAndroidResourceStringsFromXml(self): + ret, namespaces = resource_utils.ParseAndroidResourceStringsFromXml( + _TEST_XML_INPUT_1) + self.assertDictEqual(ret, _TEST_RESOURCES_MAP_1) + self.assertDictEqual(namespaces, _TEST_NAMESPACES_1) + + def test_GenerateAndroidResourceStringsXml(self): + # Fist, an empty strings map, with no namespaces + result = resource_utils.GenerateAndroidResourceStringsXml({}) + self.assertEqual(result.decode('utf8'), _TEST_XML_OUTPUT_EMPTY) + + result = resource_utils.GenerateAndroidResourceStringsXml( + _TEST_RESOURCES_MAP_1, _TEST_NAMESPACES_1) + self.assertEqual(result.decode('utf8'), _TEST_XML_INPUT_1) + + @staticmethod + def _CreateTestResourceFile(output_dir, locale, string_map, namespaces): + values_dir = os.path.join(output_dir, 'values-' + locale) + build_utils.MakeDirectory(values_dir) + file_path = os.path.join(values_dir, 'strings.xml') + with open(file_path, 'wb') as f: + file_data = resource_utils.GenerateAndroidResourceStringsXml( + string_map, namespaces) + f.write(file_data) + return file_path + + def _CheckTestResourceFile(self, file_path, expected_data): + with open(file_path) as f: + file_data = f.read() + self.assertEqual(file_data, expected_data) + + def test_FilterAndroidResourceStringsXml(self): + with build_utils.TempDir() as tmp_path: + test_file = self._CreateTestResourceFile( + tmp_path, 'foo', _TEST_RESOURCES_MAP_1, _TEST_NAMESPACES_1) + resource_utils.FilterAndroidResourceStringsXml( + test_file, lambda x: x in _TEST_RESOURCES_ALLOWLIST_1) + self._CheckTestResourceFile(test_file, _TEST_XML_OUTPUT_2) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/gyp/util/resources_parser.py b/third_party/libwebrtc/build/android/gyp/util/resources_parser.py new file mode 100644 index 0000000000..8d8d69cce8 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/resources_parser.py @@ -0,0 +1,142 @@ +# Copyright 2020 The Chromium 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 os +import re +from xml.etree import ElementTree + +from util import build_utils +from util import resource_utils + +_TextSymbolEntry = collections.namedtuple( + 'RTextEntry', ('java_type', 'resource_type', 'name', 'value')) + +_DUMMY_RTXT_ID = '0x7f010001' +_DUMMY_RTXT_INDEX = '1' + + +def _ResourceNameToJavaSymbol(resource_name): + return re.sub('[\.:]', '_', resource_name) + + +class RTxtGenerator(object): + def __init__(self, + res_dirs, + ignore_pattern=resource_utils.AAPT_IGNORE_PATTERN): + self.res_dirs = res_dirs + self.ignore_pattern = ignore_pattern + + def _ParseDeclareStyleable(self, node): + ret = set() + stylable_name = _ResourceNameToJavaSymbol(node.attrib['name']) + ret.add( + _TextSymbolEntry('int[]', 'styleable', stylable_name, + '{{{}}}'.format(_DUMMY_RTXT_ID))) + for child in node: + if child.tag == 'eat-comment': + continue + if child.tag != 'attr': + # This parser expects everything inside to be either + # an attr or an eat-comment. If new resource xml files are added that do + # not conform to this, this parser needs updating. + raise Exception('Unexpected tag {} inside '.format( + child.tag)) + entry_name = '{}_{}'.format( + stylable_name, _ResourceNameToJavaSymbol(child.attrib['name'])) + ret.add( + _TextSymbolEntry('int', 'styleable', entry_name, _DUMMY_RTXT_INDEX)) + if not child.attrib['name'].startswith('android:'): + resource_name = _ResourceNameToJavaSymbol(child.attrib['name']) + ret.add(_TextSymbolEntry('int', 'attr', resource_name, _DUMMY_RTXT_ID)) + for entry in child: + if entry.tag not in ('enum', 'flag'): + # This parser expects everything inside to be either an + # or an . If new resource xml files are added that do + # not conform to this, this parser needs updating. + raise Exception('Unexpected tag {} inside '.format(entry.tag)) + resource_name = _ResourceNameToJavaSymbol(entry.attrib['name']) + ret.add(_TextSymbolEntry('int', 'id', resource_name, _DUMMY_RTXT_ID)) + return ret + + def _ExtractNewIdsFromNode(self, node): + ret = set() + # Sometimes there are @+id/ in random attributes (not just in android:id) + # and apparently that is valid. See: + # https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html + for value in node.attrib.values(): + if value.startswith('@+id/'): + resource_name = value[5:] + ret.add(_TextSymbolEntry('int', 'id', resource_name, _DUMMY_RTXT_ID)) + for child in node: + ret.update(self._ExtractNewIdsFromNode(child)) + return ret + + def _ExtractNewIdsFromXml(self, xml_path): + root = ElementTree.parse(xml_path).getroot() + return self._ExtractNewIdsFromNode(root) + + def _ParseValuesXml(self, xml_path): + ret = set() + root = ElementTree.parse(xml_path).getroot() + assert root.tag == 'resources' + for child in root: + if child.tag == 'eat-comment': + # eat-comment is just a dummy documentation element. + continue + if child.tag == 'skip': + # skip is just a dummy element. + continue + if child.tag == 'declare-styleable': + ret.update(self._ParseDeclareStyleable(child)) + else: + if child.tag == 'item': + resource_type = child.attrib['type'] + elif child.tag in ('array', 'integer-array', 'string-array'): + resource_type = 'array' + else: + resource_type = child.tag + name = _ResourceNameToJavaSymbol(child.attrib['name']) + ret.add(_TextSymbolEntry('int', resource_type, name, _DUMMY_RTXT_ID)) + return ret + + def _CollectResourcesListFromDirectory(self, res_dir): + ret = set() + globs = resource_utils._GenerateGlobs(self.ignore_pattern) + for root, _, files in os.walk(res_dir): + resource_type = os.path.basename(root) + if '-' in resource_type: + resource_type = resource_type[:resource_type.index('-')] + for f in files: + if build_utils.MatchesGlob(f, globs): + continue + if resource_type == 'values': + ret.update(self._ParseValuesXml(os.path.join(root, f))) + else: + if '.' in f: + resource_name = f[:f.index('.')] + else: + resource_name = f + ret.add( + _TextSymbolEntry('int', resource_type, resource_name, + _DUMMY_RTXT_ID)) + # Other types not just layouts can contain new ids (eg: Menus and + # Drawables). Just in case, look for new ids in all files. + if f.endswith('.xml'): + ret.update(self._ExtractNewIdsFromXml(os.path.join(root, f))) + return ret + + def _CollectResourcesListFromDirectories(self): + ret = set() + for res_dir in self.res_dirs: + ret.update(self._CollectResourcesListFromDirectory(res_dir)) + return ret + + def WriteRTxtFile(self, rtxt_path): + resources = self._CollectResourcesListFromDirectories() + with build_utils.AtomicOutput(rtxt_path, mode='w') as f: + for resource in resources: + line = '{0.java_type} {0.resource_type} {0.name} {0.value}\n'.format( + resource) + f.write(line) diff --git a/third_party/libwebrtc/build/android/gyp/util/server_utils.py b/third_party/libwebrtc/build/android/gyp/util/server_utils.py new file mode 100644 index 0000000000..e050ef6552 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/server_utils.py @@ -0,0 +1,41 @@ +# Copyright 2021 The Chromium 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 json +import os +import socket + +# Use a unix abstract domain socket: +# https://man7.org/linux/man-pages/man7/unix.7.html#:~:text=abstract: +SOCKET_ADDRESS = '\0chromium_build_server_socket' +BUILD_SERVER_ENV_VARIABLE = 'INVOKED_BY_BUILD_SERVER' + + +def MaybeRunCommand(name, argv, stamp_file): + """Returns True if the command was successfully sent to the build server.""" + + # When the build server runs a command, it sets this environment variable. + # This prevents infinite recursion where the script sends a request to the + # build server, then the build server runs the script, and then the script + # sends another request to the build server. + if BUILD_SERVER_ENV_VARIABLE in os.environ: + return False + with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: + try: + sock.connect(SOCKET_ADDRESS) + sock.sendall( + json.dumps({ + 'name': name, + 'cmd': argv, + 'cwd': os.getcwd(), + 'stamp_file': stamp_file, + }).encode('utf8')) + except socket.error as e: + # [Errno 111] Connection refused. Either the server has not been started + # or the server is not currently accepting new connections. + if e.errno == 111: + return False + raise e + return True diff --git a/third_party/libwebrtc/build/android/gyp/util/zipalign.py b/third_party/libwebrtc/build/android/gyp/util/zipalign.py new file mode 100644 index 0000000000..c5c4ea88c6 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/util/zipalign.py @@ -0,0 +1,97 @@ +# Copyright 2019 The Chromium 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 struct +import sys +import zipfile + +from util import build_utils + +_FIXED_ZIP_HEADER_LEN = 30 + + +def _PatchedDecodeExtra(self): + # Try to decode the extra field. + extra = self.extra + unpack = struct.unpack + while len(extra) >= 4: + tp, ln = unpack('= 24: + counts = unpack('Target type `group`: + +This type corresponds to a simple target that is only used to group +dependencies. It matches the `java_group()` GN template. Its only top-level +`deps_info` keys are `supports_android` (always True), and `deps_configs`. + + +## Target type `android_resources`: + +This type corresponds to targets that are used to group Android resource files. +For example, all `android_resources` dependencies of an `android_apk` will +end up packaged into the final APK by the build system. + +It uses the following keys: + + +* `deps_info['res_sources_path']`: +Path to file containing a list of resource source files used by the +android_resources target. + +* `deps_info['resources_zip']`: +*Required*. Path to the `.resources.zip` file that contains all raw/uncompiled +resource files for this target (and also no `R.txt`, `R.java` or `R.class`). + + If `deps_info['res_sources_path']` is missing, this must point to a prebuilt + `.aar` archive containing resources. Otherwise, this will point to a zip + archive generated at build time, wrapping the sources listed in + `deps_info['res_sources_path']` into a single zip file. + +* `deps_info['package_name']`: +Java package name that the R class for this target belongs to. + +* `deps_info['android_manifest']`: +Optional. Path to the top-level Android manifest file associated with these +resources (if not provided, an empty manifest will be used to generate R.txt). + +* `deps_info['resource_overlay']`: +Optional. Whether the resources in resources_zip should override resources with +the same name. Does not affect the behaviour of any android_resources() +dependencies of this target. If a target with resource_overlay=true depends +on another target with resource_overlay=true the target with the dependency +overrides the other. + +* `deps_info['r_text_path']`: +Provide the path to the `R.txt` file that describes the resources wrapped by +this target. Normally this file is generated from the content of the resource +directories or zip file, but some targets can provide their own `R.txt` file +if they want. + +* `deps_info['srcjar_path']`: +Path to the `.srcjar` file that contains the auto-generated `R.java` source +file corresponding to the content of `deps_info['r_text_path']`. This is +*always* generated from the content of `deps_info['r_text_path']` by the +`build/android/gyp/process_resources.py` script. + +* `deps_info['static_library_dependent_classpath_configs']`: +Sub dictionary mapping .build_config paths to lists of jar files. For static +library APKs, this defines which input jars belong to each +static_library_dependent_target. + +* `deps_info['static_library_proguard_mapping_output_paths']`: +Additional paths to copy the ProGuard mapping file to for static library +APKs. + +## Target type `android_assets`: + +This type corresponds to targets used to group Android assets, i.e. liberal +files that will be placed under `//assets/` within the final APK. + +These use an `deps_info['assets']` key to hold a dictionary of values related +to assets covered by this target. + +* `assets['sources']`: +The list of all asset source paths for this target. Each source path can +use an optional `:` suffix, where `` is the final location +of the assets (relative to `//assets/`) within the APK. + +* `assets['outputs']`: +Optional. Some of the sources might be renamed before being stored in the +final //assets/ sub-directory. When this happens, this contains a list of +all renamed output file paths + + NOTE: When not empty, the first items of `assets['sources']` must match + every item in this list. Extra sources correspond to non-renamed sources. + + NOTE: This comes from the `asset_renaming_destinations` parameter for the + `android_assets()` GN template. + +* `assets['disable_compression']`: +Optional. Will be True to indicate that these assets should be stored +uncompressed in the final APK. For example, this is necessary for locale +.pak files used by the System WebView feature. + +* `assets['treat_as_locale_paks']`: +Optional. Will be True to indicate that these assets are locale `.pak` files +(containing localized strings for C++). These are later processed to generate +a special ``.build_config`.java` source file, listing all supported Locales in +the current build. + + +## Target type `java_library`: + +This type is used to describe target that wrap Java bytecode, either created +by compiling sources, or providing them with a prebuilt jar. + +* `deps_info['public_deps_configs']`: List of paths to the `.build_config` files +of *direct* dependencies of the current target which are exposed as part of the +current target's public API. This should be a subset of +deps_info['deps_configs']. + +* `deps_info['ignore_dependency_public_deps']`: If true, 'public_deps' will not +be collected from the current target's direct deps. + +* `deps_info['unprocessed_jar_path']`: +Path to the original .jar file for this target, before any kind of processing +through Proguard or other tools. For most targets this is generated +from sources, with a name like `$target_name.javac.jar`. However, when using +a prebuilt jar, this will point to the source archive directly. + +* `deps_info['device_jar_path']`: +Path to a file that is the result of processing +`deps_info['unprocessed_jar_path']` with various tools (ready to be dexed). + +* `deps_info['host_jar_path']`: +Path to a file that is the result of processing +`deps_info['unprocessed_jar_path']` with various tools (use by java_binary). + +* `deps_info['interface_jar_path']: +Path to the interface jar generated for this library. This corresponds to +a jar file that only contains declarations. Generated by running the `ijar` on +`deps_info['unprocessed_jar_path']` or the `turbine` tool on source files. + +* `deps_info['dex_path']`: +Path to the `.dex` file generated for this target, from +`deps_info['device_jar_path']` unless this comes from a prebuilt `.aar` archive. + +* `deps_info['is_prebuilt']`: +True to indicate that this target corresponds to a prebuilt `.jar` file. +In this case, `deps_info['unprocessed_jar_path']` will point to the source +`.jar` file. Otherwise, it will be point to a build-generated file. + +* `deps_info['java_sources_file']`: +Path to a single `.sources` file listing all the Java sources that were used +to generate the library (simple text format, one `.jar` path per line). + +* `deps_info['lint_android_manifest']`: +Path to an AndroidManifest.xml file to use for this lint target. + +* `deps_info['lint_java_sources']`: +The list of all `deps_info['java_sources_file']` entries for all library +dependencies that are chromium code. Note: this is a list of files, where each +file contains a list of Java source files. This is used for lint. + +* `deps_info['lint_aars']`: +List of all aars from transitive java dependencies. This allows lint to collect +their custom annotations.zip and run checks like @IntDef on their annotations. + +* `deps_info['lint_srcjars']`: +List of all bundled srcjars of all transitive java library targets. Excludes +non-chromium java libraries. + +* `deps_info['lint_resource_sources']`: +List of all resource sources files belonging to all transitive resource +dependencies of this target. Excludes resources owned by non-chromium code. + +* `deps_info['lint_resource_zips']`: +List of all resource zip files belonging to all transitive resource dependencies +of this target. Excludes resources owned by non-chromium code. + +* `deps_info['javac']`: +A dictionary containing information about the way the sources in this library +are compiled. Appears also on other Java-related targets. See the [dedicated +section about this](#dict_javac) below for details. + +* `deps_info['javac_full_classpath']`: +The classpath used when performing bytecode processing. Essentially the +collection of all `deps_info['unprocessed_jar_path']` entries for the target +and all its dependencies. + +* `deps_info['javac_full_interface_classpath']`: +The classpath used when using the errorprone compiler. + +* `deps_info['proguard_enabled"]`: +True to indicate that ProGuard processing is enabled for this target. + +* `deps_info['proguard_configs"]`: +A list of paths to ProGuard configuration files related to this library. + +* `deps_info['extra_classpath_jars']: +For some Java related types, a list of extra `.jar` files to use at build time +but not at runtime. + +## Target type `java_binary`: + +This type corresponds to a Java binary, which is nothing more than a +`java_library` target that also provides a main class name. It thus inherits +all entries from the `java_library` type, and adds: + +* `deps_info['main_class']`: +Name of the main Java class that serves as an entry point for the binary. + +* `deps_info['device_classpath']`: +The classpath used when running a Java or Android binary. Essentially the +collection of all `deps_info['device_jar_path']` entries for the target and all +its dependencies. + + +## Target type `junit_binary`: + +A target type for JUnit-specific binaries. Identical to +[`java_binary`](#target_java_binary) in the context of `.build_config` files, +except the name. + + +## Target type \ +`java_annotation_processor`: + +A target type for Java annotation processors. Identical to +[`java_binary`](#target_java_binary) in the context of `.build_config` files, +except the name, except that it requires a `deps_info['main_class']` entry. + + +## Target type `android_apk`: + +Corresponds to an Android APK. Inherits from the +[`java_binary`](#target_java_binary) type and adds: + +* `deps_info['apk_path']`: +Path to the raw, unsigned, APK generated by this target. + +* `deps_info['incremental_apk_path']`: +Path to the raw, unsigned, incremental APK generated by this target. + +* `deps_info['incremental_install_json_path']`: +Path to the JSON file with per-apk details for incremental install. +See `build/android/gyp/incremental/write_installer_json.py` for more +details about its content. + +* `deps_info['dist_jar']['all_interface_jars']`: +For `android_apk` and `dist_jar` targets, a list of all interface jar files +that will be merged into the final `.jar` file for distribution. + +* `deps_info['final_dex']['path']`: +Path to the final classes.dex file (or classes.zip in case of multi-dex) +for this APK. + +* `deps_info['final_dex']['all_dex_files']`: +The list of paths to all `deps_info['dex_path']` entries for all libraries +that comprise this APK. Valid only for debug builds. + +* `native['libraries']` +List of native libraries for the primary ABI to be embedded in this APK. +E.g. [ "libchrome.so" ] (i.e. this doesn't include any ABI sub-directory +prefix). + +* `native['java_libraries_list']` +The same list as `native['libraries']` as a string holding a Java source +fragment, e.g. `"{\"chrome\"}"`, without any `lib` prefix, and `.so` +suffix (as expected by `System.loadLibrary()`). + +* `native['second_abi_libraries']` +List of native libraries for the secondary ABI to be embedded in this APK. +Empty if only a single ABI is supported. + +* `native['uncompress_shared_libraries']` +A boolean indicating whether native libraries are stored uncompressed in the +APK. + +* `native['loadable_modules']` +A list of native libraries to store within the APK, in addition to those from +`native['libraries']`. These correspond to things like the Chromium linker +or instrumentation libraries. + +* `native['secondary_abi_loadable_modules']` +Secondary ABI version of loadable_modules + +* `native['library_always_compress']` +A list of library files that we always compress. + +* `native['library_renames']` +A list of library files that we prepend "crazy." to their file names. + +* `assets` +A list of assets stored compressed in the APK. Each entry has the format +`:`, where `` is relative to +`$CHROMIUM_OUTPUT_DIR`, and `` is relative to `//assets/` +within the APK. + +NOTE: Not to be confused with the `deps_info['assets']` dictionary that +belongs to `android_assets` targets only. + +* `uncompressed_assets` +A list of uncompressed assets stored in the APK. Each entry has the format +`:` too. + +* `locales_java_list` +A string holding a Java source fragment that gives the list of locales stored +uncompressed as android assets. + +* `extra_android_manifests` +A list of `deps_configs['android_manifest]` entries, for all resource +dependencies for this target. I.e. a list of paths to manifest files for +all the resources in this APK. These will be merged with the root manifest +file to generate the final one used to build the APK. + +* `java_resources_jars` +This is a list of `.jar` files whose *Java* resources should be included in +the final APK. For example, this is used to copy the `.res` files from the +EMMA Coverage tool. The copy will omit any `.class` file and the top-level +`//meta-inf/` directory from the input jars. Everything else will be copied +into the final APK as-is. + +NOTE: This has nothing to do with *Android* resources. + +* `jni['all_source']` +The list of all `deps_info['java_sources_file']` entries for all library +dependencies for this APK. Note: this is a list of files, where each file +contains a list of Java source files. This is used for JNI registration. + +* `deps_info['proguard_all_configs']`: +The collection of all 'deps_info['proguard_configs']` values from this target +and all its dependencies. + +* `deps_info['proguard_classpath_jars']`: +The collection of all 'deps_info['extra_classpath_jars']` values from all +dependencies. + +* `deps_info['proguard_under_test_mapping']`: +Applicable to apks with proguard enabled that have an apk_under_test. This is +the path to the apk_under_test's output proguard .mapping file. + +## Target type \ +`android_app_bundle_module`: + +Corresponds to an Android app bundle module. Very similar to an APK and +inherits the same fields, except that this does not generate an installable +file (see `android_app_bundle`), and for the following omitted fields: + +* `deps_info['apk_path']`, `deps_info['incremental_apk_path']` and + `deps_info['incremental_install_json_path']` are omitted. + +* top-level `dist_jar` is omitted as well. + +In addition to `android_apk` targets though come these new fields: + +* `deps_info['proto_resources_path']`: +The path of an zip archive containing the APK's resources compiled to the +protocol buffer format (instead of regular binary xml + resources.arsc). + +* `deps_info['module_rtxt_path']`: +The path of the R.txt file generated when compiling the resources for the bundle +module. + +* `deps_info['module_pathmap_path']`: +The path of the pathmap file generated when compiling the resources for the +bundle module, if resource path shortening is enabled. + +* `deps_info['base_allowlist_rtxt_path']`: +Optional path to an R.txt file used as a allowlist for base string resources. +This means that any string resource listed in this file *and* in +`deps_info['module_rtxt_path']` will end up in the base split APK of any +`android_app_bundle` target that uses this target as its base module. + +This ensures that such localized strings are available to all bundle installs, +even when language based splits are enabled (e.g. required for WebView strings +inside the Monochrome bundle). + + +## Target type `android_app_bundle` + +This target type corresponds to an Android app bundle, and is built from one +or more `android_app_bundle_module` targets listed as dependencies. + + +## Target type `dist_aar`: + +This type corresponds to a target used to generate an `.aar` archive for +distribution. The archive's content is determined by the target's dependencies. + +This always has the following entries: + + * `deps_info['supports_android']` (always True). + * `deps_info['requires_android']` (always True). + * `deps_info['proguard_configs']` (optional). + + +## Target type `dist_jar`: + +This type is similar to [`dist_aar`](#target_dist_aar) but is not +Android-specific, and used to create a `.jar` file that can be later +redistributed. + +This always has the following entries: + + * `deps_info['proguard_enabled']` (False by default). + * `deps_info['proguard_configs']` (optional). + * `deps_info['supports_android']` (True by default). + * `deps_info['requires_android']` (False by default). + + + +## The `deps_info['javac']` dictionary: + +This dictionary appears in Java-related targets (e.g. `java_library`, +`android_apk` and others), and contains information related to the compilation +of Java sources, class files, and jars. + +* `javac['classpath']` +The classpath used to compile this target when annotation processors are +present. + +* `javac['interface_classpath']` +The classpath used to compile this target when annotation processors are +not present. These are also always used to known when a target needs to be +rebuilt. + +* `javac['processor_classpath']` +The classpath listing the jars used for annotation processors. I.e. sent as +`-processorpath` when invoking `javac`. + +* `javac['processor_classes']` +The list of annotation processor main classes. I.e. sent as `-processor' when +invoking `javac`. + +## Target type `android_app_bundle`: + +This type corresponds to an Android app bundle (`.aab` file). + +--------------- END_MARKDOWN --------------------------------------------------- +""" + +from __future__ import print_function + +import collections +import itertools +import json +import optparse +import os +import sys +import xml.dom.minidom + +from util import build_utils +from util import resource_utils + +# TODO(crbug.com/1174969): Remove this once Python2 is obsoleted. +if sys.version_info.major == 2: + zip_longest = itertools.izip_longest +else: + zip_longest = itertools.zip_longest + + +# Types that should never be used as a dependency of another build config. +_ROOT_TYPES = ('android_apk', 'java_binary', 'java_annotation_processor', + 'junit_binary', 'android_app_bundle') +# Types that should not allow code deps to pass through. +_RESOURCE_TYPES = ('android_assets', 'android_resources', 'system_java_library') + + +class OrderedSet(collections.OrderedDict): + # Value |parameter| is present to avoid presubmit warning due to different + # number of parameters from overridden method. + @staticmethod + def fromkeys(iterable, value=None): + out = OrderedSet() + out.update(iterable) + return out + + def add(self, key): + self[key] = True + + def update(self, iterable): + for v in iterable: + self.add(v) + + +def _ExtractMarkdownDocumentation(input_text): + """Extract Markdown documentation from a list of input strings lines. + + This generates a list of strings extracted from |input_text|, by looking + for '-- BEGIN_MARKDOWN --' and '-- END_MARKDOWN --' line markers.""" + in_markdown = False + result = [] + for line in input_text.splitlines(): + if in_markdown: + if '-- END_MARKDOWN --' in line: + in_markdown = False + else: + result.append(line) + else: + if '-- BEGIN_MARKDOWN --' in line: + in_markdown = True + + return result + +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 GetInstrumentationElements(self): + instrumentation_els = self.manifest.getElementsByTagName('instrumentation') + if len(instrumentation_els) == 0: + return None + return instrumentation_els + + def CheckInstrumentationElements(self, expected_package): + instrs = self.GetInstrumentationElements() + if not instrs: + raise Exception('No elements found in %s' % self.path) + for instr in instrs: + 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: + with open(path) as jsonfile: + dep_config_cache[path] = json.load(jsonfile)['deps_info'] + return dep_config_cache[path] + + +def DepsOfType(wanted_type, configs): + return [c for c in configs if c['type'] == wanted_type] + + +def DepPathsOfType(wanted_type, config_paths): + return [p for p in config_paths if GetDepConfig(p)['type'] == wanted_type] + + +def GetAllDepsConfigsInOrder(deps_config_paths, filter_func=None): + def GetDeps(path): + config = GetDepConfig(path) + if filter_func and not filter_func(config): + return [] + return config['deps_configs'] + + return build_utils.GetSortedTransitiveDependencies(deps_config_paths, GetDeps) + + +def GetObjectByPath(obj, key_path): + """Given an object, return its nth child based on a key path. + """ + return GetObjectByPath(obj[key_path[0]], key_path[1:]) if key_path else obj + + +def RemoveObjDups(obj, base, *key_path): + """Remove array items from an object[*kep_path] that are also + contained in the base[*kep_path] (duplicates). + """ + base_target = set(GetObjectByPath(base, key_path)) + target = GetObjectByPath(obj, key_path) + target[:] = [x for x in target if x not in base_target] + + +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 + ] + self._direct_deps_config_paths = direct_deps_config_paths + + def All(self, wanted_type=None): + if wanted_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 DirectAndChildPublicDeps(self, wanted_type=None): + """Returns direct dependencies and dependencies exported via public_deps of + direct dependencies. + """ + dep_paths = set(self._direct_deps_config_paths) + for direct_dep in self._direct_deps_configs: + dep_paths.update(direct_dep.get('public_deps_configs', [])) + deps_list = [GetDepConfig(p) for p in dep_paths] + if wanted_type is None: + return deps_list + return DepsOfType(wanted_type, deps_list) + + def AllConfigPaths(self): + return self._all_deps_config_paths + + def GradlePrebuiltJarPaths(self): + ret = [] + + def helper(cur): + for config in cur.Direct('java_library'): + if config['is_prebuilt'] or config['gradle_treat_as_prebuilt']: + if config['unprocessed_jar_path'] not in ret: + ret.append(config['unprocessed_jar_path']) + + helper(self) + return ret + + def GradleLibraryProjectDeps(self): + ret = [] + + def helper(cur): + for config in cur.Direct('java_library'): + if config['is_prebuilt']: + pass + elif config['gradle_treat_as_prebuilt']: + helper(Deps(config['deps_configs'])) + elif config not in ret: + ret.append(config) + + helper(self) + return ret + + +def _MergeAssets(all_assets): + """Merges all assets from the given deps. + + Returns: + A tuple of: (compressed, uncompressed, locale_paks) + |compressed| and |uncompressed| are lists of "srcPath:zipPath". srcPath is + the path of the asset to add, and zipPath is the location within the zip + (excluding assets/ prefix). + |locale_paks| is a set of all zipPaths that have been marked as + treat_as_locale_paks=true. + """ + compressed = {} + uncompressed = {} + locale_paks = set() + for asset_dep in all_assets: + entry = asset_dep['assets'] + disable_compression = entry.get('disable_compression') + treat_as_locale_paks = entry.get('treat_as_locale_paks') + dest_map = uncompressed if disable_compression else compressed + other_map = compressed if disable_compression else uncompressed + outputs = entry.get('outputs', []) + for src, dest in zip_longest(entry['sources'], outputs): + if not dest: + dest = os.path.basename(src) + # Merge so that each path shows up in only one of the lists, and that + # deps of the same target override previous ones. + other_map.pop(dest, 0) + dest_map[dest] = src + if treat_as_locale_paks: + locale_paks.add(dest) + + def create_list(asset_map): + ret = ['%s:%s' % (src, dest) for dest, src in asset_map.items()] + # Sort to ensure deterministic ordering. + ret.sort() + return ret + + return create_list(compressed), create_list(uncompressed), locale_paks + + +def _ResolveGroups(config_paths): + """Returns a list of configs with all groups inlined.""" + ret = list(config_paths) + ret_set = set(config_paths) + while True: + group_paths = DepPathsOfType('group', ret) + if not group_paths: + return ret + for group_path in group_paths: + index = ret.index(group_path) + expanded_config_paths = [] + for deps_config_path in GetDepConfig(group_path)['deps_configs']: + if not deps_config_path in ret_set: + expanded_config_paths.append(deps_config_path) + ret[index:index + 1] = expanded_config_paths + ret_set.update(expanded_config_paths) + + +def _DepsFromPaths(dep_paths, + target_type, + filter_root_targets=True, + recursive_resource_deps=False): + """Resolves all groups and trims dependency branches that we never want. + + E.g. When a resource or asset depends on an apk target, the intent is to + include the .apk as a resource/asset, not to have the apk's classpath added. + + This method is meant to be called to get the top nodes (i.e. closest to + current target) that we could then use to get a full transitive dependants + list (eg using Deps#all). So filtering single elements out of this list, + filters whole branches of dependencies. By resolving groups (i.e. expanding + them to their constituents), depending on a group is equivalent to directly + depending on each element of that group. + """ + blocklist = [] + allowlist = [] + + # Don't allow root targets to be considered as a dep. + if filter_root_targets: + blocklist.extend(_ROOT_TYPES) + + # Don't allow java libraries to cross through assets/resources. + if target_type in _RESOURCE_TYPES: + allowlist.extend(_RESOURCE_TYPES) + # Pretend that this target directly depends on all of its transitive + # dependencies. + if recursive_resource_deps: + dep_paths = GetAllDepsConfigsInOrder(dep_paths) + # Exclude assets if recursive_resource_deps is set. The + # recursive_resource_deps arg is used to pull resources into the base + # module to workaround bugs accessing resources in isolated DFMs, but + # assets should be kept in the DFMs. + blocklist.append('android_assets') + + return _DepsFromPathsWithFilters(dep_paths, blocklist, allowlist) + + +def _DepsFromPathsWithFilters(dep_paths, blocklist=None, allowlist=None): + """Resolves all groups and trims dependency branches that we never want. + + See _DepsFromPaths. + + |blocklist| if passed, are the types of direct dependencies we do not care + about (i.e. tips of branches that we wish to prune). + + |allowlist| if passed, are the only types of direct dependencies we care + about (i.e. we wish to prune all other branches that do not start from one of + these). + """ + group_paths = DepPathsOfType('group', dep_paths) + config_paths = dep_paths + if group_paths: + config_paths = _ResolveGroups(dep_paths) + group_paths + configs = [GetDepConfig(p) for p in config_paths] + if blocklist: + configs = [c for c in configs if c['type'] not in blocklist] + if allowlist: + configs = [c for c in configs if c['type'] in allowlist] + + return Deps([c['path'] for c in configs]) + + +def _ExtractSharedLibsFromRuntimeDeps(runtime_deps_file): + ret = [] + with open(runtime_deps_file) as f: + for line in f: + line = line.rstrip() + if not line.endswith('.so'): + continue + # Only unstripped .so files are listed in runtime deps. + # Convert to the stripped .so by going up one directory. + ret.append(os.path.normpath(line.replace('lib.unstripped/', ''))) + ret.reverse() + return ret + + +def _CreateJavaLibrariesList(library_paths): + """Returns a java literal array with the "base" library names: + e.g. libfoo.so -> foo + """ + names = ['"%s"' % os.path.basename(s)[3:-3] for s in library_paths] + return ('{%s}' % ','.join(sorted(set(names)))) + + +def _CreateJavaLocaleListFromAssets(assets, locale_paks): + """Returns a java literal array from a list of locale assets. + + Args: + assets: A list of all APK asset paths in the form 'src:dst' + locale_paks: A list of asset paths that correponds to the locale pak + files of interest. Each |assets| entry will have its 'dst' part matched + against it to determine if they are part of the result. + Returns: + A string that is a Java source literal array listing the locale names + of the corresponding asset files, without directory or .pak suffix. + E.g. '{"en-GB", "en-US", "es-ES", "fr", ... }' + """ + assets_paths = [a.split(':')[1] for a in assets] + locales = [os.path.basename(a)[:-4] for a in assets_paths if a in locale_paks] + return '{%s}' % ','.join('"%s"' % l for l in sorted(locales)) + + +def _AddJarMapping(jar_to_target, configs): + for config in configs: + jar = config.get('unprocessed_jar_path') + if jar: + jar_to_target[jar] = config['gn_target'] + for jar in config.get('extra_classpath_jars', []): + jar_to_target[jar] = config['gn_target'] + + +def _CompareClasspathPriority(dep): + return 1 if dep.get('low_classpath_priority') else 0 + + +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('--gn-target', help='GN label for this target') + parser.add_option( + '--deps-configs', + help='GN-list of dependent build_config files.') + parser.add_option( + '--annotation-processor-configs', + help='GN-list of build_config files for annotation processors.') + + # 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('--package-name', + help='Java package name for these resources.') + parser.add_option('--android-manifest', + help='Path to the root android manifest.') + parser.add_option('--merged-android-manifest', + help='Path to the merged android manifest.') + parser.add_option('--resource-dirs', action='append', default=[], + help='GYP-list of resource dirs') + parser.add_option( + '--res-sources-path', + help='Path to file containing a list of paths to resources.') + parser.add_option( + '--resource-overlay', + action='store_true', + help='Whether resources passed in via --resources-zip should override ' + 'resources with the same name') + parser.add_option( + '--recursive-resource-deps', + action='store_true', + help='Whether deps should be walked recursively to find resource deps.') + + # android_assets options + parser.add_option('--asset-sources', help='List of asset sources.') + parser.add_option('--asset-renaming-sources', + help='List of asset sources with custom destinations.') + parser.add_option('--asset-renaming-destinations', + help='List of asset custom destinations.') + parser.add_option('--disable-asset-compression', action='store_true', + help='Whether to disable asset compression.') + parser.add_option('--treat-as-locale-paks', action='store_true', + help='Consider the assets as locale paks in BuildConfig.java') + + # java library options + + parser.add_option('--public-deps-configs', + help='GN list of config files of deps which are exposed as ' + 'part of the target\'s public API.') + parser.add_option( + '--ignore-dependency-public-deps', + action='store_true', + help='If true, \'public_deps\' will not be collected from the current ' + 'target\'s direct deps.') + parser.add_option('--aar-path', help='Path to containing .aar file.') + parser.add_option('--device-jar-path', help='Path to .jar for dexing.') + parser.add_option('--host-jar-path', help='Path to .jar for java_binary.') + parser.add_option('--unprocessed-jar-path', + help='Path to the .jar to use for javac classpath purposes.') + parser.add_option( + '--interface-jar-path', + help='Path to the interface .jar to use for javac classpath purposes.') + parser.add_option('--is-prebuilt', action='store_true', + help='Whether the jar was compiled or pre-compiled.') + parser.add_option('--java-sources-file', help='Path to .sources file') + parser.add_option('--bundled-srcjars', + help='GYP-list of .srcjars that have been included in this java_library.') + 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.') + parser.add_option('--extra-classpath-jars', + help='GYP-list of .jar files to include on the classpath when compiling, ' + 'but not to include in the final binary.') + parser.add_option( + '--low-classpath-priority', + action='store_true', + help='Indicates that the library should be placed at the end of the ' + 'classpath.') + parser.add_option( + '--mergeable-android-manifests', + help='GN-list of AndroidManifest.xml to include in manifest merging.') + parser.add_option('--gradle-treat-as-prebuilt', action='store_true', + help='Whether this library should be treated as a prebuilt library by ' + 'generate_gradle.py.') + parser.add_option('--main-class', + help='Main class for java_binary or java_annotation_processor targets.') + parser.add_option('--java-resources-jar-path', + help='Path to JAR that contains java resources. Everything ' + 'from this JAR except meta-inf/ content and .class files ' + 'will be added to the final APK.') + parser.add_option( + '--non-chromium-code', + action='store_true', + help='True if a java library is not chromium code, used for lint.') + + # android library options + parser.add_option('--dex-path', help='Path to target\'s dex output.') + + # native library options + parser.add_option('--shared-libraries-runtime-deps', + help='Path to file containing runtime deps for shared ' + 'libraries.') + parser.add_option( + '--loadable-modules', + action='append', + help='GN-list of native libraries for primary ' + 'android-abi. Can be specified multiple times.', + default=[]) + parser.add_option('--secondary-abi-shared-libraries-runtime-deps', + help='Path to file containing runtime deps for secondary ' + 'abi shared libraries.') + parser.add_option( + '--secondary-abi-loadable-modules', + action='append', + help='GN-list of native libraries for secondary ' + 'android-abi. Can be specified multiple times.', + default=[]) + parser.add_option( + '--native-lib-placeholders', + action='append', + help='GN-list of native library placeholders to add.', + default=[]) + parser.add_option( + '--secondary-native-lib-placeholders', + action='append', + help='GN-list of native library placeholders to add ' + 'for the secondary android-abi.', + default=[]) + parser.add_option('--uncompress-shared-libraries', default=False, + action='store_true', + help='Whether to store native libraries uncompressed') + parser.add_option( + '--library-always-compress', + help='The list of library files that we always compress.') + parser.add_option( + '--library-renames', + default=[], + help='The list of library files that we prepend crazy. to their names.') + + # apk options + parser.add_option('--apk-path', help='Path to the target\'s apk output.') + parser.add_option('--incremental-apk-path', + help="Path to the target's incremental apk output.") + parser.add_option('--incremental-install-json-path', + help="Path to the target's generated incremental install " + "json.") + parser.add_option( + '--tested-apk-config', + help='Path to the build config of the tested apk (for an instrumentation ' + 'test apk).') + parser.add_option( + '--proguard-enabled', + action='store_true', + help='Whether proguard is enabled for this apk or bundle module.') + parser.add_option( + '--proguard-configs', + help='GN-list of proguard flag files to use in final apk.') + parser.add_option( + '--proguard-mapping-path', help='Path to jar created by ProGuard step') + + # apk options that are static library specific + parser.add_option( + '--static-library-dependent-configs', + help='GN list of .build_configs of targets that use this target as a ' + 'static library.') + + # options shared between android_resources and apk targets + parser.add_option('--r-text-path', help='Path to target\'s R.txt file.') + + parser.add_option('--fail', + help='GN-list of error message lines to fail with.') + + parser.add_option('--final-dex-path', + help='Path to final input classes.dex (or classes.zip) to ' + 'use in final apk.') + parser.add_option('--res-size-info', help='Path to .ap_.info') + parser.add_option('--apk-proto-resources', + help='Path to resources compiled in protocol buffer format ' + ' for this apk.') + parser.add_option( + '--module-pathmap-path', + help='Path to pathmap file for resource paths in a bundle module.') + parser.add_option( + '--base-allowlist-rtxt-path', + help='Path to R.txt file for the base resources allowlist.') + parser.add_option( + '--is-base-module', + action='store_true', + help='Specifies that this module is a base module for some app bundle.') + + parser.add_option('--generate-markdown-format-doc', action='store_true', + help='Dump the Markdown .build_config format documentation ' + 'then exit immediately.') + + parser.add_option( + '--base-module-build-config', + help='Path to the base module\'s build config ' + 'if this is a feature module.') + + parser.add_option( + '--module-build-configs', + help='For bundles, the paths of all non-async module .build_configs ' + 'for modules that are part of the bundle.') + + parser.add_option('--version-name', help='Version name for this APK.') + parser.add_option('--version-code', help='Version code for this APK.') + + options, args = parser.parse_args(argv) + + if args: + parser.error('No positional arguments should be given.') + + if options.generate_markdown_format_doc: + doc_lines = _ExtractMarkdownDocumentation(__doc__) + for line in doc_lines: + print(line) + return 0 + + if options.fail: + parser.error('\n'.join(build_utils.ParseGnList(options.fail))) + + lib_options = ['unprocessed_jar_path', 'interface_jar_path'] + device_lib_options = ['device_jar_path', 'dex_path'] + required_options_map = { + 'android_apk': ['build_config'] + lib_options + device_lib_options, + 'android_app_bundle_module': + ['build_config', 'final_dex_path', 'res_size_info'] + lib_options + + device_lib_options, + 'android_assets': ['build_config'], + 'android_resources': ['build_config', 'resources_zip'], + 'dist_aar': ['build_config'], + 'dist_jar': ['build_config'], + 'group': ['build_config'], + 'java_annotation_processor': ['build_config', 'main_class'], + 'java_binary': ['build_config'], + 'java_library': ['build_config', 'host_jar_path'] + lib_options, + 'junit_binary': ['build_config'], + 'system_java_library': ['build_config', 'unprocessed_jar_path'], + 'android_app_bundle': ['build_config', 'module_build_configs'], + } + required_options = required_options_map.get(options.type) + if not required_options: + raise Exception('Unknown type: <%s>' % options.type) + + build_utils.CheckOptions(options, parser, required_options) + + if options.type != 'android_app_bundle_module': + if options.apk_proto_resources: + raise Exception('--apk-proto-resources can only be used with ' + '--type=android_app_bundle_module') + if options.module_pathmap_path: + raise Exception('--module-pathmap-path can only be used with ' + '--type=android_app_bundle_module') + if options.base_allowlist_rtxt_path: + raise Exception('--base-allowlist-rtxt-path can only be used with ' + '--type=android_app_bundle_module') + if options.is_base_module: + raise Exception('--is-base-module can only be used with ' + '--type=android_app_bundle_module') + + is_apk_or_module_target = options.type in ('android_apk', + 'android_app_bundle_module') + + if not is_apk_or_module_target: + if options.uncompress_shared_libraries: + raise Exception('--uncompressed-shared-libraries can only be used ' + 'with --type=android_apk or ' + '--type=android_app_bundle_module') + if options.library_always_compress: + raise Exception( + '--library-always-compress can only be used with --type=android_apk ' + 'or --type=android_app_bundle_module') + if options.library_renames: + raise Exception( + '--library-renames can only be used with --type=android_apk or ' + '--type=android_app_bundle_module') + + if options.device_jar_path and not options.dex_path: + raise Exception('java_library that supports Android requires a dex path.') + if any(getattr(options, x) for x in lib_options): + for attr in lib_options: + if not getattr(options, attr): + raise('Expected %s to be set.' % attr) + + if options.requires_android and not options.supports_android: + raise Exception( + '--supports-android is required when using --requires-android') + + is_java_target = options.type in ( + 'java_binary', 'junit_binary', 'java_annotation_processor', + 'java_library', 'android_apk', 'dist_aar', 'dist_jar', + 'system_java_library', 'android_app_bundle_module') + + is_static_library_dex_provider_target = ( + options.static_library_dependent_configs and options.proguard_enabled) + if is_static_library_dex_provider_target: + if options.type != 'android_apk': + raise Exception( + '--static-library-dependent-configs only supports --type=android_apk') + options.static_library_dependent_configs = build_utils.ParseGnList( + options.static_library_dependent_configs) + static_library_dependent_configs_by_path = { + p: GetDepConfig(p) + for p in options.static_library_dependent_configs + } + + deps_configs_paths = build_utils.ParseGnList(options.deps_configs) + deps = _DepsFromPaths(deps_configs_paths, + options.type, + recursive_resource_deps=options.recursive_resource_deps) + processor_deps = _DepsFromPaths( + build_utils.ParseGnList(options.annotation_processor_configs or ''), + options.type, filter_root_targets=False) + + all_inputs = (deps.AllConfigPaths() + processor_deps.AllConfigPaths() + + list(static_library_dependent_configs_by_path)) + + if options.recursive_resource_deps: + # Include java_library targets since changes to these targets can remove + # resource deps from the build, which would require rebuilding this target's + # build config file: crbug.com/1168655. + recursive_java_deps = _DepsFromPathsWithFilters( + GetAllDepsConfigsInOrder(deps_configs_paths), + allowlist=['java_library']) + all_inputs.extend(recursive_java_deps.AllConfigPaths()) + + direct_deps = deps.Direct() + system_library_deps = deps.Direct('system_java_library') + all_deps = deps.All() + all_library_deps = deps.All('java_library') + all_resources_deps = deps.All('android_resources') + + if options.type == 'java_library': + java_library_deps = _DepsFromPathsWithFilters( + deps_configs_paths, allowlist=['android_resources']) + # for java libraries, we only care about resources that are directly + # reachable without going through another java_library. + all_resources_deps = java_library_deps.All('android_resources') + if options.type == 'android_resources' and options.recursive_resource_deps: + # android_resources targets that want recursive resource deps also need to + # collect package_names from all library deps. This ensures the R.java files + # for these libraries will get pulled in along with the resources. + android_resources_library_deps = _DepsFromPathsWithFilters( + deps_configs_paths, allowlist=['java_library']).All('java_library') + if is_apk_or_module_target: + # android_resources deps which had recursive_resource_deps set should not + # have the manifests from the recursively collected deps added to this + # module. This keeps the manifest declarations in the child DFMs, since they + # will have the Java implementations. + def ExcludeRecursiveResourcesDeps(config): + return not config.get('includes_recursive_resources', False) + + extra_manifest_deps = [ + GetDepConfig(p) for p in GetAllDepsConfigsInOrder( + deps_configs_paths, filter_func=ExcludeRecursiveResourcesDeps) + ] + + base_module_build_config = None + if options.base_module_build_config: + with open(options.base_module_build_config, 'r') as f: + base_module_build_config = json.load(f) + + # Initialize some common config. + # Any value that needs to be queryable by dependents must go within deps_info. + config = { + 'deps_info': { + 'name': os.path.basename(options.build_config), + 'path': options.build_config, + 'type': options.type, + 'gn_target': options.gn_target, + 'deps_configs': [d['path'] for d in direct_deps], + 'chromium_code': not options.non_chromium_code, + }, + # Info needed only by generate_gradle.py. + 'gradle': {} + } + deps_info = config['deps_info'] + gradle = config['gradle'] + + if options.type == 'android_apk' and options.tested_apk_config: + tested_apk_deps = Deps([options.tested_apk_config]) + tested_apk_config = tested_apk_deps.Direct()[0] + gradle['apk_under_test'] = tested_apk_config['name'] + + if options.type == 'android_app_bundle_module': + deps_info['is_base_module'] = bool(options.is_base_module) + + # Required for generating gradle files. + if options.type == 'java_library': + deps_info['is_prebuilt'] = bool(options.is_prebuilt) + deps_info['gradle_treat_as_prebuilt'] = options.gradle_treat_as_prebuilt + + if options.android_manifest: + deps_info['android_manifest'] = options.android_manifest + + if options.merged_android_manifest: + deps_info['merged_android_manifest'] = options.merged_android_manifest + + if options.bundled_srcjars: + deps_info['bundled_srcjars'] = build_utils.ParseGnList( + options.bundled_srcjars) + + if options.java_sources_file: + deps_info['java_sources_file'] = options.java_sources_file + + if is_java_target: + if options.bundled_srcjars: + gradle['bundled_srcjars'] = deps_info['bundled_srcjars'] + + gradle['dependent_android_projects'] = [] + gradle['dependent_java_projects'] = [] + gradle['dependent_prebuilt_jars'] = deps.GradlePrebuiltJarPaths() + + if options.main_class: + deps_info['main_class'] = options.main_class + + for c in deps.GradleLibraryProjectDeps(): + if c['requires_android']: + gradle['dependent_android_projects'].append(c['path']) + else: + gradle['dependent_java_projects'].append(c['path']) + + if options.r_text_path: + deps_info['r_text_path'] = options.r_text_path + + # TODO(tiborg): Remove creation of JNI info for type group and java_library + # once we can generate the JNI registration based on APK / module targets as + # opposed to groups and libraries. + if is_apk_or_module_target or options.type in ( + 'group', 'java_library', 'junit_binary'): + deps_info['jni'] = {} + all_java_sources = [c['java_sources_file'] for c in all_library_deps + if 'java_sources_file' in c] + if options.java_sources_file: + all_java_sources.append(options.java_sources_file) + + if options.apk_proto_resources: + deps_info['proto_resources_path'] = options.apk_proto_resources + + deps_info['version_name'] = options.version_name + deps_info['version_code'] = options.version_code + if options.module_pathmap_path: + deps_info['module_pathmap_path'] = options.module_pathmap_path + else: + # Ensure there is an entry, even if it is empty, for modules + # that have not enabled resource path shortening. Otherwise + # build_utils.ExpandFileArgs fails. + deps_info['module_pathmap_path'] = '' + + if options.base_allowlist_rtxt_path: + deps_info['base_allowlist_rtxt_path'] = options.base_allowlist_rtxt_path + else: + # Ensure there is an entry, even if it is empty, for modules + # that don't need such a allowlist. + deps_info['base_allowlist_rtxt_path'] = '' + + if is_java_target: + deps_info['requires_android'] = bool(options.requires_android) + deps_info['supports_android'] = bool(options.supports_android) + + if not options.bypass_platform_checks: + 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 is_apk_or_module_target or options.type == 'dist_jar': + all_dex_files = [c['dex_path'] for c in all_library_deps] + + if is_java_target: + # Classpath values filled in below (after applying tested_apk_config). + config['javac'] = {} + if options.aar_path: + deps_info['aar_path'] = options.aar_path + if options.unprocessed_jar_path: + deps_info['unprocessed_jar_path'] = options.unprocessed_jar_path + deps_info['interface_jar_path'] = options.interface_jar_path + if options.public_deps_configs: + deps_info['public_deps_configs'] = build_utils.ParseGnList( + options.public_deps_configs) + if options.device_jar_path: + deps_info['device_jar_path'] = options.device_jar_path + if options.host_jar_path: + deps_info['host_jar_path'] = options.host_jar_path + if options.dex_path: + deps_info['dex_path'] = options.dex_path + if is_apk_or_module_target: + all_dex_files.append(options.dex_path) + if options.low_classpath_priority: + deps_info['low_classpath_priority'] = True + if options.type == 'android_apk': + deps_info['apk_path'] = options.apk_path + deps_info['incremental_apk_path'] = options.incremental_apk_path + deps_info['incremental_install_json_path'] = ( + options.incremental_install_json_path) + + if options.type == 'android_assets': + all_asset_sources = [] + if options.asset_renaming_sources: + all_asset_sources.extend( + build_utils.ParseGnList(options.asset_renaming_sources)) + if options.asset_sources: + all_asset_sources.extend(build_utils.ParseGnList(options.asset_sources)) + + deps_info['assets'] = { + 'sources': all_asset_sources + } + if options.asset_renaming_destinations: + deps_info['assets']['outputs'] = ( + build_utils.ParseGnList(options.asset_renaming_destinations)) + if options.disable_asset_compression: + deps_info['assets']['disable_compression'] = True + if options.treat_as_locale_paks: + deps_info['assets']['treat_as_locale_paks'] = True + + if options.type == 'android_resources': + deps_info['resources_zip'] = options.resources_zip + if options.resource_overlay: + deps_info['resource_overlay'] = True + 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 + deps_info['res_sources_path'] = '' + if options.res_sources_path: + deps_info['res_sources_path'] = options.res_sources_path + + if options.requires_android and options.type == 'java_library': + if options.package_name: + deps_info['package_name'] = options.package_name + + if options.type in ('android_resources', 'android_apk', 'junit_binary', + 'dist_aar', 'android_app_bundle_module', 'java_library'): + dependency_zips = [] + dependency_zip_overlays = [] + for c in all_resources_deps: + if not c['resources_zip']: + continue + + dependency_zips.append(c['resources_zip']) + if c.get('resource_overlay'): + dependency_zip_overlays.append(c['resources_zip']) + + extra_package_names = [] + + if options.type != 'android_resources': + extra_package_names = [ + c['package_name'] for c in all_resources_deps if 'package_name' in c + ] + + # android_resources targets which specified recursive_resource_deps may + # have extra_package_names. + for resources_dep in all_resources_deps: + extra_package_names.extend(resources_dep['extra_package_names']) + + # In final types (i.e. apks and modules) that create real R.java files, + # they must collect package names from java_libraries as well. + # https://crbug.com/1073476 + if options.type != 'java_library': + extra_package_names.extend([ + c['package_name'] for c in all_library_deps if 'package_name' in c + ]) + elif options.recursive_resource_deps: + # Pull extra_package_names from library deps if recursive resource deps + # are required. + extra_package_names = [ + c['package_name'] for c in android_resources_library_deps + if 'package_name' in c + ] + config['deps_info']['includes_recursive_resources'] = True + + if options.type in ('dist_aar', 'java_library'): + r_text_files = [ + c['r_text_path'] for c in all_resources_deps if 'r_text_path' in c + ] + deps_info['dependency_r_txt_files'] = r_text_files + + # For feature modules, remove any resources that already exist in the base + # module. + if base_module_build_config: + dependency_zips = [ + c for c in dependency_zips + if c not in base_module_build_config['deps_info']['dependency_zips'] + ] + dependency_zip_overlays = [ + c for c in dependency_zip_overlays if c not in + base_module_build_config['deps_info']['dependency_zip_overlays'] + ] + extra_package_names = [ + c for c in extra_package_names if c not in + base_module_build_config['deps_info']['extra_package_names'] + ] + + if options.type == 'android_apk' and options.tested_apk_config: + config['deps_info']['arsc_package_name'] = ( + tested_apk_config['package_name']) + # We should not shadow the actual R.java files of the apk_under_test by + # creating new R.java files with the same package names in the tested apk. + extra_package_names = [ + package for package in extra_package_names + if package not in tested_apk_config['extra_package_names'] + ] + if options.res_size_info: + config['deps_info']['res_size_info'] = options.res_size_info + + config['deps_info']['dependency_zips'] = dependency_zips + config['deps_info']['dependency_zip_overlays'] = dependency_zip_overlays + config['deps_info']['extra_package_names'] = extra_package_names + + # These are .jars to add to javac classpath but not to runtime classpath. + extra_classpath_jars = build_utils.ParseGnList(options.extra_classpath_jars) + if extra_classpath_jars: + deps_info['extra_classpath_jars'] = extra_classpath_jars + + mergeable_android_manifests = build_utils.ParseGnList( + options.mergeable_android_manifests) + if mergeable_android_manifests: + deps_info['mergeable_android_manifests'] = mergeable_android_manifests + + extra_proguard_classpath_jars = [] + proguard_configs = build_utils.ParseGnList(options.proguard_configs) + if proguard_configs: + # Make a copy of |proguard_configs| since it's mutated below. + deps_info['proguard_configs'] = list(proguard_configs) + + + if is_java_target: + if options.ignore_dependency_public_deps: + classpath_direct_deps = deps.Direct() + classpath_direct_library_deps = deps.Direct('java_library') + else: + classpath_direct_deps = deps.DirectAndChildPublicDeps() + classpath_direct_library_deps = deps.DirectAndChildPublicDeps( + 'java_library') + + # The classpath used to compile this target when annotation processors are + # present. + javac_classpath = set(c['unprocessed_jar_path'] + for c in classpath_direct_library_deps) + # The classpath used to compile this target when annotation processors are + # not present. These are also always used to know when a target needs to be + # rebuilt. + javac_interface_classpath = set(c['interface_jar_path'] + for c in classpath_direct_library_deps) + + # Preserve order of |all_library_deps|. Move low priority libraries to the + # end of the classpath. + all_library_deps_sorted_for_classpath = sorted( + all_library_deps[::-1], key=_CompareClasspathPriority) + + # The classpath used for bytecode-rewritting. + javac_full_classpath = OrderedSet.fromkeys( + c['unprocessed_jar_path'] + for c in all_library_deps_sorted_for_classpath) + # The classpath used for error prone. + javac_full_interface_classpath = OrderedSet.fromkeys( + c['interface_jar_path'] for c in all_library_deps_sorted_for_classpath) + + # Adding base module to classpath to compile against its R.java file + if base_module_build_config: + javac_full_classpath.add( + base_module_build_config['deps_info']['unprocessed_jar_path']) + javac_full_interface_classpath.add( + base_module_build_config['deps_info']['interface_jar_path']) + # Turbine now compiles headers against only the direct classpath, so the + # base module's interface jar must be on the direct interface classpath. + javac_interface_classpath.add( + base_module_build_config['deps_info']['interface_jar_path']) + + for dep in classpath_direct_deps: + if 'extra_classpath_jars' in dep: + javac_classpath.update(dep['extra_classpath_jars']) + javac_interface_classpath.update(dep['extra_classpath_jars']) + for dep in all_deps: + if 'extra_classpath_jars' in dep: + javac_full_classpath.update(dep['extra_classpath_jars']) + javac_full_interface_classpath.update(dep['extra_classpath_jars']) + + # TODO(agrieve): Might be less confusing to fold these into bootclasspath. + # Deps to add to the compile-time classpath (but not the runtime classpath). + # These are jars specified by input_jars_paths that almost never change. + # Just add them directly to all the classpaths. + if options.extra_classpath_jars: + javac_classpath.update(extra_classpath_jars) + javac_interface_classpath.update(extra_classpath_jars) + javac_full_classpath.update(extra_classpath_jars) + javac_full_interface_classpath.update(extra_classpath_jars) + + if is_java_target or options.type == 'android_app_bundle': + # The classpath to use to run this target (or as an input to ProGuard). + device_classpath = [] + if is_java_target and options.device_jar_path: + device_classpath.append(options.device_jar_path) + device_classpath.extend( + c.get('device_jar_path') for c in all_library_deps + if c.get('device_jar_path')) + if options.type == 'android_app_bundle': + for d in deps.Direct('android_app_bundle_module'): + device_classpath.extend(c for c in d.get('device_classpath', []) + if c not in device_classpath) + + if options.type in ('dist_jar', 'java_binary', 'junit_binary'): + # The classpath to use to run this target. + host_classpath = [] + if options.host_jar_path: + host_classpath.append(options.host_jar_path) + host_classpath.extend(c['host_jar_path'] for c in all_library_deps) + deps_info['host_classpath'] = host_classpath + + # We allow lint to be run on android_apk targets, so we collect lint + # artifacts for them. + # We allow lint to be run on android_app_bundle targets, so we need to + # collect lint artifacts for the android_app_bundle_module targets that the + # bundle includes. Different android_app_bundle targets may include different + # android_app_bundle_module targets, so the bundle needs to be able to + # de-duplicate these lint artifacts. + if options.type in ('android_app_bundle_module', 'android_apk'): + # Collect all sources and resources at the apk/bundle_module level. + lint_aars = set() + lint_srcjars = set() + lint_java_sources = set() + lint_resource_sources = set() + lint_resource_zips = set() + + if options.java_sources_file: + lint_java_sources.add(options.java_sources_file) + if options.bundled_srcjars: + lint_srcjars.update(deps_info['bundled_srcjars']) + for c in all_library_deps: + if c['chromium_code'] and c['requires_android']: + if 'java_sources_file' in c: + lint_java_sources.add(c['java_sources_file']) + lint_srcjars.update(c['bundled_srcjars']) + if 'aar_path' in c: + lint_aars.add(c['aar_path']) + + if options.res_sources_path: + lint_resource_sources.add(options.res_sources_path) + if options.resources_zip: + lint_resource_zips.add(options.resources_zip) + for c in all_resources_deps: + if c['chromium_code']: + # Prefer res_sources_path to resources_zips so that lint errors have + # real paths and to avoid needing to extract during lint. + if c['res_sources_path']: + lint_resource_sources.add(c['res_sources_path']) + else: + lint_resource_zips.add(c['resources_zip']) + + deps_info['lint_aars'] = sorted(lint_aars) + deps_info['lint_srcjars'] = sorted(lint_srcjars) + deps_info['lint_java_sources'] = sorted(lint_java_sources) + deps_info['lint_resource_sources'] = sorted(lint_resource_sources) + deps_info['lint_resource_zips'] = sorted(lint_resource_zips) + deps_info['lint_extra_android_manifests'] = [] + + if options.type == 'android_apk': + assert options.android_manifest, 'Android APKs must define a manifest' + deps_info['lint_android_manifest'] = options.android_manifest + + if options.type == 'android_app_bundle': + module_configs = [ + GetDepConfig(c) + for c in build_utils.ParseGnList(options.module_build_configs) + ] + jni_all_source = set() + lint_aars = set() + lint_srcjars = set() + lint_java_sources = set() + lint_resource_sources = set() + lint_resource_zips = set() + lint_extra_android_manifests = set() + for c in module_configs: + if c['is_base_module']: + assert 'base_module_config' not in deps_info, ( + 'Must have exactly 1 base module!') + deps_info['base_module_config'] = c['path'] + # Use the base module's android manifest for linting. + deps_info['lint_android_manifest'] = c['android_manifest'] + else: + lint_extra_android_manifests.add(c['android_manifest']) + jni_all_source.update(c['jni']['all_source']) + lint_aars.update(c['lint_aars']) + lint_srcjars.update(c['lint_srcjars']) + lint_java_sources.update(c['lint_java_sources']) + lint_resource_sources.update(c['lint_resource_sources']) + lint_resource_zips.update(c['lint_resource_zips']) + deps_info['jni'] = {'all_source': sorted(jni_all_source)} + deps_info['lint_aars'] = sorted(lint_aars) + deps_info['lint_srcjars'] = sorted(lint_srcjars) + deps_info['lint_java_sources'] = sorted(lint_java_sources) + deps_info['lint_resource_sources'] = sorted(lint_resource_sources) + deps_info['lint_resource_zips'] = sorted(lint_resource_zips) + deps_info['lint_extra_android_manifests'] = sorted( + lint_extra_android_manifests) + + # Map configs to classpath entries that should be included in their final dex. + classpath_entries_by_owning_config = collections.defaultdict(list) + extra_main_r_text_files = [] + if is_static_library_dex_provider_target: + # Map classpath entries to configs that include them in their classpath. + configs_by_classpath_entry = collections.defaultdict(list) + for config_path, dep_config in (sorted( + static_library_dependent_configs_by_path.items())): + # For bundles, only the jar path and jni sources of the base module + # are relevant for proguard. Should be updated when bundle feature + # modules support JNI. + base_config = dep_config + if dep_config['type'] == 'android_app_bundle': + base_config = GetDepConfig(dep_config['base_module_config']) + extra_main_r_text_files.append(base_config['r_text_path']) + proguard_configs.extend(dep_config['proguard_all_configs']) + extra_proguard_classpath_jars.extend( + dep_config['proguard_classpath_jars']) + all_java_sources.extend(base_config['jni']['all_source']) + + # The srcjars containing the generated R.java files are excluded for APK + # targets the use static libraries, so we add them here to ensure the + # union of resource IDs are available in the static library APK. + for package in base_config['extra_package_names']: + if package not in extra_package_names: + extra_package_names.append(package) + for cp_entry in dep_config['device_classpath']: + configs_by_classpath_entry[cp_entry].append(config_path) + + for cp_entry in device_classpath: + configs_by_classpath_entry[cp_entry].append(options.build_config) + + for cp_entry, candidate_configs in configs_by_classpath_entry.items(): + config_path = (candidate_configs[0] + if len(candidate_configs) == 1 else options.build_config) + classpath_entries_by_owning_config[config_path].append(cp_entry) + device_classpath.append(cp_entry) + + device_classpath = sorted(set(device_classpath)) + + deps_info['static_library_proguard_mapping_output_paths'] = sorted([ + d['proguard_mapping_path'] + for d in static_library_dependent_configs_by_path.values() + ]) + deps_info['static_library_dependent_classpath_configs'] = { + path: sorted(set(classpath)) + for path, classpath in classpath_entries_by_owning_config.items() + } + deps_info['extra_main_r_text_files'] = sorted(extra_main_r_text_files) + + if is_apk_or_module_target or options.type in ('group', 'java_library', + 'junit_binary'): + deps_info['jni']['all_source'] = sorted(set(all_java_sources)) + + system_jars = [c['unprocessed_jar_path'] for c in system_library_deps] + system_interface_jars = [c['interface_jar_path'] for c in system_library_deps] + if system_library_deps: + config['android'] = {} + config['android']['sdk_interface_jars'] = system_interface_jars + config['android']['sdk_jars'] = system_jars + + if options.type in ('android_apk', 'dist_aar', + 'dist_jar', 'android_app_bundle_module', 'android_app_bundle'): + for c in all_deps: + proguard_configs.extend(c.get('proguard_configs', [])) + extra_proguard_classpath_jars.extend(c.get('extra_classpath_jars', [])) + if options.type == 'android_app_bundle': + for c in deps.Direct('android_app_bundle_module'): + proguard_configs.extend(p for p in c.get('proguard_configs', [])) + if options.type == 'android_app_bundle': + for d in deps.Direct('android_app_bundle_module'): + extra_proguard_classpath_jars.extend( + c for c in d.get('proguard_classpath_jars', []) + if c not in extra_proguard_classpath_jars) + + if options.type == 'android_app_bundle': + deps_proguard_enabled = [] + deps_proguard_disabled = [] + for d in deps.Direct('android_app_bundle_module'): + if not d['device_classpath']: + # We don't care about modules that have no Java code for proguarding. + continue + if d['proguard_enabled']: + deps_proguard_enabled.append(d['name']) + else: + deps_proguard_disabled.append(d['name']) + if deps_proguard_enabled and deps_proguard_disabled: + raise Exception('Deps %s have proguard enabled while deps %s have ' + 'proguard disabled' % (deps_proguard_enabled, + deps_proguard_disabled)) + deps_info['proguard_enabled'] = bool(options.proguard_enabled) + + if options.proguard_mapping_path: + deps_info['proguard_mapping_path'] = options.proguard_mapping_path + + # The java code for an instrumentation test apk is assembled differently for + # ProGuard vs. non-ProGuard. + # + # Without ProGuard: Each library's jar is dexed separately and then combined + # into a single classes.dex. A test apk will include all dex files not already + # present in the apk-under-test. At runtime all test code lives in the test + # apk, and the program code lives in the apk-under-test. + # + # With ProGuard: Each library's .jar file is fed into ProGuard, which outputs + # a single .jar, which is then dexed into a classes.dex. A test apk includes + # all jar files from the program and the tests because having them separate + # doesn't work with ProGuard's whole-program optimizations. Although the + # apk-under-test still has all of its code in its classes.dex, none of it is + # used at runtime because the copy of it within the test apk takes precidence. + + if options.type == 'android_apk' and options.tested_apk_config: + if tested_apk_config['proguard_enabled']: + assert options.proguard_enabled, ('proguard must be enabled for ' + 'instrumentation apks if it\'s enabled for the tested apk.') + # Mutating lists, so no need to explicitly re-assign to dict. + proguard_configs.extend( + p for p in tested_apk_config['proguard_all_configs']) + extra_proguard_classpath_jars.extend( + p for p in tested_apk_config['proguard_classpath_jars']) + tested_apk_config = GetDepConfig(options.tested_apk_config) + deps_info['proguard_under_test_mapping'] = ( + tested_apk_config['proguard_mapping_path']) + elif options.proguard_enabled: + # Not sure why you'd want to proguard the test apk when the under-test apk + # is not proguarded, but it's easy enough to support. + deps_info['proguard_under_test_mapping'] = '' + + # Add all tested classes to the test's classpath to ensure that the test's + # java code is a superset of the tested apk's java code + device_classpath_extended = list(device_classpath) + device_classpath_extended.extend( + p for p in tested_apk_config['device_classpath'] + if p not in device_classpath) + # Include in the classpath classes that are added directly to the apk under + # test (those that are not a part of a java_library). + javac_classpath.add(tested_apk_config['unprocessed_jar_path']) + javac_interface_classpath.add(tested_apk_config['interface_jar_path']) + javac_full_classpath.add(tested_apk_config['unprocessed_jar_path']) + javac_full_interface_classpath.add(tested_apk_config['interface_jar_path']) + javac_full_classpath.update(tested_apk_config['javac_full_classpath']) + javac_full_interface_classpath.update( + tested_apk_config['javac_full_interface_classpath']) + + # Exclude .jar files from the test apk that exist within the apk under test. + tested_apk_library_deps = tested_apk_deps.All('java_library') + tested_apk_dex_files = {c['dex_path'] for c in tested_apk_library_deps} + all_dex_files = [p for p in all_dex_files if p not in tested_apk_dex_files] + tested_apk_jar_files = set(tested_apk_config['device_classpath']) + device_classpath = [ + p for p in device_classpath if p not in tested_apk_jar_files + ] + + if options.type in ('android_apk', 'dist_aar', 'dist_jar', + 'android_app_bundle_module', 'android_app_bundle'): + deps_info['proguard_all_configs'] = sorted(set(proguard_configs)) + deps_info['proguard_classpath_jars'] = sorted( + set(extra_proguard_classpath_jars)) + + # Dependencies for the final dex file of an apk. + if (is_apk_or_module_target or options.final_dex_path + or options.type == 'dist_jar'): + config['final_dex'] = {} + dex_config = config['final_dex'] + dex_config['path'] = options.final_dex_path + if is_apk_or_module_target or options.type == 'dist_jar': + dex_config['all_dex_files'] = all_dex_files + + if is_java_target: + config['javac']['classpath'] = sorted(javac_classpath) + config['javac']['interface_classpath'] = sorted(javac_interface_classpath) + # Direct() will be of type 'java_annotation_processor', and so not included + # in All('java_library'). + # Annotation processors run as part of the build, so need host_jar_path. + config['javac']['processor_classpath'] = [ + c['host_jar_path'] for c in processor_deps.Direct() + if c.get('host_jar_path') + ] + config['javac']['processor_classpath'] += [ + c['host_jar_path'] for c in processor_deps.All('java_library') + ] + config['javac']['processor_classes'] = [ + c['main_class'] for c in processor_deps.Direct()] + deps_info['javac_full_classpath'] = list(javac_full_classpath) + deps_info['javac_full_interface_classpath'] = list( + javac_full_interface_classpath) + elif options.type == 'android_app_bundle': + # bundles require javac_full_classpath to create .aab.jar.info and require + # javac_full_interface_classpath for lint. + javac_full_classpath = OrderedSet() + javac_full_interface_classpath = OrderedSet() + for d in deps.Direct('android_app_bundle_module'): + javac_full_classpath.update(d['javac_full_classpath']) + javac_full_interface_classpath.update(d['javac_full_interface_classpath']) + javac_full_classpath.add(d['unprocessed_jar_path']) + javac_full_interface_classpath.add(d['interface_jar_path']) + deps_info['javac_full_classpath'] = list(javac_full_classpath) + deps_info['javac_full_interface_classpath'] = list( + javac_full_interface_classpath) + + if options.type in ('android_apk', 'dist_jar', 'android_app_bundle_module', + 'android_app_bundle'): + deps_info['device_classpath'] = device_classpath + if options.tested_apk_config: + deps_info['device_classpath_extended'] = device_classpath_extended + + if options.type in ('android_apk', 'dist_jar'): + all_interface_jars = [] + if options.interface_jar_path: + all_interface_jars.append(options.interface_jar_path) + all_interface_jars.extend(c['interface_jar_path'] for c in all_library_deps) + + config['dist_jar'] = { + 'all_interface_jars': all_interface_jars, + } + + if is_apk_or_module_target: + manifest = AndroidManifest(options.android_manifest) + deps_info['package_name'] = manifest.GetPackageName() + if not options.tested_apk_config and manifest.GetInstrumentationElements(): + # This must then have instrumentation only for itself. + manifest.CheckInstrumentationElements(manifest.GetPackageName()) + + library_paths = [] + java_libraries_list = None + if options.shared_libraries_runtime_deps: + library_paths = _ExtractSharedLibsFromRuntimeDeps( + options.shared_libraries_runtime_deps) + java_libraries_list = _CreateJavaLibrariesList(library_paths) + all_inputs.append(options.shared_libraries_runtime_deps) + + secondary_abi_library_paths = [] + if options.secondary_abi_shared_libraries_runtime_deps: + secondary_abi_library_paths = _ExtractSharedLibsFromRuntimeDeps( + options.secondary_abi_shared_libraries_runtime_deps) + all_inputs.append(options.secondary_abi_shared_libraries_runtime_deps) + + native_library_placeholder_paths = build_utils.ParseGnList( + options.native_lib_placeholders) + + secondary_native_library_placeholder_paths = build_utils.ParseGnList( + options.secondary_native_lib_placeholders) + + loadable_modules = build_utils.ParseGnList(options.loadable_modules) + secondary_abi_loadable_modules = build_utils.ParseGnList( + options.secondary_abi_loadable_modules) + + config['native'] = { + 'libraries': + library_paths, + 'native_library_placeholders': + native_library_placeholder_paths, + 'secondary_abi_libraries': + secondary_abi_library_paths, + 'secondary_native_library_placeholders': + secondary_native_library_placeholder_paths, + 'java_libraries_list': + java_libraries_list, + 'uncompress_shared_libraries': + options.uncompress_shared_libraries, + 'library_always_compress': + options.library_always_compress, + 'library_renames': + options.library_renames, + 'loadable_modules': + loadable_modules, + 'secondary_abi_loadable_modules': + secondary_abi_loadable_modules, + } + config['assets'], config['uncompressed_assets'], locale_paks = ( + _MergeAssets(deps.All('android_assets'))) + + deps_info['locales_java_list'] = _CreateJavaLocaleListFromAssets( + config['uncompressed_assets'], locale_paks) + + config['extra_android_manifests'] = [] + for c in extra_manifest_deps: + config['extra_android_manifests'].extend( + c.get('mergeable_android_manifests', [])) + + # Collect java resources + java_resources_jars = [d['java_resources_jar'] for d in all_library_deps + if 'java_resources_jar' in d] + if options.tested_apk_config: + tested_apk_resource_jars = [d['java_resources_jar'] + for d in tested_apk_library_deps + if 'java_resources_jar' in d] + java_resources_jars = [jar for jar in java_resources_jars + if jar not in tested_apk_resource_jars] + config['java_resources_jars'] = java_resources_jars + + if options.java_resources_jar_path: + deps_info['java_resources_jar'] = options.java_resources_jar_path + + # DYNAMIC FEATURE MODULES: + # Make sure that dependencies that exist on the base module + # are not duplicated on the feature module. + if base_module_build_config: + base = base_module_build_config + RemoveObjDups(config, base, 'deps_info', 'device_classpath') + RemoveObjDups(config, base, 'deps_info', 'javac_full_classpath') + RemoveObjDups(config, base, 'deps_info', 'javac_full_interface_classpath') + RemoveObjDups(config, base, 'deps_info', 'jni', 'all_source') + RemoveObjDups(config, base, 'final_dex', 'all_dex_files') + RemoveObjDups(config, base, 'extra_android_manifests') + + if is_java_target: + jar_to_target = {} + _AddJarMapping(jar_to_target, [deps_info]) + _AddJarMapping(jar_to_target, all_deps) + if base_module_build_config: + _AddJarMapping(jar_to_target, [base_module_build_config['deps_info']]) + if options.tested_apk_config: + _AddJarMapping(jar_to_target, [tested_apk_config]) + for jar, target in zip(tested_apk_config['javac_full_classpath'], + tested_apk_config['javac_full_classpath_targets']): + jar_to_target[jar] = target + + # Used by bytecode_processor to give better error message when missing + # deps are found. + config['deps_info']['javac_full_classpath_targets'] = [ + jar_to_target[x] for x in deps_info['javac_full_classpath'] + ] + + build_utils.WriteJson(config, options.build_config, only_if_changed=True) + + if options.depfile: + build_utils.WriteDepfile(options.depfile, options.build_config, + sorted(set(all_inputs))) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/gyp/write_build_config.pydeps b/third_party/libwebrtc/build/android/gyp/write_build_config.pydeps new file mode 100644 index 0000000000..e9c7d9fcaa --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/write_build_config.pydeps @@ -0,0 +1,31 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/write_build_config.pydeps build/android/gyp/write_build_config.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +util/__init__.py +util/build_utils.py +util/resource_utils.py +write_build_config.py diff --git a/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.py b/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.py new file mode 100755 index 0000000000..322b8b2c82 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Chromium 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 list of native libraries to srcjar file.""" + +import argparse +import os +import sys +import zipfile + +from util import build_utils + + +_NATIVE_LIBRARIES_TEMPLATE = """\ +// This file is autogenerated by +// build/android/gyp/write_native_libraries_java.py +// Please do not change its content. + +package org.chromium.build; + +public class NativeLibraries {{ + public static final int CPU_FAMILY_UNKNOWN = 0; + public static final int CPU_FAMILY_ARM = 1; + public static final int CPU_FAMILY_MIPS = 2; + public static final int CPU_FAMILY_X86 = 3; + + // Set to true to enable the use of the Chromium Linker. + public static {MAYBE_FINAL}boolean sUseLinker{USE_LINKER}; + public static {MAYBE_FINAL}boolean sUseLibraryInZipFile{USE_LIBRARY_IN_ZIP_FILE}; + public static {MAYBE_FINAL}boolean sUseModernLinker{USE_MODERN_LINKER}; + + // This is the list of native libraries to be loaded (in the correct order) + // by LibraryLoader.java. + public static {MAYBE_FINAL}String[] LIBRARIES = {{{LIBRARIES}}}; + + public static {MAYBE_FINAL}int sCpuFamily = {CPU_FAMILY}; +}} +""" + + +def _FormatLibraryName(library_name): + filename = os.path.split(library_name)[1] + assert filename.startswith('lib') + assert filename.endswith('.so') + # Remove lib prefix and .so suffix. + return '"%s"' % filename[3:-3] + + +def main(): + parser = argparse.ArgumentParser() + + build_utils.AddDepfileOption(parser) + parser.add_argument('--final', action='store_true', help='Use final fields.') + parser.add_argument( + '--enable-chromium-linker', + action='store_true', + help='Enable Chromium linker.') + parser.add_argument( + '--load-library-from-apk', + action='store_true', + help='Load libaries from APK without uncompressing.') + parser.add_argument( + '--use-modern-linker', action='store_true', help='To use ModernLinker.') + parser.add_argument( + '--native-libraries-list', help='File with list of native libraries.') + parser.add_argument( + '--cpu-family', + choices={ + 'CPU_FAMILY_ARM', 'CPU_FAMILY_X86', 'CPU_FAMILY_MIPS', + 'CPU_FAMILY_UNKNOWN' + }, + required=True, + default='CPU_FAMILY_UNKNOWN', + help='CPU family.') + parser.add_argument( + '--main-component-library', + help='If used, the list of native libraries will only contain this ' + 'library. Dependencies are found in the library\'s "NEEDED" section.') + + parser.add_argument( + '--output', required=True, help='Path to the generated srcjar file.') + + options = parser.parse_args(build_utils.ExpandFileArgs(sys.argv[1:])) + + assert (options.enable_chromium_linker or not options.load_library_from_apk) + + native_libraries_list = [] + if options.main_component_library: + native_libraries_list.append( + _FormatLibraryName(options.main_component_library)) + elif options.native_libraries_list: + with open(options.native_libraries_list) as f: + for path in f: + path = path.strip() + native_libraries_list.append(_FormatLibraryName(path)) + + def bool_str(value): + if value: + return ' = true' + elif options.final: + return ' = false' + return '' + + format_dict = { + 'MAYBE_FINAL': 'final ' if options.final else '', + 'USE_LINKER': bool_str(options.enable_chromium_linker), + 'USE_LIBRARY_IN_ZIP_FILE': bool_str(options.load_library_from_apk), + 'USE_MODERN_LINKER': bool_str(options.use_modern_linker), + 'LIBRARIES': ','.join(native_libraries_list), + 'CPU_FAMILY': options.cpu_family, + } + with build_utils.AtomicOutput(options.output) as f: + with zipfile.ZipFile(f.name, 'w') as srcjar_file: + build_utils.AddToZipHermetic( + zip_file=srcjar_file, + zip_path='org/chromium/build/NativeLibraries.java', + data=_NATIVE_LIBRARIES_TEMPLATE.format(**format_dict)) + + if options.depfile: + assert options.native_libraries_list + build_utils.WriteDepfile(options.depfile, + options.output, + inputs=[options.native_libraries_list]) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.pydeps b/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.pydeps new file mode 100644 index 0000000000..f5176ef78e --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/write_native_libraries_java.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/write_native_libraries_java.pydeps build/android/gyp/write_native_libraries_java.py +../../gn_helpers.py +util/__init__.py +util/build_utils.py +write_native_libraries_java.py diff --git a/third_party/libwebrtc/build/android/gyp/zip.py b/third_party/libwebrtc/build/android/gyp/zip.py new file mode 100755 index 0000000000..6b405400eb --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/zip.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# 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 argparse +import os +import sys +import zipfile + +from util import build_utils + + +def main(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser(args) + parser.add_argument('--input-files', help='GN-list of files to zip.') + parser.add_argument( + '--input-files-base-dir', + help='Paths in the archive will be relative to this directory') + parser.add_argument('--input-zips', help='GN-list of zips to merge.') + parser.add_argument( + '--input-zips-excluded-globs', + help='GN-list of globs for paths to exclude.') + parser.add_argument('--output', required=True, help='Path to output archive.') + compress_group = parser.add_mutually_exclusive_group() + compress_group.add_argument( + '--compress', action='store_true', help='Compress entries') + compress_group.add_argument( + '--no-compress', + action='store_false', + dest='compress', + help='Do not compress entries') + build_utils.AddDepfileOption(parser) + options = parser.parse_args(args) + + with build_utils.AtomicOutput(options.output) as f: + with zipfile.ZipFile(f.name, 'w') as out_zip: + depfile_deps = None + if options.input_files: + files = build_utils.ParseGnList(options.input_files) + build_utils.DoZip( + files, + out_zip, + base_dir=options.input_files_base_dir, + compress_fn=lambda _: options.compress) + + if options.input_zips: + files = build_utils.ParseGnList(options.input_zips) + depfile_deps = files + path_transform = None + if options.input_zips_excluded_globs: + globs = build_utils.ParseGnList(options.input_zips_excluded_globs) + path_transform = ( + lambda p: None if build_utils.MatchesGlob(p, globs) else p) + build_utils.MergeZips( + out_zip, + files, + path_transform=path_transform, + compress=options.compress) + + # Depfile used only by dist_jar(). + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.output, + inputs=depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/gyp/zip.pydeps b/third_party/libwebrtc/build/android/gyp/zip.pydeps new file mode 100644 index 0000000000..36affd1707 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/zip.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/zip.pydeps build/android/gyp/zip.py +../../gn_helpers.py +util/__init__.py +util/build_utils.py +zip.py diff --git a/third_party/libwebrtc/build/android/host_heartbeat.py b/third_party/libwebrtc/build/android/host_heartbeat.py new file mode 100755 index 0000000000..34a1a3597c --- /dev/null +++ b/third_party/libwebrtc/build/android/host_heartbeat.py @@ -0,0 +1,36 @@ +#!/usr/bin/env vpython3 +# +# 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 + +import devil_chromium +from devil.android import device_utils + +PULSE_PERIOD = 20 + +def main(): + devil_chromium.Initialize() + + while True: + try: + devices = device_utils.DeviceUtils.HealthyDevices(denylist=None) + 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/third_party/libwebrtc/build/android/incremental_install/BUILD.gn b/third_party/libwebrtc/build/android/incremental_install/BUILD.gn new file mode 100644 index 0000000000..8d26e9622b --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/BUILD.gn @@ -0,0 +1,23 @@ +# 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/rules.gni") + +android_library("bootstrap_java") { + sources = [ + "java/org/chromium/incrementalinstall/BootstrapApplication.java", + "java/org/chromium/incrementalinstall/BootstrapInstrumentation.java", + "java/org/chromium/incrementalinstall/ClassLoaderPatcher.java", + "java/org/chromium/incrementalinstall/LockFile.java", + "java/org/chromium/incrementalinstall/Reflect.java", + "java/org/chromium/incrementalinstall/SecondInstrumentation.java", + ] + jacoco_never_instrument = true + no_build_hooks = true +} + +dist_dex("apk_dex") { + output = "$target_out_dir/apk.dex" + deps = [ ":bootstrap_java" ] +} diff --git a/third_party/libwebrtc/build/android/incremental_install/README.md b/third_party/libwebrtc/build/android/incremental_install/README.md new file mode 100644 index 0000000000..9a27b8c5a6 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/README.md @@ -0,0 +1,83 @@ +# Incremental Install + +Incremental Install is a way of building & deploying an APK that tries to +minimize the time it takes to make a change and see that change running on +device. They work best with `is_component_build=true`, and do *not* require a +rooted device. + +## Building + +Add the gn arg: + + incremental_install = true + +This causes all apks to be built as incremental except for denylisted ones. + +## Running + +It is not enough to `adb install` them. You must use the generated wrapper +script: + + out/Debug/bin/your_apk run + out/Debug/bin/run_chrome_public_test_apk # Automatically sets --fast-local-dev + +# How it Works + +## Overview + +The basic idea is to sideload .dex and .so files to `/data/local/tmp` rather +than bundling them in the .apk. Then, when making a change, only the changed +.dex / .so needs to be pushed to the device. + +Faster Builds: + + * No `final_dex` step (where all .dex files are merged into one) + * No need to rebuild .apk for code-only changes (but required for resources) + * Apks sign faster because they are smaller. + +Faster Installs: + + * The .apk is smaller, and so faster to verify. + * No need to run `adb install` for code-only changes. + * Only changed .so / .dex files are pushed. MD5s of existing on-device files + are cached on host computer. + +Slower Initial Runs: + + * The first time you run an incremental .apk, the `DexOpt` needs to run on all + .dex files. This step is normally done during `adb install`, but is done on + start-up for incremental apks. + * DexOpt results are cached, so subsequent runs are faster. + * The slowdown varies significantly based on the Android version. Android O+ + has almost no visible slow-down. + +Caveats: + * Isolated processes (on L+) are incompatible with incremental install. As a + work-around, isolated processes are disabled when building incremental apks. + * Android resources, assets, and `loadable_modules` are not sideloaded (they + remain in the apk), so builds & installs that modify any of these are not as + fast as those that modify only .java / .cc. + * Since files are sideloaded to `/data/local/tmp`, you need to use the wrapper + scripts to uninstall them fully. E.g.: + ```shell + out/Default/bin/chrome_public_apk uninstall + ``` + +## The Code + +All incremental apks have the same classes.dex, which is built from: + + //build/android/incremental_install:bootstrap_java + +They also have a transformed `AndroidManifest.xml`, which overrides the the +main application class and any instrumentation classes so that they instead +point to `BootstrapApplication`. This is built by: + + //build/android/incremental_install/generate_android_manifest.py + +Wrapper scripts and install logic is contained in: + + //build/android/incremental_install/create_install_script.py + //build/android/incremental_install/installer.py + +Finally, GN logic for incremental apks is sprinkled throughout. diff --git a/third_party/libwebrtc/build/android/incremental_install/__init__.py b/third_party/libwebrtc/build/android/incremental_install/__init__.py new file mode 100644 index 0000000000..50b23dff63 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/__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/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.py b/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.py new file mode 100755 index 0000000000..67feaa5a6f --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# 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 incremental APK. + +Given the manifest file for the real APK, generates an AndroidManifest.xml with +the application class changed to IncrementalApplication. +""" + +import argparse +import os +import subprocess +import sys +import tempfile +import zipfile +from xml.etree import ElementTree + +sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'gyp')) +from util import build_utils +from util import manifest_utils +from util import resource_utils + +_INCREMENTAL_APP_NAME = 'org.chromium.incrementalinstall.BootstrapApplication' +_META_DATA_APP_NAME = 'incremental-install-real-app' +_DEFAULT_APPLICATION_CLASS = 'android.app.Application' +_META_DATA_INSTRUMENTATION_NAMES = [ + 'incremental-install-real-instrumentation-0', + 'incremental-install-real-instrumentation-1', +] +_INCREMENTAL_INSTRUMENTATION_CLASSES = [ + 'android.app.Instrumentation', + 'org.chromium.incrementalinstall.SecondInstrumentation', +] + + +def _AddNamespace(name): + """Adds the android namespace prefix to the given identifier.""" + return '{%s}%s' % (manifest_utils.ANDROID_NAMESPACE, name) + + +def _ParseArgs(args): + parser = argparse.ArgumentParser() + parser.add_argument( + '--src-manifest', required=True, help='The main manifest of the app') + parser.add_argument('--disable-isolated-processes', + help='Changes all android:isolatedProcess to false. ' + 'This is required on Android M+', + action='store_true') + parser.add_argument( + '--out-apk', required=True, help='Path to output .ap_ file') + parser.add_argument( + '--in-apk', required=True, help='Path to non-incremental .ap_ file') + parser.add_argument( + '--aapt2-path', required=True, help='Path to the Android aapt tool') + parser.add_argument( + '--android-sdk-jars', help='GN List of resource apks to include.') + + ret = parser.parse_args(build_utils.ExpandFileArgs(args)) + ret.android_sdk_jars = build_utils.ParseGnList(ret.android_sdk_jars) + return ret + + +def _CreateMetaData(parent, name, value): + meta_data_node = ElementTree.SubElement(parent, 'meta-data') + meta_data_node.set(_AddNamespace('name'), name) + meta_data_node.set(_AddNamespace('value'), value) + + +def _ProcessManifest(path, arsc_package_name, disable_isolated_processes): + doc, manifest_node, app_node = manifest_utils.ParseManifest(path) + + # Ensure the manifest package matches that of the apk's arsc package + # So that resource references resolve correctly. The actual manifest + # package name is set via --rename-manifest-package. + manifest_node.set('package', arsc_package_name) + + # Pylint for some reason things app_node is an int. + # pylint: disable=no-member + real_app_class = app_node.get(_AddNamespace('name'), + _DEFAULT_APPLICATION_CLASS) + app_node.set(_AddNamespace('name'), _INCREMENTAL_APP_NAME) + # pylint: enable=no-member + _CreateMetaData(app_node, _META_DATA_APP_NAME, real_app_class) + + # Seems to be a bug in ElementTree, as doc.find() doesn't work here. + instrumentation_nodes = doc.findall('instrumentation') + assert len(instrumentation_nodes) <= 2, ( + 'Need to update incremental install to support >2 tags') + for i, instrumentation_node in enumerate(instrumentation_nodes): + real_instrumentation_class = instrumentation_node.get(_AddNamespace('name')) + instrumentation_node.set(_AddNamespace('name'), + _INCREMENTAL_INSTRUMENTATION_CLASSES[i]) + _CreateMetaData(app_node, _META_DATA_INSTRUMENTATION_NAMES[i], + real_instrumentation_class) + + ret = ElementTree.tostring(doc.getroot(), encoding='UTF-8') + # Disable check for page-aligned native libraries. + ret = ret.replace(b'extractNativeLibs="false"', b'extractNativeLibs="true"') + if disable_isolated_processes: + ret = ret.replace(b'isolatedProcess="true"', b'isolatedProcess="false"') + return ret + + +def main(raw_args): + options = _ParseArgs(raw_args) + + arsc_package, _ = resource_utils.ExtractArscPackage(options.aapt2_path, + options.in_apk) + assert arsc_package is not None, 'The apk does not have a valid package.' + # Extract version from the compiled manifest since it might have been set + # via aapt, and not exist in the manifest's text form. + version_code, version_name, manifest_package = ( + resource_utils.ExtractBinaryManifestValues(options.aapt2_path, + options.in_apk)) + + new_manifest_data = _ProcessManifest(options.src_manifest, arsc_package, + options.disable_isolated_processes) + with tempfile.NamedTemporaryFile() as tmp_manifest, \ + tempfile.NamedTemporaryFile() as tmp_apk: + tmp_manifest.write(new_manifest_data) + tmp_manifest.flush() + cmd = [ + options.aapt2_path, 'link', '-o', tmp_apk.name, '--manifest', + tmp_manifest.name, '-I', options.in_apk, '--replace-version', + '--version-code', version_code, '--version-name', version_name, + '--rename-manifest-package', manifest_package, '--debug-mode' + ] + for j in options.android_sdk_jars: + cmd += ['-I', j] + subprocess.check_call(cmd) + with zipfile.ZipFile(options.out_apk, 'w') as z: + path_transform = lambda p: None if p != 'AndroidManifest.xml' else p + build_utils.MergeZips(z, [tmp_apk.name], path_transform=path_transform) + path_transform = lambda p: None if p == 'AndroidManifest.xml' else p + build_utils.MergeZips(z, [options.in_apk], path_transform=path_transform) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.pydeps b/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.pydeps new file mode 100644 index 0000000000..b28c3070d8 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/generate_android_manifest.pydeps @@ -0,0 +1,32 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/incremental_install --output build/android/incremental_install/generate_android_manifest.pydeps build/android/incremental_install/generate_android_manifest.py +../../../third_party/jinja2/__init__.py +../../../third_party/jinja2/_compat.py +../../../third_party/jinja2/_identifier.py +../../../third_party/jinja2/asyncfilters.py +../../../third_party/jinja2/asyncsupport.py +../../../third_party/jinja2/bccache.py +../../../third_party/jinja2/compiler.py +../../../third_party/jinja2/defaults.py +../../../third_party/jinja2/environment.py +../../../third_party/jinja2/exceptions.py +../../../third_party/jinja2/filters.py +../../../third_party/jinja2/idtracking.py +../../../third_party/jinja2/lexer.py +../../../third_party/jinja2/loaders.py +../../../third_party/jinja2/nodes.py +../../../third_party/jinja2/optimizer.py +../../../third_party/jinja2/parser.py +../../../third_party/jinja2/runtime.py +../../../third_party/jinja2/tests.py +../../../third_party/jinja2/utils.py +../../../third_party/jinja2/visitor.py +../../../third_party/markupsafe/__init__.py +../../../third_party/markupsafe/_compat.py +../../../third_party/markupsafe/_native.py +../../gn_helpers.py +../gyp/util/__init__.py +../gyp/util/build_utils.py +../gyp/util/manifest_utils.py +../gyp/util/resource_utils.py +generate_android_manifest.py diff --git a/third_party/libwebrtc/build/android/incremental_install/installer.py b/third_party/libwebrtc/build/android/incremental_install/installer.py new file mode 100755 index 0000000000..55e578884e --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/installer.py @@ -0,0 +1,372 @@ +#!/usr/bin/env vpython3 +# +# 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. + +"""Install *_incremental.apk targets as well as their dependent files.""" + +import argparse +import collections +import functools +import glob +import json +import logging +import os +import posixpath +import shutil +import sys + +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) +import devil_chromium +from devil.android import apk_helper +from devil.android import device_utils +from devil.utils import reraiser_thread +from devil.utils import run_tests_helper +from pylib import constants +from pylib.utils import time_profile + +prev_sys_path = list(sys.path) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) +import dex +from util import build_utils +sys.path = prev_sys_path + + +_R8_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'r8', 'lib', + 'r8.jar') + + +def _DeviceCachePath(device): + file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() + return os.path.join(constants.GetOutDirectory(), file_name) + + +def _Execute(concurrently, *funcs): + """Calls all functions in |funcs| concurrently or in sequence.""" + timer = time_profile.TimeProfile() + if concurrently: + reraiser_thread.RunAsync(funcs) + else: + for f in funcs: + f() + timer.Stop(log=False) + return timer + + +def _GetDeviceIncrementalDir(package): + """Returns the device path to put incremental files for the given package.""" + return '/data/local/tmp/incremental-app-%s' % package + + +def _IsStale(src_paths, dest): + """Returns if |dest| is older than any of |src_paths|, or missing.""" + if not os.path.exists(dest): + return True + dest_time = os.path.getmtime(dest) + for path in src_paths: + if os.path.getmtime(path) > dest_time: + return True + return False + + +def _AllocateDexShards(dex_files): + """Divides input dex files into buckets.""" + # Goals: + # * Make shards small enough that they are fast to merge. + # * Minimize the number of shards so they load quickly on device. + # * Partition files into shards such that a change in one file results in only + # one shard having to be re-created. + shards = collections.defaultdict(list) + # As of Oct 2019, 10 shards results in a min/max size of 582K/2.6M. + NUM_CORE_SHARDS = 10 + # As of Oct 2019, 17 dex files are larger than 1M. + SHARD_THRESHOLD = 2**20 + for src_path in dex_files: + if os.path.getsize(src_path) >= SHARD_THRESHOLD: + # Use the path as the name rather than an incrementing number to ensure + # that it shards to the same name every time. + name = os.path.relpath(src_path, constants.GetOutDirectory()).replace( + os.sep, '.') + shards[name].append(src_path) + else: + name = 'shard{}.dex.jar'.format(hash(src_path) % NUM_CORE_SHARDS) + shards[name].append(src_path) + logging.info('Sharding %d dex files into %d buckets', len(dex_files), + len(shards)) + return shards + + +def _CreateDexFiles(shards, dex_staging_dir, min_api, use_concurrency): + """Creates dex files within |dex_staging_dir| defined by |shards|.""" + tasks = [] + for name, src_paths in shards.items(): + dest_path = os.path.join(dex_staging_dir, name) + if _IsStale(src_paths, dest_path): + tasks.append( + functools.partial(dex.MergeDexForIncrementalInstall, _R8_PATH, + src_paths, dest_path, min_api)) + + # TODO(agrieve): It would be more performant to write a custom d8.jar + # wrapper in java that would process these in bulk, rather than spinning + # up a new process for each one. + _Execute(use_concurrency, *tasks) + + # Remove any stale shards. + for name in os.listdir(dex_staging_dir): + if name not in shards: + os.unlink(os.path.join(dex_staging_dir, name)) + + +def Uninstall(device, package, enable_device_cache=False): + """Uninstalls and removes all incremental files for the given package.""" + main_timer = time_profile.TimeProfile() + device.Uninstall(package) + if enable_device_cache: + # Uninstall is rare, so just wipe the cache in this case. + cache_path = _DeviceCachePath(device) + if os.path.exists(cache_path): + os.unlink(cache_path) + device.RunShellCommand(['rm', '-rf', _GetDeviceIncrementalDir(package)], + check_return=True) + logging.info('Uninstall took %s seconds.', main_timer.GetDelta()) + + +def Install(device, install_json, apk=None, enable_device_cache=False, + use_concurrency=True, permissions=()): + """Installs the given incremental apk and all required supporting files. + + Args: + device: A DeviceUtils instance (to install to). + install_json: Path to .json file or already parsed .json object. + apk: An existing ApkHelper instance for the apk (optional). + enable_device_cache: Whether to enable on-device caching of checksums. + use_concurrency: Whether to speed things up using multiple threads. + permissions: A list of the permissions to grant, or None to grant all + non-denylisted permissions in the manifest. + """ + if isinstance(install_json, str): + with open(install_json) as f: + install_dict = json.load(f) + else: + install_dict = install_json + + main_timer = time_profile.TimeProfile() + install_timer = time_profile.TimeProfile() + push_native_timer = time_profile.TimeProfile() + merge_dex_timer = time_profile.TimeProfile() + push_dex_timer = time_profile.TimeProfile() + + def fix_path(p): + return os.path.normpath(os.path.join(constants.GetOutDirectory(), p)) + + if not apk: + apk = apk_helper.ToHelper(fix_path(install_dict['apk_path'])) + split_globs = [fix_path(p) for p in install_dict['split_globs']] + native_libs = [fix_path(p) for p in install_dict['native_libs']] + dex_files = [fix_path(p) for p in install_dict['dex_files']] + show_proguard_warning = install_dict.get('show_proguard_warning') + + apk_package = apk.GetPackageName() + device_incremental_dir = _GetDeviceIncrementalDir(apk_package) + dex_staging_dir = os.path.join(constants.GetOutDirectory(), + 'incremental-install', + install_dict['apk_path']) + device_dex_dir = posixpath.join(device_incremental_dir, 'dex') + + # Install .apk(s) if any of them have changed. + def do_install(): + install_timer.Start() + if split_globs: + splits = [] + for split_glob in split_globs: + splits.extend((f for f in glob.glob(split_glob))) + device.InstallSplitApk( + apk, + splits, + allow_downgrade=True, + reinstall=True, + allow_cached_props=True, + permissions=permissions) + else: + device.Install( + apk, allow_downgrade=True, reinstall=True, permissions=permissions) + install_timer.Stop(log=False) + + # Push .so and .dex files to the device (if they have changed). + def do_push_files(): + + def do_push_native(): + push_native_timer.Start() + if native_libs: + with build_utils.TempDir() as temp_dir: + device_lib_dir = posixpath.join(device_incremental_dir, 'lib') + for path in native_libs: + # Note: Can't use symlinks as they don't work when + # "adb push parent_dir" is used (like we do here). + shutil.copy(path, os.path.join(temp_dir, os.path.basename(path))) + device.PushChangedFiles([(temp_dir, device_lib_dir)], + delete_device_stale=True) + push_native_timer.Stop(log=False) + + def do_merge_dex(): + merge_dex_timer.Start() + shards = _AllocateDexShards(dex_files) + build_utils.MakeDirectory(dex_staging_dir) + _CreateDexFiles(shards, dex_staging_dir, apk.GetMinSdkVersion(), + use_concurrency) + merge_dex_timer.Stop(log=False) + + def do_push_dex(): + push_dex_timer.Start() + device.PushChangedFiles([(dex_staging_dir, device_dex_dir)], + delete_device_stale=True) + push_dex_timer.Stop(log=False) + + _Execute(use_concurrency, do_push_native, do_merge_dex) + do_push_dex() + + def check_device_configured(): + target_sdk_version = int(apk.GetTargetSdkVersion()) + # Beta Q builds apply allowlist to targetSdk=28 as well. + if target_sdk_version >= 28 and device.build_version_sdk >= 28: + # In P, there are two settings: + # * hidden_api_policy_p_apps + # * hidden_api_policy_pre_p_apps + # In Q, there is just one: + # * hidden_api_policy + if device.build_version_sdk == 28: + setting_name = 'hidden_api_policy_p_apps' + else: + setting_name = 'hidden_api_policy' + apis_allowed = ''.join( + device.RunShellCommand(['settings', 'get', 'global', setting_name], + check_return=True)) + if apis_allowed.strip() not in '01': + msg = """\ +Cannot use incremental installs on Android P+ without first enabling access to +non-SDK interfaces (https://developer.android.com/preview/non-sdk-q). + +To enable access: + adb -s {0} shell settings put global {1} 0 +To restore back to default: + adb -s {0} shell settings delete global {1}""" + raise Exception(msg.format(device.serial, setting_name)) + + cache_path = _DeviceCachePath(device) + def restore_cache(): + if not enable_device_cache: + return + if os.path.exists(cache_path): + logging.info('Using device cache: %s', cache_path) + with open(cache_path) as f: + device.LoadCacheData(f.read()) + # Delete the cached file so that any exceptions cause it to be cleared. + os.unlink(cache_path) + else: + logging.info('No device cache present: %s', cache_path) + + def save_cache(): + if not enable_device_cache: + return + with open(cache_path, 'w') as f: + f.write(device.DumpCacheData()) + logging.info('Wrote device cache: %s', cache_path) + + # Create 2 lock files: + # * install.lock tells the app to pause on start-up (until we release it). + # * firstrun.lock is used by the app to pause all secondary processes until + # the primary process finishes loading the .dex / .so files. + def create_lock_files(): + # Creates or zeros out lock files. + cmd = ('D="%s";' + 'mkdir -p $D &&' + 'echo -n >$D/install.lock 2>$D/firstrun.lock') + device.RunShellCommand( + cmd % device_incremental_dir, shell=True, check_return=True) + + # The firstrun.lock is released by the app itself. + def release_installer_lock(): + device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir, + check_return=True, shell=True) + + # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't + # been designed for multi-threading. Enabling only because this is a + # developer-only tool. + setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache, + check_device_configured) + + _Execute(use_concurrency, do_install, do_push_files) + + finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) + + logging.info( + 'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, ' + 'dex_merge=%s dex_push=%s, finalize=%s)', os.path.basename(apk.path), + main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), + push_native_timer.GetDelta(), merge_dex_timer.GetDelta(), + push_dex_timer.GetDelta(), finalize_timer.GetDelta()) + if show_proguard_warning: + logging.warning('Target had proguard enabled, but incremental install uses ' + 'non-proguarded .dex files. Performance characteristics ' + 'may differ.') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('json_path', + help='The path to the generated incremental apk .json.') + parser.add_argument('-d', '--device', dest='device', + help='Target device for apk to install on.') + parser.add_argument('--uninstall', + action='store_true', + default=False, + help='Remove the app and all side-loaded files.') + parser.add_argument('--output-directory', + help='Path to the root build directory.') + parser.add_argument('--no-threading', + action='store_false', + default=True, + dest='threading', + help='Do not install and push concurrently') + parser.add_argument('--no-cache', + action='store_false', + default=True, + dest='cache', + help='Do not use cached information about what files are ' + 'currently on the target device.') + parser.add_argument('-v', + '--verbose', + dest='verbose_count', + default=0, + action='count', + help='Verbose level (multiple times for more)') + + args = parser.parse_args() + + run_tests_helper.SetLogLevel(args.verbose_count) + if args.output_directory: + constants.SetOutputDirectory(args.output_directory) + + devil_chromium.Initialize(output_directory=constants.GetOutDirectory()) + + # Retries are annoying when commands fail for legitimate reasons. Might want + # to enable them if this is ever used on bots though. + device = device_utils.DeviceUtils.HealthyDevices( + device_arg=args.device, + default_retries=0, + enable_device_files_cache=True)[0] + + if args.uninstall: + with open(args.json_path) as f: + install_dict = json.load(f) + apk = apk_helper.ToHelper(install_dict['apk_path']) + Uninstall(device, apk.GetPackageName(), enable_device_cache=args.cache) + else: + Install(device, args.json_path, enable_device_cache=args.cache, + use_concurrency=args.threading) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java new file mode 100644 index 0000000000..f7003f27ea --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapApplication.java @@ -0,0 +1,297 @@ +// 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.incrementalinstall; + +import android.app.Application; +import android.app.Instrumentation; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.util.Log; + +import dalvik.system.DexFile; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Map; + +/** + * An Application that replaces itself with another Application (as defined in + * an AndroidManifext.xml meta-data tag). It loads the other application only + * after side-loading its .so and .dex files from /data/local/tmp. + * + * This class is highly dependent on the private implementation details of + * Android's ActivityThread.java. However, it has been tested to work with + * JellyBean through Marshmallow. + */ +public final class BootstrapApplication extends Application { + private static final String TAG = "incrementalinstall"; + private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incremental-app-"; + private static final String REAL_APP_META_DATA_NAME = "incremental-install-real-app"; + private static final String REAL_INSTRUMENTATION_META_DATA_NAME0 = + "incremental-install-real-instrumentation-0"; + private static final String REAL_INSTRUMENTATION_META_DATA_NAME1 = + "incremental-install-real-instrumentation-1"; + + private ClassLoaderPatcher mClassLoaderPatcher; + private Application mRealApplication; + private Instrumentation mOrigInstrumentation; + private Instrumentation mRealInstrumentation; + private Object mStashedProviderList; + private Object mActivityThread; + public static DexFile[] sIncrementalDexFiles; // Needed by junit test runner. + + @Override + protected void attachBaseContext(Context context) { + super.attachBaseContext(context); + try { + mActivityThread = Reflect.invokeMethod(Class.forName("android.app.ActivityThread"), + "currentActivityThread"); + mClassLoaderPatcher = new ClassLoaderPatcher(context); + + mOrigInstrumentation = + (Instrumentation) Reflect.getField(mActivityThread, "mInstrumentation"); + Context instContext = mOrigInstrumentation.getContext(); + if (instContext == null) { + instContext = context; + } + + // When running with an instrumentation that lives in a different package from the + // application, we must load the dex files and native libraries from both pacakges. + // This logic likely won't work when the instrumentation is incremental, but the app is + // non-incremental. This configuration isn't used right now though. + String appPackageName = getPackageName(); + String instPackageName = instContext.getPackageName(); + boolean instPackageNameDiffers = !appPackageName.equals(instPackageName); + Log.i(TAG, "App PackageName: " + appPackageName); + if (instPackageNameDiffers) { + Log.i(TAG, "Inst PackageName: " + instPackageName); + } + + File appIncrementalRootDir = new File(MANAGED_DIR_PREFIX + appPackageName); + File appLibDir = new File(appIncrementalRootDir, "lib"); + File appDexDir = new File(appIncrementalRootDir, "dex"); + File appInstallLockFile = new File(appIncrementalRootDir, "install.lock"); + File appFirstRunLockFile = new File(appIncrementalRootDir, "firstrun.lock"); + File instIncrementalRootDir = new File(MANAGED_DIR_PREFIX + instPackageName); + File instLibDir = new File(instIncrementalRootDir, "lib"); + File instDexDir = new File(instIncrementalRootDir, "dex"); + File instInstallLockFile = new File(instIncrementalRootDir, "install.lock"); + File instFirstRunLockFile = new File(instIncrementalRootDir, "firstrun.lock"); + + boolean isFirstRun = LockFile.installerLockExists(appFirstRunLockFile) + || (instPackageNameDiffers + && LockFile.installerLockExists(instFirstRunLockFile)); + if (isFirstRun) { + if (mClassLoaderPatcher.mIsPrimaryProcess) { + // Wait for incremental_install.py to finish. + LockFile.waitForInstallerLock(appInstallLockFile, 30 * 1000); + LockFile.waitForInstallerLock(instInstallLockFile, 30 * 1000); + } else { + // Wait for the browser process to create the optimized dex files + // and copy the library files. + LockFile.waitForInstallerLock(appFirstRunLockFile, 60 * 1000); + LockFile.waitForInstallerLock(instFirstRunLockFile, 60 * 1000); + } + } + + mClassLoaderPatcher.importNativeLibs(instLibDir); + sIncrementalDexFiles = mClassLoaderPatcher.loadDexFiles(instDexDir, instPackageName); + if (instPackageNameDiffers) { + mClassLoaderPatcher.importNativeLibs(appLibDir); + mClassLoaderPatcher.loadDexFiles(appDexDir, appPackageName); + } + + if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) { + LockFile.clearInstallerLock(appFirstRunLockFile); + if (instPackageNameDiffers) { + LockFile.clearInstallerLock(instFirstRunLockFile); + } + } + + // mInstrumentationAppDir is one of a set of fields that is initialized only when + // instrumentation is active. + if (Reflect.getField(mActivityThread, "mInstrumentationAppDir") != null) { + String metaDataName = REAL_INSTRUMENTATION_META_DATA_NAME0; + if (mOrigInstrumentation instanceof SecondInstrumentation) { + metaDataName = REAL_INSTRUMENTATION_META_DATA_NAME1; + } + mRealInstrumentation = + initInstrumentation(getClassNameFromMetadata(metaDataName, instContext)); + } else { + Log.i(TAG, "No instrumentation active."); + } + + // Even when instrumentation is not enabled, ActivityThread uses a default + // Instrumentation instance internally. We hook it here in order to hook into the + // call to Instrumentation.onCreate(). + BootstrapInstrumentation bootstrapInstrumentation = new BootstrapInstrumentation(this); + populateInstrumenationFields(bootstrapInstrumentation); + Reflect.setField(mActivityThread, "mInstrumentation", bootstrapInstrumentation); + + // attachBaseContext() is called from ActivityThread#handleBindApplication() and + // Application#mApplication is changed right after we return. Thus, we cannot swap + // the Application instances until onCreate() is called. + String realApplicationName = getClassNameFromMetadata(REAL_APP_META_DATA_NAME, context); + Log.i(TAG, "Instantiating " + realApplicationName); + Instrumentation anyInstrumentation = + mRealInstrumentation != null ? mRealInstrumentation : mOrigInstrumentation; + mRealApplication = anyInstrumentation.newApplication( + getClassLoader(), realApplicationName, context); + + // Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate + // all ContentProviders. The ContentProviders break without the correct Application + // class being installed, so temporarily pretend there are no providers, and then + // instantiate them explicitly within onCreate(). + disableContentProviders(); + Log.i(TAG, "Waiting for Instrumentation.onCreate"); + } catch (Exception e) { + throw new RuntimeException("Incremental install failed.", e); + } + } + + /** + * Returns the fully-qualified class name for the given key, stored in a + * <meta> witin the manifest. + */ + private static String getClassNameFromMetadata(String key, Context context) + throws NameNotFoundException { + String pkgName = context.getPackageName(); + ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(pkgName, + PackageManager.GET_META_DATA); + String value = appInfo.metaData.getString(key); + if (value != null && !value.contains(".")) { + value = pkgName + "." + value; + } + return value; + } + + /** + * Instantiates and initializes mRealInstrumentation (the real Instrumentation class). + */ + private Instrumentation initInstrumentation(String realInstrumentationName) + throws ReflectiveOperationException { + if (realInstrumentationName == null) { + // This is the case when an incremental app is used as a target for an instrumentation + // test. In this case, ActivityThread can instantiate the proper class just fine since + // it exists within the test apk (as opposed to the incremental apk-under-test). + Log.i(TAG, "Running with external instrumentation"); + return null; + } + // For unit tests, the instrumentation class is replaced in the manifest by a build step + // because ActivityThread tries to instantiate it before we get a chance to load the + // incremental dex files. + Log.i(TAG, "Instantiating instrumentation " + realInstrumentationName); + Instrumentation ret = + (Instrumentation) Reflect.newInstance(Class.forName(realInstrumentationName)); + populateInstrumenationFields(ret); + return ret; + } + + /** + * Sets important fields on a newly created Instrumentation object by copying them from the + * original Instrumentation instance. + */ + private void populateInstrumenationFields(Instrumentation target) + throws ReflectiveOperationException { + // Initialize the fields that are set by Instrumentation.init(). + String[] initFields = {"mAppContext", "mComponent", "mInstrContext", "mMessageQueue", + "mThread", "mUiAutomationConnection", "mWatcher"}; + for (String fieldName : initFields) { + Reflect.setField(target, fieldName, Reflect.getField(mOrigInstrumentation, fieldName)); + } + } + + /** + * Called by BootstrapInstrumentation from Instrumentation.onCreate(). + * This happens regardless of whether or not instrumentation is enabled. + */ + void onInstrumentationCreate(Bundle arguments) { + Log.i(TAG, "Instrumentation.onCreate() called. Swapping references."); + try { + swapApplicationReferences(); + enableContentProviders(); + if (mRealInstrumentation != null) { + Reflect.setField(mActivityThread, "mInstrumentation", mRealInstrumentation); + mRealInstrumentation.onCreate(arguments); + } + } catch (Exception e) { + throw new RuntimeException("Incremental install failed.", e); + } + } + + @Override + public void onCreate() { + super.onCreate(); + try { + Log.i(TAG, "Application.onCreate() called."); + mRealApplication.onCreate(); + } catch (Exception e) { + throw new RuntimeException("Incremental install failed.", e); + } + } + + /** + * Nulls out ActivityThread.mBoundApplication.providers. + */ + private void disableContentProviders() throws ReflectiveOperationException { + Object data = Reflect.getField(mActivityThread, "mBoundApplication"); + mStashedProviderList = Reflect.getField(data, "providers"); + Reflect.setField(data, "providers", null); + } + + /** + * Restores the value of ActivityThread.mBoundApplication.providers, and invokes + * ActivityThread#installContentProviders(). + */ + private void enableContentProviders() throws ReflectiveOperationException { + Object data = Reflect.getField(mActivityThread, "mBoundApplication"); + Reflect.setField(data, "providers", mStashedProviderList); + if (mStashedProviderList != null && mClassLoaderPatcher.mIsPrimaryProcess) { + Log.i(TAG, "Instantiating content providers"); + Reflect.invokeMethod(mActivityThread, "installContentProviders", mRealApplication, + mStashedProviderList); + } + mStashedProviderList = null; + } + + /** + * Changes all fields within framework classes that have stored an reference to this + * BootstrapApplication to instead store references to mRealApplication. + */ + @SuppressWarnings("unchecked") + private void swapApplicationReferences() throws ReflectiveOperationException { + if (Reflect.getField(mActivityThread, "mInitialApplication") == this) { + Reflect.setField(mActivityThread, "mInitialApplication", mRealApplication); + } + + List allApplications = + (List) Reflect.getField(mActivityThread, "mAllApplications"); + for (int i = 0; i < allApplications.size(); i++) { + if (allApplications.get(i) == this) { + allApplications.set(i, mRealApplication); + } + } + + // Contains a reference to BootstrapApplication and will cause BroadCastReceivers to fail + // if not replaced. + Context contextImpl = mRealApplication.getBaseContext(); + Reflect.setField(contextImpl, "mOuterContext", mRealApplication); + + for (String fieldName : new String[] {"mPackages", "mResourcePackages"}) { + Map> packageMap = + (Map>) Reflect.getField(mActivityThread, fieldName); + for (Map.Entry> entry : packageMap.entrySet()) { + Object loadedApk = entry.getValue().get(); + if (loadedApk != null && Reflect.getField(loadedApk, "mApplication") == this) { + Reflect.setField(loadedApk, "mApplication", mRealApplication); + } + } + } + } +} diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapInstrumentation.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapInstrumentation.java new file mode 100644 index 0000000000..f197406499 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/BootstrapInstrumentation.java @@ -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. + +package org.chromium.incrementalinstall; + +import android.app.Instrumentation; +import android.os.Bundle; + +/** + * Notifies BootstrapApplication of the call to Instrumentation.onCreate(). + */ +public final class BootstrapInstrumentation extends Instrumentation { + private final BootstrapApplication mApp; + + BootstrapInstrumentation(BootstrapApplication app) { + mApp = app; + } + + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + mApp.onInstrumentationCreate(arguments); + } +} diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java new file mode 100644 index 0000000000..b6d752247b --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java @@ -0,0 +1,312 @@ +// 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.incrementalinstall; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import dalvik.system.DexFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +/** + * Provides the ability to add native libraries and .dex files to an existing class loader. + * Tested with Jellybean MR2 - Marshmellow. + */ +final class ClassLoaderPatcher { + private static final String TAG = "incrementalinstall"; + private final File mAppFilesSubDir; + private final ClassLoader mClassLoader; + private final Object mLibcoreOs; + private final int mProcessUid; + final boolean mIsPrimaryProcess; + + ClassLoaderPatcher(Context context) throws ReflectiveOperationException { + mAppFilesSubDir = + new File(context.getApplicationInfo().dataDir, "incremental-install-files"); + mClassLoader = context.getClassLoader(); + mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os"); + mProcessUid = Process.myUid(); + mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid; + Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + ")"); + } + + /** + * Loads all dex files within |dexDir| into the app's ClassLoader. + */ + @SuppressLint({ + "SetWorldReadable", + "SetWorldWritable", + }) + DexFile[] loadDexFiles(File dexDir, String packageName) + throws ReflectiveOperationException, IOException { + Log.i(TAG, "Installing dex files from: " + dexDir); + + File optimizedDir = null; + boolean isAtLeastOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + + if (isAtLeastOreo) { + // In O, optimizedDirectory is ignored, and the files are always put in an "oat" + // directory that is a sibling to the dex files themselves. SELinux policies + // prevent using odex files from /data/local/tmp, so we must first copy them + // into the app's data directory in order to get the odex files to live there. + // Use a package-name subdirectory to prevent name collisions when apk-under-test is + // used. + File newDexDir = new File(mAppFilesSubDir, packageName + "-dexes"); + if (mIsPrimaryProcess) { + safeCopyAllFiles(dexDir, newDexDir); + } + dexDir = newDexDir; + } else { + // The optimized dex files will be owned by this process' user. + // Store them within the app's data dir rather than on /data/local/tmp + // so that they are still deleted (by the OS) when we uninstall + // (even on a non-rooted device). + File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes"); + File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes"); + + if (mIsPrimaryProcess) { + ensureAppFilesSubDirExists(); + // Allows isolated processes to access the same files. + incrementalDexesDir.mkdir(); + incrementalDexesDir.setReadable(true, false); + incrementalDexesDir.setExecutable(true, false); + // Create a directory for isolated processes to create directories in. + isolatedDexesDir.mkdir(); + isolatedDexesDir.setWritable(true, false); + isolatedDexesDir.setExecutable(true, false); + + optimizedDir = incrementalDexesDir; + } else { + // There is a UID check of the directory in dalvik.system.DexFile(): + // https://android.googlesource.com/platform/libcore/+/45e0260/dalvik/src/main/java/dalvik/system/DexFile.java#101 + // Rather than have each isolated process run DexOpt though, we use + // symlinks within the directory to point at the browser process' + // optimized dex files. + optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid); + optimizedDir.mkdir(); + // Always wipe it out and re-create for simplicity. + Log.i(TAG, "Creating dex file symlinks for isolated process"); + for (File f : optimizedDir.listFiles()) { + f.delete(); + } + for (File f : incrementalDexesDir.listFiles()) { + String to = "../../" + incrementalDexesDir.getName() + "/" + f.getName(); + File from = new File(optimizedDir, f.getName()); + createSymlink(to, from); + } + } + Log.i(TAG, "Code cache dir: " + optimizedDir); + } + + // Ignore "oat" directory. + // Also ignore files that sometimes show up (e.g. .jar.arm.flock). + File[] dexFilesArr = dexDir.listFiles(f -> f.getName().endsWith(".jar")); + if (dexFilesArr == null) { + throw new FileNotFoundException("Dex dir does not exist: " + dexDir); + } + + Log.i(TAG, "Loading " + dexFilesArr.length + " dex files"); + + Object dexPathList = Reflect.getField(mClassLoader, "pathList"); + Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElements"); + dexElements = addDexElements(dexFilesArr, optimizedDir, dexElements); + Reflect.setField(dexPathList, "dexElements", dexElements); + + // Return the list of new DexFile instances for the .jars in dexPathList. + DexFile[] ret = new DexFile[dexFilesArr.length]; + int startIndex = dexElements.length - dexFilesArr.length; + for (int i = 0; i < ret.length; ++i) { + ret[i] = (DexFile) Reflect.getField(dexElements[startIndex + i], "dexFile"); + } + return ret; + } + + /** + * Sets up all libraries within |libDir| to be loadable by System.loadLibrary(). + */ + @SuppressLint("SetWorldReadable") + void importNativeLibs(File libDir) throws ReflectiveOperationException, IOException { + Log.i(TAG, "Importing native libraries from: " + libDir); + if (!libDir.exists()) { + Log.i(TAG, "No native libs exist."); + return; + } + // The library copying is not necessary on older devices, but we do it anyways to + // simplify things (it's fast compared to dexing). + // https://code.google.com/p/android/issues/detail?id=79480 + File localLibsDir = new File(mAppFilesSubDir, "lib"); + safeCopyAllFiles(libDir, localLibsDir); + addNativeLibrarySearchPath(localLibsDir); + } + + @SuppressLint("SetWorldReadable") + private void safeCopyAllFiles(File srcDir, File dstDir) throws IOException { + // The library copying is not necessary on older devices, but we do it anyways to + // simplify things (it's fast compared to dexing). + // https://code.google.com/p/android/issues/detail?id=79480 + File lockFile = new File(mAppFilesSubDir, dstDir.getName() + ".lock"); + if (mIsPrimaryProcess) { + ensureAppFilesSubDirExists(); + LockFile lock = LockFile.acquireRuntimeLock(lockFile); + if (lock == null) { + LockFile.waitForRuntimeLock(lockFile, 10 * 1000); + } else { + try { + dstDir.mkdir(); + dstDir.setReadable(true, false); + dstDir.setExecutable(true, false); + copyChangedFiles(srcDir, dstDir); + } finally { + lock.release(); + } + } + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // TODO: Work around this issue by using APK splits to install each dex / lib. + throw new RuntimeException("Incremental install does not work on Android M+ " + + "with isolated processes. Build system should have removed this. " + + "Please file a bug."); + } + // Other processes: Waits for primary process to finish copying. + LockFile.waitForRuntimeLock(lockFile, 10 * 1000); + } + } + + @SuppressWarnings("unchecked") + private void addNativeLibrarySearchPath(File nativeLibDir) throws ReflectiveOperationException { + Object dexPathList = Reflect.getField(mClassLoader, "pathList"); + Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirectories"); + File[] newDirs = new File[] { nativeLibDir }; + // Switched from an array to an ArrayList in Lollipop. + if (currentDirs instanceof List) { + List dirsAsList = (List) currentDirs; + dirsAsList.add(0, nativeLibDir); + } else { + File[] dirsAsArray = (File[]) currentDirs; + Reflect.setField(dexPathList, "nativeLibraryDirectories", + Reflect.concatArrays(newDirs, newDirs, dirsAsArray)); + } + + Object[] nativeLibraryPathElements; + try { + nativeLibraryPathElements = + (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathElements"); + } catch (NoSuchFieldException e) { + // This field doesn't exist pre-M. + return; + } + Object[] additionalElements = makeNativePathElements(newDirs); + Reflect.setField(dexPathList, "nativeLibraryPathElements", + Reflect.concatArrays(nativeLibraryPathElements, additionalElements, + nativeLibraryPathElements)); + } + + private static void copyChangedFiles(File srcDir, File dstDir) throws IOException { + int numUpdated = 0; + File[] srcFiles = srcDir.listFiles(); + for (File f : srcFiles) { + // Note: Tried using hardlinks, but resulted in EACCES exceptions. + File dest = new File(dstDir, f.getName()); + if (copyIfModified(f, dest)) { + numUpdated++; + } + } + // Delete stale files. + int numDeleted = 0; + for (File f : dstDir.listFiles()) { + File src = new File(srcDir, f.getName()); + if (!src.exists()) { + numDeleted++; + f.delete(); + } + } + String msg = String.format(Locale.US, + "copyChangedFiles: %d of %d updated. %d stale files removed.", numUpdated, + srcFiles.length, numDeleted); + Log.i(TAG, msg); + } + + @SuppressLint("SetWorldReadable") + private static boolean copyIfModified(File src, File dest) throws IOException { + long lastModified = src.lastModified(); + if (dest.exists() && dest.lastModified() == lastModified) { + return false; + } + Log.i(TAG, "Copying " + src + " -> " + dest); + FileInputStream istream = new FileInputStream(src); + FileOutputStream ostream = new FileOutputStream(dest); + ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size()); + istream.close(); + ostream.close(); + dest.setReadable(true, false); + dest.setExecutable(true, false); + dest.setLastModified(lastModified); + return true; + } + + private void ensureAppFilesSubDirExists() { + mAppFilesSubDir.mkdir(); + mAppFilesSubDir.setExecutable(true, false); + } + + private void createSymlink(String to, File from) throws ReflectiveOperationException { + Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath()); + } + + private static Object[] makeNativePathElements(File[] paths) + throws ReflectiveOperationException { + Object[] entries = new Object[paths.length]; + if (Build.VERSION.SDK_INT >= 26) { + Class entryClazz = Class.forName("dalvik.system.DexPathList$NativeLibraryElement"); + for (int i = 0; i < paths.length; ++i) { + entries[i] = Reflect.newInstance(entryClazz, paths[i]); + } + } else { + Class entryClazz = Class.forName("dalvik.system.DexPathList$Element"); + for (int i = 0; i < paths.length; ++i) { + entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, null); + } + } + return entries; + } + + private Object[] addDexElements(File[] files, File optimizedDirectory, Object[] curDexElements) + throws ReflectiveOperationException { + Class entryClazz = Class.forName("dalvik.system.DexPathList$Element"); + Class clazz = Class.forName("dalvik.system.DexPathList"); + Object[] ret = + Reflect.concatArrays(curDexElements, curDexElements, new Object[files.length]); + File emptyDir = new File(""); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + Object dexFile; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // loadDexFile requires that ret contain all previously added elements. + dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, optimizedDirectory, + mClassLoader, ret); + } else { + dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, optimizedDirectory); + } + Object dexElement; + if (Build.VERSION.SDK_INT >= 26) { + dexElement = Reflect.newInstance(entryClazz, dexFile, file); + } else { + dexElement = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile); + } + ret[curDexElements.length + i] = dexElement; + } + return ret; + } +} diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/LockFile.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/LockFile.java new file mode 100644 index 0000000000..19d1f7624e --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/LockFile.java @@ -0,0 +1,129 @@ +// 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.incrementalinstall; + +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileLock; +import java.util.concurrent.Callable; + +/** + * Helpers for dealing with .lock files used during install / first run. + */ +final class LockFile { + private static final String TAG = "incrementalinstall"; + + private final File mFile; + private final FileOutputStream mOutputStream; + private final FileLock mFileLock; + + private LockFile(File file, FileOutputStream outputStream, FileLock fileLock) { + mFile = file; + mOutputStream = outputStream; + mFileLock = fileLock; + } + + /** + * Clears the lock file by writing to it (making it non-zero in length); + */ + static void clearInstallerLock(File lockFile) throws IOException { + Log.i(TAG, "Clearing " + lockFile); + // On Android M+, we can't delete files in /data/local/tmp, so we write to it instead. + FileOutputStream os = new FileOutputStream(lockFile); + os.write(1); + os.close(); + } + + /** + * Waits for the given file to be non-zero in length. + */ + static void waitForInstallerLock(final File file, long timeoutMs) { + pollingWait(new Callable() { + @Override public Boolean call() { + return !installerLockExists(file); + } + }, file, timeoutMs); + } + + /** + * Waits for the given file to be non-zero in length. + */ + private static void pollingWait(Callable func, File file, long timeoutMs) { + long pollIntervalMs = 200; + for (int i = 0; i < timeoutMs / pollIntervalMs; i++) { + try { + if (func.call()) { + if (i > 0) { + Log.i(TAG, "Finished waiting on lock file: " + file); + } + return; + } else if (i == 0) { + Log.i(TAG, "Waiting on lock file: " + file); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + try { + Thread.sleep(pollIntervalMs); + } catch (InterruptedException e) { + // Should never happen. + } + } + throw new RuntimeException("Timed out waiting for lock file: " + file); + } + + /** + * Returns whether the given lock file is missing or is in the locked state. + */ + static boolean installerLockExists(File file) { + return !file.exists() || file.length() == 0; + } + + /** + * Attempts to acquire a lock for the given file. + * @return Returns the FileLock if it was acquired, or null otherwise. + */ + static LockFile acquireRuntimeLock(File file) { + try { + FileOutputStream outputStream = new FileOutputStream(file); + FileLock lock = outputStream.getChannel().tryLock(); + if (lock != null) { + Log.i(TAG, "Created lock file: " + file); + return new LockFile(file, outputStream, lock); + } + outputStream.close(); + } catch (IOException e) { + // Do nothing. We didn't get the lock. + Log.w(TAG, "Exception trying to acquire lock " + file, e); + } + return null; + } + + /** + * Waits for the given file to not exist. + */ + static void waitForRuntimeLock(final File file, long timeoutMs) { + pollingWait(new Callable() { + @Override public Boolean call() { + return !file.exists(); + } + }, file, timeoutMs); + } + + /** + * Releases and deletes the lock file. + */ + void release() throws IOException { + Log.i(TAG, "Deleting lock file: " + mFile); + mFileLock.release(); + mOutputStream.close(); + if (!mFile.delete()) { + throw new IOException("Failed to delete lock file: " + mFile); + } + } +} diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/Reflect.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/Reflect.java new file mode 100644 index 0000000000..c64dc1e8a3 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/Reflect.java @@ -0,0 +1,142 @@ +// 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.incrementalinstall; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Reflection helper methods. + */ +final class Reflect { + /** + * Sets the value of an object's field (even if it's not visible). + * + * @param instance The object containing the field to set. + * @param name The name of the field to set. + * @param value The new value for the field. + */ + static void setField(Object instance, String name, Object value) + throws ReflectiveOperationException { + Field field = findField(instance, name); + field.setAccessible(true); + field.set(instance, value); + } + + /** + * Retrieves the value of an object's field (even if it's not visible). + * + * @param instance The object containing the field to set. + * @param name The name of the field to set. + * @return The field's value. Primitive values are returned as their boxed + * type. + */ + static Object getField(Object instance, String name) throws ReflectiveOperationException { + Field field = findField(instance, name); + field.setAccessible(true); + return field.get(instance); + } + + /** + * Concatenates two arrays into a new array. The arrays must be of the same + * type. + */ + static Object[] concatArrays(Object[] arrType, Object[] left, Object[] right) { + Object[] result = (Object[]) Array.newInstance( + arrType.getClass().getComponentType(), left.length + right.length); + System.arraycopy(left, 0, result, 0, left.length); + System.arraycopy(right, 0, result, left.length, right.length); + return result; + } + + /** + * Invokes a method with zero or more parameters. For static methods, use the Class as the + * instance. + */ + static Object invokeMethod(Object instance, String name, Object... params) + throws ReflectiveOperationException { + boolean isStatic = instance instanceof Class; + Class clazz = isStatic ? (Class) instance : instance.getClass(); + Method method = findMethod(clazz, name, params); + method.setAccessible(true); + return method.invoke(instance, params); + } + + /** + * Calls a constructor with zero or more parameters. + */ + static Object newInstance(Class clazz, Object... params) + throws ReflectiveOperationException { + Constructor constructor = findConstructor(clazz, params); + constructor.setAccessible(true); + return constructor.newInstance(params); + } + + private static Field findField(Object instance, String name) throws NoSuchFieldException { + boolean isStatic = instance instanceof Class; + Class clazz = isStatic ? (Class) instance : instance.getClass(); + for (; clazz != null; clazz = clazz.getSuperclass()) { + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // Need to look in the super class. + } + } + throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); + } + + private static Method findMethod(Class clazz, String name, Object... params) + throws NoSuchMethodException { + for (; clazz != null; clazz = clazz.getSuperclass()) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().equals(name) + && areParametersCompatible(method.getParameterTypes(), params)) { + return method; + } + } + } + throw new NoSuchMethodException("Method " + name + " with parameters " + + Arrays.asList(params) + " not found in " + clazz); + } + + private static Constructor findConstructor(Class clazz, Object... params) + throws NoSuchMethodException { + for (Constructor constructor : clazz.getDeclaredConstructors()) { + if (areParametersCompatible(constructor.getParameterTypes(), params)) { + return constructor; + } + } + throw new NoSuchMethodException("Constructor with parameters " + Arrays.asList(params) + + " not found in " + clazz); + } + + private static boolean areParametersCompatible(Class[] paramTypes, Object... params) { + if (params.length != paramTypes.length) { + return false; + } + for (int i = 0; i < params.length; i++) { + if (!isAssignableFrom(paramTypes[i], params[i])) { + return false; + } + } + return true; + } + + private static boolean isAssignableFrom(Class left, Object right) { + if (right == null) { + return !left.isPrimitive(); + } + Class rightClazz = right.getClass(); + if (left.isPrimitive()) { + // TODO(agrieve): Fill in the rest as needed. + return left == boolean.class && rightClazz == Boolean.class + || left == int.class && rightClazz == Integer.class; + } + return left.isAssignableFrom(rightClazz); + } +} diff --git a/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/SecondInstrumentation.java b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/SecondInstrumentation.java new file mode 100644 index 0000000000..3e0df0521e --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/java/org/chromium/incrementalinstall/SecondInstrumentation.java @@ -0,0 +1,12 @@ +// Copyright 2017 The Chromium 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.incrementalinstall; + +import android.app.Instrumentation; + +/** + * Exists to support an app having multiple instrumentations. + */ +public final class SecondInstrumentation extends Instrumentation {} diff --git a/third_party/libwebrtc/build/android/incremental_install/write_installer_json.py b/third_party/libwebrtc/build/android/incremental_install/write_installer_json.py new file mode 100755 index 0000000000..ce88e8a036 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/write_installer_json.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +# Copyright 2017 The Chromium 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 .json file with the per-apk details for an incremental install.""" + +import argparse +import json +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) + +from util import build_utils + + +def _ParseArgs(args): + args = build_utils.ExpandFileArgs(args) + parser = argparse.ArgumentParser() + parser.add_argument('--output-path', + help='Output path for .json file.', + required=True) + parser.add_argument('--apk-path', + help='Path to .apk relative to output directory.', + required=True) + parser.add_argument('--split', + action='append', + dest='split_globs', + default=[], + help='A glob matching the apk splits. ' + 'Can be specified multiple times.') + parser.add_argument( + '--native-libs', + action='append', + help='GN-list of paths to native libraries relative to ' + 'output directory. Can be repeated.') + parser.add_argument( + '--dex-files', help='GN-list of dex paths relative to output directory.') + parser.add_argument('--show-proguard-warning', + action='store_true', + default=False, + help='Print a warning about proguard being disabled') + + options = parser.parse_args(args) + options.dex_files = build_utils.ParseGnList(options.dex_files) + options.native_libs = build_utils.ParseGnList(options.native_libs) + return options + + +def main(args): + options = _ParseArgs(args) + + data = { + 'apk_path': options.apk_path, + 'native_libs': options.native_libs, + 'dex_files': options.dex_files, + 'show_proguard_warning': options.show_proguard_warning, + 'split_globs': options.split_globs, + } + + with build_utils.AtomicOutput(options.output_path, mode='w+') as f: + json.dump(data, f, indent=2, sort_keys=True) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/android/incremental_install/write_installer_json.pydeps b/third_party/libwebrtc/build/android/incremental_install/write_installer_json.pydeps new file mode 100644 index 0000000000..11a263f4a8 --- /dev/null +++ b/third_party/libwebrtc/build/android/incremental_install/write_installer_json.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build/android/incremental_install --output build/android/incremental_install/write_installer_json.pydeps build/android/incremental_install/write_installer_json.py +../../gn_helpers.py +../gyp/util/__init__.py +../gyp/util/build_utils.py +write_installer_json.py diff --git a/third_party/libwebrtc/build/android/java/templates/BuildConfig.template b/third_party/libwebrtc/build/android/java/templates/BuildConfig.template new file mode 100644 index 0000000000..8953ad5ca1 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/templates/BuildConfig.template @@ -0,0 +1,95 @@ +// 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.build; + +#define Q(x) #x +#define QUOTE(x) Q(x) + +#if defined(USE_FINAL) +#define MAYBE_FINAL final +#define MAYBE_ZERO = 0 +#define MAYBE_FALSE = false +#else +#define MAYBE_FINAL +#define MAYBE_ZERO +#define MAYBE_FALSE +#endif + +/** + * Build configuration. Generated on a per-target basis. + */ +public class BuildConfig { + +#if defined(ENABLE_MULTIDEX) + public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED = true; +#else + public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED MAYBE_FALSE; +#endif + +#if defined(_ENABLE_ASSERTS) + public static MAYBE_FINAL boolean ENABLE_ASSERTS = true; +#else + public static MAYBE_FINAL boolean ENABLE_ASSERTS MAYBE_FALSE; +#endif + +#if defined(_IS_UBSAN) + public static MAYBE_FINAL boolean IS_UBSAN = true; +#else + public static MAYBE_FINAL boolean IS_UBSAN MAYBE_FALSE; +#endif + +#if defined(_IS_CHROME_BRANDED) + public static MAYBE_FINAL boolean IS_CHROME_BRANDED = true; +#else + public static MAYBE_FINAL boolean IS_CHROME_BRANDED MAYBE_FALSE; +#endif + + // The ID of the android string resource that stores the product version. + // This layer of indirection is necessary to make the resource dependency + // optional for android_apk targets/base_java (ex. for cronet). +#if defined(_RESOURCES_VERSION_VARIABLE) + public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = _RESOURCES_VERSION_VARIABLE; +#else + // Default value, do not use. + public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION MAYBE_ZERO; +#endif + + // Minimum SDK Version supported by this apk. + // Be cautious when using this value, as it can happen that older apks get + // installed on newer Android version (e.g. when a device goes through a + // system upgrade). It is also convenient for developing to have all + // features available through a single APK. + // However, it's pretty safe to assument that a feature specific to KitKat + // will never be needed in an APK with MIN_SDK_VERSION = Oreo. +#if defined(_MIN_SDK_VERSION) + public static MAYBE_FINAL int MIN_SDK_VERSION = _MIN_SDK_VERSION; +#else + public static MAYBE_FINAL int MIN_SDK_VERSION = 1; +#endif + +#if defined(_BUNDLES_SUPPORTED) + public static MAYBE_FINAL boolean BUNDLES_SUPPORTED = true; +#else + public static MAYBE_FINAL boolean BUNDLES_SUPPORTED MAYBE_FALSE; +#endif + +#if defined(_IS_INCREMENTAL_INSTALL) + public static MAYBE_FINAL boolean IS_INCREMENTAL_INSTALL = true; +#else + public static MAYBE_FINAL boolean IS_INCREMENTAL_INSTALL MAYBE_FALSE; +#endif + +#if defined(_IS_CHROMECAST_BRANDING_INTERNAL) + public static MAYBE_FINAL boolean IS_CHROMECAST_BRANDING_INTERNAL = true; +#else + public static MAYBE_FINAL boolean IS_CHROMECAST_BRANDING_INTERNAL MAYBE_FALSE; +#endif + +#if defined(_ISOLATED_SPLITS_ENABLED) + public static MAYBE_FINAL boolean ISOLATED_SPLITS_ENABLED = true; +#else + public static MAYBE_FINAL boolean ISOLATED_SPLITS_ENABLED MAYBE_FALSE; +#endif +} diff --git a/third_party/libwebrtc/build/android/java/templates/ProductConfig.template b/third_party/libwebrtc/build/android/java/templates/ProductConfig.template new file mode 100644 index 0000000000..4bc0d5296b --- /dev/null +++ b/third_party/libwebrtc/build/android/java/templates/ProductConfig.template @@ -0,0 +1,34 @@ +// Copyright 2019 The Chromium 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 PACKAGE; + +#if defined(USE_FINAL) +#define MAYBE_FINAL final +#define MAYBE_USE_CHROMIUM_LINKER = USE_CHROMIUM_LINKER_VALUE +#define MAYBE_USE_MODERN_LINKER = USE_MODERN_LINKER_VALUE +#define MAYBE_IS_BUNDLE = IS_BUNDLE_VALUE +#else +#define MAYBE_FINAL +#define MAYBE_USE_CHROMIUM_LINKER +#define MAYBE_USE_MODERN_LINKER +#define MAYBE_IS_BUNDLE +#endif + +/** + * Product configuration. Generated on a per-target basis. + */ +public class ProductConfig { + // Sorted list of locales that have an uncompressed .pak within assets. + // Stored as an array because AssetManager.list() is slow. +#if defined(LOCALE_LIST) + public static final String[] LOCALES = LOCALE_LIST; +#else + public static final String[] LOCALES = {}; +#endif + + public static MAYBE_FINAL boolean USE_CHROMIUM_LINKER MAYBE_USE_CHROMIUM_LINKER; + public static MAYBE_FINAL boolean USE_MODERN_LINKER MAYBE_USE_MODERN_LINKER; + public static MAYBE_FINAL boolean IS_BUNDLE MAYBE_IS_BUNDLE; +} diff --git a/third_party/libwebrtc/build/android/java/test/DefaultLocaleLintTest.java b/third_party/libwebrtc/build/android/java/test/DefaultLocaleLintTest.java new file mode 100644 index 0000000000..21934299de --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/DefaultLocaleLintTest.java @@ -0,0 +1,17 @@ +// Copyright 2021 The Chromium 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 test; + +import android.app.Application; + +/** + * Class which fails 'DefaultLocale' lint check. + */ +public class LintTest extends Application { + public String testTriggerDefaultLocaleCheck(int any) { + // String format with an integer requires a Locale since it may be formatted differently. + return String.format("Test %d", any); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/IncrementalJavacTest.java b/third_party/libwebrtc/build/android/java/test/IncrementalJavacTest.java new file mode 100644 index 0000000000..c83178aa96 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/IncrementalJavacTest.java @@ -0,0 +1,32 @@ +// Copyright 2021 The Chromium 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 test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** + * Checks that build picked up changes to + * {@link NoSignatureChangeIncrementalJavacTestHelper#foo()}. + */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public final class IncrementalJavacTest { + @Test + public void testNoSignatureChange() { + NoSignatureChangeIncrementalJavacTestHelper helper = + new NoSignatureChangeIncrementalJavacTestHelper(); + // #foo() should return updated value. + assertEquals("foo2", helper.foo()); + + // #bar() should not crash. + assertEquals("bar", helper.bar()); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/NewApiLintTest.java b/third_party/libwebrtc/build/android/java/test/NewApiLintTest.java new file mode 100644 index 0000000000..6c68dd8b9a --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/NewApiLintTest.java @@ -0,0 +1,17 @@ +// Copyright 2021 The Chromium 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 test; + +import android.app.Application; + +/** + * Class which fails 'NewAPI' lint check. + */ +public class NewApiTest extends Application { + public String testTriggerNewApiCheck() { + // This was added in API level 30. + return getApplicationContext().getAttributionTag(); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template b/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template new file mode 100644 index 0000000000..bb0f822285 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper.template @@ -0,0 +1,18 @@ +// Copyright 2021 The Chromium 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 test; + +public class NoSignatureChangeIncrementalJavacTestHelper { + private NoSignatureChangeIncrementalJavacTestHelper2 mHelper2 = + new NoSignatureChangeIncrementalJavacTestHelper2(); + + public String foo() { + return "{{foo_return_value}}"; + } + + public String bar() { + return mHelper2.bar(); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper2.java b/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper2.java new file mode 100644 index 0000000000..12d41ea4bb --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/NoSignatureChangeIncrementalJavacTestHelper2.java @@ -0,0 +1,11 @@ +// Copyright 2021 The Chromium 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 test; + +public class NoSignatureChangeIncrementalJavacTestHelper2 { + public String bar() { + return "bar"; + } +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/B.java b/third_party/libwebrtc/build/android/java/test/missing_symbol/B.java new file mode 100644 index 0000000000..b53bc239c8 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/B.java @@ -0,0 +1,9 @@ +// Copyright 2021 The Chromium 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 test.missing_symbol; + +public class B { + public void foo() {} +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/D.template b/third_party/libwebrtc/build/android/java/test/missing_symbol/D.template new file mode 100644 index 0000000000..2ea207134f --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/D.template @@ -0,0 +1,9 @@ +// Copyright 2021 The Chromium 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 test.missing_symbol; + +public class D { + public void foo() {} +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/Importer.template b/third_party/libwebrtc/build/android/java/test/missing_symbol/Importer.template new file mode 100644 index 0000000000..7c4bb895c3 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/Importer.template @@ -0,0 +1,13 @@ +// Copyright 2021 The Chromium 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 _IMPORTER_PACKAGE; + +import _IMPORTEE_PACKAGE._IMPORTEE_CLASS_NAME; + +public class Importer { + public Importer() { + new _IMPORTEE_CLASS_NAME().foo(); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/ImportsSubB.java b/third_party/libwebrtc/build/android/java/test/missing_symbol/ImportsSubB.java new file mode 100644 index 0000000000..71124143ec --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/ImportsSubB.java @@ -0,0 +1,13 @@ +// Copyright 2021 The Chromium 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 test.missing_symbol; + +import test.missing_symbol.sub.SubB; + +public class ImportsSubB { + public ImportsSubB() { + new SubB().foo(); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/c.jar b/third_party/libwebrtc/build/android/java/test/missing_symbol/c.jar new file mode 100644 index 0000000000..5f30be80a7 Binary files /dev/null and b/third_party/libwebrtc/build/android/java/test/missing_symbol/c.jar differ diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/BInMethodSignature.java b/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/BInMethodSignature.java new file mode 100644 index 0000000000..3198fb8f99 --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/BInMethodSignature.java @@ -0,0 +1,13 @@ +// Copyright 2021 The Chromium 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 test.missing_symbol.sub; + +import test.missing_symbol.B; + +public class BInMethodSignature { + public B foo() { + return new B(); + } +} diff --git a/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/SubB.java b/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/SubB.java new file mode 100644 index 0000000000..dd217ed54d --- /dev/null +++ b/third_party/libwebrtc/build/android/java/test/missing_symbol/sub/SubB.java @@ -0,0 +1,9 @@ +// Copyright 2021 The Chromium 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 test.missing_symbol.sub; + +public class SubB { + public void foo() {} +} diff --git a/third_party/libwebrtc/build/android/lighttpd_server.py b/third_party/libwebrtc/build/android/lighttpd_server.py new file mode 100755 index 0000000000..561174de74 --- /dev/null +++ b/third_party/libwebrtc/build/android/lighttpd_server.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# +# 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 +""" + +from __future__ import print_function + +import codecs +import contextlib +import os +import random +import shutil +import socket +import subprocess +import sys +import tempfile +import time + +from six.moves import http_client +from six.moves import input # pylint: disable=redefined-builtin + +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 + omitted 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 temporary 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) + # pylint: disable=no-member + 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 'in use' not 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 range(1, 5): + client_error = None + try: + with contextlib.closing( + http_client.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 (http_client.HTTPException, socket.error) as client_error: + pass # Probably too quick connecting: try again + # Check for server startup error messages + # pylint: disable=no-member + 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) # pylint: disable=no-member + 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(): + 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/third_party/libwebrtc/build/android/list_class_verification_failures.py b/third_party/libwebrtc/build/android/list_class_verification_failures.py new file mode 100755 index 0000000000..3c38e11394 --- /dev/null +++ b/third_party/libwebrtc/build/android/list_class_verification_failures.py @@ -0,0 +1,282 @@ +#!/usr/bin/env vpython3 +# Copyright 2018 The Chromium 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 script to list class verification errors. + +This is a wrapper around the device's oatdump executable, parsing desired output +and accommodating API-level-specific details, such as file paths. +""" + + + +import argparse +import logging +import os +import re + +import devil_chromium +from devil.android import device_errors +from devil.android import device_temp_file +from devil.android import device_utils +from devil.android.ndk import abis +from devil.android.sdk import version_codes +from devil.android.tools import script_common +from devil.utils import logging_common +from py_utils import tempfile_ext + +STATUSES = [ + 'NotReady', + 'RetryVerificationAtRuntime', + 'Verified', + 'Initialized', + 'SuperclassValidated', +] + + +def DetermineDeviceToUse(devices): + """Like DeviceUtils.HealthyDevices(), but only allow a single device. + + Args: + devices: A (possibly empty) list of serial numbers, such as from the + --device flag. + Returns: + A single device_utils.DeviceUtils instance. + Raises: + device_errors.NoDevicesError: Raised when no non-denylisted devices exist. + device_errors.MultipleDevicesError: Raise when multiple devices exist, but + |devices| does not distinguish which to use. + """ + if not devices: + # If the user did not specify which device, we let HealthyDevices raise + # MultipleDevicesError. + devices = None + usable_devices = device_utils.DeviceUtils.HealthyDevices(device_arg=devices) + # If the user specified more than one device, we still only want to support a + # single device, so we explicitly raise MultipleDevicesError. + if len(usable_devices) > 1: + raise device_errors.MultipleDevicesError(usable_devices) + return usable_devices[0] + + +class DeviceOSError(Exception): + """Raised when a file is missing from the device, or something similar.""" + pass + + +class UnsupportedDeviceError(Exception): + """Raised when the device is not supported by this script.""" + pass + + +def _GetFormattedArch(device): + abi = device.product_cpu_abi + # Some architectures don't map 1:1 with the folder names. + return {abis.ARM_64: 'arm64', abis.ARM: 'arm'}.get(abi, abi) + + +def PathToDexForPlatformVersion(device, package_name): + """Gets the full path to the dex file on the device.""" + sdk_level = device.build_version_sdk + paths_to_apk = device.GetApplicationPaths(package_name) + if not paths_to_apk: + raise DeviceOSError( + 'Could not find data directory for {}. Is it installed?'.format( + package_name)) + if len(paths_to_apk) != 1: + raise DeviceOSError( + 'Expected exactly one path for {} but found {}'.format( + package_name, + paths_to_apk)) + path_to_apk = paths_to_apk[0] + + if version_codes.LOLLIPOP <= sdk_level <= version_codes.LOLLIPOP_MR1: + # Of the form "com.example.foo-\d", where \d is some digit (usually 1 or 2) + package_with_suffix = os.path.basename(os.path.dirname(path_to_apk)) + arch = _GetFormattedArch(device) + dalvik_prefix = '/data/dalvik-cache/{arch}'.format(arch=arch) + odex_file = '{prefix}/data@app@{package}@base.apk@classes.dex'.format( + prefix=dalvik_prefix, + package=package_with_suffix) + elif sdk_level >= version_codes.MARSHMALLOW: + arch = _GetFormattedArch(device) + odex_file = '{data_dir}/oat/{arch}/base.odex'.format( + data_dir=os.path.dirname(path_to_apk), arch=arch) + else: + raise UnsupportedDeviceError('Unsupported API level: {}'.format(sdk_level)) + + odex_file_exists = device.FileExists(odex_file) + if odex_file_exists: + return odex_file + elif sdk_level >= version_codes.PIE: + raise DeviceOSError( + 'Unable to find odex file: you must run dex2oat on debuggable apps ' + 'on >= P after installation.') + raise DeviceOSError('Unable to find odex file ' + odex_file) + + +def _AdbOatDumpForPackage(device, package_name, out_file): + """Runs oatdump on the device.""" + # Get the path to the odex file. + odex_file = PathToDexForPlatformVersion(device, package_name) + device.RunShellCommand( + ['oatdump', '--oat-file=' + odex_file, '--output=' + out_file], + timeout=420, + shell=True, + check_return=True) + + +class JavaClass(object): + """This represents a Java Class and its ART Class Verification status.""" + + def __init__(self, name, verification_status): + self.name = name + self.verification_status = verification_status + + +def _ParseMappingFile(proguard_map_file): + """Creates a map of obfuscated names to deobfuscated names.""" + mappings = {} + with open(proguard_map_file, 'r') as f: + pattern = re.compile(r'^(\S+) -> (\S+):') + for line in f: + m = pattern.match(line) + if m is not None: + deobfuscated_name = m.group(1) + obfuscated_name = m.group(2) + mappings[obfuscated_name] = deobfuscated_name + return mappings + + +def _DeobfuscateJavaClassName(dex_code_name, proguard_mappings): + return proguard_mappings.get(dex_code_name, dex_code_name) + + +def FormatJavaClassName(dex_code_name, proguard_mappings): + obfuscated_name = dex_code_name.replace('/', '.') + if proguard_mappings is not None: + return _DeobfuscateJavaClassName(obfuscated_name, proguard_mappings) + else: + return obfuscated_name + + +def ListClassesAndVerificationStatus(oatdump_output, proguard_mappings): + """Lists all Java classes in the dex along with verification status.""" + java_classes = [] + pattern = re.compile(r'\d+: L([^;]+).*\(type_idx=[^(]+\((\w+)\).*') + for line in oatdump_output: + m = pattern.match(line) + if m is not None: + name = FormatJavaClassName(m.group(1), proguard_mappings) + # Some platform levels prefix this with "Status" while other levels do + # not. Strip this for consistency. + verification_status = m.group(2).replace('Status', '') + java_classes.append(JavaClass(name, verification_status)) + return java_classes + + +def _PrintVerificationResults(target_status, java_classes, show_summary): + """Prints results for user output.""" + # Sort to keep output consistent between runs. + java_classes.sort(key=lambda c: c.name) + d = {} + for status in STATUSES: + d[status] = 0 + + for java_class in java_classes: + if java_class.verification_status == target_status: + print(java_class.name) + if java_class.verification_status not in d: + raise RuntimeError('Unexpected status: {0}'.format( + java_class.verification_status)) + else: + d[java_class.verification_status] += 1 + + if show_summary: + for status in d: + count = d[status] + print('Total {status} classes: {num}'.format( + status=status, num=count)) + print('Total number of classes: {num}'.format( + num=len(java_classes))) + + +def RealMain(mapping, device_arg, package, status, hide_summary, workdir): + if mapping is None: + logging.warn('Skipping deobfuscation because no map file was provided.') + device = DetermineDeviceToUse(device_arg) + device.EnableRoot() + with device_temp_file.DeviceTempFile( + device.adb) as file_on_device: + _AdbOatDumpForPackage(device, package, file_on_device.name) + file_on_host = os.path.join(workdir, 'out.dump') + device.PullFile(file_on_device.name, file_on_host, timeout=220) + proguard_mappings = (_ParseMappingFile(mapping) if mapping else None) + with open(file_on_host, 'r') as f: + java_classes = ListClassesAndVerificationStatus(f, proguard_mappings) + _PrintVerificationResults(status, java_classes, not hide_summary) + + +def main(): + parser = argparse.ArgumentParser(description=""" +List Java classes in an APK which fail ART class verification. +""") + parser.add_argument( + '--package', + '-P', + type=str, + default=None, + required=True, + help='Specify the full application package name') + parser.add_argument( + '--mapping', + '-m', + type=os.path.realpath, + default=None, + help='Mapping file for the desired APK to deobfuscate class names') + parser.add_argument( + '--hide-summary', + default=False, + action='store_true', + help='Do not output the total number of classes in each Status.') + parser.add_argument( + '--status', + type=str, + default='RetryVerificationAtRuntime', + choices=STATUSES, + help='Which category of classes to list at the end of the script') + parser.add_argument( + '--workdir', + '-w', + type=os.path.realpath, + default=None, + help=('Work directory for oatdump output (default = temporary ' + 'directory). If specified, this will not be cleaned up at the end ' + 'of the script (useful if you want to inspect oatdump output ' + 'manually)')) + + script_common.AddEnvironmentArguments(parser) + script_common.AddDeviceArguments(parser) + logging_common.AddLoggingArguments(parser) + + args = parser.parse_args() + devil_chromium.Initialize(adb_path=args.adb_path) + logging_common.InitializeLogging(args) + + if args.workdir: + if not os.path.isdir(args.workdir): + raise RuntimeError('Specified working directory does not exist') + RealMain(args.mapping, args.devices, args.package, args.status, + args.hide_summary, args.workdir) + # Assume the user wants the workdir to persist (useful for debugging). + logging.warn('Not cleaning up explicitly-specified workdir: %s', + args.workdir) + else: + with tempfile_ext.NamedTemporaryDirectory() as workdir: + RealMain(args.mapping, args.devices, args.package, args.status, + args.hide_summary, workdir) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/list_class_verification_failures_test.py b/third_party/libwebrtc/build/android/list_class_verification_failures_test.py new file mode 100755 index 0000000000..c3522d651d --- /dev/null +++ b/third_party/libwebrtc/build/android/list_class_verification_failures_test.py @@ -0,0 +1,237 @@ +#!/usr/bin/env vpython3 +# Copyright 2018 The Chromium 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 + +import list_class_verification_failures as list_verification + +import devil_chromium # pylint: disable=unused-import +from devil.android import device_errors +from devil.android import device_utils +from devil.android.ndk import abis +from devil.android.sdk import version_codes + +import mock # pylint: disable=import-error + + +def _CreateOdexLine(java_class_name, type_idx, verification_status): + """Create a rough approximation of a line of oatdump output.""" + return ('{type_idx}: L{java_class}; (offset=0xac) (type_idx={type_idx}) ' + '({verification}) ' + '(OatClassNoneCompiled)'.format(type_idx=type_idx, + java_class=java_class_name, + verification=verification_status)) + + +def _ClassForName(name, classes): + return next(c for c in classes if c.name == name) + + +class _DetermineDeviceToUseTest(unittest.TestCase): + + def testDetermineDeviceToUse_emptyListWithOneAttachedDevice(self): + fake_attached_devices = ['123'] + user_specified_devices = [] + device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( + return_value=fake_attached_devices) + result = list_verification.DetermineDeviceToUse(user_specified_devices) + self.assertEqual(result, fake_attached_devices[0]) + # pylint: disable=no-member + device_utils.DeviceUtils.HealthyDevices.assert_called_with(device_arg=None) + # pylint: enable=no-member + + def testDetermineDeviceToUse_emptyListWithNoAttachedDevices(self): + user_specified_devices = [] + device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( + side_effect=device_errors.NoDevicesError()) + with self.assertRaises(device_errors.NoDevicesError) as _: + list_verification.DetermineDeviceToUse(user_specified_devices) + # pylint: disable=no-member + device_utils.DeviceUtils.HealthyDevices.assert_called_with(device_arg=None) + # pylint: enable=no-member + + def testDetermineDeviceToUse_oneElementListWithOneAttachedDevice(self): + user_specified_devices = ['123'] + fake_attached_devices = ['123'] + device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( + return_value=fake_attached_devices) + result = list_verification.DetermineDeviceToUse(user_specified_devices) + self.assertEqual(result, fake_attached_devices[0]) + # pylint: disable=no-member + device_utils.DeviceUtils.HealthyDevices.assert_called_with( + device_arg=user_specified_devices) + # pylint: enable=no-member + + +class _ListClassVerificationFailuresTest(unittest.TestCase): + + def testPathToDexForPlatformVersion_noPaths(self): + sdk_int = version_codes.LOLLIPOP + paths_to_apk = [] + package_name = 'package.name' + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + + with self.assertRaises(list_verification.DeviceOSError) as cm: + list_verification.PathToDexForPlatformVersion(device, package_name) + message = str(cm.exception) + self.assertIn('Could not find data directory', message) + + def testPathToDexForPlatformVersion_multiplePaths(self): + sdk_int = version_codes.LOLLIPOP + paths_to_apk = ['/first/path', '/second/path'] + package_name = 'package.name' + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + + with self.assertRaises(list_verification.DeviceOSError) as cm: + list_verification.PathToDexForPlatformVersion(device, package_name) + message = str(cm.exception) + self.assertIn('Expected exactly one path for', message) + + def testPathToDexForPlatformVersion_dalvikApiLevel(self): + sdk_int = version_codes.KITKAT + paths_to_apk = ['/some/path'] + package_name = 'package.name' + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + + with self.assertRaises(list_verification.UnsupportedDeviceError) as _: + list_verification.PathToDexForPlatformVersion(device, package_name) + + def testPathToDexForPlatformVersion_lollipopArm(self): + sdk_int = version_codes.LOLLIPOP + package_name = 'package.name' + paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] + arch = 'arm' + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + device.FileExists = mock.MagicMock(return_value=True) + + odex_file = list_verification.PathToDexForPlatformVersion(device, + package_name) + self.assertEqual(odex_file, + ('/data/dalvik-cache/arm/data@app' + '@package.name-1@base.apk@classes.dex')) + + def testPathToDexForPlatformVersion_mashmallowArm(self): + sdk_int = version_codes.MARSHMALLOW + package_name = 'package.name' + paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] + arch = 'arm' + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + device.FileExists = mock.MagicMock(return_value=True) + + odex_file = list_verification.PathToDexForPlatformVersion(device, + package_name) + self.assertEqual(odex_file, + '/some/path/package.name-1/oat/arm/base.odex') + + def testPathToDexForPlatformVersion_mashmallowArm64(self): + sdk_int = version_codes.MARSHMALLOW + package_name = 'package.name' + paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + device.FileExists = mock.MagicMock(return_value=True) + + odex_file = list_verification.PathToDexForPlatformVersion(device, + package_name) + self.assertEqual(odex_file, + '/some/path/package.name-1/oat/arm64/base.odex') + + def testPathToDexForPlatformVersion_pieNoOdexFile(self): + sdk_int = version_codes.PIE + package_name = 'package.name' + paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + device.FileExists = mock.MagicMock(return_value=False) + + with self.assertRaises(list_verification.DeviceOSError) as cm: + list_verification.PathToDexForPlatformVersion(device, package_name) + message = str(cm.exception) + self.assertIn('you must run dex2oat on debuggable apps on >= P', message) + + def testPathToDexForPlatformVersion_lowerApiLevelNoOdexFile(self): + sdk_int = version_codes.MARSHMALLOW + package_name = 'package.name' + paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] + arch = abis.ARM_64 + + device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) + device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) + device.FileExists = mock.MagicMock(return_value=False) + + with self.assertRaises(list_verification.DeviceOSError) as _: + list_verification.PathToDexForPlatformVersion(device, package_name) + + def testListClasses_noProguardMap(self): + oatdump_output = [ + _CreateOdexLine('a.b.JavaClass1', 6, 'StatusVerified'), + _CreateOdexLine('a.b.JavaClass2', 7, + 'StatusRetryVerificationAtRuntime'), + ] + + classes = list_verification.ListClassesAndVerificationStatus(oatdump_output, + None) + self.assertEqual(2, len(classes)) + java_class_1 = _ClassForName('a.b.JavaClass1', classes) + java_class_2 = _ClassForName('a.b.JavaClass2', classes) + self.assertEqual(java_class_1.verification_status, 'Verified') + self.assertEqual(java_class_2.verification_status, + 'RetryVerificationAtRuntime') + + def testListClasses_proguardMap(self): + oatdump_output = [ + _CreateOdexLine('a.b.ObfuscatedJavaClass1', 6, 'StatusVerified'), + _CreateOdexLine('a.b.ObfuscatedJavaClass2', 7, + 'StatusRetryVerificationAtRuntime'), + ] + + mapping = { + 'a.b.ObfuscatedJavaClass1': 'a.b.JavaClass1', + 'a.b.ObfuscatedJavaClass2': 'a.b.JavaClass2', + } + classes = list_verification.ListClassesAndVerificationStatus(oatdump_output, + mapping) + self.assertEqual(2, len(classes)) + java_class_1 = _ClassForName('a.b.JavaClass1', classes) + java_class_2 = _ClassForName('a.b.JavaClass2', classes) + self.assertEqual(java_class_1.verification_status, 'Verified') + self.assertEqual(java_class_2.verification_status, + 'RetryVerificationAtRuntime') + + def testListClasses_noStatusPrefix(self): + oatdump_output = [ + _CreateOdexLine('a.b.JavaClass1', 6, 'Verified'), + _CreateOdexLine('a.b.JavaClass2', 7, 'RetryVerificationAtRuntime'), + ] + + classes = list_verification.ListClassesAndVerificationStatus(oatdump_output, + None) + self.assertEqual(2, len(classes)) + java_class_1 = _ClassForName('a.b.JavaClass1', classes) + java_class_2 = _ClassForName('a.b.JavaClass2', classes) + self.assertEqual(java_class_1.verification_status, 'Verified') + self.assertEqual(java_class_2.verification_status, + 'RetryVerificationAtRuntime') + +if __name__ == '__main__': + # Suppress logging messages. + unittest.main(buffer=True) diff --git a/third_party/libwebrtc/build/android/list_java_targets.py b/third_party/libwebrtc/build/android/list_java_targets.py new file mode 100755 index 0000000000..7534f6a52e --- /dev/null +++ b/third_party/libwebrtc/build/android/list_java_targets.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Lint as: python3 +"""Prints out available java targets. + +Examples: +# List GN target for bundles: +build/android/list_java_targets.py -C out/Default --type android_app_bundle \ +--gn-labels + +# List all android targets with types: +build/android/list_java_targets.py -C out/Default --print-types + +# Build all apk targets: +build/android/list_java_targets.py -C out/Default --type android_apk | xargs \ +autoninja -C out/Default + +# Show how many of each target type exist: +build/android/list_java_targets.py -C out/Default --stats + +""" + +import argparse +import collections +import json +import logging +import os +import subprocess +import sys + +_SRC_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', + '..')) +sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android')) +from pylib import constants + +_AUTONINJA_PATH = os.path.join(_SRC_ROOT, 'third_party', 'depot_tools', + 'autoninja') +_NINJA_PATH = os.path.join(_SRC_ROOT, 'third_party', 'depot_tools', 'ninja') + +_VALID_TYPES = ( + 'android_apk', + 'android_app_bundle', + 'android_app_bundle_module', + 'android_assets', + 'android_resources', + 'dist_aar', + 'dist_jar', + 'group', + 'java_annotation_processor', + 'java_binary', + 'java_library', + 'junit_binary', + 'system_java_library', +) + + +def _run_ninja(output_dir, args): + cmd = [ + _AUTONINJA_PATH, + '-C', + output_dir, + ] + cmd.extend(args) + logging.info('Running: %r', cmd) + subprocess.run(cmd, check=True, stdout=sys.stderr) + + +def _query_for_build_config_targets(output_dir): + # Query ninja rather than GN since it's faster. + cmd = [_NINJA_PATH, '-C', output_dir, '-t', 'targets'] + logging.info('Running: %r', cmd) + ninja_output = subprocess.run(cmd, + check=True, + capture_output=True, + encoding='ascii').stdout + ret = [] + SUFFIX = '__build_config_crbug_908819' + SUFFIX_LEN = len(SUFFIX) + for line in ninja_output.splitlines(): + ninja_target = line.rsplit(':', 1)[0] + # Ignore root aliases by ensuring a : exists. + if ':' in ninja_target and ninja_target.endswith(SUFFIX): + ret.append(f'//{ninja_target[:-SUFFIX_LEN]}') + return ret + + +class _TargetEntry(object): + def __init__(self, gn_target): + assert gn_target.startswith('//'), f'{gn_target} does not start with //' + assert ':' in gn_target, f'Non-root {gn_target} required' + self.gn_target = gn_target + self._build_config = None + + @property + def ninja_target(self): + return self.gn_target[2:] + + @property + def ninja_build_config_target(self): + return self.ninja_target + '__build_config_crbug_908819' + + @property + def build_config_path(self): + """Returns the filepath of the project's .build_config.json.""" + ninja_target = self.ninja_target + # Support targets at the root level. e.g. //:foo + if ninja_target[0] == ':': + ninja_target = ninja_target[1:] + subpath = ninja_target.replace(':', os.path.sep) + '.build_config.json' + return os.path.join(constants.GetOutDirectory(), 'gen', subpath) + + def build_config(self): + """Reads and returns the project's .build_config.json JSON.""" + if not self._build_config: + with open(self.build_config_path) as jsonfile: + self._build_config = json.load(jsonfile) + return self._build_config + + def get_type(self): + """Returns the target type from its .build_config.json.""" + return self.build_config()['deps_info']['type'] + + def proguard_enabled(self): + """Returns whether proguard runs for this target.""" + # Modules set proguard_enabled, but the proguarding happens only once at the + # bundle level. + if self.get_type() == 'android_app_bundle_module': + return False + return self.build_config()['deps_info'].get('proguard_enabled', False) + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-C', + '--output-directory', + help='If outdir is not provided, will attempt to guess.') + parser.add_argument('--gn-labels', + action='store_true', + help='Print GN labels rather than ninja targets') + parser.add_argument( + '--nested', + action='store_true', + help='Do not convert nested targets to their top-level equivalents. ' + 'E.g. Without this, foo_test__apk -> foo_test') + parser.add_argument('--print-types', + action='store_true', + help='Print type of each target') + parser.add_argument( + '--print-build-config-paths', + action='store_true', + help='Print path to the .build_config.json of each target') + parser.add_argument('--build', + action='store_true', + help='Build all .build_config.json files.') + parser.add_argument('--type', + action='append', + help='Restrict to targets of given type', + choices=_VALID_TYPES) + parser.add_argument('--stats', + action='store_true', + help='Print counts of each target type.') + parser.add_argument('--proguard-enabled', + action='store_true', + help='Restrict to targets that have proguard enabled') + parser.add_argument('-v', '--verbose', default=0, action='count') + args = parser.parse_args() + + args.build |= bool(args.type or args.proguard_enabled or args.print_types + or args.stats) + + logging.basicConfig(level=logging.WARNING - (10 * args.verbose), + format='%(levelname).1s %(relativeCreated)6d %(message)s') + + if args.output_directory: + constants.SetOutputDirectory(args.output_directory) + constants.CheckOutputDirectory() + output_dir = constants.GetOutDirectory() + + # Query ninja for all __build_config_crbug_908819 targets. + targets = _query_for_build_config_targets(output_dir) + entries = [_TargetEntry(t) for t in targets] + + if args.build: + logging.warning('Building %d .build_config.json files...', len(entries)) + _run_ninja(output_dir, [e.ninja_build_config_target for e in entries]) + + if args.type: + entries = [e for e in entries if e.get_type() in args.type] + + if args.proguard_enabled: + entries = [e for e in entries if e.proguard_enabled()] + + if args.stats: + counts = collections.Counter(e.get_type() for e in entries) + for entry_type, count in sorted(counts.items()): + print(f'{entry_type}: {count}') + else: + for e in entries: + if args.gn_labels: + to_print = e.gn_target + else: + to_print = e.ninja_target + + # Convert to top-level target + if not args.nested: + to_print = to_print.replace('__test_apk', '').replace('__apk', '') + + if args.print_types: + to_print = f'{to_print}: {e.get_type()}' + elif args.print_build_config_paths: + to_print = f'{to_print}: {e.build_config_path}' + + print(to_print) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/main_dex_classes.flags b/third_party/libwebrtc/build/android/main_dex_classes.flags new file mode 100644 index 0000000000..31dbdd619e --- /dev/null +++ b/third_party/libwebrtc/build/android/main_dex_classes.flags @@ -0,0 +1,52 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Proguard flags for what should be kept in the main dex. Only used +# during main dex list determination, not during actual proguarding. + +-keep @org.chromium.base.annotations.MainDex class * { + *; +} + +-keepclasseswithmembers class * { + @org.chromium.base.annotations.MainDex ; +} + +# Assume all IDL-generated classes should be kept. They can't reference other +# non-framework classes, so fairly low-risk. +-keepclasseswithmembers class * { + public static ** asInterface(android.os.IBinder); +} + +# Required when code coverage is enabled. +-keep class com.vladium.** { + *; +} + +# Renderers / GPU process don't load secondary dex. +-keep public class * extends org.chromium.base.process_launcher.ChildProcessService { + *; +} + +# Used by tests for secondary dex extraction. +-keep class android.support.v4.content.ContextCompat { + *; +} + +# The following are based on $SDK_BUILD_TOOLS/mainDexClasses.rules +# Ours differ in that: +# 1. It omits -keeps for application / instrumentation / backupagents (these are +# redundant since they are added by aapt's main dex list rules output). +# 2. Omits keep for Application.attachBaseContext(), which is overly broad. +# 3. Omits keep for all annotations, which is also overly broad (and pulls in +# any class that has an @IntDef). + +######## START mainDexClasses.rules ######## + +# Keep old fashion tests in the main dex or they'll be silently ignored by InstrumentationTestRunner +-keep public class * extends android.test.InstrumentationTestCase { + (); +} + +######## END mainDexClasses.rules ######## diff --git a/third_party/libwebrtc/build/android/method_count.py b/third_party/libwebrtc/build/android/method_count.py new file mode 100755 index 0000000000..80d00735d4 --- /dev/null +++ b/third_party/libwebrtc/build/android/method_count.py @@ -0,0 +1,118 @@ +#! /usr/bin/env python3 +# 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. + +from __future__ import print_function + +import argparse +import os +import re +import zipfile + +from pylib.dex import dex_parser + + +class DexStatsCollector(object): + """Tracks count of method/field/string/type as well as unique methods.""" + + def __init__(self): + # Signatures of all methods from all seen dex files. + self._unique_methods = set() + # Map of label -> { metric -> count }. + self._counts_by_label = {} + + def _CollectFromDexfile(self, label, dexfile): + assert label not in self._counts_by_label, 'exists: ' + label + self._counts_by_label[label] = { + 'fields': dexfile.header.field_ids_size, + 'methods': dexfile.header.method_ids_size, + 'strings': dexfile.header.string_ids_size, + 'types': dexfile.header.type_ids_size, + } + self._unique_methods.update(dexfile.IterMethodSignatureParts()) + + def CollectFromZip(self, label, path): + """Add dex stats from an .apk/.jar/.aab/.zip.""" + with zipfile.ZipFile(path, 'r') as z: + for subpath in z.namelist(): + if not re.match(r'.*classes\d*\.dex$', subpath): + continue + dexfile = dex_parser.DexFile(bytearray(z.read(subpath))) + self._CollectFromDexfile('{}!{}'.format(label, subpath), dexfile) + + def CollectFromDex(self, label, path): + """Add dex stats from a .dex file.""" + with open(path, 'rb') as f: + dexfile = dex_parser.DexFile(bytearray(f.read())) + self._CollectFromDexfile(label, dexfile) + + def MergeFrom(self, parent_label, other): + """Add dex stats from another DexStatsCollector.""" + # pylint: disable=protected-access + for label, other_counts in other._counts_by_label.items(): + new_label = '{}-{}'.format(parent_label, label) + self._counts_by_label[new_label] = other_counts.copy() + self._unique_methods.update(other._unique_methods) + # pylint: enable=protected-access + + def GetUniqueMethodCount(self): + """Returns total number of unique methods across encountered dex files.""" + return len(self._unique_methods) + + def GetCountsByLabel(self): + """Returns dict of label -> {metric -> count}.""" + return self._counts_by_label + + def GetTotalCounts(self): + """Returns dict of {metric -> count}, where |count| is sum(metric).""" + ret = {} + for metric in ('fields', 'methods', 'strings', 'types'): + ret[metric] = sum(x[metric] for x in self._counts_by_label.values()) + return ret + + def GetDexCacheSize(self, pre_oreo): + """Returns number of bytes of dirty RAM is consumed from all dex files.""" + # Dex Cache was optimized in Android Oreo: + # https://source.android.com/devices/tech/dalvik/improvements#dex-cache-removal + if pre_oreo: + total = sum(self.GetTotalCounts().values()) + else: + total = sum(c['methods'] for c in self._counts_by_label.values()) + return total * 4 # 4 bytes per entry. + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('paths', nargs='+') + args = parser.parse_args() + + collector = DexStatsCollector() + for path in args.paths: + if os.path.splitext(path)[1] in ('.zip', '.apk', '.jar', '.aab'): + collector.CollectFromZip(path, path) + else: + collector.CollectFromDex(path, path) + + counts_by_label = collector.GetCountsByLabel() + for label, counts in sorted(counts_by_label.items()): + print('{}:'.format(label)) + for metric, count in sorted(counts.items()): + print(' {}:'.format(metric), count) + print() + + if len(counts_by_label) > 1: + print('Totals:') + for metric, count in sorted(collector.GetTotalCounts().items()): + print(' {}:'.format(metric), count) + print() + + print('Unique Methods:', collector.GetUniqueMethodCount()) + print('DexCache (Pre-Oreo):', collector.GetDexCacheSize(pre_oreo=True), + 'bytes of dirty memory') + print('DexCache (Oreo+):', collector.GetDexCacheSize(pre_oreo=False), + 'bytes of dirty memory') + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/native_flags/BUILD.gn b/third_party/libwebrtc/build/android/native_flags/BUILD.gn new file mode 100644 index 0000000000..9c5be70ffd --- /dev/null +++ b/third_party/libwebrtc/build/android/native_flags/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2021 The Chromium 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_toolchain == default_toolchain) { + import("//build/toolchain/toolchain.gni") + + # A toolchain that will capture compiler and linker arguments to a file. + toolchain("flagcapture") { + tool("cxx") { + cxx = rebase_path("argcapture.py", root_build_dir) + command = "$cxx {{output}} {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}" + outputs = [ "{{root_out_dir}}/{{label_name}}.txt" ] + } + tool("solink") { + solink = rebase_path("argcapture.py", root_build_dir) + command = "$solink {{output}} {{ldflags}}" + outputs = [ "{{root_out_dir}}/{{label_name}}.txt" ] + } + tool("alink") { + command = "this-should-never-run" + outputs = [ "this-will-never-exist" ] + } + tool("stamp") { + command = stamp_command + description = stamp_description + } + } +} else if (current_toolchain == "//build/android/native_flags:flagcapture") { + # This will record flags from all default configs of the default toolchain. + source_set("default_ccflags") { + sources = [ "empty.cc" ] + } + shared_library("default_ldflags") { + no_default_deps = true + } +} diff --git a/third_party/libwebrtc/build/android/native_flags/argcapture.py b/third_party/libwebrtc/build/android/native_flags/argcapture.py new file mode 100755 index 0000000000..b0e2acd92a --- /dev/null +++ b/third_party/libwebrtc/build/android/native_flags/argcapture.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 arguments to a file.""" + +import sys + + +def main(): + with open(sys.argv[1], 'w') as f: + f.write('\n'.join(sys.argv[2:])) + f.write('\n') + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/native_flags/empty.cc b/third_party/libwebrtc/build/android/native_flags/empty.cc new file mode 100644 index 0000000000..94aac140fb --- /dev/null +++ b/third_party/libwebrtc/build/android/native_flags/empty.cc @@ -0,0 +1,5 @@ +// Copyright 2021 The Chromium 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 just needs to exist to appease GN. diff --git a/third_party/libwebrtc/build/android/provision_devices.py b/third_party/libwebrtc/build/android/provision_devices.py new file mode 100755 index 0000000000..37b9f77d9c --- /dev/null +++ b/third_party/libwebrtc/build/android/provision_devices.py @@ -0,0 +1,563 @@ +#!/usr/bin/env vpython3 +# +# 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 datetime +import json +import logging +import os +import posixpath +import re +import subprocess +import sys +import time + +# Import _strptime before threaded code. datetime.datetime.strptime is +# threadsafe except for the initial import of the _strptime module. +# See crbug.com/584730 and https://bugs.python.org/issue7980. +import _strptime # pylint: disable=unused-import + +import devil_chromium +from devil.android import battery_utils +from devil.android import device_denylist +from devil.android import device_errors +from devil.android import device_temp_file +from devil.android import device_utils +from devil.android.sdk import keyevent +from devil.android.sdk import version_codes +from devil.constants import exit_codes +from devil.utils import run_tests_helper +from devil.utils import timeout_retry +from pylib import constants +from pylib import device_settings +from pylib.constants import host_paths + +_SYSTEM_WEBVIEW_PATHS = ['/system/app/webview', '/system/app/WebViewGoogle'] +_CHROME_PACKAGE_REGEX = re.compile('.*chrom.*') +_TOMBSTONE_REGEX = re.compile('tombstone.*') + + +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(args): + denylist = (device_denylist.Denylist(args.denylist_file) + if args.denylist_file else None) + devices = [ + d for d in device_utils.DeviceUtils.HealthyDevices(denylist) + if not args.emulators or d.adb.is_emulator + ] + if args.device: + devices = [d for d in devices if d == args.device] + if not devices: + raise device_errors.DeviceUnreachableError(args.device) + parallel_devices = device_utils.DeviceUtils.parallel(devices) + if args.emulators: + parallel_devices.pMap(SetProperties, args) + else: + parallel_devices.pMap(ProvisionDevice, denylist, args) + if args.auto_reconnect: + _LaunchHostHeartbeat() + denylisted_devices = denylist.Read() if denylist else [] + if args.output_device_denylist: + with open(args.output_device_denylist, 'w') as f: + json.dump(denylisted_devices, f) + if all(d in denylisted_devices for d in devices): + raise device_errors.NoDevicesError + return 0 + + +def ProvisionDevice(device, denylist, options): + def should_run_phase(phase_name): + return not options.phases or phase_name in options.phases + + def run_phase(phase_func, reboot_timeout, 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 options.reboot_timeout: + reboot_timeout = options.reboot_timeout + elif device.build_version_sdk >= version_codes.LOLLIPOP: + reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP + else: + reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP + + if should_run_phase(_PHASES.WIPE): + if (options.chrome_specific_wipe or device.IsUserBuild() or + device.build_version_sdk >= version_codes.MARSHMALLOW): + run_phase(WipeChromeData, reboot_timeout) + else: + run_phase(WipeDevice, reboot_timeout) + + if should_run_phase(_PHASES.PROPERTIES): + run_phase(SetProperties, reboot_timeout) + + if should_run_phase(_PHASES.FINISH): + run_phase(FinishProvisioning, reboot_timeout, reboot=False) + + if options.chrome_specific_wipe: + package = "com.google.android.gms" + version_name = device.GetApplicationVersion(package) + logging.info("Version name for %s is %s", package, version_name) + + CheckExternalStorage(device) + + except device_errors.CommandTimeoutError: + logging.exception('Timed out waiting for device %s. Adding to denylist.', + str(device)) + if denylist: + denylist.Extend([str(device)], reason='provision_timeout') + + except (device_errors.CommandFailedError, + device_errors.DeviceUnreachableError): + logging.exception('Failed to provision device %s. Adding to denylist.', + str(device)) + if denylist: + denylist.Extend([str(device)], reason='provision_failure') + + +def CheckExternalStorage(device): + """Checks that storage is writable and if not makes it writable. + + Arguments: + device: The device to check. + """ + try: + with device_temp_file.DeviceTempFile( + device.adb, suffix='.sh', dir=device.GetExternalStoragePath()) as f: + device.WriteFile(f.name, 'test') + except device_errors.CommandFailedError: + logging.info('External storage not writable. Remounting / as RW') + device.RunShellCommand(['mount', '-o', 'remount,rw', '/'], + check_return=True, as_root=True) + device.EnableRoot() + with device_temp_file.DeviceTempFile( + device.adb, suffix='.sh', dir=device.GetExternalStoragePath()) as f: + device.WriteFile(f.name, 'test') + +def WipeChromeData(device, options): + """Wipes chrome specific data from device + + (1) uninstall any app whose name matches *chrom*, except + com.android.chrome, which is the chrome stable package. Doing so also + removes the corresponding dirs under /data/data/ and /data/app/ + (2) remove any dir under /data/app-lib/ whose name matches *chrom* + (3) remove any files under /data/tombstones/ whose name matches "tombstone*" + (4) remove /data/local.prop if there is any + (5) remove /data/local/chrome-command-line if there is any + (6) remove anything under /data/local/.config/ if the dir exists + (this is telemetry related) + (7) remove anything under /data/local/tmp/ + + Arguments: + device: the device to wipe + """ + if options.skip_wipe: + return + + try: + if device.IsUserBuild(): + _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX, + constants.PACKAGE_INFO['chrome_stable'].package) + device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(), + check_return=True) + device.RunShellCommand('rm -rf /data/local/tmp/*', check_return=True) + else: + device.EnableRoot() + _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX, + constants.PACKAGE_INFO['chrome_stable'].package) + _WipeUnderDirIfMatch(device, '/data/app-lib/', _CHROME_PACKAGE_REGEX) + _WipeUnderDirIfMatch(device, '/data/tombstones/', _TOMBSTONE_REGEX) + + _WipeFileOrDir(device, '/data/local.prop') + _WipeFileOrDir(device, '/data/local/chrome-command-line') + _WipeFileOrDir(device, '/data/local/.config/') + _WipeFileOrDir(device, '/data/local/tmp/') + device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(), + check_return=True) + except device_errors.CommandFailedError: + logging.exception('Possible failure while wiping the device. ' + 'Attempting to continue.') + + +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)) + + if not device.IsUserBuild(): + _ConfigureLocalProperties(device, options.enable_java_debug) + else: + logging.warning('Cannot configure properties in user builds.') + 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) + if device.build_version_sdk >= version_codes.MARSHMALLOW: + # Ensure that NFC is also switched off. + device.RunShellCommand(['svc', 'nfc', 'disable'], + as_root=True, check_return=True) + + if options.disable_system_chrome: + # The system chrome version on the device interferes with some tests. + device.RunShellCommand(['pm', 'disable', 'com.android.chrome'], + check_return=True) + + if options.remove_system_webview: + if any(device.PathExists(p) for p in _SYSTEM_WEBVIEW_PATHS): + logging.info('System WebView exists and needs to be removed') + if device.HasRoot(): + # Disabled Marshmallow's Verity security feature + if device.build_version_sdk >= version_codes.MARSHMALLOW: + device.adb.DisableVerity() + device.Reboot() + device.WaitUntilFullyBooted() + device.EnableRoot() + + # This is required, e.g., to replace the system webview on a device. + device.adb.Remount() + device.RunShellCommand(['stop'], check_return=True) + device.RunShellCommand(['rm', '-rf'] + _SYSTEM_WEBVIEW_PATHS, + check_return=True) + device.RunShellCommand(['start'], check_return=True) + else: + logging.warning('Cannot remove system webview from a non-rooted device') + else: + logging.info('System WebView already removed') + + # Some device types can momentarily disappear after setting properties. + device.adb.WaitForDevice() + + +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( + 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', 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): + # The lockscreen can't be disabled on user builds, so send a keyevent + # to unlock it. + if device.IsUserBuild(): + device.SendKeyEvent(keyevent.KEYCODE_MENU) + + if options.min_battery_level is not None: + battery = battery_utils.BatteryUtils(device) + try: + battery.ChargeDeviceToLevel(options.min_battery_level) + except device_errors.DeviceChargingError: + device.Reboot() + battery.ChargeDeviceToLevel(options.min_battery_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.') + + def _set_and_verify_date(): + if device.build_version_sdk >= version_codes.MARSHMALLOW: + date_format = '%m%d%H%M%Y.%S' + set_date_command = ['date', '-u'] + get_date_command = ['date', '-u'] + else: + date_format = '%Y%m%d.%H%M%S' + set_date_command = ['date', '-s'] + get_date_command = ['date'] + + # TODO(jbudorick): This is wrong on pre-M devices -- get/set are + # dealing in local time, but we're setting based on GMT. + strgmtime = time.strftime(date_format, time.gmtime()) + set_date_command.append(strgmtime) + device.RunShellCommand(set_date_command, as_root=True, check_return=True) + + get_date_command.append('+"%Y%m%d.%H%M%S"') + device_time = device.RunShellCommand( + get_date_command, as_root=True, single_line=True).replace('"', '') + device_time = datetime.datetime.strptime(device_time, "%Y%m%d.%H%M%S") + correct_time = datetime.datetime.strptime(strgmtime, date_format) + tdelta = (correct_time - device_time).seconds + if tdelta <= 1: + logging.info('Date/time successfully set on %s', device) + return True + else: + logging.error('Date mismatch. Device: %s Correct: %s', + device_time.isoformat(), correct_time.isoformat()) + return False + + # Sometimes the date is not set correctly on the devices. Retry on failure. + if device.IsUserBuild(): + # TODO(bpastene): Figure out how to set the date & time on user builds. + pass + else: + if not timeout_retry.WaitFor( + _set_and_verify_date, wait_period=1, max_tries=2): + raise device_errors.CommandFailedError( + 'Failed to set date & time.', device_serial=str(device)) + + props = device.RunShellCommand('getprop', check_return=True) + for prop in props: + logging.info(' %s', prop) + if options.auto_reconnect: + _PushAndLaunchAdbReboot(device, options.target) + + +def _UninstallIfMatch(device, pattern, app_to_keep): + installed_packages = device.RunShellCommand(['pm', 'list', 'packages']) + installed_system_packages = [ + pkg.split(':')[1] for pkg in device.RunShellCommand(['pm', 'list', + 'packages', '-s'])] + for package_output in installed_packages: + package = package_output.split(":")[1] + if pattern.match(package) and not package == app_to_keep: + if not device.IsUserBuild() or package not in installed_system_packages: + device.Uninstall(package) + + +def _WipeUnderDirIfMatch(device, path, pattern): + for filename in device.ListDirectory(path): + if pattern.match(filename): + _WipeFileOrDir(device, posixpath.join(path, filename)) + + +def _WipeFileOrDir(device, path): + if device.PathExists(path): + device.RunShellCommand(['rm', '-rf', path], check_return=True) + + +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(host_paths.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(host_paths.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('--adb-path', + help='Absolute path to the adb binary to use.') + parser.add_argument('--denylist-file', help='Device denylist JSON file.') + 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('--disable-system-chrome', action='store_true', + help='Disable the system chrome from devices.') + parser.add_argument('--remove-system-webview', action='store_true', + help='Remove the system webview from devices.') + 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-denylist', + help='Json file to output the device denylist.') + parser.add_argument('--chrome-specific-wipe', action='store_true', + help='only wipe chrome specific data during provisioning') + parser.add_argument('--emulators', action='store_true', + help='provision only emulators and ignore usb devices') + args = parser.parse_args() + constants.SetBuildType(args.target) + + run_tests_helper.SetLogLevel(args.verbose) + + devil_chromium.Initialize(adb_path=args.adb_path) + + try: + return ProvisionDevices(args) + except (device_errors.DeviceUnreachableError, device_errors.NoDevicesError): + logging.exception('Unable to provision local devices.') + return exit_codes.INFRA + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/pylib/__init__.py b/third_party/libwebrtc/build/android/pylib/__init__.py new file mode 100644 index 0000000000..86a3527e2e --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/__init__.py @@ -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. + + +import os +import sys + + +_SRC_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..')) + +_THIRD_PARTY_PATH = os.path.join(_SRC_PATH, 'third_party') + +_CATAPULT_PATH = os.path.join(_THIRD_PARTY_PATH, 'catapult') + +_DEVIL_PATH = os.path.join(_CATAPULT_PATH, 'devil') + +_PYTRACE_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_trace_event') + +_PY_UTILS_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_utils') + +_SIX_PATH = os.path.join(_THIRD_PARTY_PATH, 'six', 'src') + +_TRACE2HTML_PATH = os.path.join(_CATAPULT_PATH, 'tracing') + +_BUILD_UTIL_PATH = os.path.join(_SRC_PATH, 'build', 'util') + +if _DEVIL_PATH not in sys.path: + sys.path.append(_DEVIL_PATH) + +if _PYTRACE_PATH not in sys.path: + sys.path.append(_PYTRACE_PATH) + +if _PY_UTILS_PATH not in sys.path: + sys.path.append(_PY_UTILS_PATH) + +if _TRACE2HTML_PATH not in sys.path: + sys.path.append(_TRACE2HTML_PATH) + +if _SIX_PATH not in sys.path: + sys.path.append(_SIX_PATH) + +if _BUILD_UTIL_PATH not in sys.path: + sys.path.insert(0, _BUILD_UTIL_PATH) diff --git a/third_party/libwebrtc/build/android/pylib/android/__init__.py b/third_party/libwebrtc/build/android/pylib/android/__init__.py new file mode 100644 index 0000000000..a67c3501b2 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/android/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 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. diff --git a/third_party/libwebrtc/build/android/pylib/android/logcat_symbolizer.py b/third_party/libwebrtc/build/android/pylib/android/logcat_symbolizer.py new file mode 100644 index 0000000000..c9f5336184 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/android/logcat_symbolizer.py @@ -0,0 +1,99 @@ +# Copyright 2017 The Chromium 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 re + +from devil.android import logcat_monitor + +BACKTRACE_LINE_RE = re.compile(r'#\d+') +THREADTIME_RE = re.compile( + logcat_monitor.LogcatMonitor.THREADTIME_RE_FORMAT % ( + r' *\S* *', r' *\S* *', r' *\S* *', r' *\S* *', r'.*')) + +def SymbolizeLogcat(logcat, dest, symbolizer, abi): + """Symbolize stack trace in the logcat. + + Symbolize the logcat and write the symbolized logcat to a new file. + + Args: + logcat: Path to logcat file. + dest: Path to where to write the symbolized logcat. + symbolizer: The stack symbolizer to symbolize stack trace in logcat. + abi: The device's product_cpu_abi. Symbolizer needs it to symbolize. + + A sample logcat that needs to be symbolized, after stripping the prefix, + such as '08-07 18:39:37.692 28649 28649 E Ion : ', would be: + Build fingerprint: 'google/shamu/shamu:7.1.1/NMF20B/3370:userdebug/dev-keys' + Revision: '0' + ABI: 'arm' + pid: 28936, tid: 28936, name: chromium.chrome >>> org.chromium.chrome <<< + signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- + Abort message: '[FATAL:debug_urls.cc(151)] Check failed: false. + #00 0x63e16c41 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so+0x0006cc4 + #01 0x63f19be3 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so+0x0016fbe + #02 0x63f19737 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so+0x0016f73 + #03 0x63f18ddf /data/app/org.chromium.chrome-1/lib/arm/libchrome.so+0x0016edd + #04 0x63f18b79 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so+0x0016eb7 + #05 0xab53f319 /system/lib/libart.so+0x000a3319 + #06 + r0 00000000 r1 00007108 r2 00000006 r3 00000008 + r4 ae60258c r5 00000006 r6 ae602534 r7 0000010c + r8 bede5cd0 r9 00000030 sl 00000000 fp 9265a800 + ip 0000000b sp bede5c38 lr ac8e5537 pc ac8e7da0 cpsr 600f0010 + + backtrace: + #00 pc 00049da0 /system/lib/libc.so (tgkill+12) + #01 pc 00047533 /system/lib/libc.so (pthread_kill+34) + #02 pc 0001d635 /system/lib/libc.so (raise+10) + #03 pc 00019181 /system/lib/libc.so (__libc_android_abort+34) + #04 pc 00017048 /system/lib/libc.so (abort+4) + #05 pc 00948605 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + #06 pc 002c9f73 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + #07 pc 003ccbe1 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + #08 pc 003cc735 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + #09 pc 003cbddf /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + #10 pc 003cbb77 /data/app/org.chromium.chrome-1/lib/arm/libchrome.so + """ + + with open(logcat) as logcat_file: + with open(dest, 'w') as dest_file: + # The current stack script will only print out the symbolized stack, + # and completely ignore logs other than the crash log that is used for + # symbolization, if any exists. Thus the code here extracts the + # crash log inside the logcat and pass only the crash log to the script, + # because we don't want to lose other information in the logcat that, + # if passed to the stack script, will just be ignored by it. + # TODO(crbug.com/755225): Rewrite the logic here. + outside_of_crash_log = True + in_lower_half_crash = False + data_to_symbolize = [] + + for line in logcat_file: + if outside_of_crash_log: + # Check whether it is the start of crash log. + if 'Build fingerprint: ' in line: + outside_of_crash_log = False + # Only include necessary information for symbolization. + # The logic here that removes date, time, proc_id etc. + # should be in sync with _THREADTIME_RE_FORMAT in logcat_monitor. + data_to_symbolize.append( + re.search(THREADTIME_RE, line).group(7)) + else: + dest_file.write(line) + else: + # Once we have reached the end of the backtrace section, + # we will start symbolizing. + if in_lower_half_crash and not bool(BACKTRACE_LINE_RE.search(line)): + outside_of_crash_log = True + in_lower_half_crash = False + symbolized_lines = symbolizer.ExtractAndResolveNativeStackTraces( + data_to_symbolize, abi) + dest_file.write('\n'.join(symbolized_lines) + '\n' + line) + data_to_symbolize = [] + else: + if not in_lower_half_crash and 'backtrace:' in line: + in_lower_half_crash = True + data_to_symbolize.append( + re.search(THREADTIME_RE, line).group(7)) diff --git a/third_party/libwebrtc/build/android/pylib/base/__init__.py b/third_party/libwebrtc/build/android/pylib/base/__init__.py new file mode 100644 index 0000000000..96196cffb2 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/__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/third_party/libwebrtc/build/android/pylib/base/base_test_result.py b/third_party/libwebrtc/build/android/pylib/base/base_test_result.py new file mode 100644 index 0000000000..1741f132d5 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/base_test_result.py @@ -0,0 +1,276 @@ +# 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.""" + + +import functools +import threading + +from lib.results import result_types # pylint: disable=import-error + + +class ResultType(object): + """Class enumerating test types. + + Wraps the results defined in //build/util/lib/results/. + """ + PASS = result_types.PASS + SKIP = result_types.SKIP + FAIL = result_types.FAIL + CRASH = result_types.CRASH + TIMEOUT = result_types.TIMEOUT + UNKNOWN = result_types.UNKNOWN + NOTRUN = result_types.NOTRUN + + @staticmethod + def GetTypes(): + """Get a list of all test types.""" + return [ResultType.PASS, ResultType.SKIP, ResultType.FAIL, + ResultType.CRASH, ResultType.TIMEOUT, ResultType.UNKNOWN, + ResultType.NOTRUN] + + +@functools.total_ordering +class BaseTestResult(object): + """Base class for a single test result.""" + + def __init__(self, name, test_type, duration=0, log='', failure_reason=None): + """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 + self._failure_reason = failure_reason + self._links = {} + + def __str__(self): + return self._name + + def __repr__(self): + return self._name + + def __eq__(self, other): + return self.GetName() == other.GetName() + + def __lt__(self, other): + return self.GetName() == other.GetName() + + 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 + + def SetFailureReason(self, failure_reason): + """Set the reason the test failed. + + This should be the first failure the test encounters and exclude any stack + trace. + """ + self._failure_reason = failure_reason + + def GetFailureReason(self): + """Get the reason the test failed. + + Returns None if the test did not fail or if the reason the test failed is + unknown. + """ + return self._failure_reason + + def SetLink(self, name, link_url): + """Set link with test result data.""" + self._links[name] = link_url + + def GetLinks(self): + """Get dict containing links to test result data.""" + return self._links + + +class TestRunResults(object): + """Set of results for a test run.""" + + def __init__(self): + self._links = {} + self._results = set() + self._results_lock = threading.RLock() + + def SetLink(self, name, link_url): + """Add link with test run results data.""" + self._links[name] = link_url + + def GetLinks(self): + """Get dict containing links to test run result data.""" + return self._links + + def GetLogs(self): + """Get the string representation of all test logs.""" + with self._results_lock: + 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.""" + with self._results_lock: + 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.""" + with self._results_lock: + 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.GetGtestForm() + + def AddResult(self, result): + """Add |result| to the set. + + Args: + result: An instance of BaseTestResult. + """ + assert isinstance(result, BaseTestResult) + with self._results_lock: + self._results.discard(result) + self._results.add(result) + + def AddResults(self, results): + """Add |results| to the set. + + Args: + results: An iterable of BaseTestResult objects. + """ + with self._results_lock: + 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), ( + 'Expected TestRunResult object: %s' % type(results)) + with self._results_lock: + # pylint: disable=W0212 + self._results.update(results._results) + + def GetAll(self): + """Get the set of all test results.""" + with self._results_lock: + return self._results.copy() + + def _GetType(self, test_type): + """Get the set of test results with the given test type.""" + with self._results_lock: + 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/third_party/libwebrtc/build/android/pylib/base/base_test_result_unittest.py b/third_party/libwebrtc/build/android/pylib/base/base_test_result_unittest.py new file mode 100644 index 0000000000..294467e5fc --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/base_test_result_unittest.py @@ -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. + +"""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/third_party/libwebrtc/build/android/pylib/base/environment.py b/third_party/libwebrtc/build/android/pylib/base/environment.py new file mode 100644 index 0000000000..744c392c1b --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/environment.py @@ -0,0 +1,49 @@ +# 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, output_manager): + """Environment constructor. + + Args: + output_manager: Instance of |output_manager.OutputManager| used to + save test output. + """ + self._output_manager = output_manager + + # Some subclasses have different teardown behavior on receiving SIGTERM. + self._received_sigterm = False + + 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() + + @property + def output_manager(self): + return self._output_manager + + def ReceivedSigterm(self): + self._received_sigterm = True diff --git a/third_party/libwebrtc/build/android/pylib/base/environment_factory.py b/third_party/libwebrtc/build/android/pylib/base/environment_factory.py new file mode 100644 index 0000000000..491730ff8f --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/environment_factory.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. + + +from pylib import constants +from pylib.local.device import local_device_environment +from pylib.local.machine import local_machine_environment + +try: + # local_emulator_environment depends on //tools. + # If a client pulls in the //build subtree but not the //tools + # one, fail at emulator environment creation time. + from pylib.local.emulator import local_emulator_environment +except ImportError: + local_emulator_environment = None + + +def CreateEnvironment(args, output_manager, error_func): + + if args.environment == 'local': + if args.command not in constants.LOCAL_MACHINE_TESTS: + if args.avd_config: + if not local_emulator_environment: + error_func('emulator environment requested but not available.') + return local_emulator_environment.LocalEmulatorEnvironment( + args, output_manager, error_func) + return local_device_environment.LocalDeviceEnvironment( + args, output_manager, error_func) + else: + return local_machine_environment.LocalMachineEnvironment( + args, output_manager, error_func) + + error_func('Unable to create %s environment.' % args.environment) diff --git a/third_party/libwebrtc/build/android/pylib/base/mock_environment.py b/third_party/libwebrtc/build/android/pylib/base/mock_environment.py new file mode 100644 index 0000000000..02fa5afee3 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/mock_environment.py @@ -0,0 +1,11 @@ +# Copyright 2017 The Chromium 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 + +import mock # pylint: disable=import-error + + +MockEnvironment = mock.MagicMock(environment.Environment) diff --git a/third_party/libwebrtc/build/android/pylib/base/mock_test_instance.py b/third_party/libwebrtc/build/android/pylib/base/mock_test_instance.py new file mode 100644 index 0000000000..57b4e62adb --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/mock_test_instance.py @@ -0,0 +1,11 @@ +# Copyright 2017 The Chromium 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 test_instance + +import mock # pylint: disable=import-error + + +MockTestInstance = mock.MagicMock(test_instance.TestInstance) diff --git a/third_party/libwebrtc/build/android/pylib/base/output_manager.py b/third_party/libwebrtc/build/android/pylib/base/output_manager.py new file mode 100644 index 0000000000..83af7e268a --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/output_manager.py @@ -0,0 +1,159 @@ +# Copyright 2017 The Chromium 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 tempfile + +from devil.utils import reraiser_thread + + +class Datatype(object): + HTML = 'text/html' + JSON = 'application/json' + PNG = 'image/png' + TEXT = 'text/plain' + + +class OutputManager(object): + + def __init__(self): + """OutputManager Constructor. + + This class provides a simple interface to save test output. Subclasses + of this will allow users to save test results in the cloud or locally. + """ + self._allow_upload = False + self._thread_group = None + + @contextlib.contextmanager + def ArchivedTempfile( + self, out_filename, out_subdir, datatype=Datatype.TEXT): + """Archive file contents asynchonously and then deletes file. + + Args: + out_filename: Name for saved file. + out_subdir: Directory to save |out_filename| to. + datatype: Datatype of file. + + Returns: + An ArchivedFile file. This file will be uploaded async when the context + manager exits. AFTER the context manager exits, you can get the link to + where the file will be stored using the Link() API. You can use typical + file APIs to write and flish the ArchivedFile. You can also use file.name + to get the local filepath to where the underlying file exists. If you do + this, you are responsible of flushing the file before exiting the context + manager. + """ + if not self._allow_upload: + raise Exception('Must run |SetUp| before attempting to upload!') + + f = self._CreateArchivedFile(out_filename, out_subdir, datatype) + try: + yield f + finally: + f.PrepareArchive() + + def archive(): + try: + f.Archive() + finally: + f.Delete() + + thread = reraiser_thread.ReraiserThread(func=archive) + thread.start() + self._thread_group.Add(thread) + + def _CreateArchivedFile(self, out_filename, out_subdir, datatype): + """Returns an instance of ArchivedFile.""" + raise NotImplementedError + + def SetUp(self): + self._allow_upload = True + self._thread_group = reraiser_thread.ReraiserThreadGroup() + + def TearDown(self): + self._allow_upload = False + logging.info('Finishing archiving output.') + self._thread_group.JoinAll() + + def __enter__(self): + self.SetUp() + return self + + def __exit__(self, _exc_type, _exc_val, _exc_tb): + self.TearDown() + + +class ArchivedFile(object): + + def __init__(self, out_filename, out_subdir, datatype): + self._out_filename = out_filename + self._out_subdir = out_subdir + self._datatype = datatype + + self._f = tempfile.NamedTemporaryFile(delete=False) + self._ready_to_archive = False + + @property + def name(self): + return self._f.name + + def write(self, *args, **kwargs): + if self._ready_to_archive: + raise Exception('Cannot write to file after archiving has begun!') + self._f.write(*args, **kwargs) + + def flush(self, *args, **kwargs): + if self._ready_to_archive: + raise Exception('Cannot flush file after archiving has begun!') + self._f.flush(*args, **kwargs) + + def Link(self): + """Returns location of archived file.""" + if not self._ready_to_archive: + raise Exception('Cannot get link to archived file before archiving ' + 'has begun') + return self._Link() + + def _Link(self): + """Note for when overriding this function. + + This function will certainly be called before the file + has finished being archived. Therefore, this needs to be able to know the + exact location of the archived file before it is finished being archived. + """ + raise NotImplementedError + + def PrepareArchive(self): + """Meant to be called synchronously to prepare file for async archiving.""" + self.flush() + self._ready_to_archive = True + self._PrepareArchive() + + def _PrepareArchive(self): + """Note for when overriding this function. + + This function is needed for things such as computing the location of + content addressed files. This is called after the file is written but + before archiving has begun. + """ + pass + + def Archive(self): + """Archives file.""" + if not self._ready_to_archive: + raise Exception('File is not ready to archive. Be sure you are not ' + 'writing to the file and PrepareArchive has been called') + self._Archive() + + def _Archive(self): + raise NotImplementedError + + def Delete(self): + """Deletes the backing file.""" + self._f.close() + os.remove(self.name) diff --git a/third_party/libwebrtc/build/android/pylib/base/output_manager_factory.py b/third_party/libwebrtc/build/android/pylib/base/output_manager_factory.py new file mode 100644 index 0000000000..e5d0692881 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/output_manager_factory.py @@ -0,0 +1,18 @@ +# Copyright 2017 The Chromium 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.output import local_output_manager +from pylib.output import remote_output_manager +from pylib.utils import local_utils + + +def CreateOutputManager(args): + if args.local_output or not local_utils.IsOnSwarming(): + return local_output_manager.LocalOutputManager( + output_dir=constants.GetOutDirectory()) + else: + return remote_output_manager.RemoteOutputManager( + bucket=args.gs_results_bucket) diff --git a/third_party/libwebrtc/build/android/pylib/base/output_manager_test_case.py b/third_party/libwebrtc/build/android/pylib/base/output_manager_test_case.py new file mode 100644 index 0000000000..0d83f082f8 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/output_manager_test_case.py @@ -0,0 +1,15 @@ +# Copyright 2017 The Chromium 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.path +import unittest + + +class OutputManagerTestCase(unittest.TestCase): + + def assertUsableTempFile(self, archived_tempfile): + self.assertTrue(bool(archived_tempfile.name)) + self.assertTrue(os.path.exists(archived_tempfile.name)) + self.assertTrue(os.path.isfile(archived_tempfile.name)) diff --git a/third_party/libwebrtc/build/android/pylib/base/test_collection.py b/third_party/libwebrtc/build/android/pylib/base/test_collection.py new file mode 100644 index 0000000000..34f21fe873 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_collection.py @@ -0,0 +1,81 @@ +# 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/third_party/libwebrtc/build/android/pylib/base/test_exception.py b/third_party/libwebrtc/build/android/pylib/base/test_exception.py new file mode 100644 index 0000000000..c98d2cb73e --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_exception.py @@ -0,0 +1,8 @@ +# 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. + + +class TestException(Exception): + """Base class for exceptions thrown by the test runner.""" + pass diff --git a/third_party/libwebrtc/build/android/pylib/base/test_instance.py b/third_party/libwebrtc/build/android/pylib/base/test_instance.py new file mode 100644 index 0000000000..7b1099cffa --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_instance.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. + + +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 + + # pylint: disable=no-self-use + def GetPreferredAbis(self): + return None + + # pylint: enable=no-self-use + + 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/third_party/libwebrtc/build/android/pylib/base/test_instance_factory.py b/third_party/libwebrtc/build/android/pylib/base/test_instance_factory.py new file mode 100644 index 0000000000..d276428d71 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_instance_factory.py @@ -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. + + +from pylib.gtest import gtest_test_instance +from pylib.instrumentation import instrumentation_test_instance +from pylib.junit import junit_test_instance +from pylib.monkey import monkey_test_instance +from pylib.utils import device_dependencies + + +def CreateTestInstance(args, error_func): + + if args.command == 'gtest': + return gtest_test_instance.GtestTestInstance( + args, device_dependencies.GetDataDependencies, error_func) + elif args.command == 'instrumentation': + return instrumentation_test_instance.InstrumentationTestInstance( + args, device_dependencies.GetDataDependencies, error_func) + elif args.command == 'junit': + return junit_test_instance.JunitTestInstance(args, error_func) + elif args.command == 'monkey': + return monkey_test_instance.MonkeyTestInstance(args, error_func) + + error_func('Unable to create %s test instance.' % args.command) diff --git a/third_party/libwebrtc/build/android/pylib/base/test_run.py b/third_party/libwebrtc/build/android/pylib/base/test_run.py new file mode 100644 index 0000000000..fc72d3a547 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_run.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. + + +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 + + # Some subclasses have different teardown behavior on receiving SIGTERM. + self._received_sigterm = False + + def TestPackage(self): + raise NotImplementedError + + def SetUp(self): + raise NotImplementedError + + def RunTests(self, results): + """Runs Tests and populates |results|. + + Args: + results: An array that should be populated with + |base_test_result.TestRunResults| objects. + """ + 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() + + def ReceivedSigterm(self): + self._received_sigterm = True diff --git a/third_party/libwebrtc/build/android/pylib/base/test_run_factory.py b/third_party/libwebrtc/build/android/pylib/base/test_run_factory.py new file mode 100644 index 0000000000..f62ba77a2e --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_run_factory.py @@ -0,0 +1,36 @@ +# 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.instrumentation import instrumentation_test_instance +from pylib.junit import junit_test_instance +from pylib.monkey import monkey_test_instance +from pylib.local.device import local_device_environment +from pylib.local.device import local_device_gtest_run +from pylib.local.device import local_device_instrumentation_test_run +from pylib.local.device import local_device_monkey_test_run +from pylib.local.machine import local_machine_environment +from pylib.local.machine import local_machine_junit_test_run + + +def CreateTestRun(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(test_instance, monkey_test_instance.MonkeyTestInstance): + return (local_device_monkey_test_run + .LocalDeviceMonkeyTestRun(env, test_instance)) + + if isinstance(env, local_machine_environment.LocalMachineEnvironment): + if isinstance(test_instance, junit_test_instance.JunitTestInstance): + return (local_machine_junit_test_run + .LocalMachineJunitTestRun(env, test_instance)) + + error_func('Unable to create test run for %s tests in %s environment' + % (str(test_instance), str(env))) diff --git a/third_party/libwebrtc/build/android/pylib/base/test_server.py b/third_party/libwebrtc/build/android/pylib/base/test_server.py new file mode 100644 index 0000000000..763e1212c3 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/base/test_server.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. + +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/third_party/libwebrtc/build/android/pylib/constants/__init__.py b/third_party/libwebrtc/build/android/pylib/constants/__init__.py new file mode 100644 index 0000000000..e87b8fe67d --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/constants/__init__.py @@ -0,0 +1,288 @@ +# 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 glob +import logging +import os +import subprocess + +import devil.android.sdk.keyevent +from devil.android.constants import chrome +from devil.android.sdk import version_codes +from devil.constants import exit_codes + + +keyevent = devil.android.sdk.keyevent + + +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))) + +PACKAGE_INFO = dict(chrome.PACKAGE_INFO) +PACKAGE_INFO.update({ + 'legacy_browser': + chrome.PackageInfo('com.google.android.browser', + 'com.android.browser.BrowserActivity', None, None), + 'chromecast_shell': + chrome.PackageInfo('com.google.android.apps.mediashell', + 'com.google.android.apps.mediashell.MediaShellActivity', + 'castshell-command-line', None), + 'android_webview_shell': + chrome.PackageInfo('org.chromium.android_webview.shell', + 'org.chromium.android_webview.shell.AwShellActivity', + 'android-webview-command-line', None), + 'gtest': + chrome.PackageInfo('org.chromium.native_test', + 'org.chromium.native_test.NativeUnitTestActivity', + 'chrome-native-tests-command-line', None), + 'android_browsertests': + chrome.PackageInfo('org.chromium.android_browsertests_apk', + ('org.chromium.android_browsertests_apk' + + '.ChromeBrowserTestsActivity'), + 'chrome-native-tests-command-line', None), + 'components_browsertests': + chrome.PackageInfo('org.chromium.components_browsertests_apk', + ('org.chromium.components_browsertests_apk' + + '.ComponentsBrowserTestsActivity'), + 'chrome-native-tests-command-line', None), + 'content_browsertests': + chrome.PackageInfo( + 'org.chromium.content_browsertests_apk', + 'org.chromium.content_browsertests_apk.ContentBrowserTestsActivity', + 'chrome-native-tests-command-line', None), + 'chromedriver_webview_shell': + chrome.PackageInfo('org.chromium.chromedriver_webview_shell', + 'org.chromium.chromedriver_webview_shell.Main', None, + None), + 'android_webview_cts': + chrome.PackageInfo('com.android.webview', + 'com.android.cts.webkit.WebViewStartupCtsActivity', + 'webview-command-line', None), + 'android_google_webview_cts': + chrome.PackageInfo('com.google.android.webview', + 'com.android.cts.webkit.WebViewStartupCtsActivity', + 'webview-command-line', None), + 'android_system_webview_shell': + chrome.PackageInfo('org.chromium.webview_shell', + 'org.chromium.webview_shell.WebViewBrowserActivity', + 'webview-command-line', None), + 'android_webview_ui_test': + chrome.PackageInfo('org.chromium.webview_ui_test', + 'org.chromium.webview_ui_test.WebViewUiTestActivity', + 'webview-command-line', None), + 'weblayer_browsertests': + chrome.PackageInfo( + 'org.chromium.weblayer_browsertests_apk', + 'org.chromium.weblayer_browsertests_apk.WebLayerBrowserTestsActivity', + 'chrome-native-tests-command-line', 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 + + +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') + +ANDROID_SDK_BUILD_TOOLS_VERSION = '31.0.0' +ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'android_sdk', + 'public') +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_ndk') + +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' + +# TODO(jbudorick): Remove once unused. +DEVICE_LOCAL_PROPERTIES_PATH = '/data/local.prop' + +# Configure ubsan to print stack traces in the format understood by "stack" so +# that they will be symbolized, and disable signal handlers because they +# interfere with the breakpad and sandbox tests. +# This value is duplicated in +# base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java +UBSAN_OPTIONS = ( + 'print_stacktrace=1 stack_trace_format=\'#%n pc %o %m\' ' + 'handle_segv=0 handle_sigbus=0 handle_sigfpe=0') + +# TODO(jbudorick): Rework this into testing/buildbot/ +PYTHON_UNIT_TEST_SUITES = { + 'pylib_py_unittests': { + 'path': + os.path.join(DIR_SOURCE_ROOT, 'build', 'android'), + 'test_modules': [ + 'devil.android.device_utils_test', + 'devil.android.md5sum_test', + 'devil.utils.cmd_helper_test', + 'pylib.results.json_results_test', + 'pylib.utils.proguard_test', + ] + }, + 'gyp_py_unittests': { + 'path': + os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'), + 'test_modules': [ + 'java_cpp_enum_tests', + 'java_cpp_strings_tests', + 'java_google_api_keys_tests', + 'extract_unwind_tables_tests', + ] + }, +} + +LOCAL_MACHINE_TESTS = ['junit', 'python'] +VALID_ENVIRONMENTS = ['local'] +VALID_TEST_TYPES = ['gtest', 'instrumentation', 'junit', 'linker', 'monkey', + 'perf', 'python'] +VALID_DEVICE_TYPES = ['Android', 'iOS'] + + +def SetBuildType(build_type): + """Set the BUILDTYPE environment variable. + + NOTE: Using this function is deprecated, in favor of SetOutputDirectory(), + it is still maintained for a few scripts that typically call it + to implement their --release and --debug command-line options. + + When writing a new script, consider supporting an --output-dir or + --chromium-output-dir option instead, and calling SetOutputDirectory() + instead. + + NOTE: If CHROMIUM_OUTPUT_DIR if defined, or if SetOutputDirectory() was + called previously, this will be completely ignored. + """ + chromium_output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR') + if chromium_output_dir: + logging.warning( + 'SetBuildType("%s") ignored since CHROMIUM_OUTPUT_DIR is already ' + 'defined as (%s)', build_type, chromium_output_dir) + os.environ['BUILDTYPE'] = build_type + + +def SetOutputDirectory(output_directory): + """Set the Chromium output directory. + + This must be called early by scripts that rely on GetOutDirectory() or + CheckOutputDirectory(). Typically by providing an --output-dir or + --chromium-output-dir option. + """ + os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory + + +# The message that is printed when the Chromium output directory cannot +# be found. Note that CHROMIUM_OUT_DIR and BUILDTYPE are not mentioned +# intentionally to encourage the use of CHROMIUM_OUTPUT_DIR instead. +_MISSING_OUTPUT_DIR_MESSAGE = '\ +The Chromium output directory could not be found. Please use an option such as \ +--output-directory to provide it (see --help for details). Otherwise, \ +define the CHROMIUM_OUTPUT_DIR environment variable.' + + +def GetOutDirectory(): + """Returns the Chromium build output directory. + + NOTE: This is determined in the following way: + - From a previous call to SetOutputDirectory() + - Otherwise, from the CHROMIUM_OUTPUT_DIR env variable, if it is defined. + - Otherwise, from the current Chromium source directory, and a previous + call to SetBuildType() or the BUILDTYPE env variable, in combination + with the optional CHROMIUM_OUT_DIR env variable. + """ + if 'CHROMIUM_OUTPUT_DIR' in os.environ: + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR'))) + + build_type = os.environ.get('BUILDTYPE') + if not build_type: + raise EnvironmentError(_MISSING_OUTPUT_DIR_MESSAGE) + + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'), + build_type)) + + +def CheckOutputDirectory(): + """Checks that the Chromium output directory is set, or can be found. + + If it is not already set, this will also perform a little auto-detection: + + - If the current directory contains a build.ninja file, use it as + the output directory. + + - If CHROME_HEADLESS is defined in the environment (e.g. on a bot), + look if there is a single output directory under DIR_SOURCE_ROOT/out/, + and if so, use it as the output directory. + + Raises: + Exception: If no output directory is detected. + """ + output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR') + if output_dir: + return + + build_type = os.environ.get('BUILDTYPE') + if build_type and len(build_type) > 1: + return + + # If CWD is an output directory, then assume it's the desired one. + if os.path.exists('build.ninja'): + output_dir = os.getcwd() + SetOutputDirectory(output_dir) + return + + # When running on bots, see if the output directory is obvious. + # TODO(http://crbug.com/833808): Get rid of this by ensuring bots always set + # CHROMIUM_OUTPUT_DIR correctly. + if os.environ.get('CHROME_HEADLESS'): + dirs = glob.glob(os.path.join(DIR_SOURCE_ROOT, 'out', '*', 'build.ninja')) + if len(dirs) == 1: + SetOutputDirectory(dirs[0]) + return + + raise Exception( + 'Chromium output directory not set, and CHROME_HEADLESS detected. ' + + 'However, multiple out dirs exist: %r' % dirs) + + raise Exception(_MISSING_OUTPUT_DIR_MESSAGE) + + +# Exit codes +ERROR_EXIT_CODE = exit_codes.ERROR +INFRA_EXIT_CODE = exit_codes.INFRA +WARNING_EXIT_CODE = exit_codes.WARNING diff --git a/third_party/libwebrtc/build/android/pylib/constants/host_paths.py b/third_party/libwebrtc/build/android/pylib/constants/host_paths.py new file mode 100644 index 0000000000..aa5907eb15 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/constants/host_paths.py @@ -0,0 +1,97 @@ +# 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. + + +import contextlib +import os +import sys + +from pylib import constants + +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))) + +BUILD_COMMON_PATH = os.path.join( + DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common') + +# third-party libraries +ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'android_platform', 'development', + 'scripts') +BUILD_PATH = os.path.join(DIR_SOURCE_ROOT, 'build') +DEVIL_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'catapult', 'devil') +JAVA_PATH = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current', + 'bin') +TRACING_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'catapult', 'tracing') + +@contextlib.contextmanager +def SysPath(path, position=None): + if position is None: + sys.path.append(path) + else: + sys.path.insert(position, path) + try: + yield + finally: + if sys.path[-1] == path: + sys.path.pop() + else: + sys.path.remove(path) + + +# Map of CPU architecture name to (toolchain_name, binprefix) pairs. +# TODO(digit): Use the build_vars.json file generated by gn. +_TOOL_ARCH_MAP = { + 'arm': ('arm-linux-androideabi-4.9', 'arm-linux-androideabi'), + 'arm64': ('aarch64-linux-android-4.9', 'aarch64-linux-android'), + 'x86': ('x86-4.9', 'i686-linux-android'), + 'x86_64': ('x86_64-4.9', 'x86_64-linux-android'), + 'x64': ('x86_64-4.9', 'x86_64-linux-android'), + 'mips': ('mipsel-linux-android-4.9', 'mipsel-linux-android'), +} + +# Cache used to speed up the results of ToolPath() +# Maps (arch, tool_name) pairs to fully qualified program paths. +# Useful because ToolPath() is called repeatedly for demangling C++ symbols. +_cached_tool_paths = {} + + +def ToolPath(tool, cpu_arch): + """Return a fully qualifed path to an arch-specific toolchain program. + + Args: + tool: Unprefixed toolchain program name (e.g. 'objdump') + cpu_arch: Target CPU architecture (e.g. 'arm64') + Returns: + Fully qualified path (e.g. ..../aarch64-linux-android-objdump') + Raises: + Exception if the toolchain could not be found. + """ + tool_path = _cached_tool_paths.get((tool, cpu_arch)) + if tool_path: + return tool_path + + toolchain_source, toolchain_prefix = _TOOL_ARCH_MAP.get( + cpu_arch, (None, None)) + if not toolchain_source: + raise Exception('Could not find tool chain for ' + cpu_arch) + + toolchain_subdir = ( + 'toolchains/%s/prebuilt/linux-x86_64/bin' % toolchain_source) + + tool_path = os.path.join(constants.ANDROID_NDK_ROOT, + toolchain_subdir, + toolchain_prefix + '-' + tool) + + _cached_tool_paths[(tool, cpu_arch)] = tool_path + return tool_path + + +def GetAaptPath(): + """Returns the path to the 'aapt' executable.""" + return os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt') diff --git a/third_party/libwebrtc/build/android/pylib/constants/host_paths_unittest.py b/third_party/libwebrtc/build/android/pylib/constants/host_paths_unittest.py new file mode 100755 index 0000000000..f64f5c7552 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/constants/host_paths_unittest.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Chromium 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 unittest + +import six +import pylib.constants as constants +import pylib.constants.host_paths as host_paths + +# This map corresponds to the binprefix of NDK prebuilt toolchains for various +# target CPU architectures. Note that 'x86_64' and 'x64' are the same. +_EXPECTED_NDK_TOOL_SUBDIR_MAP = { + 'arm': 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/' + + 'arm-linux-androideabi-', + 'arm64': + 'toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/' + + 'aarch64-linux-android-', + 'x86': 'toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-', + 'x86_64': + 'toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-', + 'x64': + 'toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-', + 'mips': + 'toolchains/mipsel-linux-android-4.9/prebuilt/linux-x86_64/bin/' + + 'mipsel-linux-android-' +} + + +class HostPathsTest(unittest.TestCase): + def setUp(self): + logging.getLogger().setLevel(logging.ERROR) + + def test_GetAaptPath(self): + _EXPECTED_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt') + self.assertEqual(host_paths.GetAaptPath(), _EXPECTED_AAPT_PATH) + self.assertEqual(host_paths.GetAaptPath(), _EXPECTED_AAPT_PATH) + + def test_ToolPath(self): + for cpu_arch, binprefix in six.iteritems(_EXPECTED_NDK_TOOL_SUBDIR_MAP): + expected_binprefix = os.path.join(constants.ANDROID_NDK_ROOT, binprefix) + expected_path = expected_binprefix + 'foo' + self.assertEqual(host_paths.ToolPath('foo', cpu_arch), expected_path) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/content_settings.py b/third_party/libwebrtc/build/android/pylib/content_settings.py new file mode 100644 index 0000000000..5ea7c525ed --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/content_settings.py @@ -0,0 +1,80 @@ +# 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 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, int): + 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/third_party/libwebrtc/build/android/pylib/device/__init__.py b/third_party/libwebrtc/build/android/pylib/device/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/android/pylib/device/commands/BUILD.gn b/third_party/libwebrtc/build/android/pylib/device/commands/BUILD.gn new file mode 100644 index 0000000000..13b69f618c --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/device/commands/BUILD.gn @@ -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. + +import("//build/config/android/rules.gni") + +group("commands") { + data_deps = [ ":chromium_commands_java" ] +} + +android_library("unzip_java") { + jacoco_never_instrument = true + sources = [ "java/src/org/chromium/android/commands/unzip/Unzip.java" ] +} + +dist_dex("chromium_commands_java") { + deps = [ ":unzip_java" ] + output = "$root_build_dir/lib.java/chromium_commands.dex.jar" + data = [ output ] +} diff --git a/third_party/libwebrtc/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java b/third_party/libwebrtc/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java new file mode 100644 index 0000000000..cf0ff67af2 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/device/commands/java/src/org/chromium/android/commands/unzip/Unzip.java @@ -0,0 +1,93 @@ +// 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 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) { + e.printStackTrace(); + 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); + } 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/third_party/libwebrtc/build/android/pylib/device_settings.py b/third_party/libwebrtc/build/android/pylib/device_settings.py new file mode 100644 index 0000000000..c4d2bb3da3 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/device_settings.py @@ -0,0 +1,201 @@ +# 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 six + +from pylib import content_settings + +_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' +_COMPATIBLE_BUILD_TYPES = ['userdebug', 'eng'] + + +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. + """ + 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(six.iteritems(settings)): + 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 not in _COMPATIBLE_BUILD_TYPES: + logging.warning('Unable to disable lockscreen on %s builds.', + device.build_type) + 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), + + ('skip_first_use_hints', 1), + ]), + ('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/third_party/libwebrtc/build/android/pylib/dex/__init__.py b/third_party/libwebrtc/build/android/pylib/dex/__init__.py new file mode 100644 index 0000000000..4a12e35c92 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/dex/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2019 The Chromium 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/third_party/libwebrtc/build/android/pylib/dex/dex_parser.py b/third_party/libwebrtc/build/android/pylib/dex/dex_parser.py new file mode 100755 index 0000000000..1ff8d25276 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/dex/dex_parser.py @@ -0,0 +1,551 @@ +#!/usr/bin/env python3 +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Utilities for optimistically parsing dex files. + +This file is not meant to provide a generic tool for analyzing dex files. +A DexFile class that exposes access to several memory items in the dex format +is provided, but it does not include error handling or validation. +""" + + + +import argparse +import collections +import errno +import os +import re +import struct +import sys +import zipfile + +# https://source.android.com/devices/tech/dalvik/dex-format#header-item +_DEX_HEADER_FMT = ( + ('magic', '8s'), + ('checksum', 'I'), + ('signature', '20s'), + ('file_size', 'I'), + ('header_size', 'I'), + ('endian_tag', 'I'), + ('link_size', 'I'), + ('link_off', 'I'), + ('map_off', 'I'), + ('string_ids_size', 'I'), + ('string_ids_off', 'I'), + ('type_ids_size', 'I'), + ('type_ids_off', 'I'), + ('proto_ids_size', 'I'), + ('proto_ids_off', 'I'), + ('field_ids_size', 'I'), + ('field_ids_off', 'I'), + ('method_ids_size', 'I'), + ('method_ids_off', 'I'), + ('class_defs_size', 'I'), + ('class_defs_off', 'I'), + ('data_size', 'I'), + ('data_off', 'I'), +) + +DexHeader = collections.namedtuple('DexHeader', + ','.join(t[0] for t in _DEX_HEADER_FMT)) + +# Simple memory items. +_TypeIdItem = collections.namedtuple('TypeIdItem', 'descriptor_idx') +_ProtoIdItem = collections.namedtuple( + 'ProtoIdItem', 'shorty_idx,return_type_idx,parameters_off') +_MethodIdItem = collections.namedtuple('MethodIdItem', + 'type_idx,proto_idx,name_idx') +_TypeItem = collections.namedtuple('TypeItem', 'type_idx') +_StringDataItem = collections.namedtuple('StringItem', 'utf16_size,data') +_ClassDefItem = collections.namedtuple( + 'ClassDefItem', + 'class_idx,access_flags,superclass_idx,interfaces_off,source_file_idx,' + 'annotations_off,class_data_off,static_values_off') + + +class _MemoryItemList(object): + """Base class for repeated memory items.""" + + def __init__(self, + reader, + offset, + size, + factory, + alignment=None, + first_item_offset=None): + """Creates the item list using the specific item factory. + + Args: + reader: _DexReader used for decoding the memory item. + offset: Offset from start of the file to the item list, serving as the + key for some item types. + size: Number of memory items in the list. + factory: Function to extract each memory item from a _DexReader. + alignment: Optional integer specifying the alignment for the memory + section represented by this list. + first_item_offset: Optional, specifies a different offset to use for + extracting memory items (default is to use offset). + """ + self.offset = offset + self.size = size + reader.Seek(first_item_offset or offset) + self._items = [factory(reader) for _ in range(size)] + + if alignment: + reader.AlignUpTo(alignment) + + def __iter__(self): + return iter(self._items) + + def __getitem__(self, key): + return self._items[key] + + def __len__(self): + return len(self._items) + + def __repr__(self): + item_type_part = '' + if self.size != 0: + item_type = type(self._items[0]) + item_type_part = ', item type={}'.format(item_type.__name__) + + return '{}(offset={:#x}, size={}{})'.format( + type(self).__name__, self.offset, self.size, item_type_part) + + +class _TypeIdItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + factory = lambda x: _TypeIdItem(x.ReadUInt()) + super(_TypeIdItemList, self).__init__(reader, offset, size, factory) + + +class _ProtoIdItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + factory = lambda x: _ProtoIdItem(x.ReadUInt(), x.ReadUInt(), x.ReadUInt()) + super(_ProtoIdItemList, self).__init__(reader, offset, size, factory) + + +class _MethodIdItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + factory = ( + lambda x: _MethodIdItem(x.ReadUShort(), x.ReadUShort(), x.ReadUInt())) + super(_MethodIdItemList, self).__init__(reader, offset, size, factory) + + +class _StringItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + reader.Seek(offset) + string_item_offsets = iter([reader.ReadUInt() for _ in range(size)]) + + def factory(x): + data_offset = next(string_item_offsets) + string = x.ReadString(data_offset) + return _StringDataItem(len(string), string) + + super(_StringItemList, self).__init__(reader, offset, size, factory) + + +class _TypeListItem(_MemoryItemList): + + def __init__(self, reader): + offset = reader.Tell() + size = reader.ReadUInt() + factory = lambda x: _TypeItem(x.ReadUShort()) + # This is necessary because we need to extract the size of the type list + # (in other cases the list size is provided in the header). + first_item_offset = reader.Tell() + super(_TypeListItem, self).__init__( + reader, + offset, + size, + factory, + alignment=4, + first_item_offset=first_item_offset) + + +class _TypeListItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + super(_TypeListItemList, self).__init__(reader, offset, size, _TypeListItem) + + +class _ClassDefItemList(_MemoryItemList): + + def __init__(self, reader, offset, size): + reader.Seek(offset) + + def factory(x): + return _ClassDefItem(*(x.ReadUInt() + for _ in range(len(_ClassDefItem._fields)))) + + super(_ClassDefItemList, self).__init__(reader, offset, size, factory) + + +class _DexMapItem(object): + + def __init__(self, reader): + self.type = reader.ReadUShort() + reader.ReadUShort() + self.size = reader.ReadUInt() + self.offset = reader.ReadUInt() + + def __repr__(self): + return '_DexMapItem(type={}, size={}, offset={:#x})'.format( + self.type, self.size, self.offset) + + +class _DexMapList(object): + # Full list of type codes: + # https://source.android.com/devices/tech/dalvik/dex-format#type-codes + TYPE_TYPE_LIST = 0x1001 + + def __init__(self, reader, offset): + self._map = {} + reader.Seek(offset) + self._size = reader.ReadUInt() + for _ in range(self._size): + item = _DexMapItem(reader) + self._map[item.type] = item + + def __getitem__(self, key): + return self._map[key] + + def __contains__(self, key): + return key in self._map + + def __repr__(self): + return '_DexMapList(size={}, items={})'.format(self._size, self._map) + + +class _DexReader(object): + + def __init__(self, data): + self._data = data + self._pos = 0 + + def Seek(self, offset): + self._pos = offset + + def Tell(self): + return self._pos + + def ReadUByte(self): + return self._ReadData(' 1: + raise ValueError('Platform mode currently supports only 1 gtest suite') + self._coverage_dir = args.coverage_dir + self._exe_dist_dir = None + self._external_shard_index = args.test_launcher_shard_index + self._extract_test_list_from_filter = args.extract_test_list_from_filter + self._filter_tests_lock = threading.Lock() + self._gs_test_artifacts_bucket = args.gs_test_artifacts_bucket + self._isolated_script_test_output = args.isolated_script_test_output + self._isolated_script_test_perf_output = ( + args.isolated_script_test_perf_output) + self._render_test_output_dir = args.render_test_output_dir + self._shard_timeout = args.shard_timeout + self._store_tombstones = args.store_tombstones + self._suite = args.suite_name[0] + self._symbolizer = stack_symbolizer.Symbolizer(None) + self._total_external_shards = args.test_launcher_total_shards + self._wait_for_java_debugger = args.wait_for_java_debugger + self._use_existing_test_data = args.use_existing_test_data + + # GYP: + if args.executable_dist_dir: + self._exe_dist_dir = os.path.abspath(args.executable_dist_dir) + else: + # TODO(agrieve): Remove auto-detection once recipes pass flag explicitly. + exe_dist_dir = os.path.join(constants.GetOutDirectory(), + '%s__dist' % self._suite) + + if os.path.exists(exe_dist_dir): + self._exe_dist_dir = exe_dist_dir + + incremental_part = '' + if args.test_apk_incremental_install_json: + incremental_part = '_incremental' + + self._test_launcher_batch_limit = MAX_SHARDS + if (args.test_launcher_batch_limit + and 0 < args.test_launcher_batch_limit < MAX_SHARDS): + self._test_launcher_batch_limit = args.test_launcher_batch_limit + + apk_path = os.path.join( + constants.GetOutDirectory(), '%s_apk' % self._suite, + '%s-debug%s.apk' % (self._suite, incremental_part)) + self._test_apk_incremental_install_json = ( + args.test_apk_incremental_install_json) + if not os.path.exists(apk_path): + self._apk_helper = None + else: + self._apk_helper = apk_helper.ApkHelper(apk_path) + self._extras = { + _EXTRA_NATIVE_TEST_ACTIVITY: self._apk_helper.GetActivityName(), + } + if self._suite in RUN_IN_SUB_THREAD_TEST_SUITES: + self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1 + if self._suite in BROWSER_TEST_SUITES: + self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1 + self._extras[EXTRA_SHARD_NANO_TIMEOUT] = int(1e9 * self._shard_timeout) + self._shard_timeout = 10 * self._shard_timeout + if args.wait_for_java_debugger: + self._extras[EXTRA_SHARD_NANO_TIMEOUT] = int(1e15) # Forever + + if not self._apk_helper and not self._exe_dist_dir: + error_func('Could not find apk or executable for %s' % self._suite) + + self._data_deps = [] + self._gtest_filter = test_filter.InitializeFilterFromArgs(args) + self._run_disabled = args.run_disabled + + self._data_deps_delegate = data_deps_delegate + self._runtime_deps_path = args.runtime_deps_path + if not self._runtime_deps_path: + logging.warning('No data dependencies will be pushed.') + + 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 + + self._flags = None + self._initializeCommandLineFlags(args) + + # TODO(jbudorick): Remove this once it's deployed. + self._enable_xml_result_parsing = args.enable_xml_result_parsing + + def _initializeCommandLineFlags(self, args): + self._flags = [] + if args.command_line_flags: + self._flags.extend(args.command_line_flags) + if args.device_flags_file: + with open(args.device_flags_file) as f: + stripped_lines = (l.strip() for l in f) + self._flags.extend(flag for flag in stripped_lines if flag) + if args.run_disabled: + self._flags.append('--gtest_also_run_disabled_tests') + + @property + def activity(self): + return self._apk_helper and self._apk_helper.GetActivityName() + + @property + def apk(self): + return self._apk_helper and self._apk_helper.path + + @property + def apk_helper(self): + return self._apk_helper + + @property + def app_file_dir(self): + return self._app_data_file_dir + + @property + def app_files(self): + return self._app_data_files + + @property + def coverage_dir(self): + return self._coverage_dir + + @property + def enable_xml_result_parsing(self): + return self._enable_xml_result_parsing + + @property + def exe_dist_dir(self): + return self._exe_dist_dir + + @property + def external_shard_index(self): + return self._external_shard_index + + @property + def extract_test_list_from_filter(self): + return self._extract_test_list_from_filter + + @property + def extras(self): + return self._extras + + @property + def flags(self): + return self._flags + + @property + def gs_test_artifacts_bucket(self): + return self._gs_test_artifacts_bucket + + @property + def gtest_filter(self): + return self._gtest_filter + + @property + def isolated_script_test_output(self): + return self._isolated_script_test_output + + @property + def isolated_script_test_perf_output(self): + return self._isolated_script_test_perf_output + + @property + def render_test_output_dir(self): + return self._render_test_output_dir + + @property + def package(self): + return self._apk_helper and self._apk_helper.GetPackageName() + + @property + def permissions(self): + return self._apk_helper and self._apk_helper.GetPermissions() + + @property + def runner(self): + return self._apk_helper and self._apk_helper.GetInstrumentationName() + + @property + def shard_timeout(self): + return self._shard_timeout + + @property + def store_tombstones(self): + return self._store_tombstones + + @property + def suite(self): + return self._suite + + @property + def symbolizer(self): + return self._symbolizer + + @property + def test_apk_incremental_install_json(self): + return self._test_apk_incremental_install_json + + @property + def test_launcher_batch_limit(self): + return self._test_launcher_batch_limit + + @property + def total_external_shards(self): + return self._total_external_shards + + @property + def wait_for_java_debugger(self): + return self._wait_for_java_debugger + + @property + def use_existing_test_data(self): + return self._use_existing_test_data + + #override + def TestType(self): + return 'gtest' + + #override + def GetPreferredAbis(self): + if not self._apk_helper: + return None + return self._apk_helper.GetAbis() + + #override + def SetUp(self): + """Map data dependencies via isolate.""" + self._data_deps.extend( + self._data_deps_delegate(self._runtime_deps_path)) + + 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 + # This lock is required because on older versions of Python + # |unittest_util.FilterTestNames| use of |fnmatch| is not threadsafe. + with self._filter_tests_lock: + 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) + + if self._run_disabled and self._gtest_filter: + out_filtered_test_list = list(set(test_list)-set(filtered_test_list)) + for test in out_filtered_test_list: + test_name_no_disabled = TestNameWithoutDisabledPrefix(test) + if test_name_no_disabled != test and unittest_util.FilterTestNames( + [test_name_no_disabled], self._gtest_filter): + filtered_test_list.append(test) + return filtered_test_list + + def _GenerateDisabledFilterString(self, disabled_prefixes): + disabled_filter_items = [] + + if disabled_prefixes is None: + disabled_prefixes = ['FAILS_', 'PRE_'] + if '--run-manual' not in self._flags: + disabled_prefixes += ['MANUAL_'] + if not self._run_disabled: + disabled_prefixes += ['DISABLED_', 'FLAKY_'] + + 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( + host_paths.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) + + #override + def TearDown(self): + """Do nothing.""" + pass diff --git a/third_party/libwebrtc/build/android/pylib/gtest/gtest_test_instance_test.py b/third_party/libwebrtc/build/android/pylib/gtest/gtest_test_instance_test.py new file mode 100755 index 0000000000..993e2c78c4 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/gtest/gtest_test_instance_test.py @@ -0,0 +1,348 @@ +#!/usr/bin/env vpython3 +# 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.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) + + def testParseGTestListTests_emptyTestName(self): + raw_output = [ + 'TestCase.', + ' ', + ' nonEmptyTestName', + ] + actual = gtest_test_instance.ParseGTestListTests(raw_output) + expected = [ + 'TestCase.nonEmptyTestName', + ] + self.assertEqual(expected, actual) + + def testParseGTestOutput_pass(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ OK ] FooTest.Bar (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.PASS, actual[0].GetType()) + + def testParseGTestOutput_fail(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ FAILED ] FooTest.Bar (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.FAIL, actual[0].GetType()) + + def testParseGTestOutput_crash(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ CRASHED ] FooTest.Bar (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.CRASH, actual[0].GetType()) + + def testParseGTestOutput_errorCrash(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ERROR:blah] Currently running: FooTest.Bar', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertIsNone(actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.CRASH, actual[0].GetType()) + + def testParseGTestOutput_fatalDcheck(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[0324/183029.116334:FATAL:test_timeouts.cc(103)] Check failed: !init', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertIsNone(actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.CRASH, actual[0].GetType()) + + def testParseGTestOutput_unknown(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(0, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.CRASH, actual[0].GetType()) + + def testParseGTestOutput_nonterminalUnknown(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ RUN ] FooTest.Baz', + '[ OK ] FooTest.Baz (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(2, len(actual)) + + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(0, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.CRASH, actual[0].GetType()) + + self.assertEqual('FooTest.Baz', actual[1].GetName()) + self.assertEqual(1, actual[1].GetDuration()) + self.assertEqual(base_test_result.ResultType.PASS, actual[1].GetType()) + + def testParseGTestOutput_deathTestCrashOk(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ CRASHED ]', + '[ OK ] FooTest.Bar (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.PASS, actual[0].GetType()) + + def testParseGTestOutput_typeParameterized(self): + raw_output = [ + '[ RUN ] Baz/FooTest.Bar/0', + '[ FAILED ] Baz/FooTest.Bar/0, where TypeParam = (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('Baz/FooTest.Bar/0', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.FAIL, actual[0].GetType()) + + def testParseGTestOutput_valueParameterized(self): + raw_output = [ + '[ RUN ] Baz/FooTest.Bar/0', + '[ FAILED ] Baz/FooTest.Bar/0,' + + ' where GetParam() = 4-byte object <00-00 00-00> (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('Baz/FooTest.Bar/0', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.FAIL, actual[0].GetType()) + + def testParseGTestOutput_typeAndValueParameterized(self): + raw_output = [ + '[ RUN ] Baz/FooTest.Bar/0', + '[ FAILED ] Baz/FooTest.Bar/0,' + + ' where TypeParam = and GetParam() = (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('Baz/FooTest.Bar/0', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.FAIL, actual[0].GetType()) + + def testParseGTestOutput_skippedTest(self): + raw_output = [ + '[ RUN ] FooTest.Bar', + '[ SKIPPED ] FooTest.Bar (1 ms)', + ] + actual = gtest_test_instance.ParseGTestOutput(raw_output, None, None) + self.assertEqual(1, len(actual)) + self.assertEqual('FooTest.Bar', actual[0].GetName()) + self.assertEqual(1, actual[0].GetDuration()) + self.assertEqual(base_test_result.ResultType.SKIP, actual[0].GetType()) + + def testParseGTestXML_none(self): + actual = gtest_test_instance.ParseGTestXML(None) + self.assertEqual([], actual) + + def testParseGTestJSON_none(self): + actual = gtest_test_instance.ParseGTestJSON(None) + self.assertEqual([], actual) + + def testParseGTestJSON_example(self): + raw_json = """ + { + "tests": { + "mojom_tests": { + "parse": { + "ast_unittest": { + "ASTTest": { + "testNodeBase": { + "expected": "PASS", + "actual": "PASS", + "artifacts": { + "screenshot": ["screenshots/page.png"] + } + } + } + } + } + } + }, + "interrupted": false, + "path_delimiter": ".", + "version": 3, + "seconds_since_epoch": 1406662283.764424, + "num_failures_by_type": { + "FAIL": 0, + "PASS": 1 + }, + "artifact_types": { + "screenshot": "image/png" + } + }""" + actual = gtest_test_instance.ParseGTestJSON(raw_json) + self.assertEqual(1, len(actual)) + self.assertEqual('mojom_tests.parse.ast_unittest.ASTTest.testNodeBase', + actual[0].GetName()) + self.assertEqual(base_test_result.ResultType.PASS, actual[0].GetType()) + + def testParseGTestJSON_skippedTest_example(self): + raw_json = """ + { + "tests": { + "mojom_tests": { + "parse": { + "ast_unittest": { + "ASTTest": { + "testNodeBase": { + "expected": "SKIP", + "actual": "SKIP" + } + } + } + } + } + }, + "interrupted": false, + "path_delimiter": ".", + "version": 3, + "seconds_since_epoch": 1406662283.764424, + "num_failures_by_type": { + "SKIP": 1 + } + }""" + actual = gtest_test_instance.ParseGTestJSON(raw_json) + self.assertEqual(1, len(actual)) + self.assertEqual('mojom_tests.parse.ast_unittest.ASTTest.testNodeBase', + actual[0].GetName()) + self.assertEqual(base_test_result.ResultType.SKIP, actual[0].GetType()) + + def testTestNameWithoutDisabledPrefix_disabled(self): + test_name_list = [ + 'A.DISABLED_B', + 'DISABLED_A.B', + 'DISABLED_A.DISABLED_B', + ] + for test_name in test_name_list: + actual = gtest_test_instance \ + .TestNameWithoutDisabledPrefix(test_name) + expected = 'A.B' + self.assertEqual(expected, actual) + + def testTestNameWithoutDisabledPrefix_flaky(self): + test_name_list = [ + 'A.FLAKY_B', + 'FLAKY_A.B', + 'FLAKY_A.FLAKY_B', + ] + for test_name in test_name_list: + actual = gtest_test_instance \ + .TestNameWithoutDisabledPrefix(test_name) + expected = 'A.B' + self.assertEqual(expected, actual) + + def testTestNameWithoutDisabledPrefix_notDisabledOrFlaky(self): + test_name = 'A.B' + actual = gtest_test_instance \ + .TestNameWithoutDisabledPrefix(test_name) + expected = 'A.B' + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/instrumentation/__init__.py b/third_party/libwebrtc/build/android/pylib/instrumentation/__init__.py new file mode 100644 index 0000000000..96196cffb2 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/__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/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser.py b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser.py new file mode 100644 index 0000000000..d22fd48f4b --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser.py @@ -0,0 +1,112 @@ +# 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 + +# AndroidJUnitRunner would status output -3 to indicate a test is skipped +STATUS_CODE_SKIP = -3 + +# AndroidJUnitRunner outputs -4 to indicate a failed assumption +# "A test for which an assumption fails should not generate a test +# case failure" +# http://junit.org/junit4/javadoc/4.12/org/junit/AssumptionViolatedException.html +STATUS_CODE_ASSUMPTION_FAILURE = -4 + +STATUS_CODE_TEST_DURATION = 1337 + +# When a test batch fails due to post-test Assertion failures (eg. +# LifetimeAssert). +STATUS_CODE_BATCH_FAILURE = 1338 + +# http://developer.android.com/reference/android/app/Activity.html +RESULT_CODE_OK = -1 +RESULT_CODE_CANCELED = 0 + +_INSTR_LINE_RE = re.compile(r'^\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|). + """ + 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/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser_test.py b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser_test.py new file mode 100755 index 0000000000..0cd163d038 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_parser_test.py @@ -0,0 +1,135 @@ +#!/usr/bin/env vpython3 +# 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/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance.py b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance.py new file mode 100644 index 0000000000..b4a13c9031 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance.py @@ -0,0 +1,1171 @@ +# 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 copy +import logging +import os +import pickle +import re + +import six +from devil.android import apk_helper +from pylib import constants +from pylib.base import base_test_result +from pylib.base import test_exception +from pylib.base import test_instance +from pylib.constants import host_paths +from pylib.instrumentation import test_result +from pylib.instrumentation import instrumentation_parser +from pylib.symbols import deobfuscator +from pylib.symbols import stack_symbolizer +from pylib.utils import dexdump +from pylib.utils import gold_utils +from pylib.utils import instrumentation_tracing +from pylib.utils import proguard +from pylib.utils import shared_preference_utils +from pylib.utils import test_filter + + +with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): + import unittest_util # pylint: disable=import-error + +# Ref: http://developer.android.com/reference/android/app/Activity.html +_ACTIVITY_RESULT_CANCELED = 0 +_ACTIVITY_RESULT_OK = -1 + +_COMMAND_LINE_PARAMETER = 'cmdlinearg-parameter' +_DEFAULT_ANNOTATIONS = [ + 'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest', 'IntegrationTest'] +_EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS = [ + 'DisabledTest', 'FlakyTest', 'Manual'] +_VALID_ANNOTATIONS = set(_DEFAULT_ANNOTATIONS + + _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS) + +_TEST_LIST_JUNIT4_RUNNERS = [ + 'org.chromium.base.test.BaseChromiumAndroidJUnitRunner'] + +_SKIP_PARAMETERIZATION = 'SkipCommandLineParameterization' +_PARAMETERIZED_COMMAND_LINE_FLAGS = 'ParameterizedCommandLineFlags' +_PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES = ( + 'ParameterizedCommandLineFlags$Switches') +_NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) +_PICKLE_FORMAT_VERSION = 12 + +# The ID of the bundle value Instrumentation uses to report which test index the +# results are for in a collection of tests. Note that this index is 1-based. +_BUNDLE_CURRENT_ID = 'current' +# The ID of the bundle value Instrumentation uses to report the test class. +_BUNDLE_CLASS_ID = 'class' +# The ID of the bundle value Instrumentation uses to report the test name. +_BUNDLE_TEST_ID = 'test' +# The ID of the bundle value Instrumentation uses to report if a test was +# skipped. +_BUNDLE_SKIPPED_ID = 'test_skipped' +# The ID of the bundle value Instrumentation uses to report the crash stack, if +# the test crashed. +_BUNDLE_STACK_ID = 'stack' + +# The ID of the bundle value Chrome uses to report the test duration. +_BUNDLE_DURATION_ID = 'duration_ms' + +class MissingSizeAnnotationError(test_exception.TestException): + def __init__(self, class_name): + super(MissingSizeAnnotationError, self).__init__(class_name + + ': Test method is missing required size annotation. Add one of: ' + + ', '.join('@' + a for a in _VALID_ANNOTATIONS)) + + +class CommandLineParameterizationException(test_exception.TestException): + + def __init__(self, msg): + super(CommandLineParameterizationException, self).__init__(msg) + + +class TestListPickleException(test_exception.TestException): + pass + + +# TODO(jbudorick): Make these private class methods of +# InstrumentationTestInstance once the instrumentation junit3_runner_class 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, duration_ms, + device_abi, symbolizer): + """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|. + duration_ms: The duration of the test in milliseconds. + device_abi: The device_abi, which is needed for symbolization. + symbolizer: The symbolizer used to symbolize stack. + + Returns: + A list containing an instance of InstrumentationTestResult for each test + parsed. + """ + + results = [] + + current_result = None + cumulative_duration = 0 + + for status_code, bundle in statuses: + # If the last test was a failure already, don't override that failure with + # post-test failures that could be caused by the original failure. + if (status_code == instrumentation_parser.STATUS_CODE_BATCH_FAILURE + and current_result.GetType() != base_test_result.ResultType.FAIL): + current_result.SetType(base_test_result.ResultType.FAIL) + _MaybeSetLog(bundle, current_result, symbolizer, device_abi) + continue + + if status_code == instrumentation_parser.STATUS_CODE_TEST_DURATION: + # For the first result, duration will be set below to the difference + # between the reported and actual durations to account for overhead like + # starting instrumentation. + if results: + current_duration = int(bundle.get(_BUNDLE_DURATION_ID, duration_ms)) + current_result.SetDuration(current_duration) + cumulative_duration += current_duration + continue + + test_class = bundle.get(_BUNDLE_CLASS_ID, '') + test_method = bundle.get(_BUNDLE_TEST_ID, '') + 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, duration_ms) + else: + if status_code == instrumentation_parser.STATUS_CODE_OK: + if bundle.get(_BUNDLE_SKIPPED_ID, '').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) + elif status_code == instrumentation_parser.STATUS_CODE_SKIP: + current_result.SetType(base_test_result.ResultType.SKIP) + elif status_code == instrumentation_parser.STATUS_CODE_ASSUMPTION_FAILURE: + current_result.SetType(base_test_result.ResultType.SKIP) + 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) + _MaybeSetLog(bundle, current_result, symbolizer, device_abi) + + 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 six.itervalues(result_bundle))) + if crashed: + current_result.SetType(base_test_result.ResultType.CRASH) + + results.append(current_result) + + if results: + logging.info('Adding cumulative overhead to test %s: %dms', + results[0].GetName(), duration_ms - cumulative_duration) + results[0].SetDuration(duration_ms - cumulative_duration) + + return results + + +def _MaybeSetLog(bundle, current_result, symbolizer, device_abi): + if _BUNDLE_STACK_ID in bundle: + stack = bundle[_BUNDLE_STACK_ID] + if symbolizer and device_abi: + current_result.SetLog('%s\n%s' % (stack, '\n'.join( + symbolizer.ExtractAndResolveNativeStackTraces(stack, device_abi)))) + else: + current_result.SetLog(stack) + + current_result.SetFailureReason(_ParseExceptionMessage(stack)) + + +def _ParseExceptionMessage(stack): + """Extracts the exception message from the given stack trace. + """ + # This interprets stack traces reported via InstrumentationResultPrinter: + # https://source.chromium.org/chromium/chromium/src/+/main:third_party/android_support_test_runner/runner/src/main/java/android/support/test/internal/runner/listener/InstrumentationResultPrinter.java;l=181?q=InstrumentationResultPrinter&type=cs + # This is a standard Java stack trace, of the form: + # + # at SomeClass.SomeMethod(...) + # at ... + lines = stack.split('\n') + for i, line in enumerate(lines): + if line.startswith('\tat'): + return '\n'.join(lines[0:i]) + # No call stack found, so assume everything is the exception message. + return stack + + +def FilterTests(tests, filter_str=None, annotations=None, + excluded_annotations=None): + """Filter a list of tests + + Args: + tests: a list of tests. e.g. [ + {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, + {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] + filter_str: googletest-style filter string. + annotations: a dict of wanted annotations for test methods. + excluded_annotations: a dict of annotations to exclude. + + Return: + A list of filtered tests + """ + + def test_names_from_pattern(combined_pattern, test_names): + patterns = combined_pattern.split(':') + + hashable_patterns = set() + filename_patterns = [] + for pattern in patterns: + if ('*' in pattern or '?' in pattern or '[' in pattern): + filename_patterns.append(pattern) + else: + hashable_patterns.add(pattern) + + filter_test_names = set( + unittest_util.FilterTestNames(test_names, ':'.join( + filename_patterns))) if len(filename_patterns) > 0 else set() + + for test_name in test_names: + if test_name in hashable_patterns: + filter_test_names.add(test_name) + + return filter_test_names + + def get_test_names(test): + test_names = set() + # Allow fully-qualified name as well as an omitted package. + unqualified_class_test = { + 'class': test['class'].split('.')[-1], + 'method': test['method'] + } + + test_name = GetTestName(test, sep='.') + test_names.add(test_name) + + unqualified_class_test_name = GetTestName(unqualified_class_test, sep='.') + test_names.add(unqualified_class_test_name) + + unique_test_name = GetUniqueTestName(test, sep='.') + test_names.add(unique_test_name) + + if test['is_junit4']: + junit4_test_name = GetTestNameWithoutParameterPostfix(test, sep='.') + test_names.add(junit4_test_name) + + unqualified_junit4_test_name = \ + GetTestNameWithoutParameterPostfix(unqualified_class_test, sep='.') + test_names.add(unqualified_junit4_test_name) + return test_names + + def get_tests_from_names(tests, test_names, tests_to_names): + ''' Returns the tests for which the given names apply + + Args: + tests: a list of tests. e.g. [ + {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, + {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] + test_names: a collection of names determining tests to return. + + Return: + A list of tests that match the given test names + ''' + filtered_tests = [] + for t in tests: + current_test_names = tests_to_names[id(t)] + + for current_test_name in current_test_names: + if current_test_name in test_names: + filtered_tests.append(t) + break + + return filtered_tests + + def remove_tests_from_names(tests, remove_test_names, tests_to_names): + ''' Returns the tests from the given list with given names removed + + Args: + tests: a list of tests. e.g. [ + {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, + {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] + remove_test_names: a collection of names determining tests to remove. + tests_to_names: a dcitionary of test ids to a collection of applicable + names for that test + + Return: + A list of tests that don't match the given test names + ''' + filtered_tests = [] + + for t in tests: + for name in tests_to_names[id(t)]: + if name in remove_test_names: + break + else: + filtered_tests.append(t) + return filtered_tests + + def gtests_filter(tests, combined_filter): + ''' Returns the tests after the filter_str has been applied + + Args: + tests: a list of tests. e.g. [ + {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, + {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] + combined_filter: the filter string representing tests to exclude + + Return: + A list of tests that should still be included after the filter_str is + applied to their names + ''' + + if not combined_filter: + return tests + + # Collect all test names + all_test_names = set() + tests_to_names = {} + for t in tests: + tests_to_names[id(t)] = get_test_names(t) + for name in tests_to_names[id(t)]: + all_test_names.add(name) + + pattern_groups = filter_str.split('-') + negative_pattern = pattern_groups[1] if len(pattern_groups) > 1 else None + positive_pattern = pattern_groups[0] + + if positive_pattern: + # Only use the test names that match the positive pattern + positive_test_names = test_names_from_pattern(positive_pattern, + all_test_names) + tests = get_tests_from_names(tests, positive_test_names, tests_to_names) + + if negative_pattern: + # Remove any test the negative filter matches + remove_names = test_names_from_pattern(negative_pattern, all_test_names) + tests = remove_tests_from_names(tests, remove_names, tests_to_names) + + return tests + + def annotation_filter(all_annotations): + if not annotations: + return True + return any_annotation_matches(annotations, all_annotations) + + def excluded_annotation_filter(all_annotations): + if not excluded_annotations: + return True + return not any_annotation_matches(excluded_annotations, + all_annotations) + + def any_annotation_matches(filter_annotations, all_annotations): + return any( + ak in all_annotations + and annotation_value_matches(av, all_annotations[ak]) + for ak, av in filter_annotations) + + def annotation_value_matches(filter_av, av): + if filter_av is None: + return True + elif isinstance(av, dict): + tav_from_dict = av['value'] + # If tav_from_dict is an int, the 'in' operator breaks, so convert + # filter_av and manually compare. See https://crbug.com/1019707 + if isinstance(tav_from_dict, int): + return int(filter_av) == tav_from_dict + else: + return filter_av in tav_from_dict + elif isinstance(av, list): + return filter_av in av + return filter_av == av + + return_tests = [] + for t in gtests_filter(tests, filter_str): + # Enforce that all tests declare their size. + if not any(a in _VALID_ANNOTATIONS for a in t['annotations']): + raise MissingSizeAnnotationError(GetTestName(t)) + + if (not annotation_filter(t['annotations']) + or not excluded_annotation_filter(t['annotations'])): + continue + return_tests.append(t) + + return return_tests + +# TODO(yolandyan): remove this once the tests are converted to junit4 +def GetAllTestsFromJar(test_jar): + pickle_path = '%s-proguard.pickle' % test_jar + try: + tests = GetTestsFromPickle(pickle_path, os.path.getmtime(test_jar)) + except TestListPickleException as e: + logging.info('Could not get tests from pickle: %s', e) + logging.info('Getting tests from JAR via proguard.') + tests = _GetTestsFromProguard(test_jar) + SaveTestsToPickle(pickle_path, tests) + return tests + + +def GetAllTestsFromApk(test_apk): + pickle_path = '%s-dexdump.pickle' % test_apk + try: + tests = GetTestsFromPickle(pickle_path, os.path.getmtime(test_apk)) + except TestListPickleException as e: + logging.info('Could not get tests from pickle: %s', e) + logging.info('Getting tests from dex via dexdump.') + tests = _GetTestsFromDexdump(test_apk) + SaveTestsToPickle(pickle_path, tests) + return tests + +def GetTestsFromPickle(pickle_path, test_mtime): + if not os.path.exists(pickle_path): + raise TestListPickleException('%s does not exist.' % pickle_path) + if os.path.getmtime(pickle_path) <= test_mtime: + raise TestListPickleException('File is stale: %s' % pickle_path) + + with open(pickle_path, 'r') as f: + pickle_data = pickle.load(f) + if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION: + raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.') + return pickle_data['TEST_METHODS'] + + +# TODO(yolandyan): remove this once the test listing from java runner lands +@instrumentation_tracing.no_tracing +def _GetTestsFromProguard(jar_path): + p = proguard.Dump(jar_path) + class_lookup = dict((c['class'], c) for c in p['classes']) + + def is_test_class(c): + return c['class'].endswith('Test') + + def is_test_method(m): + return m['method'].startswith('test') + + def recursive_class_annotations(c): + s = c['superclass'] + if s in class_lookup: + a = recursive_class_annotations(class_lookup[s]) + else: + a = {} + a.update(c['annotations']) + return a + + def stripped_test_class(c): + return { + 'class': c['class'], + 'annotations': recursive_class_annotations(c), + 'methods': [m for m in c['methods'] if is_test_method(m)], + 'superclass': c['superclass'], + } + + return [stripped_test_class(c) for c in p['classes'] + if is_test_class(c)] + + +def _GetTestsFromDexdump(test_apk): + dex_dumps = dexdump.Dump(test_apk) + tests = [] + + def get_test_methods(methods): + return [ + { + 'method': m, + # No annotation info is available from dexdump. + # Set MediumTest annotation for default. + 'annotations': {'MediumTest': None}, + } for m in methods if m.startswith('test')] + + for dump in dex_dumps: + for package_name, package_info in six.iteritems(dump): + for class_name, class_info in six.iteritems(package_info['classes']): + if class_name.endswith('Test'): + tests.append({ + 'class': '%s.%s' % (package_name, class_name), + 'annotations': {}, + 'methods': get_test_methods(class_info['methods']), + 'superclass': class_info['superclass'], + }) + return tests + +def SaveTestsToPickle(pickle_path, tests): + pickle_data = { + 'VERSION': _PICKLE_FORMAT_VERSION, + 'TEST_METHODS': tests, + } + with open(pickle_path, 'wb') as pickle_file: + pickle.dump(pickle_data, pickle_file) + + +class MissingJUnit4RunnerException(test_exception.TestException): + """Raised when JUnit4 runner is not provided or specified in apk manifest""" + + def __init__(self): + super(MissingJUnit4RunnerException, self).__init__( + 'JUnit4 runner is not provided or specified in test apk manifest.') + + +def GetTestName(test, sep='#'): + """Gets the name of the given test. + + Note that this may return the same name for more than one test, e.g. if a + test is being run multiple times with different parameters. + + Args: + test: the instrumentation test dict. + sep: the character(s) that should join the class name and the method name. + Returns: + The test name as a string. + """ + test_name = '%s%s%s' % (test['class'], sep, test['method']) + assert ' *-:' not in test_name, ( + 'The test name must not contain any of the characters in " *-:". See ' + 'https://crbug.com/912199') + return test_name + + +def GetTestNameWithoutParameterPostfix( + test, sep='#', parameterization_sep='__'): + """Gets the name of the given JUnit4 test without parameter postfix. + + For most WebView JUnit4 javatests, each test is parameterizatized with + "__sandboxed_mode" to run in both non-sandboxed mode and sandboxed mode. + + This function returns the name of the test without parameterization + so test filters can match both parameterized and non-parameterized tests. + + Args: + test: the instrumentation test dict. + sep: the character(s) that should join the class name and the method name. + parameterization_sep: the character(s) that seperate method name and method + parameterization postfix. + Returns: + The test name without parameter postfix as a string. + """ + name = GetTestName(test, sep=sep) + return name.split(parameterization_sep)[0] + + +def GetUniqueTestName(test, sep='#'): + """Gets the unique name of the given test. + + This will include text to disambiguate between tests for which GetTestName + would return the same name. + + Args: + test: the instrumentation test dict. + sep: the character(s) that should join the class name and the method name. + Returns: + The unique test name as a string. + """ + display_name = GetTestName(test, sep=sep) + if test.get('flags', [None])[0]: + sanitized_flags = [x.replace('-', '_') for x in test['flags']] + display_name = '%s_with_%s' % (display_name, '_'.join(sanitized_flags)) + + assert ' *-:' not in display_name, ( + 'The test name must not contain any of the characters in " *-:". See ' + 'https://crbug.com/912199') + + return display_name + + +class InstrumentationTestInstance(test_instance.TestInstance): + + def __init__(self, args, data_deps_delegate, error_func): + super(InstrumentationTestInstance, self).__init__() + + self._additional_apks = [] + self._apk_under_test = None + self._apk_under_test_incremental_install_json = None + self._modules = None + self._fake_modules = None + self._additional_locales = None + self._package_info = None + self._suite = None + self._test_apk = None + self._test_apk_incremental_install_json = None + self._test_jar = None + self._test_package = None + self._junit3_runner_class = None + self._junit4_runner_class = None + self._junit4_runner_supports_listing = None + self._test_support_apk = None + self._initializeApkAttributes(args, error_func) + + self._data_deps = None + self._data_deps_delegate = None + self._runtime_deps_path = None + self._initializeDataDependencyAttributes(args, data_deps_delegate) + + self._annotations = None + self._excluded_annotations = None + self._test_filter = None + self._initializeTestFilterAttributes(args) + + self._flags = None + self._use_apk_under_test_flags_file = False + self._initializeFlagAttributes(args) + + self._screenshot_dir = None + self._timeout_scale = None + self._wait_for_java_debugger = None + self._initializeTestControlAttributes(args) + + self._coverage_directory = None + self._initializeTestCoverageAttributes(args) + + self._store_tombstones = False + self._symbolizer = None + self._enable_breakpad_dump = False + self._enable_java_deobfuscation = False + self._deobfuscator = None + self._initializeLogAttributes(args) + + self._edit_shared_prefs = [] + self._initializeEditPrefsAttributes(args) + + self._replace_system_package = None + self._initializeReplaceSystemPackageAttributes(args) + + self._system_packages_to_remove = None + self._initializeSystemPackagesToRemoveAttributes(args) + + self._use_webview_provider = None + self._initializeUseWebviewProviderAttributes(args) + + self._skia_gold_properties = None + self._initializeSkiaGoldAttributes(args) + + self._test_launcher_batch_limit = None + self._initializeTestLauncherAttributes(args) + + self._wpr_enable_record = args.wpr_enable_record + + self._external_shard_index = args.test_launcher_shard_index + self._total_external_shards = args.test_launcher_total_shards + + def _initializeApkAttributes(self, args, error_func): + if args.apk_under_test: + apk_under_test_path = args.apk_under_test + if (not args.apk_under_test.endswith('.apk') + and not args.apk_under_test.endswith('.apks')): + apk_under_test_path = os.path.join( + constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR, + '%s.apk' % args.apk_under_test) + + # TODO(jbudorick): Move the realpath up to the argument parser once + # APK-by-name is no longer supported. + apk_under_test_path = os.path.realpath(apk_under_test_path) + + if not os.path.exists(apk_under_test_path): + error_func('Unable to find APK under test: %s' % apk_under_test_path) + + self._apk_under_test = apk_helper.ToHelper(apk_under_test_path) + + test_apk_path = args.test_apk + if not os.path.exists(test_apk_path): + test_apk_path = os.path.join( + constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR, + '%s.apk' % args.test_apk) + # TODO(jbudorick): Move the realpath up to the argument parser once + # APK-by-name is no longer supported. + test_apk_path = os.path.realpath(test_apk_path) + + if not os.path.exists(test_apk_path): + error_func('Unable to find test APK: %s' % test_apk_path) + + self._test_apk = apk_helper.ToHelper(test_apk_path) + self._suite = os.path.splitext(os.path.basename(args.test_apk))[0] + + self._apk_under_test_incremental_install_json = ( + args.apk_under_test_incremental_install_json) + self._test_apk_incremental_install_json = ( + args.test_apk_incremental_install_json) + + if self._test_apk_incremental_install_json: + assert self._suite.endswith('_incremental') + self._suite = self._suite[:-len('_incremental')] + + self._modules = args.modules + self._fake_modules = args.fake_modules + self._additional_locales = args.additional_locales + + self._test_jar = args.test_jar + self._test_support_apk = apk_helper.ToHelper(os.path.join( + constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR, + '%sSupport.apk' % self._suite)) + + if not self._test_jar: + logging.warning('Test jar not specified. Test runner will not have ' + 'Java annotation info available. May not handle test ' + 'timeouts correctly.') + elif not os.path.exists(self._test_jar): + error_func('Unable to find test JAR: %s' % self._test_jar) + + self._test_package = self._test_apk.GetPackageName() + all_instrumentations = self._test_apk.GetAllInstrumentations() + all_junit3_runner_classes = [ + x for x in all_instrumentations if ('0xffffffff' in x.get( + 'chromium-junit3', ''))] + all_junit4_runner_classes = [ + x for x in all_instrumentations if ('0xffffffff' not in x.get( + 'chromium-junit3', ''))] + + if len(all_junit3_runner_classes) > 1: + logging.warning('This test apk has more than one JUnit3 instrumentation') + if len(all_junit4_runner_classes) > 1: + logging.warning('This test apk has more than one JUnit4 instrumentation') + + self._junit3_runner_class = ( + all_junit3_runner_classes[0]['android:name'] + if all_junit3_runner_classes else self.test_apk.GetInstrumentationName()) + + self._junit4_runner_class = ( + all_junit4_runner_classes[0]['android:name'] + if all_junit4_runner_classes else None) + + if self._junit4_runner_class: + if self._test_apk_incremental_install_json: + self._junit4_runner_supports_listing = next( + (True for x in self._test_apk.GetAllMetadata() + if 'real-instr' in x[0] and x[1] in _TEST_LIST_JUNIT4_RUNNERS), + False) + else: + self._junit4_runner_supports_listing = ( + self._junit4_runner_class in _TEST_LIST_JUNIT4_RUNNERS) + + self._package_info = None + if self._apk_under_test: + package_under_test = self._apk_under_test.GetPackageName() + for package_info in six.itervalues(constants.PACKAGE_INFO): + if package_under_test == package_info.package: + self._package_info = package_info + break + if not self._package_info: + logging.warning(("Unable to find package info for %s. " + + "(This may just mean that the test package is " + + "currently being installed.)"), + self._test_package) + + for apk in args.additional_apks: + if not os.path.exists(apk): + error_func('Unable to find additional APK: %s' % apk) + self._additional_apks = ( + [apk_helper.ToHelper(x) for x in args.additional_apks]) + + def _initializeDataDependencyAttributes(self, args, data_deps_delegate): + self._data_deps = [] + self._data_deps_delegate = data_deps_delegate + self._runtime_deps_path = args.runtime_deps_path + + if not self._runtime_deps_path: + logging.warning('No data dependencies will be pushed.') + + def _initializeTestFilterAttributes(self, args): + self._test_filter = test_filter.InitializeFilterFromArgs(args) + + def annotation_element(a): + a = a.split('=', 1) + return (a[0], a[1] if len(a) == 2 else None) + + if args.annotation_str: + self._annotations = [ + annotation_element(a) for a in args.annotation_str.split(',')] + elif not self._test_filter: + self._annotations = [ + annotation_element(a) for a in _DEFAULT_ANNOTATIONS] + else: + self._annotations = [] + + if args.exclude_annotation_str: + self._excluded_annotations = [ + annotation_element(a) for a in args.exclude_annotation_str.split(',')] + else: + self._excluded_annotations = [] + + requested_annotations = set(a[0] for a in self._annotations) + if not args.run_disabled: + self._excluded_annotations.extend( + annotation_element(a) for a in _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS + if a not in requested_annotations) + + def _initializeFlagAttributes(self, args): + self._use_apk_under_test_flags_file = args.use_apk_under_test_flags_file + self._flags = ['--enable-test-intents'] + if args.command_line_flags: + self._flags.extend(args.command_line_flags) + if 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) + if args.strict_mode and args.strict_mode != 'off' and ( + # TODO(yliuyliu): Turn on strict mode for coverage once + # crbug/1006397 is fixed. + not args.coverage_dir): + self._flags.append('--strict-mode=' + args.strict_mode) + + def _initializeTestControlAttributes(self, args): + self._screenshot_dir = args.screenshot_dir + self._timeout_scale = args.timeout_scale or 1 + self._wait_for_java_debugger = args.wait_for_java_debugger + + def _initializeTestCoverageAttributes(self, args): + self._coverage_directory = args.coverage_dir + + def _initializeLogAttributes(self, args): + self._enable_breakpad_dump = args.enable_breakpad_dump + self._enable_java_deobfuscation = args.enable_java_deobfuscation + self._store_tombstones = args.store_tombstones + self._symbolizer = stack_symbolizer.Symbolizer( + self.apk_under_test.path if self.apk_under_test else None) + + def _initializeEditPrefsAttributes(self, args): + if not hasattr(args, 'shared_prefs_file') or not args.shared_prefs_file: + return + if not isinstance(args.shared_prefs_file, str): + logging.warning("Given non-string for a filepath") + return + self._edit_shared_prefs = shared_preference_utils.ExtractSettingsFromJson( + args.shared_prefs_file) + + def _initializeReplaceSystemPackageAttributes(self, args): + if (not hasattr(args, 'replace_system_package') + or not args.replace_system_package): + return + self._replace_system_package = args.replace_system_package + + def _initializeSystemPackagesToRemoveAttributes(self, args): + if (not hasattr(args, 'system_packages_to_remove') + or not args.system_packages_to_remove): + return + self._system_packages_to_remove = args.system_packages_to_remove + + def _initializeUseWebviewProviderAttributes(self, args): + if (not hasattr(args, 'use_webview_provider') + or not args.use_webview_provider): + return + self._use_webview_provider = args.use_webview_provider + + def _initializeSkiaGoldAttributes(self, args): + self._skia_gold_properties = gold_utils.AndroidSkiaGoldProperties(args) + + def _initializeTestLauncherAttributes(self, args): + if hasattr(args, 'test_launcher_batch_limit'): + self._test_launcher_batch_limit = args.test_launcher_batch_limit + + @property + def additional_apks(self): + return self._additional_apks + + @property + def apk_under_test(self): + return self._apk_under_test + + @property + def apk_under_test_incremental_install_json(self): + return self._apk_under_test_incremental_install_json + + @property + def modules(self): + return self._modules + + @property + def fake_modules(self): + return self._fake_modules + + @property + def additional_locales(self): + return self._additional_locales + + @property + def coverage_directory(self): + return self._coverage_directory + + @property + def edit_shared_prefs(self): + return self._edit_shared_prefs + + @property + def enable_breakpad_dump(self): + return self._enable_breakpad_dump + + @property + def external_shard_index(self): + return self._external_shard_index + + @property + def flags(self): + return self._flags + + @property + def junit3_runner_class(self): + return self._junit3_runner_class + + @property + def junit4_runner_class(self): + return self._junit4_runner_class + + @property + def junit4_runner_supports_listing(self): + return self._junit4_runner_supports_listing + + @property + def package_info(self): + return self._package_info + + @property + def replace_system_package(self): + return self._replace_system_package + + @property + def use_webview_provider(self): + return self._use_webview_provider + + @property + def screenshot_dir(self): + return self._screenshot_dir + + @property + def skia_gold_properties(self): + return self._skia_gold_properties + + @property + def store_tombstones(self): + return self._store_tombstones + + @property + def suite(self): + return self._suite + + @property + def symbolizer(self): + return self._symbolizer + + @property + def system_packages_to_remove(self): + return self._system_packages_to_remove + + @property + def test_apk(self): + return self._test_apk + + @property + def test_apk_incremental_install_json(self): + return self._test_apk_incremental_install_json + + @property + def test_filter(self): + return self._test_filter + + @property + def test_jar(self): + return self._test_jar + + @property + def test_launcher_batch_limit(self): + return self._test_launcher_batch_limit + + @property + def test_support_apk(self): + return self._test_support_apk + + @property + def test_package(self): + return self._test_package + + @property + def timeout_scale(self): + return self._timeout_scale + + @property + def total_external_shards(self): + return self._total_external_shards + + @property + def use_apk_under_test_flags_file(self): + return self._use_apk_under_test_flags_file + + @property + def wait_for_java_debugger(self): + return self._wait_for_java_debugger + + @property + def wpr_record_mode(self): + return self._wpr_enable_record + + @property + def wpr_replay_mode(self): + return not self._wpr_enable_record + + #override + def TestType(self): + return 'instrumentation' + + #override + def GetPreferredAbis(self): + # We could alternatively take the intersection of what they all support, + # but it should never be the case that they support different things. + apks = [self._test_apk, self._apk_under_test] + self._additional_apks + for apk in apks: + if apk: + ret = apk.GetAbis() + if ret: + return ret + return [] + + #override + def SetUp(self): + self._data_deps.extend( + self._data_deps_delegate(self._runtime_deps_path)) + if self._enable_java_deobfuscation: + self._deobfuscator = deobfuscator.DeobfuscatorPool( + self.test_apk.path + '.mapping') + + def GetDataDependencies(self): + return self._data_deps + + def GetTests(self): + if self.test_jar: + raw_tests = GetAllTestsFromJar(self.test_jar) + else: + raw_tests = GetAllTestsFromApk(self.test_apk.path) + return self.ProcessRawTests(raw_tests) + + def MaybeDeobfuscateLines(self, lines): + if not self._deobfuscator: + return lines + return self._deobfuscator.TransformLines(lines) + + def ProcessRawTests(self, raw_tests): + inflated_tests = self._ParameterizeTestsWithFlags( + self._InflateTests(raw_tests)) + if self._junit4_runner_class is None and any( + t['is_junit4'] for t in inflated_tests): + raise MissingJUnit4RunnerException() + filtered_tests = FilterTests( + inflated_tests, self._test_filter, self._annotations, + self._excluded_annotations) + if self._test_filter and not filtered_tests: + for t in inflated_tests: + logging.debug(' %s', GetUniqueTestName(t)) + logging.warning('Unmatched Filter: %s', self._test_filter) + return filtered_tests + + # pylint: disable=no-self-use + 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, + # TODO(https://crbug.com/1084729): Remove is_junit4. + 'is_junit4': True + }) + return inflated_tests + + def _ParameterizeTestsWithFlags(self, tests): + + def _checkParameterization(annotations): + types = [ + _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES, + _PARAMETERIZED_COMMAND_LINE_FLAGS, + ] + if types[0] in annotations and types[1] in annotations: + raise CommandLineParameterizationException( + 'Multiple command-line parameterization types: {}.'.format( + ', '.join(types))) + + def _switchesToFlags(switches): + return ['--{}'.format(s) for s in switches if s] + + def _annotationToSwitches(clazz, methods): + if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES: + return [methods['value']] + elif clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS: + list_of_switches = [] + for annotation in methods['value']: + for clazz, methods in six.iteritems(annotation): + list_of_switches += _annotationToSwitches(clazz, methods) + return list_of_switches + else: + return [] + + def _setTestFlags(test, flags): + if flags: + test['flags'] = flags + elif 'flags' in test: + del test['flags'] + + new_tests = [] + for t in tests: + annotations = t['annotations'] + list_of_switches = [] + _checkParameterization(annotations) + if _SKIP_PARAMETERIZATION not in annotations: + for clazz, methods in six.iteritems(annotations): + list_of_switches += _annotationToSwitches(clazz, methods) + if list_of_switches: + _setTestFlags(t, _switchesToFlags(list_of_switches[0])) + for p in list_of_switches[1:]: + parameterized_t = copy.copy(t) + _setTestFlags(parameterized_t, _switchesToFlags(p)) + new_tests.append(parameterized_t) + return tests + new_tests + + @staticmethod + def ParseAmInstrumentRawOutput(raw_output): + return ParseAmInstrumentRawOutput(raw_output) + + @staticmethod + def GenerateTestResults(result_code, result_bundle, statuses, duration_ms, + device_abi, symbolizer): + return GenerateTestResults(result_code, result_bundle, statuses, + duration_ms, device_abi, symbolizer) + + #override + def TearDown(self): + self.symbolizer.CleanUp() + if self._deobfuscator: + self._deobfuscator.Close() + self._deobfuscator = None diff --git a/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance_test.py b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance_test.py new file mode 100755 index 0000000000..f3b0d4f6e6 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/instrumentation_test_instance_test.py @@ -0,0 +1,1250 @@ +#!/usr/bin/env vpython3 +# 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_test_instance.""" + +# pylint: disable=protected-access + + +import collections +import tempfile +import unittest + +from six.moves import range # pylint: disable=redefined-builtin +from pylib.base import base_test_result +from pylib.instrumentation import instrumentation_test_instance + +import mock # pylint: disable=import-error + +_INSTRUMENTATION_TEST_INSTANCE_PATH = ( + 'pylib.instrumentation.instrumentation_test_instance.%s') + +class InstrumentationTestInstanceTest(unittest.TestCase): + + def setUp(self): + options = mock.Mock() + options.tool = '' + + @staticmethod + def createTestInstance(): + c = _INSTRUMENTATION_TEST_INSTANCE_PATH % 'InstrumentationTestInstance' + # yapf: disable + with mock.patch('%s._initializeApkAttributes' % c), ( + mock.patch('%s._initializeDataDependencyAttributes' % c)), ( + mock.patch('%s._initializeTestFilterAttributes' %c)), ( + mock.patch('%s._initializeFlagAttributes' % c)), ( + mock.patch('%s._initializeTestControlAttributes' % c)), ( + mock.patch('%s._initializeTestCoverageAttributes' % c)), ( + mock.patch('%s._initializeSkiaGoldAttributes' % c)): + # yapf: enable + return instrumentation_test_instance.InstrumentationTestInstance( + mock.MagicMock(), mock.MagicMock(), lambda s: None) + + _FlagAttributesArgs = collections.namedtuple('_FlagAttributesArgs', [ + 'command_line_flags', 'device_flags_file', 'strict_mode', + 'use_apk_under_test_flags_file', 'coverage_dir' + ]) + + def createFlagAttributesArgs(self, + command_line_flags=None, + device_flags_file=None, + strict_mode=None, + use_apk_under_test_flags_file=False, + coverage_dir=None): + return self._FlagAttributesArgs(command_line_flags, device_flags_file, + strict_mode, use_apk_under_test_flags_file, + coverage_dir) + + def test_initializeFlagAttributes_commandLineFlags(self): + o = self.createTestInstance() + args = self.createFlagAttributesArgs(command_line_flags=['--foo', '--bar']) + o._initializeFlagAttributes(args) + self.assertEqual(o._flags, ['--enable-test-intents', '--foo', '--bar']) + + def test_initializeFlagAttributes_deviceFlagsFile(self): + o = self.createTestInstance() + with tempfile.NamedTemporaryFile(mode='w') as flags_file: + flags_file.write('\n'.join(['--foo', '--bar'])) + flags_file.flush() + + args = self.createFlagAttributesArgs(device_flags_file=flags_file.name) + o._initializeFlagAttributes(args) + self.assertEqual(o._flags, ['--enable-test-intents', '--foo', '--bar']) + + def test_initializeFlagAttributes_strictModeOn(self): + o = self.createTestInstance() + args = self.createFlagAttributesArgs(strict_mode='on') + o._initializeFlagAttributes(args) + self.assertEqual(o._flags, ['--enable-test-intents', '--strict-mode=on']) + + def test_initializeFlagAttributes_strictModeOn_coverageOn(self): + o = self.createTestInstance() + args = self.createFlagAttributesArgs( + strict_mode='on', coverage_dir='/coverage/dir') + o._initializeFlagAttributes(args) + self.assertEqual(o._flags, ['--enable-test-intents']) + + def test_initializeFlagAttributes_strictModeOff(self): + o = self.createTestInstance() + args = self.createFlagAttributesArgs(strict_mode='off') + o._initializeFlagAttributes(args) + self.assertEqual(o._flags, ['--enable-test-intents']) + + def testGetTests_noFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'method': 'testMethod1', + 'is_junit4': True, + }, + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'MediumTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'method': 'testMethod2', + 'is_junit4': True, + }, + { + 'annotations': { + 'Feature': {'value': ['Bar']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'method': 'testMethod1', + 'is_junit4': True, + }, + ] + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_simpleGtestFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._test_filter = 'org.chromium.test.SampleTest.testMethod1' + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_simpleGtestPositiveAndNegativeFilter(self): + o = self.createTestInstance() + raw_tests = [{ + 'annotations': { + 'Feature': { + 'value': ['Foo'] + } + }, + 'class': + 'org.chromium.test.SampleTest', + 'superclass': + 'java.lang.Object', + 'methods': [ + { + 'annotations': { + 'SmallTest': None + }, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'MediumTest': None + }, + 'method': 'testMethod2', + }, + ], + }, { + 'annotations': { + 'Feature': { + 'value': ['Foo'] + } + }, + 'class': + 'org.chromium.test.SampleTest2', + 'superclass': + 'java.lang.Object', + 'methods': [{ + 'annotations': { + 'SmallTest': None + }, + 'method': 'testMethod1', + }], + }] + + expected_tests = [ + { + 'annotations': { + 'Feature': { + 'value': ['Foo'] + }, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._test_filter = \ + 'org.chromium.test.SampleTest.*-org.chromium.test.SampleTest.testMethod2' + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_simpleGtestUnqualifiedNameFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._test_filter = 'SampleTest.testMethod1' + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_parameterizedTestGtestFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1__sandboxed_mode', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'method': 'testMethod1', + 'is_junit4': True, + }, + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'method': 'testMethod1__sandboxed_mode', + 'is_junit4': True, + }, + ] + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + o._test_filter = 'org.chromium.test.SampleTest.testMethod1' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_wildcardGtestFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Bar']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._test_filter = 'org.chromium.test.SampleTest2.*' + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_negativeGtestFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'MediumTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod2', + }, + { + 'annotations': { + 'Feature': {'value': ['Bar']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._test_filter = '*-org.chromium.test.SampleTest.testMethod1' + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_annotationFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Foo']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'Feature': {'value': ['Bar']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._annotations = [('SmallTest', None)] + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_excludedAnnotationFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': { + 'value': ['Foo'] + }, + 'MediumTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod2', + }, + ] + + o._excluded_annotations = [('SmallTest', None)] + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_annotationSimpleValueFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': { + 'SmallTest': None, + 'TestValue': '1', + }, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'MediumTest': None, + 'TestValue': '2', + }, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': { + 'SmallTest': None, + 'TestValue': '3', + }, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': { + 'value': ['Foo'] + }, + 'SmallTest': None, + 'TestValue': '1', + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._annotations = [('TestValue', '1')] + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTests_annotationDictValueFilter(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': {'MediumTest': None}, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'java.lang.Object', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': {'value': ['Bar']}, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._annotations = [('Feature', 'Bar')] + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGetTestName(self): + test = { + 'annotations': { + 'RunWith': {'value': 'class J4Runner'}, + 'SmallTest': {}, + 'Test': {'expected': 'class org.junit.Test$None', + 'timeout': '0'}, + 'UiThreadTest': {}}, + 'class': 'org.chromium.TestA', + 'is_junit4': True, + 'method': 'testSimple'} + unqualified_class_test = { + 'class': test['class'].split('.')[-1], + 'method': test['method'] + } + + self.assertEqual(instrumentation_test_instance.GetTestName(test, sep='.'), + 'org.chromium.TestA.testSimple') + self.assertEqual( + instrumentation_test_instance.GetTestName(unqualified_class_test, + sep='.'), 'TestA.testSimple') + + def testGetUniqueTestName(self): + test = { + 'annotations': { + 'RunWith': {'value': 'class J4Runner'}, + 'SmallTest': {}, + 'Test': {'expected': 'class org.junit.Test$None', 'timeout': '0'}, + 'UiThreadTest': {}}, + 'class': 'org.chromium.TestA', + 'flags': ['enable_features=abc'], + 'is_junit4': True, + 'method': 'testSimple'} + self.assertEqual( + instrumentation_test_instance.GetUniqueTestName(test, sep='.'), + 'org.chromium.TestA.testSimple_with_enable_features=abc') + + def testGetTestNameWithoutParameterPostfix(self): + test = { + 'annotations': { + 'RunWith': {'value': 'class J4Runner'}, + 'SmallTest': {}, + 'Test': {'expected': 'class org.junit.Test$None', 'timeout': '0'}, + 'UiThreadTest': {}}, + 'class': 'org.chromium.TestA__sandbox_mode', + 'flags': 'enable_features=abc', + 'is_junit4': True, + 'method': 'testSimple'} + unqualified_class_test = { + 'class': test['class'].split('.')[-1], + 'method': test['method'] + } + self.assertEqual( + instrumentation_test_instance.GetTestNameWithoutParameterPostfix( + test, sep='.'), 'org.chromium.TestA') + self.assertEqual( + instrumentation_test_instance.GetTestNameWithoutParameterPostfix( + unqualified_class_test, sep='.'), 'TestA') + + def testGetTests_multipleAnnotationValuesRequested(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': {'Feature': {'value': ['Foo']}}, + 'class': 'org.chromium.test.SampleTest', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'Feature': {'value': ['Baz']}, + 'MediumTest': None, + }, + 'method': 'testMethod2', + }, + ], + }, + { + 'annotations': {'Feature': {'value': ['Bar']}}, + 'class': 'org.chromium.test.SampleTest2', + 'superclass': 'junit.framework.TestCase', + 'methods': [ + { + 'annotations': {'SmallTest': None}, + 'method': 'testMethod1', + }, + ], + } + ] + + expected_tests = [ + { + 'annotations': { + 'Feature': { + 'value': ['Baz'] + }, + 'MediumTest': None, + }, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod2', + }, + { + 'annotations': { + 'Feature': { + 'value': ['Bar'] + }, + 'SmallTest': None, + }, + 'class': 'org.chromium.test.SampleTest2', + 'is_junit4': True, + 'method': 'testMethod1', + }, + ] + + o._annotations = [('Feature', 'Bar'), ('Feature', 'Baz')] + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + + self.assertEqual(actual_tests, expected_tests) + + def testGenerateTestResults_noStatus(self): + results = instrumentation_test_instance.GenerateTestResults( + None, None, [], 1000, None, None) + 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, 1000, None, None) + 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, 1000, None, None) + 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, 1000, None, None) + 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, 1000, None, None) + self.assertEqual(1, len(results)) + self.assertEqual(base_test_result.ResultType.FAIL, results[0].GetType()) + + def testGenerateTestResults_testUnknownException(self): + stacktrace = 'long\nstacktrace' + statuses = [ + (1, { + 'class': 'test.package.TestClass', + 'test': 'testMethod', + }), + (-1, { + 'class': 'test.package.TestClass', + 'test': 'testMethod', + 'stack': stacktrace, + }), + ] + results = instrumentation_test_instance.GenerateTestResults( + None, None, statuses, 1000, None, None) + self.assertEqual(1, len(results)) + self.assertEqual(base_test_result.ResultType.FAIL, results[0].GetType()) + self.assertEqual(stacktrace, results[0].GetLog()) + + def testGenerateJUnitTestResults_testSkipped_true(self): + statuses = [ + (1, { + 'class': 'test.package.TestClass', + 'test': 'testMethod', + }), + (-3, { + 'class': 'test.package.TestClass', + 'test': 'testMethod', + }), + ] + results = instrumentation_test_instance.GenerateTestResults( + None, None, statuses, 1000, None, None) + self.assertEqual(1, len(results)) + self.assertEqual(base_test_result.ResultType.SKIP, results[0].GetType()) + + def testParameterizedCommandLineFlagsSwitches(self): + o = self.createTestInstance() + raw_tests = [{ + 'annotations': { + 'ParameterizedCommandLineFlags$Switches': { + 'value': ['enable-features=abc', 'enable-features=def'] + } + }, + 'class': + 'org.chromium.test.SampleTest', + 'superclass': + 'java.lang.Object', + 'methods': [ + { + 'annotations': { + 'SmallTest': None + }, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'MediumTest': None, + 'ParameterizedCommandLineFlags$Switches': { + 'value': ['enable-features=ghi', 'enable-features=jkl'] + }, + }, + 'method': 'testMethod2', + }, + { + 'annotations': { + 'MediumTest': None, + 'ParameterizedCommandLineFlags$Switches': { + 'value': [] + }, + }, + 'method': 'testMethod3', + }, + { + 'annotations': { + 'MediumTest': None, + 'SkipCommandLineParameterization': None, + }, + 'method': 'testMethod4', + }, + ], + }] + + expected_tests = [ + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': ['--enable-features=abc', '--enable-features=def'], + 'is_junit4': True, + 'method': 'testMethod1' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': ['--enable-features=ghi', '--enable-features=jkl'], + 'is_junit4': True, + 'method': 'testMethod2' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod3' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod4' + }, + ] + for i in range(4): + expected_tests[i]['annotations'].update(raw_tests[0]['annotations']) + expected_tests[i]['annotations'].update( + raw_tests[0]['methods'][i]['annotations']) + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + self.assertEqual(actual_tests, expected_tests) + + def testParameterizedCommandLineFlags(self): + o = self.createTestInstance() + raw_tests = [{ + 'annotations': { + 'ParameterizedCommandLineFlags': { + 'value': [ + { + 'ParameterizedCommandLineFlags$Switches': { + 'value': [ + 'enable-features=abc', + 'force-fieldtrials=trial/group' + ], + } + }, + { + 'ParameterizedCommandLineFlags$Switches': { + 'value': [ + 'enable-features=abc2', + 'force-fieldtrials=trial/group2' + ], + } + }, + ], + }, + }, + 'class': + 'org.chromium.test.SampleTest', + 'superclass': + 'java.lang.Object', + 'methods': [ + { + 'annotations': { + 'SmallTest': None + }, + 'method': 'testMethod1', + }, + { + 'annotations': { + 'MediumTest': None, + 'ParameterizedCommandLineFlags': { + 'value': [{ + 'ParameterizedCommandLineFlags$Switches': { + 'value': ['enable-features=def'] + } + }], + }, + }, + 'method': 'testMethod2', + }, + { + 'annotations': { + 'MediumTest': None, + 'ParameterizedCommandLineFlags': { + 'value': [], + }, + }, + 'method': 'testMethod3', + }, + { + 'annotations': { + 'MediumTest': None, + 'SkipCommandLineParameterization': None, + }, + 'method': 'testMethod4', + }, + ], + }] + + expected_tests = [ + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': + ['--enable-features=abc', '--force-fieldtrials=trial/group'], + 'is_junit4': True, + 'method': 'testMethod1' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': ['--enable-features=def'], + 'is_junit4': True, + 'method': 'testMethod2' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod3' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'is_junit4': True, + 'method': 'testMethod4' + }, + { + 'annotations': {}, + 'class': + 'org.chromium.test.SampleTest', + 'flags': [ + '--enable-features=abc2', + '--force-fieldtrials=trial/group2', + ], + 'is_junit4': + True, + 'method': + 'testMethod1' + }, + ] + for i in range(4): + expected_tests[i]['annotations'].update(raw_tests[0]['annotations']) + expected_tests[i]['annotations'].update( + raw_tests[0]['methods'][i]['annotations']) + expected_tests[4]['annotations'].update(raw_tests[0]['annotations']) + expected_tests[4]['annotations'].update( + raw_tests[0]['methods'][0]['annotations']) + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + self.assertEqual(actual_tests, expected_tests) + + def testDifferentCommandLineParameterizations(self): + o = self.createTestInstance() + raw_tests = [{ + 'annotations': {}, + 'class': + 'org.chromium.test.SampleTest', + 'superclass': + 'java.lang.Object', + 'methods': [ + { + 'annotations': { + 'SmallTest': None, + 'ParameterizedCommandLineFlags': { + 'value': [ + { + 'ParameterizedCommandLineFlags$Switches': { + 'value': ['a1', 'a2'], + } + }, + ], + }, + }, + 'method': 'testMethod2', + }, + { + 'annotations': { + 'SmallTest': None, + 'ParameterizedCommandLineFlags$Switches': { + 'value': ['b1', 'b2'], + }, + }, + 'method': 'testMethod3', + }, + ], + }] + + expected_tests = [ + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': ['--a1', '--a2'], + 'is_junit4': True, + 'method': 'testMethod2' + }, + { + 'annotations': {}, + 'class': 'org.chromium.test.SampleTest', + 'flags': ['--b1', '--b2'], + 'is_junit4': True, + 'method': 'testMethod3' + }, + ] + for i in range(2): + expected_tests[i]['annotations'].update( + raw_tests[0]['methods'][i]['annotations']) + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + actual_tests = o.ProcessRawTests(raw_tests) + self.assertEqual(actual_tests, expected_tests) + + def testMultipleCommandLineParameterizations_raises(self): + o = self.createTestInstance() + raw_tests = [ + { + 'annotations': { + 'ParameterizedCommandLineFlags': { + 'value': [ + { + 'ParameterizedCommandLineFlags$Switches': { + 'value': [ + 'enable-features=abc', + 'force-fieldtrials=trial/group', + ], + } + }, + ], + }, + }, + 'class': + 'org.chromium.test.SampleTest', + 'superclass': + 'java.lang.Object', + 'methods': [ + { + 'annotations': { + 'SmallTest': None, + 'ParameterizedCommandLineFlags$Switches': { + 'value': [ + 'enable-features=abc', + 'force-fieldtrials=trial/group', + ], + }, + }, + 'method': 'testMethod1', + }, + ], + }, + ] + + o._test_jar = 'path/to/test.jar' + o._junit4_runner_class = 'J4Runner' + self.assertRaises( + instrumentation_test_instance.CommandLineParameterizationException, + o.ProcessRawTests, [raw_tests[0]]) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/instrumentation/json_perf_parser.py b/third_party/libwebrtc/build/android/pylib/instrumentation/json_perf_parser.py new file mode 100644 index 0000000000..8f53a2ffb3 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/json_perf_parser.py @@ -0,0 +1,162 @@ +# 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 = [j for j in json_data if EntryFilter(j)] + + 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/third_party/libwebrtc/build/android/pylib/instrumentation/render_test.html.jinja b/third_party/libwebrtc/build/android/pylib/instrumentation/render_test.html.jinja new file mode 100644 index 0000000000..81b85b78e3 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/render_test.html.jinja @@ -0,0 +1,40 @@ + + + {{ test_name }} + + + + Link to Golden (in repo)
+ Download Failure Image (right click and 'Save link as') + + + + + + + + + + + + {% if golden_link %} + + + {% else %} + + {% endif %} + + +
FailureGoldenDiff
No Golden Image.
+ + diff --git a/third_party/libwebrtc/build/android/pylib/instrumentation/test_result.py b/third_party/libwebrtc/build/android/pylib/instrumentation/test_result.py new file mode 100644 index 0000000000..766dad8a5d --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/instrumentation/test_result.py @@ -0,0 +1,33 @@ +# 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, 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. + 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 + + def SetDuration(self, duration): + """Set the test duration.""" + self._duration = duration diff --git a/third_party/libwebrtc/build/android/pylib/junit/__init__.py b/third_party/libwebrtc/build/android/pylib/junit/__init__.py new file mode 100644 index 0000000000..4d6aabb953 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/junit/__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/third_party/libwebrtc/build/android/pylib/junit/junit_test_instance.py b/third_party/libwebrtc/build/android/pylib/junit/junit_test_instance.py new file mode 100644 index 0000000000..8a26f98b38 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/junit/junit_test_instance.py @@ -0,0 +1,76 @@ +# 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. + + +from pylib.base import test_instance +from pylib.utils import test_filter + + +class JunitTestInstance(test_instance.TestInstance): + + def __init__(self, args, _): + super(JunitTestInstance, self).__init__() + + self._coverage_dir = args.coverage_dir + self._debug_socket = args.debug_socket + self._coverage_on_the_fly = args.coverage_on_the_fly + self._package_filter = args.package_filter + self._resource_apk = args.resource_apk + self._robolectric_runtime_deps_dir = args.robolectric_runtime_deps_dir + self._runner_filter = args.runner_filter + self._shards = args.shards + self._test_filter = test_filter.InitializeFilterFromArgs(args) + self._test_suite = args.test_suite + + #override + def TestType(self): + return 'junit' + + #override + def SetUp(self): + pass + + #override + def TearDown(self): + pass + + @property + def coverage_dir(self): + return self._coverage_dir + + @property + def coverage_on_the_fly(self): + return self._coverage_on_the_fly + + @property + def debug_socket(self): + return self._debug_socket + + @property + def package_filter(self): + return self._package_filter + + @property + def resource_apk(self): + return self._resource_apk + + @property + def robolectric_runtime_deps_dir(self): + return self._robolectric_runtime_deps_dir + + @property + def runner_filter(self): + return self._runner_filter + + @property + def test_filter(self): + return self._test_filter + + @property + def shards(self): + return self._shards + + @property + def suite(self): + return self._test_suite diff --git a/third_party/libwebrtc/build/android/pylib/local/__init__.py b/third_party/libwebrtc/build/android/pylib/local/__init__.py new file mode 100644 index 0000000000..4d6aabb953 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/local/device/__init__.py b/third_party/libwebrtc/build/android/pylib/local/device/__init__.py new file mode 100644 index 0000000000..4d6aabb953 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/local/device/local_device_environment.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_environment.py new file mode 100644 index 0000000000..c254d2e8ca --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_environment.py @@ -0,0 +1,328 @@ +# 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 datetime +import functools +import logging +import os +import shutil +import tempfile +import threading + +import devil_chromium +from devil import base_error +from devil.android import device_denylist +from devil.android import device_errors +from devil.android import device_utils +from devil.android import logcat_monitor +from devil.android.sdk import adb_wrapper +from devil.utils import file_utils +from devil.utils import parallelizer +from pylib import constants +from pylib.constants import host_paths +from pylib.base import environment +from pylib.utils import instrumentation_tracing +from py_trace_event import trace_event + + +LOGCAT_FILTERS = [ + 'chromium:v', + 'cr_*:v', + 'DEBUG:I', + 'StrictMode:D', +] + + +def _DeviceCachePath(device): + file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() + return os.path.join(constants.GetOutDirectory(), file_name) + + +def handle_shard_failures(f): + """A decorator that handles device failures for per-device functions. + + Args: + f: the function being decorated. The function must take at least one + argument, and that argument must be the device. + """ + return handle_shard_failures_with(None)(f) + + +# TODO(jbudorick): Refactor this to work as a decorator or context manager. +def handle_shard_failures_with(on_failure): + """A decorator that handles device failures for per-device functions. + + This calls on_failure in the event of a failure. + + Args: + f: the function being decorated. The function must take at least one + argument, and that argument must be the device. + on_failure: A binary function to call on failure. + """ + def decorator(f): + @functools.wraps(f) + def wrapper(dev, *args, **kwargs): + try: + return f(dev, *args, **kwargs) + except device_errors.CommandTimeoutError: + logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev)) + except device_errors.DeviceUnreachableError: + logging.exception('Shard died: %s(%s)', f.__name__, str(dev)) + except base_error.BaseError: + logging.exception('Shard failed: %s(%s)', f.__name__, str(dev)) + except SystemExit: + logging.exception('Shard killed: %s(%s)', f.__name__, str(dev)) + raise + if on_failure: + on_failure(dev, f.__name__) + return None + + return wrapper + + return decorator + + +def place_nomedia_on_device(dev, device_root): + """Places .nomedia file in test data root. + + This helps to prevent system from scanning media files inside test data. + + Args: + dev: Device to place .nomedia file. + device_root: Base path on device to place .nomedia file. + """ + + dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True) + dev.WriteFile('%s/.nomedia' % device_root, 'https://crbug.com/796640') + + +class LocalDeviceEnvironment(environment.Environment): + + def __init__(self, args, output_manager, _error_func): + super(LocalDeviceEnvironment, self).__init__(output_manager) + self._current_try = 0 + self._denylist = (device_denylist.Denylist(args.denylist_file) + if args.denylist_file else None) + self._device_serials = args.test_devices + self._devices_lock = threading.Lock() + self._devices = None + self._concurrent_adb = args.enable_concurrent_adb + self._enable_device_cache = args.enable_device_cache + self._logcat_monitors = [] + self._logcat_output_dir = args.logcat_output_dir + self._logcat_output_file = args.logcat_output_file + self._max_tries = 1 + args.num_retries + self._preferred_abis = None + self._recover_devices = args.recover_devices + self._skip_clear_data = args.skip_clear_data + self._tool_name = args.tool + self._trace_output = None + if hasattr(args, 'trace_output'): + self._trace_output = args.trace_output + self._trace_all = None + if hasattr(args, 'trace_all'): + self._trace_all = args.trace_all + + devil_chromium.Initialize( + output_directory=constants.GetOutDirectory(), + adb_path=args.adb_path) + + # Some things such as Forwarder require ADB to be in the environment path, + # while others like Devil's bundletool.py require Java on the path. + adb_dir = os.path.dirname(adb_wrapper.AdbWrapper.GetAdbPath()) + if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep): + os.environ['PATH'] = os.pathsep.join( + [adb_dir, host_paths.JAVA_PATH, os.environ['PATH']]) + + #override + def SetUp(self): + if self.trace_output and self._trace_all: + to_include = [r"pylib\..*", r"devil\..*", "__main__"] + to_exclude = ["logging"] + instrumentation_tracing.start_instrumenting(self.trace_output, to_include, + to_exclude) + elif self.trace_output: + self.EnableTracing() + + # Must be called before accessing |devices|. + def SetPreferredAbis(self, abis): + assert self._devices is None + self._preferred_abis = abis + + def _InitDevices(self): + device_arg = [] + if self._device_serials: + device_arg = self._device_serials + + self._devices = device_utils.DeviceUtils.HealthyDevices( + self._denylist, + retries=5, + enable_usb_resets=True, + enable_device_files_cache=self._enable_device_cache, + default_retries=self._max_tries - 1, + device_arg=device_arg, + abis=self._preferred_abis) + + if self._logcat_output_file: + self._logcat_output_dir = tempfile.mkdtemp() + + @handle_shard_failures_with(on_failure=self.DenylistDevice) + def prepare_device(d): + d.WaitUntilFullyBooted() + + if self._enable_device_cache: + cache_path = _DeviceCachePath(d) + if os.path.exists(cache_path): + logging.info('Using device cache: %s', cache_path) + with open(cache_path) as f: + d.LoadCacheData(f.read()) + # Delete cached file so that any exceptions cause it to be cleared. + os.unlink(cache_path) + + if self._logcat_output_dir: + logcat_file = os.path.join( + self._logcat_output_dir, + '%s_%s' % (d.adb.GetDeviceSerial(), + datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%S'))) + monitor = logcat_monitor.LogcatMonitor( + d.adb, clear=True, output_file=logcat_file) + self._logcat_monitors.append(monitor) + monitor.Start() + + self.parallel_devices.pMap(prepare_device) + + @property + def current_try(self): + return self._current_try + + def IncrementCurrentTry(self): + self._current_try += 1 + + def ResetCurrentTry(self): + self._current_try = 0 + + @property + def denylist(self): + return self._denylist + + @property + def concurrent_adb(self): + return self._concurrent_adb + + @property + def devices(self): + # Initialize lazily so that host-only tests do not fail when no devices are + # attached. + if self._devices is None: + self._InitDevices() + return self._devices + + @property + def max_tries(self): + return self._max_tries + + @property + def parallel_devices(self): + return parallelizer.SyncParallelizer(self.devices) + + @property + def recover_devices(self): + return self._recover_devices + + @property + def skip_clear_data(self): + return self._skip_clear_data + + @property + def tool(self): + return self._tool_name + + @property + def trace_output(self): + return self._trace_output + + #override + def TearDown(self): + if self.trace_output and self._trace_all: + instrumentation_tracing.stop_instrumenting() + elif self.trace_output: + self.DisableTracing() + + # By default, teardown will invoke ADB. When receiving SIGTERM due to a + # timeout, there's a high probability that ADB is non-responsive. In these + # cases, sending an ADB command will potentially take a long time to time + # out. Before this happens, the process will be hard-killed for not + # responding to SIGTERM fast enough. + if self._received_sigterm: + return + + if not self._devices: + return + + @handle_shard_failures_with(on_failure=self.DenylistDevice) + def tear_down_device(d): + # Write the cache even when not using it so that it will be ready the + # first time that it is enabled. Writing it every time is also necessary + # so that an invalid cache can be flushed just by disabling it for one + # run. + cache_path = _DeviceCachePath(d) + if os.path.exists(os.path.dirname(cache_path)): + with open(cache_path, 'w') as f: + f.write(d.DumpCacheData()) + logging.info('Wrote device cache: %s', cache_path) + else: + logging.warning( + 'Unable to write device cache as %s directory does not exist', + os.path.dirname(cache_path)) + + self.parallel_devices.pMap(tear_down_device) + + for m in self._logcat_monitors: + try: + m.Stop() + m.Close() + _, temp_path = tempfile.mkstemp() + with open(m.output_file, 'r') as infile: + with open(temp_path, 'w') as outfile: + for line in infile: + outfile.write('Device(%s) %s' % (m.adb.GetDeviceSerial(), line)) + shutil.move(temp_path, m.output_file) + except base_error.BaseError: + logging.exception('Failed to stop logcat monitor for %s', + m.adb.GetDeviceSerial()) + except IOError: + logging.exception('Failed to locate logcat for device %s', + m.adb.GetDeviceSerial()) + + if self._logcat_output_file: + file_utils.MergeFiles( + self._logcat_output_file, + [m.output_file for m in self._logcat_monitors + if os.path.exists(m.output_file)]) + shutil.rmtree(self._logcat_output_dir) + + def DenylistDevice(self, device, reason='local_device_failure'): + device_serial = device.adb.GetDeviceSerial() + if self._denylist: + self._denylist.Extend([device_serial], reason=reason) + with self._devices_lock: + self._devices = [d for d in self._devices if str(d) != device_serial] + logging.error('Device %s denylisted: %s', device_serial, reason) + if not self._devices: + raise device_errors.NoDevicesError( + 'All devices were denylisted due to errors') + + @staticmethod + def DisableTracing(): + if not trace_event.trace_is_enabled(): + logging.warning('Tracing is not running.') + else: + trace_event.trace_disable() + + def EnableTracing(self): + if trace_event.trace_is_enabled(): + logging.warning('Tracing is already running.') + else: + trace_event.trace_enable(self._trace_output) diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run.py new file mode 100644 index 0000000000..c81722da6e --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run.py @@ -0,0 +1,896 @@ +# 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 collections +import itertools +import logging +import math +import os +import posixpath +import subprocess +import shutil +import time + +from six.moves import range # pylint: disable=redefined-builtin +from devil import base_error +from devil.android import crash_handler +from devil.android import device_errors +from devil.android import device_temp_file +from devil.android import logcat_monitor +from devil.android import ports +from devil.android.sdk import version_codes +from devil.utils import reraiser_thread +from incremental_install import installer +from pylib import constants +from pylib.base import base_test_result +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 google_storage_helper +from pylib.utils import logdog_helper +from py_trace_event import trace_event +from py_utils import contextlib_ext +from py_utils import tempfile_ext +import tombstones + +_MAX_INLINE_FLAGS_LENGTH = 50 # Arbitrarily chosen. +_EXTRA_COMMAND_LINE_FILE = ( + 'org.chromium.native_test.NativeTest.CommandLineFile') +_EXTRA_COMMAND_LINE_FLAGS = ( + 'org.chromium.native_test.NativeTest.CommandLineFlags') +_EXTRA_COVERAGE_DEVICE_FILE = ( + 'org.chromium.native_test.NativeTest.CoverageDeviceFile') +_EXTRA_STDOUT_FILE = ( + 'org.chromium.native_test.NativeTestInstrumentationTestRunner' + '.StdoutFile') +_EXTRA_TEST = ( + 'org.chromium.native_test.NativeTestInstrumentationTestRunner' + '.Test') +_EXTRA_TEST_LIST = ( + 'org.chromium.native_test.NativeTestInstrumentationTestRunner' + '.TestList') + +_SECONDS_TO_NANOS = int(1e9) + +# Tests that use SpawnedTestServer must run the LocalTestServerSpawner on the +# host machine. +# 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', 'services_unittests', 'unit_tests' +] + +# These are use for code coverage. +_LLVM_PROFDATA_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', + 'llvm-build', 'Release+Asserts', 'bin', + 'llvm-profdata') +# Name of the file extension for profraw data files. +_PROFRAW_FILE_EXTENSION = 'profraw' +# Name of the file where profraw data files are merged. +_MERGE_PROFDATA_FILE_NAME = 'coverage_merged.' + _PROFRAW_FILE_EXTENSION + +# No-op context manager. If we used Python 3, we could change this to +# contextlib.ExitStack() +class _NullContextManager(object): + def __enter__(self): + pass + def __exit__(self, *args): + pass + + +def _GenerateSequentialFileNames(filename): + """Infinite generator of names: 'name.ext', 'name_1.ext', 'name_2.ext', ...""" + yield filename + base, ext = os.path.splitext(filename) + for i in itertools.count(1): + yield '%s_%d%s' % (base, i, ext) + + +def _ExtractTestsFromFilter(gtest_filter): + """Returns the list of tests specified by the given filter. + + Returns: + None if the device should be queried for the test list instead. + """ + # Empty means all tests, - means exclude filter. + if not gtest_filter or '-' in gtest_filter: + return None + + patterns = gtest_filter.split(':') + # For a single pattern, allow it even if it has a wildcard so long as the + # wildcard comes at the end and there is at least one . to prove the scope is + # not too large. + # This heuristic is not necessarily faster, but normally is. + if len(patterns) == 1 and patterns[0].endswith('*'): + no_suffix = patterns[0].rstrip('*') + if '*' not in no_suffix and '.' in no_suffix: + return patterns + + if '*' in gtest_filter: + return None + return patterns + + +def _GetDeviceTimeoutMultiplier(): + # Emulated devices typically run 20-150x slower than real-time. + # Give a way to control this through the DEVICE_TIMEOUT_MULTIPLIER + # environment variable. + multiplier = os.getenv("DEVICE_TIMEOUT_MULTIPLIER") + if multiplier: + return int(multiplier) + return 1 + + +def _MergeCoverageFiles(coverage_dir, profdata_dir): + """Merge coverage data files. + + Each instrumentation activity generates a separate profraw data file. This + merges all profraw files in profdata_dir into a single file in + coverage_dir. This happens after each test, rather than waiting until after + all tests are ran to reduce the memory footprint used by all the profraw + files. + + Args: + coverage_dir: The path to the coverage directory. + profdata_dir: The directory where the profraw data file(s) are located. + + Return: + None + """ + # profdata_dir may not exist if pulling coverage files failed. + if not os.path.exists(profdata_dir): + logging.debug('Profraw directory does not exist.') + return + + merge_file = os.path.join(coverage_dir, _MERGE_PROFDATA_FILE_NAME) + profraw_files = [ + os.path.join(profdata_dir, f) for f in os.listdir(profdata_dir) + if f.endswith(_PROFRAW_FILE_EXTENSION) + ] + + try: + logging.debug('Merging target profraw files into merged profraw file.') + subprocess_cmd = [ + _LLVM_PROFDATA_PATH, + 'merge', + '-o', + merge_file, + '-sparse=true', + ] + # Grow the merge file by merging it with itself and the new files. + if os.path.exists(merge_file): + subprocess_cmd.append(merge_file) + subprocess_cmd.extend(profraw_files) + output = subprocess.check_output(subprocess_cmd) + logging.debug('Merge output: %s', output) + except subprocess.CalledProcessError: + # Don't raise error as that will kill the test run. When code coverage + # generates a report, that will raise the error in the report generation. + logging.error( + 'Failed to merge target profdata files to create merged profraw file.') + + # Free up memory space on bot as all data is in the merge file. + for f in profraw_files: + os.remove(f) + + +def _PullCoverageFiles(device, device_coverage_dir, output_dir): + """Pulls coverage files on device to host directory. + + Args: + device: The working device. + device_coverage_dir: The directory to store coverage data on device. + output_dir: The output directory on host. + """ + try: + if not os.path.exists(output_dir): + os.makedirs(output_dir) + device.PullFile(device_coverage_dir, output_dir) + if not os.listdir(os.path.join(output_dir, 'profraw')): + logging.warning('No coverage data was generated for this run') + except (OSError, base_error.BaseError) as e: + logging.warning('Failed to handle coverage data after tests: %s', e) + finally: + device.RemovePath(device_coverage_dir, force=True, recursive=True) + + +def _GetDeviceCoverageDir(device): + """Gets the directory to generate coverage data on device. + + Args: + device: The working device. + + Returns: + The directory path on the device. + """ + return posixpath.join(device.GetExternalStoragePath(), 'chrome', 'test', + 'coverage', 'profraw') + + +def _GetLLVMProfilePath(device_coverage_dir, suite, coverage_index): + """Gets 'LLVM_PROFILE_FILE' environment variable path. + + Dumping data to ONLY 1 file may cause warning and data overwrite in + browsertests, so that pattern "%2m" is used to expand to 2 raw profiles + at runtime. + + Args: + device_coverage_dir: The directory to generate data on device. + suite: Test suite name. + coverage_index: The incremental index for this test suite. + + Returns: + The path pattern for environment variable 'LLVM_PROFILE_FILE'. + """ + return posixpath.join(device_coverage_dir, + '_'.join([suite, + str(coverage_index), '%2m.profraw'])) + + +class _ApkDelegate(object): + def __init__(self, test_instance, tool): + self._activity = test_instance.activity + self._apk_helper = test_instance.apk_helper + self._test_apk_incremental_install_json = ( + test_instance.test_apk_incremental_install_json) + self._package = test_instance.package + self._runner = test_instance.runner + self._permissions = test_instance.permissions + self._suite = test_instance.suite + self._component = '%s/%s' % (self._package, self._runner) + self._extras = test_instance.extras + self._wait_for_java_debugger = test_instance.wait_for_java_debugger + self._tool = tool + self._coverage_dir = test_instance.coverage_dir + self._coverage_index = 0 + self._use_existing_test_data = test_instance.use_existing_test_data + + def GetTestDataRoot(self, device): + # pylint: disable=no-self-use + return posixpath.join(device.GetExternalStoragePath(), + 'chromium_tests_root') + + def Install(self, device): + if self._use_existing_test_data: + return + if self._test_apk_incremental_install_json: + installer.Install(device, self._test_apk_incremental_install_json, + apk=self._apk_helper, permissions=self._permissions) + else: + device.Install( + self._apk_helper, + allow_downgrade=True, + reinstall=True, + permissions=self._permissions) + + def ResultsDirectory(self, device): + return device.GetApplicationDataDirectory(self._package) + + def Run(self, test, device, flags=None, **kwargs): + extras = dict(self._extras) + device_api = device.build_version_sdk + + if self._coverage_dir and device_api >= version_codes.LOLLIPOP: + device_coverage_dir = _GetDeviceCoverageDir(device) + extras[_EXTRA_COVERAGE_DEVICE_FILE] = _GetLLVMProfilePath( + device_coverage_dir, self._suite, self._coverage_index) + self._coverage_index += 1 + + if ('timeout' in kwargs + and gtest_test_instance.EXTRA_SHARD_NANO_TIMEOUT not in extras): + # Make sure the instrumentation doesn't kill the test before the + # scripts do. The provided timeout value is in seconds, but the + # instrumentation deals with nanoseconds because that's how Android + # handles time. + extras[gtest_test_instance.EXTRA_SHARD_NANO_TIMEOUT] = int( + kwargs['timeout'] * _SECONDS_TO_NANOS) + + # pylint: disable=redefined-variable-type + command_line_file = _NullContextManager() + if flags: + if len(flags) > _MAX_INLINE_FLAGS_LENGTH: + command_line_file = device_temp_file.DeviceTempFile(device.adb) + device.WriteFile(command_line_file.name, '_ %s' % flags) + extras[_EXTRA_COMMAND_LINE_FILE] = command_line_file.name + else: + extras[_EXTRA_COMMAND_LINE_FLAGS] = flags + + test_list_file = _NullContextManager() + if test: + if len(test) > 1: + test_list_file = device_temp_file.DeviceTempFile(device.adb) + device.WriteFile(test_list_file.name, '\n'.join(test)) + extras[_EXTRA_TEST_LIST] = test_list_file.name + else: + extras[_EXTRA_TEST] = test[0] + # pylint: enable=redefined-variable-type + + # We need to use GetAppWritablePath here instead of GetExternalStoragePath + # since we will not have yet applied legacy storage permission workarounds + # on R+. + stdout_file = device_temp_file.DeviceTempFile( + device.adb, dir=device.GetAppWritablePath(), suffix='.gtest_out') + extras[_EXTRA_STDOUT_FILE] = stdout_file.name + + if self._wait_for_java_debugger: + cmd = ['am', 'set-debug-app', '-w', self._package] + device.RunShellCommand(cmd, check_return=True) + logging.warning('*' * 80) + logging.warning('Waiting for debugger to attach to process: %s', + self._package) + logging.warning('*' * 80) + + with command_line_file, test_list_file, stdout_file: + try: + device.StartInstrumentation( + self._component, extras=extras, raw=False, **kwargs) + except device_errors.CommandFailedError: + logging.exception('gtest shard failed.') + except device_errors.CommandTimeoutError: + logging.exception('gtest shard timed out.') + except device_errors.DeviceUnreachableError: + logging.exception('gtest shard device unreachable.') + except Exception: + device.ForceStop(self._package) + raise + finally: + if self._coverage_dir and device_api >= version_codes.LOLLIPOP: + if not os.path.isdir(self._coverage_dir): + os.makedirs(self._coverage_dir) + # TODO(crbug.com/1179004) Use _MergeCoverageFiles when llvm-profdata + # not found is fixed. + _PullCoverageFiles( + device, device_coverage_dir, + os.path.join(self._coverage_dir, str(self._coverage_index))) + + return device.ReadFile(stdout_file.name).splitlines() + + def PullAppFiles(self, device, files, directory): + device_dir = device.GetApplicationDataDirectory(self._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)) + for host_file in _GenerateSequentialFileNames(host_file): + if not os.path.exists(host_file): + break + device.PullFile(device_file, host_file) + + def Clear(self, device): + device.ClearApplicationState(self._package, permissions=self._permissions) + + +class _ExeDelegate(object): + + def __init__(self, tr, test_instance, tool): + self._host_dist_dir = test_instance.exe_dist_dir + self._exe_file_name = os.path.basename( + test_instance.exe_dist_dir)[:-len('__dist')] + self._device_dist_dir = posixpath.join( + constants.TEST_EXECUTABLE_DIR, + os.path.basename(test_instance.exe_dist_dir)) + self._test_run = tr + self._tool = tool + self._suite = test_instance.suite + self._coverage_dir = test_instance.coverage_dir + self._coverage_index = 0 + + def GetTestDataRoot(self, device): + # pylint: disable=no-self-use + # pylint: disable=unused-argument + return posixpath.join(constants.TEST_EXECUTABLE_DIR, 'chromium_tests_root') + + def Install(self, device): + # TODO(jbudorick): Look into merging this with normal data deps pushing if + # executables become supported on nonlocal environments. + device.PushChangedFiles([(self._host_dist_dir, self._device_dist_dir)], + delete_device_stale=True) + + def ResultsDirectory(self, device): + # pylint: disable=no-self-use + # pylint: disable=unused-argument + return constants.TEST_EXECUTABLE_DIR + + def Run(self, test, device, flags=None, **kwargs): + tool = self._test_run.GetTool(device).GetTestWrapper() + if tool: + cmd = [tool] + else: + cmd = [] + cmd.append(posixpath.join(self._device_dist_dir, self._exe_file_name)) + + if test: + cmd.append('--gtest_filter=%s' % ':'.join(test)) + if flags: + # TODO(agrieve): This won't work if multiple flags are passed. + cmd.append(flags) + cwd = constants.TEST_EXECUTABLE_DIR + + env = { + 'LD_LIBRARY_PATH': self._device_dist_dir + } + + if self._coverage_dir: + device_coverage_dir = _GetDeviceCoverageDir(device) + env['LLVM_PROFILE_FILE'] = _GetLLVMProfilePath( + device_coverage_dir, self._suite, self._coverage_index) + self._coverage_index += 1 + + if self._tool != 'asan': + env['UBSAN_OPTIONS'] = constants.UBSAN_OPTIONS + + 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 + + # Executable tests return a nonzero exit code on test failure, which is + # fine from the test runner's perspective; thus check_return=False. + output = device.RunShellCommand( + cmd, cwd=cwd, env=env, check_return=False, large_output=True, **kwargs) + + if self._coverage_dir: + _PullCoverageFiles( + device, device_coverage_dir, + os.path.join(self._coverage_dir, str(self._coverage_index))) + + return output + + def PullAppFiles(self, device, files, directory): + pass + + def Clear(self, device): + device.KillAll(self._exe_file_name, + blocking=True, + timeout=30 * _GetDeviceTimeoutMultiplier(), + 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_helper: + self._installed_packages = [ + self._test_instance.apk_helper.GetPackageName() + ] + + # pylint: disable=redefined-variable-type + if self._test_instance.apk: + self._delegate = _ApkDelegate(self._test_instance, env.tool) + elif self._test_instance.exe_dist_dir: + self._delegate = _ExeDelegate(self, self._test_instance, self._env.tool) + if self._test_instance.isolated_script_test_perf_output: + self._test_perf_output_filenames = _GenerateSequentialFileNames( + self._test_instance.isolated_script_test_perf_output) + else: + self._test_perf_output_filenames = itertools.repeat(None) + # pylint: enable=redefined-variable-type + self._crashes = set() + self._servers = collections.defaultdict(list) + + #override + def TestPackage(self): + return self._test_instance.suite + + #override + def SetUp(self): + @local_device_environment.handle_shard_failures_with( + on_failure=self._env.DenylistDevice) + @trace_event.traced + def individual_device_set_up(device, host_device_tuples): + def install_apk(dev): + # Install test APK. + self._delegate.Install(dev) + + def push_test_data(dev): + if self._test_instance.use_existing_test_data: + return + # Push data dependencies. + device_root = self._delegate.GetTestDataRoot(dev) + host_device_tuples_substituted = [ + (h, local_device_test_run.SubstituteDeviceRoot(d, device_root)) + for h, d in host_device_tuples] + local_device_environment.place_nomedia_on_device(dev, device_root) + dev.PushChangedFiles( + host_device_tuples_substituted, + delete_device_stale=True, + # Some gtest suites, e.g. unit_tests, have data dependencies that + # can take longer than the default timeout to push. See + # crbug.com/791632 for context. + timeout=600 * math.ceil(_GetDeviceTimeoutMultiplier() / 10)) + if not host_device_tuples: + dev.RemovePath(device_root, force=True, recursive=True, rename=True) + dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True) + + def init_tool_and_start_servers(dev): + tool = self.GetTool(dev) + tool.CopyFiles(dev) + tool.SetupEnvironment() + + try: + # See https://crbug.com/1030827. + # This is a hack that may break in the future. We're relying on the + # fact that adb doesn't use ipv6 for it's server, and so doesn't + # listen on ipv6, but ssh remote forwarding does. 5037 is the port + # number adb uses for its server. + if "[::1]:5037" in subprocess.check_output( + "ss -o state listening 'sport = 5037'", shell=True): + logging.error( + 'Test Server cannot be started with a remote-forwarded adb ' + 'server. Continuing anyways, but some tests may fail.') + return + except subprocess.CalledProcessError: + pass + + 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, tool)) + + for s in self._servers[str(dev)]: + s.SetUp() + + def bind_crash_handler(step, dev): + return lambda: crash_handler.RetryOnSystemCrash(step, dev) + + # Explicitly enable root to ensure that tests run under deterministic + # conditions. Without this explicit call, EnableRoot() is called from + # push_test_data() when PushChangedFiles() determines that it should use + # _PushChangedFilesZipped(), which is only most of the time. + # Root is required (amongst maybe other reasons) to pull the results file + # from the device, since it lives within the application's data directory + # (via GetApplicationDataDirectory()). + device.EnableRoot() + + steps = [ + bind_crash_handler(s, device) + for s in (install_apk, push_test_data, init_tool_and_start_servers)] + if self._env.concurrent_adb: + reraiser_thread.RunAsync(steps) + else: + for step in steps: + step() + + self._env.parallel_devices.pMap( + individual_device_set_up, + self._test_instance.GetDataDependencies()) + + #override + def _ShouldShard(self): + return True + + #override + def _CreateShards(self, tests): + # _crashes are tests that might crash and make the tests in the same shard + # following the crashed testcase not run. + # Thus we need to create separate shards for each crashed testcase, + # so that other tests can be run. + device_count = len(self._env.devices) + shards = [] + + # Add shards with only one suspect testcase. + shards += [[crash] for crash in self._crashes if crash in tests] + + # Delete suspect testcase from tests. + tests = [test for test in tests if not test in self._crashes] + + max_shard_size = self._test_instance.test_launcher_batch_limit + + shards.extend(self._PartitionTests(tests, device_count, max_shard_size)) + return shards + + #override + def _GetTests(self): + if self._test_instance.extract_test_list_from_filter: + # When the exact list of tests to run is given via command-line (e.g. when + # locally iterating on a specific test), skip querying the device (which + # takes ~3 seconds). + tests = _ExtractTestsFromFilter(self._test_instance.gtest_filter) + if tests: + return tests + + # Even when there's only one device, it still makes sense to retrieve the + # test list so that tests can be split up and run in batches rather than all + # at once (since test output is not streamed). + @local_device_environment.handle_shard_failures_with( + on_failure=self._env.DenylistDevice) + def list_tests(dev): + timeout = 30 * _GetDeviceTimeoutMultiplier() + retries = 1 + if self._test_instance.wait_for_java_debugger: + timeout = None + + flags = [ + f for f in self._test_instance.flags + if f not in ['--wait-for-debugger', '--wait-for-java-debugger'] + ] + flags.append('--gtest_list_tests') + + # TODO(crbug.com/726880): Remove retries when no longer necessary. + for i in range(0, retries+1): + logging.info('flags:') + for f in flags: + logging.info(' %s', f) + + with self._ArchiveLogcat(dev, 'list_tests'): + raw_test_list = crash_handler.RetryOnSystemCrash( + lambda d: self._delegate.Run( + None, d, flags=' '.join(flags), timeout=timeout), + device=dev) + + tests = gtest_test_instance.ParseGTestListTests(raw_test_list) + if not tests: + logging.info('No tests found. Output:') + for l in raw_test_list: + logging.info(' %s', l) + if i < retries: + logging.info('Retrying...') + else: + break + return tests + + # Query all devices in case one fails. + test_lists = self._env.parallel_devices.pMap(list_tests).pGet(None) + + # If all devices failed to list tests, raise an exception. + # Check that tl is not None and is not empty. + if all(not tl for tl in test_lists): + raise device_errors.CommandFailedError( + 'Failed to list tests on any device') + tests = list(sorted(set().union(*[set(tl) for tl in test_lists if tl]))) + tests = self._test_instance.FilterTests(tests) + tests = self._ApplyExternalSharding( + tests, self._test_instance.external_shard_index, + self._test_instance.total_external_shards) + return tests + + def _UploadTestArtifacts(self, device, test_artifacts_dir): + # TODO(jbudorick): Reconcile this with the output manager once + # https://codereview.chromium.org/2933993002/ lands. + if test_artifacts_dir: + with tempfile_ext.NamedTemporaryDirectory() as test_artifacts_host_dir: + device.PullFile(test_artifacts_dir.name, test_artifacts_host_dir) + with tempfile_ext.NamedTemporaryDirectory() as temp_zip_dir: + zip_base_name = os.path.join(temp_zip_dir, 'test_artifacts') + test_artifacts_zip = shutil.make_archive( + zip_base_name, 'zip', test_artifacts_host_dir) + link = google_storage_helper.upload( + google_storage_helper.unique_name( + 'test_artifacts', device=device), + test_artifacts_zip, + bucket='%s/test_artifacts' % ( + self._test_instance.gs_test_artifacts_bucket)) + logging.info('Uploading test artifacts to %s.', link) + return link + return None + + def _PullRenderTestOutput(self, device, render_test_output_device_dir): + # We pull the render tests into a temp directory then copy them over + # individually. Otherwise we end up with a temporary directory name + # in the host output directory. + with tempfile_ext.NamedTemporaryDirectory() as tmp_host_dir: + try: + device.PullFile(render_test_output_device_dir, tmp_host_dir) + except device_errors.CommandFailedError: + logging.exception('Failed to pull render test output dir %s', + render_test_output_device_dir) + temp_host_dir = os.path.join( + tmp_host_dir, os.path.basename(render_test_output_device_dir)) + for output_file in os.listdir(temp_host_dir): + src_path = os.path.join(temp_host_dir, output_file) + dst_path = os.path.join(self._test_instance.render_test_output_dir, + output_file) + shutil.move(src_path, dst_path) + + @contextlib.contextmanager + def _ArchiveLogcat(self, device, test): + if isinstance(test, str): + desc = test + else: + desc = hash(tuple(test)) + + stream_name = 'logcat_%s_shard%s_%s_%s' % ( + desc, self._test_instance.external_shard_index, + time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial) + + logcat_file = None + logmon = None + try: + with self._env.output_manager.ArchivedTempfile(stream_name, + 'logcat') as logcat_file: + with logcat_monitor.LogcatMonitor( + device.adb, + filter_specs=local_device_environment.LOGCAT_FILTERS, + output_file=logcat_file.name, + check_error=False) as logmon: + with contextlib_ext.Optional(trace_event.trace(str(test)), + self._env.trace_output): + yield logcat_file + finally: + if logmon: + logmon.Close() + if logcat_file and logcat_file.Link(): + logging.info('Logcat saved to %s', logcat_file.Link()) + + #override + def _RunTest(self, device, test): + # Run the test. + timeout = (self._test_instance.shard_timeout * + self.GetTool(device).GetTimeoutScale() * + _GetDeviceTimeoutMultiplier()) + if self._test_instance.wait_for_java_debugger: + timeout = None + if self._test_instance.store_tombstones: + tombstones.ClearAllTombstones(device) + test_perf_output_filename = next(self._test_perf_output_filenames) + + if self._test_instance.isolated_script_test_output: + suffix = '.json' + else: + suffix = '.xml' + + with device_temp_file.DeviceTempFile( + adb=device.adb, + dir=self._delegate.ResultsDirectory(device), + suffix=suffix) as device_tmp_results_file: + with contextlib_ext.Optional( + device_temp_file.NamedDeviceTemporaryDirectory( + adb=device.adb, dir='/sdcard/'), + self._test_instance.gs_test_artifacts_bucket) as test_artifacts_dir: + with (contextlib_ext.Optional( + device_temp_file.DeviceTempFile( + adb=device.adb, dir=self._delegate.ResultsDirectory(device)), + test_perf_output_filename)) as isolated_script_test_perf_output: + with contextlib_ext.Optional( + device_temp_file.NamedDeviceTemporaryDirectory(adb=device.adb, + dir='/sdcard/'), + self._test_instance.render_test_output_dir + ) as render_test_output_dir: + + flags = list(self._test_instance.flags) + if self._test_instance.enable_xml_result_parsing: + flags.append('--gtest_output=xml:%s' % + device_tmp_results_file.name) + + if self._test_instance.gs_test_artifacts_bucket: + flags.append('--test_artifacts_dir=%s' % test_artifacts_dir.name) + + if self._test_instance.isolated_script_test_output: + flags.append('--isolated-script-test-output=%s' % + device_tmp_results_file.name) + + if test_perf_output_filename: + flags.append('--isolated_script_test_perf_output=%s' % + isolated_script_test_perf_output.name) + + if self._test_instance.render_test_output_dir: + flags.append('--render-test-output-dir=%s' % + render_test_output_dir.name) + + logging.info('flags:') + for f in flags: + logging.info(' %s', f) + + with self._ArchiveLogcat(device, test) as logcat_file: + output = self._delegate.Run(test, + device, + flags=' '.join(flags), + timeout=timeout, + retries=0) + + if self._test_instance.enable_xml_result_parsing: + try: + gtest_xml = device.ReadFile(device_tmp_results_file.name) + except device_errors.CommandFailedError: + logging.exception('Failed to pull gtest results XML file %s', + device_tmp_results_file.name) + gtest_xml = None + + if self._test_instance.isolated_script_test_output: + try: + gtest_json = device.ReadFile(device_tmp_results_file.name) + except device_errors.CommandFailedError: + logging.exception('Failed to pull gtest results JSON file %s', + device_tmp_results_file.name) + gtest_json = None + + if test_perf_output_filename: + try: + device.PullFile(isolated_script_test_perf_output.name, + test_perf_output_filename) + except device_errors.CommandFailedError: + logging.exception('Failed to pull chartjson results %s', + isolated_script_test_perf_output.name) + + test_artifacts_url = self._UploadTestArtifacts( + device, test_artifacts_dir) + + if render_test_output_dir: + self._PullRenderTestOutput(device, render_test_output_dir.name) + + 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) + if not self._env.skip_clear_data: + self._delegate.Clear(device) + + for l in output: + logging.info(l) + + # Parse the output. + # TODO(jbudorick): Transition test scripts away from parsing stdout. + if self._test_instance.enable_xml_result_parsing: + results = gtest_test_instance.ParseGTestXML(gtest_xml) + elif self._test_instance.isolated_script_test_output: + results = gtest_test_instance.ParseGTestJSON(gtest_json) + else: + results = gtest_test_instance.ParseGTestOutput( + output, self._test_instance.symbolizer, device.product_cpu_abi) + + tombstones_url = None + for r in results: + if logcat_file: + r.SetLink('logcat', logcat_file.Link()) + + if self._test_instance.gs_test_artifacts_bucket: + r.SetLink('test_artifacts', test_artifacts_url) + + if r.GetType() == base_test_result.ResultType.CRASH: + self._crashes.add(r.GetName()) + if self._test_instance.store_tombstones: + if not tombstones_url: + resolved_tombstones = tombstones.ResolveTombstones( + device, + resolve_all_tombstones=True, + include_stack_symbols=False, + wipe_tombstones=True) + stream_name = 'tombstones_%s_%s' % ( + time.strftime('%Y%m%dT%H%M%S', time.localtime()), + device.serial) + tombstones_url = logdog_helper.text( + stream_name, '\n'.join(resolved_tombstones)) + r.SetLink('tombstones', tombstones_url) + + tests_stripped_disabled_prefix = set() + for t in test: + tests_stripped_disabled_prefix.add( + gtest_test_instance.TestNameWithoutDisabledPrefix(t)) + not_run_tests = tests_stripped_disabled_prefix.difference( + set(r.GetName() for r in results)) + return results, list(not_run_tests) if results else None + + #override + def TearDown(self): + # By default, teardown will invoke ADB. When receiving SIGTERM due to a + # timeout, there's a high probability that ADB is non-responsive. In these + # cases, sending an ADB command will potentially take a long time to time + # out. Before this happens, the process will be hard-killed for not + # responding to SIGTERM fast enough. + if self._received_sigterm: + return + + @local_device_environment.handle_shard_failures + @trace_event.traced + def individual_device_tear_down(dev): + for s in self._servers.get(str(dev), []): + s.TearDown() + + tool = self.GetTool(dev) + tool.CleanUpEnvironment() + + self._env.parallel_devices.pMap(individual_device_tear_down) diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run_test.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run_test.py new file mode 100755 index 0000000000..b664d58131 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_gtest_run_test.py @@ -0,0 +1,79 @@ +#!/usr/bin/env vpython3 +# Copyright 2021 The Chromium 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 local_device_gtest_test_run.""" + +# pylint: disable=protected-access + + +import os +import tempfile +import unittest + +from pylib.gtest import gtest_test_instance +from pylib.local.device import local_device_environment +from pylib.local.device import local_device_gtest_run +from py_utils import tempfile_ext + +import mock # pylint: disable=import-error + + +class LocalDeviceGtestRunTest(unittest.TestCase): + def setUp(self): + self._obj = local_device_gtest_run.LocalDeviceGtestRun( + mock.MagicMock(spec=local_device_environment.LocalDeviceEnvironment), + mock.MagicMock(spec=gtest_test_instance.GtestTestInstance)) + + def testExtractTestsFromFilter(self): + # Checks splitting by colons. + self.assertEqual([ + 'b17', + 'm4e3', + 'p51', + ], local_device_gtest_run._ExtractTestsFromFilter('b17:m4e3:p51')) + # Checks the '-' sign. + self.assertIsNone(local_device_gtest_run._ExtractTestsFromFilter('-mk2')) + # Checks the more than one asterick. + self.assertIsNone( + local_device_gtest_run._ExtractTestsFromFilter('.mk2*:.M67*')) + # Checks just an asterick without a period + self.assertIsNone(local_device_gtest_run._ExtractTestsFromFilter('M67*')) + # Checks an asterick at the end with a period. + self.assertEqual(['.M67*'], + local_device_gtest_run._ExtractTestsFromFilter('.M67*')) + + def testGetLLVMProfilePath(self): + path = local_device_gtest_run._GetLLVMProfilePath('test_dir', 'sr71', '5') + self.assertEqual(path, os.path.join('test_dir', 'sr71_5_%2m.profraw')) + + @mock.patch('subprocess.check_output') + def testMergeCoverageFiles(self, mock_sub): + with tempfile_ext.NamedTemporaryDirectory() as cov_tempd: + pro_tempd = os.path.join(cov_tempd, 'profraw') + os.mkdir(pro_tempd) + profdata = tempfile.NamedTemporaryFile( + dir=pro_tempd, + delete=False, + suffix=local_device_gtest_run._PROFRAW_FILE_EXTENSION) + local_device_gtest_run._MergeCoverageFiles(cov_tempd, pro_tempd) + # Merged file should be deleted. + self.assertFalse(os.path.exists(profdata.name)) + self.assertTrue(mock_sub.called) + + @mock.patch('pylib.utils.google_storage_helper.upload') + def testUploadTestArtifacts(self, mock_gsh): + link = self._obj._UploadTestArtifacts(mock.MagicMock(), None) + self.assertFalse(mock_gsh.called) + self.assertIsNone(link) + + result = 'A/10/warthog/path' + mock_gsh.return_value = result + with tempfile_ext.NamedTemporaryFile() as temp_f: + link = self._obj._UploadTestArtifacts(mock.MagicMock(), temp_f) + self.assertTrue(mock_gsh.called) + self.assertEqual(result, link) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run.py new file mode 100644 index 0000000000..54cb92a39c --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run.py @@ -0,0 +1,1512 @@ +# 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 collections +import contextlib +import copy +import hashlib +import json +import logging +import os +import posixpath +import re +import shutil +import sys +import tempfile +import time + +from six.moves import range # pylint: disable=redefined-builtin +from six.moves import zip # pylint: disable=redefined-builtin +from devil import base_error +from devil.android import apk_helper +from devil.android import crash_handler +from devil.android import device_errors +from devil.android import device_temp_file +from devil.android import flag_changer +from devil.android.sdk import shared_prefs +from devil.android import logcat_monitor +from devil.android.tools import system_app +from devil.android.tools import webview_app +from devil.utils import reraiser_thread +from incremental_install import installer +from pylib import constants +from pylib import valgrind_tools +from pylib.base import base_test_result +from pylib.base import output_manager +from pylib.constants import host_paths +from pylib.instrumentation import instrumentation_test_instance +from pylib.local.device import local_device_environment +from pylib.local.device import local_device_test_run +from pylib.output import remote_output_manager +from pylib.utils import chrome_proxy_utils +from pylib.utils import gold_utils +from pylib.utils import instrumentation_tracing +from pylib.utils import shared_preference_utils +from py_trace_event import trace_event +from py_trace_event import trace_time +from py_utils import contextlib_ext +from py_utils import tempfile_ext +import tombstones + +with host_paths.SysPath( + os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'), 0): + import jinja2 # pylint: disable=import-error + import markupsafe # pylint: disable=import-error,unused-import + + +_JINJA_TEMPLATE_DIR = os.path.join( + host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'pylib', 'instrumentation') +_JINJA_TEMPLATE_FILENAME = 'render_test.html.jinja' + +_WPR_GO_LINUX_X86_64_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, + 'third_party', 'webpagereplay', 'bin', + 'linux', 'x86_64', 'wpr') + +_TAG = 'test_runner_py' + +TIMEOUT_ANNOTATIONS = [ + ('Manual', 10 * 60 * 60), + ('IntegrationTest', 10 * 60), + ('External', 10 * 60), + ('EnormousTest', 5 * 60), + ('LargeTest', 2 * 60), + ('MediumTest', 30), + ('SmallTest', 10), +] + +# Account for Instrumentation and process init overhead. +FIXED_TEST_TIMEOUT_OVERHEAD = 60 + +# 30 minute max timeout for an instrumentation invocation to avoid shard +# timeouts when tests never finish. The shard timeout is currently 60 minutes, +# so this needs to be less than that. +MAX_BATCH_TEST_TIMEOUT = 30 * 60 + +LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v', 'DEBUG:I', + 'StrictMode:D', '%s:I' % _TAG] + +EXTRA_SCREENSHOT_FILE = ( + 'org.chromium.base.test.ScreenshotOnFailureStatement.ScreenshotFile') + +EXTRA_UI_CAPTURE_DIR = ( + 'org.chromium.base.test.util.Screenshooter.ScreenshotDir') + +EXTRA_TRACE_FILE = ('org.chromium.base.test.BaseJUnit4ClassRunner.TraceFile') + +_EXTRA_TEST_LIST = ( + 'org.chromium.base.test.BaseChromiumAndroidJUnitRunner.TestList') + +_EXTRA_PACKAGE_UNDER_TEST = ('org.chromium.chrome.test.pagecontroller.rules.' + 'ChromeUiApplicationTestRule.PackageUnderTest') + +FEATURE_ANNOTATION = 'Feature' +RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest' +WPR_ARCHIVE_FILE_PATH_ANNOTATION = 'WPRArchiveDirectory' +WPR_RECORD_REPLAY_TEST_FEATURE_ANNOTATION = 'WPRRecordReplayTest' + +_DEVICE_GOLD_DIR = 'skia_gold' +# A map of Android product models to SDK ints. +RENDER_TEST_MODEL_SDK_CONFIGS = { + # Android x86 emulator. + 'Android SDK built for x86': [23], + # We would like this to be supported, but it is currently too prone to + # introducing flakiness due to a combination of Gold and Chromium issues. + # See crbug.com/1233700 and skbug.com/12149 for more information. + # 'Pixel 2': [28], +} + +_BATCH_SUFFIX = '_batch' +_TEST_BATCH_MAX_GROUP_SIZE = 256 + + +@contextlib.contextmanager +def _LogTestEndpoints(device, test_name): + device.RunShellCommand( + ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name], + check_return=True) + try: + yield + finally: + device.RunShellCommand( + ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name], + check_return=True) + + +def DismissCrashDialogs(device): + # Dismiss any error dialogs. Limit the number in case we have an error + # loop or we are failing to dismiss. + packages = set() + try: + for _ in range(10): + package = device.DismissCrashDialogIfNeeded(timeout=10, retries=1) + if not package: + break + packages.add(package) + except device_errors.CommandFailedError: + logging.exception('Error while attempting to dismiss crash dialog.') + return packages + + +_CURRENT_FOCUS_CRASH_RE = re.compile( + r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') + + +def _GetTargetPackageName(test_apk): + # apk_under_test does not work for smoke tests, where it is set to an + # apk that is not listed as the targetPackage in the test apk's manifest. + return test_apk.GetAllInstrumentations()[0]['android:targetPackage'] + + +class LocalDeviceInstrumentationTestRun( + local_device_test_run.LocalDeviceTestRun): + def __init__(self, env, test_instance): + super(LocalDeviceInstrumentationTestRun, self).__init__( + env, test_instance) + self._chrome_proxy = None + self._context_managers = collections.defaultdict(list) + self._flag_changers = {} + self._render_tests_device_output_dir = None + self._shared_prefs_to_restore = [] + self._skia_gold_session_manager = None + self._skia_gold_work_dir = None + + #override + def TestPackage(self): + return self._test_instance.suite + + #override + def SetUp(self): + target_package = _GetTargetPackageName(self._test_instance.test_apk) + + @local_device_environment.handle_shard_failures_with( + self._env.DenylistDevice) + @trace_event.traced + def individual_device_set_up(device, host_device_tuples): + steps = [] + + if self._test_instance.replace_system_package: + @trace_event.traced + def replace_package(dev): + # We need the context manager to be applied before modifying any + # shared preference files in case the replacement APK needs to be + # set up, and it needs to be applied while the test is running. + # Thus, it needs to be applied early during setup, but must still be + # applied during _RunTest, which isn't possible using 'with' without + # applying the context manager up in test_runner. Instead, we + # manually invoke its __enter__ and __exit__ methods in setup and + # teardown. + system_app_context = system_app.ReplaceSystemApp( + dev, self._test_instance.replace_system_package.package, + self._test_instance.replace_system_package.replacement_apk) + # Pylint is not smart enough to realize that this field has + # an __enter__ method, and will complain loudly. + # pylint: disable=no-member + system_app_context.__enter__() + # pylint: enable=no-member + self._context_managers[str(dev)].append(system_app_context) + + steps.append(replace_package) + + if self._test_instance.system_packages_to_remove: + + @trace_event.traced + def remove_packages(dev): + logging.info('Attempting to remove system packages %s', + self._test_instance.system_packages_to_remove) + system_app.RemoveSystemApps( + dev, self._test_instance.system_packages_to_remove) + logging.info('Done removing system packages') + + # This should be at the front in case we're removing the package to make + # room for another APK installation later on. Since we disallow + # concurrent adb with this option specified, this should be safe. + steps.insert(0, remove_packages) + + if self._test_instance.use_webview_provider: + @trace_event.traced + def use_webview_provider(dev): + # We need the context manager to be applied before modifying any + # shared preference files in case the replacement APK needs to be + # set up, and it needs to be applied while the test is running. + # Thus, it needs to be applied early during setup, but must still be + # applied during _RunTest, which isn't possible using 'with' without + # applying the context manager up in test_runner. Instead, we + # manually invoke its __enter__ and __exit__ methods in setup and + # teardown. + webview_context = webview_app.UseWebViewProvider( + dev, self._test_instance.use_webview_provider) + # Pylint is not smart enough to realize that this field has + # an __enter__ method, and will complain loudly. + # pylint: disable=no-member + webview_context.__enter__() + # pylint: enable=no-member + self._context_managers[str(dev)].append(webview_context) + + steps.append(use_webview_provider) + + def install_helper(apk, + modules=None, + fake_modules=None, + permissions=None, + additional_locales=None): + + @instrumentation_tracing.no_tracing + @trace_event.traced + def install_helper_internal(d, apk_path=None): + # pylint: disable=unused-argument + d.Install(apk, + modules=modules, + fake_modules=fake_modules, + permissions=permissions, + additional_locales=additional_locales) + + return install_helper_internal + + def incremental_install_helper(apk, json_path, permissions): + + @trace_event.traced + def incremental_install_helper_internal(d, apk_path=None): + # pylint: disable=unused-argument + installer.Install(d, json_path, apk=apk, permissions=permissions) + return incremental_install_helper_internal + + permissions = self._test_instance.test_apk.GetPermissions() + if self._test_instance.test_apk_incremental_install_json: + steps.append(incremental_install_helper( + self._test_instance.test_apk, + self._test_instance. + test_apk_incremental_install_json, + permissions)) + else: + steps.append( + install_helper( + self._test_instance.test_apk, permissions=permissions)) + + steps.extend( + install_helper(apk) for apk in self._test_instance.additional_apks) + + # We'll potentially need the package names later for setting app + # compatibility workarounds. + for apk in (self._test_instance.additional_apks + + [self._test_instance.test_apk]): + self._installed_packages.append(apk_helper.GetPackageName(apk)) + + # The apk under test needs to be installed last since installing other + # apks after will unintentionally clear the fake module directory. + # TODO(wnwen): Make this more robust, fix crbug.com/1010954. + if self._test_instance.apk_under_test: + self._installed_packages.append( + apk_helper.GetPackageName(self._test_instance.apk_under_test)) + permissions = self._test_instance.apk_under_test.GetPermissions() + if self._test_instance.apk_under_test_incremental_install_json: + steps.append( + incremental_install_helper( + self._test_instance.apk_under_test, + self._test_instance.apk_under_test_incremental_install_json, + permissions)) + else: + steps.append( + install_helper(self._test_instance.apk_under_test, + self._test_instance.modules, + self._test_instance.fake_modules, permissions, + self._test_instance.additional_locales)) + + @trace_event.traced + def set_debug_app(dev): + # Set debug app in order to enable reading command line flags on user + # builds + cmd = ['am', 'set-debug-app', '--persistent'] + if self._test_instance.wait_for_java_debugger: + cmd.append('-w') + cmd.append(target_package) + dev.RunShellCommand(cmd, check_return=True) + + @trace_event.traced + def edit_shared_prefs(dev): + for setting in self._test_instance.edit_shared_prefs: + shared_pref = shared_prefs.SharedPrefs( + dev, setting['package'], setting['filename'], + use_encrypted_path=setting.get('supports_encrypted_path', False)) + pref_to_restore = copy.copy(shared_pref) + pref_to_restore.Load() + self._shared_prefs_to_restore.append(pref_to_restore) + + shared_preference_utils.ApplySharedPreferenceSetting( + shared_pref, setting) + + @trace_event.traced + def set_vega_permissions(dev): + # Normally, installation of VrCore automatically grants storage + # permissions. However, since VrCore is part of the system image on + # the Vega standalone headset, we don't install the APK as part of test + # setup. Instead, grant the permissions here so that it can take + # screenshots. + if dev.product_name == 'vega': + dev.GrantPermissions('com.google.vr.vrcore', [ + 'android.permission.WRITE_EXTERNAL_STORAGE', + 'android.permission.READ_EXTERNAL_STORAGE' + ]) + + @instrumentation_tracing.no_tracing + def push_test_data(dev): + device_root = posixpath.join(dev.GetExternalStoragePath(), + 'chromium_tests_root') + host_device_tuples_substituted = [ + (h, local_device_test_run.SubstituteDeviceRoot(d, device_root)) + for h, d in host_device_tuples] + logging.info('Pushing data dependencies.') + for h, d in host_device_tuples_substituted: + logging.debug(' %r -> %r', h, d) + local_device_environment.place_nomedia_on_device(dev, device_root) + dev.PushChangedFiles(host_device_tuples_substituted, + delete_device_stale=True) + if not host_device_tuples_substituted: + dev.RunShellCommand(['rm', '-rf', device_root], check_return=True) + dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True) + + @trace_event.traced + def create_flag_changer(dev): + if self._test_instance.flags: + self._CreateFlagChangerIfNeeded(dev) + logging.debug('Attempting to set flags: %r', + self._test_instance.flags) + self._flag_changers[str(dev)].AddFlags(self._test_instance.flags) + + valgrind_tools.SetChromeTimeoutScale( + dev, self._test_instance.timeout_scale) + + steps += [ + set_debug_app, edit_shared_prefs, push_test_data, create_flag_changer, + set_vega_permissions, DismissCrashDialogs + ] + + def bind_crash_handler(step, dev): + return lambda: crash_handler.RetryOnSystemCrash(step, dev) + + steps = [bind_crash_handler(s, device) for s in steps] + + try: + if self._env.concurrent_adb: + reraiser_thread.RunAsync(steps) + else: + for step in steps: + step() + if self._test_instance.store_tombstones: + tombstones.ClearAllTombstones(device) + except device_errors.CommandFailedError: + if not device.IsOnline(): + raise + + # A bugreport can be large and take a while to generate, so only capture + # one if we're using a remote manager. + if isinstance( + self._env.output_manager, + remote_output_manager.RemoteOutputManager): + logging.error( + 'Error when setting up device for tests. Taking a bugreport for ' + 'investigation. This may take a while...') + report_name = '%s.bugreport' % device.serial + with self._env.output_manager.ArchivedTempfile( + report_name, 'bug_reports') as report_file: + device.TakeBugReport(report_file.name) + logging.error('Bug report saved to %s', report_file.Link()) + raise + + self._env.parallel_devices.pMap( + individual_device_set_up, + self._test_instance.GetDataDependencies()) + # Created here instead of on a per-test basis so that the downloaded + # expectations can be re-used between tests, saving a significant amount + # of time. + self._skia_gold_work_dir = tempfile.mkdtemp() + self._skia_gold_session_manager = gold_utils.AndroidSkiaGoldSessionManager( + self._skia_gold_work_dir, self._test_instance.skia_gold_properties) + if self._test_instance.wait_for_java_debugger: + logging.warning('*' * 80) + logging.warning('Waiting for debugger to attach to process: %s', + target_package) + logging.warning('*' * 80) + + #override + def TearDown(self): + shutil.rmtree(self._skia_gold_work_dir) + self._skia_gold_work_dir = None + self._skia_gold_session_manager = None + # By default, teardown will invoke ADB. When receiving SIGTERM due to a + # timeout, there's a high probability that ADB is non-responsive. In these + # cases, sending an ADB command will potentially take a long time to time + # out. Before this happens, the process will be hard-killed for not + # responding to SIGTERM fast enough. + if self._received_sigterm: + return + + @local_device_environment.handle_shard_failures_with( + self._env.DenylistDevice) + @trace_event.traced + def individual_device_tear_down(dev): + if str(dev) in self._flag_changers: + self._flag_changers[str(dev)].Restore() + + # Remove package-specific configuration + dev.RunShellCommand(['am', 'clear-debug-app'], check_return=True) + + valgrind_tools.SetChromeTimeoutScale(dev, None) + + # Restore any shared preference files that we stored during setup. + # This should be run sometime before the replace package contextmanager + # gets exited so we don't have to special case restoring files of + # replaced system apps. + for pref_to_restore in self._shared_prefs_to_restore: + pref_to_restore.Commit(force_commit=True) + + # Context manager exit handlers are applied in reverse order + # of the enter handlers. + for context in reversed(self._context_managers[str(dev)]): + # See pylint-related comment above with __enter__() + # pylint: disable=no-member + context.__exit__(*sys.exc_info()) + # pylint: enable=no-member + + self._env.parallel_devices.pMap(individual_device_tear_down) + + def _CreateFlagChangerIfNeeded(self, device): + if str(device) not in self._flag_changers: + cmdline_file = 'test-cmdline-file' + if self._test_instance.use_apk_under_test_flags_file: + if self._test_instance.package_info: + cmdline_file = self._test_instance.package_info.cmdline_file + else: + raise Exception('No PackageInfo found but' + '--use-apk-under-test-flags-file is specified.') + self._flag_changers[str(device)] = flag_changer.FlagChanger( + device, cmdline_file) + + #override + def _CreateShards(self, tests): + return tests + + #override + def _GetTests(self): + if self._test_instance.junit4_runner_supports_listing: + raw_tests = self._GetTestsFromRunner() + tests = self._test_instance.ProcessRawTests(raw_tests) + else: + tests = self._test_instance.GetTests() + tests = self._ApplyExternalSharding( + tests, self._test_instance.external_shard_index, + self._test_instance.total_external_shards) + return tests + + #override + def _GroupTests(self, tests): + batched_tests = dict() + other_tests = [] + for test in tests: + annotations = test['annotations'] + if 'Batch' in annotations and 'RequiresRestart' not in annotations: + batch_name = annotations['Batch']['value'] + if not batch_name: + batch_name = test['class'] + + # Feature flags won't work in instrumentation tests unless the activity + # is restarted. + # Tests with identical features are grouped to minimize restarts. + if 'Features$EnableFeatures' in annotations: + batch_name += '|enabled:' + ','.join( + sorted(annotations['Features$EnableFeatures']['value'])) + if 'Features$DisableFeatures' in annotations: + batch_name += '|disabled:' + ','.join( + sorted(annotations['Features$DisableFeatures']['value'])) + + if not batch_name in batched_tests: + batched_tests[batch_name] = [] + batched_tests[batch_name].append(test) + else: + other_tests.append(test) + + all_tests = [] + for _, tests in list(batched_tests.items()): + tests.sort() # Ensure a consistent ordering across external shards. + all_tests.extend([ + tests[i:i + _TEST_BATCH_MAX_GROUP_SIZE] + for i in range(0, len(tests), _TEST_BATCH_MAX_GROUP_SIZE) + ]) + all_tests.extend(other_tests) + return all_tests + + #override + def _GetUniqueTestName(self, test): + return instrumentation_test_instance.GetUniqueTestName(test) + + #override + def _RunTest(self, device, test): + extras = {} + + # Provide package name under test for apk_under_test. + if self._test_instance.apk_under_test: + package_name = self._test_instance.apk_under_test.GetPackageName() + extras[_EXTRA_PACKAGE_UNDER_TEST] = package_name + + flags_to_add = [] + test_timeout_scale = None + if self._test_instance.coverage_directory: + coverage_basename = '%s' % ('%s_%s_group' % + (test[0]['class'], test[0]['method']) + if isinstance(test, list) else '%s_%s' % + (test['class'], test['method'])) + extras['coverage'] = 'true' + coverage_directory = os.path.join( + device.GetExternalStoragePath(), 'chrome', 'test', 'coverage') + if not device.PathExists(coverage_directory): + device.RunShellCommand(['mkdir', '-p', coverage_directory], + check_return=True) + coverage_device_file = os.path.join(coverage_directory, coverage_basename) + coverage_device_file += '.exec' + extras['coverageFile'] = coverage_device_file + + if self._test_instance.enable_breakpad_dump: + # Use external storage directory so that the breakpad dump can be accessed + # by the test APK in addition to the apk_under_test. + breakpad_dump_directory = os.path.join(device.GetExternalStoragePath(), + 'chromium_dumps') + if device.PathExists(breakpad_dump_directory): + device.RemovePath(breakpad_dump_directory, recursive=True) + flags_to_add.append('--breakpad-dump-location=' + breakpad_dump_directory) + + # Save screenshot if screenshot dir is specified (save locally) or if + # a GS bucket is passed (save in cloud). + screenshot_device_file = device_temp_file.DeviceTempFile( + device.adb, suffix='.png', dir=device.GetExternalStoragePath()) + extras[EXTRA_SCREENSHOT_FILE] = screenshot_device_file.name + + # Set up the screenshot directory. This needs to be done for each test so + # that we only get screenshots created by that test. It has to be on + # external storage since the default location doesn't allow file creation + # from the instrumentation test app on Android L and M. + ui_capture_dir = device_temp_file.NamedDeviceTemporaryDirectory( + device.adb, + dir=device.GetExternalStoragePath()) + extras[EXTRA_UI_CAPTURE_DIR] = ui_capture_dir.name + + if self._env.trace_output: + trace_device_file = device_temp_file.DeviceTempFile( + device.adb, suffix='.json', dir=device.GetExternalStoragePath()) + extras[EXTRA_TRACE_FILE] = trace_device_file.name + + target = '%s/%s' % (self._test_instance.test_package, + self._test_instance.junit4_runner_class) + if isinstance(test, list): + + def name_and_timeout(t): + n = instrumentation_test_instance.GetTestName(t) + i = self._GetTimeoutFromAnnotations(t['annotations'], n) + return (n, i) + + test_names, timeouts = list(zip(*(name_and_timeout(t) for t in test))) + + test_name = instrumentation_test_instance.GetTestName( + test[0]) + _BATCH_SUFFIX + extras['class'] = ','.join(test_names) + test_display_name = test_name + timeout = min(MAX_BATCH_TEST_TIMEOUT, + FIXED_TEST_TIMEOUT_OVERHEAD + sum(timeouts)) + else: + assert test['is_junit4'] + test_name = instrumentation_test_instance.GetTestName(test) + test_display_name = self._GetUniqueTestName(test) + + extras['class'] = test_name + if 'flags' in test and test['flags']: + flags_to_add.extend(test['flags']) + timeout = FIXED_TEST_TIMEOUT_OVERHEAD + self._GetTimeoutFromAnnotations( + test['annotations'], test_display_name) + + test_timeout_scale = self._GetTimeoutScaleFromAnnotations( + test['annotations']) + if test_timeout_scale and test_timeout_scale != 1: + valgrind_tools.SetChromeTimeoutScale( + device, test_timeout_scale * self._test_instance.timeout_scale) + + if self._test_instance.wait_for_java_debugger: + timeout = None + logging.info('preparing to run %s: %s', test_display_name, test) + + if _IsRenderTest(test): + # TODO(mikecase): Add DeviceTempDirectory class and use that instead. + self._render_tests_device_output_dir = posixpath.join( + device.GetExternalStoragePath(), 'render_test_output_dir') + flags_to_add.append('--render-test-output-dir=%s' % + self._render_tests_device_output_dir) + + if _IsWPRRecordReplayTest(test): + wpr_archive_relative_path = _GetWPRArchivePath(test) + if not wpr_archive_relative_path: + raise RuntimeError('Could not find the WPR archive file path ' + 'from annotation.') + wpr_archive_path = os.path.join(host_paths.DIR_SOURCE_ROOT, + wpr_archive_relative_path) + if not os.path.isdir(wpr_archive_path): + raise RuntimeError('WPRArchiveDirectory annotation should point ' + 'to a directory only. ' + '{0} exist: {1}'.format( + wpr_archive_path, + os.path.exists(wpr_archive_path))) + + # Some linux version does not like # in the name. Replaces it with __. + archive_path = os.path.join( + wpr_archive_path, + _ReplaceUncommonChars(self._GetUniqueTestName(test)) + '.wprgo') + + if not os.path.exists(_WPR_GO_LINUX_X86_64_PATH): + # If we got to this stage, then we should have + # checkout_android set. + raise RuntimeError( + 'WPR Go binary not found at {}'.format(_WPR_GO_LINUX_X86_64_PATH)) + # Tells the server to use the binaries retrieved from CIPD. + chrome_proxy_utils.ChromeProxySession.SetWPRServerBinary( + _WPR_GO_LINUX_X86_64_PATH) + self._chrome_proxy = chrome_proxy_utils.ChromeProxySession() + self._chrome_proxy.wpr_record_mode = self._test_instance.wpr_record_mode + self._chrome_proxy.Start(device, archive_path) + flags_to_add.extend(self._chrome_proxy.GetFlags()) + + if flags_to_add: + self._CreateFlagChangerIfNeeded(device) + self._flag_changers[str(device)].PushFlags(add=flags_to_add) + + time_ms = lambda: int(time.time() * 1e3) + start_ms = time_ms() + + with ui_capture_dir: + with self._ArchiveLogcat(device, test_name) as logcat_file: + output = device.StartInstrumentation( + target, raw=True, extras=extras, timeout=timeout, retries=0) + + duration_ms = time_ms() - start_ms + + with contextlib_ext.Optional( + trace_event.trace('ProcessResults'), + self._env.trace_output): + output = self._test_instance.MaybeDeobfuscateLines(output) + # TODO(jbudorick): Make instrumentation tests output a JSON so this + # doesn't have to parse the output. + result_code, result_bundle, statuses = ( + self._test_instance.ParseAmInstrumentRawOutput(output)) + results = self._test_instance.GenerateTestResults( + result_code, result_bundle, statuses, duration_ms, + device.product_cpu_abi, self._test_instance.symbolizer) + + if self._env.trace_output: + self._SaveTraceData(trace_device_file, device, test['class']) + + + def restore_flags(): + if flags_to_add: + self._flag_changers[str(device)].Restore() + + def restore_timeout_scale(): + if test_timeout_scale: + valgrind_tools.SetChromeTimeoutScale( + device, self._test_instance.timeout_scale) + + def handle_coverage_data(): + if self._test_instance.coverage_directory: + try: + if not os.path.exists(self._test_instance.coverage_directory): + os.makedirs(self._test_instance.coverage_directory) + device.PullFile(coverage_device_file, + self._test_instance.coverage_directory) + device.RemovePath(coverage_device_file, True) + except (OSError, base_error.BaseError) as e: + logging.warning('Failed to handle coverage data after tests: %s', e) + + def handle_render_test_data(): + if _IsRenderTest(test): + # Render tests do not cause test failure by default. So we have to + # check to see if any failure images were generated even if the test + # does not fail. + try: + self._ProcessRenderTestResults(device, results) + finally: + device.RemovePath(self._render_tests_device_output_dir, + recursive=True, + force=True) + self._render_tests_device_output_dir = None + + def pull_ui_screen_captures(): + screenshots = [] + for filename in device.ListDirectory(ui_capture_dir.name): + if filename.endswith('.json'): + screenshots.append(pull_ui_screenshot(filename)) + if screenshots: + json_archive_name = 'ui_capture_%s_%s.json' % ( + test_name.replace('#', '.'), + time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime())) + with self._env.output_manager.ArchivedTempfile( + json_archive_name, 'ui_capture', output_manager.Datatype.JSON + ) as json_archive: + json.dump(screenshots, json_archive) + _SetLinkOnResults(results, test_name, 'ui screenshot', + json_archive.Link()) + + def pull_ui_screenshot(filename): + source_dir = ui_capture_dir.name + json_path = posixpath.join(source_dir, filename) + json_data = json.loads(device.ReadFile(json_path)) + image_file_path = posixpath.join(source_dir, json_data['location']) + with self._env.output_manager.ArchivedTempfile( + json_data['location'], 'ui_capture', output_manager.Datatype.PNG + ) as image_archive: + device.PullFile(image_file_path, image_archive.name) + json_data['image_link'] = image_archive.Link() + return json_data + + def stop_chrome_proxy(): + # Removes the port forwarding + if self._chrome_proxy: + self._chrome_proxy.Stop(device) + if not self._chrome_proxy.wpr_replay_mode: + logging.info('WPR Record test generated archive file %s', + self._chrome_proxy.wpr_archive_path) + self._chrome_proxy = None + + + # While constructing the TestResult objects, we can parallelize several + # steps that involve ADB. These steps should NOT depend on any info in + # the results! Things such as whether the test CRASHED have not yet been + # determined. + post_test_steps = [ + restore_flags, restore_timeout_scale, stop_chrome_proxy, + handle_coverage_data, handle_render_test_data, pull_ui_screen_captures + ] + if self._env.concurrent_adb: + reraiser_thread.RunAsync(post_test_steps) + else: + for step in post_test_steps: + step() + + if logcat_file: + _SetLinkOnResults(results, test_name, 'logcat', logcat_file.Link()) + + # Update the result name if the test used flags. + if flags_to_add: + for r in results: + if r.GetName() == test_name: + r.SetName(test_display_name) + + # Add UNKNOWN results for any missing tests. + iterable_test = test if isinstance(test, list) else [test] + test_names = set(self._GetUniqueTestName(t) for t in iterable_test) + results_names = set(r.GetName() for r in results) + results.extend( + base_test_result.BaseTestResult(u, base_test_result.ResultType.UNKNOWN) + for u in test_names.difference(results_names)) + + # Update the result type if we detect a crash. + try: + crashed_packages = DismissCrashDialogs(device) + # Assume test package convention of ".test" suffix + if any(p in self._test_instance.test_package for p in crashed_packages): + for r in results: + if r.GetType() == base_test_result.ResultType.UNKNOWN: + r.SetType(base_test_result.ResultType.CRASH) + elif (crashed_packages and len(results) == 1 + and results[0].GetType() != base_test_result.ResultType.PASS): + # Add log message and set failure reason if: + # 1) The app crash was likely not caused by the test. + # AND + # 2) The app crash possibly caused the test to fail. + # Crashes of the package under test are assumed to be the test's fault. + _AppendToLogForResult( + results[0], 'OS displayed error dialogs for {}'.format( + ', '.join(crashed_packages))) + results[0].SetFailureReason('{} Crashed'.format( + ','.join(crashed_packages))) + except device_errors.CommandTimeoutError: + logging.warning('timed out when detecting/dismissing error dialogs') + # Attach screenshot to the test to help with debugging the dialog boxes. + self._SaveScreenshot(device, screenshot_device_file, test_display_name, + results, 'dialog_box_screenshot') + + # The crash result can be set above or in + # InstrumentationTestRun.GenerateTestResults. If a test crashes, + # subprocesses such as the one used by EmbeddedTestServerRule can be left + # alive in a bad state, so kill them now. + for r in results: + if r.GetType() == base_test_result.ResultType.CRASH: + for apk in self._test_instance.additional_apks: + device.ForceStop(apk.GetPackageName()) + + # Handle failures by: + # - optionally taking a screenshot + # - logging the raw output at INFO level + # - clearing the application state while persisting permissions + if any(r.GetType() not in (base_test_result.ResultType.PASS, + base_test_result.ResultType.SKIP) + for r in results): + self._SaveScreenshot(device, screenshot_device_file, test_display_name, + results, 'post_test_screenshot') + + logging.info('detected failure in %s. raw output:', test_display_name) + for l in output: + logging.info(' %s', l) + if not self._env.skip_clear_data: + if self._test_instance.package_info: + permissions = (self._test_instance.apk_under_test.GetPermissions() + if self._test_instance.apk_under_test else None) + device.ClearApplicationState(self._test_instance.package_info.package, + permissions=permissions) + if self._test_instance.enable_breakpad_dump: + device.RemovePath(breakpad_dump_directory, recursive=True) + else: + logging.debug('raw output from %s:', test_display_name) + for l in output: + logging.debug(' %s', l) + + if self._test_instance.store_tombstones: + resolved_tombstones = tombstones.ResolveTombstones( + device, + resolve_all_tombstones=True, + include_stack_symbols=False, + wipe_tombstones=True, + tombstone_symbolizer=self._test_instance.symbolizer) + if resolved_tombstones: + tombstone_filename = 'tombstones_%s_%s' % (time.strftime( + '%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial) + with self._env.output_manager.ArchivedTempfile( + tombstone_filename, 'tombstones') as tombstone_file: + tombstone_file.write('\n'.join(resolved_tombstones)) + + # Associate tombstones with first crashing test. + for result in results: + if result.GetType() == base_test_result.ResultType.CRASH: + result.SetLink('tombstones', tombstone_file.Link()) + break + else: + # We don't always detect crashes correctly. In this case, + # associate with the first test. + results[0].SetLink('tombstones', tombstone_file.Link()) + + unknown_tests = set(r.GetName() for r in results + if r.GetType() == base_test_result.ResultType.UNKNOWN) + + # If a test that is batched crashes, the rest of the tests in that batch + # won't be ran and will have their status left as unknown in results, + # so rerun the tests. (see crbug/1127935) + # Need to "unbatch" the tests, so that on subsequent tries, the tests can + # get ran individually. This prevents an unrecognized crash from preventing + # the tests in the batch from being ran. Running the test as unbatched does + # not happen until a retry happens at the local_device_test_run/environment + # level. + tests_to_rerun = [] + for t in iterable_test: + if self._GetUniqueTestName(t) in unknown_tests: + prior_attempts = t.get('run_attempts', 0) + t['run_attempts'] = prior_attempts + 1 + # It's possible every test in the batch could crash, so need to + # try up to as many times as tests that there are. + if prior_attempts < len(results): + if t['annotations']: + t['annotations'].pop('Batch', None) + tests_to_rerun.append(t) + + # If we have a crash that isn't recognized as a crash in a batch, the tests + # will be marked as unknown. Sometimes a test failure causes a crash, but + # the crash isn't recorded because the failure was detected first. + # When the UNKNOWN tests are reran while unbatched and pass, + # they'll have an UNKNOWN, PASS status, so will be improperly marked as + # flaky, so change status to NOTRUN and don't try rerunning. They will + # get rerun individually at the local_device_test_run/environment level. + # as the "Batch" annotation was removed. + found_crash_or_fail = False + for r in results: + if (r.GetType() == base_test_result.ResultType.CRASH + or r.GetType() == base_test_result.ResultType.FAIL): + found_crash_or_fail = True + break + if not found_crash_or_fail: + # Don't bother rerunning since the unrecognized crashes in + # the batch will keep failing. + tests_to_rerun = None + for r in results: + if r.GetType() == base_test_result.ResultType.UNKNOWN: + r.SetType(base_test_result.ResultType.NOTRUN) + + return results, tests_to_rerun if tests_to_rerun else None + + def _GetTestsFromRunner(self): + test_apk_path = self._test_instance.test_apk.path + pickle_path = '%s-runner.pickle' % test_apk_path + # For incremental APKs, the code doesn't live in the apk, so instead check + # the timestamp of the target's .stamp file. + if self._test_instance.test_apk_incremental_install_json: + with open(self._test_instance.test_apk_incremental_install_json) as f: + data = json.load(f) + out_dir = constants.GetOutDirectory() + test_mtime = max( + os.path.getmtime(os.path.join(out_dir, p)) for p in data['dex_files']) + else: + test_mtime = os.path.getmtime(test_apk_path) + + try: + return instrumentation_test_instance.GetTestsFromPickle( + pickle_path, test_mtime) + except instrumentation_test_instance.TestListPickleException as e: + logging.info('Could not get tests from pickle: %s', e) + logging.info('Getting tests by having %s list them.', + self._test_instance.junit4_runner_class) + def list_tests(d): + def _run(dev): + # We need to use GetAppWritablePath instead of GetExternalStoragePath + # here because we will not have applied legacy storage workarounds on R+ + # yet. + with device_temp_file.DeviceTempFile( + dev.adb, suffix='.json', + dir=dev.GetAppWritablePath()) as dev_test_list_json: + junit4_runner_class = self._test_instance.junit4_runner_class + test_package = self._test_instance.test_package + extras = { + 'log': 'true', + # Workaround for https://github.com/mockito/mockito/issues/922 + 'notPackage': 'net.bytebuddy', + } + extras[_EXTRA_TEST_LIST] = dev_test_list_json.name + target = '%s/%s' % (test_package, junit4_runner_class) + timeout = 240 + if self._test_instance.wait_for_java_debugger: + timeout = None + with self._ArchiveLogcat(dev, 'list_tests'): + test_list_run_output = dev.StartInstrumentation( + target, extras=extras, retries=0, timeout=timeout) + if any(test_list_run_output): + logging.error('Unexpected output while listing tests:') + for line in test_list_run_output: + logging.error(' %s', line) + with tempfile_ext.NamedTemporaryDirectory() as host_dir: + host_file = os.path.join(host_dir, 'list_tests.json') + dev.PullFile(dev_test_list_json.name, host_file) + with open(host_file, 'r') as host_file: + return json.load(host_file) + + return crash_handler.RetryOnSystemCrash(_run, d) + + raw_test_lists = self._env.parallel_devices.pMap(list_tests).pGet(None) + + # If all devices failed to list tests, raise an exception. + # Check that tl is not None and is not empty. + if all(not tl for tl in raw_test_lists): + raise device_errors.CommandFailedError( + 'Failed to list tests on any device') + + # Get the first viable list of raw tests + raw_tests = [tl for tl in raw_test_lists if tl][0] + + instrumentation_test_instance.SaveTestsToPickle(pickle_path, raw_tests) + return raw_tests + + @contextlib.contextmanager + def _ArchiveLogcat(self, device, test_name): + stream_name = 'logcat_%s_shard%s_%s_%s' % ( + test_name.replace('#', '.'), self._test_instance.external_shard_index, + time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial) + + logcat_file = None + logmon = None + try: + with self._env.output_manager.ArchivedTempfile( + stream_name, 'logcat') as logcat_file: + with logcat_monitor.LogcatMonitor( + device.adb, + filter_specs=local_device_environment.LOGCAT_FILTERS, + output_file=logcat_file.name, + transform_func=self._test_instance.MaybeDeobfuscateLines, + check_error=False) as logmon: + with _LogTestEndpoints(device, test_name): + with contextlib_ext.Optional( + trace_event.trace(test_name), + self._env.trace_output): + yield logcat_file + finally: + if logmon: + logmon.Close() + if logcat_file and logcat_file.Link(): + logging.info('Logcat saved to %s', logcat_file.Link()) + + def _SaveTraceData(self, trace_device_file, device, test_class): + trace_host_file = self._env.trace_output + + if device.FileExists(trace_device_file.name): + try: + java_trace_json = device.ReadFile(trace_device_file.name) + except IOError: + raise Exception('error pulling trace file from device') + finally: + trace_device_file.close() + + process_name = '%s (device %s)' % (test_class, device.serial) + process_hash = int(hashlib.md5(process_name).hexdigest()[:6], 16) + + java_trace = json.loads(java_trace_json) + java_trace.sort(key=lambda event: event['ts']) + + get_date_command = 'echo $EPOCHREALTIME' + device_time = device.RunShellCommand(get_date_command, single_line=True) + device_time = float(device_time) * 1e6 + system_time = trace_time.Now() + time_difference = system_time - device_time + + threads_to_add = set() + for event in java_trace: + # Ensure thread ID and thread name will be linked in the metadata. + threads_to_add.add((event['tid'], event['name'])) + + event['pid'] = process_hash + + # Adjust time stamp to align with Python trace times (from + # trace_time.Now()). + event['ts'] += time_difference + + for tid, thread_name in threads_to_add: + thread_name_metadata = {'pid': process_hash, 'tid': tid, + 'ts': 0, 'ph': 'M', 'cat': '__metadata', + 'name': 'thread_name', + 'args': {'name': thread_name}} + java_trace.append(thread_name_metadata) + + process_name_metadata = {'pid': process_hash, 'tid': 0, 'ts': 0, + 'ph': 'M', 'cat': '__metadata', + 'name': 'process_name', + 'args': {'name': process_name}} + java_trace.append(process_name_metadata) + + java_trace_json = json.dumps(java_trace) + java_trace_json = java_trace_json.rstrip(' ]') + + with open(trace_host_file, 'r') as host_handle: + host_contents = host_handle.readline() + + if host_contents: + java_trace_json = ',%s' % java_trace_json.lstrip(' [') + + with open(trace_host_file, 'a') as host_handle: + host_handle.write(java_trace_json) + + def _SaveScreenshot(self, device, screenshot_device_file, test_name, results, + link_name): + screenshot_filename = '%s-%s.png' % ( + test_name, time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime())) + if device.FileExists(screenshot_device_file.name): + with self._env.output_manager.ArchivedTempfile( + screenshot_filename, 'screenshot', + output_manager.Datatype.PNG) as screenshot_host_file: + try: + device.PullFile(screenshot_device_file.name, + screenshot_host_file.name) + finally: + screenshot_device_file.close() + _SetLinkOnResults(results, test_name, link_name, + screenshot_host_file.Link()) + + def _ProcessRenderTestResults(self, device, results): + if not self._render_tests_device_output_dir: + return + self._ProcessSkiaGoldRenderTestResults(device, results) + + def _IsRetryWithoutPatch(self): + """Checks whether this test run is a retry without a patch/CL. + + Returns: + True iff this is being run on a trybot and the current step is a retry + without the patch applied, otherwise False. + """ + is_tryjob = self._test_instance.skia_gold_properties.IsTryjobRun() + # Builders automatically pass in --gtest_repeat, + # --test-launcher-retry-limit, --test-launcher-batch-limit, and + # --gtest_filter when running a step without a CL applied, but not for + # steps with the CL applied. + # TODO(skbug.com/12100): Check this in a less hacky way if a way can be + # found to check the actual step name. Ideally, this would not be necessary + # at all, but will be until Chromium stops doing step retries on trybots + # (extremely unlikely) or Gold is updated to not clobber earlier results + # (more likely, but a ways off). + has_filter = bool(self._test_instance.test_filter) + has_batch_limit = self._test_instance.test_launcher_batch_limit is not None + return is_tryjob and has_filter and has_batch_limit + + def _ProcessSkiaGoldRenderTestResults(self, device, results): + gold_dir = posixpath.join(self._render_tests_device_output_dir, + _DEVICE_GOLD_DIR) + if not device.FileExists(gold_dir): + return + + gold_properties = self._test_instance.skia_gold_properties + with tempfile_ext.NamedTemporaryDirectory() as host_dir: + use_luci = not (gold_properties.local_pixel_tests + or gold_properties.no_luci_auth) + + # Pull everything at once instead of pulling individually, as it's + # slightly faster since each command over adb has some overhead compared + # to doing the same thing locally. + host_dir = os.path.join(host_dir, _DEVICE_GOLD_DIR) + device.PullFile(gold_dir, host_dir) + for image_name in os.listdir(host_dir): + if not image_name.endswith('.png'): + continue + + render_name = image_name[:-4] + json_name = render_name + '.json' + json_path = os.path.join(host_dir, json_name) + image_path = os.path.join(host_dir, image_name) + full_test_name = None + if not os.path.exists(json_path): + _FailTestIfNecessary(results, full_test_name) + _AppendToLog( + results, full_test_name, + 'Unable to find corresponding JSON file for image %s ' + 'when doing Skia Gold comparison.' % image_name) + continue + + # Add 'ignore': '1' if a comparison failure would not be surfaced, as + # that implies that we aren't actively maintaining baselines for the + # test. This helps prevent unrelated CLs from getting comments posted to + # them. + should_rewrite = False + with open(json_path) as infile: + # All the key/value pairs in the JSON file are strings, so convert + # to a bool. + json_dict = json.load(infile) + fail_on_unsupported = json_dict.get('fail_on_unsupported_configs', + 'false') + fail_on_unsupported = fail_on_unsupported.lower() == 'true' + # Grab the full test name so we can associate the comparison with a + # particular test, which is necessary if tests are batched together. + # Remove the key/value pair from the JSON since we don't need/want to + # upload it to Gold. + full_test_name = json_dict.get('full_test_name') + if 'full_test_name' in json_dict: + should_rewrite = True + del json_dict['full_test_name'] + + running_on_unsupported = ( + device.build_version_sdk not in RENDER_TEST_MODEL_SDK_CONFIGS.get( + device.product_model, []) and not fail_on_unsupported) + should_ignore_in_gold = running_on_unsupported + # We still want to fail the test even if we're ignoring the image in + # Gold if we're running on a supported configuration, so + # should_ignore_in_gold != should_hide_failure. + should_hide_failure = running_on_unsupported + if should_ignore_in_gold: + should_rewrite = True + json_dict['ignore'] = '1' + if should_rewrite: + with open(json_path, 'w') as outfile: + json.dump(json_dict, outfile) + + gold_session = self._skia_gold_session_manager.GetSkiaGoldSession( + keys_input=json_path) + + try: + status, error = gold_session.RunComparison( + name=render_name, + png_file=image_path, + output_manager=self._env.output_manager, + use_luci=use_luci, + force_dryrun=self._IsRetryWithoutPatch()) + except Exception as e: # pylint: disable=broad-except + _FailTestIfNecessary(results, full_test_name) + _AppendToLog(results, full_test_name, + 'Skia Gold comparison raised exception: %s' % e) + continue + + if not status: + continue + + # Don't fail the test if we ran on an unsupported configuration unless + # the test has explicitly opted in, as it's likely that baselines + # aren't maintained for that configuration. + if should_hide_failure: + if self._test_instance.skia_gold_properties.local_pixel_tests: + _AppendToLog( + results, full_test_name, + 'Gold comparison for %s failed, but model %s with SDK ' + '%d is not a supported configuration. This failure would be ' + 'ignored on the bots, but failing since tests are being run ' + 'locally.' % + (render_name, device.product_model, device.build_version_sdk)) + else: + _AppendToLog( + results, full_test_name, + 'Gold comparison for %s failed, but model %s with SDK ' + '%d is not a supported configuration, so ignoring failure.' % + (render_name, device.product_model, device.build_version_sdk)) + continue + + _FailTestIfNecessary(results, full_test_name) + failure_log = ( + 'Skia Gold reported failure for RenderTest %s. See ' + 'RENDER_TESTS.md for how to fix this failure.' % render_name) + status_codes =\ + self._skia_gold_session_manager.GetSessionClass().StatusCodes + if status == status_codes.AUTH_FAILURE: + _AppendToLog(results, full_test_name, + 'Gold authentication failed with output %s' % error) + elif status == status_codes.INIT_FAILURE: + _AppendToLog(results, full_test_name, + 'Gold initialization failed with output %s' % error) + elif status == status_codes.COMPARISON_FAILURE_REMOTE: + public_triage_link, internal_triage_link =\ + gold_session.GetTriageLinks(render_name) + if not public_triage_link: + _AppendToLog( + results, full_test_name, + 'Failed to get triage link for %s, raw output: %s' % + (render_name, error)) + _AppendToLog( + results, full_test_name, 'Reason for no triage link: %s' % + gold_session.GetTriageLinkOmissionReason(render_name)) + continue + if gold_properties.IsTryjobRun(): + _SetLinkOnResults(results, full_test_name, + 'Public Skia Gold triage link for entire CL', + public_triage_link) + _SetLinkOnResults(results, full_test_name, + 'Internal Skia Gold triage link for entire CL', + internal_triage_link) + else: + _SetLinkOnResults( + results, full_test_name, + 'Public Skia Gold triage link for %s' % render_name, + public_triage_link) + _SetLinkOnResults( + results, full_test_name, + 'Internal Skia Gold triage link for %s' % render_name, + internal_triage_link) + _AppendToLog(results, full_test_name, failure_log) + + elif status == status_codes.COMPARISON_FAILURE_LOCAL: + given_link = gold_session.GetGivenImageLink(render_name) + closest_link = gold_session.GetClosestImageLink(render_name) + diff_link = gold_session.GetDiffImageLink(render_name) + + processed_template_output = _GenerateRenderTestHtml( + render_name, given_link, closest_link, diff_link) + with self._env.output_manager.ArchivedTempfile( + '%s.html' % render_name, 'gold_local_diffs', + output_manager.Datatype.HTML) as html_results: + html_results.write(processed_template_output) + _SetLinkOnResults(results, full_test_name, render_name, + html_results.Link()) + _AppendToLog( + results, full_test_name, + 'See %s link for diff image with closest positive.' % render_name) + elif status == status_codes.LOCAL_DIFF_FAILURE: + _AppendToLog(results, full_test_name, + 'Failed to generate diffs from Gold: %s' % error) + else: + logging.error( + 'Given unhandled SkiaGoldSession StatusCode %s with error %s', + status, error) + + #override + def _ShouldRetry(self, test, result): + # We've tried to disable retries in the past with mixed results. + # See crbug.com/619055 for historical context and crbug.com/797002 + # for ongoing efforts. + if 'Batch' in test['annotations'] and test['annotations']['Batch'][ + 'value'] == 'UnitTests': + return False + del test, result + return True + + #override + def _ShouldShard(self): + return True + + @classmethod + def _GetTimeoutScaleFromAnnotations(cls, annotations): + try: + return int(annotations.get('TimeoutScale', {}).get('value', 1)) + except ValueError as e: + logging.warning("Non-integer value of TimeoutScale ignored. (%s)", str(e)) + return 1 + + @classmethod + def _GetTimeoutFromAnnotations(cls, annotations, test_name): + for k, v in TIMEOUT_ANNOTATIONS: + if k in annotations: + timeout = v + break + else: + logging.warning('Using default 1 minute timeout for %s', test_name) + timeout = 60 + + timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) + + return timeout + + +def _IsWPRRecordReplayTest(test): + """Determines whether a test or a list of tests is a WPR RecordReplay Test.""" + if not isinstance(test, list): + test = [test] + return any([ + WPR_RECORD_REPLAY_TEST_FEATURE_ANNOTATION in t['annotations'].get( + FEATURE_ANNOTATION, {}).get('value', ()) for t in test + ]) + + +def _GetWPRArchivePath(test): + """Retrieves the archive path from the WPRArchiveDirectory annotation.""" + return test['annotations'].get(WPR_ARCHIVE_FILE_PATH_ANNOTATION, + {}).get('value', ()) + + +def _ReplaceUncommonChars(original): + """Replaces uncommon characters with __.""" + if not original: + raise ValueError('parameter should not be empty') + + uncommon_chars = ['#'] + for char in uncommon_chars: + original = original.replace(char, '__') + return original + + +def _IsRenderTest(test): + """Determines if a test or list of tests has a RenderTest amongst them.""" + if not isinstance(test, list): + test = [test] + return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get( + FEATURE_ANNOTATION, {}).get('value', ()) for t in test]) + + +def _GenerateRenderTestHtml(image_name, failure_link, golden_link, diff_link): + """Generates a RenderTest results page. + + Displays the generated (failure) image, the golden image, and the diff + between them. + + Args: + image_name: The name of the image whose comparison failed. + failure_link: The URL to the generated/failure image. + golden_link: The URL to the golden image. + diff_link: The URL to the diff image between the failure and golden images. + + Returns: + A string containing the generated HTML. + """ + jinja2_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), trim_blocks=True) + template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) + # pylint: disable=no-member + return template.render( + test_name=image_name, + failure_link=failure_link, + golden_link=golden_link, + diff_link=diff_link) + + +def _FailTestIfNecessary(results, full_test_name): + """Marks the given results as failed if it wasn't already. + + Marks the result types as ResultType.FAIL unless they were already some sort + of failure type, e.g. ResultType.CRASH. + + Args: + results: A list of base_test_result.BaseTestResult objects. + full_test_name: A string containing the full name of the test, e.g. + org.chromium.chrome.SomeTestClass#someTestMethod. + """ + found_matching_test = _MatchingTestInResults(results, full_test_name) + if not found_matching_test and _ShouldReportNoMatchingResult(full_test_name): + logging.error( + 'Could not find result specific to %s, failing all tests in the batch.', + full_test_name) + for result in results: + if found_matching_test and result.GetName() != full_test_name: + continue + if result.GetType() not in [ + base_test_result.ResultType.FAIL, base_test_result.ResultType.CRASH, + base_test_result.ResultType.TIMEOUT, base_test_result.ResultType.UNKNOWN + ]: + result.SetType(base_test_result.ResultType.FAIL) + + +def _AppendToLog(results, full_test_name, line): + """Appends the given line to the end of the logs of the given results. + + Args: + results: A list of base_test_result.BaseTestResult objects. + full_test_name: A string containing the full name of the test, e.g. + org.chromium.chrome.SomeTestClass#someTestMethod. + line: A string to be appended as a neww line to the log of |result|. + """ + found_matching_test = _MatchingTestInResults(results, full_test_name) + if not found_matching_test and _ShouldReportNoMatchingResult(full_test_name): + logging.error( + 'Could not find result specific to %s, appending to log of all tests ' + 'in the batch.', full_test_name) + for result in results: + if found_matching_test and result.GetName() != full_test_name: + continue + _AppendToLogForResult(result, line) + + +def _AppendToLogForResult(result, line): + result.SetLog(result.GetLog() + '\n' + line) + + +def _SetLinkOnResults(results, full_test_name, link_name, link): + """Sets the given link on the given results. + + Args: + results: A list of base_test_result.BaseTestResult objects. + full_test_name: A string containing the full name of the test, e.g. + org.chromium.chrome.SomeTestClass#someTestMethod. + link_name: A string containing the name of the link being set. + link: A string containing the lkink being set. + """ + found_matching_test = _MatchingTestInResults(results, full_test_name) + if not found_matching_test and _ShouldReportNoMatchingResult(full_test_name): + logging.error( + 'Could not find result specific to %s, adding link to results of all ' + 'tests in the batch.', full_test_name) + for result in results: + if found_matching_test and result.GetName() != full_test_name: + continue + result.SetLink(link_name, link) + + +def _MatchingTestInResults(results, full_test_name): + """Checks if any tests named |full_test_name| are in |results|. + + Args: + results: A list of base_test_result.BaseTestResult objects. + full_test_name: A string containing the full name of the test, e.g. + org.chromium.chrome.Some + + Returns: + True if one of the results in |results| has the same name as + |full_test_name|, otherwise False. + """ + return any([r for r in results if r.GetName() == full_test_name]) + + +def _ShouldReportNoMatchingResult(full_test_name): + """Determines whether a failure to find a matching result is actually bad. + + Args: + full_test_name: A string containing the full name of the test, e.g. + org.chromium.chrome.Some + + Returns: + False if the failure to find a matching result is expected and should not + be reported, otherwise True. + """ + if full_test_name is not None and full_test_name.endswith(_BATCH_SUFFIX): + # Handle batched tests, whose reported name is the first test's name + + # "_batch". + return False + return True diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run_test.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run_test.py new file mode 100755 index 0000000000..948e34c17a --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_instrumentation_test_run_test.py @@ -0,0 +1,169 @@ +#!/usr/bin/env vpython3 +# Copyright 2017 The Chromium 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 local_device_instrumentation_test_run.""" + +# pylint: disable=protected-access + + +import unittest + +from pylib.base import base_test_result +from pylib.base import mock_environment +from pylib.base import mock_test_instance +from pylib.local.device import local_device_instrumentation_test_run + + +class LocalDeviceInstrumentationTestRunTest(unittest.TestCase): + + def setUp(self): + super(LocalDeviceInstrumentationTestRunTest, self).setUp() + self._env = mock_environment.MockEnvironment() + self._ti = mock_test_instance.MockTestInstance() + self._obj = ( + local_device_instrumentation_test_run.LocalDeviceInstrumentationTestRun( + self._env, self._ti)) + + # TODO(crbug.com/797002): Decide whether the _ShouldRetry hook is worth + # retaining and remove these tests if not. + + def testShouldRetry_failure(self): + test = { + 'annotations': {}, + 'class': 'SadTest', + 'method': 'testFailure', + 'is_junit4': True, + } + result = base_test_result.BaseTestResult( + 'SadTest.testFailure', base_test_result.ResultType.FAIL) + self.assertTrue(self._obj._ShouldRetry(test, result)) + + def testShouldRetry_retryOnFailure(self): + test = { + 'annotations': {'RetryOnFailure': None}, + 'class': 'SadTest', + 'method': 'testRetryOnFailure', + 'is_junit4': True, + } + result = base_test_result.BaseTestResult( + 'SadTest.testRetryOnFailure', base_test_result.ResultType.FAIL) + self.assertTrue(self._obj._ShouldRetry(test, result)) + + def testShouldRetry_notRun(self): + test = { + 'annotations': {}, + 'class': 'SadTest', + 'method': 'testNotRun', + 'is_junit4': True, + } + result = base_test_result.BaseTestResult( + 'SadTest.testNotRun', base_test_result.ResultType.NOTRUN) + self.assertTrue(self._obj._ShouldRetry(test, result)) + + def testIsWPRRecordReplayTest_matchedWithKey(self): + test = { + 'annotations': { + 'Feature': { + 'value': ['WPRRecordReplayTest', 'dummy'] + } + }, + 'class': 'WPRDummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertTrue( + local_device_instrumentation_test_run._IsWPRRecordReplayTest(test)) + + def testIsWPRRecordReplayTest_noMatchedKey(self): + test = { + 'annotations': { + 'Feature': { + 'value': ['abc', 'dummy'] + } + }, + 'class': 'WPRDummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertFalse( + local_device_instrumentation_test_run._IsWPRRecordReplayTest(test)) + + def testGetWPRArchivePath_matchedWithKey(self): + test = { + 'annotations': { + 'WPRArchiveDirectory': { + 'value': 'abc' + } + }, + 'class': 'WPRDummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertEqual( + local_device_instrumentation_test_run._GetWPRArchivePath(test), 'abc') + + def testGetWPRArchivePath_noMatchedWithKey(self): + test = { + 'annotations': { + 'Feature': { + 'value': 'abc' + } + }, + 'class': 'WPRDummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertFalse( + local_device_instrumentation_test_run._GetWPRArchivePath(test)) + + def testIsRenderTest_matchedWithKey(self): + test = { + 'annotations': { + 'Feature': { + 'value': ['RenderTest', 'dummy'] + } + }, + 'class': 'DummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertTrue(local_device_instrumentation_test_run._IsRenderTest(test)) + + def testIsRenderTest_noMatchedKey(self): + test = { + 'annotations': { + 'Feature': { + 'value': ['abc', 'dummy'] + } + }, + 'class': 'DummyTest', + 'method': 'testRun', + 'is_junit4': True, + } + self.assertFalse(local_device_instrumentation_test_run._IsRenderTest(test)) + + def testReplaceUncommonChars(self): + original = 'abc#edf' + self.assertEqual( + local_device_instrumentation_test_run._ReplaceUncommonChars(original), + 'abc__edf') + original = 'abc#edf#hhf' + self.assertEqual( + local_device_instrumentation_test_run._ReplaceUncommonChars(original), + 'abc__edf__hhf') + original = 'abcedfhhf' + self.assertEqual( + local_device_instrumentation_test_run._ReplaceUncommonChars(original), + 'abcedfhhf') + original = None + with self.assertRaises(ValueError): + local_device_instrumentation_test_run._ReplaceUncommonChars(original) + original = '' + with self.assertRaises(ValueError): + local_device_instrumentation_test_run._ReplaceUncommonChars(original) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_monkey_test_run.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_monkey_test_run.py new file mode 100644 index 0000000000..71dd9bd793 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_monkey_test_run.py @@ -0,0 +1,128 @@ +# 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. + + +import logging + +from six.moves import range # pylint: disable=redefined-builtin +from devil.android import device_errors +from devil.android.sdk import intent +from pylib import constants +from pylib.base import base_test_result +from pylib.local.device import local_device_test_run + + +_CHROME_PACKAGE = constants.PACKAGE_INFO['chrome'].package + +class LocalDeviceMonkeyTestRun(local_device_test_run.LocalDeviceTestRun): + def __init__(self, env, test_instance): + super(LocalDeviceMonkeyTestRun, self).__init__(env, test_instance) + + def TestPackage(self): + return 'monkey' + + #override + def SetUp(self): + pass + + #override + def _RunTest(self, device, test): + device.ClearApplicationState(self._test_instance.package) + + # Chrome crashes are not always caught by Monkey test runner. + # Launch Chrome and verify Chrome has the same PID before and after + # the test. + device.StartActivity( + intent.Intent(package=self._test_instance.package, + activity=self._test_instance.activity, + action='android.intent.action.MAIN'), + blocking=True, force_stop=True) + before_pids = device.GetPids(self._test_instance.package) + + output = '' + if before_pids: + if len(before_pids.get(self._test_instance.package, [])) > 1: + raise Exception( + 'At most one instance of process %s expected but found pids: ' + '%s' % (self._test_instance.package, before_pids)) + output = '\n'.join(self._LaunchMonkeyTest(device)) + after_pids = device.GetPids(self._test_instance.package) + + crashed = True + if not self._test_instance.package in before_pids: + logging.error('Failed to start the process.') + elif not self._test_instance.package in after_pids: + logging.error('Process %s has died.', + before_pids[self._test_instance.package]) + elif (before_pids[self._test_instance.package] != + after_pids[self._test_instance.package]): + logging.error('Detected process restart %s -> %s', + before_pids[self._test_instance.package], + after_pids[self._test_instance.package]) + else: + crashed = False + + success_pattern = 'Events injected: %d' % self._test_instance.event_count + if success_pattern in output and not crashed: + result = base_test_result.BaseTestResult( + test, base_test_result.ResultType.PASS, log=output) + else: + result = base_test_result.BaseTestResult( + test, base_test_result.ResultType.FAIL, log=output) + if 'chrome' in self._test_instance.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._test_instance.package, + activity='%s.crash.MinidumpUploadService' % _CHROME_PACKAGE) + try: + device.RunShellCommand( + ['am', 'startservice'] + minidump_intent.am_args, + as_root=True, check_return=True) + except device_errors.CommandFailedError: + logging.exception('Failed to start MinidumpUploadService') + + return result, None + + #override + def TearDown(self): + pass + + #override + def _CreateShards(self, tests): + return tests + + #override + def _ShouldShard(self): + # TODO(mikecase): Run Monkey test concurrently on each attached device. + return False + + #override + def _GetTests(self): + return ['MonkeyTest'] + + def _LaunchMonkeyTest(self, device): + try: + cmd = ['monkey', + '-p', self._test_instance.package, + '--throttle', str(self._test_instance.throttle), + '-s', str(self._test_instance.seed), + '--monitor-native-crashes', + '--kill-process-after-error'] + for category in self._test_instance.categories: + cmd.extend(['-c', category]) + for _ in range(self._test_instance.verbose_count): + cmd.append('-v') + cmd.append(str(self._test_instance.event_count)) + return device.RunShellCommand( + cmd, timeout=self._test_instance.timeout, check_return=True) + finally: + try: + # Kill the monkey test process on the device. If you manually + # interrupt the test run, this will prevent the monkey test from + # continuing to run. + device.KillAll('com.android.commands.monkey') + except device_errors.CommandFailedError: + pass diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run.py new file mode 100644 index 0000000000..645d9c7471 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run.py @@ -0,0 +1,395 @@ +# 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 logging +import posixpath +import signal +try: + import _thread as thread +except ImportError: + import thread +import threading + +from devil import base_error +from devil.android import crash_handler +from devil.android import device_errors +from devil.android.sdk import version_codes +from devil.android.tools import device_recovery +from devil.utils import signal_handler +from pylib import valgrind_tools +from pylib.base import base_test_result +from pylib.base import test_run +from pylib.base import test_collection +from pylib.local.device import local_device_environment + + +_SIGTERM_TEST_LOG = ( + ' Suite execution terminated, probably due to swarming timeout.\n' + ' Your test may not have run.') + + +def SubstituteDeviceRoot(device_path, device_root): + if not device_path: + return device_root + elif isinstance(device_path, list): + return posixpath.join(*(p if p else device_root for p in device_path)) + else: + return device_path + + +class TestsTerminated(Exception): + pass + + +class InvalidShardingSettings(Exception): + def __init__(self, shard_index, total_shards): + super(InvalidShardingSettings, self).__init__( + 'Invalid sharding settings. shard_index: %d total_shards: %d' + % (shard_index, total_shards)) + + +class LocalDeviceTestRun(test_run.TestRun): + + def __init__(self, env, test_instance): + super(LocalDeviceTestRun, self).__init__(env, test_instance) + self._tools = {} + # This is intended to be filled by a child class. + self._installed_packages = [] + env.SetPreferredAbis(test_instance.GetPreferredAbis()) + + #override + def RunTests(self, results): + tests = self._GetTests() + + exit_now = threading.Event() + + @local_device_environment.handle_shard_failures + def run_tests_on_device(dev, tests, results): + # This is performed here instead of during setup because restarting the + # device clears app compatibility flags, which will happen if a device + # needs to be recovered. + SetAppCompatibilityFlagsIfNecessary(self._installed_packages, dev) + consecutive_device_errors = 0 + for test in tests: + if not test: + logging.warning('No tests in shared. Continuing.') + tests.test_completed() + continue + if exit_now.isSet(): + thread.exit() + + result = None + rerun = None + try: + result, rerun = crash_handler.RetryOnSystemCrash( + lambda d, t=test: self._RunTest(d, t), + device=dev) + consecutive_device_errors = 0 + if isinstance(result, base_test_result.BaseTestResult): + results.AddResult(result) + elif isinstance(result, list): + results.AddResults(result) + else: + raise Exception( + 'Unexpected result type: %s' % type(result).__name__) + except device_errors.CommandTimeoutError: + # Test timeouts don't count as device errors for the purpose + # of bad device detection. + consecutive_device_errors = 0 + + if isinstance(test, list): + results.AddResults( + base_test_result.BaseTestResult( + self._GetUniqueTestName(t), + base_test_result.ResultType.TIMEOUT) for t in test) + else: + results.AddResult( + base_test_result.BaseTestResult( + self._GetUniqueTestName(test), + base_test_result.ResultType.TIMEOUT)) + except Exception as e: # pylint: disable=broad-except + if isinstance(tests, test_collection.TestCollection): + rerun = test + if (isinstance(e, device_errors.DeviceUnreachableError) + or not isinstance(e, base_error.BaseError)): + # If we get a device error but believe the device is still + # reachable, attempt to continue using it. Otherwise, raise + # the exception and terminate this run_tests_on_device call. + raise + + consecutive_device_errors += 1 + if consecutive_device_errors >= 3: + # We believe the device is still reachable and may still be usable, + # but if it fails repeatedly, we shouldn't attempt to keep using + # it. + logging.error('Repeated failures on device %s. Abandoning.', + str(dev)) + raise + + logging.exception( + 'Attempting to continue using device %s despite failure (%d/3).', + str(dev), consecutive_device_errors) + + finally: + if isinstance(tests, test_collection.TestCollection): + if rerun: + tests.add(rerun) + tests.test_completed() + + logging.info('Finished running tests on this device.') + + def stop_tests(_signum, _frame): + logging.critical('Received SIGTERM. Stopping test execution.') + exit_now.set() + raise TestsTerminated() + + try: + with signal_handler.AddSignalHandler(signal.SIGTERM, stop_tests): + self._env.ResetCurrentTry() + while self._env.current_try < self._env.max_tries and tests: + tries = self._env.current_try + grouped_tests = self._GroupTests(tests) + logging.info('STARTING TRY #%d/%d', tries + 1, self._env.max_tries) + if tries > 0 and self._env.recover_devices: + if any(d.build_version_sdk == version_codes.LOLLIPOP_MR1 + for d in self._env.devices): + logging.info( + 'Attempting to recover devices due to known issue on L MR1. ' + 'See crbug.com/787056 for details.') + self._env.parallel_devices.pMap( + device_recovery.RecoverDevice, None) + elif tries + 1 == self._env.max_tries: + logging.info( + 'Attempting to recover devices prior to last test attempt.') + self._env.parallel_devices.pMap( + device_recovery.RecoverDevice, None) + logging.info('Will run %d tests on %d devices: %s', + len(tests), len(self._env.devices), + ', '.join(str(d) for d in self._env.devices)) + for t in tests: + logging.debug(' %s', t) + + try_results = base_test_result.TestRunResults() + test_names = (self._GetUniqueTestName(t) for t in tests) + try_results.AddResults( + base_test_result.BaseTestResult( + t, base_test_result.ResultType.NOTRUN) + for t in test_names if not t.endswith('*')) + + # As soon as we know the names of the tests, we populate |results|. + # The tests in try_results will have their results updated by + # try_results.AddResult() as they are run. + results.append(try_results) + + try: + if self._ShouldShard(): + tc = test_collection.TestCollection( + self._CreateShards(grouped_tests)) + self._env.parallel_devices.pMap( + run_tests_on_device, tc, try_results).pGet(None) + else: + self._env.parallel_devices.pMap(run_tests_on_device, + grouped_tests, + try_results).pGet(None) + except TestsTerminated: + for unknown_result in try_results.GetUnknown(): + try_results.AddResult( + base_test_result.BaseTestResult( + unknown_result.GetName(), + base_test_result.ResultType.TIMEOUT, + log=_SIGTERM_TEST_LOG)) + raise + + self._env.IncrementCurrentTry() + tests = self._GetTestsToRetry(tests, try_results) + + logging.info('FINISHED TRY #%d/%d', tries + 1, self._env.max_tries) + if tests: + logging.info('%d failed tests remain.', len(tests)) + else: + logging.info('All tests completed.') + except TestsTerminated: + pass + + def _GetTestsToRetry(self, tests, try_results): + + def is_failure_result(test_result): + if isinstance(test_result, list): + return any(is_failure_result(r) for r in test_result) + return ( + test_result is None + or test_result.GetType() not in ( + base_test_result.ResultType.PASS, + base_test_result.ResultType.SKIP)) + + all_test_results = {r.GetName(): r for r in try_results.GetAll()} + + tests_and_names = ((t, self._GetUniqueTestName(t)) for t in tests) + + tests_and_results = {} + for test, name in tests_and_names: + if name.endswith('*'): + tests_and_results[name] = (test, [ + r for n, r in all_test_results.items() if fnmatch.fnmatch(n, name) + ]) + else: + tests_and_results[name] = (test, all_test_results.get(name)) + + failed_tests_and_results = ((test, result) + for test, result in tests_and_results.values() + if is_failure_result(result)) + + return [t for t, r in failed_tests_and_results if self._ShouldRetry(t, r)] + + def _ApplyExternalSharding(self, tests, shard_index, total_shards): + logging.info('Using external sharding settings. This is shard %d/%d', + shard_index, total_shards) + + if total_shards < 0 or shard_index < 0 or total_shards <= shard_index: + raise InvalidShardingSettings(shard_index, total_shards) + + sharded_tests = [] + + # Group tests by tests that should run in the same test invocation - either + # unit tests or batched tests. + grouped_tests = self._GroupTests(tests) + + # Partition grouped tests approximately evenly across shards. + partitioned_tests = self._PartitionTests(grouped_tests, total_shards, + float('inf')) + if len(partitioned_tests) <= shard_index: + return [] + for t in partitioned_tests[shard_index]: + if isinstance(t, list): + sharded_tests.extend(t) + else: + sharded_tests.append(t) + return sharded_tests + + # Partition tests evenly into |num_desired_partitions| partitions where + # possible. However, many constraints make partitioning perfectly impossible. + # If the max_partition_size isn't large enough, extra partitions may be + # created (infinite max size should always return precisely the desired + # number of partitions). Even if the |max_partition_size| is technically large + # enough to hold all of the tests in |num_desired_partitions|, we attempt to + # keep test order relatively stable to minimize flakes, so when tests are + # grouped (eg. batched tests), we cannot perfectly fill all paritions as that + # would require breaking up groups. + def _PartitionTests(self, tests, num_desired_partitions, max_partition_size): + # pylint: disable=no-self-use + partitions = [] + + # Sort by hash so we don't put all tests in a slow suite in the same + # partition. + tests = sorted( + tests, + key=lambda t: hash( + self._GetUniqueTestName(t[0] if isinstance(t, list) else t))) + + def CountTestsIndividually(test): + if not isinstance(test, list): + return False + annotations = test[0]['annotations'] + # UnitTests tests are really fast, so to balance shards better, count + # UnitTests Batches as single tests. + return ('Batch' not in annotations + or annotations['Batch']['value'] != 'UnitTests') + + num_not_yet_allocated = sum( + [len(test) - 1 for test in tests if CountTestsIndividually(test)]) + num_not_yet_allocated += len(tests) + + # Fast linear partition approximation capped by max_partition_size. We + # cannot round-robin or otherwise re-order tests dynamically because we want + # test order to remain stable. + partition_size = min(num_not_yet_allocated // num_desired_partitions, + max_partition_size) + partitions.append([]) + last_partition_size = 0 + for test in tests: + test_count = len(test) if CountTestsIndividually(test) else 1 + # Make a new shard whenever we would overfill the previous one. However, + # if the size of the test group is larger than the max partition size on + # its own, just put the group in its own shard instead of splitting up the + # group. + if (last_partition_size + test_count > partition_size + and last_partition_size > 0): + num_desired_partitions -= 1 + if num_desired_partitions <= 0: + # Too many tests for number of partitions, just fill all partitions + # beyond num_desired_partitions. + partition_size = max_partition_size + else: + # Re-balance remaining partitions. + partition_size = min(num_not_yet_allocated // num_desired_partitions, + max_partition_size) + partitions.append([]) + partitions[-1].append(test) + last_partition_size = test_count + else: + partitions[-1].append(test) + last_partition_size += test_count + + num_not_yet_allocated -= test_count + + if not partitions[-1]: + partitions.pop() + return partitions + + def GetTool(self, device): + if str(device) not 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 _GetUniqueTestName(self, test): + # pylint: disable=no-self-use + return test + + def _ShouldRetry(self, test, result): + # pylint: disable=no-self-use,unused-argument + return True + + def _GetTests(self): + raise NotImplementedError + + def _GroupTests(self, tests): + # pylint: disable=no-self-use + return tests + + def _RunTest(self, device, test): + raise NotImplementedError + + def _ShouldShard(self): + raise NotImplementedError + + +def SetAppCompatibilityFlagsIfNecessary(packages, device): + """Sets app compatibility flags on the given packages and device. + + Args: + packages: A list of strings containing package names to apply flags to. + device: A DeviceUtils instance to apply the flags on. + """ + + def set_flag_for_packages(flag, enable): + enable_str = 'enable' if enable else 'disable' + for p in packages: + cmd = ['am', 'compat', enable_str, flag, p] + device.RunShellCommand(cmd) + + sdk_version = device.build_version_sdk + if sdk_version >= version_codes.R: + # These flags are necessary to use the legacy storage permissions on R+. + # See crbug.com/1173699 for more information. + set_flag_for_packages('DEFAULT_SCOPED_STORAGE', False) + set_flag_for_packages('FORCE_ENABLE_SCOPED_STORAGE', False) + + +class NoTestsError(Exception): + """Error for when no tests are found.""" diff --git a/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run_test.py b/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run_test.py new file mode 100755 index 0000000000..0f6c9b5421 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/device/local_device_test_run_test.py @@ -0,0 +1,171 @@ +#!/usr/bin/env vpython3 +# 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. + +# pylint: disable=protected-access + + +import unittest + +from pylib.base import base_test_result +from pylib.local.device import local_device_test_run + +import mock # pylint: disable=import-error + + +class SubstituteDeviceRootTest(unittest.TestCase): + + def testNoneDevicePath(self): + self.assertEqual( + '/fake/device/root', + local_device_test_run.SubstituteDeviceRoot(None, '/fake/device/root')) + + def testStringDevicePath(self): + self.assertEqual( + '/another/fake/device/path', + local_device_test_run.SubstituteDeviceRoot('/another/fake/device/path', + '/fake/device/root')) + + def testListWithNoneDevicePath(self): + self.assertEqual( + '/fake/device/root/subpath', + local_device_test_run.SubstituteDeviceRoot([None, 'subpath'], + '/fake/device/root')) + + def testListWithoutNoneDevicePath(self): + self.assertEqual( + '/another/fake/device/path', + local_device_test_run.SubstituteDeviceRoot( + ['/', 'another', 'fake', 'device', 'path'], '/fake/device/root')) + + +class TestLocalDeviceTestRun(local_device_test_run.LocalDeviceTestRun): + + # pylint: disable=abstract-method + + def __init__(self): + super(TestLocalDeviceTestRun, self).__init__( + mock.MagicMock(), mock.MagicMock()) + + +class TestLocalDeviceNonStringTestRun( + local_device_test_run.LocalDeviceTestRun): + + # pylint: disable=abstract-method + + def __init__(self): + super(TestLocalDeviceNonStringTestRun, self).__init__( + mock.MagicMock(), mock.MagicMock()) + + def _GetUniqueTestName(self, test): + return test['name'] + + +class LocalDeviceTestRunTest(unittest.TestCase): + + def testGetTestsToRetry_allTestsPassed(self): + results = [ + base_test_result.BaseTestResult( + 'Test1', base_test_result.ResultType.PASS), + base_test_result.BaseTestResult( + 'Test2', base_test_result.ResultType.PASS), + ] + + tests = [r.GetName() for r in results] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(0, len(tests_to_retry)) + + def testGetTestsToRetry_testFailed(self): + results = [ + base_test_result.BaseTestResult( + 'Test1', base_test_result.ResultType.FAIL), + base_test_result.BaseTestResult( + 'Test2', base_test_result.ResultType.PASS), + ] + + tests = [r.GetName() for r in results] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(1, len(tests_to_retry)) + self.assertIn('Test1', tests_to_retry) + + def testGetTestsToRetry_testUnknown(self): + results = [ + base_test_result.BaseTestResult( + 'Test2', base_test_result.ResultType.PASS), + ] + + tests = ['Test1'] + [r.GetName() for r in results] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(1, len(tests_to_retry)) + self.assertIn('Test1', tests_to_retry) + + def testGetTestsToRetry_wildcardFilter_allPass(self): + results = [ + base_test_result.BaseTestResult( + 'TestCase.Test1', base_test_result.ResultType.PASS), + base_test_result.BaseTestResult( + 'TestCase.Test2', base_test_result.ResultType.PASS), + ] + + tests = ['TestCase.*'] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(0, len(tests_to_retry)) + + def testGetTestsToRetry_wildcardFilter_oneFails(self): + results = [ + base_test_result.BaseTestResult( + 'TestCase.Test1', base_test_result.ResultType.PASS), + base_test_result.BaseTestResult( + 'TestCase.Test2', base_test_result.ResultType.FAIL), + ] + + tests = ['TestCase.*'] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(1, len(tests_to_retry)) + self.assertIn('TestCase.*', tests_to_retry) + + def testGetTestsToRetry_nonStringTests(self): + results = [ + base_test_result.BaseTestResult( + 'TestCase.Test1', base_test_result.ResultType.PASS), + base_test_result.BaseTestResult( + 'TestCase.Test2', base_test_result.ResultType.FAIL), + ] + + tests = [ + {'name': 'TestCase.Test1'}, + {'name': 'TestCase.Test2'}, + ] + try_results = base_test_result.TestRunResults() + try_results.AddResults(results) + + test_run = TestLocalDeviceNonStringTestRun() + tests_to_retry = test_run._GetTestsToRetry(tests, try_results) + self.assertEqual(1, len(tests_to_retry)) + self.assertIsInstance(tests_to_retry[0], dict) + self.assertEqual(tests[1], tests_to_retry[0]) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/OWNERS b/third_party/libwebrtc/build/android/pylib/local/emulator/OWNERS new file mode 100644 index 0000000000..0853590d4b --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/OWNERS @@ -0,0 +1,4 @@ +bpastene@chromium.org +hypan@google.com +jbudorick@chromium.org +liaoyuke@chromium.org diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/__init__.py b/third_party/libwebrtc/build/android/pylib/local/emulator/__init__.py new file mode 100644 index 0000000000..4a12e35c92 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2019 The Chromium 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/third_party/libwebrtc/build/android/pylib/local/emulator/avd.py b/third_party/libwebrtc/build/android/pylib/local/emulator/avd.py new file mode 100644 index 0000000000..d32fbd93e7 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/avd.py @@ -0,0 +1,629 @@ +# Copyright 2019 The Chromium 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 json +import logging +import os +import socket +import stat +import subprocess +import threading + +from google.protobuf import text_format # pylint: disable=import-error + +from devil.android import device_utils +from devil.android.sdk import adb_wrapper +from devil.utils import cmd_helper +from devil.utils import timeout_retry +from py_utils import tempfile_ext +from pylib import constants +from pylib.local.emulator import ini +from pylib.local.emulator.proto import avd_pb2 + +_ALL_PACKAGES = object() +_DEFAULT_AVDMANAGER_PATH = os.path.join( + constants.ANDROID_SDK_ROOT, 'cmdline-tools', 'latest', 'bin', 'avdmanager') +# Default to a 480dp mdpi screen (a relatively large phone). +# See https://developer.android.com/training/multiscreen/screensizes +# and https://developer.android.com/training/multiscreen/screendensities +# for more information. +_DEFAULT_SCREEN_DENSITY = 160 +_DEFAULT_SCREEN_HEIGHT = 960 +_DEFAULT_SCREEN_WIDTH = 480 + +# Default to swiftshader_indirect since it works for most cases. +_DEFAULT_GPU_MODE = 'swiftshader_indirect' + + +class AvdException(Exception): + """Raised when this module has a problem interacting with an AVD.""" + + def __init__(self, summary, command=None, stdout=None, stderr=None): + message_parts = [summary] + if command: + message_parts.append(' command: %s' % ' '.join(command)) + if stdout: + message_parts.append(' stdout:') + message_parts.extend(' %s' % line for line in stdout.splitlines()) + if stderr: + message_parts.append(' stderr:') + message_parts.extend(' %s' % line for line in stderr.splitlines()) + + super(AvdException, self).__init__('\n'.join(message_parts)) + + +def _Load(avd_proto_path): + """Loads an Avd proto from a textpb file at the given path. + + Should not be called outside of this module. + + Args: + avd_proto_path: path to a textpb file containing an Avd message. + """ + with open(avd_proto_path) as avd_proto_file: + return text_format.Merge(avd_proto_file.read(), avd_pb2.Avd()) + + +class _AvdManagerAgent(object): + """Private utility for interacting with avdmanager.""" + + def __init__(self, avd_home, sdk_root): + """Create an _AvdManagerAgent. + + Args: + avd_home: path to ANDROID_AVD_HOME directory. + Typically something like /path/to/dir/.android/avd + sdk_root: path to SDK root directory. + """ + self._avd_home = avd_home + self._sdk_root = sdk_root + + self._env = dict(os.environ) + + # The avdmanager from cmdline-tools would look two levels + # up from toolsdir to find the SDK root. + # Pass avdmanager a fake directory under the directory in which + # we install the system images s.t. avdmanager can find the + # system images. + fake_tools_dir = os.path.join(self._sdk_root, 'non-existent-tools', + 'non-existent-version') + self._env.update({ + 'ANDROID_AVD_HOME': + self._avd_home, + 'AVDMANAGER_OPTS': + '-Dcom.android.sdkmanager.toolsdir=%s' % fake_tools_dir, + }) + + def Create(self, avd_name, system_image, force=False): + """Call `avdmanager create`. + + Args: + avd_name: name of the AVD to create. + system_image: system image to use for the AVD. + force: whether to force creation, overwriting any existing + AVD with the same name. + """ + create_cmd = [ + _DEFAULT_AVDMANAGER_PATH, + '-v', + 'create', + 'avd', + '-n', + avd_name, + '-k', + system_image, + ] + if force: + create_cmd += ['--force'] + + create_proc = cmd_helper.Popen( + create_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self._env) + output, error = create_proc.communicate(input='\n') + if create_proc.returncode != 0: + raise AvdException( + 'AVD creation failed', + command=create_cmd, + stdout=output, + stderr=error) + + for line in output.splitlines(): + logging.info(' %s', line) + + def Delete(self, avd_name): + """Call `avdmanager delete`. + + Args: + avd_name: name of the AVD to delete. + """ + delete_cmd = [ + _DEFAULT_AVDMANAGER_PATH, + '-v', + 'delete', + 'avd', + '-n', + avd_name, + ] + try: + for line in cmd_helper.IterCmdOutputLines(delete_cmd, env=self._env): + logging.info(' %s', line) + except subprocess.CalledProcessError as e: + raise AvdException('AVD deletion failed: %s' % str(e), command=delete_cmd) + + +class AvdConfig(object): + """Represents a particular AVD configuration. + + This class supports creation, installation, and execution of an AVD + from a given Avd proto message, as defined in + //build/android/pylib/local/emulator/proto/avd.proto. + """ + + def __init__(self, avd_proto_path): + """Create an AvdConfig object. + + Args: + avd_proto_path: path to a textpb file containing an Avd message. + """ + self._config = _Load(avd_proto_path) + + self._emulator_home = os.path.join(constants.DIR_SOURCE_ROOT, + self._config.avd_package.dest_path) + self._emulator_sdk_root = os.path.join( + constants.DIR_SOURCE_ROOT, self._config.emulator_package.dest_path) + self._emulator_path = os.path.join(self._emulator_sdk_root, 'emulator', + 'emulator') + + self._initialized = False + self._initializer_lock = threading.Lock() + + @property + def avd_settings(self): + return self._config.avd_settings + + def Create(self, + force=False, + snapshot=False, + keep=False, + cipd_json_output=None, + dry_run=False): + """Create an instance of the AVD CIPD package. + + This method: + - installs the requisite system image + - creates the AVD + - modifies the AVD's ini files to support running chromium tests + in chromium infrastructure + - optionally starts & stops the AVD for snapshotting (default no) + - By default creates and uploads an instance of the AVD CIPD package + (can be turned off by dry_run flag). + - optionally deletes the AVD (default yes) + + Args: + force: bool indicating whether to force create the AVD. + snapshot: bool indicating whether to snapshot the AVD before creating + the CIPD package. + keep: bool indicating whether to keep the AVD after creating + the CIPD package. + cipd_json_output: string path to pass to `cipd create` via -json-output. + dry_run: When set to True, it will skip the CIPD package creation + after creating the AVD. + """ + logging.info('Installing required packages.') + self._InstallCipdPackages(packages=[ + self._config.emulator_package, + self._config.system_image_package, + ]) + + android_avd_home = os.path.join(self._emulator_home, 'avd') + + if not os.path.exists(android_avd_home): + os.makedirs(android_avd_home) + + avd_manager = _AvdManagerAgent( + avd_home=android_avd_home, sdk_root=self._emulator_sdk_root) + + logging.info('Creating AVD.') + avd_manager.Create( + avd_name=self._config.avd_name, + system_image=self._config.system_image_name, + force=force) + + try: + logging.info('Modifying AVD configuration.') + + # Clear out any previous configuration or state from this AVD. + root_ini = os.path.join(android_avd_home, + '%s.ini' % self._config.avd_name) + features_ini = os.path.join(self._emulator_home, 'advancedFeatures.ini') + avd_dir = os.path.join(android_avd_home, '%s.avd' % self._config.avd_name) + config_ini = os.path.join(avd_dir, 'config.ini') + + with ini.update_ini_file(root_ini) as root_ini_contents: + root_ini_contents['path.rel'] = 'avd/%s.avd' % self._config.avd_name + + with ini.update_ini_file(features_ini) as features_ini_contents: + # features_ini file will not be refreshed by avdmanager during + # creation. So explicitly clear its content to exclude any leftover + # from previous creation. + features_ini_contents.clear() + features_ini_contents.update(self.avd_settings.advanced_features) + + with ini.update_ini_file(config_ini) as config_ini_contents: + height = self.avd_settings.screen.height or _DEFAULT_SCREEN_HEIGHT + width = self.avd_settings.screen.width or _DEFAULT_SCREEN_WIDTH + density = self.avd_settings.screen.density or _DEFAULT_SCREEN_DENSITY + + config_ini_contents.update({ + 'disk.dataPartition.size': '4G', + 'hw.keyboard': 'yes', + 'hw.lcd.density': density, + 'hw.lcd.height': height, + 'hw.lcd.width': width, + 'hw.mainKeys': 'no', # Show nav buttons on screen + }) + + if self.avd_settings.ram_size: + config_ini_contents['hw.ramSize'] = self.avd_settings.ram_size + + # Start & stop the AVD. + self._Initialize() + instance = _AvdInstance(self._emulator_path, self._emulator_home, + self._config) + # Enable debug for snapshot when it is set to True + debug_tags = 'init,snapshot' if snapshot else None + instance.Start(read_only=False, + snapshot_save=snapshot, + debug_tags=debug_tags, + gpu_mode=_DEFAULT_GPU_MODE) + # Android devices with full-disk encryption are encrypted on first boot, + # and then get decrypted to continue the boot process (See details in + # https://bit.ly/3agmjcM). + # Wait for this step to complete since it can take a while for old OSs + # like M, otherwise the avd may have "Encryption Unsuccessful" error. + device = device_utils.DeviceUtils(instance.serial) + device.WaitUntilFullyBooted(decrypt=True, timeout=180, retries=0) + + # Skip network disabling on pre-N for now since the svc commands fail + # on Marshmallow. + if device.build_version_sdk > 23: + # Always disable the network to prevent built-in system apps from + # updating themselves, which could take over package manager and + # cause shell command timeout. + # Use svc as this also works on the images with build type "user". + logging.info('Disabling the network in emulator.') + device.RunShellCommand(['svc', 'wifi', 'disable'], check_return=True) + device.RunShellCommand(['svc', 'data', 'disable'], check_return=True) + + instance.Stop() + + # The multiinstance lock file seems to interfere with the emulator's + # operation in some circumstances (beyond the obvious -read-only ones), + # and there seems to be no mechanism by which it gets closed or deleted. + # See https://bit.ly/2pWQTH7 for context. + multiInstanceLockFile = os.path.join(avd_dir, 'multiinstance.lock') + if os.path.exists(multiInstanceLockFile): + os.unlink(multiInstanceLockFile) + + package_def_content = { + 'package': + self._config.avd_package.package_name, + 'root': + self._emulator_home, + 'install_mode': + 'copy', + 'data': [{ + 'dir': os.path.relpath(avd_dir, self._emulator_home) + }, { + 'file': os.path.relpath(root_ini, self._emulator_home) + }, { + 'file': os.path.relpath(features_ini, self._emulator_home) + }], + } + + logging.info('Creating AVD CIPD package.') + logging.debug('ensure file content: %s', + json.dumps(package_def_content, indent=2)) + + with tempfile_ext.TemporaryFileName(suffix='.json') as package_def_path: + with open(package_def_path, 'w') as package_def_file: + json.dump(package_def_content, package_def_file) + + logging.info(' %s', self._config.avd_package.package_name) + cipd_create_cmd = [ + 'cipd', + 'create', + '-pkg-def', + package_def_path, + '-tag', + 'emulator_version:%s' % self._config.emulator_package.version, + '-tag', + 'system_image_version:%s' % + self._config.system_image_package.version, + ] + if cipd_json_output: + cipd_create_cmd.extend([ + '-json-output', + cipd_json_output, + ]) + logging.info('running %r%s', cipd_create_cmd, + ' (dry_run)' if dry_run else '') + if not dry_run: + try: + for line in cmd_helper.IterCmdOutputLines(cipd_create_cmd): + logging.info(' %s', line) + except subprocess.CalledProcessError as e: + raise AvdException( + 'CIPD package creation failed: %s' % str(e), + command=cipd_create_cmd) + + finally: + if not keep: + logging.info('Deleting AVD.') + avd_manager.Delete(avd_name=self._config.avd_name) + + def Install(self, packages=_ALL_PACKAGES): + """Installs the requested CIPD packages and prepares them for use. + + This includes making files writeable and revising some of the + emulator's internal config files. + + Returns: None + Raises: AvdException on failure to install. + """ + self._InstallCipdPackages(packages=packages) + self._MakeWriteable() + self._EditConfigs() + + def _InstallCipdPackages(self, packages): + pkgs_by_dir = {} + if packages is _ALL_PACKAGES: + packages = [ + self._config.avd_package, + self._config.emulator_package, + self._config.system_image_package, + ] + for pkg in packages: + if not pkg.dest_path in pkgs_by_dir: + pkgs_by_dir[pkg.dest_path] = [] + pkgs_by_dir[pkg.dest_path].append(pkg) + + for pkg_dir, pkgs in list(pkgs_by_dir.items()): + logging.info('Installing packages in %s', pkg_dir) + cipd_root = os.path.join(constants.DIR_SOURCE_ROOT, pkg_dir) + if not os.path.exists(cipd_root): + os.makedirs(cipd_root) + ensure_path = os.path.join(cipd_root, '.ensure') + with open(ensure_path, 'w') as ensure_file: + # Make CIPD ensure that all files are present and correct, + # even if it thinks the package is installed. + ensure_file.write('$ParanoidMode CheckIntegrity\n\n') + for pkg in pkgs: + ensure_file.write('%s %s\n' % (pkg.package_name, pkg.version)) + logging.info(' %s %s', pkg.package_name, pkg.version) + ensure_cmd = [ + 'cipd', + 'ensure', + '-ensure-file', + ensure_path, + '-root', + cipd_root, + ] + try: + for line in cmd_helper.IterCmdOutputLines(ensure_cmd): + logging.info(' %s', line) + except subprocess.CalledProcessError as e: + raise AvdException( + 'Failed to install CIPD package %s: %s' % (pkg.package_name, + str(e)), + command=ensure_cmd) + + def _MakeWriteable(self): + # The emulator requires that some files are writable. + for dirname, _, filenames in os.walk(self._emulator_home): + for f in filenames: + path = os.path.join(dirname, f) + mode = os.lstat(path).st_mode + if mode & stat.S_IRUSR: + mode = mode | stat.S_IWUSR + os.chmod(path, mode) + + def _EditConfigs(self): + android_avd_home = os.path.join(self._emulator_home, 'avd') + avd_dir = os.path.join(android_avd_home, '%s.avd' % self._config.avd_name) + + config_path = os.path.join(avd_dir, 'config.ini') + if os.path.exists(config_path): + with open(config_path) as config_file: + config_contents = ini.load(config_file) + else: + config_contents = {} + + config_contents['hw.sdCard'] = 'true' + if self.avd_settings.sdcard.size: + sdcard_path = os.path.join(avd_dir, 'cr-sdcard.img') + if not os.path.exists(sdcard_path): + mksdcard_path = os.path.join( + os.path.dirname(self._emulator_path), 'mksdcard') + mksdcard_cmd = [ + mksdcard_path, + self.avd_settings.sdcard.size, + sdcard_path, + ] + cmd_helper.RunCmd(mksdcard_cmd) + + config_contents['hw.sdCard.path'] = sdcard_path + + with open(config_path, 'w') as config_file: + ini.dump(config_contents, config_file) + + def _Initialize(self): + if self._initialized: + return + + with self._initializer_lock: + if self._initialized: + return + + # Emulator start-up looks for the adb daemon. Make sure it's running. + adb_wrapper.AdbWrapper.StartServer() + + # Emulator start-up tries to check for the SDK root by looking for + # platforms/ and platform-tools/. Ensure they exist. + # See http://bit.ly/2YAkyFE for context. + required_dirs = [ + os.path.join(self._emulator_sdk_root, 'platforms'), + os.path.join(self._emulator_sdk_root, 'platform-tools'), + ] + for d in required_dirs: + if not os.path.exists(d): + os.makedirs(d) + + def CreateInstance(self): + """Creates an AVD instance without starting it. + + Returns: + An _AvdInstance. + """ + self._Initialize() + return _AvdInstance(self._emulator_path, self._emulator_home, self._config) + + def StartInstance(self): + """Starts an AVD instance. + + Returns: + An _AvdInstance. + """ + instance = self.CreateInstance() + instance.Start() + return instance + + +class _AvdInstance(object): + """Represents a single running instance of an AVD. + + This class should only be created directly by AvdConfig.StartInstance, + but its other methods can be freely called. + """ + + def __init__(self, emulator_path, emulator_home, avd_config): + """Create an _AvdInstance object. + + Args: + emulator_path: path to the emulator binary. + emulator_home: path to the emulator home directory. + avd_config: AVD config proto. + """ + self._avd_config = avd_config + self._avd_name = avd_config.avd_name + self._emulator_home = emulator_home + self._emulator_path = emulator_path + self._emulator_proc = None + self._emulator_serial = None + self._sink = None + + def __str__(self): + return '%s|%s' % (self._avd_name, (self._emulator_serial or id(self))) + + def Start(self, + read_only=True, + snapshot_save=False, + window=False, + writable_system=False, + gpu_mode=_DEFAULT_GPU_MODE, + debug_tags=None): + """Starts the emulator running an instance of the given AVD.""" + + with tempfile_ext.TemporaryFileName() as socket_path, (contextlib.closing( + socket.socket(socket.AF_UNIX))) as sock: + sock.bind(socket_path) + emulator_cmd = [ + self._emulator_path, + '-avd', + self._avd_name, + '-report-console', + 'unix:%s' % socket_path, + '-no-boot-anim', + ] + + if read_only: + emulator_cmd.append('-read-only') + if not snapshot_save: + emulator_cmd.append('-no-snapshot-save') + if writable_system: + emulator_cmd.append('-writable-system') + # Note when "--gpu-mode" is set to "host": + # * It needs a valid DISPLAY env, even if "--emulator-window" is false. + # Otherwise it may throw errors like "Failed to initialize backend + # EGL display". See the code in https://bit.ly/3ruiMlB as an example + # to setup the DISPLAY env with xvfb. + # * It will not work under remote sessions like chrome remote desktop. + if gpu_mode: + emulator_cmd.extend(['-gpu', gpu_mode]) + if debug_tags: + emulator_cmd.extend(['-debug', debug_tags]) + + emulator_env = {} + if self._emulator_home: + emulator_env['ANDROID_EMULATOR_HOME'] = self._emulator_home + if 'DISPLAY' in os.environ: + emulator_env['DISPLAY'] = os.environ.get('DISPLAY') + if window: + if 'DISPLAY' not in emulator_env: + raise AvdException('Emulator failed to start: DISPLAY not defined') + else: + emulator_cmd.append('-no-window') + + sock.listen(1) + + logging.info('Starting emulator with commands: %s', + ' '.join(emulator_cmd)) + + # TODO(jbudorick): Add support for logging emulator stdout & stderr at + # higher logging levels. + # Enable the emulator log when debug_tags is set. + if not debug_tags: + self._sink = open('/dev/null', 'w') + self._emulator_proc = cmd_helper.Popen( + emulator_cmd, stdout=self._sink, stderr=self._sink, env=emulator_env) + + # Waits for the emulator to report its serial as requested via + # -report-console. See http://bit.ly/2lK3L18 for more. + def listen_for_serial(s): + logging.info('Waiting for connection from emulator.') + with contextlib.closing(s.accept()[0]) as conn: + val = conn.recv(1024) + return 'emulator-%d' % int(val) + + try: + self._emulator_serial = timeout_retry.Run( + listen_for_serial, timeout=30, retries=0, args=[sock]) + logging.info('%s started', self._emulator_serial) + except Exception as e: + self.Stop() + raise AvdException('Emulator failed to start: %s' % str(e)) + + def Stop(self): + """Stops the emulator process.""" + if self._emulator_proc: + if self._emulator_proc.poll() is None: + if self._emulator_serial: + device_utils.DeviceUtils(self._emulator_serial).adb.Emu('kill') + else: + self._emulator_proc.terminate() + self._emulator_proc.wait() + self._emulator_proc = None + + if self._sink: + self._sink.close() + self._sink = None + + @property + def serial(self): + return self._emulator_serial diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/ini.py b/third_party/libwebrtc/build/android/pylib/local/emulator/ini.py new file mode 100644 index 0000000000..2c5409934b --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/ini.py @@ -0,0 +1,58 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Basic .ini encoding and decoding.""" + + +import contextlib +import os + + +def loads(ini_str, strict=True): + ret = {} + for line in ini_str.splitlines(): + key, val = line.split('=', 1) + key = key.strip() + val = val.strip() + if strict and key in ret: + raise ValueError('Multiple entries present for key "%s"' % key) + ret[key] = val + + return ret + + +def load(fp): + return loads(fp.read()) + + +def dumps(obj): + ret = '' + for k, v in sorted(obj.items()): + ret += '%s = %s\n' % (k, str(v)) + return ret + + +def dump(obj, fp): + fp.write(dumps(obj)) + + +@contextlib.contextmanager +def update_ini_file(ini_file_path): + """Load and update the contents of an ini file. + + Args: + ini_file_path: A string containing the absolute path of the ini file. + Yields: + The contents of the file, as a dict + """ + if os.path.exists(ini_file_path): + with open(ini_file_path) as ini_file: + ini_contents = load(ini_file) + else: + ini_contents = {} + + yield ini_contents + + with open(ini_file_path, 'w') as ini_file: + dump(ini_contents, ini_file) diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/ini_test.py b/third_party/libwebrtc/build/android/pylib/local/emulator/ini_test.py new file mode 100755 index 0000000000..279e964304 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/ini_test.py @@ -0,0 +1,69 @@ +#! /usr/bin/env vpython3 +# Copyright 2020 The Chromium 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 ini.py.""" + + +import textwrap +import unittest + +from pylib.local.emulator import ini + + +class IniTest(unittest.TestCase): + def testLoadsBasic(self): + ini_str = textwrap.dedent("""\ + foo.bar = 1 + foo.baz= example + bar.bad =/path/to/thing + """) + expected = { + 'foo.bar': '1', + 'foo.baz': 'example', + 'bar.bad': '/path/to/thing', + } + self.assertEqual(expected, ini.loads(ini_str)) + + def testLoadsStrictFailure(self): + ini_str = textwrap.dedent("""\ + foo.bar = 1 + foo.baz = example + bar.bad = /path/to/thing + foo.bar = duplicate + """) + with self.assertRaises(ValueError): + ini.loads(ini_str, strict=True) + + def testLoadsPermissive(self): + ini_str = textwrap.dedent("""\ + foo.bar = 1 + foo.baz = example + bar.bad = /path/to/thing + foo.bar = duplicate + """) + expected = { + 'foo.bar': 'duplicate', + 'foo.baz': 'example', + 'bar.bad': '/path/to/thing', + } + self.assertEqual(expected, ini.loads(ini_str, strict=False)) + + def testDumpsBasic(self): + ini_contents = { + 'foo.bar': '1', + 'foo.baz': 'example', + 'bar.bad': '/path/to/thing', + } + # ini.dumps is expected to dump to string alphabetically + # by key. + expected = textwrap.dedent("""\ + bar.bad = /path/to/thing + foo.bar = 1 + foo.baz = example + """) + self.assertEqual(expected, ini.dumps(ini_contents)) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/local_emulator_environment.py b/third_party/libwebrtc/build/android/pylib/local/emulator/local_emulator_environment.py new file mode 100644 index 0000000000..3bd3c50a19 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/local_emulator_environment.py @@ -0,0 +1,102 @@ +# Copyright 2019 The Chromium 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 six.moves import range # pylint: disable=redefined-builtin +from devil import base_error +from devil.android import device_errors +from devil.android import device_utils +from devil.utils import parallelizer +from devil.utils import reraiser_thread +from devil.utils import timeout_retry +from pylib.local.device import local_device_environment +from pylib.local.emulator import avd + +# Mirroring https://bit.ly/2OjuxcS#23 +_MAX_ANDROID_EMULATORS = 16 + + +class LocalEmulatorEnvironment(local_device_environment.LocalDeviceEnvironment): + + def __init__(self, args, output_manager, error_func): + super(LocalEmulatorEnvironment, self).__init__(args, output_manager, + error_func) + self._avd_config = avd.AvdConfig(args.avd_config) + if args.emulator_count < 1: + error_func('--emulator-count must be >= 1') + elif args.emulator_count >= _MAX_ANDROID_EMULATORS: + logging.warning('--emulator-count capped at 16.') + self._emulator_count = min(_MAX_ANDROID_EMULATORS, args.emulator_count) + self._emulator_window = args.emulator_window + self._writable_system = ((hasattr(args, 'use_webview_provider') + and args.use_webview_provider) + or (hasattr(args, 'replace_system_package') + and args.replace_system_package) + or (hasattr(args, 'system_packages_to_remove') + and args.system_packages_to_remove)) + + self._emulator_instances = [] + self._device_serials = [] + + #override + def SetUp(self): + self._avd_config.Install() + + emulator_instances = [ + self._avd_config.CreateInstance() for _ in range(self._emulator_count) + ] + + def start_emulator_instance(e): + + def impl(e): + try: + e.Start( + window=self._emulator_window, + writable_system=self._writable_system) + except avd.AvdException: + logging.exception('Failed to start emulator instance.') + return None + try: + device_utils.DeviceUtils(e.serial).WaitUntilFullyBooted() + except base_error.BaseError: + e.Stop() + raise + return e + + def retry_on_timeout(exc): + return (isinstance(exc, device_errors.CommandTimeoutError) + or isinstance(exc, reraiser_thread.TimeoutError)) + + return timeout_retry.Run( + impl, + timeout=120 if self._writable_system else 30, + retries=2, + args=[e], + retry_if_func=retry_on_timeout) + + parallel_emulators = parallelizer.SyncParallelizer(emulator_instances) + self._emulator_instances = [ + emu + for emu in parallel_emulators.pMap(start_emulator_instance).pGet(None) + if emu is not None + ] + self._device_serials = [e.serial for e in self._emulator_instances] + + if not self._emulator_instances: + raise Exception('Failed to start any instances of the emulator.') + elif len(self._emulator_instances) < self._emulator_count: + logging.warning( + 'Running with fewer emulator instances than requested (%d vs %d)', + len(self._emulator_instances), self._emulator_count) + + super(LocalEmulatorEnvironment, self).SetUp() + + #override + def TearDown(self): + try: + super(LocalEmulatorEnvironment, self).TearDown() + finally: + parallelizer.SyncParallelizer(self._emulator_instances).Stop() diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/proto/__init__.py b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/__init__.py new file mode 100644 index 0000000000..4a12e35c92 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2019 The Chromium 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/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd.proto b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd.proto new file mode 100644 index 0000000000..b06da4900b --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd.proto @@ -0,0 +1,75 @@ + +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto3"; + +package tools.android.avd.proto; + +message CIPDPackage { + // CIPD package name. + string package_name = 1; + // CIPD package version to use. + // Ignored when creating AVD packages. + string version = 2; + // Path into which the package should be installed. + // src-relative. + string dest_path = 3; +} + +message ScreenSettings { + // Screen height in pixels. + uint32 height = 1; + + // Screen width in pixels. + uint32 width = 2; + + // Scren density in dpi. + uint32 density = 3; +} + +message SdcardSettings { + // Size of the sdcard that should be created for this AVD. + // Can be anything that `mksdcard` or `avdmanager -c` would accept: + // - a number of bytes + // - a number followed by K, M, or G, indicating that many + // KiB, MiB, or GiB, respectively. + string size = 1; +} + +message AvdSettings { + // Settings pertaining to the AVD's screen. + ScreenSettings screen = 1; + + // Settings pertaining to the AVD's sdcard. + SdcardSettings sdcard = 2; + + // Advanced Features for AVD. The pairs here will override the + // default ones in the given system image. + // See https://bit.ly/2P1qK2X for all the available keys. + // The values should be on, off, default, or null + map advanced_features = 3; + + // The physical RAM size on the device, in megabytes. + uint32 ram_size = 4; +} + +message Avd { + // The emulator to use in running the AVD. + CIPDPackage emulator_package = 1; + + // The system image to use. + CIPDPackage system_image_package = 2; + // The name of the system image to use, as reported by sdkmanager. + string system_image_name = 3; + + // The AVD to create or use. + // (Only the package_name is used during AVD creation.) + CIPDPackage avd_package = 4; + // The name of the AVD to create or use. + string avd_name = 5; + + // How to configure the AVD at creation. + AvdSettings avd_settings = 6; +} diff --git a/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd_pb2.py b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd_pb2.py new file mode 100644 index 0000000000..49cc1aa830 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/emulator/proto/avd_pb2.py @@ -0,0 +1,362 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: avd.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='avd.proto', + package='tools.android.avd.proto', + syntax='proto3', + serialized_options=None, + serialized_pb=b'\n\tavd.proto\x12\x17tools.android.avd.proto\"G\n\x0b\x43IPDPackage\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x11\n\tdest_path\x18\x03 \x01(\t\"@\n\x0eScreenSettings\x12\x0e\n\x06height\x18\x01 \x01(\r\x12\r\n\x05width\x18\x02 \x01(\r\x12\x0f\n\x07\x64\x65nsity\x18\x03 \x01(\r\"\x1e\n\x0eSdcardSettings\x12\x0c\n\x04size\x18\x01 \x01(\t\"\xa1\x02\n\x0b\x41vdSettings\x12\x37\n\x06screen\x18\x01 \x01(\x0b\x32\'.tools.android.avd.proto.ScreenSettings\x12\x37\n\x06sdcard\x18\x02 \x01(\x0b\x32\'.tools.android.avd.proto.SdcardSettings\x12U\n\x11\x61\x64vanced_features\x18\x03 \x03(\x0b\x32:.tools.android.avd.proto.AvdSettings.AdvancedFeaturesEntry\x12\x10\n\x08ram_size\x18\x04 \x01(\r\x1a\x37\n\x15\x41\x64vancedFeaturesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x02\n\x03\x41vd\x12>\n\x10\x65mulator_package\x18\x01 \x01(\x0b\x32$.tools.android.avd.proto.CIPDPackage\x12\x42\n\x14system_image_package\x18\x02 \x01(\x0b\x32$.tools.android.avd.proto.CIPDPackage\x12\x19\n\x11system_image_name\x18\x03 \x01(\t\x12\x39\n\x0b\x61vd_package\x18\x04 \x01(\x0b\x32$.tools.android.avd.proto.CIPDPackage\x12\x10\n\x08\x61vd_name\x18\x05 \x01(\t\x12:\n\x0c\x61vd_settings\x18\x06 \x01(\x0b\x32$.tools.android.avd.proto.AvdSettingsb\x06proto3' +) + + + + +_CIPDPACKAGE = _descriptor.Descriptor( + name='CIPDPackage', + full_name='tools.android.avd.proto.CIPDPackage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='package_name', full_name='tools.android.avd.proto.CIPDPackage.package_name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='version', full_name='tools.android.avd.proto.CIPDPackage.version', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dest_path', full_name='tools.android.avd.proto.CIPDPackage.dest_path', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=38, + serialized_end=109, +) + + +_SCREENSETTINGS = _descriptor.Descriptor( + name='ScreenSettings', + full_name='tools.android.avd.proto.ScreenSettings', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='height', full_name='tools.android.avd.proto.ScreenSettings.height', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='width', full_name='tools.android.avd.proto.ScreenSettings.width', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='density', full_name='tools.android.avd.proto.ScreenSettings.density', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=111, + serialized_end=175, +) + + +_SDCARDSETTINGS = _descriptor.Descriptor( + name='SdcardSettings', + full_name='tools.android.avd.proto.SdcardSettings', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='size', full_name='tools.android.avd.proto.SdcardSettings.size', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=177, + serialized_end=207, +) + + +_AVDSETTINGS_ADVANCEDFEATURESENTRY = _descriptor.Descriptor( + name='AdvancedFeaturesEntry', + full_name='tools.android.avd.proto.AvdSettings.AdvancedFeaturesEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='tools.android.avd.proto.AvdSettings.AdvancedFeaturesEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='tools.android.avd.proto.AvdSettings.AdvancedFeaturesEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=b'8\001', + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=444, + serialized_end=499, +) + +_AVDSETTINGS = _descriptor.Descriptor( + name='AvdSettings', + full_name='tools.android.avd.proto.AvdSettings', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='screen', full_name='tools.android.avd.proto.AvdSettings.screen', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sdcard', full_name='tools.android.avd.proto.AvdSettings.sdcard', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='advanced_features', full_name='tools.android.avd.proto.AvdSettings.advanced_features', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ram_size', full_name='tools.android.avd.proto.AvdSettings.ram_size', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_AVDSETTINGS_ADVANCEDFEATURESENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=210, + serialized_end=499, +) + + +_AVD = _descriptor.Descriptor( + name='Avd', + full_name='tools.android.avd.proto.Avd', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='emulator_package', full_name='tools.android.avd.proto.Avd.emulator_package', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='system_image_package', full_name='tools.android.avd.proto.Avd.system_image_package', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='system_image_name', full_name='tools.android.avd.proto.Avd.system_image_name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='avd_package', full_name='tools.android.avd.proto.Avd.avd_package', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='avd_name', full_name='tools.android.avd.proto.Avd.avd_name', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='avd_settings', full_name='tools.android.avd.proto.Avd.avd_settings', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=502, + serialized_end=803, +) + +_AVDSETTINGS_ADVANCEDFEATURESENTRY.containing_type = _AVDSETTINGS +_AVDSETTINGS.fields_by_name['screen'].message_type = _SCREENSETTINGS +_AVDSETTINGS.fields_by_name['sdcard'].message_type = _SDCARDSETTINGS +_AVDSETTINGS.fields_by_name['advanced_features'].message_type = _AVDSETTINGS_ADVANCEDFEATURESENTRY +_AVD.fields_by_name['emulator_package'].message_type = _CIPDPACKAGE +_AVD.fields_by_name['system_image_package'].message_type = _CIPDPACKAGE +_AVD.fields_by_name['avd_package'].message_type = _CIPDPACKAGE +_AVD.fields_by_name['avd_settings'].message_type = _AVDSETTINGS +DESCRIPTOR.message_types_by_name['CIPDPackage'] = _CIPDPACKAGE +DESCRIPTOR.message_types_by_name['ScreenSettings'] = _SCREENSETTINGS +DESCRIPTOR.message_types_by_name['SdcardSettings'] = _SDCARDSETTINGS +DESCRIPTOR.message_types_by_name['AvdSettings'] = _AVDSETTINGS +DESCRIPTOR.message_types_by_name['Avd'] = _AVD +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CIPDPackage = _reflection.GeneratedProtocolMessageType('CIPDPackage', (_message.Message,), { + 'DESCRIPTOR' : _CIPDPACKAGE, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.CIPDPackage) + }) +_sym_db.RegisterMessage(CIPDPackage) + +ScreenSettings = _reflection.GeneratedProtocolMessageType('ScreenSettings', (_message.Message,), { + 'DESCRIPTOR' : _SCREENSETTINGS, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.ScreenSettings) + }) +_sym_db.RegisterMessage(ScreenSettings) + +SdcardSettings = _reflection.GeneratedProtocolMessageType('SdcardSettings', (_message.Message,), { + 'DESCRIPTOR' : _SDCARDSETTINGS, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.SdcardSettings) + }) +_sym_db.RegisterMessage(SdcardSettings) + +AvdSettings = _reflection.GeneratedProtocolMessageType('AvdSettings', (_message.Message,), { + + 'AdvancedFeaturesEntry' : _reflection.GeneratedProtocolMessageType('AdvancedFeaturesEntry', (_message.Message,), { + 'DESCRIPTOR' : _AVDSETTINGS_ADVANCEDFEATURESENTRY, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.AvdSettings.AdvancedFeaturesEntry) + }) + , + 'DESCRIPTOR' : _AVDSETTINGS, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.AvdSettings) + }) +_sym_db.RegisterMessage(AvdSettings) +_sym_db.RegisterMessage(AvdSettings.AdvancedFeaturesEntry) + +Avd = _reflection.GeneratedProtocolMessageType('Avd', (_message.Message,), { + 'DESCRIPTOR' : _AVD, + '__module__' : 'avd_pb2' + # @@protoc_insertion_point(class_scope:tools.android.avd.proto.Avd) + }) +_sym_db.RegisterMessage(Avd) + + +_AVDSETTINGS_ADVANCEDFEATURESENTRY._options = None +# @@protoc_insertion_point(module_scope) diff --git a/third_party/libwebrtc/build/android/pylib/local/local_test_server_spawner.py b/third_party/libwebrtc/build/android/pylib/local/local_test_server_spawner.py new file mode 100644 index 0000000000..f5f9875c24 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/local_test_server_spawner.py @@ -0,0 +1,101 @@ +# 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 time + +from six.moves import range # pylint: disable=redefined-builtin +from devil.android import forwarder +from devil.android import ports +from pylib.base import test_server +from pylib.constants import host_paths + +with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): + import chrome_test_server_spawner + + +# The tests should not need more than one test server instance. +MAX_TEST_SERVER_INSTANCES = 1 + + +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 range(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 + + +class PortForwarderAndroid(chrome_test_server_spawner.PortForwarder): + def __init__(self, device, tool): + self.device = device + self.tool = tool + + def Map(self, port_pairs): + forwarder.Forwarder.Map(port_pairs, self.device, self.tool) + + def GetDevicePortForHostPort(self, host_port): + return forwarder.Forwarder.DevicePortForHostPort(host_port) + + def WaitHostPortAvailable(self, port): + return _WaitUntil(lambda: ports.IsHostPortAvailable(port)) + + def WaitPortNotAvailable(self, port): + return _WaitUntil(lambda: not ports.IsHostPortAvailable(port)) + + def WaitDevicePortReady(self, port): + return _WaitUntil(lambda: ports.IsDevicePortUsed(self.device, port)) + + def Unmap(self, device_port): + forwarder.Forwarder.UnmapDevicePort(device_port, self.device) + + +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, PortForwarderAndroid(device, tool), MAX_TEST_SERVER_INSTANCES) + 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): + # See net/test/spawned_test_server/remote_test_server.h for description of + # the fields in the config file. + test_server_config = json.dumps({ + 'spawner_url_base': 'http://localhost:%d' % self.port + }) + self._device.WriteFile( + '%s/net-test-server-config' % self._device.GetExternalStoragePath(), + test_server_config) + 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/third_party/libwebrtc/build/android/pylib/local/machine/__init__.py b/third_party/libwebrtc/build/android/pylib/local/machine/__init__.py new file mode 100644 index 0000000000..ca3e206fdd --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/machine/__init__.py @@ -0,0 +1,3 @@ +# 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. diff --git a/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_environment.py b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_environment.py new file mode 100644 index 0000000000..447204cfd6 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_environment.py @@ -0,0 +1,25 @@ +# 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. + + +import devil_chromium +from pylib import constants +from pylib.base import environment + + +class LocalMachineEnvironment(environment.Environment): + + def __init__(self, _args, output_manager, _error_func): + super(LocalMachineEnvironment, self).__init__(output_manager) + + devil_chromium.Initialize( + output_directory=constants.GetOutDirectory()) + + #override + def SetUp(self): + pass + + #override + def TearDown(self): + pass diff --git a/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run.py b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run.py new file mode 100644 index 0000000000..6cdbf47570 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run.py @@ -0,0 +1,309 @@ +# 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. + + +import collections +import json +import logging +import multiprocessing +import os +import select +import subprocess +import sys +import zipfile + +from six.moves import range # pylint: disable=redefined-builtin +from pylib import constants +from pylib.base import base_test_result +from pylib.base import test_run +from pylib.constants import host_paths +from pylib.results import json_results +from py_utils import tempfile_ext + + +# These Test classes are used for running tests and are excluded in the test +# runner. See: +# https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/runner/src/main/java/android/support/test/internal/runner/TestRequestBuilder.java +# base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java # pylint: disable=line-too-long +_EXCLUDED_CLASSES_PREFIXES = ('android', 'junit', 'org/bouncycastle/util', + 'org/hamcrest', 'org/junit', 'org/mockito') + +# Suites we shouldn't shard, usually because they don't contain enough test +# cases. +_EXCLUDED_SUITES = { + 'password_check_junit_tests', + 'touch_to_fill_junit_tests', +} + + +# It can actually take longer to run if you shard too much, especially on +# smaller suites. Locally media_base_junit_tests takes 4.3 sec with 1 shard, +# and 6 sec with 2 or more shards. +_MIN_CLASSES_PER_SHARD = 8 + + +class LocalMachineJunitTestRun(test_run.TestRun): + def __init__(self, env, test_instance): + super(LocalMachineJunitTestRun, self).__init__(env, test_instance) + + #override + def TestPackage(self): + return self._test_instance.suite + + #override + def SetUp(self): + pass + + def _CreateJarArgsList(self, json_result_file_paths, group_test_list, shards): + # Creates a list of jar_args. The important thing is each jar_args list + # has a different json_results file for writing test results to and that + # each list of jar_args has its own test to run as specified in the + # -gtest-filter. + jar_args_list = [['-json-results-file', result_file] + for result_file in json_result_file_paths] + for index, jar_arg in enumerate(jar_args_list): + if shards > 1: + jar_arg.extend(['-gtest-filter', ':'.join(group_test_list[index])]) + elif self._test_instance.test_filter: + jar_arg.extend(['-gtest-filter', self._test_instance.test_filter]) + + if self._test_instance.package_filter: + jar_arg.extend(['-package-filter', self._test_instance.package_filter]) + if self._test_instance.runner_filter: + jar_arg.extend(['-runner-filter', self._test_instance.runner_filter]) + + return jar_args_list + + def _CreateJvmArgsList(self): + # Creates a list of jvm_args (robolectric, code coverage, etc...) + jvm_args = [ + '-Drobolectric.dependency.dir=%s' % + self._test_instance.robolectric_runtime_deps_dir, + '-Ddir.source.root=%s' % constants.DIR_SOURCE_ROOT, + '-Drobolectric.resourcesMode=binary', + ] + if logging.getLogger().isEnabledFor(logging.INFO): + jvm_args += ['-Drobolectric.logging=stdout'] + if self._test_instance.debug_socket: + jvm_args += [ + '-agentlib:jdwp=transport=dt_socket' + ',server=y,suspend=y,address=%s' % self._test_instance.debug_socket + ] + + if self._test_instance.coverage_dir: + if not os.path.exists(self._test_instance.coverage_dir): + os.makedirs(self._test_instance.coverage_dir) + elif not os.path.isdir(self._test_instance.coverage_dir): + raise Exception('--coverage-dir takes a directory, not file path.') + if self._test_instance.coverage_on_the_fly: + jacoco_coverage_file = os.path.join( + self._test_instance.coverage_dir, + '%s.exec' % self._test_instance.suite) + jacoco_agent_path = os.path.join(host_paths.DIR_SOURCE_ROOT, + 'third_party', 'jacoco', 'lib', + 'jacocoagent.jar') + + # inclnolocationclasses is false to prevent no class def found error. + jacoco_args = '-javaagent:{}=destfile={},inclnolocationclasses=false' + jvm_args.append( + jacoco_args.format(jacoco_agent_path, jacoco_coverage_file)) + else: + jvm_args.append('-Djacoco-agent.destfile=%s' % + os.path.join(self._test_instance.coverage_dir, + '%s.exec' % self._test_instance.suite)) + + return jvm_args + + #override + def RunTests(self, results): + wrapper_path = os.path.join(constants.GetOutDirectory(), 'bin', 'helper', + self._test_instance.suite) + + # This avoids searching through the classparth jars for tests classes, + # which takes about 1-2 seconds. + # Do not shard when a test filter is present since we do not know at this + # point which tests will be filtered out. + if (self._test_instance.shards == 1 or self._test_instance.test_filter + or self._test_instance.suite in _EXCLUDED_SUITES): + test_classes = [] + shards = 1 + else: + test_classes = _GetTestClasses(wrapper_path) + shards = ChooseNumOfShards(test_classes, self._test_instance.shards) + + logging.info('Running tests on %d shard(s).', shards) + group_test_list = GroupTestsForShard(shards, test_classes) + + with tempfile_ext.NamedTemporaryDirectory() as temp_dir: + cmd_list = [[wrapper_path] for _ in range(shards)] + json_result_file_paths = [ + os.path.join(temp_dir, 'results%d.json' % i) for i in range(shards) + ] + jar_args_list = self._CreateJarArgsList(json_result_file_paths, + group_test_list, shards) + for i in range(shards): + cmd_list[i].extend(['--jar-args', '"%s"' % ' '.join(jar_args_list[i])]) + + jvm_args = self._CreateJvmArgsList() + if jvm_args: + for cmd in cmd_list: + cmd.extend(['--jvm-args', '"%s"' % ' '.join(jvm_args)]) + + AddPropertiesJar(cmd_list, temp_dir, self._test_instance.resource_apk) + + procs = [ + subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) for cmd in cmd_list + ] + PrintProcessesStdout(procs) + + results_list = [] + try: + for json_file_path in json_result_file_paths: + with open(json_file_path, 'r') as f: + results_list += json_results.ParseResultsFromJson( + json.loads(f.read())) + except IOError: + # In the case of a failure in the JUnit or Robolectric test runner + # the output json file may never be written. + results_list = [ + base_test_result.BaseTestResult( + 'Test Runner Failure', base_test_result.ResultType.UNKNOWN) + ] + + test_run_results = base_test_result.TestRunResults() + test_run_results.AddResults(results_list) + results.append(test_run_results) + + #override + def TearDown(self): + pass + + +def AddPropertiesJar(cmd_list, temp_dir, resource_apk): + # Create properties file for Robolectric test runners so they can find the + # binary resources. + properties_jar_path = os.path.join(temp_dir, 'properties.jar') + with zipfile.ZipFile(properties_jar_path, 'w') as z: + z.writestr('com/android/tools/test_config.properties', + 'android_resource_apk=%s' % resource_apk) + + for cmd in cmd_list: + cmd.extend(['--classpath', properties_jar_path]) + + +def ChooseNumOfShards(test_classes, shards): + # Don't override requests to not shard. + if shards == 1: + return 1 + + # Sharding doesn't reduce runtime on just a few tests. + if shards > (len(test_classes) // _MIN_CLASSES_PER_SHARD) or shards < 1: + shards = max(1, (len(test_classes) // _MIN_CLASSES_PER_SHARD)) + + # Local tests of explicit --shard values show that max speed is achieved + # at cpu_count() / 2. + # Using -XX:TieredStopAtLevel=1 is required for this result. The flag reduces + # CPU time by two-thirds, making sharding more effective. + shards = max(1, min(shards, multiprocessing.cpu_count() // 2)) + # Can have at minimum one test_class per shard. + shards = min(len(test_classes), shards) + + return shards + + +def GroupTestsForShard(num_of_shards, test_classes): + """Groups tests that will be ran on each shard. + + Args: + num_of_shards: number of shards to split tests between. + test_classes: A list of test_class files in the jar. + + Return: + Returns a dictionary containing a list of test classes. + """ + test_dict = {i: [] for i in range(num_of_shards)} + + # Round robin test distribiution to reduce chance that a sequential group of + # classes all have an unusually high number of tests. + for count, test_cls in enumerate(test_classes): + test_cls = test_cls.replace('.class', '*') + test_cls = test_cls.replace('/', '.') + test_dict[count % num_of_shards].append(test_cls) + + return test_dict + + +def PrintProcessesStdout(procs): + """Prints the stdout of all the processes. + + Buffers the stdout of the processes and prints it when finished. + + Args: + procs: A list of subprocesses. + + Returns: N/A + """ + streams = [p.stdout for p in procs] + outputs = collections.defaultdict(list) + first_fd = streams[0].fileno() + + while streams: + rstreams, _, _ = select.select(streams, [], []) + for stream in rstreams: + line = stream.readline() + if line: + # Print out just one output so user can see work being done rather + # than waiting for it all at the end. + if stream.fileno() == first_fd: + sys.stdout.write(line) + else: + outputs[stream.fileno()].append(line) + else: + streams.remove(stream) # End of stream. + + for p in procs: + sys.stdout.write(''.join(outputs[p.stdout.fileno()])) + + +def _GetTestClasses(file_path): + test_jar_paths = subprocess.check_output([file_path, '--print-classpath']) + test_jar_paths = test_jar_paths.split(':') + + test_classes = [] + for test_jar_path in test_jar_paths: + # Avoid searching through jars that are for the test runner. + # TODO(crbug.com/1144077): Use robolectric buildconfig file arg. + if 'third_party/robolectric/' in test_jar_path: + continue + + test_classes += _GetTestClassesFromJar(test_jar_path) + + logging.info('Found %d test classes in class_path jars.', len(test_classes)) + return test_classes + + +def _GetTestClassesFromJar(test_jar_path): + """Returns a list of test classes from a jar. + + Test files end in Test, this is enforced: + //tools/android/errorprone_plugin/src/org/chromium/tools/errorprone + /plugin/TestClassNameCheck.java + + Args: + test_jar_path: Path to the jar. + + Return: + Returns a list of test classes that were in the jar. + """ + class_list = [] + with zipfile.ZipFile(test_jar_path, 'r') as zip_f: + for test_class in zip_f.namelist(): + if test_class.startswith(_EXCLUDED_CLASSES_PREFIXES): + continue + if test_class.endswith('Test.class') and '$' not in test_class: + class_list.append(test_class) + + return class_list diff --git a/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run_test.py b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run_test.py new file mode 100755 index 0000000000..553451d650 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/local/machine/local_machine_junit_test_run_test.py @@ -0,0 +1,89 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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=protected-access + + +import os +import unittest + +from pylib.local.machine import local_machine_junit_test_run +from py_utils import tempfile_ext +from mock import patch # pylint: disable=import-error + + +class LocalMachineJunitTestRunTests(unittest.TestCase): + def testAddPropertiesJar(self): + with tempfile_ext.NamedTemporaryDirectory() as temp_dir: + apk = 'resource_apk' + cmd_list = [] + local_machine_junit_test_run.AddPropertiesJar(cmd_list, temp_dir, apk) + self.assertEqual(cmd_list, []) + cmd_list = [['test1']] + local_machine_junit_test_run.AddPropertiesJar(cmd_list, temp_dir, apk) + self.assertEqual( + cmd_list[0], + ['test1', '--classpath', + os.path.join(temp_dir, 'properties.jar')]) + cmd_list = [['test1'], ['test2']] + local_machine_junit_test_run.AddPropertiesJar(cmd_list, temp_dir, apk) + self.assertEqual(len(cmd_list[0]), 3) + self.assertEqual( + cmd_list[1], + ['test2', '--classpath', + os.path.join(temp_dir, 'properties.jar')]) + + @patch('multiprocessing.cpu_count') + def testChooseNumOfShards(self, mock_cpu_count): + mock_cpu_count.return_value = 36 + # Test shards is 1 when filter is set. + test_shards = 1 + test_classes = [1] * 50 + shards = local_machine_junit_test_run.ChooseNumOfShards( + test_classes, test_shards) + self.assertEqual(1, shards) + + # Tests setting shards. + test_shards = 4 + shards = local_machine_junit_test_run.ChooseNumOfShards( + test_classes, test_shards) + self.assertEqual(4, shards) + + # Tests using min_class per shards. + test_classes = [1] * 20 + test_shards = 8 + shards = local_machine_junit_test_run.ChooseNumOfShards( + test_classes, test_shards) + self.assertEqual(2, shards) + + def testGroupTestsForShard(self): + test_classes = [] + results = local_machine_junit_test_run.GroupTestsForShard(1, test_classes) + self.assertDictEqual(results, {0: []}) + + test_classes = ['dir/test.class'] * 5 + results = local_machine_junit_test_run.GroupTestsForShard(1, test_classes) + self.assertDictEqual(results, {0: ['dir.test*'] * 5}) + + test_classes = ['dir/test.class'] * 5 + results = local_machine_junit_test_run.GroupTestsForShard(2, test_classes) + ans_dict = { + 0: ['dir.test*'] * 3, + 1: ['dir.test*'] * 2, + } + self.assertDictEqual(results, ans_dict) + + test_classes = ['a10 warthog', 'b17', 'SR71'] + results = local_machine_junit_test_run.GroupTestsForShard(3, test_classes) + ans_dict = { + 0: ['a10 warthog'], + 1: ['b17'], + 2: ['SR71'], + } + self.assertDictEqual(results, ans_dict) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/monkey/__init__.py b/third_party/libwebrtc/build/android/pylib/monkey/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/android/pylib/monkey/monkey_test_instance.py b/third_party/libwebrtc/build/android/pylib/monkey/monkey_test_instance.py new file mode 100644 index 0000000000..0d5aed6095 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/monkey/monkey_test_instance.py @@ -0,0 +1,73 @@ +# 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. + + +import random + +from pylib import constants +from pylib.base import test_instance + + +_SINGLE_EVENT_TIMEOUT = 100 # Milliseconds + +class MonkeyTestInstance(test_instance.TestInstance): + + def __init__(self, args, _): + super(MonkeyTestInstance, self).__init__() + + self._categories = args.categories + self._event_count = args.event_count + self._seed = args.seed or random.randint(1, 100) + self._throttle = args.throttle + self._verbose_count = args.verbose_count + + self._package = constants.PACKAGE_INFO[args.browser].package + self._activity = constants.PACKAGE_INFO[args.browser].activity + + self._timeout_s = ( + self.event_count * (self.throttle + _SINGLE_EVENT_TIMEOUT)) / 1000 + + #override + def TestType(self): + return 'monkey' + + #override + def SetUp(self): + pass + + #override + def TearDown(self): + pass + + @property + def activity(self): + return self._activity + + @property + def categories(self): + return self._categories + + @property + def event_count(self): + return self._event_count + + @property + def package(self): + return self._package + + @property + def seed(self): + return self._seed + + @property + def throttle(self): + return self._throttle + + @property + def timeout(self): + return self._timeout_s + + @property + def verbose_count(self): + return self._verbose_count diff --git a/third_party/libwebrtc/build/android/pylib/output/__init__.py b/third_party/libwebrtc/build/android/pylib/output/__init__.py new file mode 100644 index 0000000000..a22a6ee39a --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2017 The Chromium 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/third_party/libwebrtc/build/android/pylib/output/local_output_manager.py b/third_party/libwebrtc/build/android/pylib/output/local_output_manager.py new file mode 100644 index 0000000000..2b5c0f4393 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/local_output_manager.py @@ -0,0 +1,49 @@ +# Copyright 2017 The Chromium 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 time +import os +import shutil + +try: + from urllib.parse import quote +except ImportError: + from urllib import quote + +from pylib.base import output_manager + + +class LocalOutputManager(output_manager.OutputManager): + """Saves and manages test output files locally in output directory. + + Location files will be saved in {output_dir}/TEST_RESULTS_{timestamp}. + """ + + def __init__(self, output_dir): + super(LocalOutputManager, self).__init__() + timestamp = time.strftime( + '%Y_%m_%dT%H_%M_%S', time.localtime()) + self._output_root = os.path.abspath(os.path.join( + output_dir, 'TEST_RESULTS_%s' % timestamp)) + + #override + def _CreateArchivedFile(self, out_filename, out_subdir, datatype): + return LocalArchivedFile( + out_filename, out_subdir, datatype, self._output_root) + + +class LocalArchivedFile(output_manager.ArchivedFile): + + def __init__(self, out_filename, out_subdir, datatype, out_root): + super(LocalArchivedFile, self).__init__( + out_filename, out_subdir, datatype) + self._output_path = os.path.join(out_root, out_subdir, out_filename) + + def _Link(self): + return 'file://%s' % quote(self._output_path) + + def _Archive(self): + if not os.path.exists(os.path.dirname(self._output_path)): + os.makedirs(os.path.dirname(self._output_path)) + shutil.copy(self.name, self._output_path) diff --git a/third_party/libwebrtc/build/android/pylib/output/local_output_manager_test.py b/third_party/libwebrtc/build/android/pylib/output/local_output_manager_test.py new file mode 100755 index 0000000000..14f556c42f --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/local_output_manager_test.py @@ -0,0 +1,34 @@ +#! /usr/bin/env vpython3 +# Copyright 2017 The Chromium 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=protected-access + +import tempfile +import shutil +import unittest + +from pylib.base import output_manager +from pylib.base import output_manager_test_case +from pylib.output import local_output_manager + + +class LocalOutputManagerTest(output_manager_test_case.OutputManagerTestCase): + + def setUp(self): + self._output_dir = tempfile.mkdtemp() + self._output_manager = local_output_manager.LocalOutputManager( + self._output_dir) + + def testUsableTempFile(self): + self.assertUsableTempFile( + self._output_manager._CreateArchivedFile( + 'test_file', 'test_subdir', output_manager.Datatype.TEXT)) + + def tearDown(self): + shutil.rmtree(self._output_dir) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/output/noop_output_manager.py b/third_party/libwebrtc/build/android/pylib/output/noop_output_manager.py new file mode 100644 index 0000000000..d29a7432f9 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/noop_output_manager.py @@ -0,0 +1,42 @@ +# Copyright 2017 The Chromium 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 output_manager + +# TODO(jbudorick): This class is currently mostly unused. +# Add a --bot-mode argument that all bots pass. If --bot-mode and +# --local-output args are both not passed to test runner then use this +# as the output manager impl. + +# pylint: disable=no-self-use + +class NoopOutputManager(output_manager.OutputManager): + + def __init__(self): + super(NoopOutputManager, self).__init__() + + #override + def _CreateArchivedFile(self, out_filename, out_subdir, datatype): + del out_filename, out_subdir, datatype + return NoopArchivedFile() + + +class NoopArchivedFile(output_manager.ArchivedFile): + + def __init__(self): + super(NoopArchivedFile, self).__init__(None, None, None) + + def Link(self): + """NoopArchivedFiles are not retained.""" + return '' + + def _Link(self): + pass + + def Archive(self): + """NoopArchivedFiles are not retained.""" + pass + + def _Archive(self): + pass diff --git a/third_party/libwebrtc/build/android/pylib/output/noop_output_manager_test.py b/third_party/libwebrtc/build/android/pylib/output/noop_output_manager_test.py new file mode 100755 index 0000000000..4335563383 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/noop_output_manager_test.py @@ -0,0 +1,27 @@ +#! /usr/bin/env vpython3 +# Copyright 2017 The Chromium 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=protected-access + +import unittest + +from pylib.base import output_manager +from pylib.base import output_manager_test_case +from pylib.output import noop_output_manager + + +class NoopOutputManagerTest(output_manager_test_case.OutputManagerTestCase): + + def setUp(self): + self._output_manager = noop_output_manager.NoopOutputManager() + + def testUsableTempFile(self): + self.assertUsableTempFile( + self._output_manager._CreateArchivedFile( + 'test_file', 'test_subdir', output_manager.Datatype.TEXT)) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/output/remote_output_manager.py b/third_party/libwebrtc/build/android/pylib/output/remote_output_manager.py new file mode 100644 index 0000000000..9fdb4bf65f --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/remote_output_manager.py @@ -0,0 +1,89 @@ +# Copyright 2017 The Chromium 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 + +from pylib.base import output_manager +from pylib.output import noop_output_manager +from pylib.utils import logdog_helper +from pylib.utils import google_storage_helper + + +class RemoteOutputManager(output_manager.OutputManager): + + def __init__(self, bucket): + """Uploads output files to Google Storage or LogDog. + + Files will either be uploaded directly to Google Storage or LogDog + depending on the datatype. + + Args + bucket: Bucket to use when saving to Google Storage. + """ + super(RemoteOutputManager, self).__init__() + self._bucket = bucket + + #override + def _CreateArchivedFile(self, out_filename, out_subdir, datatype): + if datatype == output_manager.Datatype.TEXT: + try: + logdog_helper.get_logdog_client() + return LogdogArchivedFile(out_filename, out_subdir, datatype) + except RuntimeError: + return noop_output_manager.NoopArchivedFile() + else: + if self._bucket is None: + return noop_output_manager.NoopArchivedFile() + return GoogleStorageArchivedFile( + out_filename, out_subdir, datatype, self._bucket) + + +class LogdogArchivedFile(output_manager.ArchivedFile): + + def __init__(self, out_filename, out_subdir, datatype): + super(LogdogArchivedFile, self).__init__(out_filename, out_subdir, datatype) + self._stream_name = '%s_%s' % (out_subdir, out_filename) + + def _Link(self): + return logdog_helper.get_viewer_url(self._stream_name) + + def _Archive(self): + with open(self.name, 'r') as f: + logdog_helper.text(self._stream_name, f.read()) + + +class GoogleStorageArchivedFile(output_manager.ArchivedFile): + + def __init__(self, out_filename, out_subdir, datatype, bucket): + super(GoogleStorageArchivedFile, self).__init__( + out_filename, out_subdir, datatype) + self._bucket = bucket + self._upload_path = None + self._content_addressed = None + + def _PrepareArchive(self): + self._content_addressed = (self._datatype in ( + output_manager.Datatype.HTML, + output_manager.Datatype.PNG, + output_manager.Datatype.JSON)) + if self._content_addressed: + sha1 = hashlib.sha1() + with open(self.name, 'rb') as f: + sha1.update(f.read()) + self._upload_path = sha1.hexdigest() + else: + self._upload_path = os.path.join(self._out_subdir, self._out_filename) + + def _Link(self): + return google_storage_helper.get_url_link( + self._upload_path, self._bucket) + + def _Archive(self): + if (self._content_addressed and + google_storage_helper.exists(self._upload_path, self._bucket)): + return + + google_storage_helper.upload( + self._upload_path, self.name, self._bucket, content_type=self._datatype) diff --git a/third_party/libwebrtc/build/android/pylib/output/remote_output_manager_test.py b/third_party/libwebrtc/build/android/pylib/output/remote_output_manager_test.py new file mode 100755 index 0000000000..c9582f5959 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/output/remote_output_manager_test.py @@ -0,0 +1,32 @@ +#! /usr/bin/env vpython3 +# Copyright 2017 The Chromium 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=protected-access + +import unittest + +from pylib.base import output_manager +from pylib.base import output_manager_test_case +from pylib.output import remote_output_manager + +import mock # pylint: disable=import-error + + +@mock.patch('pylib.utils.google_storage_helper') +class RemoteOutputManagerTest(output_manager_test_case.OutputManagerTestCase): + + def setUp(self): + self._output_manager = remote_output_manager.RemoteOutputManager( + 'this-is-a-fake-bucket') + + def testUsableTempFile(self, google_storage_helper_mock): + del google_storage_helper_mock + self.assertUsableTempFile( + self._output_manager._CreateArchivedFile( + 'test_file', 'test_subdir', output_manager.Datatype.TEXT)) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/android/pylib/pexpect.py b/third_party/libwebrtc/build/android/pylib/pexpect.py new file mode 100644 index 0000000000..cf59fb0f6d --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/restart_adbd.sh b/third_party/libwebrtc/build/android/pylib/restart_adbd.sh new file mode 100755 index 0000000000..393b2ebac0 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/results/__init__.py b/third_party/libwebrtc/build/android/pylib/results/__init__.py new file mode 100644 index 0000000000..4d6aabb953 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/__init__.py b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/__init__.py new file mode 100644 index 0000000000..4d6aabb953 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator.py b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator.py new file mode 100644 index 0000000000..ff035ec1c7 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator.py @@ -0,0 +1,702 @@ +# 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/blinkpy/web_tests/layout_package/json_results_generator.py +# tools/blinkpy/common/net/file_uploader.py +# + +import json +import logging +import mimetypes +import os +import time +try: + from urllib.request import urlopen, Request + from urllib.error import HTTPError, URLError + from urllib.parse import quote +except ImportError: + from urllib import quote + from urllib2 import urlopen, HTTPError, URLError, Request + +_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.items(): + 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, _, 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) = list(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 = ( + 'https://%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 = list(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(list(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 = 'https://%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 as err: # pylint: disable=broad-except + _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 list(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 % + (quote(self._test_results_server), + quote(self._builder_name), self.RESULTS_FILENAME, + quote(self._test_type), quote(self._master_name))) + + # pylint: disable=redefined-variable-type + try: + # FIXME: We should talk to the network via a Host object. + results_file = urlopen(results_file_url) + old_results = results_file.read() + except HTTPError as 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 URLError as url_error: + error = url_error + # pylint: enable=redefined-variable-type + + 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: # pylint: disable=broad-except + _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.keys(): + 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 list(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.keys(): + 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 = Request(self._url, data, {'Content-Type': content_type}) + return urlopen(request) + except 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, str): + 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, str): + 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/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator_unittest.py b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/json_results_generator_unittest.py new file mode 100644 index 0000000000..70c808c71f --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/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.results.flakiness_dashboard 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.items(): + 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.keys(): + 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/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/results_uploader.py b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/results_uploader.py new file mode 100644 index 0000000000..b68a898b7d --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/flakiness_dashboard/results_uploader.py @@ -0,0 +1,176 @@ +# 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 devil.utils import cmd_helper +from pylib.constants import host_paths +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 == host_paths.DIR_SOURCE_ROOT or parent == in_directory: + return False + return _is_git_directory(parent) + + in_directory = os.path.join(host_paths.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._master_name = os.environ.get('BUILDBOT_MASTERNAME') + self._builder_name = os.environ.get('BUILDBOT_BUILDERNAME') + self._tests_type = tests_type + self._build_name = None + + 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 not upstream: + 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: # pylint: disable=broad-except + 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/third_party/libwebrtc/build/android/pylib/results/json_results.py b/third_party/libwebrtc/build/android/pylib/results/json_results.py new file mode 100644 index 0000000000..ed63c1540c --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/json_results.py @@ -0,0 +1,239 @@ +# 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 itertools +import json +import logging +import time + +import six + +from pylib.base import base_test_result + +def GenerateResultsDict(test_run_results, global_tags=None): + """Create a results dict from |test_run_results| suitable for writing to JSON. + Args: + test_run_results: a list of base_test_result.TestRunResults objects. + 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": "", + # }, + # ... + # ], + # }, + # { + # "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": "", + # }, + # ], + # }, + # ... + # ], + # } + + all_tests = set() + per_iteration_data = [] + test_run_links = {} + + for test_run_result in test_run_results: + iteration_data = collections.defaultdict(list) + if isinstance(test_run_result, list): + results_iterable = itertools.chain(*(t.GetAll() for t in test_run_result)) + for tr in test_run_result: + test_run_links.update(tr.GetLinks()) + + else: + results_iterable = test_run_result.GetAll() + test_run_links.update(test_run_result.GetLinks()) + + for r in results_iterable: + result_dict = { + 'status': r.GetType(), + 'elapsed_time_ms': r.GetDuration(), + 'output_snippet': six.ensure_text(r.GetLog(), errors='replace'), + 'losless_snippet': True, + 'output_snippet_base64': '', + 'links': r.GetLinks(), + } + iteration_data[r.GetName()].append(result_dict) + + all_tests = all_tests.union(set(six.iterkeys(iteration_data))) + per_iteration_data.append(iteration_data) + + return { + 'global_tags': global_tags or [], + 'all_tests': sorted(list(all_tests)), + # TODO(jbudorick): Add support for disabled tests within base_test_result. + 'disabled_tests': [], + 'per_iteration_data': per_iteration_data, + 'links': test_run_links, + } + + +def GenerateJsonTestResultFormatDict(test_run_results, interrupted): + """Create a results dict from |test_run_results| suitable for writing to JSON. + + Args: + test_run_results: a list of base_test_result.TestRunResults objects. + interrupted: True if tests were interrupted, e.g. timeout listing tests + Returns: + A results dict that mirrors the standard JSON Test Results Format. + """ + + tests = {} + counts = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'CRASH': 0, 'TIMEOUT': 0} + + for test_run_result in test_run_results: + if isinstance(test_run_result, list): + results_iterable = itertools.chain(*(t.GetAll() for t in test_run_result)) + else: + results_iterable = test_run_result.GetAll() + + for r in results_iterable: + element = tests + for key in r.GetName().split('.'): + if key not in element: + element[key] = {} + element = element[key] + + element['expected'] = 'PASS' + + if r.GetType() == base_test_result.ResultType.PASS: + result = 'PASS' + elif r.GetType() == base_test_result.ResultType.SKIP: + result = 'SKIP' + elif r.GetType() == base_test_result.ResultType.CRASH: + result = 'CRASH' + elif r.GetType() == base_test_result.ResultType.TIMEOUT: + result = 'TIMEOUT' + else: + result = 'FAIL' + + if 'actual' in element: + element['actual'] += ' ' + result + else: + counts[result] += 1 + element['actual'] = result + if result == 'FAIL': + element['is_unexpected'] = True + + if r.GetDuration() != 0: + element['time'] = r.GetDuration() + + # Fill in required fields. + return { + 'interrupted': interrupted, + 'num_failures_by_type': counts, + 'path_delimiter': '.', + 'seconds_since_epoch': time.time(), + 'tests': tests, + 'version': 3, + } + + +def GenerateJsonResultsFile(test_run_result, file_path, global_tags=None, + **kwargs): + """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, global_tags=global_tags), + **kwargs)) + logging.info('Generated json results file at %s', file_path) + + +def GenerateJsonTestResultFormatFile(test_run_result, interrupted, file_path, + **kwargs): + """Write |test_run_result| to JSON. + + This uses the official Chromium Test Results Format. + + Args: + test_run_result: a base_test_result.TestRunResults object. + interrupted: True if tests were interrupted, e.g. timeout listing tests + 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( + GenerateJsonTestResultFormatDict(test_run_result, interrupted), + **kwargs)) + logging.info('Generated json results file at %s', file_path) + + +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 in base_test_result.ResultType.GetTypes(): + return s + 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 six.iteritems(testsuite_run): + results_list.extend( + [base_test_result.BaseTestResult(test, + string_as_status(tr['status']), + duration=tr['elapsed_time_ms'], + log=tr.get('output_snippet')) + for tr in test_runs]) + return results_list diff --git a/third_party/libwebrtc/build/android/pylib/results/json_results_test.py b/third_party/libwebrtc/build/android/pylib/results/json_results_test.py new file mode 100755 index 0000000000..cb942e2898 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/json_results_test.py @@ -0,0 +1,311 @@ +#!/usr/bin/env vpython3 +# 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 + +import six +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.assertEqual(['test.package.TestName'], results_dict['all_tests']) + self.assertEqual(1, len(results_dict['per_iteration_data'])) + + iteration_result = results_dict['per_iteration_data'][0] + self.assertTrue('test.package.TestName' in iteration_result) + self.assertEqual(1, len(iteration_result['test.package.TestName'])) + + test_iteration_result = iteration_result['test.package.TestName'][0] + self.assertTrue('status' in test_iteration_result) + self.assertEqual('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.assertEqual(['test.package.TestName'], results_dict['all_tests']) + self.assertEqual(1, len(results_dict['per_iteration_data'])) + + iteration_result = results_dict['per_iteration_data'][0] + self.assertTrue('test.package.TestName' in iteration_result) + self.assertEqual(1, len(iteration_result['test.package.TestName'])) + + test_iteration_result = iteration_result['test.package.TestName'][0] + self.assertTrue('status' in test_iteration_result) + self.assertEqual('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.assertEqual(['test.package.TestName'], results_dict['all_tests']) + self.assertEqual(1, len(results_dict['per_iteration_data'])) + + iteration_result = results_dict['per_iteration_data'][0] + self.assertTrue('test.package.TestName' in iteration_result) + self.assertEqual(1, len(iteration_result['test.package.TestName'])) + + test_iteration_result = iteration_result['test.package.TestName'][0] + self.assertTrue('status' in test_iteration_result) + self.assertEqual('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.assertEqual(['test.package.TestName'], results_dict['all_tests']) + self.assertEqual(1, len(results_dict['per_iteration_data'])) + + iteration_result = results_dict['per_iteration_data'][0] + self.assertTrue('test.package.TestName' in iteration_result) + self.assertEqual(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.assertEqual(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.assertEqual(['test.package.TestName1', 'test.package.TestName2'], + results_dict['all_tests']) + + self.assertTrue('per_iteration_data' in results_dict) + iterations = results_dict['per_iteration_data'] + self.assertEqual(1, len(iterations)) + + expected_tests = set([ + 'test.package.TestName1', + 'test.package.TestName2', + ]) + + for test_name, iteration_result in six.iteritems(iterations[0]): + self.assertTrue(test_name in expected_tests) + expected_tests.remove(test_name) + self.assertEqual(1, len(iteration_result)) + + test_iteration_result = iteration_result[0] + self.assertTrue('status' in test_iteration_result) + self.assertEqual('SUCCESS', test_iteration_result['status']) + + def testGenerateResultsDict_passOnRetry(self): + raw_results = [] + + result1 = base_test_result.BaseTestResult( + 'test.package.TestName1', base_test_result.ResultType.FAIL) + run_results1 = base_test_result.TestRunResults() + run_results1.AddResult(result1) + raw_results.append(run_results1) + + result2 = base_test_result.BaseTestResult( + 'test.package.TestName1', base_test_result.ResultType.PASS) + run_results2 = base_test_result.TestRunResults() + run_results2.AddResult(result2) + raw_results.append(run_results2) + + results_dict = json_results.GenerateResultsDict([raw_results]) + self.assertEqual(['test.package.TestName1'], results_dict['all_tests']) + + # Check that there's only one iteration. + self.assertIn('per_iteration_data', results_dict) + iterations = results_dict['per_iteration_data'] + self.assertEqual(1, len(iterations)) + + # Check that test.package.TestName1 is the only test in the iteration. + self.assertEqual(1, len(iterations[0])) + self.assertIn('test.package.TestName1', iterations[0]) + + # Check that there are two results for test.package.TestName1. + actual_test_results = iterations[0]['test.package.TestName1'] + self.assertEqual(2, len(actual_test_results)) + + # Check that the first result is a failure. + self.assertIn('status', actual_test_results[0]) + self.assertEqual('FAILURE', actual_test_results[0]['status']) + + # Check that the second result is a success. + self.assertIn('status', actual_test_results[1]) + self.assertEqual('SUCCESS', actual_test_results[1]['status']) + + def testGenerateResultsDict_globalTags(self): + raw_results = [] + global_tags = ['UNRELIABLE_RESULTS'] + + results_dict = json_results.GenerateResultsDict( + [raw_results], global_tags=global_tags) + self.assertEqual(['UNRELIABLE_RESULTS'], results_dict['global_tags']) + + def testGenerateResultsDict_loslessSnippet(self): + result = base_test_result.BaseTestResult( + 'test.package.TestName', base_test_result.ResultType.FAIL) + log = 'blah-blah' + result.SetLog(log) + + all_results = base_test_result.TestRunResults() + all_results.AddResult(result) + + results_dict = json_results.GenerateResultsDict([all_results]) + self.assertEqual(['test.package.TestName'], results_dict['all_tests']) + self.assertEqual(1, len(results_dict['per_iteration_data'])) + + iteration_result = results_dict['per_iteration_data'][0] + self.assertTrue('test.package.TestName' in iteration_result) + self.assertEqual(1, len(iteration_result['test.package.TestName'])) + + test_iteration_result = iteration_result['test.package.TestName'][0] + self.assertTrue('losless_snippet' in test_iteration_result) + self.assertTrue(test_iteration_result['losless_snippet']) + self.assertTrue('output_snippet' in test_iteration_result) + self.assertEqual(log, test_iteration_result['output_snippet']) + self.assertTrue('output_snippet_base64' in test_iteration_result) + self.assertEqual('', test_iteration_result['output_snippet_base64']) + + def testGenerateJsonTestResultFormatDict_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.GenerateJsonTestResultFormatDict([all_results], + False) + self.assertEqual(1, len(results_dict['tests'])) + self.assertEqual(1, len(results_dict['tests']['test'])) + self.assertEqual(1, len(results_dict['tests']['test']['package'])) + self.assertEqual( + 'PASS', + results_dict['tests']['test']['package']['TestName']['expected']) + self.assertEqual( + 'PASS', results_dict['tests']['test']['package']['TestName']['actual']) + + self.assertTrue('FAIL' not in results_dict['num_failures_by_type'] + or results_dict['num_failures_by_type']['FAIL'] == 0) + self.assertIn('PASS', results_dict['num_failures_by_type']) + self.assertEqual(1, results_dict['num_failures_by_type']['PASS']) + + def testGenerateJsonTestResultFormatDict_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.GenerateJsonTestResultFormatDict([all_results], + False) + self.assertEqual(1, len(results_dict['tests'])) + self.assertEqual(1, len(results_dict['tests']['test'])) + self.assertEqual(1, len(results_dict['tests']['test']['package'])) + self.assertEqual( + 'PASS', + results_dict['tests']['test']['package']['TestName']['expected']) + self.assertEqual( + 'FAIL', results_dict['tests']['test']['package']['TestName']['actual']) + self.assertEqual( + True, + results_dict['tests']['test']['package']['TestName']['is_unexpected']) + + self.assertTrue('PASS' not in results_dict['num_failures_by_type'] + or results_dict['num_failures_by_type']['PASS'] == 0) + self.assertIn('FAIL', results_dict['num_failures_by_type']) + self.assertEqual(1, results_dict['num_failures_by_type']['FAIL']) + + def testGenerateJsonTestResultFormatDict_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.GenerateJsonTestResultFormatDict([all_results], + False) + self.assertEqual(1, len(results_dict['tests'])) + self.assertEqual(1, len(results_dict['tests']['test'])) + self.assertEqual(1, len(results_dict['tests']['test']['package'])) + self.assertEqual( + 'PASS', + results_dict['tests']['test']['package']['TestName']['expected']) + self.assertEqual( + 'SKIP', results_dict['tests']['test']['package']['TestName']['actual']) + # Should only be set if the test fails. + self.assertNotIn('is_unexpected', + results_dict['tests']['test']['package']['TestName']) + + self.assertTrue('FAIL' not in results_dict['num_failures_by_type'] + or results_dict['num_failures_by_type']['FAIL'] == 0) + self.assertTrue('PASS' not in results_dict['num_failures_by_type'] + or results_dict['num_failures_by_type']['PASS'] == 0) + self.assertIn('SKIP', results_dict['num_failures_by_type']) + self.assertEqual(1, results_dict['num_failures_by_type']['SKIP']) + + def testGenerateJsonTestResultFormatDict_failedResultWithRetry(self): + result_1 = base_test_result.BaseTestResult('test.package.TestName', + base_test_result.ResultType.FAIL) + run_results_1 = base_test_result.TestRunResults() + run_results_1.AddResult(result_1) + + # Simulate a second retry with failure. + result_2 = base_test_result.BaseTestResult('test.package.TestName', + base_test_result.ResultType.FAIL) + run_results_2 = base_test_result.TestRunResults() + run_results_2.AddResult(result_2) + + all_results = [run_results_1, run_results_2] + + results_dict = json_results.GenerateJsonTestResultFormatDict( + all_results, False) + self.assertEqual(1, len(results_dict['tests'])) + self.assertEqual(1, len(results_dict['tests']['test'])) + self.assertEqual(1, len(results_dict['tests']['test']['package'])) + self.assertEqual( + 'PASS', + results_dict['tests']['test']['package']['TestName']['expected']) + self.assertEqual( + 'FAIL FAIL', + results_dict['tests']['test']['package']['TestName']['actual']) + self.assertEqual( + True, + results_dict['tests']['test']['package']['TestName']['is_unexpected']) + + self.assertTrue('PASS' not in results_dict['num_failures_by_type'] + or results_dict['num_failures_by_type']['PASS'] == 0) + # According to the spec: If a test was run more than once, only the first + # invocation's result is included in the totals. + self.assertIn('FAIL', results_dict['num_failures_by_type']) + self.assertEqual(1, results_dict['num_failures_by_type']['FAIL']) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/android/pylib/results/presentation/__init__.py b/third_party/libwebrtc/build/android/pylib/results/presentation/__init__.py new file mode 100644 index 0000000000..a22a6ee39a --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2017 The Chromium 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/third_party/libwebrtc/build/android/pylib/results/presentation/javascript/main_html.js b/third_party/libwebrtc/build/android/pylib/results/presentation/javascript/main_html.js new file mode 100644 index 0000000000..3d94663e33 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/javascript/main_html.js @@ -0,0 +1,193 @@ +// Copyright 2017 The Chromium 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 getArguments() { + // Returns the URL arguments as a dictionary. + args = {} + var s = location.search; + if (s) { + var vals = s.substring(1).split('&'); + for (var i = 0; i < vals.length; i++) { + var pair = vals[i].split('='); + args[pair[0]] = pair[1]; + } + } + return args; +} + +function showSuiteTable(show_the_table) { + document.getElementById('suite-table').style.display = ( + show_the_table ? 'table' : 'none'); +} + +function showTestTable(show_the_table) { + document.getElementById('test-table').style.display = ( + show_the_table ? 'table' : 'none'); +} + +function showTestsOfOneSuiteOnly(suite_name) { + setTitle('Test Results of Suite: ' + suite_name) + show_all = (suite_name == 'TOTAL') + var testTableBlocks = document.getElementById('test-table') + .getElementsByClassName('row_block'); + Array.prototype.slice.call(testTableBlocks) + .forEach(function(testTableBlock) { + if (!show_all) { + var table_block_in_suite = (testTableBlock.firstElementChild + .firstElementChild.firstElementChild.innerHTML) + .startsWith(suite_name); + if (!table_block_in_suite) { + testTableBlock.style.display = 'none'; + return; + } + } + testTableBlock.style.display = 'table-row-group'; + }); + showTestTable(true); + showSuiteTable(false); + window.scrollTo(0, 0); +} + +function showTestsOfOneSuiteOnlyWithNewState(suite_name) { + showTestsOfOneSuiteOnly(suite_name); + history.pushState({suite: suite_name}, suite_name, ''); +} + +function showSuiteTableOnly() { + setTitle('Suites Summary') + showTestTable(false); + showSuiteTable(true); + window.scrollTo(0, 0); +} + +function showSuiteTableOnlyWithReplaceState() { + showSuiteTableOnly(); + history.replaceState({}, 'suite_table', ''); +} + +function setBrowserBackButtonLogic() { + window.onpopstate = function(event) { + if (!event.state || !event.state.suite) { + showSuiteTableOnly(); + } else { + showTestsOfOneSuiteOnly(event.state.suite); + } + }; +} + +function setTitle(title) { + document.getElementById('summary-header').textContent = title; +} + +function sortByColumn(head) { + var table = head.parentNode.parentNode.parentNode; + var rowBlocks = Array.prototype.slice.call( + table.getElementsByTagName('tbody')); + + // Determine whether to asc or desc and set arrows. + var headers = head.parentNode.getElementsByTagName('th'); + var headIndex = Array.prototype.slice.call(headers).indexOf(head); + var asc = -1; + for (var i = 0; i < headers.length; i++) { + if (headers[i].dataset.ascSorted != 0) { + if (headers[i].dataset.ascSorted == 1) { + headers[i].getElementsByClassName('up')[0] + .style.display = 'none'; + } else { + headers[i].getElementsByClassName('down')[0] + .style.display = 'none'; + } + if (headers[i] == head) { + asc = headers[i].dataset.ascSorted * -1; + } else { + headers[i].dataset.ascSorted = 0; + } + break; + } + } + headers[headIndex].dataset.ascSorted = asc; + if (asc == 1) { + headers[headIndex].getElementsByClassName('up')[0] + .style.display = 'inline'; + } else { + headers[headIndex].getElementsByClassName('down')[0] + .style.display = 'inline'; + } + + // Sort the array by the specified column number (col) and order (asc). + rowBlocks.sort(function (a, b) { + if (a.style.display == 'none') { + return -1; + } else if (b.style.display == 'none') { + return 1; + } + var a_rows = Array.prototype.slice.call(a.children); + var b_rows = Array.prototype.slice.call(b.children); + if (head.className == "text") { + // If sorting by text, we only compare the entry on the first row. + var aInnerHTML = a_rows[0].children[headIndex].innerHTML; + var bInnerHTML = b_rows[0].children[headIndex].innerHTML; + return (aInnerHTML == bInnerHTML) ? 0 : ( + (aInnerHTML > bInnerHTML) ? asc : -1 * asc); + } else if (head.className == "number") { + // If sorting by number, for example, duration, + // we will sum up the durations of different test runs + // for one specific test case and sort by the sum. + var avalue = 0; + var bvalue = 0; + a_rows.forEach(function (row, i) { + var index = (i > 0) ? headIndex - 1 : headIndex; + avalue += Number(row.children[index].innerHTML); + }); + b_rows.forEach(function (row, i) { + var index = (i > 0) ? headIndex - 1 : headIndex; + bvalue += Number(row.children[index].innerHTML); + }); + } else if (head.className == "flaky") { + // Flakiness = (#total - #success - #skipped) / (#total - #skipped) + var a_success_or_skipped = 0; + var a_skipped = 0; + var b_success_or_skipped = 0; + var b_skipped = 0; + a_rows.forEach(function (row, i) { + var index = (i > 0) ? headIndex - 1 : headIndex; + var status = row.children[index].innerHTML.trim(); + if (status == 'SUCCESS') { + a_success_or_skipped += 1; + } + if (status == 'SKIPPED') { + a_success_or_skipped += 1; + a_skipped += 1; + } + }); + b_rows.forEach(function (row, i) { + var index = (i > 0) ? headIndex - 1 : headIndex; + var status = row.children[index].innerHTML.trim(); + if (status == 'SUCCESS') { + b_success_or_skipped += 1; + } + if (status == 'SKIPPED') { + b_success_or_skipped += 1; + b_skipped += 1; + } + }); + var atotal_minus_skipped = a_rows.length - a_skipped; + var btotal_minus_skipped = b_rows.length - b_skipped; + + var avalue = ((atotal_minus_skipped == 0) ? -1 : + (a_rows.length - a_success_or_skipped) / atotal_minus_skipped); + var bvalue = ((btotal_minus_skipped == 0) ? -1 : + (b_rows.length - b_success_or_skipped) / btotal_minus_skipped); + } + return asc * (avalue - bvalue); + }); + + for (var i = 0; i < rowBlocks.length; i++) { + table.appendChild(rowBlocks[i]); + } +} + +function sortSuiteTableByFailedTestCases() { + sortByColumn(document.getElementById('number_fail_tests')); +} diff --git a/third_party/libwebrtc/build/android/pylib/results/presentation/standard_gtest_merge.py b/third_party/libwebrtc/build/android/pylib/results/presentation/standard_gtest_merge.py new file mode 100755 index 0000000000..d458223abb --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/standard_gtest_merge.py @@ -0,0 +1,173 @@ +#! /usr/bin/env python3 +# +# Copyright 2017 The Chromium 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 print_function + +import argparse +import json +import os +import sys + + +def merge_shard_results(summary_json, jsons_to_merge): + """Reads JSON test output from all shards and combines them into one. + + Returns dict with merged test output on success or None on failure. Emits + annotations. + """ + try: + with open(summary_json) as f: + summary = json.load(f) + except (IOError, ValueError): + raise Exception('Summary json cannot be loaded.') + + # Merge all JSON files together. Keep track of missing shards. + merged = { + 'all_tests': set(), + 'disabled_tests': set(), + 'global_tags': set(), + 'missing_shards': [], + 'per_iteration_data': [], + 'swarming_summary': summary, + 'links': set() + } + for index, result in enumerate(summary['shards']): + if result is None: + merged['missing_shards'].append(index) + continue + + # Author note: this code path doesn't trigger convert_to_old_format() in + # client/swarming.py, which means the state enum is saved in its string + # name form, not in the number form. + state = result.get('state') + if state == 'BOT_DIED': + print( + 'Shard #%d had a Swarming internal failure' % index, file=sys.stderr) + elif state == 'EXPIRED': + print('There wasn\'t enough capacity to run your test', file=sys.stderr) + elif state == 'TIMED_OUT': + print('Test runtime exceeded allocated time' + 'Either it ran for too long (hard timeout) or it didn\'t produce ' + 'I/O for an extended period of time (I/O timeout)', + file=sys.stderr) + elif state != 'COMPLETED': + print('Invalid Swarming task state: %s' % state, file=sys.stderr) + + json_data, err_msg = load_shard_json(index, result.get('task_id'), + jsons_to_merge) + if json_data: + # Set-like fields. + for key in ('all_tests', 'disabled_tests', 'global_tags', 'links'): + merged[key].update(json_data.get(key), []) + + # 'per_iteration_data' is a list of dicts. Dicts should be merged + # together, not the 'per_iteration_data' list itself. + merged['per_iteration_data'] = merge_list_of_dicts( + merged['per_iteration_data'], json_data.get('per_iteration_data', [])) + else: + merged['missing_shards'].append(index) + print('No result was found: %s' % err_msg, file=sys.stderr) + + # If some shards are missing, make it known. Continue parsing anyway. Step + # should be red anyway, since swarming.py return non-zero exit code in that + # case. + if merged['missing_shards']: + as_str = ', '.join([str(shard) for shard in merged['missing_shards']]) + print('some shards did not complete: %s' % as_str, file=sys.stderr) + # Not all tests run, combined JSON summary can not be trusted. + merged['global_tags'].add('UNRELIABLE_RESULTS') + + # Convert to jsonish dict. + for key in ('all_tests', 'disabled_tests', 'global_tags', 'links'): + merged[key] = sorted(merged[key]) + return merged + + +OUTPUT_JSON_SIZE_LIMIT = 100 * 1024 * 1024 # 100 MB + + +def load_shard_json(index, task_id, jsons_to_merge): + """Reads JSON output of the specified shard. + + Args: + output_dir: The directory in which to look for the JSON output to load. + index: The index of the shard to load data for, this is for old api. + task_id: The directory of the shard to load data for, this is for new api. + + Returns: A tuple containing: + * The contents of path, deserialized into a python object. + * An error string. + (exactly one of the tuple elements will be non-None). + """ + matching_json_files = [ + j for j in jsons_to_merge + if (os.path.basename(j) == 'output.json' and + (os.path.basename(os.path.dirname(j)) == str(index) or + os.path.basename(os.path.dirname(j)) == task_id))] + + if not matching_json_files: + print('shard %s test output missing' % index, file=sys.stderr) + return (None, 'shard %s test output was missing' % index) + elif len(matching_json_files) > 1: + print('duplicate test output for shard %s' % index, file=sys.stderr) + return (None, 'shard %s test output was duplicated' % index) + + path = matching_json_files[0] + + try: + filesize = os.stat(path).st_size + if filesize > OUTPUT_JSON_SIZE_LIMIT: + print( + 'output.json is %d bytes. Max size is %d' % (filesize, + OUTPUT_JSON_SIZE_LIMIT), + file=sys.stderr) + return (None, 'shard %s test output exceeded the size limit' % index) + + with open(path) as f: + return (json.load(f), None) + except (IOError, ValueError, OSError) as e: + print('Missing or invalid gtest JSON file: %s' % path, file=sys.stderr) + print('%s: %s' % (type(e).__name__, e), file=sys.stderr) + + return (None, 'shard %s test output was missing or invalid' % index) + + +def merge_list_of_dicts(left, right): + """Merges dicts left[0] with right[0], left[1] with right[1], etc.""" + output = [] + for i in range(max(len(left), len(right))): + left_dict = left[i] if i < len(left) else {} + right_dict = right[i] if i < len(right) else {} + merged_dict = left_dict.copy() + merged_dict.update(right_dict) + output.append(merged_dict) + return output + + +def standard_gtest_merge( + output_json, summary_json, jsons_to_merge): + + output = merge_shard_results(summary_json, jsons_to_merge) + with open(output_json, 'wb') as f: + json.dump(output, f) + + return 0 + + +def main(raw_args): + parser = argparse.ArgumentParser() + parser.add_argument('--summary-json') + parser.add_argument('-o', '--output-json', required=True) + parser.add_argument('jsons_to_merge', nargs='*') + + args = parser.parse_args(raw_args) + + return standard_gtest_merge( + args.output_json, args.summary_json, args.jsons_to_merge) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/pylib/results/presentation/template/main.html b/third_party/libwebrtc/build/android/pylib/results/presentation/template/main.html new file mode 100644 index 0000000000..e30d7d3f23 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/template/main.html @@ -0,0 +1,93 @@ + + + + + + + + +
+

+ {% for tb_value in tb_values %} + {% include 'template/table.html' %} + {% endfor %} +
+ {% if feedback_url %} +
+ Feedback + + {%- endif %} + + diff --git a/third_party/libwebrtc/build/android/pylib/results/presentation/template/table.html b/third_party/libwebrtc/build/android/pylib/results/presentation/template/table.html new file mode 100644 index 0000000000..4240043490 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/template/table.html @@ -0,0 +1,60 @@ + + + + {% for cell in tb_value.table_headers -%} + + {%- endfor %} + + + {% for block in tb_value.table_row_blocks -%} + + {% for row in block -%} + + {% for cell in row -%} + {% if cell.rowspan -%} + + {%- endfor %} + + {%- endfor %} + + {%- endfor %} + + + {% for cell in tb_value.table_footer -%} + + {%- endfor %} + + + diff --git a/third_party/libwebrtc/build/android/pylib/results/presentation/test_results_presentation.py b/third_party/libwebrtc/build/android/pylib/results/presentation/test_results_presentation.py new file mode 100755 index 0000000000..fc14b8bf03 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/results/presentation/test_results_presentation.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +# +# Copyright 2017 The Chromium 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 collections +import contextlib +import json +import logging +import tempfile +import os +import sys +try: + from urllib.parse import urlencode + from urllib.request import urlopen +except ImportError: + from urllib import urlencode + from urllib2 import urlopen + + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +BASE_DIR = os.path.abspath(os.path.join( + CURRENT_DIR, '..', '..', '..', '..', '..')) + +sys.path.append(os.path.join(BASE_DIR, 'build', 'android')) +from pylib.results.presentation import standard_gtest_merge +from pylib.utils import google_storage_helper # pylint: disable=import-error + +sys.path.append(os.path.join(BASE_DIR, 'third_party')) +import jinja2 # pylint: disable=import-error +JINJA_ENVIRONMENT = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), + autoescape=True) + + +def cell(data, html_class='center'): + """Formats table cell data for processing in jinja template.""" + return { + 'data': data, + 'class': html_class, + } + + +def pre_cell(data, html_class='center'): + """Formats table
 cell data for processing in jinja template."""
+  return {
+    'cell_type': 'pre',
+    'data': data,
+    'class': html_class,
+  }
+
+
+class LinkTarget(object):
+  # Opens the linked document in a new window or tab.
+  NEW_TAB = '_blank'
+  # Opens the linked document in the same frame as it was clicked.
+  CURRENT_TAB = '_self'
+
+
+def link(data, href, target=LinkTarget.CURRENT_TAB):
+  """Formats  tag data for processing in jinja template.
+
+  Args:
+    data: String link appears as on HTML page.
+    href: URL where link goes.
+    target: Where link should be opened (e.g. current tab or new tab).
+  """
+  return {
+    'data': data,
+    'href': href,
+    'target': target,
+  }
+
+
+def links_cell(links, html_class='center', rowspan=None):
+  """Formats table cell with links for processing in jinja template.
+
+  Args:
+    links: List of link dictionaries. Use |link| function to generate them.
+    html_class: Class for table cell.
+    rowspan: Rowspan HTML attribute.
+  """
+  return {
+    'cell_type': 'links',
+    'class': html_class,
+    'links': links,
+    'rowspan': rowspan,
+  }
+
+
+def action_cell(action, data, html_class):
+  """Formats table cell with javascript actions.
+
+  Args:
+    action: Javscript action.
+    data: Data in cell.
+    class: Class for table cell.
+  """
+  return {
+    'cell_type': 'action',
+    'action': action,
+    'data': data,
+    'class': html_class,
+  }
+
+
+def flakiness_dashbord_link(test_name, suite_name):
+  url_args = urlencode([('testType', suite_name), ('tests', test_name)])
+  return ('https://test-results.appspot.com/'
+         'dashboards/flakiness_dashboard.html#%s' % url_args)
+
+
+def logs_cell(result, test_name, suite_name):
+  """Formats result logs data for processing in jinja template."""
+  link_list = []
+  result_link_dict = result.get('links', {})
+  result_link_dict['flakiness'] = flakiness_dashbord_link(
+      test_name, suite_name)
+  for name, href in sorted(result_link_dict.items()):
+    link_list.append(link(
+        data=name,
+        href=href,
+        target=LinkTarget.NEW_TAB))
+  if link_list:
+    return links_cell(link_list)
+  else:
+    return cell('(no logs)')
+
+
+def code_search(test, cs_base_url):
+  """Returns URL for test on codesearch."""
+  search = test.replace('#', '.')
+  return '%s/search/?q=%s&type=cs' % (cs_base_url, search)
+
+
+def status_class(status):
+  """Returns HTML class for test status."""
+  if not status:
+    return 'failure unknown'
+  status = status.lower()
+  if status not in ('success', 'skipped'):
+    return 'failure %s' % status
+  return status
+
+
+def create_test_table(results_dict, cs_base_url, suite_name):
+  """Format test data for injecting into HTML table."""
+
+  header_row = [
+    cell(data='test_name', html_class='text'),
+    cell(data='status', html_class='flaky'),
+    cell(data='elapsed_time_ms', html_class='number'),
+    cell(data='logs', html_class='text'),
+    cell(data='output_snippet', html_class='text'),
+  ]
+
+  test_row_blocks = []
+  for test_name, test_results in results_dict.items():
+    test_runs = []
+    for index, result in enumerate(test_results):
+      if index == 0:
+        test_run = [links_cell(
+            links=[
+                link(href=code_search(test_name, cs_base_url),
+                     target=LinkTarget.NEW_TAB,
+                     data=test_name)],
+            rowspan=len(test_results),
+            html_class='left %s' % test_name
+        )]                                          # test_name
+      else:
+        test_run = []
+
+      test_run.extend([
+          cell(data=result['status'] or 'UNKNOWN',
+                                                    # status
+               html_class=('center %s' %
+                  status_class(result['status']))),
+          cell(data=result['elapsed_time_ms']),     # elapsed_time_ms
+          logs_cell(result, test_name, suite_name), # logs
+          pre_cell(data=result['output_snippet'],   # output_snippet
+                   html_class='left'),
+      ])
+      test_runs.append(test_run)
+    test_row_blocks.append(test_runs)
+  return header_row, test_row_blocks
+
+
+def create_suite_table(results_dict):
+  """Format test suite data for injecting into HTML table."""
+
+  SUCCESS_COUNT_INDEX = 1
+  FAIL_COUNT_INDEX = 2
+  ALL_COUNT_INDEX = 3
+  TIME_INDEX = 4
+
+  header_row = [
+    cell(data='suite_name', html_class='text'),
+    cell(data='number_success_tests', html_class='number'),
+    cell(data='number_fail_tests', html_class='number'),
+    cell(data='all_tests', html_class='number'),
+    cell(data='elapsed_time_ms', html_class='number'),
+  ]
+
+  footer_row = [
+    action_cell(
+          'showTestsOfOneSuiteOnlyWithNewState("TOTAL")',
+          'TOTAL',
+          'center'
+        ),         # TOTAL
+    cell(data=0),  # number_success_tests
+    cell(data=0),  # number_fail_tests
+    cell(data=0),  # all_tests
+    cell(data=0),  # elapsed_time_ms
+  ]
+
+  suite_row_dict = {}
+  for test_name, test_results in results_dict.items():
+    # TODO(mikecase): This logic doesn't work if there are multiple test runs.
+    # That is, if 'per_iteration_data' has multiple entries.
+    # Since we only care about the result of the last test run.
+    result = test_results[-1]
+
+    suite_name = (test_name.split('#')[0] if '#' in test_name
+                  else test_name.split('.')[0])
+    if suite_name in suite_row_dict:
+      suite_row = suite_row_dict[suite_name]
+    else:
+      suite_row = [
+        action_cell(
+          'showTestsOfOneSuiteOnlyWithNewState("%s")' % suite_name,
+          suite_name,
+          'left'
+        ),             # suite_name
+        cell(data=0),  # number_success_tests
+        cell(data=0),  # number_fail_tests
+        cell(data=0),  # all_tests
+        cell(data=0),  # elapsed_time_ms
+      ]
+
+    suite_row_dict[suite_name] = suite_row
+
+    suite_row[ALL_COUNT_INDEX]['data'] += 1
+    footer_row[ALL_COUNT_INDEX]['data'] += 1
+
+    if result['status'] == 'SUCCESS':
+      suite_row[SUCCESS_COUNT_INDEX]['data'] += 1
+      footer_row[SUCCESS_COUNT_INDEX]['data'] += 1
+    elif result['status'] != 'SKIPPED':
+      suite_row[FAIL_COUNT_INDEX]['data'] += 1
+      footer_row[FAIL_COUNT_INDEX]['data'] += 1
+
+    # Some types of crashes can have 'null' values for elapsed_time_ms.
+    if result['elapsed_time_ms'] is not None:
+      suite_row[TIME_INDEX]['data'] += result['elapsed_time_ms']
+      footer_row[TIME_INDEX]['data'] += result['elapsed_time_ms']
+
+  for suite in list(suite_row_dict.values()):
+    if suite[FAIL_COUNT_INDEX]['data'] > 0:
+      suite[FAIL_COUNT_INDEX]['class'] += ' failure'
+    else:
+      suite[FAIL_COUNT_INDEX]['class'] += ' success'
+
+  if footer_row[FAIL_COUNT_INDEX]['data'] > 0:
+    footer_row[FAIL_COUNT_INDEX]['class'] += ' failure'
+  else:
+    footer_row[FAIL_COUNT_INDEX]['class'] += ' success'
+
+  return (header_row, [[suite_row]
+                       for suite_row in list(suite_row_dict.values())],
+          footer_row)
+
+
+def feedback_url(result_details_link):
+  # pylint: disable=redefined-variable-type
+  url_args = [
+      ('labels', 'Pri-2,Type-Bug,Restrict-View-Google'),
+      ('summary', 'Result Details Feedback:'),
+      ('components', 'Test>Android'),
+  ]
+  if result_details_link:
+    url_args.append(('comment', 'Please check out: %s' % result_details_link))
+  url_args = urlencode(url_args)
+  # pylint: enable=redefined-variable-type
+  return 'https://bugs.chromium.org/p/chromium/issues/entry?%s' % url_args
+
+
+def results_to_html(results_dict, cs_base_url, bucket, test_name,
+                    builder_name, build_number, local_output):
+  """Convert list of test results into html format.
+
+  Args:
+    local_output: Whether this results file is uploaded to Google Storage or
+        just a local file.
+  """
+  test_rows_header, test_rows = create_test_table(
+      results_dict, cs_base_url, test_name)
+  suite_rows_header, suite_rows, suite_row_footer = create_suite_table(
+      results_dict)
+
+  suite_table_values = {
+    'table_id': 'suite-table',
+    'table_headers': suite_rows_header,
+    'table_row_blocks': suite_rows,
+    'table_footer': suite_row_footer,
+  }
+
+  test_table_values = {
+    'table_id': 'test-table',
+    'table_headers': test_rows_header,
+    'table_row_blocks': test_rows,
+  }
+
+  main_template = JINJA_ENVIRONMENT.get_template(
+      os.path.join('template', 'main.html'))
+
+  if local_output:
+    html_render = main_template.render(  #  pylint: disable=no-member
+        {
+          'tb_values': [suite_table_values, test_table_values],
+          'feedback_url': feedback_url(None),
+        })
+    return (html_render, None, None)
+  else:
+    dest = google_storage_helper.unique_name(
+        '%s_%s_%s' % (test_name, builder_name, build_number))
+    result_details_link = google_storage_helper.get_url_link(
+        dest, '%s/html' % bucket)
+    html_render = main_template.render(  #  pylint: disable=no-member
+        {
+          'tb_values': [suite_table_values, test_table_values],
+          'feedback_url': feedback_url(result_details_link),
+        })
+    return (html_render, dest, result_details_link)
+
+
+def result_details(json_path, test_name, cs_base_url, bucket=None,
+                   builder_name=None, build_number=None, local_output=False):
+  """Get result details from json path and then convert results to html.
+
+  Args:
+    local_output: Whether this results file is uploaded to Google Storage or
+        just a local file.
+  """
+
+  with open(json_path) as json_file:
+    json_object = json.loads(json_file.read())
+
+  if not 'per_iteration_data' in json_object:
+    return 'Error: json file missing per_iteration_data.'
+
+  results_dict = collections.defaultdict(list)
+  for testsuite_run in json_object['per_iteration_data']:
+    for test, test_runs in testsuite_run.items():
+      results_dict[test].extend(test_runs)
+  return results_to_html(results_dict, cs_base_url, bucket, test_name,
+                         builder_name, build_number, local_output)
+
+
+def upload_to_google_bucket(html, bucket, dest):
+  with tempfile.NamedTemporaryFile(suffix='.html') as temp_file:
+    temp_file.write(html)
+    temp_file.flush()
+    return google_storage_helper.upload(
+        name=dest,
+        filepath=temp_file.name,
+        bucket='%s/html' % bucket,
+        content_type='text/html',
+        authenticated_link=True)
+
+
+def ui_screenshot_set(json_path):
+  with open(json_path) as json_file:
+    json_object = json.loads(json_file.read())
+  if not 'per_iteration_data' in json_object:
+    # This will be reported as an error by result_details, no need to duplicate.
+    return None
+  ui_screenshots = []
+  # pylint: disable=too-many-nested-blocks
+  for testsuite_run in json_object['per_iteration_data']:
+    for _, test_runs in testsuite_run.items():
+      for test_run in test_runs:
+        if 'ui screenshot' in test_run['links']:
+          screenshot_link = test_run['links']['ui screenshot']
+          if screenshot_link.startswith('file:'):
+            with contextlib.closing(urlopen(screenshot_link)) as f:
+              test_screenshots = json.load(f)
+          else:
+            # Assume anything that isn't a file link is a google storage link
+            screenshot_string = google_storage_helper.read_from_link(
+                screenshot_link)
+            if not screenshot_string:
+              logging.error('Bad screenshot link %s', screenshot_link)
+              continue
+            test_screenshots = json.loads(
+                screenshot_string)
+          ui_screenshots.extend(test_screenshots)
+  # pylint: enable=too-many-nested-blocks
+
+  if ui_screenshots:
+    return json.dumps(ui_screenshots)
+  return None
+
+
+def upload_screenshot_set(json_path, test_name, bucket, builder_name,
+                          build_number):
+  screenshot_set = ui_screenshot_set(json_path)
+  if not screenshot_set:
+    return None
+  dest = google_storage_helper.unique_name(
+    'screenshots_%s_%s_%s' % (test_name, builder_name, build_number),
+    suffix='.json')
+  with tempfile.NamedTemporaryFile(suffix='.json') as temp_file:
+    temp_file.write(screenshot_set)
+    temp_file.flush()
+    return google_storage_helper.upload(
+        name=dest,
+        filepath=temp_file.name,
+        bucket='%s/json' % bucket,
+        content_type='application/json',
+        authenticated_link=True)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--json-file', help='Path of json file.')
+  parser.add_argument('--cs-base-url', help='Base url for code search.',
+                      default='http://cs.chromium.org')
+  parser.add_argument('--bucket', help='Google storage bucket.', required=True)
+  parser.add_argument('--builder-name', help='Builder name.')
+  parser.add_argument('--build-number', help='Build number.')
+  parser.add_argument('--test-name', help='The name of the test.',
+                      required=True)
+  parser.add_argument(
+      '-o', '--output-json',
+      help='(Swarming Merge Script API) '
+           'Output JSON file to create.')
+  parser.add_argument(
+      '--build-properties',
+      help='(Swarming Merge Script API) '
+           'Build property JSON file provided by recipes.')
+  parser.add_argument(
+      '--summary-json',
+      help='(Swarming Merge Script API) '
+           'Summary of shard state running on swarming. '
+           '(Output of the swarming.py collect '
+           '--task-summary-json=XXX command.)')
+  parser.add_argument(
+      '--task-output-dir',
+      help='(Swarming Merge Script API) '
+           'Directory containing all swarming task results.')
+  parser.add_argument(
+      'positional', nargs='*',
+      help='output.json from shards.')
+
+  args = parser.parse_args()
+
+  if ((args.build_properties is None) ==
+         (args.build_number is None or args.builder_name is None)):
+    raise parser.error('Exactly one of build_perperties or '
+                       '(build_number or builder_name) should be given.')
+
+  if (args.build_number is None) != (args.builder_name is None):
+    raise parser.error('args.build_number and args.builder_name '
+                       'has to be be given together'
+                       'or not given at all.')
+
+  if len(args.positional) == 0 and args.json_file is None:
+    if args.output_json:
+      with open(args.output_json, 'w') as f:
+        json.dump({}, f)
+    return
+  elif len(args.positional) != 0 and args.json_file:
+    raise parser.error('Exactly one of args.positional and '
+                       'args.json_file should be given.')
+
+  if args.build_properties:
+    build_properties = json.loads(args.build_properties)
+    if ((not 'buildnumber' in build_properties) or
+        (not 'buildername' in build_properties)):
+      raise parser.error('Build number/builder name not specified.')
+    build_number = build_properties['buildnumber']
+    builder_name = build_properties['buildername']
+  elif args.build_number and args.builder_name:
+    build_number = args.build_number
+    builder_name = args.builder_name
+
+  if args.positional:
+    if len(args.positional) == 1:
+      json_file = args.positional[0]
+    else:
+      if args.output_json and args.summary_json:
+        standard_gtest_merge.standard_gtest_merge(
+            args.output_json, args.summary_json, args.positional)
+        json_file = args.output_json
+      elif not args.output_json:
+        raise Exception('output_json required by merge API is missing.')
+      else:
+        raise Exception('summary_json required by merge API is missing.')
+  elif args.json_file:
+    json_file = args.json_file
+
+  if not os.path.exists(json_file):
+    raise IOError('--json-file %s not found.' % json_file)
+
+  # Link to result details presentation page is a part of the page.
+  result_html_string, dest, result_details_link = result_details(
+      json_file, args.test_name, args.cs_base_url, args.bucket,
+      builder_name, build_number)
+
+  result_details_link_2 = upload_to_google_bucket(
+      result_html_string.encode('UTF-8'),
+      args.bucket, dest)
+  assert result_details_link == result_details_link_2, (
+      'Result details link do not match. The link returned by get_url_link'
+      ' should be the same as that returned by upload.')
+
+  ui_screenshot_set_link = upload_screenshot_set(json_file, args.test_name,
+      args.bucket, builder_name, build_number)
+
+  if ui_screenshot_set_link:
+    ui_catalog_url = 'https://chrome-ui-catalog.appspot.com/'
+    ui_catalog_query = urlencode({'screenshot_source': ui_screenshot_set_link})
+    ui_screenshot_link = '%s?%s' % (ui_catalog_url, ui_catalog_query)
+
+  if args.output_json:
+    with open(json_file) as original_json_file:
+      json_object = json.load(original_json_file)
+      json_object['links'] = {
+          'result_details (logcats, flakiness links)': result_details_link
+      }
+
+      if ui_screenshot_set_link:
+        json_object['links']['ui screenshots'] = ui_screenshot_link
+
+      with open(args.output_json, 'w') as f:
+        json.dump(json_object, f)
+  else:
+    print('Result Details: %s' % result_details_link)
+
+    if ui_screenshot_set_link:
+      print('UI Screenshots %s' % ui_screenshot_link)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/pylib/results/report_results.py b/third_party/libwebrtc/build/android/pylib/results/report_results.py
new file mode 100644
index 0000000000..56eefac46c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/results/report_results.py
@@ -0,0 +1,136 @@
+# 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."""
+
+from __future__ import print_function
+
+import logging
+import os
+import re
+
+from pylib import constants
+from pylib.results.flakiness_dashboard import results_uploader
+from pylib.utils import logging_utils
+
+
+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(
+          '\n%s results for %s build %s:' %
+          (test_type, os.environ.get('BUILDBOT_BUILDERNAME'),
+           os.environ.get('BUILDBOT_BUILDNUMBER')),
+          file=log_file)
+    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(
+        '%s%s' % (shortened_suite_name.ljust(30), results.GetShortForm()),
+        file=log_file)
+
+
+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:
+    # TODO(jbudorick): remove Instrumentation once instrumentation tests
+    # switch to platform mode.
+    if test_type in ('instrumentation', 'Instrumentation'):
+      if flakiness_server == constants.UPSTREAM_FLAKINESS_SERVER:
+        assert test_package in ['ContentShellTest',
+                                'ChromePublicTest',
+                                'ChromeSyncShellTest',
+                                'SystemWebViewShellLayoutTest',
+                                'WebViewInstrumentationTest']
+        dashboard_test_type = ('%s_instrumentation_tests' %
+                               test_package.lower().rstrip('test'))
+      # Downstream server.
+      else:
+        dashboard_test_type = 'Chromium_Android_Instrumentation'
+
+    elif test_type == 'gtest':
+      dashboard_test_type = test_package
+
+    else:
+      logging.warning('Invalid test type')
+      return
+
+    results_uploader.Upload(
+        results, flakiness_server, dashboard_test_type)
+
+  except Exception: # pylint: disable=broad-except
+    logging.exception('Failure while logging to %s', flakiness_server)
+
+
+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. ['Feature', 'SmallTest']).
+    flakiness_server: If provider, upload the results to flakiness dashboard
+                      with this URL.
+    """
+  # pylint doesn't like how colorama set up its color enums.
+  # pylint: disable=no-member
+  black_on_white = (logging_utils.BACK.WHITE, logging_utils.FORE.BLACK)
+  with logging_utils.OverrideColor(logging.CRITICAL, black_on_white):
+    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():
+      color = black_on_white
+      if 'FAILED' in line:
+        # Red on white, dim.
+        color = (logging_utils.BACK.WHITE, logging_utils.FORE.RED,
+                 logging_utils.STYLE.DIM)
+      elif 'PASSED' in line:
+        # Green on white, dim.
+        color = (logging_utils.BACK.WHITE, logging_utils.FORE.GREEN,
+                 logging_utils.STYLE.DIM)
+      with logging_utils.OverrideColor(logging.CRITICAL, color):
+        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/third_party/libwebrtc/build/android/pylib/symbols/__init__.py b/third_party/libwebrtc/build/android/pylib/symbols/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py
new file mode 100755
index 0000000000..f40c7581fc
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Dump shared library information from an APK file.
+
+This script is used to dump which *uncompressed* native shared libraries an
+APK contains, as well as their position within the file. This is mostly useful
+to diagnose logcat and tombstone symbolization issues when the libraries are
+loaded directly from the APK at runtime.
+
+The default format will print one line per uncompressed shared library with the
+following format:
+
+  0x 0x 0x 
+
+The --format=python option can be used to dump the same information that is
+easy to use in a Python script, e.g. with a line like:
+
+  (0x, 0x, 0x, ),
+"""
+
+
+
+import argparse
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
+
+from pylib.symbols import apk_native_libs
+
+def main():
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.RawDescriptionHelpFormatter)
+
+  parser.add_argument('apk', help='Input APK file path.')
+
+  parser.add_argument('--format', help='Select output format',
+                      default='default', choices=['default', 'python'])
+
+  args = parser.parse_args()
+
+  apk_reader = apk_native_libs.ApkReader(args.apk)
+  lib_map = apk_native_libs.ApkNativeLibraries(apk_reader)
+  for lib_path, file_offset, file_size in lib_map.GetDumpList():
+    if args.format == 'python':
+      print('(0x%08x, 0x%08x, 0x%08x, \'%s\'),' %
+            (file_offset, file_offset + file_size, file_size, lib_path))
+    else:
+      print('0x%08x 0x%08x 0x%08x %s' % (file_offset, file_offset + file_size,
+                                         file_size, lib_path))
+
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py
new file mode 100644
index 0000000000..59b303990b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py
@@ -0,0 +1,419 @@
+# Copyright 2018 The Chromium 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 struct
+import zipfile
+
+# The default zipfile python module cannot open APKs properly, but this
+# fixes it. Note that simply importing this file is sufficient to
+# ensure that zip works correctly for all other modules. See:
+# http://bugs.python.org/issue14315
+# https://hg.python.org/cpython/rev/6dd5e9556a60#l2.8
+def _PatchZipFile():
+  # pylint: disable=protected-access
+  oldDecodeExtra = zipfile.ZipInfo._decodeExtra
+  def decodeExtra(self):
+    try:
+      oldDecodeExtra(self)
+    except struct.error:
+      pass
+  zipfile.ZipInfo._decodeExtra = decodeExtra
+_PatchZipFile()
+
+
+class ApkZipInfo(object):
+  """Models a single file entry from an ApkReader.
+
+  This is very similar to the zipfile.ZipInfo class. It provides a few
+  properties describing the entry:
+    - filename          (same as ZipInfo.filename)
+    - file_size         (same as ZipInfo.file_size)
+    - compress_size     (same as ZipInfo.file_size)
+    - file_offset       (note: not provided by ZipInfo)
+
+  And a few useful methods: IsCompressed() and IsElfFile().
+
+  Entries can be created by using ApkReader() methods.
+  """
+  def __init__(self, zip_file, zip_info):
+    """Construct instance. Do not call this directly. Use ApkReader methods."""
+    self._file = zip_file
+    self._info = zip_info
+    self._file_offset = None
+
+  @property
+  def filename(self):
+    """Entry's file path within APK."""
+    return self._info.filename
+
+  @property
+  def file_size(self):
+    """Entry's extracted file size in bytes."""
+    return self._info.file_size
+
+  @property
+  def compress_size(self):
+    """Entry' s compressed file size in bytes."""
+    return self._info.compress_size
+
+  @property
+  def file_offset(self):
+    """Entry's starting file offset in the APK."""
+    if self._file_offset is None:
+      self._file_offset = self._ZipFileOffsetFromLocalHeader(
+          self._file.fp, self._info.header_offset)
+    return self._file_offset
+
+  def __repr__(self):
+    """Convert to string for debugging."""
+    return 'ApkZipInfo["%s",size=0x%x,compressed=0x%x,offset=0x%x]' % (
+        self.filename, self.file_size, self.compress_size, self.file_offset)
+
+  def IsCompressed(self):
+    """Returns True iff the entry is compressed."""
+    return self._info.compress_type != zipfile.ZIP_STORED
+
+  def IsElfFile(self):
+    """Returns True iff the entry is an ELF file."""
+    with self._file.open(self._info, 'r') as f:
+      return f.read(4) == '\x7fELF'
+
+  @staticmethod
+  def _ZipFileOffsetFromLocalHeader(fd, local_header_offset):
+    """Return a file's start offset from its zip archive local header.
+
+    Args:
+      fd: Input file object.
+      local_header_offset: Local header offset (from its ZipInfo entry).
+    Returns:
+      file start offset.
+    """
+    FILE_NAME_LEN_OFFSET = 26
+    FILE_NAME_OFFSET = 30
+    fd.seek(local_header_offset + FILE_NAME_LEN_OFFSET)
+    file_name_len = struct.unpack('H', fd.read(2))[0]
+    extra_field_len = struct.unpack('H', fd.read(2))[0]
+    file_offset = (local_header_offset + FILE_NAME_OFFSET +
+                    file_name_len + extra_field_len)
+    return file_offset
+
+
+class ApkReader(object):
+  """A convenience class used to read the content of APK files.
+
+  Its design is very similar to the one from zipfile.ZipFile, except
+  that its returns ApkZipInfo entries which provide a |file_offset|
+  property that can be used to know where a given file is located inside
+  the archive.
+
+  It is also easy to mock for unit-testing (see MockApkReader in
+  apk_utils_unittest.py) without creating any files on disk.
+
+  Usage is the following:
+    - Create an instance using a with statement (for proper unit-testing).
+    - Call ListEntries() to list all entries in the archive. This returns
+      a list of ApkZipInfo entries.
+    - Or call FindEntry() corresponding to a given path within the archive.
+
+  For example:
+     with ApkReader(input_apk_path) as reader:
+       info = reader.FindEntry('lib/armeabi-v7a/libfoo.so')
+       if info.IsCompressed() or not info.IsElfFile():
+         raise Exception('Invalid library path")
+
+  The ApkZipInfo can be used to inspect the entry's metadata, or read its
+  content with the ReadAll() method. See its documentation for all details.
+  """
+  def __init__(self, apk_path):
+    """Initialize instance."""
+    self._zip_file = zipfile.ZipFile(apk_path, 'r')
+    self._path = apk_path
+
+  def __enter__(self):
+    """Python context manager entry."""
+    return self
+
+  def __exit__(self, *kwargs):
+    """Python context manager exit."""
+    self.Close()
+
+  @property
+  def path(self):
+    """The corresponding input APK path."""
+    return self._path
+
+  def Close(self):
+    """Close the reader (and underlying ZipFile instance)."""
+    self._zip_file.close()
+
+  def ListEntries(self):
+    """Return a list of ApkZipInfo entries for this APK."""
+    result = []
+    for info in self._zip_file.infolist():
+      result.append(ApkZipInfo(self._zip_file, info))
+    return result
+
+  def FindEntry(self, file_path):
+    """Return an ApkZipInfo instance for a given archive file path.
+
+    Args:
+      file_path: zip file path.
+    Return:
+      A new ApkZipInfo entry on success.
+    Raises:
+      KeyError on failure (entry not found).
+    """
+    info = self._zip_file.getinfo(file_path)
+    return ApkZipInfo(self._zip_file, info)
+
+
+
+class ApkNativeLibraries(object):
+  """A class for the list of uncompressed shared libraries inside an APK.
+
+  Create a new instance by passing the path to an input APK, then use
+  the FindLibraryByOffset() method to find the native shared library path
+  corresponding to a given file offset.
+
+  GetAbiList() and GetLibrariesList() can also be used to inspect
+  the state of the instance.
+  """
+  def __init__(self, apk_reader):
+    """Initialize instance.
+
+    Args:
+      apk_reader: An ApkReader instance corresponding to the input APK.
+    """
+    self._native_libs = []
+    for entry in apk_reader.ListEntries():
+      # Chromium uses so-called 'placeholder' native shared libraries
+      # that have a size of 0, and are only used to deal with bugs in
+      # older Android system releases (they are never loaded and cannot
+      # appear in stack traces). Ignore these here to avoid generating
+      # confusing results.
+      if entry.file_size == 0:
+        continue
+
+      # Only uncompressed libraries can appear in stack traces.
+      if entry.IsCompressed():
+        continue
+
+      # Only consider files within lib/ and with a filename ending with .so
+      # at the moment. NOTE: Do not require a 'lib' prefix, since that would
+      # prevent finding the 'crazy.libXXX.so' libraries used by Chromium.
+      if (not entry.filename.startswith('lib/') or
+          not entry.filename.endswith('.so')):
+        continue
+
+      lib_path = entry.filename
+
+      self._native_libs.append(
+          (lib_path, entry.file_offset, entry.file_offset + entry.file_size))
+
+  def IsEmpty(self):
+    """Return true iff the list is empty."""
+    return not bool(self._native_libs)
+
+  def GetLibraries(self):
+    """Return the list of all library paths in this instance."""
+    return sorted([x[0] for x in self._native_libs])
+
+  def GetDumpList(self):
+    """Retrieve full library map.
+
+    Returns:
+      A list of (lib_path, file_offset, file_size) tuples, sorted
+      in increasing |file_offset| values.
+    """
+    result = []
+    for entry in self._native_libs:
+      lib_path, file_start, file_end = entry
+      result.append((lib_path, file_start, file_end - file_start))
+
+    return sorted(result, key=lambda x: x[1])
+
+  def FindLibraryByOffset(self, file_offset):
+    """Find the native library at a given file offset.
+
+    Args:
+      file_offset: File offset within the original APK.
+    Returns:
+      Returns a (lib_path, lib_offset) tuple on success, or (None, 0)
+      on failure. Note that lib_path will omit the 'lib/$ABI/' prefix,
+      lib_offset is the adjustment of file_offset within the library.
+    """
+    for lib_path, start_offset, end_offset in self._native_libs:
+      if file_offset >= start_offset and file_offset < end_offset:
+        return (lib_path, file_offset - start_offset)
+
+    return (None, 0)
+
+
+class ApkLibraryPathTranslator(object):
+  """Translates APK file paths + byte offsets into library path + offset.
+
+  The purpose of this class is to translate a native shared library path
+  that points to an APK into a new device-specific path that points to a
+  native shared library, as if it was installed there. E.g.:
+
+     ('/data/data/com.example.app-1/base.apk', 0x123be00)
+
+  would be translated into:
+
+     ('/data/data/com.example.app-1/base.apk!lib/libfoo.so', 0x3be00)
+
+  If the original APK (installed as base.apk) contains an uncompressed shared
+  library under lib/armeabi-v7a/libfoo.so at offset 0x120000.
+
+  Note that the virtual device path after the ! doesn't necessarily match
+  the path inside the .apk. This doesn't really matter for the rest of
+  the symbolization functions since only the file's base name can be used
+  to find the corresponding file on the host.
+
+  Usage is the following:
+
+     1/ Create new instance.
+
+     2/ Call AddHostApk() one or several times to add the host path
+        of an APK, its package name, and device-installed named.
+
+     3/ Call TranslatePath() to translate a (path, offset) tuple corresponding
+        to an on-device APK, into the corresponding virtual device library
+        path and offset.
+  """
+
+  # Depending on the version of the system, a non-system APK might be installed
+  # on a path that looks like the following:
+  #
+  #  * /data/..../-.apk, where  is used to
+  #    distinguish several versions of the APK during package updates.
+  #
+  #  * /data/..../-/base.apk, where  is a
+  #    string of random ASCII characters following the dash after the
+  #    package name. This serves as a way to distinguish the installation
+  #    paths during package update, and randomize its final location
+  #    (to prevent apps from hard-coding the paths to other apps).
+  #
+  #    Note that the 'base.apk' name comes from the system.
+  #
+  #  * /data/.../-/.apk, where 
+  #    is the same as above, and  is the name of am app bundle
+  #    split APK.
+  #
+  # System APKs are installed on paths that look like /system/app/Foo.apk
+  # but this class ignores them intentionally.
+
+  # Compiler regular expression for the first format above.
+  _RE_APK_PATH_1 = re.compile(
+      r'/data/.*/(?P[A-Za-z0-9_.]+)-(?P[0-9]+)\.apk')
+
+  # Compiled regular expression for the second and third formats above.
+  _RE_APK_PATH_2 = re.compile(
+      r'/data/.*/(?P[A-Za-z0-9_.]+)-(?P[^/]+)/' +
+      r'(?P.+\.apk)')
+
+  def __init__(self):
+    """Initialize instance. Call AddHostApk() to add host apk file paths."""
+    self._path_map = {}  # Maps (package_name, apk_name) to host-side APK path.
+    self._libs_map = {}  # Maps APK host path to ApkNativeLibrariesMap instance.
+
+  def AddHostApk(self, package_name, native_libs, device_apk_name=None):
+    """Add a file path to the host APK search list.
+
+    Args:
+      package_name: Corresponding apk package name.
+      native_libs: ApkNativeLibraries instance for the corresponding APK.
+      device_apk_name: Optional expected name of the installed APK on the
+        device. This is only useful when symbolizing app bundle that run on
+        Android L+. I.e. it will be ignored in other cases.
+    """
+    if native_libs.IsEmpty():
+      logging.debug('Ignoring host APK without any uncompressed native ' +
+                    'libraries: %s', device_apk_name)
+      return
+
+    # If the APK name is not provided, use the default of 'base.apk'. This
+    # will be ignored if we find -.apk file paths
+    # in the input, but will work properly for Android L+, as long as we're
+    # not using Android app bundles.
+    device_apk_name = device_apk_name or 'base.apk'
+
+    key = "%s/%s" % (package_name, device_apk_name)
+    if key in self._libs_map:
+      raise KeyError('There is already an APK associated with (%s)' % key)
+
+    self._libs_map[key] = native_libs
+
+  @staticmethod
+  def _MatchApkDeviceInstallPath(apk_path):
+    """Check whether a given path matches an installed APK device file path.
+
+    Args:
+      apk_path: Device-specific file path.
+    Returns:
+      On success, a (package_name, apk_name) tuple. On failure, (None. None).
+    """
+    m = ApkLibraryPathTranslator._RE_APK_PATH_1.match(apk_path)
+    if m:
+      return (m.group('package_name'), 'base.apk')
+
+    m = ApkLibraryPathTranslator._RE_APK_PATH_2.match(apk_path)
+    if m:
+      return (m.group('package_name'), m.group('apk_name'))
+
+    return (None, None)
+
+  def TranslatePath(self, apk_path, apk_offset):
+    """Translate a potential apk file path + offset into library path + offset.
+
+    Args:
+      apk_path: Library or apk file path on the device (e.g.
+        '/data/data/com.example.app-XSAHKSJH/base.apk').
+      apk_offset: Byte offset within the library or apk.
+
+    Returns:
+      a new (lib_path, lib_offset) tuple. If |apk_path| points to an APK,
+      then this function searches inside the corresponding host-side APKs
+      (added with AddHostApk() above) for the corresponding uncompressed
+      native shared library at |apk_offset|, if found, this returns a new
+      device-specific path corresponding to a virtual installation of said
+      library with an adjusted offset.
+
+      Otherwise, just return the original (apk_path, apk_offset) values.
+    """
+    if not apk_path.endswith('.apk'):
+      return (apk_path, apk_offset)
+
+    apk_package, apk_name = self._MatchApkDeviceInstallPath(apk_path)
+    if not apk_package:
+      return (apk_path, apk_offset)
+
+    key = '%s/%s' % (apk_package, apk_name)
+    native_libs = self._libs_map.get(key)
+    if not native_libs:
+      logging.debug('Unknown %s package', key)
+      return (apk_path, apk_offset)
+
+    lib_name, new_offset = native_libs.FindLibraryByOffset(apk_offset)
+    if not lib_name:
+      logging.debug('Invalid offset in %s.apk package: %d', key, apk_offset)
+      return (apk_path, apk_offset)
+
+    lib_name = os.path.basename(lib_name)
+
+    # Some libraries are stored with a crazy. prefix inside the APK, this
+    # is done to prevent the PackageManager from extracting the libraries
+    # at installation time when running on pre Android M systems, where the
+    # system linker cannot load libraries directly from APKs.
+    crazy_prefix = 'crazy.'
+    if lib_name.startswith(crazy_prefix):
+      lib_name = lib_name[len(crazy_prefix):]
+
+    # Put this in a fictional lib sub-directory for good measure.
+    new_path = '%s!lib/%s' % (apk_path, lib_name)
+
+    return (new_path, new_offset)
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py
new file mode 100755
index 0000000000..59f7e2b02a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py
@@ -0,0 +1,397 @@
+#!/usr/bin/env vpython3
+# Copyright 2018 The Chromium 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 unittest
+
+from pylib.symbols import apk_native_libs
+
+# Mock ELF-like data
+MOCK_ELF_DATA = '\x7fELFFFFFFFFFFFFFFFF'
+
+class MockApkZipInfo(object):
+  """A mock ApkZipInfo class, returned by MockApkReaderFactory instances."""
+  def __init__(self, filename, file_size, compress_size, file_offset,
+               file_data):
+    self.filename = filename
+    self.file_size = file_size
+    self.compress_size = compress_size
+    self.file_offset = file_offset
+    self._data = file_data
+
+  def __repr__(self):
+    """Convert to string for debugging."""
+    return 'MockApkZipInfo["%s",size=%d,compressed=%d,offset=%d]' % (
+        self.filename, self.file_size, self.compress_size, self.file_offset)
+
+  def IsCompressed(self):
+    """Returns True iff the entry is compressed."""
+    return self.file_size != self.compress_size
+
+  def IsElfFile(self):
+    """Returns True iff the entry is an ELF file."""
+    if not self._data or len(self._data) < 4:
+      return False
+
+    return self._data[0:4] == '\x7fELF'
+
+
+class MockApkReader(object):
+  """A mock ApkReader instance used during unit-testing.
+
+  Do not use directly, but use a MockApkReaderFactory context, as in:
+
+     with MockApkReaderFactory() as mock:
+       mock.AddTestEntry(file_path, file_size, compress_size, file_data)
+       ...
+
+       # Actually returns the mock instance.
+       apk_reader = apk_native_libs.ApkReader('/some/path.apk')
+  """
+  def __init__(self, apk_path='test.apk'):
+    """Initialize instance."""
+    self._entries = []
+    self._fake_offset = 0
+    self._path = apk_path
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, *kwarg):
+    self.Close()
+    return
+
+  @property
+  def path(self):
+    return self._path
+
+  def AddTestEntry(self, filepath, file_size, compress_size, file_data):
+    """Add a new entry to the instance for unit-tests.
+
+    Do not call this directly, use the AddTestEntry() method on the parent
+    MockApkReaderFactory instance.
+
+    Args:
+      filepath: archive file path.
+      file_size: uncompressed file size in bytes.
+      compress_size: compressed size in bytes.
+      file_data: file data to be checked by IsElfFile()
+
+    Note that file_data can be None, or that its size can be actually
+    smaller than |compress_size| when used during unit-testing.
+    """
+    self._entries.append(MockApkZipInfo(filepath, file_size, compress_size,
+                         self._fake_offset, file_data))
+    self._fake_offset += compress_size
+
+  def Close(self): # pylint: disable=no-self-use
+    """Close this reader instance."""
+    return
+
+  def ListEntries(self):
+    """Return a list of MockApkZipInfo instances for this input APK."""
+    return self._entries
+
+  def FindEntry(self, file_path):
+    """Find the MockApkZipInfo instance corresponds to a given file path."""
+    for entry in self._entries:
+      if entry.filename == file_path:
+        return entry
+    raise KeyError('Could not find mock zip archive member for: ' + file_path)
+
+
+class MockApkReaderTest(unittest.TestCase):
+
+  def testEmpty(self):
+    with MockApkReader() as reader:
+      entries = reader.ListEntries()
+      self.assertTrue(len(entries) == 0)
+      with self.assertRaises(KeyError):
+        reader.FindEntry('non-existent-entry.txt')
+
+  def testSingleEntry(self):
+    with MockApkReader() as reader:
+      reader.AddTestEntry('some-path/some-file', 20000, 12345, file_data=None)
+      entries = reader.ListEntries()
+      self.assertTrue(len(entries) == 1)
+      entry = entries[0]
+      self.assertEqual(entry.filename, 'some-path/some-file')
+      self.assertEqual(entry.file_size, 20000)
+      self.assertEqual(entry.compress_size, 12345)
+      self.assertTrue(entry.IsCompressed())
+
+      entry2 = reader.FindEntry('some-path/some-file')
+      self.assertEqual(entry, entry2)
+
+  def testMultipleEntries(self):
+    with MockApkReader() as reader:
+      _ENTRIES = {
+        'foo.txt': (1024, 1024, 'FooFooFoo'),
+        'lib/bar/libcode.so': (16000, 3240, 1024, '\x7fELFFFFFFFFFFFF'),
+      }
+      for path, props in _ENTRIES.items():
+        reader.AddTestEntry(path, props[0], props[1], props[2])
+
+      entries = reader.ListEntries()
+      self.assertEqual(len(entries), len(_ENTRIES))
+      for path, props in _ENTRIES.items():
+        entry = reader.FindEntry(path)
+        self.assertEqual(entry.filename, path)
+        self.assertEqual(entry.file_size, props[0])
+        self.assertEqual(entry.compress_size, props[1])
+
+
+class ApkNativeLibrariesTest(unittest.TestCase):
+
+  def setUp(self):
+    logging.getLogger().setLevel(logging.ERROR)
+
+  def testEmptyApk(self):
+    with MockApkReader() as reader:
+      libs_map = apk_native_libs.ApkNativeLibraries(reader)
+      self.assertTrue(libs_map.IsEmpty())
+      self.assertEqual(len(libs_map.GetLibraries()), 0)
+      lib_path, lib_offset = libs_map.FindLibraryByOffset(0)
+      self.assertIsNone(lib_path)
+      self.assertEqual(lib_offset, 0)
+
+  def testSimpleApk(self):
+    with MockApkReader() as reader:
+      _MOCK_ENTRIES = [
+        # Top-level library should be ignored.
+        ('libfoo.so', 1000, 1000, MOCK_ELF_DATA, False),
+        # Library not under lib/ should be ignored.
+        ('badlib/test-abi/libfoo2.so', 1001, 1001, MOCK_ELF_DATA, False),
+        # Library under lib// but without .so extension should be ignored.
+        ('lib/test-abi/libfoo4.so.1', 1003, 1003, MOCK_ELF_DATA, False),
+        # Library under lib// with .so suffix, but compressed -> ignored.
+        ('lib/test-abi/libfoo5.so', 1004, 1003, MOCK_ELF_DATA, False),
+        # First correct library
+        ('lib/test-abi/libgood1.so', 1005, 1005, MOCK_ELF_DATA, True),
+        # Second correct library: support sub-directories
+        ('lib/test-abi/subdir/libgood2.so', 1006, 1006, MOCK_ELF_DATA, True),
+        # Third correct library, no lib prefix required
+        ('lib/test-abi/crazy.libgood3.so', 1007, 1007, MOCK_ELF_DATA, True),
+      ]
+      file_offsets = []
+      prev_offset = 0
+      for ent in _MOCK_ENTRIES:
+        reader.AddTestEntry(ent[0], ent[1], ent[2], ent[3])
+        file_offsets.append(prev_offset)
+        prev_offset += ent[2]
+
+      libs_map = apk_native_libs.ApkNativeLibraries(reader)
+      self.assertFalse(libs_map.IsEmpty())
+      self.assertEqual(libs_map.GetLibraries(), [
+          'lib/test-abi/crazy.libgood3.so',
+          'lib/test-abi/libgood1.so',
+          'lib/test-abi/subdir/libgood2.so',
+          ])
+
+      BIAS = 10
+      for mock_ent, file_offset in zip(_MOCK_ENTRIES, file_offsets):
+        if mock_ent[4]:
+          lib_path, lib_offset = libs_map.FindLibraryByOffset(
+              file_offset + BIAS)
+          self.assertEqual(lib_path, mock_ent[0])
+          self.assertEqual(lib_offset, BIAS)
+
+
+  def testMultiAbiApk(self):
+    with MockApkReader() as reader:
+      _MOCK_ENTRIES = [
+        ('lib/abi1/libfoo.so', 1000, 1000, MOCK_ELF_DATA),
+        ('lib/abi2/libfoo.so', 1000, 1000, MOCK_ELF_DATA),
+      ]
+      for ent in _MOCK_ENTRIES:
+        reader.AddTestEntry(ent[0], ent[1], ent[2], ent[3])
+
+      libs_map = apk_native_libs.ApkNativeLibraries(reader)
+      self.assertFalse(libs_map.IsEmpty())
+      self.assertEqual(libs_map.GetLibraries(), [
+          'lib/abi1/libfoo.so', 'lib/abi2/libfoo.so'])
+
+      lib1_name, lib1_offset = libs_map.FindLibraryByOffset(10)
+      self.assertEqual(lib1_name, 'lib/abi1/libfoo.so')
+      self.assertEqual(lib1_offset, 10)
+
+      lib2_name, lib2_offset = libs_map.FindLibraryByOffset(1000)
+      self.assertEqual(lib2_name, 'lib/abi2/libfoo.so')
+      self.assertEqual(lib2_offset, 0)
+
+
+class MockApkNativeLibraries(apk_native_libs.ApkNativeLibraries):
+  """A mock ApkNativeLibraries instance that can be used as input to
+     ApkLibraryPathTranslator without creating an ApkReader instance.
+
+     Create a new instance, then call AddTestEntry or AddTestEntries
+     as many times as necessary, before using it as a regular
+     ApkNativeLibraries instance.
+  """
+  # pylint: disable=super-init-not-called
+  def __init__(self):
+    self._native_libs = []
+
+  # pylint: enable=super-init-not-called
+
+  def AddTestEntry(self, lib_path, file_offset, file_size):
+    """Add a new test entry.
+
+    Args:
+      entry: A tuple of (library-path, file-offset, file-size) values,
+          (e.g. ('lib/armeabi-v8a/libfoo.so', 0x10000, 0x2000)).
+    """
+    self._native_libs.append((lib_path, file_offset, file_offset + file_size))
+
+  def AddTestEntries(self, entries):
+    """Add a list of new test entries.
+
+    Args:
+      entries: A list of (library-path, file-offset, file-size) values.
+    """
+    for entry in entries:
+      self.AddTestEntry(entry[0], entry[1], entry[2])
+
+
+class MockApkNativeLibrariesTest(unittest.TestCase):
+
+  def testEmptyInstance(self):
+    mock = MockApkNativeLibraries()
+    self.assertTrue(mock.IsEmpty())
+    self.assertEqual(mock.GetLibraries(), [])
+    self.assertEqual(mock.GetDumpList(), [])
+
+  def testAddTestEntry(self):
+    mock = MockApkNativeLibraries()
+    mock.AddTestEntry('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000)
+    mock.AddTestEntry('lib/x86/libzoo.so', 0x10000, 0x10000)
+    mock.AddTestEntry('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000)
+    self.assertFalse(mock.IsEmpty())
+    self.assertEqual(mock.GetLibraries(), ['lib/armeabi-v7a/libbar.so',
+                                           'lib/armeabi-v7a/libfoo.so',
+                                           'lib/x86/libzoo.so'])
+    self.assertEqual(mock.GetDumpList(), [
+        ('lib/x86/libzoo.so', 0x10000, 0x10000),
+        ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+        ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+    ])
+
+  def testAddTestEntries(self):
+    mock = MockApkNativeLibraries()
+    mock.AddTestEntries([
+      ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+      ('lib/x86/libzoo.so', 0x10000, 0x10000),
+      ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+    ])
+    self.assertFalse(mock.IsEmpty())
+    self.assertEqual(mock.GetLibraries(), ['lib/armeabi-v7a/libbar.so',
+                                           'lib/armeabi-v7a/libfoo.so',
+                                           'lib/x86/libzoo.so'])
+    self.assertEqual(mock.GetDumpList(), [
+        ('lib/x86/libzoo.so', 0x10000, 0x10000),
+        ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+        ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+    ])
+
+
+class ApkLibraryPathTranslatorTest(unittest.TestCase):
+
+  def _CheckUntranslated(self, translator, path, offset):
+    """Check that a given (path, offset) is not modified by translation."""
+    self.assertEqual(translator.TranslatePath(path, offset), (path, offset))
+
+
+  def _CheckTranslated(self, translator, path, offset, new_path, new_offset):
+    """Check that (path, offset) is translated into (new_path, new_offset)."""
+    self.assertEqual(translator.TranslatePath(path, offset),
+                     (new_path, new_offset))
+
+  def testEmptyInstance(self):
+    translator = apk_native_libs.ApkLibraryPathTranslator()
+    self._CheckUntranslated(
+        translator, '/data/data/com.example.app-1/base.apk', 0x123456)
+
+  def testSimpleApk(self):
+    mock_libs = MockApkNativeLibraries()
+    mock_libs.AddTestEntries([
+      ('lib/test-abi/libfoo.so', 200, 2000),
+      ('lib/test-abi/libbar.so', 3200, 3000),
+      ('lib/test-abi/crazy.libzoo.so', 6200, 2000),
+    ])
+    translator = apk_native_libs.ApkLibraryPathTranslator()
+    translator.AddHostApk('com.example.app', mock_libs)
+
+    # Offset is within the first uncompressed library
+    self._CheckTranslated(
+        translator,
+        '/data/data/com.example.app-9.apk', 757,
+        '/data/data/com.example.app-9.apk!lib/libfoo.so', 557)
+
+    # Offset is within the second compressed library.
+    self._CheckUntranslated(
+        translator,
+        '/data/data/com.example.app-9/base.apk', 2800)
+
+    # Offset is within the third uncompressed library.
+    self._CheckTranslated(
+        translator,
+        '/data/data/com.example.app-1/base.apk', 3628,
+        '/data/data/com.example.app-1/base.apk!lib/libbar.so', 428)
+
+    # Offset is within the fourth uncompressed library with crazy. prefix
+    self._CheckTranslated(
+        translator,
+        '/data/data/com.example.app-XX/base.apk', 6500,
+        '/data/data/com.example.app-XX/base.apk!lib/libzoo.so', 300)
+
+    # Out-of-bounds apk offset.
+    self._CheckUntranslated(
+        translator,
+        '/data/data/com.example.app-1/base.apk', 10000)
+
+    # Invalid package name.
+    self._CheckUntranslated(
+        translator, '/data/data/com.example2.app-1/base.apk', 757)
+
+    # Invalid apk name.
+    self._CheckUntranslated(
+          translator, '/data/data/com.example.app-2/not-base.apk', 100)
+
+    # Invalid file extensions.
+    self._CheckUntranslated(
+          translator, '/data/data/com.example.app-2/base', 100)
+
+    self._CheckUntranslated(
+          translator, '/data/data/com.example.app-2/base.apk.dex', 100)
+
+  def testBundleApks(self):
+    mock_libs1 = MockApkNativeLibraries()
+    mock_libs1.AddTestEntries([
+      ('lib/test-abi/libfoo.so', 200, 2000),
+      ('lib/test-abi/libbbar.so', 3200, 3000),
+    ])
+    mock_libs2 = MockApkNativeLibraries()
+    mock_libs2.AddTestEntries([
+      ('lib/test-abi/libzoo.so', 200, 2000),
+      ('lib/test-abi/libtool.so', 3000, 4000),
+    ])
+    translator = apk_native_libs.ApkLibraryPathTranslator()
+    translator.AddHostApk('com.example.app', mock_libs1, 'base-master.apk')
+    translator.AddHostApk('com.example.app', mock_libs2, 'feature-master.apk')
+
+    self._CheckTranslated(
+      translator,
+      '/data/app/com.example.app-XUIYIUW/base-master.apk', 757,
+      '/data/app/com.example.app-XUIYIUW/base-master.apk!lib/libfoo.so', 557)
+
+    self._CheckTranslated(
+      translator,
+      '/data/app/com.example.app-XUIYIUW/feature-master.apk', 3200,
+      '/data/app/com.example.app-XUIYIUW/feature-master.apk!lib/libtool.so',
+      200)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py b/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py
new file mode 100644
index 0000000000..1fd188a425
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py
@@ -0,0 +1,178 @@
+# Copyright 2017 The Chromium 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 subprocess
+import threading
+import time
+import uuid
+
+from devil.utils import reraiser_thread
+from pylib import constants
+
+
+_MINIUMUM_TIMEOUT = 3.0
+_PER_LINE_TIMEOUT = .002  # Should be able to process 500 lines per second.
+_PROCESS_START_TIMEOUT = 10.0
+_MAX_RESTARTS = 10  # Should be plenty unless tool is crashing on start-up.
+
+
+class Deobfuscator(object):
+  def __init__(self, mapping_path):
+    script_path = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android',
+                               'stacktrace', 'java_deobfuscate.py')
+    cmd = [script_path, mapping_path]
+    # Allow only one thread to call TransformLines() at a time.
+    self._lock = threading.Lock()
+    # Ensure that only one thread attempts to kill self._proc in Close().
+    self._close_lock = threading.Lock()
+    self._closed_called = False
+    # Assign to None so that attribute exists if Popen() throws.
+    self._proc = None
+    # Start process eagerly to hide start-up latency.
+    self._proc_start_time = time.time()
+    self._proc = subprocess.Popen(cmd,
+                                  bufsize=1,
+                                  stdin=subprocess.PIPE,
+                                  stdout=subprocess.PIPE,
+                                  universal_newlines=True,
+                                  close_fds=True)
+
+  def IsClosed(self):
+    return self._closed_called or self._proc.returncode is not None
+
+  def IsBusy(self):
+    return self._lock.locked()
+
+  def IsReady(self):
+    return not self.IsClosed() and not self.IsBusy()
+
+  def TransformLines(self, lines):
+    """Deobfuscates obfuscated names found in the given lines.
+
+    If anything goes wrong (process crashes, timeout, etc), returns |lines|.
+
+    Args:
+      lines: A list of strings without trailing newlines.
+
+    Returns:
+      A list of strings without trailing newlines.
+    """
+    if not lines:
+      return []
+
+    # Deobfuscated stacks contain more frames than obfuscated ones when method
+    # inlining occurs. To account for the extra output lines, keep reading until
+    # this eof_line token is reached.
+    eof_line = uuid.uuid4().hex
+    out_lines = []
+
+    def deobfuscate_reader():
+      while True:
+        line = self._proc.stdout.readline()
+        # Return an empty string at EOF (when stdin is closed).
+        if not line:
+          break
+        line = line[:-1]
+        if line == eof_line:
+          break
+        out_lines.append(line)
+
+    if self.IsBusy():
+      logging.warning('deobfuscator: Having to wait for Java deobfuscation.')
+
+    # Allow only one thread to operate at a time.
+    with self._lock:
+      if self.IsClosed():
+        if not self._closed_called:
+          logging.warning('deobfuscator: Process exited with code=%d.',
+                          self._proc.returncode)
+          self.Close()
+        return lines
+
+      # TODO(agrieve): Can probably speed this up by only sending lines through
+      #     that might contain an obfuscated name.
+      reader_thread = reraiser_thread.ReraiserThread(deobfuscate_reader)
+      reader_thread.start()
+
+      try:
+        self._proc.stdin.write('\n'.join(lines))
+        self._proc.stdin.write('\n{}\n'.format(eof_line))
+        self._proc.stdin.flush()
+        time_since_proc_start = time.time() - self._proc_start_time
+        timeout = (max(0, _PROCESS_START_TIMEOUT - time_since_proc_start) +
+                   max(_MINIUMUM_TIMEOUT, len(lines) * _PER_LINE_TIMEOUT))
+        reader_thread.join(timeout)
+        if self.IsClosed():
+          logging.warning(
+              'deobfuscator: Close() called by another thread during join().')
+          return lines
+        if reader_thread.is_alive():
+          logging.error('deobfuscator: Timed out.')
+          self.Close()
+          return lines
+        return out_lines
+      except IOError:
+        logging.exception('deobfuscator: Exception during java_deobfuscate')
+        self.Close()
+        return lines
+
+  def Close(self):
+    with self._close_lock:
+      needs_closing = not self.IsClosed()
+      self._closed_called = True
+
+    if needs_closing:
+      self._proc.stdin.close()
+      self._proc.kill()
+      self._proc.wait()
+
+  def __del__(self):
+    # self._proc is None when Popen() fails.
+    if not self._closed_called and self._proc:
+      logging.error('deobfuscator: Forgot to Close()')
+      self.Close()
+
+
+class DeobfuscatorPool(object):
+  # As of Sep 2017, each instance requires about 500MB of RAM, as measured by:
+  # /usr/bin/time -v build/android/stacktrace/java_deobfuscate.py \
+  #     out/Release/apks/ChromePublic.apk.mapping
+  def __init__(self, mapping_path, pool_size=4):
+    self._mapping_path = mapping_path
+    self._pool = [Deobfuscator(mapping_path) for _ in range(pool_size)]
+    # Allow only one thread to select from the pool at a time.
+    self._lock = threading.Lock()
+    self._num_restarts = 0
+
+  def TransformLines(self, lines):
+    with self._lock:
+      assert self._pool, 'TransformLines() called on a closed DeobfuscatorPool.'
+
+      # De-obfuscation is broken.
+      if self._num_restarts == _MAX_RESTARTS:
+        raise Exception('Deobfuscation seems broken.')
+
+      # Restart any closed Deobfuscators.
+      for i, d in enumerate(self._pool):
+        if d.IsClosed():
+          logging.warning('deobfuscator: Restarting closed instance.')
+          self._pool[i] = Deobfuscator(self._mapping_path)
+          self._num_restarts += 1
+          if self._num_restarts == _MAX_RESTARTS:
+            logging.warning('deobfuscator: MAX_RESTARTS reached.')
+
+      selected = next((x for x in self._pool if x.IsReady()), self._pool[0])
+      # Rotate the order so that next caller will not choose the same one.
+      self._pool.remove(selected)
+      self._pool.append(selected)
+
+    return selected.TransformLines(lines)
+
+  def Close(self):
+    with self._lock:
+      for d in self._pool:
+        d.Close()
+      self._pool = None
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py
new file mode 100644
index 0000000000..4198511bf3
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py
@@ -0,0 +1,497 @@
+# 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
+try:
+  from queue import Empty, Queue
+except ImportError:
+  from Queue import Empty, 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
+
+
+ELF_MAGIC = '\x7f\x45\x4c\x46'
+
+
+def ContainsElfMagic(file_path):
+  if os.path.getsize(file_path) < 4:
+    return False
+  try:
+    with open(file_path, 'r') as f:
+      b = f.read(4)
+      return b == ELF_MAGIC
+  except IOError:
+    return False
+
+
+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 WaitForIdle(self):
+    """Waits for all the outstanding requests to complete."""
+    for a2l in self._a2l_instances:
+      a2l.WaitForIdle()
+
+  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 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 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 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: # pylint: disable=broad-except
+        # 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()
+
+      # 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,
+                                    universal_newlines=True,
+                                    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, my_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')
+          if not line1:
+            break
+          line2 = process_pipe.readline().rstrip('\r\n')
+          if 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
+          my_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/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py
new file mode 100755
index 0000000000..f906da8314
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# 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 unittest
+
+from pylib.symbols import elf_symbolizer
+from pylib.symbols 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 range(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 testWaitForIdle(self):
+    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)
+    symbolizer.WaitForIdle()
+
+    # 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 range(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 range(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/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line
new file mode 100755
index 0000000000..8b2a72375d
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line
@@ -0,0 +1,81 @@
+#!/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).
+"""
+
+from __future__ import print_function
+
+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/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py b/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py
new file mode 100644
index 0000000000..fdd47780f7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py
@@ -0,0 +1,86 @@
+# Copyright 2017 The Chromium 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
+import time
+
+from devil.utils import cmd_helper
+from pylib import constants
+
+_STACK_TOOL = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..',
+                          'third_party', 'android_platform', 'development',
+                          'scripts', 'stack')
+ABI_REG = re.compile('ABI: \'(.+?)\'')
+
+
+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)
+
+
+class Symbolizer(object):
+  """A helper class to symbolize stack."""
+
+  def __init__(self, apk_under_test=None):
+    self._apk_under_test = apk_under_test
+    self._time_spent_symbolizing = 0
+
+
+  def __del__(self):
+    self.CleanUp()
+
+
+  def CleanUp(self):
+    """Clean up the temporary directory of apk libs."""
+    if self._time_spent_symbolizing > 0:
+      logging.info(
+          'Total time spent symbolizing: %.2fs', self._time_spent_symbolizing)
+
+
+  def ExtractAndResolveNativeStackTraces(self, data_to_symbolize,
+                                         device_abi, include_stack=True):
+    """Run the stack tool for given input.
+
+    Args:
+      data_to_symbolize: a list of strings to symbolize.
+      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.
+    """
+    if not os.path.exists(_STACK_TOOL):
+      logging.warning('%s missing. Unable to resolve native stack traces.',
+                      _STACK_TOOL)
+      return
+
+    arch = _DeviceAbiToArch(device_abi)
+    if not arch:
+      logging.warning('No device_abi can be found.')
+      return
+
+    cmd = [_STACK_TOOL, '--arch', arch, '--output-directory',
+           constants.GetOutDirectory(), '--more-info']
+    env = dict(os.environ)
+    env['PYTHONDONTWRITEBYTECODE'] = '1'
+    with tempfile.NamedTemporaryFile(mode='w') as f:
+      f.write('\n'.join(data_to_symbolize))
+      f.flush()
+      start = time.time()
+      try:
+        _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env)
+      finally:
+        self._time_spent_symbolizing += time.time() - start
+    for line in output.splitlines():
+      if not include_stack and 'Stack Data:' in line:
+        break
+      yield line
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py
new file mode 100644
index 0000000000..0b6ec8bb29
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py
@@ -0,0 +1,813 @@
+# Copyright 2018 The Chromium 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 bisect
+import collections
+import logging
+import os
+import re
+
+from pylib.constants import host_paths
+from pylib.symbols import elf_symbolizer
+
+
+def _AndroidAbiToCpuArch(android_abi):
+  """Return the Chromium CPU architecture name for a given Android ABI."""
+  _ARCH_MAP = {
+    'armeabi': 'arm',
+    'armeabi-v7a': 'arm',
+    'arm64-v8a': 'arm64',
+    'x86_64': 'x64',
+  }
+  return _ARCH_MAP.get(android_abi, android_abi)
+
+
+def _HexAddressRegexpFor(android_abi):
+  """Return a regexp matching hexadecimal addresses for a given Android ABI."""
+  if android_abi in ['x86_64', 'arm64-v8a', 'mips64']:
+    width = 16
+  else:
+    width = 8
+  return '[0-9a-f]{%d}' % width
+
+
+class HostLibraryFinder(object):
+  """Translate device library path to matching host unstripped library path.
+
+  Usage is the following:
+    1) Create instance.
+    2) Call AddSearchDir() once or more times to add host directory path to
+       look for unstripped native libraries.
+    3) Call Find(device_libpath) repeatedly to translate a device-specific
+       library path into the corresponding host path to the unstripped
+       version.
+  """
+  def __init__(self):
+    """Initialize instance."""
+    self._search_dirs = []
+    self._lib_map = {}        # Map of library name to host file paths.
+
+  def AddSearchDir(self, lib_dir):
+    """Add a directory to the search path for host native shared libraries.
+
+    Args:
+      lib_dir: host path containing native libraries.
+    """
+    if not os.path.exists(lib_dir):
+      logging.warning('Ignoring missing host library directory: %s', lib_dir)
+      return
+    if not os.path.isdir(lib_dir):
+      logging.warning('Ignoring invalid host library directory: %s', lib_dir)
+      return
+    self._search_dirs.append(lib_dir)
+    self._lib_map = {}  # Reset the map.
+
+  def Find(self, device_libpath):
+    """Find the host file path matching a specific device library path.
+
+    Args:
+      device_libpath: device-specific file path to library or executable.
+    Returns:
+      host file path to the unstripped version of the library, or None.
+    """
+    host_lib_path = None
+    lib_name = os.path.basename(device_libpath)
+    host_lib_path = self._lib_map.get(lib_name)
+    if not host_lib_path:
+      for search_dir in self._search_dirs:
+        lib_path = os.path.join(search_dir, lib_name)
+        if os.path.exists(lib_path):
+          host_lib_path = lib_path
+          break
+
+      if not host_lib_path:
+        logging.debug('Could not find host library for: %s', lib_name)
+      self._lib_map[lib_name] = host_lib_path
+
+    return host_lib_path
+
+
+
+class SymbolResolver(object):
+  """A base class for objets that can symbolize library (path, offset)
+     pairs into symbol information strings. Usage is the following:
+
+     1) Create new instance (by calling the constructor of a derived
+        class, since this is only the base one).
+
+     2) Call SetAndroidAbi() before any call to FindSymbolInfo() in order
+        to set the Android CPU ABI used for symbolization.
+
+     3) Before the first call to FindSymbolInfo(), one can call
+        AddLibraryOffset(), or AddLibraryOffsets() to record a set of offsets
+        that you will want to symbolize later through FindSymbolInfo(). Doing
+        so allows some SymbolResolver derived classes to work faster (e.g. the
+        one that invokes the 'addr2line' program, since the latter works faster
+        if the offsets provided as inputs are sorted in increasing order).
+
+     3) Call FindSymbolInfo(path, offset) to return the corresponding
+        symbol information string, or None if this doesn't correspond
+        to anything the instance can handle.
+
+        Note that whether the path is specific to the device or to the
+        host depends on the derived class implementation.
+  """
+  def __init__(self):
+    self._android_abi = None
+    self._lib_offsets_map = collections.defaultdict(set)
+
+  def SetAndroidAbi(self, android_abi):
+    """Set the Android ABI value for this instance.
+
+    Calling this function before FindSymbolInfo() is required by some
+    derived class implementations.
+
+    Args:
+      android_abi: Native Android CPU ABI name (e.g. 'armeabi-v7a').
+    Raises:
+      Exception if the ABI was already set with a different value.
+    """
+    if self._android_abi and self._android_abi != android_abi:
+      raise Exception('Cannot reset Android ABI to new value %s, already set '
+                      'to %s' % (android_abi, self._android_abi))
+
+    self._android_abi = android_abi
+
+  def AddLibraryOffset(self, lib_path, offset):
+    """Associate a single offset to a given device library.
+
+    This must be called before FindSymbolInfo(), otherwise its input arguments
+    will be ignored.
+
+    Args:
+      lib_path: A library path.
+      offset: An integer offset within the corresponding library that will be
+        symbolized by future calls to FindSymbolInfo.
+    """
+    self._lib_offsets_map[lib_path].add(offset)
+
+  def AddLibraryOffsets(self, lib_path, lib_offsets):
+    """Associate a set of wanted offsets to a given device library.
+
+    This must be called before FindSymbolInfo(), otherwise its input arguments
+    will be ignored.
+
+    Args:
+      lib_path: A library path.
+      lib_offsets: An iterable of integer offsets within the corresponding
+        library that will be symbolized by future calls to FindSymbolInfo.
+    """
+    self._lib_offsets_map[lib_path].update(lib_offsets)
+
+  # pylint: disable=unused-argument,no-self-use
+  def FindSymbolInfo(self, lib_path, lib_offset):
+    """Symbolize a device library path and offset.
+
+    Args:
+      lib_path: Library path (device or host specific, depending on the
+        derived class implementation).
+      lib_offset: Integer offset within the library.
+    Returns:
+      Corresponding symbol information string, or None.
+    """
+    # The base implementation cannot symbolize anything.
+    return None
+  # pylint: enable=unused-argument,no-self-use
+
+
+class ElfSymbolResolver(SymbolResolver):
+  """A SymbolResolver that can symbolize host path + offset values using
+     an elf_symbolizer.ELFSymbolizer instance.
+  """
+  def __init__(self, addr2line_path_for_tests=None):
+    super(ElfSymbolResolver, self).__init__()
+    self._addr2line_path = addr2line_path_for_tests
+
+    # Used to cache one ELFSymbolizer instance per library path.
+    self._elf_symbolizer_cache = {}
+
+    # Used to cache FindSymbolInfo() results. Maps host library paths
+    # to (offset -> symbol info string) dictionaries.
+    self._symbol_info_cache = collections.defaultdict(dict)
+    self._allow_symbolizer = True
+
+  def _CreateSymbolizerFor(self, host_path):
+    """Create the ELFSymbolizer instance associated with a given lib path."""
+    addr2line_path = self._addr2line_path
+    if not addr2line_path:
+      if not self._android_abi:
+        raise Exception(
+            'Android CPU ABI must be set before calling FindSymbolInfo!')
+
+      cpu_arch = _AndroidAbiToCpuArch(self._android_abi)
+      self._addr2line_path = host_paths.ToolPath('addr2line', cpu_arch)
+
+    return elf_symbolizer.ELFSymbolizer(
+        elf_file_path=host_path, addr2line_path=self._addr2line_path,
+        callback=ElfSymbolResolver._Callback, inlines=True)
+
+  def DisallowSymbolizerForTesting(self):
+    """Disallow FindSymbolInfo() from using a symbolizer.
+
+    This is used during unit-testing to ensure that the offsets that were
+    recorded via AddLibraryOffset()/AddLibraryOffsets() are properly
+    symbolized, but not anything else.
+    """
+    self._allow_symbolizer = False
+
+  def FindSymbolInfo(self, host_path, offset):
+    """Override SymbolResolver.FindSymbolInfo.
+
+    Args:
+      host_path: Host-specific path to the native shared library.
+      offset: Integer offset within the native library.
+    Returns:
+      A symbol info string, or None.
+    """
+    offset_map = self._symbol_info_cache[host_path]
+    symbol_info = offset_map.get(offset)
+    if symbol_info:
+      return symbol_info
+
+    # Create symbolizer on demand.
+    symbolizer = self._elf_symbolizer_cache.get(host_path)
+    if not symbolizer:
+      symbolizer = self._CreateSymbolizerFor(host_path)
+      self._elf_symbolizer_cache[host_path] = symbolizer
+
+      # If there are pre-recorded offsets for this path, symbolize them now.
+      offsets = self._lib_offsets_map.get(host_path)
+      if offsets:
+        offset_map = {}
+        for pre_offset in offsets:
+          symbolizer.SymbolizeAsync(
+              pre_offset, callback_arg=(offset_map, pre_offset))
+        symbolizer.WaitForIdle()
+        self._symbol_info_cache[host_path] = offset_map
+
+        symbol_info = offset_map.get(offset)
+        if symbol_info:
+          return symbol_info
+
+    if not self._allow_symbolizer:
+      return None
+
+    # Symbolize single offset. Slower if addresses are not provided in
+    # increasing order to addr2line.
+    symbolizer.SymbolizeAsync(offset,
+                              callback_arg=(offset_map, offset))
+    symbolizer.WaitForIdle()
+    return offset_map.get(offset)
+
+  @staticmethod
+  def _Callback(sym_info, callback_arg):
+    offset_map, offset = callback_arg
+    offset_map[offset] = str(sym_info)
+
+
+class DeviceSymbolResolver(SymbolResolver):
+  """A SymbolResolver instance that accepts device-specific path.
+
+  Usage is the following:
+    1) Create new instance, passing a parent SymbolResolver instance that
+       accepts host-specific paths, and a HostLibraryFinder instance.
+
+    2) Optional: call AddApkOffsets() to add offsets from within an APK
+       that contains uncompressed native shared libraries.
+
+    3) Use it as any SymbolResolver instance.
+  """
+  def __init__(self, host_resolver, host_lib_finder):
+    """Initialize instance.
+
+    Args:
+      host_resolver: A parent SymbolResolver instance that will be used
+        to resolve symbols from host library paths.
+      host_lib_finder: A HostLibraryFinder instance used to locate
+        unstripped libraries on the host.
+    """
+    super(DeviceSymbolResolver, self).__init__()
+    self._host_lib_finder = host_lib_finder
+    self._bad_device_lib_paths = set()
+    self._host_resolver = host_resolver
+
+  def SetAndroidAbi(self, android_abi):
+    super(DeviceSymbolResolver, self).SetAndroidAbi(android_abi)
+    self._host_resolver.SetAndroidAbi(android_abi)
+
+  def AddLibraryOffsets(self, device_lib_path, lib_offsets):
+    """Associate a set of wanted offsets to a given device library.
+
+    This must be called before FindSymbolInfo(), otherwise its input arguments
+    will be ignored.
+
+    Args:
+      device_lib_path: A device-specific library path.
+      lib_offsets: An iterable of integer offsets within the corresponding
+        library that will be symbolized by future calls to FindSymbolInfo.
+        want to symbolize.
+    """
+    if device_lib_path in self._bad_device_lib_paths:
+      return
+
+    host_lib_path = self._host_lib_finder.Find(device_lib_path)
+    if not host_lib_path:
+      # NOTE: self._bad_device_lib_paths is only used to only print this
+      #       warning once per bad library.
+      logging.warning('Could not find host library matching device path: %s',
+                      device_lib_path)
+      self._bad_device_lib_paths.add(device_lib_path)
+      return
+
+    self._host_resolver.AddLibraryOffsets(host_lib_path, lib_offsets)
+
+  def AddApkOffsets(self, device_apk_path, apk_offsets, apk_translator):
+    """Associate a set of wanted offsets to a given device APK path.
+
+    This converts the APK-relative offsets into offsets relative to the
+    uncompressed libraries it contains, then calls AddLibraryOffsets()
+    for each one of the libraries.
+
+    Must be called before FindSymbolInfo() as well, otherwise input arguments
+    will be ignored.
+
+    Args:
+      device_apk_path: Device-specific APK path.
+      apk_offsets: Iterable of offsets within the APK file.
+      apk_translator: An ApkLibraryPathTranslator instance used to extract
+        library paths from the APK.
+    """
+    libraries_map = collections.defaultdict(set)
+    for offset in apk_offsets:
+      lib_path, lib_offset = apk_translator.TranslatePath(device_apk_path,
+                                                          offset)
+      libraries_map[lib_path].add(lib_offset)
+
+    for lib_path, lib_offsets in libraries_map.items():
+      self.AddLibraryOffsets(lib_path, lib_offsets)
+
+  def FindSymbolInfo(self, device_path, offset):
+    """Overrides SymbolResolver.FindSymbolInfo.
+
+    Args:
+      device_path: Device-specific library path (e.g.
+        '/data/app/com.example.app-1/lib/x86/libfoo.so')
+      offset: Offset in device library path.
+    Returns:
+      Corresponding symbol information string, or None.
+    """
+    host_path = self._host_lib_finder.Find(device_path)
+    if not host_path:
+      return None
+
+    return self._host_resolver.FindSymbolInfo(host_path, offset)
+
+
+class MemoryMap(object):
+  """Models the memory map of a given process. Usage is:
+
+    1) Create new instance, passing the Android ABI.
+
+    2) Call TranslateLine() whenever you want to detect and translate any
+       memory map input line.
+
+    3) Otherwise, it is possible to parse the whole memory map input with
+       ParseLines(), then call FindSectionForAddress() repeatedly in order
+       to translate a memory address into the corresponding mapping and
+       file information tuple (e.g. to symbolize stack entries).
+  """
+
+  # A named tuple describing interesting memory map line items.
+  # Fields:
+  #   addr_start: Mapping start address in memory.
+  #   file_offset: Corresponding file offset.
+  #   file_size: Corresponding mapping size in bytes.
+  #   file_path: Input file path.
+  #   match: Corresponding regular expression match object.
+  LineTuple = collections.namedtuple('MemoryMapLineTuple',
+                                     'addr_start,file_offset,file_size,'
+                                     'file_path, match')
+
+  # A name tuple describing a memory map section.
+  # Fields:
+  #   address: Memory address.
+  #   size: Size in bytes in memory
+  #   offset: Starting file offset.
+  #   path: Input file path.
+  SectionTuple = collections.namedtuple('MemoryMapSection',
+                                        'address,size,offset,path')
+
+  def __init__(self, android_abi):
+    """Initializes instance.
+
+    Args:
+      android_abi: Android CPU ABI name (e.g. 'armeabi-v7a')
+    """
+    hex_addr = _HexAddressRegexpFor(android_abi)
+
+    # pylint: disable=line-too-long
+    # A regular expression used to match memory map entries which look like:
+    #    b278c000-b2790fff r--   4fda000      5000  /data/app/com.google.android.apps.chrome-2/base.apk
+    # pylint: enable=line-too-long
+    self._re_map_section = re.compile(
+        r'\s*(?P' + hex_addr + r')-(?P' + hex_addr + ')' +
+        r'\s+' +
+        r'(?P...)\s+' +
+        r'(?P[0-9a-f]+)\s+' +
+        r'(?P[0-9a-f]+)\s*' +
+        r'(?P[^ \t]+)?')
+
+    self._addr_map = []  # Sorted list of (address, size, path, offset) tuples.
+    self._sorted_addresses = []  # Sorted list of address fields in _addr_map.
+    self._in_section = False
+
+  def TranslateLine(self, line, apk_path_translator):
+    """Try to translate a memory map input line, if detected.
+
+    This only takes care of converting mapped APK file path and offsets
+    into a corresponding uncompressed native library file path + new offsets,
+    e.g. '.....   /data/.../base.apk' gets
+    translated into '....   /data/.../base.apk!lib/libfoo.so'
+
+    This function should always work, even if ParseLines() was not called
+    previously.
+
+    Args:
+      line: Input memory map / tombstone line.
+      apk_translator: An ApkLibraryPathTranslator instance, used to map
+        APK offsets into uncompressed native libraries + new offsets.
+    Returns:
+      Translated memory map line, if relevant, or unchanged input line
+      otherwise.
+    """
+    t = self._ParseLine(line.rstrip())
+    if not t:
+      return line
+
+    new_path, new_offset = apk_path_translator.TranslatePath(
+        t.file_path, t.file_offset)
+
+    if new_path == t.file_path:
+      return line
+
+    pos = t.match.start('file_path')
+    return '%s%s (offset 0x%x)%s' % (line[0:pos], new_path, new_offset,
+                                     line[t.match.end('file_path'):])
+
+  def ParseLines(self, input_lines, in_section=False):
+    """Parse a list of input lines and extract the APK memory map out of it.
+
+    Args:
+      input_lines: list, or iterable, of input lines.
+      in_section: Optional. If true, considers that the input lines are
+        already part of the memory map. Otherwise, wait until the start of
+        the section appears in the input before trying to record data.
+    Returns:
+      True iff APK-related memory map entries were found. False otherwise.
+    """
+    addr_list = []  # list of (address, size, file_path, file_offset) tuples.
+    self._in_section = in_section
+    for line in input_lines:
+      t = self._ParseLine(line.rstrip())
+      if not t:
+        continue
+
+      addr_list.append(t)
+
+    self._addr_map = sorted(addr_list, key=lambda x: x.addr_start)
+    self._sorted_addresses = [e.addr_start for e in self._addr_map]
+    return bool(self._addr_map)
+
+  def _ParseLine(self, line):
+    """Used internally to recognized memory map input lines.
+
+    Args:
+      line: Input logcat or tomstone line.
+    Returns:
+      A LineTuple instance on success, or None on failure.
+    """
+    if not self._in_section:
+      self._in_section = line.startswith('memory map:')
+      return None
+
+    m = self._re_map_section.match(line)
+    if not m:
+      self._in_section = False  # End of memory map section
+      return None
+
+    # Only accept .apk and .so files that are not from the system partitions.
+    file_path = m.group('file_path')
+    if not file_path:
+      return None
+
+    if file_path.startswith('/system') or file_path.startswith('/vendor'):
+      return None
+
+    if not (file_path.endswith('.apk') or file_path.endswith('.so')):
+      return None
+
+    addr_start = int(m.group('addr_start'), 16)
+    file_offset = int(m.group('file_offset'), 16)
+    file_size = int(m.group('file_size'), 16)
+
+    return self.LineTuple(addr_start, file_offset, file_size, file_path, m)
+
+  def Dump(self):
+    """Print memory map for debugging."""
+    print('MEMORY MAP [')
+    for t in self._addr_map:
+      print('[%08x-%08x %08x %08x %s]' %
+            (t.addr_start, t.addr_start + t.file_size, t.file_size,
+             t.file_offset, t.file_path))
+    print('] MEMORY MAP')
+
+  def FindSectionForAddress(self, addr):
+    """Find the map section corresponding to a specific memory address.
+
+    Call this method only after using ParseLines() was called to extract
+    relevant information from the memory map.
+
+    Args:
+      addr: Memory address
+    Returns:
+      A SectionTuple instance on success, or None on failure.
+    """
+    pos = bisect.bisect_right(self._sorted_addresses, addr)
+    if pos > 0:
+      # All values in [0,pos) are <= addr, just ensure that the last
+      # one contains the address as well.
+      entry = self._addr_map[pos - 1]
+      if entry.addr_start + entry.file_size > addr:
+        return self.SectionTuple(entry.addr_start, entry.file_size,
+                                 entry.file_offset, entry.file_path)
+    return None
+
+
+class BacktraceTranslator(object):
+  """Translates backtrace-related lines in a tombstone or crash report.
+
+  Usage is the following:
+    1) Create new instance with appropriate arguments.
+    2) If the tombstone / logcat input is available, one can call
+       FindLibraryOffsets() in order to detect which library offsets
+       will need to be symbolized during a future parse. Doing so helps
+       speed up the ELF symbolizer.
+    3) For each tombstone/logcat input line, call TranslateLine() to
+       try to detect and symbolize backtrace lines.
+  """
+
+  # A named tuple for relevant input backtrace lines.
+  # Fields:
+  #   rel_pc: Instruction pointer, relative to offset in library start.
+  #   location: Library or APK file path.
+  #   offset: Load base of executable code in library or apk file path.
+  #   match: The corresponding regular expression match object.
+  # Note:
+  #   The actual instruction pointer always matches the position at
+  #   |offset + rel_pc| in |location|.
+  LineTuple = collections.namedtuple('BacktraceLineTuple',
+                                      'rel_pc,location,offset,match')
+
+  def __init__(self, android_abi, apk_translator):
+    """Initialize instance.
+
+    Args:
+      android_abi: Android CPU ABI name (e.g. 'armeabi-v7a').
+      apk_translator: ApkLibraryPathTranslator instance used to convert
+        mapped APK file offsets into uncompressed library file paths with
+        new offsets.
+    """
+    hex_addr = _HexAddressRegexpFor(android_abi)
+
+    # A regular expression used to match backtrace lines.
+    self._re_backtrace = re.compile(
+        r'.*#(?P[0-9]{2})\s+' +
+        r'(..)\s+' +
+        r'(?P' + hex_addr + r')\s+' +
+        r'(?P[^ \t]+)' +
+        r'(\s+\(offset 0x(?P[0-9a-f]+)\))?')
+
+    # In certain cases, offset will be provided as +0x
+    # instead of  (offset 0x). This is a regexp to detect
+    # this.
+    self._re_location_offset = re.compile(
+        r'.*\+0x(?P[0-9a-f]+)$')
+
+    self._apk_translator = apk_translator
+    self._in_section = False
+
+  def _ParseLine(self, line):
+    """Used internally to detect and decompose backtrace input lines.
+
+    Args:
+      line: input tombstone line.
+    Returns:
+      A LineTuple instance on success, None on failure.
+    """
+    if not self._in_section:
+      self._in_section = line.startswith('backtrace:')
+      return None
+
+    line = line.rstrip()
+    m = self._re_backtrace.match(line)
+    if not m:
+      self._in_section = False
+      return None
+
+    location = m.group('location')
+    offset = m.group('offset')
+    if not offset:
+      m2 = self._re_location_offset.match(location)
+      if m2:
+        offset = m2.group('offset')
+        location = location[0:m2.start('offset') - 3]
+
+    if not offset:
+      return None
+
+    offset = int(offset, 16)
+    rel_pc = int(m.group('rel_pc'), 16)
+
+    # Two cases to consider here:
+    #
+    # * If this is a library file directly mapped in memory, then |rel_pc|
+    #   if the direct offset within the library, and doesn't need any kind
+    #   of adjustement.
+    #
+    # * If this is a library mapped directly from an .apk file, then
+    #   |rel_pc| is the offset in the APK, and |offset| happens to be the
+    #   load base of the corresponding library.
+    #
+    if location.endswith('.so'):
+      # For a native library directly mapped from the file system,
+      return self.LineTuple(rel_pc, location, offset, m)
+
+    if location.endswith('.apk'):
+      # For a native library inside an memory-mapped APK file,
+      new_location, new_offset = self._apk_translator.TranslatePath(
+          location, offset)
+
+      return self.LineTuple(rel_pc, new_location, new_offset, m)
+
+    # Ignore anything else (e.g. .oat or .odex files).
+    return None
+
+  def FindLibraryOffsets(self, input_lines, in_section=False):
+    """Parse a tombstone's backtrace section and find all library offsets in it.
+
+    Args:
+      input_lines: List or iterables of intput tombstone lines.
+      in_section: Optional. If True, considers that the stack section has
+        already started.
+    Returns:
+      A dictionary mapping device library paths to sets of offsets within
+      then.
+    """
+    self._in_section = in_section
+    result = collections.defaultdict(set)
+    for line in input_lines:
+      t = self._ParseLine(line)
+      if not t:
+        continue
+
+      result[t.location].add(t.offset + t.rel_pc)
+    return result
+
+  def TranslateLine(self, line, symbol_resolver):
+    """Symbolize backtrace line if recognized.
+
+    Args:
+      line: input backtrace line.
+      symbol_resolver: symbol resolver instance to use. This method will
+        call its FindSymbolInfo(device_lib_path, lib_offset) method to
+        convert offsets into symbol informations strings.
+    Returns:
+      Translated line (unchanged if not recognized as a back trace).
+    """
+    t = self._ParseLine(line)
+    if not t:
+      return line
+
+    symbol_info = symbol_resolver.FindSymbolInfo(t.location,
+                                                 t.offset + t.rel_pc)
+    if not symbol_info:
+      symbol_info = 'offset 0x%x' % t.offset
+
+    pos = t.match.start('location')
+    pos2 = t.match.end('offset') + 1
+    if pos2 <= 0:
+      pos2 = t.match.end('location')
+    return '%s%s (%s)%s' % (line[:pos], t.location, symbol_info, line[pos2:])
+
+
+class StackTranslator(object):
+  """Translates stack-related lines in a tombstone or crash report."""
+
+  # A named tuple describing relevant stack input lines.
+  # Fields:
+  #  address: Address as it appears in the stack.
+  #  lib_path: Library path where |address| is mapped.
+  #  lib_offset: Library load base offset. for |lib_path|.
+  #  match: Corresponding regular expression match object.
+  LineTuple = collections.namedtuple('StackLineTuple',
+                                     'address, lib_path, lib_offset, match')
+
+  def __init__(self, android_abi, memory_map, apk_translator):
+    """Initialize instance."""
+    hex_addr = _HexAddressRegexpFor(android_abi)
+
+    # pylint: disable=line-too-long
+    # A regular expression used to recognize stack entries like:
+    #
+    #    #05  bf89a180  bf89a1e4  [stack]
+    #         bf89a1c8  a0c01c51  /data/app/com.google.android.apps.chrome-2/base.apk
+    #         bf89a080  00000000
+    #         ........  ........
+    # pylint: enable=line-too-long
+    self._re_stack_line = re.compile(
+        r'\s+(?P#[0-9]+)?\s*' +
+        r'(?P' + hex_addr + r')\s+' +
+        r'(?P' + hex_addr + r')' +
+        r'(\s+(?P[^ \t]+))?')
+
+    self._re_stack_abbrev = re.compile(r'\s+[.]+\s+[.]+')
+
+    self._memory_map = memory_map
+    self._apk_translator = apk_translator
+    self._in_section = False
+
+  def _ParseLine(self, line):
+    """Check a given input line for a relevant _re_stack_line match.
+
+    Args:
+      line: input tombstone line.
+    Returns:
+      A LineTuple instance on success, None on failure.
+    """
+    line = line.rstrip()
+    if not self._in_section:
+      self._in_section = line.startswith('stack:')
+      return None
+
+    m = self._re_stack_line.match(line)
+    if not m:
+      if not self._re_stack_abbrev.match(line):
+        self._in_section = False
+      return None
+
+    location = m.group('location')
+    if not location:
+      return None
+
+    if not location.endswith('.apk') and not location.endswith('.so'):
+      return None
+
+    addr = int(m.group('stack_value'), 16)
+    t = self._memory_map.FindSectionForAddress(addr)
+    if t is None:
+      return None
+
+    lib_path = t.path
+    lib_offset = t.offset + (addr - t.address)
+
+    if lib_path.endswith('.apk'):
+      lib_path, lib_offset = self._apk_translator.TranslatePath(
+          lib_path, lib_offset)
+
+    return self.LineTuple(addr, lib_path, lib_offset, m)
+
+  def FindLibraryOffsets(self, input_lines, in_section=False):
+    """Parse a tombstone's stack section and find all library offsets in it.
+
+    Args:
+      input_lines: List or iterables of intput tombstone lines.
+      in_section: Optional. If True, considers that the stack section has
+        already started.
+    Returns:
+      A dictionary mapping device library paths to sets of offsets within
+      then.
+    """
+    result = collections.defaultdict(set)
+    self._in_section = in_section
+    for line in input_lines:
+      t = self._ParseLine(line)
+      if t:
+        result[t.lib_path].add(t.lib_offset)
+    return result
+
+  def TranslateLine(self, line, symbol_resolver=None):
+    """Try to translate a line of the stack dump."""
+    t = self._ParseLine(line)
+    if not t:
+      return line
+
+    symbol_info = symbol_resolver.FindSymbolInfo(t.lib_path, t.lib_offset)
+    if not symbol_info:
+      return line
+
+    pos = t.match.start('location')
+    pos2 = t.match.end('location')
+    return '%s%s (%s)%s' % (line[:pos], t.lib_path, symbol_info, line[pos2:])
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py
new file mode 100755
index 0000000000..2ec81f96fa
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py
@@ -0,0 +1,942 @@
+#!/usr/bin/env vpython3
+# Copyright 2018 The Chromium 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 contextlib
+import logging
+import os
+import re
+import shutil
+import tempfile
+import unittest
+
+from pylib.symbols import apk_native_libs_unittest
+from pylib.symbols import mock_addr2line
+from pylib.symbols import symbol_utils
+
+_MOCK_ELF_DATA = apk_native_libs_unittest.MOCK_ELF_DATA
+
+_MOCK_A2L_PATH = os.path.join(os.path.dirname(mock_addr2line.__file__),
+                              'mock_addr2line')
+
+
+# pylint: disable=line-too-long
+
+# list of (start_offset, end_offset, size, libpath) tuples corresponding
+# to the content of base.apk. This was taken from an x86 ChromeModern.apk
+# component build.
+_TEST_APK_LIBS = [
+  (0x01331000, 0x013696bc, 0x000386bc, 'libaccessibility.cr.so'),
+  (0x0136a000, 0x013779c4, 0x0000d9c4, 'libanimation.cr.so'),
+  (0x01378000, 0x0137f7e8, 0x000077e8, 'libapdu.cr.so'),
+  (0x01380000, 0x0155ccc8, 0x001dccc8, 'libbase.cr.so'),
+  (0x0155d000, 0x015ab98c, 0x0004e98c, 'libbase_i18n.cr.so'),
+  (0x015ac000, 0x015dff4c, 0x00033f4c, 'libbindings.cr.so'),
+  (0x015e0000, 0x015f5a54, 0x00015a54, 'libbindings_base.cr.so'),
+  (0x0160e000, 0x01731960, 0x00123960, 'libblink_common.cr.so'),
+  (0x01732000, 0x0174ce54, 0x0001ae54, 'libblink_controller.cr.so'),
+  (0x0174d000, 0x0318c528, 0x01a3f528, 'libblink_core.cr.so'),
+  (0x0318d000, 0x03191700, 0x00004700, 'libblink_mojom_broadcastchannel_bindings_shared.cr.so'),
+  (0x03192000, 0x03cd7918, 0x00b45918, 'libblink_modules.cr.so'),
+  (0x03cd8000, 0x03d137d0, 0x0003b7d0, 'libblink_mojo_bindings_shared.cr.so'),
+  (0x03d14000, 0x03d2670c, 0x0001270c, 'libblink_offscreen_canvas_mojo_bindings_shared.cr.so'),
+  (0x03d27000, 0x046c7054, 0x009a0054, 'libblink_platform.cr.so'),
+  (0x046c8000, 0x0473fbfc, 0x00077bfc, 'libbluetooth.cr.so'),
+  (0x04740000, 0x04878f40, 0x00138f40, 'libboringssl.cr.so'),
+  (0x04879000, 0x0498466c, 0x0010b66c, 'libc++_shared.so'),
+  (0x04985000, 0x0498d93c, 0x0000893c, 'libcaptive_portal.cr.so'),
+  (0x0498e000, 0x049947cc, 0x000067cc, 'libcapture_base.cr.so'),
+  (0x04995000, 0x04b39f18, 0x001a4f18, 'libcapture_lib.cr.so'),
+  (0x04b3a000, 0x04b488ec, 0x0000e8ec, 'libcbor.cr.so'),
+  (0x04b49000, 0x04e9ea5c, 0x00355a5c, 'libcc.cr.so'),
+  (0x04e9f000, 0x04ed6404, 0x00037404, 'libcc_animation.cr.so'),
+  (0x04ed7000, 0x04ef5ab4, 0x0001eab4, 'libcc_base.cr.so'),
+  (0x04ef6000, 0x04fd9364, 0x000e3364, 'libcc_blink.cr.so'),
+  (0x04fda000, 0x04fe2758, 0x00008758, 'libcc_debug.cr.so'),
+  (0x04fe3000, 0x0500ae0c, 0x00027e0c, 'libcc_ipc.cr.so'),
+  (0x0500b000, 0x05078f38, 0x0006df38, 'libcc_paint.cr.so'),
+  (0x05079000, 0x0507e734, 0x00005734, 'libcdm_manager.cr.so'),
+  (0x0507f000, 0x06f4d744, 0x01ece744, 'libchrome.cr.so'),
+  (0x06f54000, 0x06feb830, 0x00097830, 'libchromium_sqlite3.cr.so'),
+  (0x06fec000, 0x0706f554, 0x00083554, 'libclient.cr.so'),
+  (0x07070000, 0x0708da60, 0x0001da60, 'libcloud_policy_proto_generated_compile.cr.so'),
+  (0x0708e000, 0x07121f28, 0x00093f28, 'libcodec.cr.so'),
+  (0x07122000, 0x07134ab8, 0x00012ab8, 'libcolor_space.cr.so'),
+  (0x07135000, 0x07138614, 0x00003614, 'libcommon.cr.so'),
+  (0x07139000, 0x0717c938, 0x00043938, 'libcompositor.cr.so'),
+  (0x0717d000, 0x0923d78c, 0x020c078c, 'libcontent.cr.so'),
+  (0x0923e000, 0x092ae87c, 0x0007087c, 'libcontent_common_mojo_bindings_shared.cr.so'),
+  (0x092af000, 0x092be718, 0x0000f718, 'libcontent_public_common_mojo_bindings_shared.cr.so'),
+  (0x092bf000, 0x092d9a20, 0x0001aa20, 'libcrash_key.cr.so'),
+  (0x092da000, 0x092eda58, 0x00013a58, 'libcrcrypto.cr.so'),
+  (0x092ee000, 0x092f16e0, 0x000036e0, 'libdevice_base.cr.so'),
+  (0x092f2000, 0x092fe8d8, 0x0000c8d8, 'libdevice_event_log.cr.so'),
+  (0x092ff000, 0x093026a4, 0x000036a4, 'libdevice_features.cr.so'),
+  (0x09303000, 0x093f1220, 0x000ee220, 'libdevice_gamepad.cr.so'),
+  (0x093f2000, 0x09437f54, 0x00045f54, 'libdevice_vr_mojo_bindings.cr.so'),
+  (0x09438000, 0x0954c168, 0x00114168, 'libdevice_vr_mojo_bindings_blink.cr.so'),
+  (0x0954d000, 0x0955d720, 0x00010720, 'libdevice_vr_mojo_bindings_shared.cr.so'),
+  (0x0955e000, 0x0956b9c0, 0x0000d9c0, 'libdevices.cr.so'),
+  (0x0956c000, 0x0957cae8, 0x00010ae8, 'libdiscardable_memory_client.cr.so'),
+  (0x0957d000, 0x09588854, 0x0000b854, 'libdiscardable_memory_common.cr.so'),
+  (0x09589000, 0x0959cbb4, 0x00013bb4, 'libdiscardable_memory_service.cr.so'),
+  (0x0959d000, 0x095b6b90, 0x00019b90, 'libdisplay.cr.so'),
+  (0x095b7000, 0x095be930, 0x00007930, 'libdisplay_types.cr.so'),
+  (0x095bf000, 0x095c46c4, 0x000056c4, 'libdisplay_util.cr.so'),
+  (0x095c5000, 0x095f54a4, 0x000304a4, 'libdomain_reliability.cr.so'),
+  (0x095f6000, 0x0966fe08, 0x00079e08, 'libembedder.cr.so'),
+  (0x09670000, 0x096735f8, 0x000035f8, 'libembedder_switches.cr.so'),
+  (0x09674000, 0x096a3460, 0x0002f460, 'libevents.cr.so'),
+  (0x096a4000, 0x096b6d40, 0x00012d40, 'libevents_base.cr.so'),
+  (0x096b7000, 0x0981a778, 0x00163778, 'libffmpeg.cr.so'),
+  (0x0981b000, 0x09945c94, 0x0012ac94, 'libfido.cr.so'),
+  (0x09946000, 0x09a330dc, 0x000ed0dc, 'libfingerprint.cr.so'),
+  (0x09a34000, 0x09b53170, 0x0011f170, 'libfreetype_harfbuzz.cr.so'),
+  (0x09b54000, 0x09bc5c5c, 0x00071c5c, 'libgcm.cr.so'),
+  (0x09bc6000, 0x09cc8584, 0x00102584, 'libgeolocation.cr.so'),
+  (0x09cc9000, 0x09cdc8d4, 0x000138d4, 'libgeometry.cr.so'),
+  (0x09cdd000, 0x09cec8b4, 0x0000f8b4, 'libgeometry_skia.cr.so'),
+  (0x09ced000, 0x09d10e14, 0x00023e14, 'libgesture_detection.cr.so'),
+  (0x09d11000, 0x09d7595c, 0x0006495c, 'libgfx.cr.so'),
+  (0x09d76000, 0x09d7d7cc, 0x000077cc, 'libgfx_ipc.cr.so'),
+  (0x09d7e000, 0x09d82708, 0x00004708, 'libgfx_ipc_buffer_types.cr.so'),
+  (0x09d83000, 0x09d89748, 0x00006748, 'libgfx_ipc_color.cr.so'),
+  (0x09d8a000, 0x09d8f6f4, 0x000056f4, 'libgfx_ipc_geometry.cr.so'),
+  (0x09d90000, 0x09d94754, 0x00004754, 'libgfx_ipc_skia.cr.so'),
+  (0x09d95000, 0x09d9869c, 0x0000369c, 'libgfx_switches.cr.so'),
+  (0x09d99000, 0x09dba0ac, 0x000210ac, 'libgin.cr.so'),
+  (0x09dbb000, 0x09e0a8cc, 0x0004f8cc, 'libgl_in_process_context.cr.so'),
+  (0x09e0b000, 0x09e17a18, 0x0000ca18, 'libgl_init.cr.so'),
+  (0x09e18000, 0x09ee34e4, 0x000cb4e4, 'libgl_wrapper.cr.so'),
+  (0x09ee4000, 0x0a1a2e00, 0x002bee00, 'libgles2.cr.so'),
+  (0x0a1a3000, 0x0a24556c, 0x000a256c, 'libgles2_implementation.cr.so'),
+  (0x0a246000, 0x0a267038, 0x00021038, 'libgles2_utils.cr.so'),
+  (0x0a268000, 0x0a3288e4, 0x000c08e4, 'libgpu.cr.so'),
+  (0x0a329000, 0x0a3627ec, 0x000397ec, 'libgpu_ipc_service.cr.so'),
+  (0x0a363000, 0x0a388a18, 0x00025a18, 'libgpu_util.cr.so'),
+  (0x0a389000, 0x0a506d8c, 0x0017dd8c, 'libhost.cr.so'),
+  (0x0a507000, 0x0a6f0ec0, 0x001e9ec0, 'libicui18n.cr.so'),
+  (0x0a6f1000, 0x0a83b4c8, 0x0014a4c8, 'libicuuc.cr.so'),
+  (0x0a83c000, 0x0a8416e4, 0x000056e4, 'libinterfaces_shared.cr.so'),
+  (0x0a842000, 0x0a87e2a0, 0x0003c2a0, 'libipc.cr.so'),
+  (0x0a87f000, 0x0a88c98c, 0x0000d98c, 'libipc_mojom.cr.so'),
+  (0x0a88d000, 0x0a8926e4, 0x000056e4, 'libipc_mojom_shared.cr.so'),
+  (0x0a893000, 0x0a8a1e18, 0x0000ee18, 'libkeyed_service_content.cr.so'),
+  (0x0a8a2000, 0x0a8b4a30, 0x00012a30, 'libkeyed_service_core.cr.so'),
+  (0x0a8b5000, 0x0a930a80, 0x0007ba80, 'libleveldatabase.cr.so'),
+  (0x0a931000, 0x0a9b3908, 0x00082908, 'libmanager.cr.so'),
+  (0x0a9b4000, 0x0aea9bb4, 0x004f5bb4, 'libmedia.cr.so'),
+  (0x0aeaa000, 0x0b08cb88, 0x001e2b88, 'libmedia_blink.cr.so'),
+  (0x0b08d000, 0x0b0a4728, 0x00017728, 'libmedia_devices_mojo_bindings_shared.cr.so'),
+  (0x0b0a5000, 0x0b1943ec, 0x000ef3ec, 'libmedia_gpu.cr.so'),
+  (0x0b195000, 0x0b2d07d4, 0x0013b7d4, 'libmedia_mojo_services.cr.so'),
+  (0x0b2d1000, 0x0b2d4760, 0x00003760, 'libmessage_center.cr.so'),
+  (0x0b2d5000, 0x0b2e0938, 0x0000b938, 'libmessage_support.cr.so'),
+  (0x0b2e1000, 0x0b2f3ad0, 0x00012ad0, 'libmetrics_cpp.cr.so'),
+  (0x0b2f4000, 0x0b313bb8, 0x0001fbb8, 'libmidi.cr.so'),
+  (0x0b314000, 0x0b31b848, 0x00007848, 'libmojo_base_lib.cr.so'),
+  (0x0b31c000, 0x0b3329f8, 0x000169f8, 'libmojo_base_mojom.cr.so'),
+  (0x0b333000, 0x0b34b98c, 0x0001898c, 'libmojo_base_mojom_blink.cr.so'),
+  (0x0b34c000, 0x0b354700, 0x00008700, 'libmojo_base_mojom_shared.cr.so'),
+  (0x0b355000, 0x0b3608b0, 0x0000b8b0, 'libmojo_base_shared_typemap_traits.cr.so'),
+  (0x0b361000, 0x0b3ad454, 0x0004c454, 'libmojo_edk.cr.so'),
+  (0x0b3ae000, 0x0b3c4a20, 0x00016a20, 'libmojo_edk_ports.cr.so'),
+  (0x0b3c5000, 0x0b3d38a0, 0x0000e8a0, 'libmojo_mojom_bindings.cr.so'),
+  (0x0b3d4000, 0x0b3da6e8, 0x000066e8, 'libmojo_mojom_bindings_shared.cr.so'),
+  (0x0b3db000, 0x0b3e27f0, 0x000077f0, 'libmojo_public_system.cr.so'),
+  (0x0b3e3000, 0x0b3fa9fc, 0x000179fc, 'libmojo_public_system_cpp.cr.so'),
+  (0x0b3fb000, 0x0b407728, 0x0000c728, 'libmojom_core_shared.cr.so'),
+  (0x0b408000, 0x0b421744, 0x00019744, 'libmojom_platform_shared.cr.so'),
+  (0x0b422000, 0x0b43451c, 0x0001251c, 'libnative_theme.cr.so'),
+  (0x0b435000, 0x0baaa1bc, 0x006751bc, 'libnet.cr.so'),
+  (0x0bac4000, 0x0bb74670, 0x000b0670, 'libnetwork_cpp.cr.so'),
+  (0x0bb75000, 0x0bbaee8c, 0x00039e8c, 'libnetwork_cpp_base.cr.so'),
+  (0x0bbaf000, 0x0bd21844, 0x00172844, 'libnetwork_service.cr.so'),
+  (0x0bd22000, 0x0bd256e4, 0x000036e4, 'libnetwork_session_configurator.cr.so'),
+  (0x0bd26000, 0x0bd33734, 0x0000d734, 'libonc.cr.so'),
+  (0x0bd34000, 0x0bd9ce18, 0x00068e18, 'libperfetto.cr.so'),
+  (0x0bd9d000, 0x0bda4854, 0x00007854, 'libplatform.cr.so'),
+  (0x0bda5000, 0x0bec5ce4, 0x00120ce4, 'libpolicy_component.cr.so'),
+  (0x0bec6000, 0x0bf5ab58, 0x00094b58, 'libpolicy_proto.cr.so'),
+  (0x0bf5b000, 0x0bf86fbc, 0x0002bfbc, 'libprefs.cr.so'),
+  (0x0bf87000, 0x0bfa5d74, 0x0001ed74, 'libprinting.cr.so'),
+  (0x0bfa6000, 0x0bfe0e80, 0x0003ae80, 'libprotobuf_lite.cr.so'),
+  (0x0bfe1000, 0x0bff0a18, 0x0000fa18, 'libproxy_config.cr.so'),
+  (0x0bff1000, 0x0c0f6654, 0x00105654, 'libpublic.cr.so'),
+  (0x0c0f7000, 0x0c0fa6a4, 0x000036a4, 'librange.cr.so'),
+  (0x0c0fb000, 0x0c118058, 0x0001d058, 'libraster.cr.so'),
+  (0x0c119000, 0x0c133d00, 0x0001ad00, 'libresource_coordinator_cpp.cr.so'),
+  (0x0c134000, 0x0c1396a0, 0x000056a0, 'libresource_coordinator_cpp_base.cr.so'),
+  (0x0c13a000, 0x0c1973b8, 0x0005d3b8, 'libresource_coordinator_public_mojom.cr.so'),
+  (0x0c198000, 0x0c2033e8, 0x0006b3e8, 'libresource_coordinator_public_mojom_blink.cr.so'),
+  (0x0c204000, 0x0c219744, 0x00015744, 'libresource_coordinator_public_mojom_shared.cr.so'),
+  (0x0c21a000, 0x0c21e700, 0x00004700, 'libsandbox.cr.so'),
+  (0x0c21f000, 0x0c22f96c, 0x0001096c, 'libsandbox_services.cr.so'),
+  (0x0c230000, 0x0c249d58, 0x00019d58, 'libseccomp_bpf.cr.so'),
+  (0x0c24a000, 0x0c24e714, 0x00004714, 'libseccomp_starter_android.cr.so'),
+  (0x0c24f000, 0x0c4ae9f0, 0x0025f9f0, 'libservice.cr.so'),
+  (0x0c4af000, 0x0c4c3ae4, 0x00014ae4, 'libservice_manager_cpp.cr.so'),
+  (0x0c4c4000, 0x0c4cb708, 0x00007708, 'libservice_manager_cpp_types.cr.so'),
+  (0x0c4cc000, 0x0c4fbe30, 0x0002fe30, 'libservice_manager_mojom.cr.so'),
+  (0x0c4fc000, 0x0c532e78, 0x00036e78, 'libservice_manager_mojom_blink.cr.so'),
+  (0x0c533000, 0x0c53669c, 0x0000369c, 'libservice_manager_mojom_constants.cr.so'),
+  (0x0c537000, 0x0c53e85c, 0x0000785c, 'libservice_manager_mojom_constants_blink.cr.so'),
+  (0x0c53f000, 0x0c542668, 0x00003668, 'libservice_manager_mojom_constants_shared.cr.so'),
+  (0x0c543000, 0x0c54d700, 0x0000a700, 'libservice_manager_mojom_shared.cr.so'),
+  (0x0c54e000, 0x0c8fc6ec, 0x003ae6ec, 'libsessions.cr.so'),
+  (0x0c8fd000, 0x0c90a924, 0x0000d924, 'libshared_memory_support.cr.so'),
+  (0x0c90b000, 0x0c9148ec, 0x000098ec, 'libshell_dialogs.cr.so'),
+  (0x0c915000, 0x0cf8de70, 0x00678e70, 'libskia.cr.so'),
+  (0x0cf8e000, 0x0cf978bc, 0x000098bc, 'libsnapshot.cr.so'),
+  (0x0cf98000, 0x0cfb7d9c, 0x0001fd9c, 'libsql.cr.so'),
+  (0x0cfb8000, 0x0cfbe744, 0x00006744, 'libstartup_tracing.cr.so'),
+  (0x0cfbf000, 0x0d19b4e4, 0x001dc4e4, 'libstorage_browser.cr.so'),
+  (0x0d19c000, 0x0d2a773c, 0x0010b73c, 'libstorage_common.cr.so'),
+  (0x0d2a8000, 0x0d2ac6fc, 0x000046fc, 'libsurface.cr.so'),
+  (0x0d2ad000, 0x0d2baa98, 0x0000da98, 'libtracing.cr.so'),
+  (0x0d2bb000, 0x0d2f36b0, 0x000386b0, 'libtracing_cpp.cr.so'),
+  (0x0d2f4000, 0x0d326e70, 0x00032e70, 'libtracing_mojom.cr.so'),
+  (0x0d327000, 0x0d33270c, 0x0000b70c, 'libtracing_mojom_shared.cr.so'),
+  (0x0d333000, 0x0d46d804, 0x0013a804, 'libui_android.cr.so'),
+  (0x0d46e000, 0x0d4cb3f8, 0x0005d3f8, 'libui_base.cr.so'),
+  (0x0d4cc000, 0x0d4dbc40, 0x0000fc40, 'libui_base_ime.cr.so'),
+  (0x0d4dc000, 0x0d4e58d4, 0x000098d4, 'libui_data_pack.cr.so'),
+  (0x0d4e6000, 0x0d51d1e0, 0x000371e0, 'libui_devtools.cr.so'),
+  (0x0d51e000, 0x0d52b984, 0x0000d984, 'libui_message_center_cpp.cr.so'),
+  (0x0d52c000, 0x0d539a48, 0x0000da48, 'libui_touch_selection.cr.so'),
+  (0x0d53a000, 0x0d55bc60, 0x00021c60, 'liburl.cr.so'),
+  (0x0d55c000, 0x0d55f6b4, 0x000036b4, 'liburl_ipc.cr.so'),
+  (0x0d560000, 0x0d5af110, 0x0004f110, 'liburl_matcher.cr.so'),
+  (0x0d5b0000, 0x0d5e2fac, 0x00032fac, 'libuser_manager.cr.so'),
+  (0x0d5e3000, 0x0d5e66e4, 0x000036e4, 'libuser_prefs.cr.so'),
+  (0x0d5e7000, 0x0e3e1cc8, 0x00dfacc8, 'libv8.cr.so'),
+  (0x0e3e2000, 0x0e400ae0, 0x0001eae0, 'libv8_libbase.cr.so'),
+  (0x0e401000, 0x0e4d91d4, 0x000d81d4, 'libviz_common.cr.so'),
+  (0x0e4da000, 0x0e4df7e4, 0x000057e4, 'libviz_resource_format.cr.so'),
+  (0x0e4e0000, 0x0e5b7120, 0x000d7120, 'libweb_dialogs.cr.so'),
+  (0x0e5b8000, 0x0e5c7a18, 0x0000fa18, 'libwebdata_common.cr.so'),
+  (0x0e5c8000, 0x0e61bfe4, 0x00053fe4, 'libwtf.cr.so'),
+]
+
+
+# A small memory map fragment extracted from a tombstone for a process that
+# had loaded the APK corresponding to _TEST_APK_LIBS above.
+_TEST_MEMORY_MAP = r'''memory map:
+12c00000-12ccafff rw-         0     cb000  /dev/ashmem/dalvik-main space (deleted)
+12ccb000-130cafff rw-     cb000    400000  /dev/ashmem/dalvik-main space (deleted)
+130cb000-32bfffff ---    4cb000  1fb35000  /dev/ashmem/dalvik-main space (deleted)
+32c00000-32c00fff rw-         0      1000  /dev/ashmem/dalvik-main space 1 (deleted)
+32c01000-52bfffff ---      1000  1ffff000  /dev/ashmem/dalvik-main space 1 (deleted)
+6f3b8000-6fd90fff rw-         0    9d9000  /data/dalvik-cache/x86/system@framework@boot.art
+6fd91000-71c42fff r--         0   1eb2000  /data/dalvik-cache/x86/system@framework@boot.oat
+71c43000-7393efff r-x   1eb2000   1cfc000  /data/dalvik-cache/x86/system@framework@boot.oat (load base 0x71c43000)
+7393f000-7393ffff rw-   3bae000      1000  /data/dalvik-cache/x86/system@framework@boot.oat
+73940000-73a1bfff rw-         0     dc000  /dev/ashmem/dalvik-zygote space (deleted)
+73a1c000-73a1cfff rw-         0      1000  /dev/ashmem/dalvik-non moving space (deleted)
+73a1d000-73a2dfff rw-      1000     11000  /dev/ashmem/dalvik-non moving space (deleted)
+73a2e000-77540fff ---     12000   3b13000  /dev/ashmem/dalvik-non moving space (deleted)
+77541000-7793ffff rw-   3b25000    3ff000  /dev/ashmem/dalvik-non moving space (deleted)
+923aa000-92538fff r--    8a9000    18f000  /data/app/com.example.app-2/base.apk
+92539000-9255bfff r--         0     23000  /data/data/com.example.app/app_data/paks/es.pak@162db1c6689
+9255c000-92593fff r--    213000     38000  /data/app/com.example.app-2/base.apk
+92594000-925c0fff r--    87d000     2d000  /data/app/com.example.app-2/base.apk
+925c1000-927d3fff r--    a37000    213000  /data/app/com.example.app-2/base.apk
+927d4000-92e07fff r--    24a000    634000  /data/app/com.example.app-2/base.apk
+92e08000-92e37fff r--   a931000     30000  /data/app/com.example.app-2/base.apk
+92e38000-92e86fff r-x   a961000     4f000  /data/app/com.example.app-2/base.apk
+92e87000-92e8afff rw-   a9b0000      4000  /data/app/com.example.app-2/base.apk
+92e8b000-92e8bfff rw-         0      1000
+92e8c000-92e9dfff r--   d5b0000     12000  /data/app/com.example.app-2/base.apk
+92e9e000-92ebcfff r-x   d5c2000     1f000  /data/app/com.example.app-2/base.apk
+92ebd000-92ebefff rw-   d5e1000      2000  /data/app/com.example.app-2/base.apk
+92ebf000-92ebffff rw-         0      1000
+'''
+
+# list of (address, size, path, offset)  tuples that must appear in
+# _TEST_MEMORY_MAP. Not all sections need to be listed.
+_TEST_MEMORY_MAP_SECTIONS = [
+  (0x923aa000, 0x18f000, '/data/app/com.example.app-2/base.apk', 0x8a9000),
+  (0x9255c000, 0x038000, '/data/app/com.example.app-2/base.apk', 0x213000),
+  (0x92594000, 0x02d000, '/data/app/com.example.app-2/base.apk', 0x87d000),
+  (0x925c1000, 0x213000, '/data/app/com.example.app-2/base.apk', 0xa37000),
+]
+
+_EXPECTED_TEST_MEMORY_MAP = r'''memory map:
+12c00000-12ccafff rw-         0     cb000  /dev/ashmem/dalvik-main space (deleted)
+12ccb000-130cafff rw-     cb000    400000  /dev/ashmem/dalvik-main space (deleted)
+130cb000-32bfffff ---    4cb000  1fb35000  /dev/ashmem/dalvik-main space (deleted)
+32c00000-32c00fff rw-         0      1000  /dev/ashmem/dalvik-main space 1 (deleted)
+32c01000-52bfffff ---      1000  1ffff000  /dev/ashmem/dalvik-main space 1 (deleted)
+6f3b8000-6fd90fff rw-         0    9d9000  /data/dalvik-cache/x86/system@framework@boot.art
+6fd91000-71c42fff r--         0   1eb2000  /data/dalvik-cache/x86/system@framework@boot.oat
+71c43000-7393efff r-x   1eb2000   1cfc000  /data/dalvik-cache/x86/system@framework@boot.oat (load base 0x71c43000)
+7393f000-7393ffff rw-   3bae000      1000  /data/dalvik-cache/x86/system@framework@boot.oat
+73940000-73a1bfff rw-         0     dc000  /dev/ashmem/dalvik-zygote space (deleted)
+73a1c000-73a1cfff rw-         0      1000  /dev/ashmem/dalvik-non moving space (deleted)
+73a1d000-73a2dfff rw-      1000     11000  /dev/ashmem/dalvik-non moving space (deleted)
+73a2e000-77540fff ---     12000   3b13000  /dev/ashmem/dalvik-non moving space (deleted)
+77541000-7793ffff rw-   3b25000    3ff000  /dev/ashmem/dalvik-non moving space (deleted)
+923aa000-92538fff r--    8a9000    18f000  /data/app/com.example.app-2/base.apk
+92539000-9255bfff r--         0     23000  /data/data/com.example.app/app_data/paks/es.pak@162db1c6689
+9255c000-92593fff r--    213000     38000  /data/app/com.example.app-2/base.apk
+92594000-925c0fff r--    87d000     2d000  /data/app/com.example.app-2/base.apk
+925c1000-927d3fff r--    a37000    213000  /data/app/com.example.app-2/base.apk
+927d4000-92e07fff r--    24a000    634000  /data/app/com.example.app-2/base.apk
+92e08000-92e37fff r--   a931000     30000  /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x0)
+92e38000-92e86fff r-x   a961000     4f000  /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x30000)
+92e87000-92e8afff rw-   a9b0000      4000  /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x7f000)
+92e8b000-92e8bfff rw-         0      1000
+92e8c000-92e9dfff r--   d5b0000     12000  /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x0)
+92e9e000-92ebcfff r-x   d5c2000     1f000  /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x12000)
+92ebd000-92ebefff rw-   d5e1000      2000  /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x31000)
+92ebf000-92ebffff rw-         0      1000
+'''
+
+# Example stack section, taken from the same tombstone that _TEST_MEMORY_MAP
+# was extracted from.
+_TEST_STACK = r'''stack:
+        bf89a070  b7439468  /system/lib/libc.so
+        bf89a074  bf89a1e4  [stack]
+        bf89a078  932d4000  /data/app/com.example.app-2/base.apk
+        bf89a07c  b73bfbc9  /system/lib/libc.so (pthread_mutex_lock+65)
+        bf89a080  00000000
+        bf89a084  4000671c  /dev/ashmem/dalvik-main space 1 (deleted)
+        bf89a088  932d1d86  /data/app/com.example.app-2/base.apk
+        bf89a08c  b743671c  /system/lib/libc.so
+        bf89a090  b77f8c00  /system/bin/linker
+        bf89a094  b743cc90
+        bf89a098  932d1d4a  /data/app/com.example.app-2/base.apk
+        bf89a09c  b73bf271  /system/lib/libc.so (__pthread_internal_find(long)+65)
+        bf89a0a0  b743cc90
+        bf89a0a4  bf89a0b0  [stack]
+        bf89a0a8  bf89a0b8  [stack]
+        bf89a0ac  00000008
+        ........  ........
+  #00  bf89a0b0  00000006
+        bf89a0b4  00000002
+        bf89a0b8  b743671c  /system/lib/libc.so
+        bf89a0bc  b73bf5d9  /system/lib/libc.so (pthread_kill+71)
+  #01  bf89a0c0  00006937
+        bf89a0c4  00006937
+        bf89a0c8  00000006
+        bf89a0cc  b77fd3a9  /system/bin/app_process32 (sigprocmask+141)
+        bf89a0d0  00000002
+        bf89a0d4  bf89a0ec  [stack]
+        bf89a0d8  00000000
+        bf89a0dc  b743671c  /system/lib/libc.so
+        bf89a0e0  bf89a12c  [stack]
+        bf89a0e4  bf89a1e4  [stack]
+        bf89a0e8  932d1d4a  /data/app/com.example.app-2/base.apk
+        bf89a0ec  b7365206  /system/lib/libc.so (raise+37)
+  #02  bf89a0f0  b77f8c00  /system/bin/linker
+        bf89a0f4  00000006
+        bf89a0f8  b7439468  /system/lib/libc.so
+        bf89a0fc  b743671c  /system/lib/libc.so
+        bf89a100  bf89a12c  [stack]
+        bf89a104  b743671c  /system/lib/libc.so
+        bf89a108  bf89a12c  [stack]
+        bf89a10c  b735e9e5  /system/lib/libc.so (abort+81)
+  #03  bf89a110  00000006
+        bf89a114  bf89a12c  [stack]
+        bf89a118  00000000
+        bf89a11c  b55a3d3b  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::DefaultLogHandler(google::protobuf::LogLevel, char const*, int, std::__1::basic_string, std::__1::allocator > const&)+99)
+        bf89a120  b7439468  /system/lib/libc.so
+        bf89a124  b55ba38d  /system/lib/libprotobuf-cpp-lite.so
+        bf89a128  b55ba408  /system/lib/libprotobuf-cpp-lite.so
+        bf89a12c  ffffffdf
+        bf89a130  0000003d
+        bf89a134  adfedf00  [anon:libc_malloc]
+        bf89a138  bf89a158  [stack]
+  #04  bf89a13c  a0cee7f0  /data/app/com.example.app-2/base.apk
+        bf89a140  b55c1cb0  /system/lib/libprotobuf-cpp-lite.so
+        bf89a144  bf89a1e4  [stack]
+'''
+
+# Expected value of _TEST_STACK after translation of addresses in the APK
+# into offsets into libraries.
+_EXPECTED_STACK = r'''stack:
+        bf89a070  b7439468  /system/lib/libc.so
+        bf89a074  bf89a1e4  [stack]
+        bf89a078  932d4000  /data/app/com.example.app-2/base.apk
+        bf89a07c  b73bfbc9  /system/lib/libc.so (pthread_mutex_lock+65)
+        bf89a080  00000000
+        bf89a084  4000671c  /dev/ashmem/dalvik-main space 1 (deleted)
+        bf89a088  932d1d86  /data/app/com.example.app-2/base.apk
+        bf89a08c  b743671c  /system/lib/libc.so
+        bf89a090  b77f8c00  /system/bin/linker
+        bf89a094  b743cc90
+        bf89a098  932d1d4a  /data/app/com.example.app-2/base.apk
+        bf89a09c  b73bf271  /system/lib/libc.so (__pthread_internal_find(long)+65)
+        bf89a0a0  b743cc90
+        bf89a0a4  bf89a0b0  [stack]
+        bf89a0a8  bf89a0b8  [stack]
+        bf89a0ac  00000008
+        ........  ........
+  #00  bf89a0b0  00000006
+        bf89a0b4  00000002
+        bf89a0b8  b743671c  /system/lib/libc.so
+        bf89a0bc  b73bf5d9  /system/lib/libc.so (pthread_kill+71)
+  #01  bf89a0c0  00006937
+        bf89a0c4  00006937
+        bf89a0c8  00000006
+        bf89a0cc  b77fd3a9  /system/bin/app_process32 (sigprocmask+141)
+        bf89a0d0  00000002
+        bf89a0d4  bf89a0ec  [stack]
+        bf89a0d8  00000000
+        bf89a0dc  b743671c  /system/lib/libc.so
+        bf89a0e0  bf89a12c  [stack]
+        bf89a0e4  bf89a1e4  [stack]
+        bf89a0e8  932d1d4a  /data/app/com.example.app-2/base.apk
+        bf89a0ec  b7365206  /system/lib/libc.so (raise+37)
+  #02  bf89a0f0  b77f8c00  /system/bin/linker
+        bf89a0f4  00000006
+        bf89a0f8  b7439468  /system/lib/libc.so
+        bf89a0fc  b743671c  /system/lib/libc.so
+        bf89a100  bf89a12c  [stack]
+        bf89a104  b743671c  /system/lib/libc.so
+        bf89a108  bf89a12c  [stack]
+        bf89a10c  b735e9e5  /system/lib/libc.so (abort+81)
+  #03  bf89a110  00000006
+        bf89a114  bf89a12c  [stack]
+        bf89a118  00000000
+        bf89a11c  b55a3d3b  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::DefaultLogHandler(google::protobuf::LogLevel, char const*, int, std::__1::basic_string, std::__1::allocator > const&)+99)
+        bf89a120  b7439468  /system/lib/libc.so
+        bf89a124  b55ba38d  /system/lib/libprotobuf-cpp-lite.so
+        bf89a128  b55ba408  /system/lib/libprotobuf-cpp-lite.so
+        bf89a12c  ffffffdf
+        bf89a130  0000003d
+        bf89a134  adfedf00  [anon:libc_malloc]
+        bf89a138  bf89a158  [stack]
+  #04  bf89a13c  a0cee7f0  /data/app/com.example.app-2/base.apk
+        bf89a140  b55c1cb0  /system/lib/libprotobuf-cpp-lite.so
+        bf89a144  bf89a1e4  [stack]
+'''
+
+_TEST_BACKTRACE = r'''backtrace:
+    #00 pc 00084126  /system/lib/libc.so (tgkill+22)
+    #01 pc 000815d8  /system/lib/libc.so (pthread_kill+70)
+    #02 pc 00027205  /system/lib/libc.so (raise+36)
+    #03 pc 000209e4  /system/lib/libc.so (abort+80)
+    #04 pc 0000cf73  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogMessage::Finish()+117)
+    #05 pc 0000cf8e  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogFinisher::operator=(google::protobuf::internal::LogMessage&)+26)
+    #06 pc 0000d27f  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::VerifyVersion(int, int, char const*)+574)
+    #07 pc 007cd236  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #08 pc 000111a9  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+    #09 pc 00013228  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+    #10 pc 000131de  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+    #11 pc 007cd2d8  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #12 pc 007cd956  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #13 pc 007c2d4a  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #14 pc 009fc9f1  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #15 pc 009fc8ea  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #16 pc 00561c63  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #17 pc 0106fbdb  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #18 pc 004d7371  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #19 pc 004d8159  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #20 pc 004d7b96  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #21 pc 004da4b6  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #22 pc 005ab66c  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #23 pc 005afca2  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #24 pc 0000cae8  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+    #25 pc 00ce864f  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #26 pc 00ce8dfa  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #27 pc 00ce74c6  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #28 pc 00004616  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x961e000)
+    #29 pc 00ce8215  /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+    #30 pc 0013d8c7  /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
+    #31 pc 00137c52  /system/lib/libart.so (art_quick_invoke_static_stub+418)
+    #32 pc 00143651  /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+353)
+    #33 pc 005e06ae  /system/lib/libart.so (artInterpreterToCompiledCodeBridge+190)
+    #34 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #35 pc 0032cfc0  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+    #36 pc 000fc703  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+    #37 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #38 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #39 pc 0032cfc0  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+    #40 pc 000fc703  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+    #41 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #42 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #43 pc 0032ebf9  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+297)
+    #44 pc 000fc955  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+30485)
+    #45 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #46 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #47 pc 0033090c  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+636)
+    #48 pc 000fc67f  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29759)
+    #49 pc 00300700  /system/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*)+128)
+    #50 pc 00667c73  /system/lib/libart.so (artQuickToInterpreterBridge+808)
+    #51 pc 0013d98d  /system/lib/libart.so (art_quick_to_interpreter_bridge+77)
+    #52 pc 7264bc5b  /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
+'''
+
+_EXPECTED_BACKTRACE = r'''backtrace:
+    #00 pc 00084126  /system/lib/libc.so (tgkill+22)
+    #01 pc 000815d8  /system/lib/libc.so (pthread_kill+70)
+    #02 pc 00027205  /system/lib/libc.so (raise+36)
+    #03 pc 000209e4  /system/lib/libc.so (abort+80)
+    #04 pc 0000cf73  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogMessage::Finish()+117)
+    #05 pc 0000cf8e  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogFinisher::operator=(google::protobuf::internal::LogMessage&)+26)
+    #06 pc 0000d27f  /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::VerifyVersion(int, int, char const*)+574)
+    #07 pc 007cd236  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #08 pc 000111a9  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+    #09 pc 00013228  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+    #10 pc 000131de  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+    #11 pc 007cd2d8  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #12 pc 007cd956  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #13 pc 007c2d4a  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #14 pc 009fc9f1  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #15 pc 009fc8ea  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #16 pc 00561c63  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #17 pc 0106fbdb  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #18 pc 004d7371  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #19 pc 004d8159  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #20 pc 004d7b96  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #21 pc 004da4b6  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #22 pc 005ab66c  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #23 pc 005afca2  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #24 pc 0000cae8  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+    #25 pc 00ce864f  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #26 pc 00ce8dfa  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #27 pc 00ce74c6  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #28 pc 00004616  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libembedder.cr.so (offset 0x28000)
+    #29 pc 00ce8215  /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+    #30 pc 0013d8c7  /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
+    #31 pc 00137c52  /system/lib/libart.so (art_quick_invoke_static_stub+418)
+    #32 pc 00143651  /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+353)
+    #33 pc 005e06ae  /system/lib/libart.so (artInterpreterToCompiledCodeBridge+190)
+    #34 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #35 pc 0032cfc0  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+    #36 pc 000fc703  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+    #37 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #38 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #39 pc 0032cfc0  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+    #40 pc 000fc703  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+    #41 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #42 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #43 pc 0032ebf9  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+297)
+    #44 pc 000fc955  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+30485)
+    #45 pc 00300af7  /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+    #46 pc 00328b5d  /system/lib/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+    #47 pc 0033090c  /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+636)
+    #48 pc 000fc67f  /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29759)
+    #49 pc 00300700  /system/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*)+128)
+    #50 pc 00667c73  /system/lib/libart.so (artQuickToInterpreterBridge+808)
+    #51 pc 0013d98d  /system/lib/libart.so (art_quick_to_interpreter_bridge+77)
+    #52 pc 7264bc5b  /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
+'''
+
+_EXPECTED_BACKTRACE_OFFSETS_MAP = {
+  '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so':
+      set([
+          0x1c000 + 0x111a9,
+          0x1c000 + 0x13228,
+          0x1c000 + 0x131de,
+      ]),
+
+  '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so':
+      set([
+          0x90e000 + 0x7cd236,
+          0x90e000 + 0x7cd2d8,
+          0x90e000 + 0x7cd956,
+          0x90e000 + 0x7c2d4a,
+          0x90e000 + 0x9fc9f1,
+          0x90e000 + 0x9fc8ea,
+          0x90e000 + 0x561c63,
+          0x90e000 + 0x106fbdb,
+          0x90e000 + 0x4d7371,
+          0x90e000 + 0x4d8159,
+          0x90e000 + 0x4d7b96,
+          0x90e000 + 0x4da4b6,
+          0x90e000 + 0xcae8,
+      ]),
+  '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so':
+      set([
+          0xc2d000 + 0x5ab66c,
+          0xc2d000 + 0x5afca2,
+          0xc2d000 + 0xce864f,
+          0xc2d000 + 0xce8dfa,
+          0xc2d000 + 0xce74c6,
+          0xc2d000 + 0xce8215,
+      ]),
+  '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libembedder.cr.so':
+      set([
+          0x28000 + 0x4616,
+      ])
+}
+
+# pylint: enable=line-too-long
+
+_ONE_MB = 1024 * 1024
+_TEST_SYMBOL_DATA = {
+  # Regular symbols
+  0: 'mock_sym_for_addr_0 [mock_src/libmock1.so.c:0]',
+  0x1000: 'mock_sym_for_addr_4096 [mock_src/libmock1.so.c:4096]',
+
+  # Symbols without source file path.
+  _ONE_MB: 'mock_sym_for_addr_1048576 [??:0]',
+  _ONE_MB + 0x8234: 'mock_sym_for_addr_1081908 [??:0]',
+
+  # Unknown symbol.
+  2 * _ONE_MB: '?? [??:0]',
+
+  # Inlined symbol.
+  3 * _ONE_MB:
+    'mock_sym_for_addr_3145728_inner [mock_src/libmock1.so.c:3145728]',
+}
+
+@contextlib.contextmanager
+def _TempDir():
+  dirname = tempfile.mkdtemp()
+  try:
+    yield dirname
+  finally:
+    shutil.rmtree(dirname)
+
+
+def _TouchFile(path):
+  # Create parent directories.
+  try:
+    os.makedirs(os.path.dirname(path))
+  except OSError:
+    pass
+  with open(path, 'a'):
+    os.utime(path, None)
+
+class MockApkTranslator(object):
+  """A mock ApkLibraryPathTranslator object used for testing."""
+
+  # Regex that matches the content of APK native library map files generated
+  # with apk_lib_dump.py.
+  _RE_MAP_FILE = re.compile(
+      r'0x(?P[0-9a-f]+)\s+' +
+      r'0x(?P[0-9a-f]+)\s+' +
+      r'0x(?P[0-9a-f]+)\s+' +
+      r'0x(?P[0-9a-f]+)\s+')
+
+  def __init__(self, test_apk_libs=None):
+    """Initialize instance.
+
+    Args:
+      test_apk_libs: Optional list of (file_start, file_end, size, lib_path)
+        tuples, like _TEST_APK_LIBS for example. This will be used to
+        implement TranslatePath().
+    """
+    self._apk_libs = []
+    if test_apk_libs:
+      self._AddLibEntries(test_apk_libs)
+
+  def _AddLibEntries(self, entries):
+    self._apk_libs = sorted(self._apk_libs + entries, key=lambda x: x[0])
+
+  def ReadMapFile(self, file_path):
+    """Read an .apk.native-libs file that was produced with apk_lib_dump.py.
+
+    Args:
+      file_path: input path to .apk.native-libs file. Its format is
+        essentially: 0x  0x 0x 
+    """
+    new_libs = []
+    with open(file_path) as f:
+      for line in f.readlines():
+        m = MockApkTranslator._RE_MAP_FILE.match(line)
+        if m:
+          file_start = int(m.group('file_start'), 16)
+          file_end = int(m.group('file_end'), 16)
+          file_size = int(m.group('file_size'), 16)
+          lib_path = m.group('lib_path')
+          # Sanity check
+          if file_start + file_size != file_end:
+            logging.warning('%s: Inconsistent (start, end, size) values '
+                            '(0x%x, 0x%x, 0x%x)',
+                            file_path, file_start, file_end, file_size)
+          else:
+            new_libs.append((file_start, file_end, file_size, lib_path))
+
+    self._AddLibEntries(new_libs)
+
+  def TranslatePath(self, lib_path, lib_offset):
+    """Translate an APK file path + offset into a library path + offset."""
+    min_pos = 0
+    max_pos = len(self._apk_libs)
+    while min_pos < max_pos:
+      mid_pos = (min_pos + max_pos) // 2
+      mid_entry = self._apk_libs[mid_pos]
+      mid_offset = mid_entry[0]
+      mid_size = mid_entry[2]
+      if lib_offset < mid_offset:
+        max_pos = mid_pos
+      elif lib_offset >= mid_offset + mid_size:
+        min_pos = mid_pos + 1
+      else:
+        # Found it
+        new_path = '%s!lib/%s' % (lib_path, mid_entry[3])
+        new_offset = lib_offset - mid_offset
+        return (new_path, new_offset)
+
+    return lib_path, lib_offset
+
+
+class HostLibraryFinderTest(unittest.TestCase):
+
+  def testEmpty(self):
+    finder = symbol_utils.HostLibraryFinder()
+    self.assertIsNone(finder.Find('/data/data/com.example.app-1/lib/libfoo.so'))
+    self.assertIsNone(
+        finder.Find('/data/data/com.example.app-1/base.apk!lib/libfoo.so'))
+
+
+  def testSimpleDirectory(self):
+    finder = symbol_utils.HostLibraryFinder()
+    with _TempDir() as tmp_dir:
+      host_libfoo_path = os.path.join(tmp_dir, 'libfoo.so')
+      host_libbar_path = os.path.join(tmp_dir, 'libbar.so')
+      _TouchFile(host_libfoo_path)
+      _TouchFile(host_libbar_path)
+
+      finder.AddSearchDir(tmp_dir)
+
+      # Regular library path (extracted at installation by the PackageManager).
+      # Note that the extraction path has changed between Android releases,
+      # i.e. it can be /data/app/, /data/data/ or /data/app-lib/ depending
+      # on the system.
+      self.assertEqual(
+          host_libfoo_path,
+          finder.Find('/data/app-lib/com.example.app-1/lib/libfoo.so'))
+
+      # Verify that the path doesn't really matter
+      self.assertEqual(
+          host_libfoo_path,
+          finder.Find('/whatever/what.apk!lib/libfoo.so'))
+
+      self.assertEqual(
+          host_libbar_path,
+          finder.Find('/data/data/com.example.app-1/lib/libbar.so'))
+
+      self.assertIsNone(
+          finder.Find('/data/data/com.example.app-1/lib/libunknown.so'))
+
+
+  def testMultipleDirectories(self):
+    with _TempDir() as tmp_dir:
+      # Create the following files:
+      #   /aaa/
+      #      libfoo.so
+      #   /bbb/
+      #      libbar.so
+      #      libfoo.so    (this one should never be seen because 'aaa'
+      #                    shall be first in the search path list).
+      #
+      aaa_dir = os.path.join(tmp_dir, 'aaa')
+      bbb_dir = os.path.join(tmp_dir, 'bbb')
+      os.makedirs(aaa_dir)
+      os.makedirs(bbb_dir)
+
+      host_libfoo_path = os.path.join(aaa_dir, 'libfoo.so')
+      host_libbar_path = os.path.join(bbb_dir, 'libbar.so')
+      host_libfoo2_path = os.path.join(bbb_dir, 'libfoo.so')
+
+      _TouchFile(host_libfoo_path)
+      _TouchFile(host_libbar_path)
+      _TouchFile(host_libfoo2_path)
+
+      finder = symbol_utils.HostLibraryFinder()
+      finder.AddSearchDir(aaa_dir)
+      finder.AddSearchDir(bbb_dir)
+
+      self.assertEqual(
+          host_libfoo_path,
+          finder.Find('/data/data/com.example.app-1/lib/libfoo.so'))
+
+      self.assertEqual(
+          host_libfoo_path,
+          finder.Find('/data/whatever/base.apk!lib/libfoo.so'))
+
+      self.assertEqual(
+          host_libbar_path,
+          finder.Find('/data/data/com.example.app-1/lib/libbar.so'))
+
+      self.assertIsNone(
+          finder.Find('/data/data/com.example.app-1/lib/libunknown.so'))
+
+
+class ElfSymbolResolverTest(unittest.TestCase):
+
+  def testCreation(self):
+    resolver = symbol_utils.ElfSymbolResolver(
+        addr2line_path_for_tests=_MOCK_A2L_PATH)
+    self.assertTrue(resolver)
+
+  def testWithSimpleOffsets(self):
+    resolver = symbol_utils.ElfSymbolResolver(
+        addr2line_path_for_tests=_MOCK_A2L_PATH)
+    resolver.SetAndroidAbi('ignored-abi')
+
+    for addr, expected_sym in _TEST_SYMBOL_DATA.items():
+      self.assertEqual(resolver.FindSymbolInfo('/some/path/libmock1.so', addr),
+                       expected_sym)
+
+  def testWithPreResolvedSymbols(self):
+    resolver = symbol_utils.ElfSymbolResolver(
+        addr2line_path_for_tests=_MOCK_A2L_PATH)
+    resolver.SetAndroidAbi('ignored-abi')
+    resolver.AddLibraryOffsets('/some/path/libmock1.so',
+                               list(_TEST_SYMBOL_DATA.keys()))
+
+    resolver.DisallowSymbolizerForTesting()
+
+    for addr, expected_sym in _TEST_SYMBOL_DATA.items():
+      sym_info = resolver.FindSymbolInfo('/some/path/libmock1.so', addr)
+      self.assertIsNotNone(sym_info, 'None symbol info for addr %x' % addr)
+      self.assertEqual(
+          sym_info, expected_sym,
+          'Invalid symbol info for addr %x [%s] expected [%s]' % (
+              addr, sym_info, expected_sym))
+
+
+class MemoryMapTest(unittest.TestCase):
+
+  def testCreation(self):
+    mem_map = symbol_utils.MemoryMap('test-abi32')
+    self.assertIsNone(mem_map.FindSectionForAddress(0))
+
+  def testParseLines(self):
+    mem_map = symbol_utils.MemoryMap('test-abi32')
+    mem_map.ParseLines(_TEST_MEMORY_MAP.splitlines())
+    for exp_addr, exp_size, exp_path, exp_offset in _TEST_MEMORY_MAP_SECTIONS:
+      text = '(addr:%x, size:%x, path:%s, offset=%x)' % (
+          exp_addr, exp_size, exp_path, exp_offset)
+
+      t = mem_map.FindSectionForAddress(exp_addr)
+      self.assertTrue(t, 'Could not find %s' % text)
+      self.assertEqual(t.address, exp_addr)
+      self.assertEqual(t.size, exp_size)
+      self.assertEqual(t.offset, exp_offset)
+      self.assertEqual(t.path, exp_path)
+
+  def testTranslateLine(self):
+    android_abi = 'test-abi'
+    apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+    mem_map = symbol_utils.MemoryMap(android_abi)
+    for line, expected_line in zip(_TEST_MEMORY_MAP.splitlines(),
+                                   _EXPECTED_TEST_MEMORY_MAP.splitlines()):
+      self.assertEqual(mem_map.TranslateLine(line, apk_translator),
+                       expected_line)
+
+class StackTranslatorTest(unittest.TestCase):
+
+  def testSimpleStack(self):
+    android_abi = 'test-abi32'
+    mem_map = symbol_utils.MemoryMap(android_abi)
+    mem_map.ParseLines(_TEST_MEMORY_MAP)
+    apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+    stack_translator = symbol_utils.StackTranslator(android_abi, mem_map,
+                                                    apk_translator)
+    input_stack = _TEST_STACK.splitlines()
+    expected_stack = _EXPECTED_STACK.splitlines()
+    self.assertEqual(len(input_stack), len(expected_stack))
+    for stack_line, expected_line in zip(input_stack, expected_stack):
+      new_line = stack_translator.TranslateLine(stack_line)
+      self.assertEqual(new_line, expected_line)
+
+
+class MockSymbolResolver(symbol_utils.SymbolResolver):
+
+  # A regex matching a symbol definition as it appears in a test symbol file.
+  # Format is:   
+  _RE_SYMBOL_DEFINITION = re.compile(
+      r'(?P[0-9a-f]+)\s+(?P.*)')
+
+  def __init__(self):
+    super(MockSymbolResolver, self).__init__()
+    self._map = collections.defaultdict(dict)
+
+  def AddTestLibrarySymbols(self, lib_name, offsets_map):
+    """Add a new test entry for a given library name.
+
+    Args:
+      lib_name: Library name (e.g. 'libfoo.so')
+      offsets_map: A mapping from offsets to symbol info strings.
+    """
+    self._map[lib_name] = offsets_map
+
+  def ReadTestFile(self, file_path, lib_name):
+    """Read a single test symbol file, matching a given library.
+
+    Args:
+      file_path: Input file path.
+      lib_name: Library name these symbols correspond to (e.g. 'libfoo.so')
+    """
+    with open(file_path) as f:
+      for line in f.readlines():
+        line = line.rstrip()
+        m = MockSymbolResolver._RE_SYMBOL_DEFINITION.match(line)
+        if m:
+          offset = int(m.group('offset'))
+          symbol = m.group('symbol')
+          self._map[lib_name][offset] = symbol
+
+  def ReadTestFilesInDir(self, dir_path, file_suffix):
+    """Read all symbol test files in a given directory.
+
+    Args:
+      dir_path: Directory path.
+      file_suffix: File suffix used to detect test symbol files.
+    """
+    for filename in os.listdir(dir_path):
+      if filename.endswith(file_suffix):
+        lib_name = filename[:-len(file_suffix)]
+        self.ReadTestFile(os.path.join(dir_path, filename), lib_name)
+
+  def FindSymbolInfo(self, device_path, device_offset):
+    """Implement SymbolResolver.FindSymbolInfo."""
+    lib_name = os.path.basename(device_path)
+    offsets = self._map.get(lib_name)
+    if not offsets:
+      return None
+
+    return offsets.get(device_offset)
+
+
+class BacktraceTranslatorTest(unittest.TestCase):
+
+  def testEmpty(self):
+    android_abi = 'test-abi'
+    apk_translator = MockApkTranslator()
+    backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+                                                            apk_translator)
+    self.assertTrue(backtrace_translator)
+
+  def testFindLibraryOffsets(self):
+    android_abi = 'test-abi'
+    apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+    backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+                                                            apk_translator)
+    input_backtrace = _EXPECTED_BACKTRACE.splitlines()
+    expected_lib_offsets_map = _EXPECTED_BACKTRACE_OFFSETS_MAP
+    offset_map = backtrace_translator.FindLibraryOffsets(input_backtrace)
+    for lib_path, offsets in offset_map.items():
+      self.assertTrue(lib_path in expected_lib_offsets_map,
+                      '%s is not in expected library-offsets map!' % lib_path)
+      sorted_offsets = sorted(offsets)
+      sorted_expected_offsets = sorted(expected_lib_offsets_map[lib_path])
+      self.assertEqual(sorted_offsets, sorted_expected_offsets,
+                       '%s has invalid offsets %s expected %s' % (
+                          lib_path, sorted_offsets, sorted_expected_offsets))
+
+  def testTranslateLine(self):
+    android_abi = 'test-abi'
+    apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+    backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+                                                            apk_translator)
+    input_backtrace = _TEST_BACKTRACE.splitlines()
+    expected_backtrace = _EXPECTED_BACKTRACE.splitlines()
+    self.assertEqual(len(input_backtrace), len(expected_backtrace))
+    for trace_line, expected_line in zip(input_backtrace, expected_backtrace):
+      line = backtrace_translator.TranslateLine(trace_line,
+                                                MockSymbolResolver())
+      self.assertEqual(line, expected_line)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/__init__.py b/third_party/libwebrtc/build/android/pylib/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/third_party/libwebrtc/build/android/pylib/utils/app_bundle_utils.py b/third_party/libwebrtc/build/android/pylib/utils/app_bundle_utils.py
new file mode 100644
index 0000000000..986e12688e
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/app_bundle_utils.py
@@ -0,0 +1,169 @@
+# Copyright 2018 The Chromium 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
+import os
+import re
+import sys
+import tempfile
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'gyp'))
+
+from util import build_utils
+from util import md5_check
+from util import resource_utils
+import bundletool
+
+# List of valid modes for GenerateBundleApks()
+BUILD_APKS_MODES = ('default', 'universal', 'system', 'system_compressed')
+OPTIMIZE_FOR_OPTIONS = ('ABI', 'SCREEN_DENSITY', 'LANGUAGE',
+                        'TEXTURE_COMPRESSION_FORMAT')
+_SYSTEM_MODES = ('system_compressed', 'system')
+
+_ALL_ABIS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
+
+
+def _CreateDeviceSpec(bundle_path, sdk_version, locales):
+  if not sdk_version:
+    manifest_data = bundletool.RunBundleTool(
+        ['dump', 'manifest', '--bundle', bundle_path])
+    sdk_version = int(
+        re.search(r'minSdkVersion.*?(\d+)', manifest_data).group(1))
+
+  # Setting sdkVersion=minSdkVersion prevents multiple per-minSdkVersion .apk
+  # files from being created within the .apks file.
+  return {
+      'screenDensity': 1000,  # Ignored since we don't split on density.
+      'sdkVersion': sdk_version,
+      'supportedAbis': _ALL_ABIS,  # Our .aab files are already split on abi.
+      'supportedLocales': locales,
+  }
+
+
+def GenerateBundleApks(bundle_path,
+                       bundle_apks_path,
+                       aapt2_path,
+                       keystore_path,
+                       keystore_password,
+                       keystore_alias,
+                       mode=None,
+                       local_testing=False,
+                       minimal=False,
+                       minimal_sdk_version=None,
+                       check_for_noop=True,
+                       system_image_locales=None,
+                       optimize_for=None):
+  """Generate an .apks archive from a an app bundle if needed.
+
+  Args:
+    bundle_path: Input bundle file path.
+    bundle_apks_path: Output bundle .apks archive path. Name must end with
+      '.apks' or this operation will fail.
+    aapt2_path: Path to aapt2 build tool.
+    keystore_path: Path to keystore.
+    keystore_password: Keystore password, as a string.
+    keystore_alias: Keystore signing key alias.
+    mode: Build mode, which must be either None or one of BUILD_APKS_MODES.
+    minimal: Create the minimal set of apks possible (english-only).
+    minimal_sdk_version: Use this sdkVersion when |minimal| or
+      |system_image_locales| args are present.
+    check_for_noop: Use md5_check to short-circuit when inputs have not changed.
+    system_image_locales: Locales to package in the APK when mode is "system"
+      or "system_compressed".
+    optimize_for: Overrides split configuration, which must be None or
+      one of OPTIMIZE_FOR_OPTIONS.
+  """
+  device_spec = None
+  if minimal_sdk_version:
+    assert minimal or system_image_locales, (
+        'minimal_sdk_version is only used when minimal or system_image_locales '
+        'is specified')
+  if minimal:
+    # Measure with one language split installed. Use Hindi because it is
+    # popular. resource_size.py looks for splits/base-hi.apk.
+    # Note: English is always included since it's in base-master.apk.
+    device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, ['hi'])
+  elif mode in _SYSTEM_MODES:
+    if not system_image_locales:
+      raise Exception('system modes require system_image_locales')
+    # Bundletool doesn't seem to understand device specs with locales in the
+    # form of "-r", so just provide the language code instead.
+    locales = [
+        resource_utils.ToAndroidLocaleName(l).split('-')[0]
+        for l in system_image_locales
+    ]
+    device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, locales)
+
+  def rebuild():
+    logging.info('Building %s', bundle_apks_path)
+    with tempfile.NamedTemporaryFile(suffix='.apks') as tmp_apks_file:
+      cmd_args = [
+          'build-apks',
+          '--aapt2=%s' % aapt2_path,
+          '--output=%s' % tmp_apks_file.name,
+          '--bundle=%s' % bundle_path,
+          '--ks=%s' % keystore_path,
+          '--ks-pass=pass:%s' % keystore_password,
+          '--ks-key-alias=%s' % keystore_alias,
+          '--overwrite',
+      ]
+
+      if local_testing:
+        cmd_args += ['--local-testing']
+
+      if mode is not None:
+        if mode not in BUILD_APKS_MODES:
+          raise Exception('Invalid mode parameter %s (should be in %s)' %
+                          (mode, BUILD_APKS_MODES))
+        cmd_args += ['--mode=' + mode]
+
+      if optimize_for:
+        if optimize_for not in OPTIMIZE_FOR_OPTIONS:
+          raise Exception('Invalid optimize_for parameter %s '
+                          '(should be in %s)' %
+                          (mode, OPTIMIZE_FOR_OPTIONS))
+        cmd_args += ['--optimize-for=' + optimize_for]
+
+      with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as spec_file:
+        if device_spec:
+          json.dump(device_spec, spec_file)
+          spec_file.flush()
+          cmd_args += ['--device-spec=' + spec_file.name]
+        bundletool.RunBundleTool(cmd_args)
+
+      # Make the resulting .apks file hermetic.
+      with build_utils.TempDir() as temp_dir, \
+        build_utils.AtomicOutput(bundle_apks_path, only_if_changed=False) as f:
+        files = build_utils.ExtractAll(tmp_apks_file.name, temp_dir)
+        build_utils.DoZip(files, f, base_dir=temp_dir)
+
+  if check_for_noop:
+    # NOTE: BUNDLETOOL_JAR_PATH is added to input_strings, rather than
+    # input_paths, to speed up MD5 computations by about 400ms (the .jar file
+    # contains thousands of class files which are checked independently,
+    # resulting in an .md5.stamp of more than 60000 lines!).
+    input_paths = [bundle_path, aapt2_path, keystore_path]
+    input_strings = [
+        keystore_password,
+        keystore_alias,
+        bundletool.BUNDLETOOL_JAR_PATH,
+        # NOTE: BUNDLETOOL_VERSION is already part of BUNDLETOOL_JAR_PATH, but
+        # it's simpler to assume that this may not be the case in the future.
+        bundletool.BUNDLETOOL_VERSION,
+        device_spec,
+    ]
+    if mode is not None:
+      input_strings.append(mode)
+
+    # Avoid rebuilding (saves ~20s) when the input files have not changed. This
+    # is essential when calling the apk_operations.py script multiple times with
+    # the same bundle (e.g. out/Debug/bin/monochrome_public_bundle run).
+    md5_check.CallAndRecordIfStale(
+        rebuild,
+        input_paths=input_paths,
+        input_strings=input_strings,
+        output_paths=[bundle_apks_path])
+  else:
+    rebuild()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/argparse_utils.py b/third_party/libwebrtc/build/android/pylib/utils/argparse_utils.py
new file mode 100644
index 0000000000..bd603c9d5a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/argparse_utils.py
@@ -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.
+
+
+
+import argparse
+
+
+class CustomHelpAction(argparse.Action):
+  '''Allows defining custom help actions.
+
+  Help actions can run even when the parser would otherwise fail on missing
+  arguments. The first help or custom help command mentioned on the command
+  line will have its help text displayed.
+
+  Usage:
+      parser = argparse.ArgumentParser(...)
+      CustomHelpAction.EnableFor(parser)
+      parser.add_argument('--foo-help',
+                          action='custom_help',
+                          custom_help_text='this is the help message',
+                          help='What this helps with')
+  '''
+  # Derived from argparse._HelpAction from
+  # https://github.com/python/cpython/blob/master/Lib/argparse.py
+
+  # pylint: disable=redefined-builtin
+  # (complains about 'help' being redefined)
+  def __init__(self,
+               option_strings,
+               dest=argparse.SUPPRESS,
+               default=argparse.SUPPRESS,
+               custom_help_text=None,
+               help=None):
+    super(CustomHelpAction, self).__init__(option_strings=option_strings,
+                                           dest=dest,
+                                           default=default,
+                                           nargs=0,
+                                           help=help)
+
+    if not custom_help_text:
+      raise ValueError('custom_help_text is required')
+    self._help_text = custom_help_text
+
+  def __call__(self, parser, namespace, values, option_string=None):
+    print(self._help_text)
+    parser.exit()
+
+  @staticmethod
+  def EnableFor(parser):
+    parser.register('action', 'custom_help', CustomHelpAction)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils.py b/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils.py
new file mode 100644
index 0000000000..149d0b9c8c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils.py
@@ -0,0 +1,171 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Utilities for setting up and tear down WPR and TsProxy service."""
+
+from py_utils import ts_proxy_server
+from py_utils import webpagereplay_go_server
+
+from devil.android import forwarder
+
+PROXY_HOST_IP = '127.0.0.1'
+# From Catapult/WebPageReplay document.
+IGNORE_CERT_ERROR_SPKI_LIST = 'PhrPvGIaAMmd29hj8BCZOq096yj7uMpRNHpn5PDxI6I='
+PROXY_SERVER = 'socks5://localhost'
+DEFAULT_DEVICE_PORT = 1080
+DEFAULT_ROUND_TRIP_LATENCY_MS = 100
+DEFAULT_DOWNLOAD_BANDWIDTH_KBPS = 72000
+DEFAULT_UPLOAD_BANDWIDTH_KBPS = 72000
+
+
+class WPRServer(object):
+  """Utils to set up a webpagereplay_go_server instance."""
+
+  def __init__(self):
+    self._archive_path = None
+    self._host_http_port = 0
+    self._host_https_port = 0
+    self._record_mode = False
+    self._server = None
+
+  def StartServer(self, wpr_archive_path):
+    """Starts a webpagereplay_go_server instance."""
+    if wpr_archive_path == self._archive_path and self._server:
+      # Reuse existing webpagereplay_go_server instance.
+      return
+
+    if self._server:
+      self.StopServer()
+
+    replay_options = []
+    if self._record_mode:
+      replay_options.append('--record')
+
+    ports = {}
+    if not self._server:
+      self._server = webpagereplay_go_server.ReplayServer(
+          wpr_archive_path,
+          PROXY_HOST_IP,
+          http_port=self._host_http_port,
+          https_port=self._host_https_port,
+          replay_options=replay_options)
+      self._archive_path = wpr_archive_path
+      ports = self._server.StartServer()
+
+    self._host_http_port = ports['http']
+    self._host_https_port = ports['https']
+
+  def StopServer(self):
+    """Stops the webpagereplay_go_server instance and resets archive."""
+    self._server.StopServer()
+    self._server = None
+    self._host_http_port = 0
+    self._host_https_port = 0
+
+  @staticmethod
+  def SetServerBinaryPath(go_binary_path):
+    """Sets the go_binary_path for webpagereplay_go_server.ReplayServer."""
+    webpagereplay_go_server.ReplayServer.SetGoBinaryPath(go_binary_path)
+
+  @property
+  def record_mode(self):
+    return self._record_mode
+
+  @record_mode.setter
+  def record_mode(self, value):
+    self._record_mode = value
+
+  @property
+  def http_port(self):
+    return self._host_http_port
+
+  @property
+  def https_port(self):
+    return self._host_https_port
+
+  @property
+  def archive_path(self):
+    return self._archive_path
+
+
+class ChromeProxySession(object):
+  """Utils to help set up a Chrome Proxy."""
+
+  def __init__(self, device_proxy_port=DEFAULT_DEVICE_PORT):
+    self._device_proxy_port = device_proxy_port
+    self._ts_proxy_server = ts_proxy_server.TsProxyServer(PROXY_HOST_IP)
+    self._wpr_server = WPRServer()
+
+  @property
+  def wpr_record_mode(self):
+    """Returns whether this proxy session was running in record mode."""
+    return self._wpr_server.record_mode
+
+  @wpr_record_mode.setter
+  def wpr_record_mode(self, value):
+    self._wpr_server.record_mode = value
+
+  @property
+  def wpr_replay_mode(self):
+    """Returns whether this proxy session was running in replay mode."""
+    return not self._wpr_server.record_mode
+
+  @property
+  def wpr_archive_path(self):
+    """Returns the wpr archive file path used in this proxy session."""
+    return self._wpr_server.archive_path
+
+  @property
+  def device_proxy_port(self):
+    return self._device_proxy_port
+
+  def GetFlags(self):
+    """Gets the chrome command line flags to be needed by ChromeProxySession."""
+    extra_flags = []
+
+    extra_flags.append('--ignore-certificate-errors-spki-list=%s' %
+                       IGNORE_CERT_ERROR_SPKI_LIST)
+    extra_flags.append('--proxy-server=%s:%s' %
+                       (PROXY_SERVER, self._device_proxy_port))
+    return extra_flags
+
+  @staticmethod
+  def SetWPRServerBinary(go_binary_path):
+    """Sets the WPR server go_binary_path."""
+    WPRServer.SetServerBinaryPath(go_binary_path)
+
+  def Start(self, device, wpr_archive_path):
+    """Starts the wpr_server as well as the ts_proxy server and setups env.
+
+    Args:
+      device: A DeviceUtils instance.
+      wpr_archive_path: A abs path to the wpr archive file.
+
+    """
+    self._wpr_server.StartServer(wpr_archive_path)
+    self._ts_proxy_server.StartServer()
+
+    # Maps device port to host port
+    forwarder.Forwarder.Map(
+        [(self._device_proxy_port, self._ts_proxy_server.port)], device)
+    # Maps tsProxy port to wpr http/https ports
+    self._ts_proxy_server.UpdateOutboundPorts(
+        http_port=self._wpr_server.http_port,
+        https_port=self._wpr_server.https_port)
+    self._ts_proxy_server.UpdateTrafficSettings(
+        round_trip_latency_ms=DEFAULT_ROUND_TRIP_LATENCY_MS,
+        download_bandwidth_kbps=DEFAULT_DOWNLOAD_BANDWIDTH_KBPS,
+        upload_bandwidth_kbps=DEFAULT_UPLOAD_BANDWIDTH_KBPS)
+
+  def Stop(self, device):
+    """Stops the wpr_server, and ts_proxy server and tears down env.
+
+    Note that Stop does not reset wpr_record_mode, wpr_replay_mode,
+    wpr_archive_path property.
+
+    Args:
+      device: A DeviceUtils instance.
+    """
+    self._wpr_server.StopServer()
+    self._ts_proxy_server.StopServer()
+    forwarder.Forwarder.UnmapDevicePort(self._device_proxy_port, device)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils_test.py b/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils_test.py
new file mode 100755
index 0000000000..7a52024661
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/chrome_proxy_utils_test.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env vpython3
+# Copyright 2020 The Chromium 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 chrome_proxy_utils."""
+
+#pylint: disable=protected-access
+
+import os
+import unittest
+
+from pylib.utils import chrome_proxy_utils
+
+from devil.android import forwarder
+from devil.android import device_utils
+from devil.android.sdk import adb_wrapper
+from py_utils import ts_proxy_server
+from py_utils import webpagereplay_go_server
+
+import mock  # pylint: disable=import-error
+
+
+def _DeviceUtilsMock(test_serial, is_ready=True):
+  """Returns a DeviceUtils instance based on given serial."""
+  adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+  adb.__str__ = mock.Mock(return_value=test_serial)
+  adb.GetDeviceSerial.return_value = test_serial
+  adb.is_ready = is_ready
+  return device_utils.DeviceUtils(adb)
+
+
+class ChromeProxySessionTest(unittest.TestCase):
+  """Unittest for ChromeProxySession."""
+
+  #pylint: disable=no-self-use
+
+  @mock.patch.object(forwarder.Forwarder, 'Map')
+  @mock.patch.object(chrome_proxy_utils.WPRServer, 'StartServer')
+  @mock.patch.object(ts_proxy_server.TsProxyServer, 'StartServer')
+  @mock.patch.object(ts_proxy_server.TsProxyServer, 'UpdateOutboundPorts')
+  @mock.patch.object(ts_proxy_server.TsProxyServer, 'UpdateTrafficSettings')
+  @mock.patch('py_utils.ts_proxy_server.TsProxyServer.port',
+              new_callable=mock.PropertyMock)
+  def test_Start(self, port_mock, traffic_setting_mock, outboundport_mock,
+                 start_server_mock, wpr_mock, forwarder_mock):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(4)
+    chrome_proxy._wpr_server._host_http_port = 1
+    chrome_proxy._wpr_server._host_https_port = 2
+    port_mock.return_value = 3
+    device = _DeviceUtilsMock('01234')
+    chrome_proxy.Start(device, 'abc')
+
+    forwarder_mock.assert_called_once_with([(4, 3)], device)
+    wpr_mock.assert_called_once_with('abc')
+    start_server_mock.assert_called_once()
+    outboundport_mock.assert_called_once_with(http_port=1, https_port=2)
+    traffic_setting_mock.assert_called_once_with(download_bandwidth_kbps=72000,
+                                                 round_trip_latency_ms=100,
+                                                 upload_bandwidth_kbps=72000)
+    port_mock.assert_called_once()
+
+  @mock.patch.object(forwarder.Forwarder, 'UnmapDevicePort')
+  @mock.patch.object(chrome_proxy_utils.WPRServer, 'StopServer')
+  @mock.patch.object(ts_proxy_server.TsProxyServer, 'StopServer')
+  def test_Stop(self, ts_proxy_mock, wpr_mock, forwarder_mock):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(4)
+    device = _DeviceUtilsMock('01234')
+    chrome_proxy.wpr_record_mode = True
+    chrome_proxy._wpr_server._archive_path = 'abc'
+    chrome_proxy.Stop(device)
+
+    forwarder_mock.assert_called_once_with(4, device)
+    wpr_mock.assert_called_once_with()
+    ts_proxy_mock.assert_called_once_with()
+
+  #pylint: enable=no-self-use
+
+  @mock.patch.object(forwarder.Forwarder, 'UnmapDevicePort')
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StopServer')
+  @mock.patch.object(ts_proxy_server.TsProxyServer, 'StopServer')
+  def test_Stop_WithProperties(self, ts_proxy_mock, wpr_mock, forwarder_mock):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(4)
+    chrome_proxy._wpr_server._server = webpagereplay_go_server.ReplayServer(
+        os.path.abspath(__file__), chrome_proxy_utils.PROXY_HOST_IP, 0, 0, [])
+    chrome_proxy._wpr_server._archive_path = os.path.abspath(__file__)
+    device = _DeviceUtilsMock('01234')
+    chrome_proxy.wpr_record_mode = True
+    chrome_proxy.Stop(device)
+
+    forwarder_mock.assert_called_once_with(4, device)
+    wpr_mock.assert_called_once_with()
+    ts_proxy_mock.assert_called_once_with()
+    self.assertFalse(chrome_proxy.wpr_replay_mode)
+    self.assertEqual(chrome_proxy.wpr_archive_path, os.path.abspath(__file__))
+
+  def test_SetWPRRecordMode(self):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(4)
+    chrome_proxy.wpr_record_mode = True
+    self.assertTrue(chrome_proxy._wpr_server.record_mode)
+    self.assertTrue(chrome_proxy.wpr_record_mode)
+    self.assertFalse(chrome_proxy.wpr_replay_mode)
+
+    chrome_proxy.wpr_record_mode = False
+    self.assertFalse(chrome_proxy._wpr_server.record_mode)
+    self.assertFalse(chrome_proxy.wpr_record_mode)
+    self.assertTrue(chrome_proxy.wpr_replay_mode)
+
+  def test_SetWPRArchivePath(self):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(4)
+    chrome_proxy._wpr_server._archive_path = 'abc'
+    self.assertEqual(chrome_proxy.wpr_archive_path, 'abc')
+
+  def test_UseDefaultDeviceProxyPort(self):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession()
+    expected_flags = [
+        '--ignore-certificate-errors-spki-list='
+        'PhrPvGIaAMmd29hj8BCZOq096yj7uMpRNHpn5PDxI6I=',
+        '--proxy-server=socks5://localhost:1080'
+    ]
+    self.assertEqual(chrome_proxy.device_proxy_port, 1080)
+    self.assertListEqual(chrome_proxy.GetFlags(), expected_flags)
+
+  def test_UseNewDeviceProxyPort(self):
+    chrome_proxy = chrome_proxy_utils.ChromeProxySession(1)
+    expected_flags = [
+        '--ignore-certificate-errors-spki-list='
+        'PhrPvGIaAMmd29hj8BCZOq096yj7uMpRNHpn5PDxI6I=',
+        '--proxy-server=socks5://localhost:1'
+    ]
+    self.assertEqual(chrome_proxy.device_proxy_port, 1)
+    self.assertListEqual(chrome_proxy.GetFlags(), expected_flags)
+
+
+class WPRServerTest(unittest.TestCase):
+  @mock.patch('py_utils.webpagereplay_go_server.ReplayServer')
+  def test_StartSever_fresh_replaymode(self, wpr_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_archive_file = os.path.abspath(__file__)
+    wpr_server.StartServer(wpr_archive_file)
+
+    wpr_mock.assert_called_once_with(wpr_archive_file,
+                                     '127.0.0.1',
+                                     http_port=0,
+                                     https_port=0,
+                                     replay_options=[])
+
+    self.assertEqual(wpr_server._archive_path, wpr_archive_file)
+    self.assertTrue(wpr_server._server)
+
+  @mock.patch('py_utils.webpagereplay_go_server.ReplayServer')
+  def test_StartSever_fresh_recordmode(self, wpr_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_server.record_mode = True
+    wpr_server.StartServer(os.path.abspath(__file__))
+    wpr_archive_file = os.path.abspath(__file__)
+
+    wpr_mock.assert_called_once_with(wpr_archive_file,
+                                     '127.0.0.1',
+                                     http_port=0,
+                                     https_port=0,
+                                     replay_options=['--record'])
+
+    self.assertEqual(wpr_server._archive_path, os.path.abspath(__file__))
+    self.assertTrue(wpr_server._server)
+
+  #pylint: disable=no-self-use
+
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StartServer')
+  def test_StartSever_recordmode(self, start_server_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    start_server_mock.return_value = {'http': 1, 'https': 2}
+    wpr_server.StartServer(os.path.abspath(__file__))
+
+    start_server_mock.assert_called_once()
+    self.assertEqual(wpr_server._host_http_port, 1)
+    self.assertEqual(wpr_server._host_https_port, 2)
+    self.assertEqual(wpr_server._archive_path, os.path.abspath(__file__))
+    self.assertTrue(wpr_server._server)
+
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StartServer')
+  def test_StartSever_reuseServer(self, start_server_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_server._server = webpagereplay_go_server.ReplayServer(
+        os.path.abspath(__file__),
+        chrome_proxy_utils.PROXY_HOST_IP,
+        http_port=0,
+        https_port=0,
+        replay_options=[])
+    wpr_server._archive_path = os.path.abspath(__file__)
+    wpr_server.StartServer(os.path.abspath(__file__))
+    start_server_mock.assert_not_called()
+
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StartServer')
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StopServer')
+  def test_StartSever_notReuseServer(self, stop_server_mock, start_server_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_server._server = webpagereplay_go_server.ReplayServer(
+        os.path.abspath(__file__),
+        chrome_proxy_utils.PROXY_HOST_IP,
+        http_port=0,
+        https_port=0,
+        replay_options=[])
+    wpr_server._archive_path = ''
+    wpr_server.StartServer(os.path.abspath(__file__))
+    start_server_mock.assert_called_once()
+    stop_server_mock.assert_called_once()
+
+  #pylint: enable=no-self-use
+
+  @mock.patch.object(webpagereplay_go_server.ReplayServer, 'StopServer')
+  def test_StopServer(self, stop_server_mock):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_server._server = webpagereplay_go_server.ReplayServer(
+        os.path.abspath(__file__),
+        chrome_proxy_utils.PROXY_HOST_IP,
+        http_port=0,
+        https_port=0,
+        replay_options=[])
+    wpr_server.StopServer()
+    stop_server_mock.assert_called_once()
+    self.assertFalse(wpr_server._server)
+    self.assertFalse(wpr_server._archive_path)
+    self.assertFalse(wpr_server.http_port)
+    self.assertFalse(wpr_server.https_port)
+
+  def test_SetWPRRecordMode(self):
+    wpr_server = chrome_proxy_utils.WPRServer()
+    wpr_server.record_mode = True
+    self.assertTrue(wpr_server.record_mode)
+    wpr_server.record_mode = False
+    self.assertFalse(wpr_server.record_mode)
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/decorators.py b/third_party/libwebrtc/build/android/pylib/utils/decorators.py
new file mode 100644
index 0000000000..8eec1d1e58
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/decorators.py
@@ -0,0 +1,37 @@
+# Copyright 2017 The Chromium 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
+
+
+def Memoize(f):
+  """Decorator to cache return values of function."""
+  memoize_dict = {}
+  @functools.wraps(f)
+  def wrapper(*args, **kwargs):
+    key = repr((args, kwargs))
+    if key not in memoize_dict:
+      memoize_dict[key] = f(*args, **kwargs)
+    return memoize_dict[key]
+  return wrapper
+
+
+def NoRaiseException(default_return_value=None, exception_message=''):
+  """Returns decorator that catches and logs uncaught Exceptions.
+
+  Args:
+    default_return_value: Value to return in the case of uncaught Exception.
+    exception_message: Message for uncaught exceptions.
+  """
+  def decorator(f):
+    @functools.wraps(f)
+    def wrapper(*args, **kwargs):
+      try:
+        return f(*args, **kwargs)
+      except Exception:  # pylint: disable=broad-except
+        logging.exception(exception_message)
+        return default_return_value
+    return wrapper
+  return decorator
diff --git a/third_party/libwebrtc/build/android/pylib/utils/decorators_test.py b/third_party/libwebrtc/build/android/pylib/utils/decorators_test.py
new file mode 100755
index 0000000000..5d39846824
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/decorators_test.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env vpython3
+# Copyright 2017 The Chromium 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."""
+
+import unittest
+
+from pylib.utils import decorators
+
+
+class NoRaiseExceptionDecoratorTest(unittest.TestCase):
+
+  def testFunctionDoesNotRaiseException(self):
+    """Tests that the |NoRaiseException| decorator catches exception."""
+
+    @decorators.NoRaiseException()
+    def raiseException():
+      raise Exception()
+
+    try:
+      raiseException()
+    except Exception:  # pylint: disable=broad-except
+      self.fail('Exception was not caught by |NoRaiseException| decorator')
+
+  def testFunctionReturnsCorrectValues(self):
+    """Tests that the |NoRaiseException| decorator returns correct values."""
+
+    @decorators.NoRaiseException(default_return_value=111)
+    def raiseException():
+      raise Exception()
+
+    @decorators.NoRaiseException(default_return_value=111)
+    def doesNotRaiseException():
+      return 999
+
+    self.assertEqual(raiseException(), 111)
+    self.assertEqual(doesNotRaiseException(), 999)
+
+
+class MemoizeDecoratorTest(unittest.TestCase):
+
+  def testFunctionExceptionNotMemoized(self):
+    """Tests that |Memoize| decorator does not cache exception results."""
+
+    class ExceptionType1(Exception):
+      pass
+
+    class ExceptionType2(Exception):
+      pass
+
+    @decorators.Memoize
+    def raiseExceptions():
+      if raiseExceptions.count == 0:
+        raiseExceptions.count += 1
+        raise ExceptionType1()
+
+      if raiseExceptions.count == 1:
+        raise ExceptionType2()
+    raiseExceptions.count = 0
+
+    with self.assertRaises(ExceptionType1):
+      raiseExceptions()
+    with self.assertRaises(ExceptionType2):
+      raiseExceptions()
+
+  def testFunctionResultMemoized(self):
+    """Tests that |Memoize| decorator caches results."""
+
+    @decorators.Memoize
+    def memoized():
+      memoized.count += 1
+      return memoized.count
+    memoized.count = 0
+
+    def notMemoized():
+      notMemoized.count += 1
+      return notMemoized.count
+    notMemoized.count = 0
+
+    self.assertEqual(memoized(), 1)
+    self.assertEqual(memoized(), 1)
+    self.assertEqual(memoized(), 1)
+
+    self.assertEqual(notMemoized(), 1)
+    self.assertEqual(notMemoized(), 2)
+    self.assertEqual(notMemoized(), 3)
+
+  def testFunctionMemoizedBasedOnArgs(self):
+    """Tests that |Memoize| caches results based on args and kwargs."""
+
+    @decorators.Memoize
+    def returnValueBasedOnArgsKwargs(a, k=0):
+      return a + k
+
+    self.assertEqual(returnValueBasedOnArgsKwargs(1, 1), 2)
+    self.assertEqual(returnValueBasedOnArgsKwargs(1, 2), 3)
+    self.assertEqual(returnValueBasedOnArgsKwargs(2, 1), 3)
+    self.assertEqual(returnValueBasedOnArgsKwargs(3, 3), 6)
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/device_dependencies.py b/third_party/libwebrtc/build/android/pylib/utils/device_dependencies.py
new file mode 100644
index 0000000000..9cb5bd892a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/device_dependencies.py
@@ -0,0 +1,136 @@
+# 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.
+
+import os
+import re
+
+from pylib import constants
+
+
+_EXCLUSIONS = [
+    re.compile(r'.*OWNERS'),  # Should never be included.
+    re.compile(r'.*\.crx'),  # Chrome extension zip files.
+    re.compile(os.path.join('.*',
+                            r'\.git.*')),  # Any '.git*' directories/files.
+    re.compile(r'.*\.so'),  # Libraries packed into .apk.
+    re.compile(r'.*Mojo.*manifest\.json'),  # Some source_set()s pull these in.
+    re.compile(r'.*\.py'),  # Some test_support targets include python deps.
+    re.compile(r'.*\.apk'),  # Should be installed separately.
+    re.compile(r'.*lib.java/.*'),  # Never need java intermediates.
+
+    # Test filter files:
+    re.compile(r'.*/testing/buildbot/filters/.*'),
+
+    # Chrome external extensions config file.
+    re.compile(r'.*external_extensions\.json'),
+
+    # Exists just to test the compile, not to be run.
+    re.compile(r'.*jni_generator_tests'),
+
+    # v8's blobs and icu data get packaged into APKs.
+    re.compile(r'.*snapshot_blob.*\.bin'),
+    re.compile(r'.*icudtl.bin'),
+
+    # Scripts that are needed by swarming, but not on devices:
+    re.compile(r'.*llvm-symbolizer'),
+    re.compile(r'.*md5sum_bin'),
+    re.compile(os.path.join('.*', 'development', 'scripts', 'stack')),
+
+    # Required for java deobfuscation on the host:
+    re.compile(r'.*build/android/stacktrace/.*'),
+    re.compile(r'.*third_party/jdk/.*'),
+    re.compile(r'.*third_party/proguard/.*'),
+
+    # Build artifacts:
+    re.compile(r'.*\.stamp'),
+    re.compile(r'.*.pak\.info'),
+    re.compile(r'.*\.incremental\.json'),
+]
+
+
+def _FilterDataDeps(abs_host_files):
+  exclusions = _EXCLUSIONS + [
+      re.compile(os.path.join(constants.GetOutDirectory(), 'bin'))
+  ]
+  return [p for p in abs_host_files if not any(r.match(p) for r in exclusions)]
+
+
+def DevicePathComponentsFor(host_path, output_directory):
+  """Returns the device path components for a given host path.
+
+  This returns the device path as a list of joinable path components,
+  with None as the first element to indicate that the path should be
+  rooted at $EXTERNAL_STORAGE.
+
+  e.g., given
+
+    '$RUNTIME_DEPS_ROOT_DIR/foo/bar/baz.txt'
+
+  this would return
+
+    [None, 'foo', 'bar', 'baz.txt']
+
+  This handles a couple classes of paths differently than it otherwise would:
+    - All .pak files get mapped to top-level paks/
+    - All other dependencies get mapped to the top level directory
+        - If a file is not in the output directory then it's relative path to
+          the output directory will start with .. strings, so we remove those
+          and then the path gets mapped to the top-level directory
+        - If a file is in the output directory then the relative path to the
+          output directory gets mapped to the top-level directory
+
+  e.g. given
+
+    '$RUNTIME_DEPS_ROOT_DIR/out/Release/icu_fake_dir/icudtl.dat'
+
+  this would return
+
+    [None, 'icu_fake_dir', 'icudtl.dat']
+
+  Args:
+    host_path: The absolute path to the host file.
+  Returns:
+    A list of device path components.
+  """
+  if (host_path.startswith(output_directory) and
+      os.path.splitext(host_path)[1] == '.pak'):
+    return [None, 'paks', os.path.basename(host_path)]
+
+  rel_host_path = os.path.relpath(host_path, output_directory)
+
+  device_path_components = [None]
+  p = rel_host_path
+  while p:
+    p, d = os.path.split(p)
+    # The relative path from the output directory to a file under the runtime
+    # deps root directory may start with multiple .. strings, so they need to
+    # be skipped.
+    if d and d != os.pardir:
+      device_path_components.insert(1, d)
+  return device_path_components
+
+
+def GetDataDependencies(runtime_deps_path):
+  """Returns a list of device data dependencies.
+
+  Args:
+    runtime_deps_path: A str path to the .runtime_deps file.
+  Returns:
+    A list of (host_path, device_path) tuples.
+  """
+  if not runtime_deps_path:
+    return []
+
+  with open(runtime_deps_path, 'r') as runtime_deps_file:
+    rel_host_files = [l.strip() for l in runtime_deps_file if l]
+
+  output_directory = constants.GetOutDirectory()
+  abs_host_files = [
+      os.path.abspath(os.path.join(output_directory, r))
+      for r in rel_host_files]
+  filtered_abs_host_files = _FilterDataDeps(abs_host_files)
+  # TODO(crbug.com/752610): Filter out host executables, and investigate
+  # whether other files could be filtered as well.
+  return [(f, DevicePathComponentsFor(f, output_directory))
+          for f in filtered_abs_host_files]
diff --git a/third_party/libwebrtc/build/android/pylib/utils/device_dependencies_test.py b/third_party/libwebrtc/build/android/pylib/utils/device_dependencies_test.py
new file mode 100755
index 0000000000..35879882b7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/device_dependencies_test.py
@@ -0,0 +1,52 @@
+#! /usr/bin/env vpython3
+# 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.
+
+import os
+import unittest
+
+from pylib import constants
+from pylib.utils import device_dependencies
+
+
+class DevicePathComponentsForTest(unittest.TestCase):
+
+  def testCheckedInFile(self):
+    test_path = os.path.join(constants.DIR_SOURCE_ROOT, 'foo', 'bar', 'baz.txt')
+    output_directory = os.path.join(
+        constants.DIR_SOURCE_ROOT, 'out-foo', 'Release')
+    self.assertEqual([None, 'foo', 'bar', 'baz.txt'],
+                     device_dependencies.DevicePathComponentsFor(
+                         test_path, output_directory))
+
+  def testOutputDirectoryFile(self):
+    test_path = os.path.join(constants.DIR_SOURCE_ROOT, 'out-foo', 'Release',
+                             'icudtl.dat')
+    output_directory = os.path.join(
+        constants.DIR_SOURCE_ROOT, 'out-foo', 'Release')
+    self.assertEqual([None, 'icudtl.dat'],
+                     device_dependencies.DevicePathComponentsFor(
+                         test_path, output_directory))
+
+  def testOutputDirectorySubdirFile(self):
+    test_path = os.path.join(constants.DIR_SOURCE_ROOT, 'out-foo', 'Release',
+                             'test_dir', 'icudtl.dat')
+    output_directory = os.path.join(
+        constants.DIR_SOURCE_ROOT, 'out-foo', 'Release')
+    self.assertEqual([None, 'test_dir', 'icudtl.dat'],
+                     device_dependencies.DevicePathComponentsFor(
+                         test_path, output_directory))
+
+  def testOutputDirectoryPakFile(self):
+    test_path = os.path.join(constants.DIR_SOURCE_ROOT, 'out-foo', 'Release',
+                             'foo.pak')
+    output_directory = os.path.join(
+        constants.DIR_SOURCE_ROOT, 'out-foo', 'Release')
+    self.assertEqual([None, 'paks', 'foo.pak'],
+                     device_dependencies.DevicePathComponentsFor(
+                         test_path, output_directory))
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/dexdump.py b/third_party/libwebrtc/build/android/pylib/utils/dexdump.py
new file mode 100644
index 0000000000..f81ac603d4
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/dexdump.py
@@ -0,0 +1,136 @@
+# 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.
+
+import os
+import re
+import shutil
+import sys
+import tempfile
+from xml.etree import ElementTree
+
+from devil.utils import cmd_helper
+from pylib import constants
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'gyp'))
+from util import build_utils
+
+DEXDUMP_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'dexdump')
+
+
+def Dump(apk_path):
+  """Dumps class and method information from a APK into a dict via dexdump.
+
+  Args:
+    apk_path: An absolute path to an APK file to dump.
+  Returns:
+    A dict in the following format:
+      {
+        : {
+          'classes': {
+            : {
+              'methods': [, ]
+            }
+          }
+        }
+      }
+  """
+  try:
+    dexfile_dir = tempfile.mkdtemp()
+    parsed_dex_files = []
+    for dex_file in build_utils.ExtractAll(apk_path,
+                                           dexfile_dir,
+                                           pattern='*classes*.dex'):
+      output_xml = cmd_helper.GetCmdOutput(
+          [DEXDUMP_PATH, '-l', 'xml', dex_file])
+      # Dexdump doesn't escape its XML output very well; decode it as utf-8 with
+      # invalid sequences replaced, then remove forbidden characters and
+      # re-encode it (as etree expects a byte string as input so it can figure
+      # out the encoding itself from the XML declaration)
+      BAD_XML_CHARS = re.compile(
+          u'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x84\x86-\x9f' +
+          u'\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]')
+      if sys.version_info[0] < 3:
+        decoded_xml = output_xml.decode('utf-8', 'replace')
+        clean_xml = BAD_XML_CHARS.sub(u'\ufffd', decoded_xml)
+      else:
+        # Line duplicated to avoid pylint redefined-variable-type error.
+        clean_xml = BAD_XML_CHARS.sub(u'\ufffd', output_xml)
+      parsed_dex_files.append(
+          _ParseRootNode(ElementTree.fromstring(clean_xml.encode('utf-8'))))
+    return parsed_dex_files
+  finally:
+    shutil.rmtree(dexfile_dir)
+
+
+def _ParseRootNode(root):
+  """Parses the XML output of dexdump. This output is in the following format.
+
+  This is a subset of the information contained within dexdump output.
+
+  
+    
+      
+        
+        
+        
+          
+          
+        
+        
+          
+          
+        
+      
+    
+  
+  """
+  results = {}
+  for child in root:
+    if child.tag == 'package':
+      package_name = child.attrib['name']
+      parsed_node = _ParsePackageNode(child)
+      if package_name in results:
+        results[package_name]['classes'].update(parsed_node['classes'])
+      else:
+        results[package_name] = parsed_node
+  return results
+
+
+def _ParsePackageNode(package_node):
+  """Parses a  node from the dexdump xml output.
+
+  Returns:
+    A dict in the format:
+      {
+        'classes': {
+          : {
+            'methods': [, ]
+          },
+          : {
+            'methods': [, ]
+          },
+        }
+      }
+  """
+  classes = {}
+  for child in package_node:
+    if child.tag == 'class':
+      classes[child.attrib['name']] = _ParseClassNode(child)
+  return {'classes': classes}
+
+
+def _ParseClassNode(class_node):
+  """Parses a  node from the dexdump xml output.
+
+  Returns:
+    A dict in the format:
+      {
+        'methods': [, ]
+      }
+  """
+  methods = []
+  for child in class_node:
+    if child.tag == 'method':
+      methods.append(child.attrib['name'])
+  return {'methods': methods, 'superclass': class_node.attrib['extends']}
diff --git a/third_party/libwebrtc/build/android/pylib/utils/dexdump_test.py b/third_party/libwebrtc/build/android/pylib/utils/dexdump_test.py
new file mode 100755
index 0000000000..fc2914a4e5
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/dexdump_test.py
@@ -0,0 +1,141 @@
+#! /usr/bin/env vpython3
+# 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.
+
+import unittest
+from xml.etree import ElementTree
+
+from pylib.utils import dexdump
+
+# pylint: disable=protected-access
+
+
+class DexdumpXMLParseTest(unittest.TestCase):
+
+  def testParseRootXmlNode(self):
+    example_xml_string = (
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        ''
+        '')
+
+    actual = dexdump._ParseRootNode(
+        ElementTree.fromstring(example_xml_string))
+
+    expected = {
+      'com.foo.bar1' : {
+        'classes': {
+          'Class1': {
+            'methods': ['class1Method1', 'class1Method2'],
+            'superclass': 'java.lang.Object',
+          },
+          'Class2': {
+            'methods': ['class2Method1'],
+            'superclass': 'java.lang.Object',
+          }
+        },
+      },
+      'com.foo.bar2' : {'classes': {}},
+      'com.foo.bar3' : {'classes': {}},
+    }
+    self.assertEqual(expected, actual)
+
+  def testParsePackageNode(self):
+    example_xml_string = (
+        ''
+        ''
+        ''
+        ''
+        ''
+        '')
+
+
+    actual = dexdump._ParsePackageNode(
+        ElementTree.fromstring(example_xml_string))
+
+    expected = {
+      'classes': {
+        'Class1': {
+          'methods': [],
+          'superclass': 'java.lang.Object',
+        },
+        'Class2': {
+          'methods': [],
+          'superclass': 'java.lang.Object',
+        },
+      },
+    }
+    self.assertEqual(expected, actual)
+
+  def testParseClassNode(self):
+    example_xml_string = (
+        ''
+        ''
+        ''
+        ''
+        ''
+        '')
+
+    actual = dexdump._ParseClassNode(
+        ElementTree.fromstring(example_xml_string))
+
+    expected = {
+      'methods': ['method1', 'method2'],
+      'superclass': 'java.lang.Object',
+    }
+    self.assertEqual(expected, actual)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/gold_utils.py b/third_party/libwebrtc/build/android/pylib/utils/gold_utils.py
new file mode 100644
index 0000000000..0b79a6d7cb
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/gold_utils.py
@@ -0,0 +1,78 @@
+# Copyright 2020 The Chromium 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/android implementations of //testing/skia_gold_common.
+
+Used for interacting with the Skia Gold image diffing service.
+"""
+
+import os
+import shutil
+
+from devil.utils import cmd_helper
+from pylib.base.output_manager import Datatype
+from pylib.constants import host_paths
+from pylib.utils import repo_utils
+
+with host_paths.SysPath(host_paths.BUILD_PATH):
+  from skia_gold_common import skia_gold_session
+  from skia_gold_common import skia_gold_session_manager
+  from skia_gold_common import skia_gold_properties
+
+
+class AndroidSkiaGoldSession(skia_gold_session.SkiaGoldSession):
+  def _StoreDiffLinks(self, image_name, output_manager, output_dir):
+    """See SkiaGoldSession._StoreDiffLinks for general documentation.
+
+    |output_manager| must be a build.android.pylib.base.OutputManager instance.
+    """
+    given_path = closest_path = diff_path = None
+    # The directory should contain "input-.png", "closest-.png",
+    # and "diff.png".
+    for f in os.listdir(output_dir):
+      filepath = os.path.join(output_dir, f)
+      if f.startswith('input-'):
+        given_path = filepath
+      elif f.startswith('closest-'):
+        closest_path = filepath
+      elif f == 'diff.png':
+        diff_path = filepath
+    results = self._comparison_results.setdefault(image_name,
+                                                  self.ComparisonResults())
+    if given_path:
+      with output_manager.ArchivedTempfile('given_%s.png' % image_name,
+                                           'gold_local_diffs',
+                                           Datatype.PNG) as given_file:
+        shutil.move(given_path, given_file.name)
+      results.local_diff_given_image = given_file.Link()
+    if closest_path:
+      with output_manager.ArchivedTempfile('closest_%s.png' % image_name,
+                                           'gold_local_diffs',
+                                           Datatype.PNG) as closest_file:
+        shutil.move(closest_path, closest_file.name)
+      results.local_diff_closest_image = closest_file.Link()
+    if diff_path:
+      with output_manager.ArchivedTempfile('diff_%s.png' % image_name,
+                                           'gold_local_diffs',
+                                           Datatype.PNG) as diff_file:
+        shutil.move(diff_path, diff_file.name)
+      results.local_diff_diff_image = diff_file.Link()
+
+  @staticmethod
+  def _RunCmdForRcAndOutput(cmd):
+    rc, stdout, _ = cmd_helper.GetCmdStatusOutputAndError(cmd,
+                                                          merge_stderr=True)
+    return rc, stdout
+
+
+class AndroidSkiaGoldSessionManager(
+    skia_gold_session_manager.SkiaGoldSessionManager):
+  @staticmethod
+  def GetSessionClass():
+    return AndroidSkiaGoldSession
+
+
+class AndroidSkiaGoldProperties(skia_gold_properties.SkiaGoldProperties):
+  @staticmethod
+  def _GetGitOriginMasterHeadSha1():
+    return repo_utils.GetGitOriginMasterHeadSHA1(host_paths.DIR_SOURCE_ROOT)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/gold_utils_test.py b/third_party/libwebrtc/build/android/pylib/utils/gold_utils_test.py
new file mode 100755
index 0000000000..cc1da043fc
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/gold_utils_test.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env vpython3
+# Copyright 2020 The Chromium 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 gold_utils."""
+
+#pylint: disable=protected-access
+
+import contextlib
+import os
+import tempfile
+import unittest
+
+from pylib.constants import host_paths
+from pylib.utils import gold_utils
+
+with host_paths.SysPath(host_paths.BUILD_PATH):
+  from skia_gold_common import unittest_utils
+
+import mock  # pylint: disable=import-error
+from pyfakefs import fake_filesystem_unittest  # pylint: disable=import-error
+
+createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs
+
+
+def assertArgWith(test, arg_list, arg, value):
+  i = arg_list.index(arg)
+  test.assertEqual(arg_list[i + 1], value)
+
+
+class AndroidSkiaGoldSessionDiffTest(fake_filesystem_unittest.TestCase):
+  def setUp(self):
+    self.setUpPyfakefs()
+    self._working_dir = tempfile.mkdtemp()
+    self._json_keys = tempfile.NamedTemporaryFile(delete=False).name
+
+  @mock.patch.object(gold_utils.AndroidSkiaGoldSession, '_RunCmdForRcAndOutput')
+  def test_commandCommonArgs(self, cmd_mock):
+    cmd_mock.return_value = (None, None)
+    args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False)
+    sgp = gold_utils.AndroidSkiaGoldProperties(args)
+    session = gold_utils.AndroidSkiaGoldSession(self._working_dir,
+                                                sgp,
+                                                self._json_keys,
+                                                'corpus',
+                                                instance='instance')
+    session.Diff('name', 'png_file', None)
+    call_args = cmd_mock.call_args[0][0]
+    self.assertIn('diff', call_args)
+    assertArgWith(self, call_args, '--corpus', 'corpus')
+    # TODO(skbug.com/10610): Remove the -public once we go back to using the
+    # non-public instance, or add a second test for testing that the correct
+    # instance is chosen if we decide to support both depending on what the
+    # user is authenticated for.
+    assertArgWith(self, call_args, '--instance', 'instance-public')
+    assertArgWith(self, call_args, '--input', 'png_file')
+    assertArgWith(self, call_args, '--test', 'name')
+    # TODO(skbug.com/10611): Re-add this assert and remove the check for the
+    # absence of the directory once we switch back to using the proper working
+    # directory.
+    # assertArgWith(self, call_args, '--work-dir', self._working_dir)
+    self.assertNotIn(self._working_dir, call_args)
+    i = call_args.index('--out-dir')
+    # The output directory should be a subdirectory of the working directory.
+    self.assertIn(self._working_dir, call_args[i + 1])
+
+
+class AndroidSkiaGoldSessionDiffLinksTest(fake_filesystem_unittest.TestCase):
+  class FakeArchivedFile(object):
+    def __init__(self, path):
+      self.name = path
+
+    def Link(self):
+      return 'file://' + self.name
+
+  class FakeOutputManager(object):
+    def __init__(self):
+      self.output_dir = tempfile.mkdtemp()
+
+    @contextlib.contextmanager
+    def ArchivedTempfile(self, image_name, _, __):
+      filepath = os.path.join(self.output_dir, image_name)
+      yield AndroidSkiaGoldSessionDiffLinksTest.FakeArchivedFile(filepath)
+
+  def setUp(self):
+    self.setUpPyfakefs()
+    self._working_dir = tempfile.mkdtemp()
+    self._json_keys = tempfile.NamedTemporaryFile(delete=False).name
+
+  def test_outputManagerUsed(self):
+    args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
+    sgp = gold_utils.AndroidSkiaGoldProperties(args)
+    session = gold_utils.AndroidSkiaGoldSession(self._working_dir, sgp,
+                                                self._json_keys, None, None)
+    with open(os.path.join(self._working_dir, 'input-inputhash.png'), 'w') as f:
+      f.write('input')
+    with open(os.path.join(self._working_dir, 'closest-closesthash.png'),
+              'w') as f:
+      f.write('closest')
+    with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
+      f.write('diff')
+
+    output_manager = AndroidSkiaGoldSessionDiffLinksTest.FakeOutputManager()
+    session._StoreDiffLinks('foo', output_manager, self._working_dir)
+
+    copied_input = os.path.join(output_manager.output_dir, 'given_foo.png')
+    copied_closest = os.path.join(output_manager.output_dir, 'closest_foo.png')
+    copied_diff = os.path.join(output_manager.output_dir, 'diff_foo.png')
+    with open(copied_input) as f:
+      self.assertEqual(f.read(), 'input')
+    with open(copied_closest) as f:
+      self.assertEqual(f.read(), 'closest')
+    with open(copied_diff) as f:
+      self.assertEqual(f.read(), 'diff')
+
+    self.assertEqual(session.GetGivenImageLink('foo'), 'file://' + copied_input)
+    self.assertEqual(session.GetClosestImageLink('foo'),
+                     'file://' + copied_closest)
+    self.assertEqual(session.GetDiffImageLink('foo'), 'file://' + copied_diff)
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/google_storage_helper.py b/third_party/libwebrtc/build/android/pylib/utils/google_storage_helper.py
new file mode 100644
index 0000000000..94efe33f85
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/google_storage_helper.py
@@ -0,0 +1,129 @@
+# Copyright 2017 The Chromium 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 upload data to Google Storage.
+
+Text data should be streamed to logdog using |logdog_helper| module.
+Due to logdog not having image or HTML viewer, those instead should be uploaded
+to Google Storage directly using this module.
+"""
+
+import logging
+import os
+import sys
+import time
+try:
+  from urllib.parse import urlparse
+except ImportError:
+  from urlparse import urlparse
+
+from pylib.constants import host_paths
+from pylib.utils import decorators
+
+if host_paths.DEVIL_PATH not in sys.path:
+  sys.path.append(host_paths.DEVIL_PATH)
+from devil.utils import cmd_helper
+
+_GSUTIL_PATH = os.path.join(
+    host_paths.DIR_SOURCE_ROOT, 'third_party', 'catapult',
+    'third_party', 'gsutil', 'gsutil.py')
+_PUBLIC_URL = 'https://storage.googleapis.com/%s/'
+_AUTHENTICATED_URL = 'https://storage.cloud.google.com/%s/'
+
+
+@decorators.NoRaiseException(default_return_value='')
+def upload(name, filepath, bucket, gs_args=None, command_args=None,
+           content_type=None, authenticated_link=True):
+  """Uploads data to Google Storage.
+
+  Args:
+    name: Name of the file on Google Storage.
+    filepath: Path to file you want to upload.
+    bucket: Bucket to upload file to.
+    content_type: Content type to upload as. If not specified, Google storage
+        will attempt to infer content type from file extension.
+    authenticated_link: Whether to return a link that requires user to
+        authenticate with a Google account. Setting this to false will return
+        a link that does not require user to be signed into Google account but
+        will only work for completely public storage buckets.
+  Returns:
+    Web link to item uploaded to Google Storage bucket.
+  """
+  bucket = _format_bucket_name(bucket)
+
+  gs_path = 'gs://%s/%s' % (bucket, name)
+  logging.info('Uploading %s to %s', filepath, gs_path)
+
+  cmd = [_GSUTIL_PATH, '-q']
+  cmd.extend(gs_args or [])
+  if content_type:
+    cmd.extend(['-h', 'Content-Type:%s' % content_type])
+  cmd.extend(['cp'] + (command_args or []) + [filepath, gs_path])
+
+  cmd_helper.RunCmd(cmd)
+
+  return get_url_link(name, bucket, authenticated_link)
+
+
+@decorators.NoRaiseException(default_return_value='')
+def read_from_link(link):
+  # Note that urlparse returns the path with an initial '/', so we only need to
+  # add one more after the 'gs;'
+  gs_path = 'gs:/%s' % urlparse(link).path
+  cmd = [_GSUTIL_PATH, '-q', 'cat', gs_path]
+  return cmd_helper.GetCmdOutput(cmd)
+
+
+@decorators.NoRaiseException(default_return_value=False)
+def exists(name, bucket):
+  bucket = _format_bucket_name(bucket)
+  gs_path = 'gs://%s/%s' % (bucket, name)
+
+  cmd = [_GSUTIL_PATH, '-q', 'stat', gs_path]
+  return_code = cmd_helper.RunCmd(cmd)
+  return return_code == 0
+
+
+# TODO(jbudorick): Delete this function. Only one user of it.
+def unique_name(basename, suffix='', timestamp=True, device=None):
+  """Helper function for creating a unique name for a file to store in GS.
+
+  Args:
+    basename: Base of the unique filename.
+    suffix: Suffix of filename.
+    timestamp: Whether or not to add a timestamp to name.
+    device: Device to add device serial of to name.
+  """
+  return '%s%s%s%s' % (
+      basename,
+      '_%s' % time.strftime('%Y_%m_%d_T%H_%M_%S-UTC', time.gmtime())
+          if timestamp else '',
+      '_%s' % device.serial if device else '',
+      suffix)
+
+
+def get_url_link(name, bucket, authenticated_link=True):
+  """Get url link before/without uploading.
+
+  Args:
+    name: Name of the file on Google Storage.
+    bucket: Bucket to upload file to.
+    authenticated_link: Whether to return a link that requires user to
+        authenticate with a Google account. Setting this to false will return
+        a link that does not require user to be signed into Google account but
+        will only work for completely public storage buckets.
+  Returns:
+    Web link to item to be uploaded to Google Storage bucket
+  """
+  bucket = _format_bucket_name(bucket)
+  url_template = _AUTHENTICATED_URL if authenticated_link else _PUBLIC_URL
+  return os.path.join(url_template % bucket, name)
+
+
+def _format_bucket_name(bucket):
+  if bucket.startswith('gs://'):
+    bucket = bucket[len('gs://'):]
+  if bucket.endswith('/'):
+    bucket = bucket[:-1]
+  return bucket
diff --git a/third_party/libwebrtc/build/android/pylib/utils/instrumentation_tracing.py b/third_party/libwebrtc/build/android/pylib/utils/instrumentation_tracing.py
new file mode 100644
index 0000000000..f1d03a0dcf
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/instrumentation_tracing.py
@@ -0,0 +1,204 @@
+# Copyright 2017 The Chromium 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 to instrument all Python function calls.
+
+This generates a JSON file readable by Chrome's about:tracing. To use it,
+either call start_instrumenting and stop_instrumenting at the appropriate times,
+or use the Instrument context manager.
+
+A function is only traced if it is from a Python module that matches at least
+one regular expression object in to_include, and does not match any in
+to_exclude. In between the start and stop events, every function call of a
+function from such a module will be added to the trace.
+"""
+
+import contextlib
+import functools
+import inspect
+import os
+import re
+import sys
+import threading
+
+from py_trace_event import trace_event
+
+
+# Modules to exclude by default (to avoid problems like infinite loops)
+DEFAULT_EXCLUDE = [r'py_trace_event\..*']
+
+class _TraceArguments(object):
+  def __init__(self):
+    """Wraps a dictionary to ensure safe evaluation of repr()."""
+    self._arguments = {}
+
+  @staticmethod
+  def _safeStringify(item):
+    try:
+      item_str = repr(item)
+    except Exception: # pylint: disable=broad-except
+      try:
+        item_str = str(item)
+      except Exception: # pylint: disable=broad-except
+        item_str = ""
+    return item_str
+
+  def add(self, key, val):
+    key_str = _TraceArguments._safeStringify(key)
+    val_str = _TraceArguments._safeStringify(val)
+
+    self._arguments[key_str] = val_str
+
+  def __repr__(self):
+    return repr(self._arguments)
+
+
+saved_thread_ids = set()
+
+def _shouldTrace(frame, to_include, to_exclude, included, excluded):
+  """
+  Decides whether or not the function called in frame should be traced.
+
+  Args:
+    frame: The Python frame object of this function call.
+    to_include: Set of regex objects for modules which should be traced.
+    to_exclude: Set of regex objects for modules which should not be traced.
+    included: Set of module names we've determined should be traced.
+    excluded: Set of module names we've determined should not be traced.
+  """
+  if not inspect.getmodule(frame):
+    return False
+
+  module_name = inspect.getmodule(frame).__name__
+
+  if module_name in included:
+    includes = True
+  elif to_include:
+    includes = any([pattern.match(module_name) for pattern in to_include])
+  else:
+    includes = True
+
+  if includes:
+    included.add(module_name)
+  else:
+    return False
+
+  # Find the modules of every function in the stack trace.
+  frames = inspect.getouterframes(frame)
+  calling_module_names = [inspect.getmodule(fr[0]).__name__ for fr in frames]
+
+  # Return False for anything with an excluded module's function anywhere in the
+  # stack trace (even if the function itself is in an included module).
+  if to_exclude:
+    for calling_module in calling_module_names:
+      if calling_module in excluded:
+        return False
+      for pattern in to_exclude:
+        if pattern.match(calling_module):
+          excluded.add(calling_module)
+          return False
+
+  return True
+
+def _generate_trace_function(to_include, to_exclude):
+  to_include = {re.compile(item) for item in to_include}
+  to_exclude = {re.compile(item) for item in to_exclude}
+  to_exclude.update({re.compile(item) for item in DEFAULT_EXCLUDE})
+
+  included = set()
+  excluded = set()
+
+  tracing_pid = os.getpid()
+
+  def traceFunction(frame, event, arg):
+    del arg
+
+    # Don't try to trace in subprocesses.
+    if os.getpid() != tracing_pid:
+      sys.settrace(None)
+      return None
+
+    # pylint: disable=unused-argument
+    if event not in ("call", "return"):
+      return None
+
+    function_name = frame.f_code.co_name
+    filename = frame.f_code.co_filename
+    line_number = frame.f_lineno
+
+    if _shouldTrace(frame, to_include, to_exclude, included, excluded):
+      if event == "call":
+        # This function is beginning; we save the thread name (if that hasn't
+        # been done), record the Begin event, and return this function to be
+        # used as the local trace function.
+
+        thread_id = threading.current_thread().ident
+
+        if thread_id not in saved_thread_ids:
+          thread_name = threading.current_thread().name
+
+          trace_event.trace_set_thread_name(thread_name)
+
+          saved_thread_ids.add(thread_id)
+
+        arguments = _TraceArguments()
+        # The function's argument values are stored in the frame's
+        # |co_varnames| as the first |co_argcount| elements. (Following that
+        # are local variables.)
+        for idx in range(frame.f_code.co_argcount):
+          arg_name = frame.f_code.co_varnames[idx]
+          arguments.add(arg_name, frame.f_locals[arg_name])
+        trace_event.trace_begin(function_name, arguments=arguments,
+                                module=inspect.getmodule(frame).__name__,
+                                filename=filename, line_number=line_number)
+
+        # Return this function, so it gets used as the "local trace function"
+        # within this function's frame (and in particular, gets called for this
+        # function's "return" event).
+        return traceFunction
+
+      if event == "return":
+        trace_event.trace_end(function_name)
+        return None
+
+  return traceFunction
+
+
+def no_tracing(f):
+  @functools.wraps(f)
+  def wrapper(*args, **kwargs):
+    trace_func = sys.gettrace()
+    try:
+      sys.settrace(None)
+      threading.settrace(None)
+      return f(*args, **kwargs)
+    finally:
+      sys.settrace(trace_func)
+      threading.settrace(trace_func)
+  return wrapper
+
+
+def start_instrumenting(output_file, to_include=(), to_exclude=()):
+  """Enable tracing of all function calls (from specified modules)."""
+  trace_event.trace_enable(output_file)
+
+  traceFunc = _generate_trace_function(to_include, to_exclude)
+  sys.settrace(traceFunc)
+  threading.settrace(traceFunc)
+
+
+def stop_instrumenting():
+  trace_event.trace_disable()
+
+  sys.settrace(None)
+  threading.settrace(None)
+
+
+@contextlib.contextmanager
+def Instrument(output_file, to_include=(), to_exclude=()):
+  try:
+    start_instrumenting(output_file, to_include, to_exclude)
+    yield None
+  finally:
+    stop_instrumenting()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/local_utils.py b/third_party/libwebrtc/build/android/pylib/utils/local_utils.py
new file mode 100644
index 0000000000..027cca3925
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/local_utils.py
@@ -0,0 +1,19 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Utilities for determining if a test is being run locally or not."""
+
+import os
+
+
+def IsOnSwarming():
+  """Determines whether we are on swarming or not.
+
+  Returns:
+    True if the test is being run on swarming, otherwise False.
+  """
+  # Look for the presence of the SWARMING_SERVER environment variable as a
+  # heuristic to determine whether we're running on a workstation or a bot.
+  # This should always be set on swarming, but would be strange to be set on
+  # a workstation.
+  return 'SWARMING_SERVER' in os.environ
diff --git a/third_party/libwebrtc/build/android/pylib/utils/logdog_helper.py b/third_party/libwebrtc/build/android/pylib/utils/logdog_helper.py
new file mode 100644
index 0000000000..3000a2f7cb
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/logdog_helper.py
@@ -0,0 +1,96 @@
+# Copyright 2017 The Chromium 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 upload data to logdog."""
+
+import logging
+import os
+import sys
+
+from pylib import constants
+from pylib.utils import decorators
+
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'logdog')))
+from logdog import bootstrap  # pylint: disable=import-error
+
+
+@decorators.NoRaiseException(default_return_value='',
+                             exception_message=('Ignore this exception. '
+                                                'crbug.com/675666'))
+def text(name, data, content_type=None):
+  """Uploads text to logdog.
+
+  Args:
+    name: Name of the logdog stream.
+    data: String with data you want to upload.
+    content_type: The optional content type of the stream. If None, a
+      default content type will be chosen.
+
+  Returns:
+    Link to view uploaded text in logdog viewer.
+  """
+  logging.info('Writing text to logdog stream, %s', name)
+  with get_logdog_client().text(name, content_type=content_type) as stream:
+    stream.write(data)
+    return stream.get_viewer_url()
+
+
+@decorators.NoRaiseException(default_return_value=None,
+                             exception_message=('Ignore this exception. '
+                                                'crbug.com/675666'))
+def open_text(name):
+  """Returns a file like object which you can write to.
+
+  Args:
+    name: Name of the logdog stream.
+
+  Returns:
+    A file like object. close() file when done.
+  """
+  logging.info('Opening text logdog stream, %s', name)
+  return get_logdog_client().open_text(name)
+
+
+@decorators.NoRaiseException(default_return_value='',
+                             exception_message=('Ignore this exception. '
+                                                'crbug.com/675666'))
+def binary(name, binary_path):
+  """Uploads binary to logdog.
+
+  Args:
+    name: Name of the logdog stream.
+    binary_path: Path to binary you want to upload.
+
+  Returns:
+    Link to view uploaded binary in logdog viewer.
+  """
+  logging.info('Writing binary to logdog stream, %s', name)
+  with get_logdog_client().binary(name) as stream:
+    with open(binary_path, 'r') as f:
+      stream.write(f.read())
+      return stream.get_viewer_url()
+
+
+@decorators.NoRaiseException(default_return_value='',
+                             exception_message=('Ignore this exception. '
+                                                'crbug.com/675666'))
+def get_viewer_url(name):
+  """Get Logdog viewer URL.
+
+  Args:
+    name: Name of the logdog stream.
+
+  Returns:
+    Link to view uploaded binary in logdog viewer.
+  """
+  return get_logdog_client().get_viewer_url(name)
+
+
+@decorators.Memoize
+def get_logdog_client():
+  logging.info('Getting logdog client.')
+  return bootstrap.ButlerBootstrap.probe().stream_client()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/logging_utils.py b/third_party/libwebrtc/build/android/pylib/utils/logging_utils.py
new file mode 100644
index 0000000000..846d336c2c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/logging_utils.py
@@ -0,0 +1,136 @@
+# 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
+
+from pylib.constants import host_paths
+
+_COLORAMA_PATH = os.path.join(
+    host_paths.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src')
+
+with host_paths.SysPath(_COLORAMA_PATH, position=0):
+  import colorama
+
+BACK = colorama.Back
+FORE = colorama.Fore
+STYLE = colorama.Style
+
+
+class _ColorFormatter(logging.Formatter):
+  # pylint does not see members added dynamically in the constructor.
+  # pylint: disable=no-member
+  color_map = {
+    logging.DEBUG: (FORE.CYAN),
+    logging.WARNING: (FORE.YELLOW),
+    logging.ERROR: (FORE.RED),
+    logging.CRITICAL: (BACK.RED),
+  }
+
+  def __init__(self, wrapped_formatter=None):
+    """Wraps a |logging.Formatter| and adds color."""
+    super(_ColorFormatter, self).__init__(self)
+    self._wrapped_formatter = wrapped_formatter or logging.Formatter()
+
+  #override
+  def format(self, record):
+    message = self._wrapped_formatter.format(record)
+    return self.Colorize(message, record.levelno)
+
+  def Colorize(self, message, log_level):
+    try:
+      return (''.join(self.color_map[log_level]) + message +
+              colorama.Style.RESET_ALL)
+    except KeyError:
+      return message
+
+
+class ColorStreamHandler(logging.StreamHandler):
+  """Handler that can be used to colorize logging output.
+
+  Example using a specific logger:
+
+    logger = logging.getLogger('my_logger')
+    logger.addHandler(ColorStreamHandler())
+    logger.info('message')
+
+  Example using the root logger:
+
+    ColorStreamHandler.MakeDefault()
+    logging.info('message')
+
+  """
+  def __init__(self, force_color=False):
+    super(ColorStreamHandler, self).__init__()
+    self.force_color = force_color
+    self.setFormatter(logging.Formatter())
+
+  @property
+  def is_tty(self):
+    isatty = getattr(self.stream, 'isatty', None)
+    return isatty and isatty()
+
+  #override
+  def setFormatter(self, formatter):
+    if self.force_color or self.is_tty:
+      formatter = _ColorFormatter(formatter)
+    super(ColorStreamHandler, self).setFormatter(formatter)
+
+  @staticmethod
+  def MakeDefault(force_color=False):
+    """
+     Replaces the default logging handlers with a coloring handler. To use
+     a colorizing handler at the same time as others, either register them
+     after this call, or add the ColorStreamHandler on the logger using
+     Logger.addHandler()
+
+     Args:
+       force_color: Set to True to bypass the tty check and always colorize.
+     """
+    # If the existing handlers aren't removed, messages are duplicated
+    logging.getLogger().handlers = []
+    logging.getLogger().addHandler(ColorStreamHandler(force_color))
+
+
+@contextlib.contextmanager
+def OverrideColor(level, color):
+  """Temporarily override the logging color for a specified level.
+
+  Args:
+    level: logging level whose color gets overridden.
+    color: tuple of formats to apply to log lines.
+  """
+  prev_colors = {}
+  for handler in logging.getLogger().handlers:
+    if isinstance(handler.formatter, _ColorFormatter):
+      prev_colors[handler.formatter] = handler.formatter.color_map[level]
+      handler.formatter.color_map[level] = color
+  try:
+    yield
+  finally:
+    for formatter, prev_color in prev_colors.items():
+      formatter.color_map[level] = prev_color
+
+
+@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 disappear.
+
+  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/third_party/libwebrtc/build/android/pylib/utils/maven_downloader.py b/third_party/libwebrtc/build/android/pylib/utils/maven_downloader.py
new file mode 100755
index 0000000000..7247f7c88c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/maven_downloader.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env vpython3
+# Copyright 2017 The Chromium 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 errno
+import logging
+import os
+import shutil
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
+import devil_chromium  # pylint: disable=unused-import
+from devil.utils import cmd_helper
+from devil.utils import parallelizer
+
+
+def _MakeDirsIfAbsent(path):
+  try:
+    os.makedirs(path)
+  except OSError as err:
+    if err.errno != errno.EEXIST or not os.path.isdir(path):
+      raise
+
+
+class MavenDownloader(object):
+  '''
+  Downloads and installs the requested artifacts from the Google Maven repo.
+  The artifacts are expected to be specified in the format
+  "group_id:artifact_id:version:file_type", as the default file type is JAR
+  but most Android libraries are provided as AARs, which would otherwise fail
+  downloading. See Install()
+  '''
+
+  # Remote repository to download the artifacts from. The support library and
+  # Google Play service are only distributed there, but third party libraries
+  # could use Maven Central or JCenter for example. The default Maven remote
+  # is Maven Central.
+  _REMOTE_REPO = 'https://maven.google.com'
+
+  # Default Maven repository.
+  _DEFAULT_REPO_PATH = os.path.join(
+      os.path.expanduser('~'), '.m2', 'repository')
+
+  def __init__(self, debug=False):
+    self._repo_path = MavenDownloader._DEFAULT_REPO_PATH
+    self._remote_url = MavenDownloader._REMOTE_REPO
+    self._debug = debug
+
+  def Install(self, target_repo, artifacts, include_poms=False):
+    logging.info('Installing %d artifacts...', len(artifacts))
+    downloaders = [_SingleArtifactDownloader(self, artifact, target_repo)
+                   for artifact in artifacts]
+    if self._debug:
+      for downloader in downloaders:
+        downloader.Run(include_poms)
+    else:
+      parallelizer.SyncParallelizer(downloaders).Run(include_poms)
+    logging.info('%d artifacts installed to %s', len(artifacts), target_repo)
+
+  @property
+  def repo_path(self):
+    return self._repo_path
+
+  @property
+  def remote_url(self):
+    return self._remote_url
+
+  @property
+  def debug(self):
+    return self._debug
+
+
+class _SingleArtifactDownloader(object):
+  '''Handles downloading and installing a single Maven artifact.'''
+
+  _POM_FILE_TYPE = 'pom'
+
+  def __init__(self, download_manager, artifact, target_repo):
+    self._download_manager = download_manager
+    self._artifact = artifact
+    self._target_repo = target_repo
+
+  def Run(self, include_pom=False):
+    parts = self._artifact.split(':')
+    if len(parts) != 4:
+      raise Exception('Artifacts expected as '
+                      '"group_id:artifact_id:version:file_type".')
+    group_id, artifact_id, version, file_type = parts
+    self._InstallArtifact(group_id, artifact_id, version, file_type)
+
+    if include_pom and file_type != _SingleArtifactDownloader._POM_FILE_TYPE:
+      self._InstallArtifact(group_id, artifact_id, version,
+                            _SingleArtifactDownloader._POM_FILE_TYPE)
+
+  def _InstallArtifact(self, group_id, artifact_id, version, file_type):
+    logging.debug('Processing %s', self._artifact)
+
+    download_relpath = self._DownloadArtifact(
+        group_id, artifact_id, version, file_type)
+    logging.debug('Downloaded.')
+
+    install_path = self._ImportArtifact(download_relpath)
+    logging.debug('Installed %s', os.path.relpath(install_path))
+
+  def _DownloadArtifact(self, group_id, artifact_id, version, file_type):
+    '''
+    Downloads the specified artifact using maven, to its standard location, see
+    MavenDownloader._DEFAULT_REPO_PATH.
+    '''
+    cmd = ['mvn',
+           'org.apache.maven.plugins:maven-dependency-plugin:RELEASE:get',
+           '-DremoteRepositories={}'.format(self._download_manager.remote_url),
+           '-Dartifact={}:{}:{}:{}'.format(group_id, artifact_id, version,
+                                           file_type)]
+
+    stdout = None if self._download_manager.debug else open(os.devnull, 'wb')
+
+    try:
+      ret_code = cmd_helper.Call(cmd, stdout=stdout)
+      if ret_code != 0:
+        raise Exception('Command "{}" failed'.format(' '.join(cmd)))
+    except OSError as e:
+      if e.errno == os.errno.ENOENT:
+        raise Exception('mvn command not found. Please install Maven.')
+      raise
+
+    return os.path.join(os.path.join(*group_id.split('.')),
+                        artifact_id,
+                        version,
+                        '{}-{}.{}'.format(artifact_id, version, file_type))
+
+  def _ImportArtifact(self, artifact_path):
+    src_dir = os.path.join(self._download_manager.repo_path, artifact_path)
+    dst_dir = os.path.join(self._target_repo, os.path.dirname(artifact_path))
+
+    _MakeDirsIfAbsent(dst_dir)
+    shutil.copy(src_dir, dst_dir)
+
+    return dst_dir
diff --git a/third_party/libwebrtc/build/android/pylib/utils/proguard.py b/third_party/libwebrtc/build/android/pylib/utils/proguard.py
new file mode 100644
index 0000000000..9d5bae285a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/proguard.py
@@ -0,0 +1,285 @@
+# 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 devil.utils import cmd_helper
+from pylib import constants
+
+
+_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*);\]:$')
+_ELEMENT_PRIMITIVE = 0
+_ELEMENT_ARRAY = 1
+_ELEMENT_ANNOTATION = 2
+_PROGUARD_ELEMENT_RES = [
+  (_ELEMENT_PRIMITIVE,
+   re.compile(r'^(\s*?)- Constant element value \[(\S*) .*\]$')),
+  (_ELEMENT_ARRAY,
+   re.compile(r'^(\s*?)- Array element value \[(\S*)\]:$')),
+  (_ELEMENT_ANNOTATION,
+   re.compile(r'^(\s*?)- Annotation element value \[(\S*)\]:$'))
+]
+_PROGUARD_INDENT_WIDTH = 2
+_PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'^(\s*?)- \S+? \[(.*)\]$')
+
+
+def _GetProguardPath():
+  return os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'proguard',
+                      'lib', 'proguard603.jar')
+
+
+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': {/* dict -- see below */},
+            'methods': [
+              {
+                'method': '',
+                'annotations': {/* dict -- see below */},
+              },
+              ...
+            ],
+          },
+          ...
+        ],
+      }
+
+    Annotations dict format:
+      {
+        'empty-annotation-class-name': None,
+        'annotation-class-name': {
+          'field': 'primitive-value',
+          'field': [ 'array-item-1', 'array-item-2', ... ],
+          'field': {
+            /* Object value */
+            'field': 'primitive-value',
+            'field': [ 'array-item-1', 'array-item-2', ... ],
+            'field': { /* Object value */ }
+          }
+        }
+      }
+
+    Note that for top-level annotations their class names are used for
+    identification, whereas for any nested annotations the corresponding
+    field names are used.
+
+    One drawback of this approach is that an array containing empty
+    annotation classes will be represented as an array of 'None' values,
+    thus it will not be possible to find out annotation class names.
+    On the other hand, storing both annotation class name and the field name
+    would produce a very complex JSON.
+  """
+
+  with tempfile.NamedTemporaryFile() as proguard_output:
+    cmd_helper.GetCmdStatusAndOutput([
+        'java',
+        '-jar', _GetProguardPath(),
+        '-injars', jar_path,
+        '-dontshrink', '-dontoptimize', '-dontobfuscate', '-dontpreverify',
+        '-dump', proguard_output.name])
+    return Parse(proguard_output)
+
+class _AnnotationElement(object):
+  def __init__(self, name, ftype, depth):
+    self.ref = None
+    self.name = name
+    self.ftype = ftype
+    self.depth = depth
+
+class _ParseState(object):
+  _INITIAL_VALUES = (lambda: None, list, dict)
+  # Empty annotations are represented as 'None', not as an empty dictionary.
+  _LAZY_INITIAL_VALUES = (lambda: None, list, lambda: None)
+
+  def __init__(self):
+    self._class_result = None
+    self._method_result = None
+    self._parse_annotations = False
+    self._annotation_stack = []
+
+  def ResetPerSection(self, section_name):
+    self.InitMethod(None)
+    self._parse_annotations = (
+      section_name in ['Class file attributes', 'Methods'])
+
+  def ParseAnnotations(self):
+    return self._parse_annotations
+
+  def CreateAndInitClass(self, class_name):
+    self.InitMethod(None)
+    self._class_result = {
+      'class': class_name,
+      'superclass': '',
+      'annotations': {},
+      'methods': [],
+    }
+    return self._class_result
+
+  def HasCurrentClass(self):
+    return bool(self._class_result)
+
+  def SetSuperClass(self, superclass):
+    assert self.HasCurrentClass()
+    self._class_result['superclass'] = superclass
+
+  def InitMethod(self, method_name):
+    self._annotation_stack = []
+    if method_name:
+      self._method_result = {
+        'method': method_name,
+        'annotations': {},
+      }
+      self._class_result['methods'].append(self._method_result)
+    else:
+      self._method_result = None
+
+  def InitAnnotation(self, annotation, depth):
+    if not self._annotation_stack:
+      # Add a fake parent element comprising 'annotations' dictionary,
+      # so we can work uniformly with both top-level and nested annotations.
+      annotations = _AnnotationElement(
+        '<<>>', _ELEMENT_ANNOTATION, depth - 1)
+      if self._method_result:
+        annotations.ref = self._method_result['annotations']
+      else:
+        annotations.ref = self._class_result['annotations']
+      self._annotation_stack = [annotations]
+    self._BacktrackAnnotationStack(depth)
+    if not self.HasCurrentAnnotation():
+      self._annotation_stack.append(
+        _AnnotationElement(annotation, _ELEMENT_ANNOTATION, depth))
+    self._CreateAnnotationPlaceHolder(self._LAZY_INITIAL_VALUES)
+
+  def HasCurrentAnnotation(self):
+    return len(self._annotation_stack) > 1
+
+  def InitAnnotationField(self, field, field_type, depth):
+    self._BacktrackAnnotationStack(depth)
+    # Create the parent representation, if needed. E.g. annotations
+    # are represented with `None`, not with `{}` until they receive the first
+    # field.
+    self._CreateAnnotationPlaceHolder(self._INITIAL_VALUES)
+    if self._annotation_stack[-1].ftype == _ELEMENT_ARRAY:
+      # Nested arrays are not allowed in annotations.
+      assert not field_type == _ELEMENT_ARRAY
+      # Use array index instead of bogus field name.
+      field = len(self._annotation_stack[-1].ref)
+    self._annotation_stack.append(_AnnotationElement(field, field_type, depth))
+    self._CreateAnnotationPlaceHolder(self._LAZY_INITIAL_VALUES)
+
+  def UpdateCurrentAnnotationFieldValue(self, value, depth):
+    self._BacktrackAnnotationStack(depth)
+    self._InitOrUpdateCurrentField(value)
+
+  def _CreateAnnotationPlaceHolder(self, constructors):
+    assert self.HasCurrentAnnotation()
+    field = self._annotation_stack[-1]
+    if field.ref is None:
+      field.ref = constructors[field.ftype]()
+      self._InitOrUpdateCurrentField(field.ref)
+
+  def _BacktrackAnnotationStack(self, depth):
+    stack = self._annotation_stack
+    while len(stack) > 0 and stack[-1].depth >= depth:
+      stack.pop()
+
+  def _InitOrUpdateCurrentField(self, value):
+    assert self.HasCurrentAnnotation()
+    parent = self._annotation_stack[-2]
+    assert not parent.ref is None
+    # There can be no nested constant element values.
+    assert parent.ftype in [_ELEMENT_ARRAY, _ELEMENT_ANNOTATION]
+    field = self._annotation_stack[-1]
+    if isinstance(value, str) and not field.ftype == _ELEMENT_PRIMITIVE:
+      # The value comes from the output parser via
+      # UpdateCurrentAnnotationFieldValue, and should be a value of a constant
+      # element. If it isn't, just skip it.
+      return
+    if parent.ftype == _ELEMENT_ARRAY and field.name >= len(parent.ref):
+      parent.ref.append(value)
+    else:
+      parent.ref[field.name] = value
+
+
+def _GetDepth(prefix):
+  return len(prefix) // _PROGUARD_INDENT_WIDTH
+
+def Parse(proguard_output):
+  results = {
+    'classes': [],
+  }
+
+  state = _ParseState()
+
+  for line in proguard_output:
+    line = line.strip('\r\n')
+
+    m = _PROGUARD_CLASS_RE.match(line)
+    if m:
+      results['classes'].append(
+        state.CreateAndInitClass(m.group(1).replace('/', '.')))
+      continue
+
+    if not state.HasCurrentClass():
+      continue
+
+    m = _PROGUARD_SUPERCLASS_RE.match(line)
+    if m:
+      state.SetSuperClass(m.group(1).replace('/', '.'))
+      continue
+
+    m = _PROGUARD_SECTION_RE.match(line)
+    if m:
+      state.ResetPerSection(m.group(1))
+      continue
+
+    m = _PROGUARD_METHOD_RE.match(line)
+    if m:
+      state.InitMethod(m.group(1))
+      continue
+
+    if not state.ParseAnnotations():
+      continue
+
+    m = _PROGUARD_ANNOTATION_RE.match(line)
+    if m:
+      # Ignore the annotation package.
+      state.InitAnnotation(m.group(2).split('/')[-1], _GetDepth(m.group(1)))
+      continue
+
+    if state.HasCurrentAnnotation():
+      m = None
+      for (element_type, element_re) in _PROGUARD_ELEMENT_RES:
+        m = element_re.match(line)
+        if m:
+          state.InitAnnotationField(
+            m.group(2), element_type, _GetDepth(m.group(1)))
+          break
+      if m:
+        continue
+      m = _PROGUARD_ANNOTATION_VALUE_RE.match(line)
+      if m:
+        state.UpdateCurrentAnnotationFieldValue(
+          m.group(2), _GetDepth(m.group(1)))
+      else:
+        state.InitMethod(None)
+
+  return results
diff --git a/third_party/libwebrtc/build/android/pylib/utils/proguard_test.py b/third_party/libwebrtc/build/android/pylib/utils/proguard_test.py
new file mode 100755
index 0000000000..775bbbac35
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/proguard_test.py
@@ -0,0 +1,495 @@
+#! /usr/bin/env vpython3
+# 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.utils import proguard
+
+class TestParse(unittest.TestCase):
+
+  def setUp(self):
+    self.maxDiff = None
+
+  def testClass(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       '  Superclass: java/lang/Object'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': 'java.lang.Object',
+          'annotations': {},
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testMethod(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       ()V'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': '',
+              'annotations': {}
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testClassAnnotation(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Class file attributes (count = 3):',
+       '  - Annotation [Lorg/example/Annotation;]:',
+       '  - Annotation [Lorg/example/AnnotationWithValue;]:',
+       '    - Constant element value [attr \'13\']',
+       '      - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationWithTwoValues;]:',
+       '    - Constant element value [attr1 \'13\']',
+       '      - Utf8 [val1]',
+       '    - Constant element value [attr2 \'13\']',
+       '      - Utf8 [val2]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {
+            'Annotation': None,
+            'AnnotationWithValue': {'attr': 'val'},
+            'AnnotationWithTwoValues': {'attr1': 'val1', 'attr2': 'val2'}
+          },
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testClassAnnotationWithArrays(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Class file attributes (count = 3):',
+       '  - Annotation [Lorg/example/AnnotationWithEmptyArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '  - Annotation [Lorg/example/AnnotationWithOneElemArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationWithTwoElemArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val1]',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val2]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {
+            'AnnotationWithEmptyArray': {'arrayAttr': []},
+            'AnnotationWithOneElemArray': {'arrayAttr': ['val']},
+            'AnnotationWithTwoElemArray': {'arrayAttr': ['val1', 'val2']}
+          },
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testNestedClassAnnotations(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Class file attributes (count = 1):',
+       '  - Annotation [Lorg/example/OuterAnnotation;]:',
+       '    - Constant element value [outerAttr \'13\']',
+       '      - Utf8 [outerVal]',
+       '    - Array element value [outerArr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [outerArrVal1]',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [outerArrVal2]',
+       '    - Annotation element value [emptyAnn]:',
+       '      - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '    - Annotation element value [ann]:',
+       '      - Annotation [Lorg/example/InnerAnnotation;]:',
+       '        - Constant element value [innerAttr \'13\']',
+       '          - Utf8 [innerVal]',
+       '        - Array element value [innerArr]:',
+       '          - Constant element value [(default) \'13\']',
+       '            - Utf8 [innerArrVal1]',
+       '          - Constant element value [(default) \'13\']',
+       '            - Utf8 [innerArrVal2]',
+       '        - Annotation element value [emptyInnerAnn]:',
+       '          - Annotation [Lorg/example/EmptyAnnotation;]:'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {
+            'OuterAnnotation': {
+              'outerAttr': 'outerVal',
+              'outerArr': ['outerArrVal1', 'outerArrVal2'],
+              'emptyAnn': None,
+              'ann': {
+                'innerAttr': 'innerVal',
+                'innerArr': ['innerArrVal1', 'innerArrVal2'],
+                'emptyInnerAnn': None
+              }
+            }
+          },
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testClassArraysOfAnnotations(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Class file attributes (count = 1):',
+       '   - Annotation [Lorg/example/OuterAnnotation;]:',
+       '     - Array element value [arrayWithEmptyAnnotations]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '     - Array element value [outerArray]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/InnerAnnotation;]:',
+       '           - Constant element value [innerAttr \'115\']',
+       '             - Utf8 [innerVal]',
+       '           - Array element value [arguments]:',
+       '             - Annotation element value [(default)]:',
+       '               - Annotation [Lorg/example/InnerAnnotation$Argument;]:',
+       '                 - Constant element value [arg1Attr \'115\']',
+       '                   - Utf8 [arg1Val]',
+       '                 - Array element value [arg1Array]:',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [11]',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [12]',
+       '             - Annotation element value [(default)]:',
+       '               - Annotation [Lorg/example/InnerAnnotation$Argument;]:',
+       '                 - Constant element value [arg2Attr \'115\']',
+       '                   - Utf8 [arg2Val]',
+       '                 - Array element value [arg2Array]:',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [21]',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [22]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {
+            'OuterAnnotation': {
+              'arrayWithEmptyAnnotations': [None, None],
+              'outerArray': [
+                {
+                  'innerAttr': 'innerVal',
+                  'arguments': [
+                    {'arg1Attr': 'arg1Val', 'arg1Array': ['11', '12']},
+                    {'arg2Attr': 'arg2Val', 'arg2Array': ['21', '22']}
+                  ]
+                }
+              ]
+            }
+          },
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testReadFullClassFileAttributes(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Class file attributes (count = 3):',
+       '  - Source file attribute:',
+       '    - Utf8 [Class.java]',
+       '  - Runtime visible annotations attribute:',
+       '    - Annotation [Lorg/example/IntValueAnnotation;]:',
+       '      - Constant element value [value \'73\']',
+       '        - Integer [19]',
+       '  - Inner classes attribute (count = 1)',
+       '    - InnerClassesInfo:',
+       '      Access flags:  0x9 = public static',
+       '      - Class [org/example/Class1]',
+       '      - Class [org/example/Class2]',
+       '      - Utf8 [OnPageFinishedHelper]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {
+            'IntValueAnnotation': {
+              'value': '19',
+            }
+          },
+          'methods': []
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testMethodAnnotation(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       Test()V',
+       '  - Annotation [Lorg/example/Annotation;]:',
+       '  - Annotation [Lorg/example/AnnotationWithValue;]:',
+       '    - Constant element value [attr \'13\']',
+       '      - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationWithTwoValues;]:',
+       '    - Constant element value [attr1 \'13\']',
+       '      - Utf8 [val1]',
+       '    - Constant element value [attr2 \'13\']',
+       '      - Utf8 [val2]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': 'Test',
+              'annotations': {
+                'Annotation': None,
+                'AnnotationWithValue': {'attr': 'val'},
+                'AnnotationWithTwoValues': {'attr1': 'val1', 'attr2': 'val2'}
+              },
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testMethodAnnotationWithArrays(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       Test()V',
+       '  - Annotation [Lorg/example/AnnotationWithEmptyArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '  - Annotation [Lorg/example/AnnotationWithOneElemArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationWithTwoElemArray;]:',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val1]',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val2]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': 'Test',
+              'annotations': {
+                'AnnotationWithEmptyArray': {'arrayAttr': []},
+                'AnnotationWithOneElemArray': {'arrayAttr': ['val']},
+                'AnnotationWithTwoElemArray': {'arrayAttr': ['val1', 'val2']}
+              },
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testMethodAnnotationWithPrimitivesAndArrays(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       Test()V',
+       '  - Annotation [Lorg/example/AnnotationPrimitiveThenArray;]:',
+       '    - Constant element value [attr \'13\']',
+       '      - Utf8 [val]',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationArrayThenPrimitive;]:',
+       '    - Array element value [arrayAttr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val]',
+       '    - Constant element value [attr \'13\']',
+       '      - Utf8 [val]',
+       '  - Annotation [Lorg/example/AnnotationTwoArrays;]:',
+       '    - Array element value [arrayAttr1]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val1]',
+       '    - Array element value [arrayAttr2]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [val2]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': 'Test',
+              'annotations': {
+                'AnnotationPrimitiveThenArray': {'attr': 'val',
+                                                 'arrayAttr': ['val']},
+                'AnnotationArrayThenPrimitive': {'arrayAttr': ['val'],
+                                                 'attr': 'val'},
+                'AnnotationTwoArrays': {'arrayAttr1': ['val1'],
+                                        'arrayAttr2': ['val2']}
+              },
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testNestedMethodAnnotations(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       Test()V',
+       '  - Annotation [Lorg/example/OuterAnnotation;]:',
+       '    - Constant element value [outerAttr \'13\']',
+       '      - Utf8 [outerVal]',
+       '    - Array element value [outerArr]:',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [outerArrVal1]',
+       '      - Constant element value [(default) \'13\']',
+       '        - Utf8 [outerArrVal2]',
+       '    - Annotation element value [emptyAnn]:',
+       '      - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '    - Annotation element value [ann]:',
+       '      - Annotation [Lorg/example/InnerAnnotation;]:',
+       '        - Constant element value [innerAttr \'13\']',
+       '          - Utf8 [innerVal]',
+       '        - Array element value [innerArr]:',
+       '          - Constant element value [(default) \'13\']',
+       '            - Utf8 [innerArrVal1]',
+       '          - Constant element value [(default) \'13\']',
+       '            - Utf8 [innerArrVal2]',
+       '        - Annotation element value [emptyInnerAnn]:',
+       '          - Annotation [Lorg/example/EmptyAnnotation;]:'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': 'Test',
+              'annotations': {
+                'OuterAnnotation': {
+                  'outerAttr': 'outerVal',
+                  'outerArr': ['outerArrVal1', 'outerArrVal2'],
+                  'emptyAnn': None,
+                  'ann': {
+                    'innerAttr': 'innerVal',
+                    'innerArr': ['innerArrVal1', 'innerArrVal2'],
+                    'emptyInnerAnn': None
+                  }
+                }
+              },
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+  def testMethodArraysOfAnnotations(self):
+    actual = proguard.Parse(
+      ['- Program class: org/example/Test',
+       'Methods (count = 1):',
+       '- Method:       Test()V',
+       '   - Annotation [Lorg/example/OuterAnnotation;]:',
+       '     - Array element value [arrayWithEmptyAnnotations]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/EmptyAnnotation;]:',
+       '     - Array element value [outerArray]:',
+       '       - Annotation element value [(default)]:',
+       '         - Annotation [Lorg/example/InnerAnnotation;]:',
+       '           - Constant element value [innerAttr \'115\']',
+       '             - Utf8 [innerVal]',
+       '           - Array element value [arguments]:',
+       '             - Annotation element value [(default)]:',
+       '               - Annotation [Lorg/example/InnerAnnotation$Argument;]:',
+       '                 - Constant element value [arg1Attr \'115\']',
+       '                   - Utf8 [arg1Val]',
+       '                 - Array element value [arg1Array]:',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [11]',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [12]',
+       '             - Annotation element value [(default)]:',
+       '               - Annotation [Lorg/example/InnerAnnotation$Argument;]:',
+       '                 - Constant element value [arg2Attr \'115\']',
+       '                   - Utf8 [arg2Val]',
+       '                 - Array element value [arg2Array]:',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [21]',
+       '                   - Constant element value [(default) \'73\']',
+       '                     - Integer [22]'])
+    expected = {
+      'classes': [
+        {
+          'class': 'org.example.Test',
+          'superclass': '',
+          'annotations': {},
+          'methods': [
+            {
+              'method': 'Test',
+              'annotations': {
+                'OuterAnnotation': {
+                  'arrayWithEmptyAnnotations': [None, None],
+                  'outerArray': [
+                    {
+                      'innerAttr': 'innerVal',
+                      'arguments': [
+                        {'arg1Attr': 'arg1Val', 'arg1Array': ['11', '12']},
+                        {'arg2Attr': 'arg2Val', 'arg2Array': ['21', '22']}
+                      ]
+                    }
+                  ]
+                }
+              }
+            }
+          ]
+        }
+      ]
+    }
+    self.assertEqual(expected, actual)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/repo_utils.py b/third_party/libwebrtc/build/android/pylib/utils/repo_utils.py
new file mode 100644
index 0000000000..f9d300a214
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/repo_utils.py
@@ -0,0 +1,22 @@
+# 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 devil.utils 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]
+
+
+def GetGitOriginMasterHeadSHA1(in_directory):
+  command_line = ['git', 'rev-parse', 'origin/master']
+  output = cmd_helper.GetCmdOutput(command_line, cwd=in_directory)
+  return output.strip()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/shared_preference_utils.py b/third_party/libwebrtc/build/android/pylib/utils/shared_preference_utils.py
new file mode 100644
index 0000000000..64c4c3f919
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/shared_preference_utils.py
@@ -0,0 +1,116 @@
+# Copyright 2017 The Chromium 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 functions for modifying an app's settings file using JSON."""
+
+import json
+import logging
+
+
+def UnicodeToStr(data):
+  """Recursively converts any Unicode to Python strings.
+
+  Args:
+    data: The data to be converted.
+
+  Return:
+    A copy of the given data, but with instances of Unicode converted to Python
+    strings.
+  """
+  if isinstance(data, dict):
+    return {
+        UnicodeToStr(key): UnicodeToStr(value)
+        for key, value in data.items()
+    }
+  elif isinstance(data, list):
+    return [UnicodeToStr(element) for element in data]
+  try:
+    # Python-2 compatibility.
+    if isinstance(data, unicode):
+      return data.encode('utf-8')
+  except NameError:
+    # Strings are already unicode in python3.
+    pass
+  return data
+
+
+def ExtractSettingsFromJson(filepath):
+  """Extracts the settings data from the given JSON file.
+
+  Args:
+    filepath: The path to the JSON file to read.
+
+  Return:
+    The data read from the JSON file with strings converted to Python strings.
+  """
+  # json.load() loads strings as unicode, which causes issues when trying
+  # to edit string values in preference files, so convert to Python strings
+  with open(filepath) as prefs_file:
+    return UnicodeToStr(json.load(prefs_file))
+
+
+def ApplySharedPreferenceSetting(shared_pref, setting):
+  """Applies the given app settings to the given device.
+
+  Modifies an installed app's settings by modifying its shared preference
+  settings file. Provided settings data must be a settings dictionary,
+  which are in the following format:
+  {
+    "package": "com.example.package",
+    "filename": "AppSettingsFile.xml",
+    "supports_encrypted_path": true,
+    "set": {
+      "SomeBoolToSet": true,
+      "SomeStringToSet": "StringValue",
+    },
+    "remove": [
+      "list",
+      "of",
+      "keys",
+      "to",
+      "remove",
+    ]
+  }
+
+  Example JSON files that can be read with ExtractSettingsFromJson and passed to
+  this function are in //chrome/android/shared_preference_files/test/.
+
+  Args:
+    shared_pref: The devil SharedPrefs object for the device the settings will
+        be applied to.
+    setting: A settings dictionary to apply.
+  """
+  shared_pref.Load()
+  for key in setting.get('remove', []):
+    try:
+      shared_pref.Remove(key)
+    except KeyError:
+      logging.warning("Attempted to remove non-existent key %s", key)
+  for key, value in setting.get('set', {}).items():
+    is_set = False
+    if not is_set and isinstance(value, bool):
+      shared_pref.SetBoolean(key, value)
+      is_set = True
+    try:
+      # Python-2 compatibility.
+      if not is_set and isinstance(value, basestring):
+        shared_pref.SetString(key, value)
+        is_set = True
+      if not is_set and (isinstance(value, long) or isinstance(value, int)):
+        shared_pref.SetLong(key, value)
+        is_set = True
+    except NameError:
+      if not is_set and isinstance(value, str):
+        shared_pref.SetString(key, value)
+        is_set = True
+      if not is_set and isinstance(value, int):
+        shared_pref.SetLong(key, value)
+        is_set = True
+    if not is_set and isinstance(value, list):
+      shared_pref.SetStringSet(key, value)
+      is_set = True
+    if not is_set:
+      raise ValueError("Given invalid value type %s for key %s" % (
+          str(type(value)), key))
+  shared_pref.Commit()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py b/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py
new file mode 100644
index 0000000000..b3ba00e6c2
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py
@@ -0,0 +1,260 @@
+# Copyright 2018 The Chromium 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 os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+from devil import devil_env
+from devil.android import device_signal
+from devil.android.sdk import version_codes
+from pylib import constants
+
+
+def _ProcessType(proc):
+  _, _, suffix = proc.name.partition(':')
+  if not suffix:
+    return 'browser'
+  if suffix.startswith('sandboxed_process'):
+    return 'renderer'
+  if suffix.startswith('privileged_process'):
+    return 'gpu'
+  return None
+
+
+def _GetSpecifiedPID(device, package_name, process_specifier):
+  if process_specifier is None:
+    return None
+
+  # Check for numeric PID
+  try:
+    pid = int(process_specifier)
+    return pid
+  except ValueError:
+    pass
+
+  # Check for exact process name; can be any of these formats:
+  #   :, i.e. 'org.chromium.chrome:sandboxed_process0'
+  #   :, i.e. ':sandboxed_process0'
+  #   , i.e. 'sandboxed_process0'
+  full_process_name = process_specifier
+  if process_specifier.startswith(':'):
+    full_process_name = package_name + process_specifier
+  elif ':' not in process_specifier:
+    full_process_name = '%s:%s' % (package_name, process_specifier)
+  matching_processes = device.ListProcesses(full_process_name)
+  if len(matching_processes) == 1:
+    return matching_processes[0].pid
+  if len(matching_processes) > 1:
+    raise RuntimeError('Found %d processes with name "%s".' % (
+        len(matching_processes), process_specifier))
+
+  # Check for process type (i.e. 'renderer')
+  package_processes = device.ListProcesses(package_name)
+  matching_processes = [p for p in package_processes if (
+      _ProcessType(p) == process_specifier)]
+  if process_specifier == 'renderer' and len(matching_processes) > 1:
+    raise RuntimeError('Found %d renderer processes; please re-run with only '
+                       'one open tab.' % len(matching_processes))
+  if len(matching_processes) != 1:
+    raise RuntimeError('Found %d processes of type "%s".' % (
+        len(matching_processes), process_specifier))
+  return matching_processes[0].pid
+
+
+def _ThreadsForProcess(device, pid):
+  # The thread list output format for 'ps' is the same regardless of version.
+  # Here's the column headers, and a sample line for a thread belonging to
+  # pid 12345 (note that the last few columns are not aligned with headers):
+  #
+  # USER        PID   TID  PPID     VSZ    RSS WCHAN            ADDR S CMD
+  # u0_i101   12345 24680   567 1357902  97531 futex_wait_queue_me e85acd9c S \
+  #     CrRendererMain
+  if device.build_version_sdk >= version_codes.OREO:
+    pid_regex = (
+        r'^[[:graph:]]\{1,\}[[:blank:]]\{1,\}%d[[:blank:]]\{1,\}' % pid)
+    ps_cmd = "ps -T -e | grep '%s'" % pid_regex
+    ps_output_lines = device.RunShellCommand(
+        ps_cmd, shell=True, check_return=True)
+  else:
+    ps_cmd = ['ps', '-p', str(pid), '-t']
+    ps_output_lines = device.RunShellCommand(ps_cmd, check_return=True)
+  result = []
+  for l in ps_output_lines:
+    fields = l.split()
+    # fields[2] is tid, fields[-1] is thread name. Output may include an entry
+    # for the process itself with tid=pid; omit that one.
+    if fields[2] == str(pid):
+      continue
+    result.append((int(fields[2]), fields[-1]))
+  return result
+
+
+def _ThreadType(thread_name):
+  if not thread_name:
+    return 'unknown'
+  if (thread_name.startswith('Chrome_ChildIO') or
+      thread_name.startswith('Chrome_IO')):
+    return 'io'
+  if thread_name.startswith('Compositor'):
+    return 'compositor'
+  if (thread_name.startswith('ChildProcessMai') or
+      thread_name.startswith('CrGpuMain') or
+      thread_name.startswith('CrRendererMain')):
+    return 'main'
+  if thread_name.startswith('RenderThread'):
+    return 'render'
+
+
+def _GetSpecifiedTID(device, pid, thread_specifier):
+  if thread_specifier is None:
+    return None
+
+  # Check for numeric TID
+  try:
+    tid = int(thread_specifier)
+    return tid
+  except ValueError:
+    pass
+
+  # Check for thread type
+  if pid is not None:
+    matching_threads = [t for t in _ThreadsForProcess(device, pid) if (
+        _ThreadType(t[1]) == thread_specifier)]
+    if len(matching_threads) != 1:
+      raise RuntimeError('Found %d threads of type "%s".' % (
+          len(matching_threads), thread_specifier))
+    return matching_threads[0][0]
+
+  return None
+
+
+def PrepareDevice(device):
+  if device.build_version_sdk < version_codes.NOUGAT:
+    raise RuntimeError('Simpleperf profiling is only supported on Android N '
+                       'and later.')
+
+  # Necessary for profiling
+  # https://android-review.googlesource.com/c/platform/system/sepolicy/+/234400
+  device.SetProp('security.perf_harden', '0')
+
+
+def InstallSimpleperf(device, package_name):
+  package_arch = device.GetPackageArchitecture(package_name) or 'armeabi-v7a'
+  host_simpleperf_path = devil_env.config.LocalPath('simpleperf', package_arch)
+  if not host_simpleperf_path:
+    raise Exception('Could not get path to simpleperf executable on host.')
+  device_simpleperf_path = '/'.join(
+      ('/data/local/tmp/profilers', package_arch, 'simpleperf'))
+  device.PushChangedFiles([(host_simpleperf_path, device_simpleperf_path)])
+  return device_simpleperf_path
+
+
+@contextlib.contextmanager
+def RunSimpleperf(device, device_simpleperf_path, package_name,
+                  process_specifier, thread_specifier, profiler_args,
+                  host_out_path):
+  pid = _GetSpecifiedPID(device, package_name, process_specifier)
+  tid = _GetSpecifiedTID(device, pid, thread_specifier)
+  if pid is None and tid is None:
+    raise RuntimeError('Could not find specified process/thread running on '
+                       'device. Make sure the apk is already running before '
+                       'attempting to profile.')
+  profiler_args = list(profiler_args)
+  if profiler_args and profiler_args[0] == 'record':
+    profiler_args.pop(0)
+  if '--call-graph' not in profiler_args and '-g' not in profiler_args:
+    profiler_args.append('-g')
+  if '-f' not in profiler_args:
+    profiler_args.extend(('-f', '1000'))
+  device_out_path = '/data/local/tmp/perf.data'
+  if '-o' in profiler_args:
+    device_out_path = profiler_args[profiler_args.index('-o') + 1]
+  else:
+    profiler_args.extend(('-o', device_out_path))
+
+  if tid:
+    profiler_args.extend(('-t', str(tid)))
+  else:
+    profiler_args.extend(('-p', str(pid)))
+
+  adb_shell_simpleperf_process = device.adb.StartShell(
+      [device_simpleperf_path, 'record'] + profiler_args)
+
+  completed = False
+  try:
+    yield
+    completed = True
+
+  finally:
+    device.KillAll('simpleperf', signum=device_signal.SIGINT, blocking=True,
+                   quiet=True)
+    if completed:
+      adb_shell_simpleperf_process.wait()
+      device.PullFile(device_out_path, host_out_path)
+
+
+def ConvertSimpleperfToPprof(simpleperf_out_path, build_directory,
+                             pprof_out_path):
+  # The simpleperf scripts require the unstripped libs to be installed in the
+  # same directory structure as the libs on the device. Much of the logic here
+  # is just figuring out and creating the necessary directory structure, and
+  # symlinking the unstripped shared libs.
+
+  # Get the set of libs that we can symbolize
+  unstripped_lib_dir = os.path.join(build_directory, 'lib.unstripped')
+  unstripped_libs = set(
+      f for f in os.listdir(unstripped_lib_dir) if f.endswith('.so'))
+
+  # report.py will show the directory structure above the shared libs;
+  # that is the directory structure we need to recreate on the host.
+  script_dir = devil_env.config.LocalPath('simpleperf_scripts')
+  report_path = os.path.join(script_dir, 'report.py')
+  report_cmd = [sys.executable, report_path, '-i', simpleperf_out_path]
+  device_lib_path = None
+  for line in subprocess.check_output(
+      report_cmd, stderr=subprocess.STDOUT).splitlines():
+    fields = line.split()
+    if len(fields) < 5:
+      continue
+    shlib_path = fields[4]
+    shlib_dirname, shlib_basename = shlib_path.rpartition('/')[::2]
+    if shlib_basename in unstripped_libs:
+      device_lib_path = shlib_dirname
+      break
+  if not device_lib_path:
+    raise RuntimeError('No chrome-related symbols in profiling data in %s. '
+                       'Either the process was idle for the entire profiling '
+                       'period, or something went very wrong (and you should '
+                       'file a bug at crbug.com/new with component '
+                       'Speed>Tracing, and assign it to szager@chromium.org).'
+                       % simpleperf_out_path)
+
+  # Recreate the directory structure locally, and symlink unstripped libs.
+  processing_dir = tempfile.mkdtemp()
+  try:
+    processing_lib_dir = os.path.join(
+        processing_dir, 'binary_cache', device_lib_path.lstrip('/'))
+    os.makedirs(processing_lib_dir)
+    for lib in unstripped_libs:
+      unstripped_lib_path = os.path.join(unstripped_lib_dir, lib)
+      processing_lib_path = os.path.join(processing_lib_dir, lib)
+      os.symlink(unstripped_lib_path, processing_lib_path)
+
+    # Run the script to annotate symbols and convert from simpleperf format to
+    # pprof format.
+    pprof_converter_script = os.path.join(
+        script_dir, 'pprof_proto_generator.py')
+    pprof_converter_cmd = [
+        sys.executable, pprof_converter_script, '-i', simpleperf_out_path, '-o',
+        os.path.abspath(pprof_out_path), '--ndk_path',
+        constants.ANDROID_NDK_ROOT
+    ]
+    subprocess.check_output(pprof_converter_cmd, stderr=subprocess.STDOUT,
+                            cwd=processing_dir)
+  finally:
+    shutil.rmtree(processing_dir, ignore_errors=True)
diff --git a/third_party/libwebrtc/build/android/pylib/utils/test_filter.py b/third_party/libwebrtc/build/android/pylib/utils/test_filter.py
new file mode 100644
index 0000000000..7bafd002e3
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/test_filter.py
@@ -0,0 +1,148 @@
+# Copyright 2018 The Chromium 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
+
+
+_CMDLINE_NAME_SEGMENT_RE = re.compile(
+    r' with(?:out)? \{[^\}]*\}')
+
+class ConflictingPositiveFiltersException(Exception):
+  """Raised when both filter file and filter argument have positive filters."""
+
+
+def ParseFilterFile(input_lines):
+  """Converts test filter file contents to positive and negative pattern lists.
+
+  See //testing/buildbot/filters/README.md for description of the
+  syntax that |input_lines| are expected to follow.
+
+  See
+  https://github.com/google/googletest/blob/master/docs/advanced.md#running-a-subset-of-the-tests
+  for description of the syntax that --gtest_filter argument should follow.
+
+  Args:
+    input_lines: An iterable (e.g. a list or a file) containing input lines.
+  Returns:
+    tuple containing the lists of positive patterns and negative patterns
+  """
+  # Strip comments and whitespace from each line and filter non-empty lines.
+  stripped_lines = (l.split('#', 1)[0].strip() for l in input_lines)
+  filter_lines = [l for l in stripped_lines if l]
+
+  # Split the tests into positive and negative patterns (gtest treats
+  # every pattern after the first '-' sign as an exclusion).
+  positive_patterns = [l for l in filter_lines if l[0] != '-']
+  negative_patterns = [l[1:] for l in filter_lines if l[0] == '-']
+  return positive_patterns, negative_patterns
+
+
+def AddFilterOptions(parser):
+  """Adds filter command-line options to the provided parser.
+
+  Args:
+    parser: an argparse.ArgumentParser instance.
+  """
+  parser.add_argument(
+      # Deprecated argument.
+      '--gtest-filter-file',
+      # New argument.
+      '--test-launcher-filter-file',
+      action='append',
+      dest='test_filter_files',
+      help='Path to file that contains googletest-style filter strings. '
+      'See also //testing/buildbot/filters/README.md.')
+
+  filter_group = parser.add_mutually_exclusive_group()
+  filter_group.add_argument(
+      '-f', '--test-filter', '--gtest_filter', '--gtest-filter',
+      dest='test_filter',
+      help='googletest-style filter string.',
+      default=os.environ.get('GTEST_FILTER'))
+  filter_group.add_argument(
+      '--isolated-script-test-filter',
+      help='isolated script filter string. '
+           'Like gtest filter strings, but with :: separators instead of :')
+
+
+def AppendPatternsToFilter(test_filter, positive_patterns=None,
+                           negative_patterns=None):
+  """Returns a test-filter string with additional patterns.
+
+  Args:
+    test_filter: test filter string
+    positive_patterns: list of positive patterns to add to string
+    negative_patterns: list of negative patterns to add to string
+  """
+  positives = []
+  negatives = []
+  positive = ''
+  negative = ''
+
+  split_filter = test_filter.split('-', 1)
+  if len(split_filter) == 1:
+    positive = split_filter[0]
+  else:
+    positive, negative = split_filter
+
+  positives += [f for f in positive.split(':') if f]
+  negatives += [f for f in negative.split(':') if f]
+
+  positives += positive_patterns if positive_patterns else []
+  negatives += negative_patterns if negative_patterns else []
+
+  final_filter = ':'.join([p.replace('#', '.') for p in positives])
+  if negatives:
+    final_filter += '-' + ':'.join([n.replace('#', '.') for n in negatives])
+  return final_filter
+
+
+def HasPositivePatterns(test_filter):
+  """Returns True if test_filter contains a positive pattern, else False
+
+  Args:
+    test_filter: test-filter style string
+  """
+  return bool(len(test_filter) > 0 and test_filter[0] != '-')
+
+
+def InitializeFilterFromArgs(args):
+  """Returns a filter string from the command-line option values.
+
+  Args:
+    args: an argparse.Namespace instance resulting from a using parser
+      to which the filter options above were added.
+
+  Raises:
+    ConflictingPositiveFiltersException if both filter file and command line
+    specify positive filters.
+  """
+  test_filter = ''
+  if args.isolated_script_test_filter:
+    args.test_filter = args.isolated_script_test_filter.replace('::', ':')
+  if args.test_filter:
+    test_filter = _CMDLINE_NAME_SEGMENT_RE.sub(
+        '', args.test_filter.replace('#', '.'))
+
+  if not args.test_filter_files:
+    return test_filter
+
+  # At this point it's potentially several files, in a list and ; separated
+  for test_filter_file in args.test_filter_files:
+    # At this point it's potentially several files, ; separated
+    for test_filter_file in test_filter_file.split(';'):
+      # At this point it's individual files
+      with open(test_filter_file, 'r') as f:
+        positive_file_patterns, negative_file_patterns = ParseFilterFile(f)
+        if positive_file_patterns and HasPositivePatterns(test_filter):
+          raise ConflictingPositiveFiltersException(
+              'Cannot specify positive pattern in both filter file and ' +
+              'filter command line argument')
+        test_filter = AppendPatternsToFilter(
+            test_filter,
+            positive_patterns=positive_file_patterns,
+            negative_patterns=negative_file_patterns)
+
+  return test_filter
diff --git a/third_party/libwebrtc/build/android/pylib/utils/test_filter_test.py b/third_party/libwebrtc/build/android/pylib/utils/test_filter_test.py
new file mode 100755
index 0000000000..3f1f21e4cb
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/test_filter_test.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env vpython3
+# Copyright 2018 The Chromium 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 sys
+import tempfile
+import unittest
+
+from pylib.utils import test_filter
+
+class ParseFilterFileTest(unittest.TestCase):
+
+  def testParseFilterFile_commentsAndBlankLines(self):
+    input_lines = [
+      'positive1',
+      '# comment',
+      'positive2  # Another comment',
+      ''
+      'positive3'
+    ]
+    actual = test_filter.ParseFilterFile(input_lines)
+    expected = ['positive1', 'positive2', 'positive3'], []
+    self.assertEqual(expected, actual)
+
+  def testParseFilterFile_onlyPositive(self):
+    input_lines = [
+      'positive1',
+      'positive2'
+    ]
+    actual = test_filter.ParseFilterFile(input_lines)
+    expected = ['positive1', 'positive2'], []
+    self.assertEqual(expected, actual)
+
+  def testParseFilterFile_onlyNegative(self):
+    input_lines = [
+      '-negative1',
+      '-negative2'
+    ]
+    actual = test_filter.ParseFilterFile(input_lines)
+    expected = [], ['negative1', 'negative2']
+    self.assertEqual(expected, actual)
+
+  def testParseFilterFile_positiveAndNegative(self):
+    input_lines = [
+      'positive1',
+      'positive2',
+      '-negative1',
+      '-negative2'
+    ]
+    actual = test_filter.ParseFilterFile(input_lines)
+    expected = ['positive1', 'positive2'], ['negative1', 'negative2']
+    self.assertEqual(expected, actual)
+
+
+class InitializeFilterFromArgsTest(unittest.TestCase):
+
+  def testInitializeBasicFilter(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    args = parser.parse_args([
+        '--test-filter',
+        'FooTest.testFoo:BarTest.testBar'])
+    expected = 'FooTest.testFoo:BarTest.testBar'
+    actual = test_filter.InitializeFilterFromArgs(args)
+    self.assertEqual(actual, expected)
+
+  def testInitializeJavaStyleFilter(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    args = parser.parse_args([
+        '--test-filter',
+        'FooTest#testFoo:BarTest#testBar'])
+    expected = 'FooTest.testFoo:BarTest.testBar'
+    actual = test_filter.InitializeFilterFromArgs(args)
+    self.assertEqual(actual, expected)
+
+  def testInitializeBasicIsolatedScript(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    args = parser.parse_args([
+        '--isolated-script-test-filter',
+        'FooTest.testFoo::BarTest.testBar'])
+    expected = 'FooTest.testFoo:BarTest.testBar'
+    actual = test_filter.InitializeFilterFromArgs(args)
+    self.assertEqual(actual, expected)
+
+  def testFilterArgWithPositiveFilterInFilterFile(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    with tempfile.NamedTemporaryFile(mode='w') as tmp_file:
+      tmp_file.write('positive1\npositive2\n-negative2\n-negative3\n')
+      tmp_file.seek(0)
+      args = parser.parse_args([
+          '--test-filter=-negative1',
+          '--test-launcher-filter-file',
+          tmp_file.name])
+      expected = 'positive1:positive2-negative1:negative2:negative3'
+      actual = test_filter.InitializeFilterFromArgs(args)
+      self.assertEqual(actual, expected)
+
+  def testFilterFileWithPositiveFilterInFilterArg(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    with tempfile.NamedTemporaryFile(mode='w') as tmp_file:
+      tmp_file.write('-negative2\n-negative3\n')
+      tmp_file.seek(0)
+      args = parser.parse_args([
+          '--test-filter',
+          'positive1:positive2-negative1',
+          '--test-launcher-filter-file',
+          tmp_file.name])
+      expected = 'positive1:positive2-negative1:negative2:negative3'
+      actual = test_filter.InitializeFilterFromArgs(args)
+      self.assertEqual(actual, expected)
+
+  def testPositiveFilterInBothFileAndArg(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    with tempfile.NamedTemporaryFile(mode='w') as tmp_file:
+      tmp_file.write('positive1\n')
+      tmp_file.seek(0)
+      args = parser.parse_args([
+          '--test-filter',
+          'positive2',
+          '--test-launcher-filter-file',
+          tmp_file.name])
+      with self.assertRaises(test_filter.ConflictingPositiveFiltersException):
+        test_filter.InitializeFilterFromArgs(args)
+
+  def testFilterArgWithFilterFileAllNegative(self):
+    parser = argparse.ArgumentParser()
+    test_filter.AddFilterOptions(parser)
+    with tempfile.NamedTemporaryFile(mode='w') as tmp_file:
+      tmp_file.write('-negative3\n-negative4\n')
+      tmp_file.seek(0)
+      args = parser.parse_args([
+          '--test-filter=-negative1:negative2',
+          '--test-launcher-filter-file',
+          tmp_file.name])
+      expected = '-negative1:negative2:negative3:negative4'
+      actual = test_filter.InitializeFilterFromArgs(args)
+      self.assertEqual(actual, expected)
+
+
+class AppendPatternsToFilter(unittest.TestCase):
+  def testAllEmpty(self):
+    expected = ''
+    actual = test_filter.AppendPatternsToFilter('', [], [])
+    self.assertEqual(actual, expected)
+
+  def testAppendOnlyPositiveToEmptyFilter(self):
+    expected = 'positive'
+    actual = test_filter.AppendPatternsToFilter('', ['positive'])
+    self.assertEqual(actual, expected)
+
+  def testAppendOnlyNegativeToEmptyFilter(self):
+    expected = '-negative'
+    actual = test_filter.AppendPatternsToFilter('',
+                                                negative_patterns=['negative'])
+    self.assertEqual(actual, expected)
+
+  def testAppendToEmptyFilter(self):
+    expected = 'positive-negative'
+    actual = test_filter.AppendPatternsToFilter('', ['positive'], ['negative'])
+    self.assertEqual(actual, expected)
+
+  def testAppendToPositiveOnlyFilter(self):
+    expected = 'positive1:positive2-negative'
+    actual = test_filter.AppendPatternsToFilter('positive1', ['positive2'],
+                                                ['negative'])
+    self.assertEqual(actual, expected)
+
+  def testAppendToNegativeOnlyFilter(self):
+    expected = 'positive-negative1:negative2'
+    actual = test_filter.AppendPatternsToFilter('-negative1', ['positive'],
+                                                ['negative2'])
+    self.assertEqual(actual, expected)
+
+  def testAppendPositiveToFilter(self):
+    expected = 'positive1:positive2-negative1'
+    actual = test_filter.AppendPatternsToFilter('positive1-negative1',
+                                                ['positive2'])
+    self.assertEqual(actual, expected)
+
+  def testAppendNegativeToFilter(self):
+    expected = 'positive1-negative1:negative2'
+    actual = test_filter.AppendPatternsToFilter('positive1-negative1',
+                                                negative_patterns=['negative2'])
+    self.assertEqual(actual, expected)
+
+  def testAppendBothToFilter(self):
+    expected = 'positive1:positive2-negative1:negative2'
+    actual = test_filter.AppendPatternsToFilter('positive1-negative1',
+                                                positive_patterns=['positive2'],
+                                                negative_patterns=['negative2'])
+    self.assertEqual(actual, expected)
+
+  def testAppendMultipleToFilter(self):
+    expected = 'positive1:positive2:positive3-negative1:negative2:negative3'
+    actual = test_filter.AppendPatternsToFilter('positive1-negative1',
+                                                ['positive2', 'positive3'],
+                                                ['negative2', 'negative3'])
+    self.assertEqual(actual, expected)
+
+  def testRepeatedAppendToFilter(self):
+    expected = 'positive1:positive2:positive3-negative1:negative2:negative3'
+    filter_string = test_filter.AppendPatternsToFilter('positive1-negative1',
+                                                       ['positive2'],
+                                                       ['negative2'])
+    actual = test_filter.AppendPatternsToFilter(filter_string, ['positive3'],
+                                                ['negative3'])
+    self.assertEqual(actual, expected)
+
+  def testAppendHashSeparatedPatternsToFilter(self):
+    expected = 'positive.test1:positive.test2-negative.test1:negative.test2'
+    actual = test_filter.AppendPatternsToFilter('positive#test1-negative#test1',
+                                                       ['positive#test2'],
+                                                       ['negative#test2'])
+    self.assertEqual(actual, expected)
+
+
+class HasPositivePatterns(unittest.TestCase):
+  def testEmpty(self):
+    expected = False
+    actual = test_filter.HasPositivePatterns('')
+    self.assertEqual(actual, expected)
+
+  def testHasOnlyPositive(self):
+    expected = True
+    actual = test_filter.HasPositivePatterns('positive')
+    self.assertEqual(actual, expected)
+
+  def testHasOnlyNegative(self):
+    expected = False
+    actual = test_filter.HasPositivePatterns('-negative')
+    self.assertEqual(actual, expected)
+
+  def testHasBoth(self):
+    expected = True
+    actual = test_filter.HasPositivePatterns('positive-negative')
+    self.assertEqual(actual, expected)
+
+
+if __name__ == '__main__':
+  sys.exit(unittest.main())
diff --git a/third_party/libwebrtc/build/android/pylib/utils/time_profile.py b/third_party/libwebrtc/build/android/pylib/utils/time_profile.py
new file mode 100644
index 0000000000..094799c4f2
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/utils/time_profile.py
@@ -0,0 +1,45 @@
+# 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='operation'):
+    self._starttime = None
+    self._endtime = None
+    self._description = description
+    self.Start()
+
+  def Start(self):
+    self._starttime = time.time()
+    self._endtime = None
+
+  def GetDelta(self):
+    """Returns the rounded delta.
+
+    Also stops the timer if Stop() has not already been called.
+    """
+    if self._endtime is None:
+      self.Stop(log=False)
+    delta = self._endtime - self._starttime
+    delta = round(delta, 2) if delta < 10 else round(delta, 1)
+    return delta
+
+  def LogResult(self):
+    """Logs the result."""
+    logging.info('%s seconds to perform %s', self.GetDelta(), self._description)
+
+  def Stop(self, log=True):
+    """Stop profiling.
+
+    Args:
+      log: Log the delta (defaults to true).
+    """
+    self._endtime = time.time()
+    if log:
+      self.LogResult()
diff --git a/third_party/libwebrtc/build/android/pylib/utils/xvfb.py b/third_party/libwebrtc/build/android/pylib/utils/xvfb.py
new file mode 100644
index 0000000000..cb9d50e8fd
--- /dev/null
+++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/pylib/valgrind_tools.py b/third_party/libwebrtc/build/android/pylib/valgrind_tools.py
new file mode 100644
index 0000000000..fec71beaf7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/valgrind_tools.py
@@ -0,0 +1,116 @@
+# 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=R0201
+
+
+
+
+import logging
+import sys
+
+from devil.android import device_errors
+from devil.android.valgrind_tools import base_tool
+
+
+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.RemovePath(path, force=True, as_root=True)
+  else:
+    device.WriteFile(path, '%f' % scale, as_root=True)
+
+
+
+class AddressSanitizerTool(base_tool.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
+
+  @classmethod
+  def CopyFiles(cls, device):
+    """Copies ASan tools to the device."""
+    del device
+
+  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
+
+
+TOOL_REGISTRY = {
+    '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 base_tool.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/third_party/libwebrtc/build/android/pylintrc b/third_party/libwebrtc/build/android/pylintrc
new file mode 100644
index 0000000000..2a721bf270
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylintrc
@@ -0,0 +1,15 @@
+[FORMAT]
+
+max-line-length=80
+
+[MESSAGES CONTROL]
+
+disable=abstract-class-not-used,bad-continuation,bad-indentation,duplicate-code,fixme,invalid-name,locally-disabled,locally-enabled,missing-docstring,star-args,too-few-public-methods,too-many-arguments,too-many-branches,too-many-instance-attributes,too-many-lines,too-many-locals,too-many-public-methods,too-many-statements,wrong-import-position
+
+[REPORTS]
+
+reports=no
+
+[VARIABLES]
+
+dummy-variables-rgx=^_.*$|dummy
diff --git a/third_party/libwebrtc/build/android/resource_sizes.gni b/third_party/libwebrtc/build/android/resource_sizes.gni
new file mode 100644
index 0000000000..2c91749c5e
--- /dev/null
+++ b/third_party/libwebrtc/build/android/resource_sizes.gni
@@ -0,0 +1,100 @@
+# Copyright 2019 The Chromium 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/internal_rules.gni")
+
+# Generates a script in the bin directory that runs
+# //build/android/resource_sizes.py against the provided apk.
+#
+# Only one of apk_name or file_path should be provided.
+#
+# Variables:
+#   apk_name: The name of the apk, without the extension.
+#   file_path: The path to the apk or .minimal.apks.
+#   trichrome_chrome_path: The path to chrome apk or .minimal.apks.
+#   trichrome_webview_path: The path to webview apk or .minimal.apks.
+#   trichrome_library_path: The path to library apk or .minimal.apks.
+template("android_resource_sizes_test") {
+  generate_android_wrapper(target_name) {
+    forward_variables_from(invoker, [ "data_deps" ])
+    executable = "//build/android/resource_sizes.py"
+    wrapper_script = "$root_out_dir/bin/run_${target_name}"
+
+    assert(defined(invoker.apk_name) != defined(invoker.file_path),
+           "Exactly one of apk_name or file_path should be provided.")
+
+    deps = [ "//build/android:resource_sizes_py" ]
+    executable_args = [
+      "--output-format",
+      "histograms",
+      "--chromium-output-directory",
+      "@WrappedPath(.)",
+    ]
+
+    data = [
+      "//.vpython",
+      "//.vpython3",
+    ]
+    if (defined(invoker.trichrome_chrome_path)) {
+      data += [
+        invoker.trichrome_chrome_path,
+        invoker.trichrome_webview_path,
+        invoker.trichrome_library_path,
+      ]
+      _rebased_chrome =
+          rebase_path(invoker.trichrome_chrome_path, root_build_dir)
+      _rebased_webview =
+          rebase_path(invoker.trichrome_webview_path, root_build_dir)
+      _rebased_library =
+          rebase_path(invoker.trichrome_library_path, root_build_dir)
+
+      # apk_name used only as test suite name. Not a path in this case.
+      executable_args += [
+        "--trichrome-chrome",
+        "@WrappedPath(${_rebased_chrome})",
+        "--trichrome-webview",
+        "@WrappedPath(${_rebased_webview})",
+        "--trichrome-library",
+        "@WrappedPath(${_rebased_library})",
+        "${invoker.apk_name}",
+      ]
+    } else {
+      if (defined(invoker.apk_name)) {
+        _file_path = "$root_out_dir/apks/${invoker.apk_name}.apk"
+        data += [ "$root_out_dir/arsc/apks/${invoker.apk_name}.ap_" ]
+      } else if (defined(invoker.file_path)) {
+        _file_path = invoker.file_path
+      }
+      data += [ _file_path ]
+      _rebased_file_path = rebase_path(_file_path, root_build_dir)
+      executable_args += [ "@WrappedPath(${_rebased_file_path})" ]
+    }
+  }
+}
+
+# Generates a "size config JSON file" to specify data to be passed from recipes
+# to Python scripts for binary size measurement on bots. All filenames are
+# relative to $root_build_dir. The resulting JSON file is written to
+# "$root_build_dir/config/${invoker.name}_size_config.json".
+#
+# Variables:
+#   name: The name of the path to the generated size config JSON file.
+#   mapping_files: List of mapping files.
+#   to_resource_sizes_py: Scope containing data to pass to resource_sizes.py,
+#     processed by generate_commit_size_analysis.py.
+#   supersize_input_file: Main input for SuperSize.
+template("android_size_bot_config") {
+  _full_target_name = get_label_info(target_name, "label_no_toolchain")
+  _out_json = {
+    _HEADER = "Written by build target '${_full_target_name}'"
+    forward_variables_from(invoker,
+                           [
+                             "mapping_files",
+                             "to_resource_sizes_py",
+                             "supersize_input_file",
+                           ])
+  }
+  _output_json_path = "$root_build_dir/config/${invoker.name}_size_config.json"
+  write_file(_output_json_path, _out_json, "json")
+}
diff --git a/third_party/libwebrtc/build/android/resource_sizes.py b/third_party/libwebrtc/build/android/resource_sizes.py
new file mode 100755
index 0000000000..825edadedf
--- /dev/null
+++ b/third_party/libwebrtc/build/android/resource_sizes.py
@@ -0,0 +1,910 @@
+#!/usr/bin/env vpython3
+# 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.
+
+"""Reports binary size metrics for an APK.
+
+More information at //docs/speed/binary_size/metrics.md.
+"""
+
+from __future__ import print_function
+
+import argparse
+import collections
+from contextlib import contextmanager
+import json
+import logging
+import os
+import posixpath
+import re
+import struct
+import sys
+import tempfile
+import zipfile
+import zlib
+
+import devil_chromium
+from devil.android.sdk import build_tools
+from devil.utils import cmd_helper
+from devil.utils import lazy
+import method_count
+from pylib import constants
+from pylib.constants import host_paths
+
+_AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt'))
+_BUILD_UTILS_PATH = os.path.join(
+    host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp')
+
+with host_paths.SysPath(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build')):
+  import gn_helpers  # pylint: disable=import-error
+
+with host_paths.SysPath(host_paths.BUILD_COMMON_PATH):
+  import perf_tests_results_helper  # pylint: disable=import-error
+
+with host_paths.SysPath(host_paths.TRACING_PATH):
+  from tracing.value import convert_chart_json  # pylint: disable=import-error
+
+with host_paths.SysPath(_BUILD_UTILS_PATH, 0):
+  from util import build_utils  # pylint: disable=import-error
+  from util import zipalign  # pylint: disable=import-error
+
+
+zipalign.ApplyZipFileZipAlignFix()
+
+# Captures an entire config from aapt output.
+_AAPT_CONFIG_PATTERN = r'config %s:(.*?)config [a-zA-Z-]+:'
+# Matches string resource entries from aapt output.
+_AAPT_ENTRY_RE = re.compile(
+    r'resource (?P\w{10}) [\w\.]+:string/.*?"(?P.+?)"', re.DOTALL)
+_BASE_CHART = {
+    'format_version': '0.1',
+    'benchmark_name': 'resource_sizes',
+    'benchmark_description': 'APK resource size information.',
+    'trace_rerun_options': [],
+    'charts': {}
+}
+# Macro definitions look like (something, 123) when
+# enable_resource_allowlist_generation=true.
+_RC_HEADER_RE = re.compile(r'^#define (?P\w+).* (?P\d+)\)?$')
+_RE_NON_LANGUAGE_PAK = re.compile(r'^assets/.*(resources|percent)\.pak$')
+_READELF_SIZES_METRICS = {
+    'text': ['.text'],
+    'data': ['.data', '.rodata', '.data.rel.ro', '.data.rel.ro.local'],
+    'relocations': ['.rel.dyn', '.rel.plt', '.rela.dyn', '.rela.plt'],
+    'unwind': [
+        '.ARM.extab', '.ARM.exidx', '.eh_frame', '.eh_frame_hdr',
+        '.ARM.exidxsentinel_section_after_text'
+    ],
+    'symbols': [
+        '.dynsym', '.dynstr', '.dynamic', '.shstrtab', '.got', '.plt',
+        '.got.plt', '.hash', '.gnu.hash'
+    ],
+    'other': [
+        '.init_array', '.preinit_array', '.ctors', '.fini_array', '.comment',
+        '.note.gnu.gold-version', '.note.crashpad.info', '.note.android.ident',
+        '.ARM.attributes', '.note.gnu.build-id', '.gnu.version',
+        '.gnu.version_d', '.gnu.version_r', '.interp', '.gcc_except_table'
+    ]
+}
+
+
+class _AccumulatingReporter(object):
+  def __init__(self):
+    self._combined_metrics = collections.defaultdict(int)
+
+  def __call__(self, graph_title, trace_title, value, units):
+    self._combined_metrics[(graph_title, trace_title, units)] += value
+
+  def DumpReports(self, report_func):
+    for (graph_title, trace_title,
+         units), value in sorted(self._combined_metrics.items()):
+      report_func(graph_title, trace_title, value, units)
+
+
+class _ChartJsonReporter(_AccumulatingReporter):
+  def __init__(self, chartjson):
+    super(_ChartJsonReporter, self).__init__()
+    self._chartjson = chartjson
+    self.trace_title_prefix = ''
+
+  def __call__(self, graph_title, trace_title, value, units):
+    super(_ChartJsonReporter, self).__call__(graph_title, trace_title, value,
+                                             units)
+
+    perf_tests_results_helper.ReportPerfResult(
+        self._chartjson, graph_title, self.trace_title_prefix + trace_title,
+        value, units)
+
+  def SynthesizeTotals(self, unique_method_count):
+    for tup, value in sorted(self._combined_metrics.items()):
+      graph_title, trace_title, units = tup
+      if trace_title == 'unique methods':
+        value = unique_method_count
+      perf_tests_results_helper.ReportPerfResult(self._chartjson, graph_title,
+                                                 'Combined_' + trace_title,
+                                                 value, units)
+
+
+def _PercentageDifference(a, b):
+  if a == 0:
+    return 0
+  return float(b - a) / a
+
+
+def _ReadZipInfoExtraFieldLength(zip_file, zip_info):
+  """Reads the value of |extraLength| from |zip_info|'s local file header.
+
+  |zip_info| has an |extra| field, but it's read from the central directory.
+  Android's zipalign tool sets the extra field only in local file headers.
+  """
+  # Refer to https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
+  zip_file.fp.seek(zip_info.header_offset + 28)
+  return struct.unpack(', or .
+  on_demand = parse_attr('dist:onDemand') or 'dist:on-demand' in output
+  on_demand = bool(on_demand and is_feature_split)
+
+  return sdk_version, skip_extract_lib, on_demand
+
+
+def _NormalizeLanguagePaks(translations, factor):
+  english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak')
+  num_translations = translations.GetNumEntries()
+  ret = 0
+  if english_pak:
+    ret -= translations.ComputeZippedSize()
+    ret += int(english_pak.compress_size * num_translations * factor)
+  return ret
+
+
+def _NormalizeResourcesArsc(apk_path, num_arsc_files, num_translations,
+                            out_dir):
+  """Estimates the expected overhead of untranslated strings in resources.arsc.
+
+  See http://crbug.com/677966 for why this is necessary.
+  """
+  # If there are multiple .arsc files, use the resource packaged APK instead.
+  if num_arsc_files > 1:
+    if not out_dir:
+      return -float('inf')
+    ap_name = os.path.basename(apk_path).replace('.apk', '.ap_')
+    ap_path = os.path.join(out_dir, 'arsc/apks', ap_name)
+    if not os.path.exists(ap_path):
+      raise Exception('Missing expected file: %s, try rebuilding.' % ap_path)
+    apk_path = ap_path
+
+  aapt_output = _RunAaptDumpResources(apk_path)
+  # en-rUS is in the default config and may be cluttered with non-translatable
+  # strings, so en-rGB is a better baseline for finding missing translations.
+  en_strings = _CreateResourceIdValueMap(aapt_output, 'en-rGB')
+  fr_strings = _CreateResourceIdValueMap(aapt_output, 'fr')
+
+  # en-US and en-GB will never be translated.
+  config_count = num_translations - 2
+
+  size = 0
+  for res_id, string_val in en_strings.items():
+    if string_val == fr_strings[res_id]:
+      string_size = len(string_val)
+      # 7 bytes is the per-entry overhead (not specific to any string). See
+      # https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/tools/aapt/StringPool.cpp#414.
+      # The 1.5 factor was determined experimentally and is meant to account for
+      # other languages generally having longer strings than english.
+      size += config_count * (7 + string_size * 1.5)
+
+  return int(size)
+
+
+def _CreateResourceIdValueMap(aapt_output, lang):
+  """Return a map of resource ids to string values for the given |lang|."""
+  config_re = _AAPT_CONFIG_PATTERN % lang
+  return {entry.group('id'): entry.group('val')
+          for config_section in re.finditer(config_re, aapt_output, re.DOTALL)
+          for entry in re.finditer(_AAPT_ENTRY_RE, config_section.group(0))}
+
+
+def _RunAaptDumpResources(apk_path):
+  cmd = [_AAPT_PATH.read(), 'dump', '--values', 'resources', apk_path]
+  status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
+  if status != 0:
+    raise Exception('Failed running aapt command: "%s" with output "%s".' %
+                    (' '.join(cmd), output))
+  return output
+
+
+class _FileGroup(object):
+  """Represents a category that apk files can fall into."""
+
+  def __init__(self, name):
+    self.name = name
+    self._zip_infos = []
+    self._extracted_multipliers = []
+
+  def AddZipInfo(self, zip_info, extracted_multiplier=0):
+    self._zip_infos.append(zip_info)
+    self._extracted_multipliers.append(extracted_multiplier)
+
+  def AllEntries(self):
+    return iter(self._zip_infos)
+
+  def GetNumEntries(self):
+    return len(self._zip_infos)
+
+  def FindByPattern(self, pattern):
+    return next((i for i in self._zip_infos if re.match(pattern, i.filename)),
+                None)
+
+  def FindLargest(self):
+    if not self._zip_infos:
+      return None
+    return max(self._zip_infos, key=lambda i: i.file_size)
+
+  def ComputeZippedSize(self):
+    return sum(i.compress_size for i in self._zip_infos)
+
+  def ComputeUncompressedSize(self):
+    return sum(i.file_size for i in self._zip_infos)
+
+  def ComputeExtractedSize(self):
+    ret = 0
+    for zi, multiplier in zip(self._zip_infos, self._extracted_multipliers):
+      ret += zi.file_size * multiplier
+    return ret
+
+  def ComputeInstallSize(self):
+    return self.ComputeExtractedSize() + self.ComputeZippedSize()
+
+
+def _AnalyzeInternal(apk_path,
+                     sdk_version,
+                     report_func,
+                     dex_stats_collector,
+                     out_dir,
+                     tool_prefix,
+                     apks_path=None,
+                     split_name=None):
+  """Analyse APK to determine size contributions of different file classes.
+
+  Returns: Normalized APK size.
+  """
+  dex_stats_collector.CollectFromZip(split_name or '', apk_path)
+  file_groups = []
+
+  def make_group(name):
+    group = _FileGroup(name)
+    file_groups.append(group)
+    return group
+
+  def has_no_extension(filename):
+    return os.path.splitext(filename)[1] == ''
+
+  native_code = make_group('Native code')
+  java_code = make_group('Java code')
+  native_resources_no_translations = make_group('Native resources (no l10n)')
+  translations = make_group('Native resources (l10n)')
+  stored_translations = make_group('Native resources stored (l10n)')
+  icu_data = make_group('ICU (i18n library) data')
+  v8_snapshots = make_group('V8 Snapshots')
+  png_drawables = make_group('PNG drawables')
+  res_directory = make_group('Non-compiled Android resources')
+  arsc = make_group('Compiled Android resources')
+  metadata = make_group('Package metadata')
+  unknown = make_group('Unknown files')
+  notices = make_group('licenses.notice file')
+  unwind_cfi = make_group('unwind_cfi (dev and canary only)')
+
+  with zipfile.ZipFile(apk_path, 'r') as apk:
+    apk_contents = apk.infolist()
+    # Account for zipalign overhead that exists in local file header.
+    zipalign_overhead = sum(
+        _ReadZipInfoExtraFieldLength(apk, i) for i in apk_contents)
+    # Account for zipalign overhead that exists in central directory header.
+    # Happens when python aligns entries in apkbuilder.py, but does not
+    # exist when using Android's zipalign. E.g. for bundle .apks files.
+    zipalign_overhead += sum(len(i.extra) for i in apk_contents)
+    signing_block_size = _MeasureApkSignatureBlock(apk)
+
+  _, skip_extract_lib, _ = _ParseManifestAttributes(apk_path)
+
+  # Pre-L: Dalvik - .odex file is simply decompressed/optimized dex file (~1x).
+  # L, M: ART - .odex file is compiled version of the dex file (~4x).
+  # N: ART - Uses Dalvik-like JIT for normal apps (~1x), full compilation for
+  #    shared apps (~4x).
+  # Actual multipliers calculated using "apk_operations.py disk-usage".
+  # Will need to update multipliers once apk obfuscation is enabled.
+  # E.g. with obfuscation, the 4.04 changes to 4.46.
+  speed_profile_dex_multiplier = 1.17
+  orig_filename = apks_path or apk_path
+  is_webview = 'WebView' in orig_filename
+  is_monochrome = 'Monochrome' in orig_filename
+  is_library = 'Library' in orig_filename
+  is_shared_apk = sdk_version >= 24 and (is_monochrome or is_webview
+                                         or is_library)
+  # Dex decompression overhead varies by Android version.
+  if sdk_version < 21:
+    # JellyBean & KitKat
+    dex_multiplier = 1.16
+  elif sdk_version < 24:
+    # Lollipop & Marshmallow
+    dex_multiplier = 4.04
+  elif is_shared_apk:
+    # Oreo and above, compilation_filter=speed
+    dex_multiplier = 4.04
+  else:
+    # Oreo and above, compilation_filter=speed-profile
+    dex_multiplier = speed_profile_dex_multiplier
+
+  total_apk_size = os.path.getsize(apk_path)
+  for member in apk_contents:
+    filename = member.filename
+    if filename.endswith('/'):
+      continue
+    if filename.endswith('.so'):
+      basename = posixpath.basename(filename)
+      should_extract_lib = not skip_extract_lib and basename.startswith('lib')
+      native_code.AddZipInfo(
+          member, extracted_multiplier=int(should_extract_lib))
+    elif filename.endswith('.dex'):
+      java_code.AddZipInfo(member, extracted_multiplier=dex_multiplier)
+    elif re.search(_RE_NON_LANGUAGE_PAK, filename):
+      native_resources_no_translations.AddZipInfo(member)
+    elif filename.endswith('.pak') or filename.endswith('.lpak'):
+      compressed = member.compress_type != zipfile.ZIP_STORED
+      bucket = translations if compressed else stored_translations
+      extracted_multiplier = 0
+      if compressed:
+        extracted_multiplier = int('en_' in filename or 'en-' in filename)
+      bucket.AddZipInfo(member, extracted_multiplier=extracted_multiplier)
+    elif 'icu' in filename and filename.endswith('.dat'):
+      icu_data.AddZipInfo(member)
+    elif filename.endswith('.bin'):
+      v8_snapshots.AddZipInfo(member)
+    elif filename.startswith('res/'):
+      if (filename.endswith('.png') or filename.endswith('.webp')
+          or has_no_extension(filename)):
+        png_drawables.AddZipInfo(member)
+      else:
+        res_directory.AddZipInfo(member)
+    elif filename.endswith('.arsc'):
+      arsc.AddZipInfo(member)
+    elif filename.startswith('META-INF') or filename in (
+        'AndroidManifest.xml', 'assets/webapk_dex_version.txt'):
+      metadata.AddZipInfo(member)
+    elif filename.endswith('.notice'):
+      notices.AddZipInfo(member)
+    elif filename.startswith('assets/unwind_cfi'):
+      unwind_cfi.AddZipInfo(member)
+    else:
+      unknown.AddZipInfo(member)
+
+  if apks_path:
+    # We're mostly focused on size of Chrome for non-English locales, so assume
+    # Hindi (arbitrarily chosen) locale split is installed.
+    with zipfile.ZipFile(apks_path) as z:
+      subpath = 'splits/{}-hi.apk'.format(split_name)
+      if subpath in z.namelist():
+        hindi_apk_info = z.getinfo(subpath)
+        total_apk_size += hindi_apk_info.file_size
+      else:
+        assert split_name != 'base', 'splits/base-hi.apk should always exist'
+
+  total_install_size = total_apk_size
+  total_install_size_android_go = total_apk_size
+  zip_overhead = total_apk_size
+
+  for group in file_groups:
+    actual_size = group.ComputeZippedSize()
+    install_size = group.ComputeInstallSize()
+    uncompressed_size = group.ComputeUncompressedSize()
+    extracted_size = group.ComputeExtractedSize()
+    total_install_size += extracted_size
+    zip_overhead -= actual_size
+
+    report_func('Breakdown', group.name + ' size', actual_size, 'bytes')
+    report_func('InstallBreakdown', group.name + ' size', int(install_size),
+                'bytes')
+    # Only a few metrics are compressed in the first place.
+    # To avoid over-reporting, track uncompressed size only for compressed
+    # entries.
+    if uncompressed_size != actual_size:
+      report_func('Uncompressed', group.name + ' size', uncompressed_size,
+                  'bytes')
+
+    if group is java_code and is_shared_apk:
+      # Updates are compiled using quicken, but system image uses speed-profile.
+      extracted_size = int(uncompressed_size * speed_profile_dex_multiplier)
+      total_install_size_android_go += extracted_size
+      report_func('InstallBreakdownGo', group.name + ' size',
+                  actual_size + extracted_size, 'bytes')
+    elif group is translations and apks_path:
+      # Assume Hindi rather than English (accounted for above in total_apk_size)
+      total_install_size_android_go += actual_size
+    else:
+      total_install_size_android_go += extracted_size
+
+  # Per-file zip overhead is caused by:
+  # * 30 byte entry header + len(file name)
+  # * 46 byte central directory entry + len(file name)
+  # * 0-3 bytes for zipalign.
+  report_func('Breakdown', 'Zip Overhead', zip_overhead, 'bytes')
+  report_func('InstallSize', 'APK size', total_apk_size, 'bytes')
+  report_func('InstallSize', 'Estimated installed size',
+              int(total_install_size), 'bytes')
+  if is_shared_apk:
+    report_func('InstallSize', 'Estimated installed size (Android Go)',
+                int(total_install_size_android_go), 'bytes')
+  transfer_size = _CalculateCompressedSize(apk_path)
+  report_func('TransferSize', 'Transfer size (deflate)', transfer_size, 'bytes')
+
+  # Size of main dex vs remaining.
+  main_dex_info = java_code.FindByPattern('classes.dex')
+  if main_dex_info:
+    main_dex_size = main_dex_info.file_size
+    report_func('Specifics', 'main dex size', main_dex_size, 'bytes')
+    secondary_size = java_code.ComputeUncompressedSize() - main_dex_size
+    report_func('Specifics', 'secondary dex size', secondary_size, 'bytes')
+
+  main_lib_info = native_code.FindLargest()
+  native_code_unaligned_size = 0
+  for lib_info in native_code.AllEntries():
+    section_sizes = _ExtractLibSectionSizesFromApk(apk_path, lib_info.filename,
+                                                   tool_prefix)
+    native_code_unaligned_size += sum(v for k, v in section_sizes.items()
+                                      if k != 'bss')
+    # Size of main .so vs remaining.
+    if lib_info == main_lib_info:
+      main_lib_size = lib_info.file_size
+      report_func('Specifics', 'main lib size', main_lib_size, 'bytes')
+      secondary_size = native_code.ComputeUncompressedSize() - main_lib_size
+      report_func('Specifics', 'other lib size', secondary_size, 'bytes')
+
+      for metric_name, size in section_sizes.items():
+        report_func('MainLibInfo', metric_name, size, 'bytes')
+
+  # Main metric that we want to monitor for jumps.
+  normalized_apk_size = total_apk_size
+  # unwind_cfi exists only in dev, canary, and non-channel builds.
+  normalized_apk_size -= unwind_cfi.ComputeZippedSize()
+  # Sections within .so files get 4kb aligned, so use section sizes rather than
+  # file size. Also gets rid of compression.
+  normalized_apk_size -= native_code.ComputeZippedSize()
+  normalized_apk_size += native_code_unaligned_size
+  # Normalized dex size: Size within the zip + size on disk for Android Go
+  # devices running Android O (which ~= uncompressed dex size).
+  # Use a constant compression factor to account for fluctuations.
+  normalized_apk_size -= java_code.ComputeZippedSize()
+  normalized_apk_size += java_code.ComputeUncompressedSize()
+  # Don't include zipalign overhead in normalized size, since it effectively
+  # causes size changes files that proceed aligned files to be rounded.
+  # For APKs where classes.dex directly proceeds libchrome.so (the normal case),
+  # this causes small dex size changes to disappear into libchrome.so alignment.
+  normalized_apk_size -= zipalign_overhead
+  # Don't include the size of the apk's signing block because it can fluctuate
+  # by up to 4kb (from my non-scientific observations), presumably based on hash
+  # sizes.
+  normalized_apk_size -= signing_block_size
+
+  # Unaligned size should be ~= uncompressed size or something is wrong.
+  # As of now, padding_fraction ~= .007
+  padding_fraction = -_PercentageDifference(
+      native_code.ComputeUncompressedSize(), native_code_unaligned_size)
+  # Ignore this check for small / no native code
+  if native_code.ComputeUncompressedSize() > 1000000:
+    assert 0 <= padding_fraction < .02, (
+        'Padding was: {} (file_size={}, sections_sum={})'.format(
+            padding_fraction, native_code.ComputeUncompressedSize(),
+            native_code_unaligned_size))
+
+  if apks_path:
+    # Locale normalization not needed when measuring only one locale.
+    # E.g. a change that adds 300 chars of unstranslated strings would cause the
+    # metric to be off by only 390 bytes (assuming a multiplier of 2.3 for
+    # Hindi).
+    pass
+  else:
+    # Avoid noise caused when strings change and translations haven't yet been
+    # updated.
+    num_translations = translations.GetNumEntries()
+    num_stored_translations = stored_translations.GetNumEntries()
+
+    if num_translations > 1:
+      # Multipliers found by looking at MonochromePublic.apk and seeing how much
+      # smaller en-US.pak is relative to the average locale.pak.
+      normalized_apk_size += _NormalizeLanguagePaks(translations, 1.17)
+    if num_stored_translations > 1:
+      normalized_apk_size += _NormalizeLanguagePaks(stored_translations, 1.43)
+    if num_translations + num_stored_translations > 1:
+      if num_translations == 0:
+        # WebView stores all locale paks uncompressed.
+        num_arsc_translations = num_stored_translations
+      else:
+        # Monochrome has more configurations than Chrome since it includes
+        # WebView (which supports more locales), but these should mostly be
+        # empty so ignore them here.
+        num_arsc_translations = num_translations
+      normalized_apk_size += _NormalizeResourcesArsc(apk_path,
+                                                     arsc.GetNumEntries(),
+                                                     num_arsc_translations,
+                                                     out_dir)
+
+  # It will be -Inf for .apk files with multiple .arsc files and no out_dir set.
+  if normalized_apk_size < 0:
+    sys.stderr.write('Skipping normalized_apk_size (no output directory set)\n')
+  else:
+    report_func('Specifics', 'normalized apk size', normalized_apk_size,
+                'bytes')
+  # The "file count" metric cannot be grouped with any other metrics when the
+  # end result is going to be uploaded to the perf dashboard in the HistogramSet
+  # format due to mixed units (bytes vs. zip entries) causing malformed
+  # summaries to be generated.
+  # TODO(https://crbug.com/903970): Remove this workaround if unit mixing is
+  # ever supported.
+  report_func('FileCount', 'file count', len(apk_contents), 'zip entries')
+
+  for info in unknown.AllEntries():
+    sys.stderr.write(
+        'Unknown entry: %s %d\n' % (info.filename, info.compress_size))
+  return normalized_apk_size
+
+
+def _CalculateCompressedSize(file_path):
+  CHUNK_SIZE = 256 * 1024
+  compressor = zlib.compressobj()
+  total_size = 0
+  with open(file_path, 'rb') as f:
+    for chunk in iter(lambda: f.read(CHUNK_SIZE), b''):
+      total_size += len(compressor.compress(chunk))
+  total_size += len(compressor.flush())
+  return total_size
+
+
+@contextmanager
+def Unzip(zip_file, filename=None):
+  """Utility for temporary use of a single file in a zip archive."""
+  with build_utils.TempDir() as unzipped_dir:
+    unzipped_files = build_utils.ExtractAll(
+        zip_file, unzipped_dir, True, pattern=filename)
+    if len(unzipped_files) == 0:
+      raise Exception(
+          '%s not found in %s' % (filename, zip_file))
+    yield unzipped_files[0]
+
+
+def _ConfigOutDirAndToolsPrefix(out_dir):
+  if out_dir:
+    constants.SetOutputDirectory(out_dir)
+  else:
+    try:
+      # Triggers auto-detection when CWD == output directory.
+      constants.CheckOutputDirectory()
+      out_dir = constants.GetOutDirectory()
+    except Exception:  # pylint: disable=broad-except
+      return out_dir, ''
+  build_vars = gn_helpers.ReadBuildVars(out_dir)
+  tool_prefix = os.path.join(out_dir, build_vars['android_tool_prefix'])
+  return out_dir, tool_prefix
+
+
+def _IterSplits(namelist):
+  for subpath in namelist:
+    # Looks for paths like splits/vr-master.apk, splits/vr-hi.apk.
+    name_parts = subpath.split('/')
+    if name_parts[0] == 'splits' and len(name_parts) == 2:
+      name_parts = name_parts[1].split('-')
+      if len(name_parts) == 2:
+        split_name, config_name = name_parts
+        if config_name == 'master.apk':
+          yield subpath, split_name
+
+
+def _ExtractToTempFile(zip_obj, subpath, temp_file):
+  temp_file.seek(0)
+  temp_file.truncate()
+  temp_file.write(zip_obj.read(subpath))
+  temp_file.flush()
+
+
+def _AnalyzeApkOrApks(report_func, apk_path, args):
+  # Create DexStatsCollector here to track unique methods across base & chrome
+  # modules.
+  dex_stats_collector = method_count.DexStatsCollector()
+  out_dir, tool_prefix = _ConfigOutDirAndToolsPrefix(args.out_dir)
+
+  if apk_path.endswith('.apk'):
+    sdk_version, _, _ = _ParseManifestAttributes(apk_path)
+    _AnalyzeInternal(apk_path, sdk_version, report_func, dex_stats_collector,
+                     out_dir, tool_prefix)
+  elif apk_path.endswith('.apks'):
+    with tempfile.NamedTemporaryFile(suffix='.apk') as f:
+      with zipfile.ZipFile(apk_path) as z:
+        # Currently bundletool is creating two apks when .apks is created
+        # without specifying an sdkVersion. Always measure the one with an
+        # uncompressed shared library.
+        try:
+          info = z.getinfo('splits/base-master_2.apk')
+        except KeyError:
+          info = z.getinfo('splits/base-master.apk')
+        _ExtractToTempFile(z, info.filename, f)
+        sdk_version, _, _ = _ParseManifestAttributes(f.name)
+
+        orig_report_func = report_func
+        report_func = _AccumulatingReporter()
+
+        def do_measure(split_name, on_demand):
+          logging.info('Measuring %s on_demand=%s', split_name, on_demand)
+          # Use no-op reporting functions to get normalized size for DFMs.
+          inner_report_func = report_func
+          inner_dex_stats_collector = dex_stats_collector
+          if on_demand:
+            inner_report_func = lambda *_: None
+            inner_dex_stats_collector = method_count.DexStatsCollector()
+
+          size = _AnalyzeInternal(f.name,
+                                  sdk_version,
+                                  inner_report_func,
+                                  inner_dex_stats_collector,
+                                  out_dir,
+                                  tool_prefix,
+                                  apks_path=apk_path,
+                                  split_name=split_name)
+          report_func('DFM_' + split_name, 'Size with hindi', size, 'bytes')
+
+        # Measure base outside of the loop since we've already extracted it.
+        do_measure('base', on_demand=False)
+
+        for subpath, split_name in _IterSplits(z.namelist()):
+          if split_name != 'base':
+            _ExtractToTempFile(z, subpath, f)
+            _, _, on_demand = _ParseManifestAttributes(f.name)
+            do_measure(split_name, on_demand=on_demand)
+
+        report_func.DumpReports(orig_report_func)
+        report_func = orig_report_func
+  else:
+    raise Exception('Unknown file type: ' + apk_path)
+
+  # Report dex stats outside of _AnalyzeInternal() so that the "unique methods"
+  # metric is not just the sum of the base and chrome modules.
+  for metric, count in dex_stats_collector.GetTotalCounts().items():
+    report_func('Dex', metric, count, 'entries')
+  report_func('Dex', 'unique methods',
+              dex_stats_collector.GetUniqueMethodCount(), 'entries')
+  report_func('DexCache', 'DexCache',
+              dex_stats_collector.GetDexCacheSize(pre_oreo=sdk_version < 26),
+              'bytes')
+
+  return dex_stats_collector
+
+
+def _ResourceSizes(args):
+  chartjson = _BASE_CHART.copy() if args.output_format else None
+  reporter = _ChartJsonReporter(chartjson)
+  # Create DexStatsCollector here to track unique methods across trichrome APKs.
+  dex_stats_collector = method_count.DexStatsCollector()
+
+  specs = [
+      ('Chrome_', args.trichrome_chrome),
+      ('WebView_', args.trichrome_webview),
+      ('Library_', args.trichrome_library),
+  ]
+  for prefix, path in specs:
+    if path:
+      reporter.trace_title_prefix = prefix
+      child_dex_stats_collector = _AnalyzeApkOrApks(reporter, path, args)
+      dex_stats_collector.MergeFrom(prefix, child_dex_stats_collector)
+
+  if any(path for _, path in specs):
+    reporter.SynthesizeTotals(dex_stats_collector.GetUniqueMethodCount())
+  else:
+    _AnalyzeApkOrApks(reporter, args.input, args)
+
+  if chartjson:
+    _DumpChartJson(args, chartjson)
+
+
+def _DumpChartJson(args, chartjson):
+  if args.output_file == '-':
+    json_file = sys.stdout
+  elif args.output_file:
+    json_file = open(args.output_file, 'w')
+  else:
+    results_path = os.path.join(args.output_dir, 'results-chart.json')
+    logging.critical('Dumping chartjson to %s', results_path)
+    json_file = open(results_path, 'w')
+
+  json.dump(chartjson, json_file, indent=2)
+
+  if json_file is not sys.stdout:
+    json_file.close()
+
+  # We would ideally generate a histogram set directly instead of generating
+  # chartjson then converting. However, perf_tests_results_helper is in
+  # //build, which doesn't seem to have any precedent for depending on
+  # anything in Catapult. This can probably be fixed, but since this doesn't
+  # need to be super fast or anything, converting is a good enough solution
+  # for the time being.
+  if args.output_format == 'histograms':
+    histogram_result = convert_chart_json.ConvertChartJson(results_path)
+    if histogram_result.returncode != 0:
+      raise Exception('chartjson conversion failed with error: ' +
+                      histogram_result.stdout)
+
+    histogram_path = os.path.join(args.output_dir, 'perf_results.json')
+    logging.critical('Dumping histograms to %s', histogram_path)
+    with open(histogram_path, 'w') as json_file:
+      json_file.write(histogram_result.stdout)
+
+
+def main():
+  build_utils.InitLogging('RESOURCE_SIZES_DEBUG')
+  argparser = argparse.ArgumentParser(description='Print APK size metrics.')
+  argparser.add_argument(
+      '--min-pak-resource-size',
+      type=int,
+      default=20 * 1024,
+      help='Minimum byte size of displayed pak resources.')
+  argparser.add_argument(
+      '--chromium-output-directory',
+      dest='out_dir',
+      type=os.path.realpath,
+      help='Location of the build artifacts.')
+  argparser.add_argument(
+      '--chartjson',
+      action='store_true',
+      help='DEPRECATED. Use --output-format=chartjson '
+      'instead.')
+  argparser.add_argument(
+      '--output-format',
+      choices=['chartjson', 'histograms'],
+      help='Output the results to a file in the given '
+      'format instead of printing the results.')
+  argparser.add_argument('--loadable_module', help='Obsolete (ignored).')
+
+  # Accepted to conform to the isolated script interface, but ignored.
+  argparser.add_argument(
+      '--isolated-script-test-filter', help=argparse.SUPPRESS)
+  argparser.add_argument(
+      '--isolated-script-test-perf-output',
+      type=os.path.realpath,
+      help=argparse.SUPPRESS)
+
+  output_group = argparser.add_mutually_exclusive_group()
+
+  output_group.add_argument(
+      '--output-dir', default='.', help='Directory to save chartjson to.')
+  output_group.add_argument(
+      '--output-file',
+      help='Path to output .json (replaces --output-dir). Works only for '
+      '--output-format=chartjson')
+  output_group.add_argument(
+      '--isolated-script-test-output',
+      type=os.path.realpath,
+      help='File to which results will be written in the '
+      'simplified JSON output format.')
+
+  argparser.add_argument('input', help='Path to .apk or .apks file to measure.')
+  trichrome_group = argparser.add_argument_group(
+      'Trichrome inputs',
+      description='When specified, |input| is used only as Test suite name.')
+  trichrome_group.add_argument(
+      '--trichrome-chrome', help='Path to Trichrome Chrome .apks')
+  trichrome_group.add_argument(
+      '--trichrome-webview', help='Path to Trichrome WebView .apk(s)')
+  trichrome_group.add_argument(
+      '--trichrome-library', help='Path to Trichrome Library .apk')
+  args = argparser.parse_args()
+
+  devil_chromium.Initialize(output_directory=args.out_dir)
+
+  # TODO(bsheedy): Remove this once uses of --chartjson have been removed.
+  if args.chartjson:
+    args.output_format = 'chartjson'
+
+  isolated_script_output = {'valid': False, 'failures': []}
+
+  test_name = 'resource_sizes (%s)' % os.path.basename(args.input)
+
+  if args.isolated_script_test_output:
+    args.output_dir = os.path.join(
+        os.path.dirname(args.isolated_script_test_output), test_name)
+    if not os.path.exists(args.output_dir):
+      os.makedirs(args.output_dir)
+
+  try:
+    _ResourceSizes(args)
+    isolated_script_output = {
+        'valid': True,
+        'failures': [],
+    }
+  finally:
+    if args.isolated_script_test_output:
+      results_path = os.path.join(args.output_dir, 'test_results.json')
+      with open(results_path, 'w') as output_file:
+        json.dump(isolated_script_output, output_file)
+      with open(args.isolated_script_test_output, 'w') as output_file:
+        json.dump(isolated_script_output, output_file)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/third_party/libwebrtc/build/android/resource_sizes.pydeps b/third_party/libwebrtc/build/android/resource_sizes.pydeps
new file mode 100644
index 0000000000..d956f5bae7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/resource_sizes.pydeps
@@ -0,0 +1,58 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android --output build/android/resource_sizes.pydeps build/android/resource_sizes.py
+../../third_party/catapult/common/py_utils/py_utils/__init__.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
+../../third_party/catapult/common/py_utils/py_utils/lock.py
+../../third_party/catapult/dependency_manager/dependency_manager/__init__.py
+../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/base_config.py
+../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py
+../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py
+../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/manager.py
+../../third_party/catapult/dependency_manager/dependency_manager/uploader.py
+../../third_party/catapult/devil/devil/__init__.py
+../../third_party/catapult/devil/devil/android/__init__.py
+../../third_party/catapult/devil/devil/android/constants/__init__.py
+../../third_party/catapult/devil/devil/android/constants/chrome.py
+../../third_party/catapult/devil/devil/android/ndk/__init__.py
+../../third_party/catapult/devil/devil/android/ndk/abis.py
+../../third_party/catapult/devil/devil/android/sdk/__init__.py
+../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../third_party/catapult/devil/devil/android/sdk/keyevent.py
+../../third_party/catapult/devil/devil/android/sdk/version_codes.py
+../../third_party/catapult/devil/devil/base_error.py
+../../third_party/catapult/devil/devil/constants/__init__.py
+../../third_party/catapult/devil/devil/constants/exit_codes.py
+../../third_party/catapult/devil/devil/devil_env.py
+../../third_party/catapult/devil/devil/utils/__init__.py
+../../third_party/catapult/devil/devil/utils/cmd_helper.py
+../../third_party/catapult/devil/devil/utils/lazy/__init__.py
+../../third_party/catapult/devil/devil/utils/lazy/weak_constant.py
+../../third_party/catapult/devil/devil/utils/reraiser_thread.py
+../../third_party/catapult/devil/devil/utils/timeout_retry.py
+../../third_party/catapult/devil/devil/utils/watchdog_timer.py
+../../third_party/catapult/third_party/six/six.py
+../../third_party/catapult/third_party/vinn/vinn/__init__.py
+../../third_party/catapult/third_party/vinn/vinn/_vinn.py
+../../third_party/catapult/tracing/tracing/__init__.py
+../../third_party/catapult/tracing/tracing/value/__init__.py
+../../third_party/catapult/tracing/tracing/value/convert_chart_json.py
+../../third_party/catapult/tracing/tracing_project.py
+../gn_helpers.py
+../util/lib/common/perf_result_data_type.py
+../util/lib/common/perf_tests_results_helper.py
+devil_chromium.py
+gyp/util/__init__.py
+gyp/util/build_utils.py
+gyp/util/zipalign.py
+method_count.py
+pylib/__init__.py
+pylib/constants/__init__.py
+pylib/constants/host_paths.py
+pylib/dex/__init__.py
+pylib/dex/dex_parser.py
+resource_sizes.py
diff --git a/third_party/libwebrtc/build/android/screenshot.py b/third_party/libwebrtc/build/android/screenshot.py
new file mode 100755
index 0000000000..9b47d6acb3
--- /dev/null
+++ b/third_party/libwebrtc/build/android/screenshot.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env vpython3
+# 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 sys
+
+import devil_chromium
+from devil.android.tools import screenshot
+
+if __name__ == '__main__':
+  devil_chromium.Initialize()
+  sys.exit(screenshot.main())
diff --git a/third_party/libwebrtc/build/android/stacktrace/BUILD.gn b/third_party/libwebrtc/build/android/stacktrace/BUILD.gn
new file mode 100644
index 0000000000..ce13a15b4b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/BUILD.gn
@@ -0,0 +1,28 @@
+# Copyright 2017 The Chromium 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")
+
+java_library("java_deobfuscate_java") {
+  sources = [ "java/org/chromium/build/FlushingReTrace.java" ]
+
+  # Avoid using java_prebuilt() to ensure all uses go through the checked-in
+  # wrapper script.
+  input_jars_paths = [
+    "//third_party/proguard/lib/proguard603.jar",
+    "//third_party/proguard/lib/retrace603.jar",
+  ]
+}
+
+# Use the checked-in copy of the wrapper script & .jar rather than the built
+# one to simplify usage of the tool.
+group("java_deobfuscate") {
+  data = [
+    "java_deobfuscate.py",
+    "java_deobfuscate.jar",
+    "//third_party/proguard/lib/proguard603.jar",
+    "//third_party/proguard/lib/retrace603.jar",
+  ]
+  deps = [ "//third_party/jdk:java_data" ]
+}
diff --git a/third_party/libwebrtc/build/android/stacktrace/README.md b/third_party/libwebrtc/build/android/stacktrace/README.md
new file mode 100644
index 0000000000..58ea94be98
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/README.md
@@ -0,0 +1,28 @@
+# java_deobfuscate.py
+
+A wrapper around ProGuard's ReTrace tool, which:
+
+1) Updates the regular expression used to identify stack lines, and
+2) Streams its output.
+
+The second point here is what allows you to run:
+
+    adb logcat | build/android/stacktrace/java_deobfuscate.py out/Default/apks/ChromePublic.apk.mapping
+
+And have it actually show output without logcat terminating.
+
+
+## Update Instructions:
+
+    ninja -C out/Release java_deobfuscate
+    cp out/Release/lib.java/build/android/stacktrace/java_deobfuscate.jar build/android/stacktrace
+
+# stackwalker.py
+
+Extracts Breakpad microdumps from a log file and uses `stackwalker` to symbolize
+them.
+
+
+# crashpad_stackwalker.py
+
+Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
diff --git a/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py b/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py
new file mode 100755
index 0000000000..ab5dfe195c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env vpython3
+#
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
+# All the non-trivial operations are performed by generate_breakpad_symbols.py,
+# dump_syms, minidump_dump and minidump_stackwalk.
+
+import argparse
+import logging
+import os
+import posixpath
+import re
+import sys
+import shutil
+import subprocess
+import tempfile
+
+_BUILD_ANDROID_PATH = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..'))
+sys.path.append(_BUILD_ANDROID_PATH)
+import devil_chromium
+from devil.android import device_utils
+from devil.utils import timeout_retry
+
+
+def _CreateSymbolsDir(build_path, dynamic_library_names):
+  generator = os.path.normpath(
+      os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash',
+                   'content', 'tools', 'generate_breakpad_symbols.py'))
+  syms_dir = os.path.join(build_path, 'crashpad_syms')
+  shutil.rmtree(syms_dir, ignore_errors=True)
+  os.mkdir(syms_dir)
+  for lib in dynamic_library_names:
+    unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib)
+    if not os.path.exists(unstripped_library_path):
+      continue
+    logging.info('Generating symbols for: %s', unstripped_library_path)
+    cmd = [
+        generator,
+        '--symbols-dir',
+        syms_dir,
+        '--build-dir',
+        build_path,
+        '--binary',
+        unstripped_library_path,
+        '--platform',
+        'android',
+    ]
+    return_code = subprocess.call(cmd)
+    if return_code != 0:
+      logging.error('Could not extract symbols, command failed: %s',
+                    ' '.join(cmd))
+  return syms_dir
+
+
+def _ChooseLatestCrashpadDump(device, crashpad_dump_path):
+  if not device.PathExists(crashpad_dump_path):
+    logging.warning('Crashpad dump directory does not exist: %s',
+                    crashpad_dump_path)
+    return None
+  latest = None
+  latest_timestamp = 0
+  for crashpad_file in device.ListDirectory(crashpad_dump_path):
+    if crashpad_file.endswith('.dmp'):
+      stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file))
+      current_timestamp = stat['st_mtime']
+      if current_timestamp > latest_timestamp:
+        latest_timestamp = current_timestamp
+        latest = crashpad_file
+  return latest
+
+
+def _ExtractLibraryNamesFromDump(build_path, dump_path):
+  default_library_name = 'libmonochrome.so'
+  dumper_path = os.path.join(build_path, 'minidump_dump')
+  if not os.access(dumper_path, os.X_OK):
+    logging.warning(
+        'Cannot extract library name from dump because %s is not found, '
+        'default to: %s', dumper_path, default_library_name)
+    return [default_library_name]
+  p = subprocess.Popen([dumper_path, dump_path],
+                       stdout=subprocess.PIPE,
+                       stderr=subprocess.PIPE)
+  stdout, stderr = p.communicate()
+  if p.returncode != 0:
+    # Dumper errors often do not affect stack walkability, just a warning.
+    logging.warning('Reading minidump failed with output:\n%s', stderr)
+
+  library_names = []
+  module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
+                                      r'"(?Plib[^. ]+.so)"')
+  in_module = False
+  for line in stdout.splitlines():
+    line = line.lstrip().rstrip('\n')
+    if line == 'MDRawModule':
+      in_module = True
+      continue
+    if line == '':
+      in_module = False
+      continue
+    if in_module:
+      m = module_library_line_re.match(line)
+      if m:
+        library_names.append(m.group('library_name'))
+  if not library_names:
+    logging.warning(
+        'Could not find any library name in the dump, '
+        'default to: %s', default_library_name)
+    return [default_library_name]
+  return library_names
+
+
+def main():
+  logging.basicConfig(level=logging.INFO)
+  parser = argparse.ArgumentParser(
+      description='Fetches Crashpad dumps from a given device, '
+      'walks and symbolizes the stacks.')
+  parser.add_argument('--device', required=True, help='Device serial number')
+  parser.add_argument('--adb-path', help='Path to the "adb" command')
+  parser.add_argument(
+      '--build-path',
+      required=True,
+      help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR')
+  parser.add_argument(
+      '--chrome-cache-path',
+      required=True,
+      help='Directory on the device where Chrome stores cached files,'
+      ' crashpad stores dumps in a subdirectory of it')
+  args = parser.parse_args()
+
+  stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk')
+  if not os.path.exists(stackwalk_path):
+    logging.error('Missing minidump_stackwalk executable')
+    return 1
+
+  devil_chromium.Initialize(output_directory=args.build_path,
+                            adb_path=args.adb_path)
+  device = device_utils.DeviceUtils(args.device)
+
+  device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
+                                        'pending')
+
+  def CrashpadDumpExists():
+    return _ChooseLatestCrashpadDump(device, device_crashpad_path)
+
+  crashpad_file = timeout_retry.WaitFor(
+      CrashpadDumpExists, wait_period=1, max_tries=9)
+  if not crashpad_file:
+    logging.error('Could not locate a crashpad dump')
+    return 1
+
+  dump_dir = tempfile.mkdtemp()
+  symbols_dir = None
+  try:
+    device.PullFile(
+        device_path=posixpath.join(device_crashpad_path, crashpad_file),
+        host_path=dump_dir)
+    dump_full_path = os.path.join(dump_dir, crashpad_file)
+    library_names = _ExtractLibraryNamesFromDump(args.build_path,
+                                                 dump_full_path)
+    symbols_dir = _CreateSymbolsDir(args.build_path, library_names)
+    stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir]
+    subprocess.call(stackwalk_cmd)
+  finally:
+    shutil.rmtree(dump_dir, ignore_errors=True)
+    if symbols_dir:
+      shutil.rmtree(symbols_dir, ignore_errors=True)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java b/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java
new file mode 100644
index 0000000000..baa931328b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java
@@ -0,0 +1,116 @@
+// Copyright 2017 The Chromium 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.build;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import proguard.retrace.ReTrace;
+
+/**
+ * A wrapper around ReTrace that:
+ *  1. Hardcodes a more useful line regular expression
+ *  2. Disables output buffering
+ */
+public class FlushingReTrace {
+    // E.g.: D/ConnectivityService(18029): Message
+    // E.g.: W/GCM     ( 151): Message
+    // E.g.: 09-08 14:22:59.995 18029 18055 I ProcessStatsService: Message
+    // E.g.: 09-08 14:30:59.145 17731 18020 D MDnsDS  : Message
+    private static final String LOGCAT_PREFIX =
+            "(?:[VDIWEF]/.*?\\( *\\d+\\): |\\d\\d-\\d\\d [0-9:. ]+[VDIWEF] .*?: )?";
+
+    // Note: Order of these sub-patterns defines their precedence.
+    // Note: Deobfuscation of methods without the presense of line numbers basically never works.
+    // There is a test for these pattern at //build/android/stacktrace/java_deobfuscate_test.py
+    private static final String LINE_PARSE_REGEX =
+            // Eagerly match logcat prefix to avoid conflicting with the patterns below.
+            LOGCAT_PREFIX
+            + "(?:"
+            // Based on default ReTrace regex, but with whitespaces allowed in file:line parentheses
+            // and "at" changed to to allow :
+            // E.g.: 06-22 13:58:02.895  4674  4674 E THREAD_STATE:     bLA.a( PG : 173 )
+            // Normal stack trace lines look like:
+            // \tat org.chromium.chrome.browser.tab.Tab.handleJavaCrash(Tab.java:682)
+            + "(?:.*?(?::|\\bat)\\s+%c\\.%m\\s*\\(\\s*%s(?:\\s*:\\s*%l\\s*)?\\))|"
+            // E.g.: Caused by: java.lang.NullPointerException: Attempt to read from field 'int bLA'
+            // on a null object reference
+            + "(?:.*java\\.lang\\.NullPointerException.*[\"']%t\\s*%c\\.(?:%f|%m\\(%a\\))[\"'].*)|"
+            // E.g.: java.lang.VerifyError: bLA
+            + "(?:java\\.lang\\.VerifyError: %c)|"
+            // E.g.: java.lang.NoSuchFieldError: No instance field e of type L...; in class LbxK;
+            + "(?:java\\.lang\\.NoSuchFieldError: No instance field %f of type .*? in class L%C;)|"
+            // E.g.: Object of type Clazz was not destroyed... (See LifetimeAssert.java)
+            + "(?:.*?Object of type %c .*)|"
+            // E.g.: VFY: unable to resolve new-instance 3810 (LSome/Framework/Class;) in Lfoo/Bar;
+            + "(?:.*L%C;.*)|"
+            // E.g.: END SomeTestClass#someMethod
+            + "(?:.*?%c#%m.*?)|"
+            // Special-case for a common junit logcat message:
+            // E.g.: java.lang.NoClassDefFoundError: SomeFrameworkClass in isTestClass for Foo
+            + "(?:.* isTestClass for %c)|"
+            // E.g.: Caused by: java.lang.RuntimeException: Intentional Java Crash
+            + "(?:Caused by: %c:.*)|"
+            // Quoted values and lines that end with a class / class+method:
+            // E.g.: The class: Foo
+            // E.g.: INSTRUMENTATION_STATUS: class=Foo
+            // E.g.: NoClassDefFoundError: SomeFrameworkClass in isTestClass for Foo
+            // E.g.: Could not find class 'SomeFrameworkClass', referenced from method Foo.bar
+            // E.g.: Could not find method SomeFrameworkMethod, referenced from method Foo.bar
+            // E.g.: The member "Foo.bar"
+            // E.g.: The class "Foobar"
+            // Be careful about matching %c without %m since language tags look like class names.
+            + "(?:.*?%c\\.%m)|"
+            + "(?:.*?\"%c\\.%m\".*)|"
+            + "(?:.*\\b(?:[Cc]lass|[Tt]ype)\\b.*?\"%c\".*)|"
+            + "(?:.*\\b(?:[Cc]lass|[Tt]ype)\\b.*?%c)|"
+            // E.g.: java.lang.RuntimeException: Intentional Java Crash
+            + "(?:%c:.*)|"
+            // See if entire line matches a class name (e.g. for manual deobfuscation)
+            + "(?:%c)"
+            + ")";
+
+    private static void usage() {
+        System.err.println("Usage: echo $OBFUSCATED_CLASS | java_deobfuscate Foo.apk.mapping");
+        System.err.println("Usage: java_deobfuscate Foo.apk.mapping < foo.log");
+        System.err.println("Note: Deobfuscation of symbols outside the context of stack "
+                + "traces will work only when lines match the regular expression defined "
+                + "in FlushingReTrace.java.");
+        System.err.println("Also: Deobfuscation of method names without associated line "
+                + "numbers does not seem to work.");
+        System.exit(1);
+    }
+
+    public static void main(String[] args) {
+        if (args.length != 1 || args[0].startsWith("-")) {
+            usage();
+        }
+
+        File mappingFile = new File(args[0]);
+        try {
+            LineNumberReader reader = new LineNumberReader(
+                    new BufferedReader(new InputStreamReader(System.in, "UTF-8")));
+
+            // Enabling autoFlush is the main difference from ReTrace.main().
+            boolean autoFlush = true;
+            PrintWriter writer =
+                    new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), autoFlush);
+
+            boolean verbose = false;
+            new ReTrace(LINE_PARSE_REGEX, verbose, mappingFile).retrace(reader, writer);
+        } catch (IOException ex) {
+            // Print a verbose stack trace.
+            ex.printStackTrace();
+            System.exit(1);
+        }
+
+        System.exit(0);
+    }
+}
diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar
new file mode 100644
index 0000000000..36a1b706a3
Binary files /dev/null and b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar differ
diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py
new file mode 100755
index 0000000000..8c231ecfcc
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 The Chromium 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 script for java_deobfuscate.
+
+This is also a buildable target, but having it pre-built here simplifies usage.
+"""
+
+import os
+import sys
+
+DIR_SOURCE_ROOT = os.path.normpath(
+    os.path.join(os.path.dirname(__file__), '../../../'))
+
+
+def main():
+  classpath = [
+      os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'stacktrace',
+                   'java_deobfuscate.jar'),
+      os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib',
+                   'proguard603.jar'),
+      os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib',
+                   'retrace603.jar'),
+  ]
+  java_path = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current',
+                           'bin', 'java')
+
+  cmd = [
+      java_path, '-classpath', ':'.join(classpath),
+      'org.chromium.build.FlushingReTrace'
+  ]
+  cmd.extend(sys.argv[1:])
+  os.execvp(cmd[0], cmd)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py
new file mode 100755
index 0000000000..d68323f129
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env vpython3
+#
+# Copyright 2017 The Chromium 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 java_deobfuscate."""
+
+import argparse
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+
+# Set by command-line argument.
+_JAVA_DEOBFUSCATE_PATH = None
+
+LINE_PREFIXES = [
+    '',
+    # logcat -v threadtime
+    '09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ',
+    # logcat
+    'W/GCM     (15158): ',
+    'W/GCM     (  158): ',
+]
+
+TEST_MAP = """\
+this.was.Deobfuscated -> FOO:
+    int[] mFontFamily -> a
+    1:3:void someMethod(int,android.os.Bundle):65:67 -> bar
+never.Deobfuscated -> NOTFOO:
+    int[] mFontFamily -> a
+    1:3:void someMethod(int,android.os.Bundle):65:67 -> bar
+"""
+
+TEST_DATA = [
+    '',
+    'FOO',
+    'FOO.bar',
+    'Here is a FOO',
+    'Here is a class FOO',
+    'Here is a class FOO baz',
+    'Here is a "FOO" baz',
+    'Here is a type "FOO" baz',
+    'Here is a "FOO.bar" baz',
+    'SomeError: SomeFrameworkClass in isTestClass for FOO',
+    'Here is a FOO.bar',
+    'Here is a FOO.bar baz',
+    'END FOO#bar',
+    'new-instance 3810 (LSome/Framework/Class;) in LFOO;',
+    'FOO: Error message',
+    'Caused by: FOO: Error message',
+    '\tat FOO.bar(PG:1)',
+    '\t at\t FOO.bar\t (\t PG:\t 1\t )',
+    ('Unable to start activity ComponentInfo{garbage.in/here.test}:'
+     ' java.lang.NullPointerException: Attempt to invoke interface method'
+     ' \'void FOO.bar(int,android.os.Bundle)\' on a null object reference'),
+    ('Caused by: java.lang.NullPointerException: Attempt to read from field'
+     ' \'int[] FOO.a\' on a null object reference'),
+    'java.lang.VerifyError: FOO',
+    ('java.lang.NoSuchFieldError: No instance field a of type '
+     'Ljava/lang/Class; in class LFOO;'),
+    'NOTFOO: Object of type FOO was not destroyed...',
+]
+
+EXPECTED_OUTPUT = [
+    '',
+    'this.was.Deobfuscated',
+    'this.was.Deobfuscated.someMethod',
+    'Here is a FOO',
+    'Here is a class this.was.Deobfuscated',
+    'Here is a class FOO baz',
+    'Here is a "FOO" baz',
+    'Here is a type "this.was.Deobfuscated" baz',
+    'Here is a "this.was.Deobfuscated.someMethod" baz',
+    'SomeError: SomeFrameworkClass in isTestClass for this.was.Deobfuscated',
+    'Here is a this.was.Deobfuscated.someMethod',
+    'Here is a FOO.bar baz',
+    'END this.was.Deobfuscated#someMethod',
+    'new-instance 3810 (LSome/Framework/Class;) in Lthis/was/Deobfuscated;',
+    'this.was.Deobfuscated: Error message',
+    'Caused by: this.was.Deobfuscated: Error message',
+    '\tat this.was.Deobfuscated.someMethod(Deobfuscated.java:65)',
+    ('\t at\t this.was.Deobfuscated.someMethod\t '
+     '(\t Deobfuscated.java:\t 65\t )'),
+    ('Unable to start activity ComponentInfo{garbage.in/here.test}:'
+     ' java.lang.NullPointerException: Attempt to invoke interface method'
+     ' \'void this.was.Deobfuscated.someMethod(int,android.os.Bundle)\' on a'
+     ' null object reference'),
+    ('Caused by: java.lang.NullPointerException: Attempt to read from field'
+     ' \'int[] this.was.Deobfuscated.mFontFamily\' on a null object reference'),
+    'java.lang.VerifyError: this.was.Deobfuscated',
+    ('java.lang.NoSuchFieldError: No instance field mFontFamily of type '
+     'Ljava/lang/Class; in class Lthis/was/Deobfuscated;'),
+    'NOTFOO: Object of type this.was.Deobfuscated was not destroyed...',
+]
+TEST_DATA = [s + '\n' for s in TEST_DATA]
+EXPECTED_OUTPUT = [s + '\n' for s in EXPECTED_OUTPUT]
+
+
+class JavaDeobfuscateTest(unittest.TestCase):
+
+  def __init__(self, *args, **kwargs):
+    super(JavaDeobfuscateTest, self).__init__(*args, **kwargs)
+    self._map_file = None
+
+  def setUp(self):
+    self._map_file = tempfile.NamedTemporaryFile()
+    self._map_file.write(TEST_MAP)
+    self._map_file.flush()
+
+  def tearDown(self):
+    if self._map_file:
+      self._map_file.close()
+
+  def _testImpl(self, input_lines=None, expected_output_lines=None,
+                prefix=''):
+    self.assertTrue(bool(input_lines) == bool(expected_output_lines))
+
+    if not input_lines:
+      input_lines = [prefix + x for x in TEST_DATA]
+    if not expected_output_lines:
+      expected_output_lines = [prefix + x for x in EXPECTED_OUTPUT]
+
+    cmd = [_JAVA_DEOBFUSCATE_PATH, self._map_file.name]
+    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+    proc_output, _ = proc.communicate(''.join(input_lines))
+    actual_output_lines = proc_output.splitlines(True)
+    for actual, expected in zip(actual_output_lines, expected_output_lines):
+      self.assertTrue(
+          actual == expected or actual.replace('bar', 'someMethod') == expected,
+          msg=''.join([
+              'Deobfuscation failed.\n',
+              '  actual:   %s' % actual,
+              '  expected: %s' % expected]))
+
+  def testNoPrefix(self):
+    self._testImpl(prefix='')
+
+  def testThreadtimePrefix(self):
+    self._testImpl(prefix='09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ')
+
+  def testStandardPrefix(self):
+    self._testImpl(prefix='W/GCM     (15158): ')
+
+  def testStandardPrefixWithPadding(self):
+    self._testImpl(prefix='W/GCM     (  158): ')
+
+  @unittest.skip('causes java_deobfuscate to hang, see crbug.com/876539')
+  def testIndefiniteHang(self):
+    # Test for crbug.com/876539.
+    self._testImpl(
+        input_lines=[
+            'VFY: unable to resolve virtual method 2: LFOO;'
+                + '.onDescendantInvalidated '
+                + '(Landroid/view/View;Landroid/view/View;)V',
+        ],
+        expected_output_lines=[
+            'VFY: unable to resolve virtual method 2: Lthis.was.Deobfuscated;'
+                + '.onDescendantInvalidated '
+                + '(Landroid/view/View;Landroid/view/View;)V',
+        ])
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--java-deobfuscate-path', type=os.path.realpath,
+                      required=True)
+  known_args, unittest_args = parser.parse_known_args()
+  _JAVA_DEOBFUSCATE_PATH = known_args.java_deobfuscate_path
+  unittest_args = [sys.argv[0]] + unittest_args
+  unittest.main(argv=unittest_args)
diff --git a/third_party/libwebrtc/build/android/stacktrace/stackwalker.py b/third_party/libwebrtc/build/android/stacktrace/stackwalker.py
new file mode 100755
index 0000000000..16b514901b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/stacktrace/stackwalker.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env vpython3
+#
+# 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.
+
+from __future__ import print_function
+
+import argparse
+import os
+import re
+import sys
+import tempfile
+
+if __name__ == '__main__':
+  sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+from pylib.constants import host_paths
+
+if host_paths.DEVIL_PATH not in sys.path:
+  sys.path.append(host_paths.DEVIL_PATH)
+from devil.utils import cmd_helper
+
+
+_MICRODUMP_BEGIN = re.compile(
+    '.*google-breakpad: -----BEGIN BREAKPAD MICRODUMP-----')
+_MICRODUMP_END = re.compile(
+    '.*google-breakpad: -----END BREAKPAD MICRODUMP-----')
+
+""" Example Microdump
+  6270  6131 F google-breakpad: -----BEGIN BREAKPAD MICRODUMP-----
+  6270  6131 F google-breakpad: V Chrome_Android:54.0.2790.0
+...
+  6270  6131 F google-breakpad: -----END BREAKPAD MICRODUMP-----
+
+"""
+
+
+def GetMicroDumps(dump_path):
+  """Returns all microdumps found in given log file
+
+  Args:
+    dump_path: Path to the log file.
+
+  Returns:
+    List of all microdumps as lists of lines.
+  """
+  with open(dump_path, 'r') as d:
+    data = d.read()
+  all_dumps = []
+  current_dump = None
+  for line in data.splitlines():
+    if current_dump is not None:
+      if _MICRODUMP_END.match(line):
+        current_dump.append(line)
+        all_dumps.append(current_dump)
+        current_dump = None
+      else:
+        current_dump.append(line)
+    elif _MICRODUMP_BEGIN.match(line):
+      current_dump = []
+      current_dump.append(line)
+  return all_dumps
+
+
+def SymbolizeMicroDump(stackwalker_binary_path, dump, symbols_path):
+  """Runs stackwalker on microdump.
+
+  Runs the stackwalker binary at stackwalker_binary_path on a given microdump
+  using the symbols at symbols_path.
+
+  Args:
+    stackwalker_binary_path: Path to the stackwalker binary.
+    dump: The microdump to run the stackwalker on.
+    symbols_path: Path the the symbols file to use.
+
+  Returns:
+    Output from stackwalker tool.
+  """
+  with tempfile.NamedTemporaryFile() as tf:
+    for l in dump:
+      tf.write('%s\n' % l)
+    cmd = [stackwalker_binary_path, tf.name, symbols_path]
+    return cmd_helper.GetCmdOutput(cmd)
+
+
+def AddArguments(parser):
+  parser.add_argument('--stackwalker-binary-path', required=True,
+                      help='Path to stackwalker binary.')
+  parser.add_argument('--stack-trace-path', required=True,
+                      help='Path to stacktrace containing microdump.')
+  parser.add_argument('--symbols-path', required=True,
+                      help='Path to symbols file.')
+  parser.add_argument('--output-file',
+                      help='Path to dump stacktrace output to')
+
+
+def _PrintAndLog(line, fp):
+  if fp:
+    fp.write('%s\n' % line)
+  print(line)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  AddArguments(parser)
+  args = parser.parse_args()
+
+  micro_dumps = GetMicroDumps(args.stack_trace_path)
+  if not micro_dumps:
+    print('No microdump found. Exiting.')
+    return 0
+
+  symbolized_dumps = []
+  for micro_dump in micro_dumps:
+    symbolized_dumps.append(SymbolizeMicroDump(
+        args.stackwalker_binary_path, micro_dump, args.symbols_path))
+
+  try:
+    fp = open(args.output_file, 'w') if args.output_file else None
+    _PrintAndLog('%d microdumps found.' % len(micro_dumps), fp)
+    _PrintAndLog('---------- Start output from stackwalker ----------', fp)
+    for index, symbolized_dump in list(enumerate(symbolized_dumps)):
+      _PrintAndLog(
+          '------------------ Start dump %d ------------------' % index, fp)
+      _PrintAndLog(symbolized_dump, fp)
+      _PrintAndLog(
+          '------------------- End dump %d -------------------' % index, fp)
+    _PrintAndLog('----------- End output from stackwalker -----------', fp)
+  except Exception:
+    if fp:
+      fp.close()
+    raise
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/test/BUILD.gn b/third_party/libwebrtc/build/android/test/BUILD.gn
new file mode 100644
index 0000000000..b4bda3d8c4
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/BUILD.gn
@@ -0,0 +1,95 @@
+# Copyright 2021 The Chromium 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/android_nocompile.gni")
+import("missing_symbol_test.gni")
+import("nocompile_gn/nocompile_sources.gni")
+
+if (enable_java_templates) {
+  group("android_nocompile_tests") {
+    testonly = true
+
+    # No-compile tests use an output directory dedicated to no-compile tests.
+    # All test suites use targets in nocompile_gn/BUILD.gn in order to share the
+    # same target output directory and avoid running 'gn gen' for each
+    # android_nocompile_test_suite().
+    deps = [
+      ":android_lint_tests",
+      ":android_lookup_dep_tests",
+    ]
+  }
+
+  android_nocompile_test_suite("android_lint_tests") {
+    # Depend on lint script so that the action is re-run whenever the script is  modified.
+    pydeps = [ "//build/android/gyp/lint.pydeps" ]
+
+    tests = [
+      {
+        target = "nocompile_gn:default_locale_lint_test"
+        nocompile_sources =
+            rebase_path(default_locale_lint_test_nocompile_sources,
+                        "",
+                        "nocompile_gn")
+        expected_compile_output_regex = "Warning:.*DefaultLocale"
+      },
+      {
+        target = "nocompile_gn:new_api_lint_test"
+        nocompile_sources =
+            rebase_path(new_api_lint_test_nocompile_sources, "", "nocompile_gn")
+        expected_compile_output_regex = "Error:.*NewApi"
+      },
+    ]
+  }
+
+  android_nocompile_test_suite("android_lookup_dep_tests") {
+    sources = [ rebase_path(
+            missing_symbol_generated_importer_template_nocompile_source,
+            "",
+            "nocompile_gn") ]
+
+    tests = [
+      {
+        target = "nocompile_gn:import_child_missing_symbol_test_java"
+        nocompile_sources =
+            rebase_path(import_child_missing_symbol_test_nocompile_sources,
+                        "",
+                        "nocompile_gn")
+        expected_compile_output_regex = "error: package test\.missing_symbol\.sub does not exist\nPlease add //build/android/test/nocompile_gn:sub_b_java dep to //build/android/test/nocompile_gn:import_child_missing_symbol_test_java\."
+      },
+      {
+        target = "nocompile_gn:import_parent_missing_symbol_test_java"
+        nocompile_sources = []
+        expected_compile_output_regex = "error: cannot find symbol test\.missing_symbol\.B\nPlease add //build/android/test/nocompile_gn:b_java dep to //build/android/test/nocompile_gn:import_parent_missing_symbol_test_java\."
+      },
+      {
+        target = "nocompile_gn:import_turbine_missing_symbol_test_java"
+        nocompile_sources =
+            rebase_path(import_turbine_missing_symbol_test_nocompile_sources,
+                        "",
+                        "nocompile_gn")
+        expected_compile_output_regex = "error: symbol not found test\.missing_symbol\.B\nPlease add //build/android/test/nocompile_gn:b_java dep to //build/android/test/nocompile_gn:import_turbine_missing_symbol_test_java\."
+      },
+      {
+        target = "nocompile_gn:prebuilt_missing_symbol_test_java"
+        nocompile_sources = []
+        expected_compile_output_regex = "error: cannot find symbol test\.missing_symbol\.C\nPlease add //build/android/test/nocompile_gn:c_prebuilt_java dep to //build/android/test/nocompile_gn:prebuilt_missing_symbol_test_java\."
+      },
+      {
+        target = "nocompile_gn:cpp_template_missing_symbol_test_java"
+        nocompile_sources = []
+        expected_compile_output_regex = "error: cannot find symbol test\.missing_symbol\.D\nPlease add //build/android/test/nocompile_gn:d_java dep to //build/android/test/nocompile_gn:cpp_template_missing_symbol_test_java\."
+      },
+    ]
+  }
+
+  # Tests that builds which use incremental javac are valid.
+  junit_binary("incremental_javac_junit_tests") {
+    sources = [ "../java/test/IncrementalJavacTest.java" ]
+    deps = [
+      "incremental_javac_gn:no_signature_change_prebuilt_java",
+      "//base:base_junit_test_support",
+      "//third_party/junit",
+    ]
+  }
+}
diff --git a/third_party/libwebrtc/build/android/test/incremental_javac_gn/BUILD.gn b/third_party/libwebrtc/build/android/test/incremental_javac_gn/BUILD.gn
new file mode 100644
index 0000000000..18c6374486
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/incremental_javac_gn/BUILD.gn
@@ -0,0 +1,98 @@
+# Copyright 2021 The Chromium 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")
+
+declare_args() {
+  incremental_javac_test_toggle_gn = false
+}
+
+all_test_sources = [
+  "../../java/test/NoSignatureChangeIncrementalJavacTestHelper.template",
+  "../../java/test/NoSignatureChangeIncrementalJavacTestHelper2.java",
+]
+
+template("incremental_javac_prebuilt") {
+  _out_jar = "${target_gen_dir}/${target_name}.jar"
+
+  action(target_name) {
+    script = "incremental_javac_test_android_library.py"
+    forward_variables_from(invoker,
+                           [
+                             "sources",
+                             "testonly",
+                           ])
+    deps = [ invoker.toggle_gn_target ]
+
+    inputs = []
+    if (defined(invoker.pydeps)) {
+      foreach(_pydeps_file, invoker.pydeps) {
+        _pydeps_file_lines = []
+        _pydeps_file_lines = read_file(_pydeps_file, "list lines")
+        _pydeps_entries = []
+        _pydeps_entries = filter_exclude(_pydeps_file_lines, [ "#*" ])
+        _pydeps_file_dir = get_path_info(_pydeps_file, "dir")
+        inputs += rebase_path(_pydeps_entries, ".", _pydeps_file_dir)
+      }
+    }
+
+    outputs = [ _out_jar ]
+
+    args = [
+      "--target-name",
+      get_label_info("${invoker.toggle_gn_target}", "label_no_toolchain"),
+      "--gn-args-path",
+      "args.gn",
+      "--out-dir",
+      rebase_path("${target_out_dir}/${target_name}/incremental_javac_out",
+                  root_build_dir),
+      "--out-jar",
+      rebase_path(_out_jar, root_build_dir),
+    ]
+  }
+}
+
+# Use jinja_template() instead of java_cpp_template() because incremental builds
+# are not done when non-.java files change.
+jinja_template("changing_javagen") {
+  input = "../../java/test/NoSignatureChangeIncrementalJavacTestHelper.template"
+  assert(filter_include(all_test_sources, [ input ]) != [])
+  output =
+      "${target_gen_dir}/test/NoSignatureChangeIncrementalJavacTestHelper.java"
+  if (incremental_javac_test_toggle_gn) {
+    variables = [ "foo_return_value=foo2" ]
+  } else {
+    variables = [ "foo_return_value=foo" ]
+  }
+}
+
+android_library("changing_java") {
+  testonly = true
+
+  # Should not be re-compiled during incremental build.
+  sources =
+      [ "../../java/test/NoSignatureChangeIncrementalJavacTestHelper2.java" ]
+  assert(filter_include(all_test_sources, sources) != [])
+
+  # Should be recompiled during incremental build.
+  sources += get_target_outputs(":changing_javagen")
+  deps = [ ":changing_javagen" ]
+}
+
+# Compiles :changing_java with and without |incremental_javac_test_toggle_gn|.
+incremental_javac_prebuilt("no_signature_change_prebuilt_generator") {
+  testonly = true
+  sources = all_test_sources
+  toggle_gn_target = ":changing_java"
+  pydeps = [ "//build/android/gyp/compile_java.pydeps" ]
+}
+
+android_java_prebuilt("no_signature_change_prebuilt_java") {
+  testonly = true
+  _generator_outputs =
+      get_target_outputs(":no_signature_change_prebuilt_generator")
+  jar_paths = filter_include(_generator_outputs, [ "*.jar" ])
+  jar_path = jar_paths[0]
+  deps = [ ":no_signature_change_prebuilt_generator" ]
+}
diff --git a/third_party/libwebrtc/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py b/third_party/libwebrtc/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py
new file mode 100755
index 0000000000..c84cff0d3b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+#
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Compiles twice: With incremental_javac_test_toggle_gn=[false, true]
+
+The purpose of compiling the target twice is to test that builds generated by
+the incremental build code path are valid.
+"""
+
+import argparse
+import os
+import pathlib
+import subprocess
+import shutil
+
+_CHROMIUM_SRC = pathlib.Path(__file__).resolve().parents[4].resolve()
+_NINJA_PATH = _CHROMIUM_SRC / 'third_party' / 'depot_tools' / 'ninja'
+
+# Relative to _CHROMIUM_SRC
+_GN_SRC_REL_PATH = 'third_party/depot_tools/gn'
+
+_USING_PARTIAL_JAVAC_MSG = 'Using partial javac optimization'
+
+
+def _raise_command_exception(args, returncode, output):
+  """Raises an exception whose message describes a command failure.
+
+    Args:
+      args: shell command-line (as passed to subprocess.Popen())
+      returncode: status code.
+      output: command output.
+    Raises:
+      a new Exception.
+    """
+  message = ('Command failed with status {}: {}\n'
+             'Output:-----------------------------------------\n{}\n'
+             '------------------------------------------------\n').format(
+                 returncode, args, output)
+  raise Exception(message)
+
+
+def _run_command(args, check_returncode=True, cwd=None, env=None):
+  """Runs shell command. Raises exception if command fails."""
+  p = subprocess.Popen(args,
+                       stdout=subprocess.PIPE,
+                       stderr=subprocess.STDOUT,
+                       cwd=cwd,
+                       env=env,
+                       universal_newlines=True)
+  pout, _ = p.communicate()
+  if check_returncode and p.returncode != 0:
+    _raise_command_exception(args, p.returncode, pout)
+  return pout
+
+
+def _copy_and_append_gn_args(src_args_path, dest_args_path, extra_args):
+  """Copies args.gn.
+
+    Args:
+      src_args_path: args.gn file to copy.
+      dest_args_path: Copy file destination.
+      extra_args: Text to append to args.gn after copy.
+    """
+  with open(src_args_path) as f:
+    initial_args_str = f.read()
+
+  with open(dest_args_path, 'w') as f:
+    f.write(initial_args_str)
+    f.write('\n')
+
+    # Write |extra_args| after |initial_args_str| so that |extra_args|
+    # overwrites |initial_args_str| in the case of duplicate entries.
+    f.write('\n'.join(extra_args))
+
+
+def _run_gn(args, check_returncode=True):
+  _run_command([_GN_SRC_REL_PATH] + args,
+               check_returncode=check_returncode,
+               cwd=_CHROMIUM_SRC)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--target-name',
+                      required=True,
+                      help='name of target to build with and without ' +
+                      'incremental_javac_test_toggle_gn=true')
+  parser.add_argument('--gn-args-path',
+                      required=True,
+                      help='Path to args.gn file to copy args from.')
+  parser.add_argument('--out-dir',
+                      required=True,
+                      help='Path to output directory to use for compilation.')
+  parser.add_argument('--out-jar',
+                      required=True,
+                      help='Path where output jar should be stored.')
+  options = parser.parse_args()
+
+  options.out_dir = pathlib.Path(options.out_dir).resolve()
+
+  options.out_dir.mkdir(parents=True, exist_ok=True)
+
+  # Clear the output directory so that first compile is not an incremental
+  # build.
+  # This will make the test fail in the scenario that:
+  # - The output directory contains a previous build generated by this script.
+  # - Incremental builds are broken and are a no-op.
+  _run_gn(['clean', options.out_dir.relative_to(_CHROMIUM_SRC)],
+          check_returncode=False)
+
+  out_gn_args_path = options.out_dir / 'args.gn'
+  extra_gn_args = [
+      'treat_warnings_as_errors = true',
+      # GOMA does not work with non-standard output directories.
+      'use_goma = false',
+  ]
+  _copy_and_append_gn_args(options.gn_args_path, out_gn_args_path,
+                           extra_gn_args)
+
+  _run_gn([
+      '--root-target=' + options.target_name, 'gen',
+      options.out_dir.relative_to(_CHROMIUM_SRC)
+  ])
+
+  ninja_env = os.environ.copy()
+  ninja_env['JAVAC_DEBUG'] = '1'
+
+  # Strip leading '//'
+  gn_path = options.target_name[2:]
+  ninja_args = [_NINJA_PATH, '-C', options.out_dir, gn_path]
+  ninja_output = _run_command(ninja_args, env=ninja_env)
+  if _USING_PARTIAL_JAVAC_MSG in ninja_output:
+    raise Exception("Incorrectly using partial javac for clean compile.")
+
+  _copy_and_append_gn_args(
+      options.gn_args_path, out_gn_args_path,
+      extra_gn_args + ['incremental_javac_test_toggle_gn = true'])
+  ninja_output = _run_command(ninja_args, env=ninja_env)
+  if _USING_PARTIAL_JAVAC_MSG not in ninja_output:
+    raise Exception("Not using partial javac for incremental compile.")
+
+  expected_output_path = "{}/lib.java/{}.jar".format(options.out_dir,
+                                                     gn_path.replace(':', '/'))
+  if not os.path.exists(expected_output_path):
+    raise Exception("{} not created.".format(expected_output_path))
+
+  shutil.copyfile(expected_output_path, options.out_jar)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/third_party/libwebrtc/build/android/test/missing_symbol_test.gni b/third_party/libwebrtc/build/android/test/missing_symbol_test.gni
new file mode 100644
index 0000000000..a11eef3cf6
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/missing_symbol_test.gni
@@ -0,0 +1,57 @@
+# Copyright 2021 The Chromium 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/android_nocompile.gni")
+import("//build/config/android/rules.gni")
+
+missing_symbol_generated_importer_template_nocompile_source =
+    "//build/android/java/test/missing_symbol/Importer.template"
+
+template("missing_symbol_test") {
+  # Not named "_java" to prevent target from being considered a classpath dep.
+  _helper_target_name = string_replace("${target_name}__helper", "java", "")
+
+  group(_helper_target_name) {
+    # Make group() depend on dependencies that |target_name| cannot find so that
+    # the missing symbol resolver can find and suggest the missing GN dep.
+    deps = invoker.deps
+  }
+
+  android_library(target_name) {
+    sources = [ "//tools/android/errorprone_plugin/test/src/org/chromium/tools/errorprone/plugin/Empty.java" ]
+    not_needed(invoker,
+               [
+                 "sources",
+                 "importer_srcjar_deps",
+               ])
+    if (enable_android_nocompile_tests) {
+      if (defined(invoker.sources)) {
+        sources += invoker.sources
+      }
+      if (defined(invoker.importer_srcjar_deps)) {
+        srcjar_deps = invoker.importer_srcjar_deps
+      }
+    }
+
+    deps = [ ":${_helper_target_name}" ]
+  }
+}
+
+# missing_symbol_test() template wrapper which generates importer class.
+template("missing_symbol_generated_importer_test") {
+  _importer_generator_target = "${target_name}__importer_javagen"
+  java_cpp_template(_importer_generator_target) {
+    sources = [ missing_symbol_generated_importer_template_nocompile_source ]
+    defines = [
+      "_IMPORTER_PACKAGE=${invoker.importer_package}",
+      "_IMPORTEE_PACKAGE=${invoker.imported_package}",
+      "_IMPORTEE_CLASS_NAME=${invoker.imported_class_name}",
+    ]
+  }
+
+  missing_symbol_test(target_name) {
+    importer_srcjar_deps = [ ":${_importer_generator_target}" ]
+    forward_variables_from(invoker, [ "deps" ])
+  }
+}
diff --git a/third_party/libwebrtc/build/android/test/nocompile_gn/BUILD.gn b/third_party/libwebrtc/build/android/test/nocompile_gn/BUILD.gn
new file mode 100644
index 0000000000..11feef6a4c
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/nocompile_gn/BUILD.gn
@@ -0,0 +1,101 @@
+# Copyright 2021 The Chromium 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/android/test/missing_symbol_test.gni")
+import("//build/config/android/android_nocompile.gni")
+import("//build/config/android/rules.gni")
+import("nocompile_sources.gni")
+
+template("lint_test") {
+  _library_target_name = "${target_name}_test_java"
+  _apk_target_name = "${target_name}_apk"
+
+  android_library(_library_target_name) {
+    sources = [ "//tools/android/errorprone_plugin/test/src/org/chromium/tools/errorprone/plugin/Empty.java" ]
+    not_needed(invoker, [ "sources" ])
+    if (enable_android_nocompile_tests) {
+      sources += invoker.sources
+    }
+  }
+
+  android_apk(_apk_target_name) {
+    # This cannot be marked testonly since lint has special ignores for testonly
+    # targets. We need to test linting a normal apk target.
+    apk_name = _apk_target_name
+    deps = [ ":$_library_target_name" ]
+    android_manifest = "//build/android/AndroidManifest.xml"
+  }
+
+  android_lint(target_name) {
+    _apk_target = ":${_apk_target_name}"
+    deps = [ "${_apk_target}__java" ]
+    build_config_dep = "$_apk_target$build_config_target_suffix"
+    build_config = get_label_info(_apk_target, "target_gen_dir") + "/" +
+                   get_label_info(_apk_target, "name") + ".build_config.json"
+    if (enable_android_nocompile_tests) {
+      skip_build_server = true
+    }
+  }
+}
+
+lint_test("default_locale_lint_test") {
+  sources = default_locale_lint_test_nocompile_sources
+}
+
+lint_test("new_api_lint_test") {
+  sources = new_api_lint_test_nocompile_sources
+}
+
+missing_symbol_generated_importer_test(
+    "import_parent_missing_symbol_test_java") {
+  importer_package = "test.missing_symbol.child_missing"
+  imported_package = "test.missing_symbol"
+  imported_class_name = "B"
+  deps = [ ":b_java" ]
+}
+
+missing_symbol_test("import_child_missing_symbol_test_java") {
+  sources = import_child_missing_symbol_test_nocompile_sources
+  deps = [ ":sub_b_java" ]
+}
+
+missing_symbol_test("import_turbine_missing_symbol_test_java") {
+  sources = import_turbine_missing_symbol_test_nocompile_sources
+  deps = [ ":b_java" ]
+}
+
+missing_symbol_generated_importer_test("prebuilt_missing_symbol_test_java") {
+  importer_package = "test.missing_symbol.prebuilt_missing"
+  imported_package = "test.missing_symbol"
+  imported_class_name = "C"
+  deps = [ ":c_prebuilt_java" ]
+}
+
+missing_symbol_generated_importer_test(
+    "cpp_template_missing_symbol_test_java") {
+  importer_package = "test.missing_symbol.cpp_template_missing"
+  imported_package = "test.missing_symbol"
+  imported_class_name = "D"
+  deps = [ ":d_java" ]
+}
+
+android_library("b_java") {
+  sources = [ "../../java/test/missing_symbol/B.java" ]
+}
+
+android_library("sub_b_java") {
+  sources = [ "../../java/test/missing_symbol/sub/SubB.java" ]
+}
+
+android_java_prebuilt("c_prebuilt_java") {
+  jar_path = "../../java/test/missing_symbol/c.jar"
+}
+
+android_library("d_java") {
+  srcjar_deps = [ ":d_template_javagen" ]
+}
+
+java_cpp_template("d_template_javagen") {
+  sources = [ "../../java/test/missing_symbol/D.template" ]
+}
diff --git a/third_party/libwebrtc/build/android/test/nocompile_gn/nocompile_sources.gni b/third_party/libwebrtc/build/android/test/nocompile_gn/nocompile_sources.gni
new file mode 100644
index 0000000000..1338f19cd9
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test/nocompile_gn/nocompile_sources.gni
@@ -0,0 +1,14 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+default_locale_lint_test_nocompile_sources =
+    [ "../../java/test/DefaultLocaleLintTest.java" ]
+
+new_api_lint_test_nocompile_sources = [ "../../java/test/NewApiLintTest.java" ]
+
+import_child_missing_symbol_test_nocompile_sources =
+    [ "../../java/test/missing_symbol/ImportsSubB.java" ]
+
+import_turbine_missing_symbol_test_nocompile_sources =
+    [ "../../java/test/missing_symbol/sub/BInMethodSignature.java" ]
diff --git a/third_party/libwebrtc/build/android/test_runner.py b/third_party/libwebrtc/build/android/test_runner.py
new file mode 100755
index 0000000000..1e312ba0b8
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test_runner.py
@@ -0,0 +1,1194 @@
+#!/usr/bin/env vpython3
+#
+# 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."""
+
+from __future__ import absolute_import
+import argparse
+import collections
+import contextlib
+import itertools
+import logging
+import os
+import re
+import shutil
+import signal
+import sys
+import tempfile
+import threading
+import traceback
+import unittest
+
+# Import _strptime before threaded code. datetime.datetime.strptime is
+# threadsafe except for the initial import of the _strptime module.
+# See http://crbug.com/724524 and https://bugs.python.org/issue7980.
+import _strptime  # pylint: disable=unused-import
+
+# pylint: disable=ungrouped-imports
+from pylib.constants import host_paths
+
+if host_paths.DEVIL_PATH not in sys.path:
+  sys.path.append(host_paths.DEVIL_PATH)
+
+from devil import base_error
+from devil.utils import reraiser_thread
+from devil.utils import run_tests_helper
+
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import environment_factory
+from pylib.base import output_manager
+from pylib.base import output_manager_factory
+from pylib.base import test_instance_factory
+from pylib.base import test_run_factory
+from pylib.results import json_results
+from pylib.results import report_results
+from pylib.results.presentation import test_results_presentation
+from pylib.utils import local_utils
+from pylib.utils import logdog_helper
+from pylib.utils import logging_utils
+from pylib.utils import test_filter
+
+from py_utils import contextlib_ext
+
+from lib.results import result_sink  # pylint: disable=import-error
+
+_DEVIL_STATIC_CONFIG_FILE = os.path.abspath(os.path.join(
+    host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'devil_config.json'))
+
+
+def _RealPath(arg):
+  if arg.startswith('//'):
+    arg = os.path.abspath(os.path.join(host_paths.DIR_SOURCE_ROOT,
+                                       arg[2:].replace('/', os.sep)))
+  return os.path.realpath(arg)
+
+
+def AddTestLauncherOptions(parser):
+  """Adds arguments mirroring //base/test/launcher.
+
+  Args:
+    parser: The parser to which arguments should be added.
+  Returns:
+    The given parser.
+  """
+  parser.add_argument(
+      '--test-launcher-retry-limit',
+      '--test_launcher_retry_limit',
+      '--num_retries', '--num-retries',
+      '--isolated-script-test-launcher-retry-limit',
+      dest='num_retries', type=int, default=2,
+      help='Number of retries for a test before '
+           'giving up (default: %(default)s).')
+  parser.add_argument(
+      '--test-launcher-summary-output',
+      '--json-results-file',
+      dest='json_results_file', type=os.path.realpath,
+      help='If set, will dump results in JSON form to the specified file. '
+           'Note that this will also trigger saving per-test logcats to '
+           'logdog.')
+  parser.add_argument(
+      '--test-launcher-shard-index',
+      type=int, default=os.environ.get('GTEST_SHARD_INDEX', 0),
+      help='Index of the external shard to run.')
+  parser.add_argument(
+      '--test-launcher-total-shards',
+      type=int, default=os.environ.get('GTEST_TOTAL_SHARDS', 1),
+      help='Total number of external shards.')
+
+  test_filter.AddFilterOptions(parser)
+
+  return parser
+
+
+def AddCommandLineOptions(parser):
+  """Adds arguments to support passing command-line flags to the device."""
+  parser.add_argument(
+      '--device-flags-file',
+      type=os.path.realpath,
+      help='The relative filepath to a file containing '
+           'command-line flags to set on the device')
+  parser.add_argument(
+      '--use-apk-under-test-flags-file',
+      action='store_true',
+      help='Wether to use the flags file for the apk under test. If set, '
+           "the filename will be looked up in the APK's PackageInfo.")
+  parser.set_defaults(allow_unknown=True)
+  parser.set_defaults(command_line_flags=None)
+
+
+def AddTracingOptions(parser):
+  # TODO(shenghuazhang): Move this into AddCommonOptions once it's supported
+  # for all test types.
+  parser.add_argument(
+      '--trace-output',
+      metavar='FILENAME', type=os.path.realpath,
+      help='Path to save test_runner trace json output to.')
+
+  parser.add_argument(
+      '--trace-all',
+      action='store_true',
+      help='Whether to trace all function calls.')
+
+
+def AddCommonOptions(parser):
+  """Adds all common options to |parser|."""
+
+  default_build_type = os.environ.get('BUILDTYPE', 'Debug')
+
+  debug_or_release_group = parser.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.')
+
+  parser.add_argument(
+      '--break-on-failure', '--break_on_failure',
+      dest='break_on_failure', action='store_true',
+      help='Whether to break on failure.')
+
+  # TODO(jbudorick): Remove this once everything has switched to platform
+  # mode.
+  parser.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]')
+
+  parser.add_argument(
+      '-e', '--environment',
+      default='local', choices=constants.VALID_ENVIRONMENTS,
+      help='Test environment to run in (default: %(default)s).')
+
+  parser.add_argument(
+      '--local-output',
+      action='store_true',
+      help='Whether to archive test output locally and generate '
+           'a local results detail page.')
+
+  class FastLocalDevAction(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+      namespace.enable_concurrent_adb = True
+      namespace.enable_device_cache = True
+      namespace.extract_test_list_from_filter = True
+      namespace.local_output = True
+      namespace.num_retries = 0
+      namespace.skip_clear_data = True
+
+  parser.add_argument(
+      '--fast-local-dev',
+      type=bool,
+      nargs=0,
+      action=FastLocalDevAction,
+      help='Alias for: --num-retries=0 --enable-device-cache '
+      '--enable-concurrent-adb --skip-clear-data '
+      '--extract-test-list-from-filter --local-output')
+
+  # TODO(jbudorick): Remove this once downstream bots have switched to
+  # api.test_results.
+  parser.add_argument(
+      '--flakiness-dashboard-server',
+      dest='flakiness_dashboard_server',
+      help=argparse.SUPPRESS)
+  parser.add_argument(
+      '--gs-results-bucket',
+      help='Google Storage bucket to upload results to.')
+
+  parser.add_argument(
+      '--output-directory',
+      dest='output_directory', type=os.path.realpath,
+      help='Path to the directory in which build files are'
+           ' located (must include build type). This will take'
+           ' precedence over --debug and --release')
+  parser.add_argument(
+      '-v', '--verbose',
+      dest='verbose_count', default=0, action='count',
+      help='Verbose level (multiple times for more)')
+
+  parser.add_argument(
+      '--repeat', '--gtest_repeat', '--gtest-repeat',
+      '--isolated-script-test-repeat',
+      dest='repeat', type=int, default=0,
+      help='Number of times to repeat the specified set of tests.')
+
+  # This is currently only implemented for gtests and instrumentation tests.
+  parser.add_argument(
+      '--gtest_also_run_disabled_tests', '--gtest-also-run-disabled-tests',
+      '--isolated-script-test-also-run-disabled-tests',
+      dest='run_disabled', action='store_true',
+      help='Also run disabled tests if applicable.')
+
+  # These are currently only implemented for gtests.
+  parser.add_argument('--isolated-script-test-output',
+                      help='If present, store test results on this path.')
+  parser.add_argument('--isolated-script-test-perf-output',
+                      help='If present, store chartjson results on this path.')
+
+  AddTestLauncherOptions(parser)
+
+
+def ProcessCommonOptions(args):
+  """Processes and handles all common options."""
+  run_tests_helper.SetLogLevel(args.verbose_count, add_handler=False)
+  # pylint: disable=redefined-variable-type
+  if args.verbose_count > 0:
+    handler = logging_utils.ColorStreamHandler()
+  else:
+    handler = logging.StreamHandler(sys.stdout)
+  # pylint: enable=redefined-variable-type
+  handler.setFormatter(run_tests_helper.CustomFormatter())
+  logging.getLogger().addHandler(handler)
+
+  constants.SetBuildType(args.build_type)
+  if args.output_directory:
+    constants.SetOutputDirectory(args.output_directory)
+
+
+def AddDeviceOptions(parser):
+  """Adds device options to |parser|."""
+
+  parser = parser.add_argument_group('device arguments')
+
+  parser.add_argument(
+      '--adb-path',
+      type=os.path.realpath,
+      help='Specify the absolute path of the adb binary that '
+           'should be used.')
+  parser.add_argument('--denylist-file',
+                      type=os.path.realpath,
+                      help='Device denylist file.')
+  parser.add_argument(
+      '-d', '--device', nargs='+',
+      dest='test_devices',
+      help='Target device(s) for the test suite to run on.')
+  parser.add_argument(
+      '--enable-concurrent-adb',
+      action='store_true',
+      help='Run multiple adb commands at the same time, even '
+           'for the same device.')
+  parser.add_argument(
+      '--enable-device-cache',
+      action='store_true',
+      help='Cache device state to disk between runs')
+  parser.add_argument(
+      '--skip-clear-data',
+      action='store_true',
+      help='Do not wipe app data between tests. Use this to '
+           'speed up local development and never on bots '
+                     '(increases flakiness)')
+  parser.add_argument(
+      '--recover-devices',
+      action='store_true',
+      help='Attempt to recover devices prior to the final retry. Warning: '
+           'this will cause all devices to reboot.')
+  parser.add_argument(
+      '--tool',
+      dest='tool',
+      help='Run the test under a tool '
+           '(use --tool help to list them)')
+
+  parser.add_argument(
+      '--upload-logcats-file',
+      action='store_true',
+      dest='upload_logcats_file',
+      help='Whether to upload logcat file to logdog.')
+
+  logcat_output_group = parser.add_mutually_exclusive_group()
+  logcat_output_group.add_argument(
+      '--logcat-output-dir', type=os.path.realpath,
+      help='If set, will dump logcats recorded during test run to directory. '
+           'File names will be the device ids with timestamps.')
+  logcat_output_group.add_argument(
+      '--logcat-output-file', type=os.path.realpath,
+      help='If set, will merge logcats recorded during test run and dump them '
+           'to the specified file.')
+
+
+def AddEmulatorOptions(parser):
+  """Adds emulator-specific options to |parser|."""
+  parser = parser.add_argument_group('emulator arguments')
+
+  parser.add_argument(
+      '--avd-config',
+      type=os.path.realpath,
+      help='Path to the avd config textpb. '
+      '(See //tools/android/avd/proto/ for message definition'
+      ' and existing textpb files.)')
+  parser.add_argument(
+      '--emulator-count',
+      type=int,
+      default=1,
+      help='Number of emulators to use.')
+  parser.add_argument(
+      '--emulator-window',
+      action='store_true',
+      default=False,
+      help='Enable graphical window display on the emulator.')
+
+
+def AddGTestOptions(parser):
+  """Adds gtest options to |parser|."""
+
+  parser = parser.add_argument_group('gtest arguments')
+
+  parser.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.')
+  parser.add_argument(
+      '--app-data-file-dir',
+      help='Host directory to which app data files will be'
+           ' saved. Used with --app-data-file.')
+  parser.add_argument(
+      '--enable-xml-result-parsing',
+      action='store_true', help=argparse.SUPPRESS)
+  parser.add_argument(
+      '--executable-dist-dir',
+      type=os.path.realpath,
+      help="Path to executable's dist directory for native"
+           " (non-apk) tests.")
+  parser.add_argument(
+      '--extract-test-list-from-filter',
+      action='store_true',
+      help='When a test filter is specified, and the list of '
+           'tests can be determined from it, skip querying the '
+           'device for the list of all tests. Speeds up local '
+           'development, but is not safe to use on bots ('
+           'http://crbug.com/549214')
+  parser.add_argument(
+      '--gs-test-artifacts-bucket',
+      help=('If present, test artifacts will be uploaded to this Google '
+            'Storage bucket.'))
+  parser.add_argument(
+      '--render-test-output-dir',
+      help='If present, store rendering artifacts in this path.')
+  parser.add_argument(
+      '--runtime-deps-path',
+      dest='runtime_deps_path', type=os.path.realpath,
+      help='Runtime data dependency file from GN.')
+  parser.add_argument(
+      '-t', '--shard-timeout',
+      dest='shard_timeout', type=int, default=120,
+      help='Timeout to wait for each test (default: %(default)s).')
+  parser.add_argument(
+      '--store-tombstones',
+      dest='store_tombstones', action='store_true',
+      help='Add tombstones in results if crash.')
+  parser.add_argument(
+      '-s', '--suite',
+      dest='suite_name', nargs='+', metavar='SUITE_NAME', required=True,
+      help='Executable name of the test suite to run.')
+  parser.add_argument(
+      '--test-apk-incremental-install-json',
+      type=os.path.realpath,
+      help='Path to install json for the test apk.')
+  parser.add_argument('--test-launcher-batch-limit',
+                      dest='test_launcher_batch_limit',
+                      type=int,
+                      help='The max number of tests to run in a shard. '
+                      'Ignores non-positive ints and those greater than '
+                      'MAX_SHARDS')
+  parser.add_argument(
+      '-w', '--wait-for-java-debugger', action='store_true',
+      help='Wait for java debugger to attach before running any application '
+           'code. Also disables test timeouts and sets retries=0.')
+  parser.add_argument(
+      '--coverage-dir',
+      type=os.path.realpath,
+      help='Directory in which to place all generated coverage files.')
+  parser.add_argument(
+      '--use-existing-test-data',
+      action='store_true',
+      help='Do not push new files to the device, instead using existing APK '
+      'and test data. Only use when running the same test for multiple '
+      'iterations.')
+
+
+def AddInstrumentationTestOptions(parser):
+  """Adds Instrumentation test options to |parser|."""
+
+  parser = parser.add_argument_group('instrumentation arguments')
+
+  parser.add_argument(
+      '--additional-apk',
+      action='append', dest='additional_apks', default=[],
+      type=_RealPath,
+      help='Additional apk that must be installed on '
+           'the device when the tests are run')
+  parser.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".')
+  # TODO(jbudorick): Remove support for name-style APK specification once
+  # bots are no longer doing it.
+  parser.add_argument(
+      '--apk-under-test',
+      help='Path or name of the apk under test.')
+  parser.add_argument(
+      '--module',
+      action='append',
+      dest='modules',
+      help='Specify Android App Bundle modules to install in addition to the '
+      'base module.')
+  parser.add_argument(
+      '--fake-module',
+      action='append',
+      dest='fake_modules',
+      help='Specify Android App Bundle modules to fake install in addition to '
+      'the real modules.')
+  parser.add_argument(
+      '--additional-locale',
+      action='append',
+      dest='additional_locales',
+      help='Specify locales in addition to the device locale to install splits '
+      'for when --apk-under-test is an Android App Bundle.')
+  parser.add_argument(
+      '--coverage-dir',
+      type=os.path.realpath,
+      help='Directory in which to place all generated '
+      'Jacoco coverage files.')
+  parser.add_argument(
+      '--disable-dalvik-asserts',
+      dest='set_asserts', action='store_false', default=True,
+      help='Removes the dalvik.vm.enableassertions property')
+  parser.add_argument(
+      '--enable-java-deobfuscation',
+      action='store_true',
+      help='Deobfuscate java stack traces in test output and logcat.')
+  parser.add_argument(
+      '-E', '--exclude-annotation',
+      dest='exclude_annotation_str',
+      help='Comma-separated list of annotations. Exclude tests with these '
+           'annotations.')
+  parser.add_argument(
+      '--enable-breakpad-dump',
+      action='store_true',
+      help='Stores any breakpad dumps till the end of the test.')
+
+  def package_replacement(arg):
+    split_arg = arg.split(',')
+    if len(split_arg) != 2:
+      raise argparse.ArgumentError(
+          arg,
+          'Expected two comma-separated strings for --replace-system-package, '
+          'received %d' % len(split_arg))
+    PackageReplacement = collections.namedtuple('PackageReplacement',
+                                                ['package', 'replacement_apk'])
+    return PackageReplacement(package=split_arg[0],
+                              replacement_apk=_RealPath(split_arg[1]))
+  parser.add_argument(
+      '--replace-system-package',
+      type=package_replacement, default=None,
+      help='Specifies a system package to replace with a given APK for the '
+           'duration of the test. Given as a comma-separated pair of strings, '
+           'the first element being the package and the second the path to the '
+           'replacement APK. Only supports replacing one package. Example: '
+           '--replace-system-package com.example.app,path/to/some.apk')
+  parser.add_argument(
+      '--remove-system-package',
+      default=[],
+      action='append',
+      dest='system_packages_to_remove',
+      help='Specifies a system package to remove before testing if it exists '
+      'on the system. WARNING: THIS WILL PERMANENTLY REMOVE THE SYSTEM APP. '
+      'Unlike --replace-system-package, the app will not be restored after '
+      'tests are finished.')
+
+  parser.add_argument(
+      '--use-webview-provider',
+      type=_RealPath, default=None,
+      help='Use this apk as the webview provider during test. '
+           'The original provider will be restored if possible, '
+           "on Nougat the provider can't be determined and so "
+           'the system will choose the default provider.')
+  parser.add_argument(
+      '--runtime-deps-path',
+      dest='runtime_deps_path', type=os.path.realpath,
+      help='Runtime data dependency file from GN.')
+  parser.add_argument(
+      '--screenshot-directory',
+      dest='screenshot_dir', type=os.path.realpath,
+      help='Capture screenshots of test failures')
+  parser.add_argument(
+      '--shared-prefs-file',
+      dest='shared_prefs_file', type=_RealPath,
+      help='The relative path to a file containing JSON list of shared '
+           'preference files to edit and how to do so. Example list: '
+           '[{'
+           '  "package": "com.package.example",'
+           '  "filename": "ExampleSettings.xml",'
+           '  "set": {'
+           '    "boolean_key_in_xml": true,'
+           '    "string_key_in_xml": "string_value"'
+           '  },'
+           '  "remove": ['
+           '    "key_in_xml_to_remove"'
+           '  ]'
+           '}]')
+  parser.add_argument(
+      '--store-tombstones',
+      action='store_true', dest='store_tombstones',
+      help='Add tombstones in results if crash.')
+  parser.add_argument(
+      '--strict-mode',
+      dest='strict_mode', default='testing',
+      help='StrictMode command-line flag set on the device, '
+           'death/testing to kill the process, off to stop '
+           'checking, flash to flash only. (default: %(default)s)')
+  parser.add_argument(
+      '--test-apk',
+      required=True,
+      help='Path or name of the apk containing the tests.')
+  parser.add_argument(
+      '--test-jar',
+      help='Path of jar containing test java files.')
+  parser.add_argument(
+      '--test-launcher-batch-limit',
+      dest='test_launcher_batch_limit',
+      type=int,
+      help=('Not actually used for instrumentation tests, but can be used as '
+            'a proxy for determining if the current run is a retry without '
+            'patch.'))
+  parser.add_argument(
+      '--timeout-scale',
+      type=float,
+      help='Factor by which timeouts should be scaled.')
+  parser.add_argument(
+      '-w', '--wait-for-java-debugger', action='store_true',
+      help='Wait for java debugger to attach before running any application '
+           'code. Also disables test timeouts and sets retries=0.')
+
+  # WPR record mode.
+  parser.add_argument('--wpr-enable-record',
+                      action='store_true',
+                      default=False,
+                      help='If true, WPR server runs in record mode.'
+                      'otherwise, runs in replay mode.')
+
+  # These arguments are suppressed from the help text because they should
+  # only ever be specified by an intermediate script.
+  parser.add_argument(
+      '--apk-under-test-incremental-install-json',
+      help=argparse.SUPPRESS)
+  parser.add_argument(
+      '--test-apk-incremental-install-json',
+      type=os.path.realpath,
+      help=argparse.SUPPRESS)
+
+
+def AddSkiaGoldTestOptions(parser):
+  """Adds Skia Gold test options to |parser|."""
+  parser = parser.add_argument_group("Skia Gold arguments")
+  parser.add_argument(
+      '--code-review-system',
+      help='A non-default code review system to pass to pass to Gold, if '
+      'applicable')
+  parser.add_argument(
+      '--continuous-integration-system',
+      help='A non-default continuous integration system to pass to Gold, if '
+      'applicable')
+  parser.add_argument(
+      '--git-revision', help='The git commit currently being tested.')
+  parser.add_argument(
+      '--gerrit-issue',
+      help='The Gerrit issue this test is being run on, if applicable.')
+  parser.add_argument(
+      '--gerrit-patchset',
+      help='The Gerrit patchset this test is being run on, if applicable.')
+  parser.add_argument(
+      '--buildbucket-id',
+      help='The Buildbucket build ID that this test was triggered from, if '
+      'applicable.')
+  local_group = parser.add_mutually_exclusive_group()
+  local_group.add_argument(
+      '--local-pixel-tests',
+      action='store_true',
+      default=None,
+      help='Specifies to run the Skia Gold pixel tests in local mode. When run '
+      'in local mode, uploading to Gold is disabled and traditional '
+      'generated/golden/diff images are output instead of triage links. '
+      'Running in local mode also implies --no-luci-auth. If both this '
+      'and --no-local-pixel-tests are left unset, the test harness will '
+      'attempt to detect whether it is running on a workstation or not '
+      'and set the options accordingly.')
+  local_group.add_argument(
+      '--no-local-pixel-tests',
+      action='store_false',
+      dest='local_pixel_tests',
+      help='Specifies to run the Skia Gold pixel tests in non-local (bot) '
+      'mode. When run in this mode, data is actually uploaded to Gold and '
+      'triage links are generated. If both this and --local-pixel-tests '
+      'are left unset, the test harness will attempt to detect whether '
+      'it is running on a workstation or not and set the options '
+      'accordingly.')
+  parser.add_argument(
+      '--no-luci-auth',
+      action='store_true',
+      default=False,
+      help="Don't use the serve account provided by LUCI for authentication "
+      'with Skia Gold, instead relying on gsutil to be pre-authenticated. '
+      'Meant for testing locally instead of on the bots.')
+  parser.add_argument(
+      '--bypass-skia-gold-functionality',
+      action='store_true',
+      default=False,
+      help='Bypass all interaction with Skia Gold, effectively disabling the '
+      'image comparison portion of any tests that use Gold. Only meant to be '
+      'used in case a Gold outage occurs and cannot be fixed quickly.')
+
+
+def AddJUnitTestOptions(parser):
+  """Adds junit test options to |parser|."""
+
+  parser = parser.add_argument_group('junit arguments')
+
+  parser.add_argument(
+      '--coverage-on-the-fly',
+      action='store_true',
+      help='Generate coverage data by Jacoco on-the-fly instrumentation.')
+  parser.add_argument(
+      '--coverage-dir', type=os.path.realpath,
+      help='Directory to store coverage info.')
+  parser.add_argument(
+      '--package-filter',
+      help='Filters tests by package.')
+  parser.add_argument(
+      '--runner-filter',
+      help='Filters tests by runner class. Must be fully qualified.')
+  parser.add_argument(
+      '--shards',
+      default=-1,
+      type=int,
+      help='Number of shards to run junit tests in parallel on. Only 1 shard '
+      'is supported when test-filter is specified. Values less than 1 will '
+      'use auto select.')
+  parser.add_argument(
+      '-s', '--test-suite', required=True,
+      help='JUnit test suite to run.')
+  debug_group = parser.add_mutually_exclusive_group()
+  debug_group.add_argument(
+      '-w', '--wait-for-java-debugger', action='store_const', const='8701',
+      dest='debug_socket', help='Alias for --debug-socket=8701')
+  debug_group.add_argument(
+      '--debug-socket',
+      help='Wait for java debugger to attach at specified socket address '
+           'before running any application code. Also disables test timeouts '
+           'and sets retries=0.')
+
+  # These arguments are for Android Robolectric tests.
+  parser.add_argument(
+      '--robolectric-runtime-deps-dir',
+      help='Path to runtime deps for Robolectric.')
+  parser.add_argument(
+      '--resource-apk',
+      required=True,
+      help='Path to .ap_ containing binary resources for Robolectric.')
+
+
+def AddLinkerTestOptions(parser):
+
+  parser = parser.add_argument_group('linker arguments')
+
+  parser.add_argument(
+      '--test-apk',
+      type=os.path.realpath,
+      help='Path to the linker test APK.')
+
+
+def AddMonkeyTestOptions(parser):
+  """Adds monkey test options to |parser|."""
+
+  parser = parser.add_argument_group('monkey arguments')
+
+  parser.add_argument('--browser',
+                      required=True,
+                      choices=list(constants.PACKAGE_INFO.keys()),
+                      metavar='BROWSER',
+                      help='Browser under test.')
+  parser.add_argument(
+      '--category',
+      nargs='*', dest='categories', default=[],
+      help='A list of allowed categories. Monkey will only visit activities '
+           'that are listed with one of the specified categories.')
+  parser.add_argument(
+      '--event-count',
+      default=10000, type=int,
+      help='Number of events to generate (default: %(default)s).')
+  parser.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.')
+  parser.add_argument(
+      '--throttle',
+      default=100, type=int,
+      help='Delay between events (ms) (default: %(default)s). ')
+
+
+def AddPythonTestOptions(parser):
+
+  parser = parser.add_argument_group('python arguments')
+
+  parser.add_argument('-s',
+                      '--suite',
+                      dest='suite_name',
+                      metavar='SUITE_NAME',
+                      choices=list(constants.PYTHON_UNIT_TEST_SUITES.keys()),
+                      help='Name of the test suite to run.')
+
+
+def _CreateClassToFileNameDict(test_apk):
+  """Creates a dict mapping classes to file names from size-info apk."""
+  constants.CheckOutputDirectory()
+  test_apk_size_info = os.path.join(constants.GetOutDirectory(), 'size-info',
+                                    os.path.basename(test_apk) + '.jar.info')
+
+  class_to_file_dict = {}
+  # Some tests such as webview_cts_tests use a separately downloaded apk to run
+  # tests. This means the apk may not have been built by the system and hence
+  # no size info file exists.
+  if not os.path.exists(test_apk_size_info):
+    logging.debug('Apk size file not found. %s', test_apk_size_info)
+    return class_to_file_dict
+
+  with open(test_apk_size_info, 'r') as f:
+    for line in f:
+      file_class, file_name = line.rstrip().split(',', 1)
+      # Only want files that are not prebuilt.
+      if file_name.startswith('../../'):
+        class_to_file_dict[file_class] = str(
+            file_name.replace('../../', '//', 1))
+
+  return class_to_file_dict
+
+
+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:]
+
+
+_DEFAULT_PLATFORM_MODE_TESTS = [
+    'gtest', 'instrumentation', 'junit', 'linker', 'monkey'
+]
+
+
+def RunTestsCommand(args, result_sink_client=None):
+  """Checks test type and dispatches to the appropriate function.
+
+  Args:
+    args: argparse.Namespace object.
+    result_sink_client: A ResultSinkClient 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)
+  logging.info('command: %s', ' '.join(sys.argv))
+  if args.enable_platform_mode or command in _DEFAULT_PLATFORM_MODE_TESTS:
+    return RunTestsInPlatformMode(args, result_sink_client)
+
+  if 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',
+  'junit',
+  'linker',
+  'monkey',
+]
+
+
+def RunTestsInPlatformMode(args, result_sink_client=None):
+
+  def infra_error(message):
+    logging.fatal(message)
+    sys.exit(constants.INFRA_EXIT_CODE)
+
+  if args.command not in _SUPPORTED_IN_PLATFORM_MODE:
+    infra_error('%s is not yet supported in platform mode' % args.command)
+
+  ### Set up sigterm handler.
+
+  contexts_to_notify_on_sigterm = []
+  def unexpected_sigterm(_signum, _frame):
+    msg = [
+      'Received SIGTERM. Shutting down.',
+    ]
+    for live_thread in threading.enumerate():
+      # pylint: disable=protected-access
+      thread_stack = ''.join(traceback.format_stack(
+          sys._current_frames()[live_thread.ident]))
+      msg.extend([
+        'Thread "%s" (ident: %s) is currently running:' % (
+            live_thread.name, live_thread.ident),
+        thread_stack])
+
+    for context in contexts_to_notify_on_sigterm:
+      context.ReceivedSigterm()
+
+    infra_error('\n'.join(msg))
+
+  signal.signal(signal.SIGTERM, unexpected_sigterm)
+
+  ### Set up results handling.
+  # TODO(jbudorick): Rewrite results handling.
+
+  # all_raw_results is a list of lists of
+  # base_test_result.TestRunResults objects. Each instance of
+  # TestRunResults contains all test results produced by a single try,
+  # while each list of TestRunResults contains all tries in a single
+  # iteration.
+  all_raw_results = []
+
+  # all_iteration_results is a list of base_test_result.TestRunResults
+  # objects. Each instance of TestRunResults contains the last test
+  # result for each test run in that iteration.
+  all_iteration_results = []
+
+  global_results_tags = set()
+
+  json_file = tempfile.NamedTemporaryFile(delete=False)
+  json_file.close()
+
+  @contextlib.contextmanager
+  def json_finalizer():
+    try:
+      yield
+    finally:
+      if args.json_results_file and os.path.exists(json_file.name):
+        shutil.move(json_file.name, args.json_results_file)
+      elif args.isolated_script_test_output and os.path.exists(json_file.name):
+        shutil.move(json_file.name, args.isolated_script_test_output)
+      else:
+        os.remove(json_file.name)
+
+  @contextlib.contextmanager
+  def json_writer():
+    try:
+      yield
+    except Exception:
+      global_results_tags.add('UNRELIABLE_RESULTS')
+      raise
+    finally:
+      if args.isolated_script_test_output:
+        interrupted = 'UNRELIABLE_RESULTS' in global_results_tags
+        json_results.GenerateJsonTestResultFormatFile(all_raw_results,
+                                                      interrupted,
+                                                      json_file.name,
+                                                      indent=2)
+      else:
+        json_results.GenerateJsonResultsFile(
+            all_raw_results,
+            json_file.name,
+            global_tags=list(global_results_tags),
+            indent=2)
+
+      test_class_to_file_name_dict = {}
+      # Test Location is only supported for instrumentation tests as it
+      # requires the size-info file.
+      if test_instance.TestType() == 'instrumentation':
+        test_class_to_file_name_dict = _CreateClassToFileNameDict(args.test_apk)
+
+      if result_sink_client:
+        for run in all_raw_results:
+          for results in run:
+            for r in results.GetAll():
+              # Matches chrome.page_info.PageInfoViewTest#testChromePage
+              match = re.search(r'^(.+\..+)#', r.GetName())
+              test_file_name = test_class_to_file_name_dict.get(
+                  match.group(1)) if match else None
+              # Some tests put in non utf-8 char as part of the test
+              # which breaks uploads, so need to decode and re-encode.
+              log_decoded = r.GetLog()
+              if isinstance(log_decoded, bytes):
+                log_decoded = log_decoded.decode('utf-8', 'replace')
+              result_sink_client.Post(r.GetName(),
+                                      r.GetType(),
+                                      r.GetDuration(),
+                                      log_decoded.encode('utf-8'),
+                                      test_file_name,
+                                      failure_reason=r.GetFailureReason())
+
+  @contextlib.contextmanager
+  def upload_logcats_file():
+    try:
+      yield
+    finally:
+      if not args.logcat_output_file:
+        logging.critical('Cannot upload logcat file: no file specified.')
+      elif not os.path.exists(args.logcat_output_file):
+        logging.critical("Cannot upload logcat file: file doesn't exist.")
+      else:
+        with open(args.logcat_output_file) as src:
+          dst = logdog_helper.open_text('unified_logcats')
+          if dst:
+            shutil.copyfileobj(src, dst)
+            dst.close()
+            logging.critical(
+                'Logcat: %s', logdog_helper.get_viewer_url('unified_logcats'))
+
+
+  logcats_uploader = contextlib_ext.Optional(
+      upload_logcats_file(),
+      'upload_logcats_file' in args and args.upload_logcats_file)
+
+  ### Set up test objects.
+
+  out_manager = output_manager_factory.CreateOutputManager(args)
+  env = environment_factory.CreateEnvironment(
+      args, out_manager, infra_error)
+  test_instance = test_instance_factory.CreateTestInstance(args, infra_error)
+  test_run = test_run_factory.CreateTestRun(env, test_instance, infra_error)
+
+  contexts_to_notify_on_sigterm.append(env)
+  contexts_to_notify_on_sigterm.append(test_run)
+
+  ### Run.
+  with out_manager, json_finalizer():
+    with json_writer(), logcats_uploader, env, test_instance, test_run:
+
+      repetitions = (range(args.repeat +
+                           1) if args.repeat >= 0 else itertools.count())
+      result_counts = collections.defaultdict(
+          lambda: collections.defaultdict(int))
+      iteration_count = 0
+      for _ in repetitions:
+        # raw_results will be populated with base_test_result.TestRunResults by
+        # test_run.RunTests(). It is immediately added to all_raw_results so
+        # that in the event of an exception, all_raw_results will already have
+        # the up-to-date results and those can be written to disk.
+        raw_results = []
+        all_raw_results.append(raw_results)
+
+        test_run.RunTests(raw_results)
+        if not raw_results:
+          all_raw_results.pop()
+          continue
+
+        iteration_results = base_test_result.TestRunResults()
+        for r in reversed(raw_results):
+          iteration_results.AddTestRunResults(r)
+        all_iteration_results.append(iteration_results)
+        iteration_count += 1
+
+        for r in iteration_results.GetAll():
+          result_counts[r.GetName()][r.GetType()] += 1
+
+        report_results.LogFull(
+            results=iteration_results,
+            test_type=test_instance.TestType(),
+            test_package=test_run.TestPackage(),
+            annotation=getattr(args, 'annotations', None),
+            flakiness_server=getattr(args, 'flakiness_dashboard_server',
+                                     None))
+        if args.break_on_failure and not iteration_results.DidRunPass():
+          break
+
+      if iteration_count > 1:
+        # display summary results
+        # only display results for a test if at least one test did not pass
+        all_pass = 0
+        tot_tests = 0
+        for test_name in result_counts:
+          tot_tests += 1
+          if any(result_counts[test_name][x] for x in (
+              base_test_result.ResultType.FAIL,
+              base_test_result.ResultType.CRASH,
+              base_test_result.ResultType.TIMEOUT,
+              base_test_result.ResultType.UNKNOWN)):
+            logging.critical(
+                '%s: %s',
+                test_name,
+                ', '.join('%s %s' % (str(result_counts[test_name][i]), i)
+                          for i in base_test_result.ResultType.GetTypes()))
+          else:
+            all_pass += 1
+
+        logging.critical('%s of %s tests passed in all %s runs',
+                         str(all_pass),
+                         str(tot_tests),
+                         str(iteration_count))
+
+    if (args.local_output or not local_utils.IsOnSwarming()
+        ) and not args.isolated_script_test_output:
+      with out_manager.ArchivedTempfile(
+          'test_results_presentation.html',
+          'test_results_presentation',
+          output_manager.Datatype.HTML) as results_detail_file:
+        result_html_string, _, _ = test_results_presentation.result_details(
+            json_path=json_file.name,
+            test_name=args.command,
+            cs_base_url='http://cs.chromium.org',
+            local_output=True)
+        results_detail_file.write(result_html_string.encode('utf-8'))
+        results_detail_file.flush()
+      logging.critical('TEST RESULTS: %s', results_detail_file.Link())
+
+      ui_screenshots = test_results_presentation.ui_screenshot_set(
+          json_file.name)
+      if ui_screenshots:
+        with out_manager.ArchivedTempfile(
+            'ui_screenshots.json',
+            'ui_capture',
+            output_manager.Datatype.JSON) as ui_screenshot_file:
+          ui_screenshot_file.write(ui_screenshots)
+        logging.critical('UI Screenshots: %s', ui_screenshot_file.Link())
+
+  return (0 if all(r.DidRunPass() for r in all_iteration_results)
+          else constants.ERROR_EXIT_CODE)
+
+
+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')
+
+  subp = command_parsers.add_parser(
+      'gtest',
+      help='googletest-based C++ tests')
+  AddCommonOptions(subp)
+  AddDeviceOptions(subp)
+  AddEmulatorOptions(subp)
+  AddGTestOptions(subp)
+  AddTracingOptions(subp)
+  AddCommandLineOptions(subp)
+
+  subp = command_parsers.add_parser(
+      'instrumentation',
+      help='InstrumentationTestCase-based Java tests')
+  AddCommonOptions(subp)
+  AddDeviceOptions(subp)
+  AddEmulatorOptions(subp)
+  AddInstrumentationTestOptions(subp)
+  AddSkiaGoldTestOptions(subp)
+  AddTracingOptions(subp)
+  AddCommandLineOptions(subp)
+
+  subp = command_parsers.add_parser(
+      'junit',
+      help='JUnit4-based Java tests')
+  AddCommonOptions(subp)
+  AddJUnitTestOptions(subp)
+
+  subp = command_parsers.add_parser(
+      'linker',
+      help='linker tests')
+  AddCommonOptions(subp)
+  AddDeviceOptions(subp)
+  AddEmulatorOptions(subp)
+  AddLinkerTestOptions(subp)
+
+  subp = command_parsers.add_parser(
+      'monkey',
+      help="tests based on Android's monkey command")
+  AddCommonOptions(subp)
+  AddDeviceOptions(subp)
+  AddEmulatorOptions(subp)
+  AddMonkeyTestOptions(subp)
+
+  subp = command_parsers.add_parser(
+      'python',
+      help='python tests based on unittest.TestCase')
+  AddCommonOptions(subp)
+  AddPythonTestOptions(subp)
+
+  args, unknown_args = parser.parse_known_args()
+  if unknown_args:
+    if hasattr(args, 'allow_unknown') and args.allow_unknown:
+      args.command_line_flags = unknown_args
+    else:
+      parser.error('unrecognized arguments: %s' % ' '.join(unknown_args))
+
+  # --replace-system-package/--remove-system-package has the potential to cause
+  # issues if --enable-concurrent-adb is set, so disallow that combination.
+  concurrent_adb_enabled = (hasattr(args, 'enable_concurrent_adb')
+                            and args.enable_concurrent_adb)
+  replacing_system_packages = (hasattr(args, 'replace_system_package')
+                               and args.replace_system_package)
+  removing_system_packages = (hasattr(args, 'system_packages_to_remove')
+                              and args.system_packages_to_remove)
+  if (concurrent_adb_enabled
+      and (replacing_system_packages or removing_system_packages)):
+    parser.error('--enable-concurrent-adb cannot be used with either '
+                 '--replace-system-package or --remove-system-package')
+
+  # --use-webview-provider has the potential to cause issues if
+  # --enable-concurrent-adb is set, so disallow that combination
+  if (hasattr(args, 'use_webview_provider') and
+      hasattr(args, 'enable_concurrent_adb') and args.use_webview_provider and
+      args.enable_concurrent_adb):
+    parser.error('--use-webview-provider and --enable-concurrent-adb cannot '
+                 'be used together')
+
+  if (getattr(args, 'coverage_on_the_fly', False)
+      and not getattr(args, 'coverage_dir', '')):
+    parser.error('--coverage-on-the-fly requires --coverage-dir')
+
+  if (hasattr(args, 'debug_socket') or
+      (hasattr(args, 'wait_for_java_debugger') and
+      args.wait_for_java_debugger)):
+    args.num_retries = 0
+
+  # Result-sink may not exist in the environment if rdb stream is not enabled.
+  result_sink_client = result_sink.TryInitClient()
+
+  try:
+    return RunTestsCommand(args, result_sink_client)
+  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/third_party/libwebrtc/build/android/test_runner.pydeps b/third_party/libwebrtc/build/android/test_runner.pydeps
new file mode 100644
index 0000000000..87b39ef563
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test_runner.pydeps
@@ -0,0 +1,229 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android --output build/android/test_runner.pydeps build/android/test_runner.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/__init__.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/__init__.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/meta_class.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/multiprocessing_shim.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/perfetto_proto_classes.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/perfetto_trace_writer.py
+../../third_party/catapult/common/py_trace_event/py_trace_event/trace_time.py
+../../third_party/catapult/common/py_trace_event/third_party/protobuf/encoder.py
+../../third_party/catapult/common/py_trace_event/third_party/protobuf/wire_format.py
+../../third_party/catapult/common/py_utils/py_utils/__init__.py
+../../third_party/catapult/common/py_utils/py_utils/atexit_with_log.py
+../../third_party/catapult/common/py_utils/py_utils/binary_manager.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
+../../third_party/catapult/common/py_utils/py_utils/contextlib_ext.py
+../../third_party/catapult/common/py_utils/py_utils/lock.py
+../../third_party/catapult/common/py_utils/py_utils/modules_util.py
+../../third_party/catapult/common/py_utils/py_utils/retry_util.py
+../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
+../../third_party/catapult/common/py_utils/py_utils/ts_proxy_server.py
+../../third_party/catapult/common/py_utils/py_utils/webpagereplay_go_server.py
+../../third_party/catapult/dependency_manager/dependency_manager/__init__.py
+../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/base_config.py
+../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py
+../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py
+../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/manager.py
+../../third_party/catapult/dependency_manager/dependency_manager/uploader.py
+../../third_party/catapult/devil/devil/__init__.py
+../../third_party/catapult/devil/devil/android/__init__.py
+../../third_party/catapult/devil/devil/android/apk_helper.py
+../../third_party/catapult/devil/devil/android/battery_utils.py
+../../third_party/catapult/devil/devil/android/constants/__init__.py
+../../third_party/catapult/devil/devil/android/constants/chrome.py
+../../third_party/catapult/devil/devil/android/constants/file_system.py
+../../third_party/catapult/devil/devil/android/crash_handler.py
+../../third_party/catapult/devil/devil/android/decorators.py
+../../third_party/catapult/devil/devil/android/device_denylist.py
+../../third_party/catapult/devil/devil/android/device_errors.py
+../../third_party/catapult/devil/devil/android/device_list.py
+../../third_party/catapult/devil/devil/android/device_signal.py
+../../third_party/catapult/devil/devil/android/device_temp_file.py
+../../third_party/catapult/devil/devil/android/device_utils.py
+../../third_party/catapult/devil/devil/android/flag_changer.py
+../../third_party/catapult/devil/devil/android/forwarder.py
+../../third_party/catapult/devil/devil/android/install_commands.py
+../../third_party/catapult/devil/devil/android/logcat_monitor.py
+../../third_party/catapult/devil/devil/android/md5sum.py
+../../third_party/catapult/devil/devil/android/ndk/__init__.py
+../../third_party/catapult/devil/devil/android/ndk/abis.py
+../../third_party/catapult/devil/devil/android/ports.py
+../../third_party/catapult/devil/devil/android/sdk/__init__.py
+../../third_party/catapult/devil/devil/android/sdk/aapt.py
+../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
+../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../third_party/catapult/devil/devil/android/sdk/bundletool.py
+../../third_party/catapult/devil/devil/android/sdk/intent.py
+../../third_party/catapult/devil/devil/android/sdk/keyevent.py
+../../third_party/catapult/devil/devil/android/sdk/shared_prefs.py
+../../third_party/catapult/devil/devil/android/sdk/split_select.py
+../../third_party/catapult/devil/devil/android/sdk/version_codes.py
+../../third_party/catapult/devil/devil/android/tools/__init__.py
+../../third_party/catapult/devil/devil/android/tools/device_recovery.py
+../../third_party/catapult/devil/devil/android/tools/device_status.py
+../../third_party/catapult/devil/devil/android/tools/script_common.py
+../../third_party/catapult/devil/devil/android/tools/system_app.py
+../../third_party/catapult/devil/devil/android/tools/webview_app.py
+../../third_party/catapult/devil/devil/android/valgrind_tools/__init__.py
+../../third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py
+../../third_party/catapult/devil/devil/base_error.py
+../../third_party/catapult/devil/devil/constants/__init__.py
+../../third_party/catapult/devil/devil/constants/exit_codes.py
+../../third_party/catapult/devil/devil/devil_env.py
+../../third_party/catapult/devil/devil/utils/__init__.py
+../../third_party/catapult/devil/devil/utils/cmd_helper.py
+../../third_party/catapult/devil/devil/utils/file_utils.py
+../../third_party/catapult/devil/devil/utils/host_utils.py
+../../third_party/catapult/devil/devil/utils/lazy/__init__.py
+../../third_party/catapult/devil/devil/utils/lazy/weak_constant.py
+../../third_party/catapult/devil/devil/utils/logging_common.py
+../../third_party/catapult/devil/devil/utils/lsusb.py
+../../third_party/catapult/devil/devil/utils/parallelizer.py
+../../third_party/catapult/devil/devil/utils/reraiser_thread.py
+../../third_party/catapult/devil/devil/utils/reset_usb.py
+../../third_party/catapult/devil/devil/utils/run_tests_helper.py
+../../third_party/catapult/devil/devil/utils/signal_handler.py
+../../third_party/catapult/devil/devil/utils/timeout_retry.py
+../../third_party/catapult/devil/devil/utils/watchdog_timer.py
+../../third_party/catapult/devil/devil/utils/zip_utils.py
+../../third_party/catapult/third_party/six/six.py
+../../third_party/colorama/src/colorama/__init__.py
+../../third_party/colorama/src/colorama/ansi.py
+../../third_party/colorama/src/colorama/ansitowin32.py
+../../third_party/colorama/src/colorama/initialise.py
+../../third_party/colorama/src/colorama/win32.py
+../../third_party/colorama/src/colorama/winterm.py
+../../third_party/jinja2/__init__.py
+../../third_party/jinja2/_compat.py
+../../third_party/jinja2/bccache.py
+../../third_party/jinja2/compiler.py
+../../third_party/jinja2/defaults.py
+../../third_party/jinja2/environment.py
+../../third_party/jinja2/exceptions.py
+../../third_party/jinja2/filters.py
+../../third_party/jinja2/idtracking.py
+../../third_party/jinja2/lexer.py
+../../third_party/jinja2/loaders.py
+../../third_party/jinja2/nodes.py
+../../third_party/jinja2/optimizer.py
+../../third_party/jinja2/parser.py
+../../third_party/jinja2/runtime.py
+../../third_party/jinja2/tests.py
+../../third_party/jinja2/utils.py
+../../third_party/jinja2/visitor.py
+../../third_party/logdog/logdog/__init__.py
+../../third_party/logdog/logdog/bootstrap.py
+../../third_party/logdog/logdog/stream.py
+../../third_party/logdog/logdog/streamname.py
+../../third_party/logdog/logdog/varint.py
+../../third_party/markupsafe/__init__.py
+../../third_party/markupsafe/_compat.py
+../../third_party/markupsafe/_native.py
+../gn_helpers.py
+../print_python_deps.py
+../skia_gold_common/__init__.py
+../skia_gold_common/skia_gold_properties.py
+../skia_gold_common/skia_gold_session.py
+../skia_gold_common/skia_gold_session_manager.py
+../util/lib/__init__.py
+../util/lib/common/chrome_test_server_spawner.py
+../util/lib/common/unittest_util.py
+../util/lib/results/__init__.py
+../util/lib/results/result_sink.py
+../util/lib/results/result_types.py
+convert_dex_profile.py
+devil_chromium.py
+gyp/dex.py
+gyp/util/__init__.py
+gyp/util/build_utils.py
+gyp/util/md5_check.py
+gyp/util/zipalign.py
+incremental_install/__init__.py
+incremental_install/installer.py
+pylib/__init__.py
+pylib/base/__init__.py
+pylib/base/base_test_result.py
+pylib/base/environment.py
+pylib/base/environment_factory.py
+pylib/base/output_manager.py
+pylib/base/output_manager_factory.py
+pylib/base/test_collection.py
+pylib/base/test_exception.py
+pylib/base/test_instance.py
+pylib/base/test_instance_factory.py
+pylib/base/test_run.py
+pylib/base/test_run_factory.py
+pylib/base/test_server.py
+pylib/constants/__init__.py
+pylib/constants/host_paths.py
+pylib/gtest/__init__.py
+pylib/gtest/gtest_test_instance.py
+pylib/instrumentation/__init__.py
+pylib/instrumentation/instrumentation_parser.py
+pylib/instrumentation/instrumentation_test_instance.py
+pylib/instrumentation/test_result.py
+pylib/junit/__init__.py
+pylib/junit/junit_test_instance.py
+pylib/local/__init__.py
+pylib/local/device/__init__.py
+pylib/local/device/local_device_environment.py
+pylib/local/device/local_device_gtest_run.py
+pylib/local/device/local_device_instrumentation_test_run.py
+pylib/local/device/local_device_monkey_test_run.py
+pylib/local/device/local_device_test_run.py
+pylib/local/emulator/__init__.py
+pylib/local/emulator/avd.py
+pylib/local/emulator/ini.py
+pylib/local/emulator/local_emulator_environment.py
+pylib/local/emulator/proto/__init__.py
+pylib/local/emulator/proto/avd_pb2.py
+pylib/local/local_test_server_spawner.py
+pylib/local/machine/__init__.py
+pylib/local/machine/local_machine_environment.py
+pylib/local/machine/local_machine_junit_test_run.py
+pylib/monkey/__init__.py
+pylib/monkey/monkey_test_instance.py
+pylib/output/__init__.py
+pylib/output/local_output_manager.py
+pylib/output/noop_output_manager.py
+pylib/output/remote_output_manager.py
+pylib/results/__init__.py
+pylib/results/flakiness_dashboard/__init__.py
+pylib/results/flakiness_dashboard/json_results_generator.py
+pylib/results/flakiness_dashboard/results_uploader.py
+pylib/results/json_results.py
+pylib/results/presentation/__init__.py
+pylib/results/presentation/standard_gtest_merge.py
+pylib/results/presentation/test_results_presentation.py
+pylib/results/report_results.py
+pylib/symbols/__init__.py
+pylib/symbols/deobfuscator.py
+pylib/symbols/stack_symbolizer.py
+pylib/utils/__init__.py
+pylib/utils/chrome_proxy_utils.py
+pylib/utils/decorators.py
+pylib/utils/device_dependencies.py
+pylib/utils/dexdump.py
+pylib/utils/gold_utils.py
+pylib/utils/google_storage_helper.py
+pylib/utils/instrumentation_tracing.py
+pylib/utils/local_utils.py
+pylib/utils/logdog_helper.py
+pylib/utils/logging_utils.py
+pylib/utils/proguard.py
+pylib/utils/repo_utils.py
+pylib/utils/shared_preference_utils.py
+pylib/utils/test_filter.py
+pylib/utils/time_profile.py
+pylib/valgrind_tools.py
+test_runner.py
+tombstones.py
diff --git a/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.py b/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.py
new file mode 100755
index 0000000000..303e2883be
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env vpython3
+# 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.
+
+"""Wrapper for adding logdog streaming support to swarming tasks."""
+
+import argparse
+import contextlib
+import logging
+import os
+import signal
+import subprocess
+import sys
+
+_SRC_PATH = os.path.abspath(os.path.join(
+    os.path.dirname(__file__), '..', '..', '..'))
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'common',
+                             'py_utils'))
+
+from devil.utils import signal_handler
+from devil.utils import timeout_retry
+from py_utils import tempfile_ext
+
+PROJECT = 'chromium'
+OUTPUT = 'logdog'
+COORDINATOR_HOST = 'luci-logdog.appspot.com'
+SERVICE_ACCOUNT_JSON = ('/creds/service_accounts'
+                        '/service-account-luci-logdog-publisher.json')
+LOGDOG_TERMINATION_TIMEOUT = 30
+
+
+def CommandParser():
+  # Parses the command line arguments being passed in
+  parser = argparse.ArgumentParser()
+  wrapped = parser.add_mutually_exclusive_group()
+  wrapped.add_argument(
+      '--target',
+      help='The test target to be run. If neither target nor script are set,'
+      ' any extra args passed to this script are assumed to be the'
+      ' full test command to run.')
+  wrapped.add_argument(
+      '--script',
+      help='The script target to be run. If neither target nor script are set,'
+      ' any extra args passed to this script are assumed to be the'
+      ' full test command to run.')
+  parser.add_argument('--logdog-bin-cmd', required=True,
+                      help='The logdog bin cmd.')
+  return parser
+
+
+def CreateStopTestsMethod(proc):
+  def StopTests(signum, _frame):
+    logging.error('Forwarding signal %s to test process', str(signum))
+    proc.send_signal(signum)
+  return StopTests
+
+
+@contextlib.contextmanager
+def NoLeakingProcesses(popen):
+  try:
+    yield popen
+  finally:
+    if popen is not None:
+      try:
+        if popen.poll() is None:
+          popen.kill()
+      except OSError:
+        logging.warning('Failed to kill %s. Process may be leaked.',
+                        str(popen.pid))
+
+
+def main():
+  parser = CommandParser()
+  args, extra_cmd_args = parser.parse_known_args(sys.argv[1:])
+
+  logging.basicConfig(level=logging.INFO)
+  if args.target:
+    test_cmd = [os.path.join('bin', 'run_%s' % args.target), '-v']
+    test_cmd += extra_cmd_args
+  elif args.script:
+    test_cmd = [args.script]
+    test_cmd += extra_cmd_args
+  else:
+    test_cmd = extra_cmd_args
+
+  test_env = dict(os.environ)
+  logdog_cmd = []
+
+  with tempfile_ext.NamedTemporaryDirectory(
+      prefix='tmp_android_logdog_wrapper') as temp_directory:
+    if not os.path.exists(args.logdog_bin_cmd):
+      logging.error(
+          'Logdog binary %s unavailable. Unable to create logdog client',
+          args.logdog_bin_cmd)
+    else:
+      streamserver_uri = 'unix:%s' % os.path.join(temp_directory,
+                                                  'butler.sock')
+      prefix = os.path.join('android', 'swarming', 'logcats',
+                            os.environ.get('SWARMING_TASK_ID'))
+
+      logdog_cmd = [
+          args.logdog_bin_cmd,
+          '-project', PROJECT,
+          '-output', OUTPUT,
+          '-prefix', prefix,
+          '--service-account-json', SERVICE_ACCOUNT_JSON,
+          '-coordinator-host', COORDINATOR_HOST,
+          'serve',
+          '-streamserver-uri', streamserver_uri]
+      test_env.update({
+          'LOGDOG_STREAM_PROJECT': PROJECT,
+          'LOGDOG_STREAM_PREFIX': prefix,
+          'LOGDOG_STREAM_SERVER_PATH': streamserver_uri,
+          'LOGDOG_COORDINATOR_HOST': COORDINATOR_HOST,
+      })
+
+    logdog_proc = None
+    if logdog_cmd:
+      logdog_proc = subprocess.Popen(logdog_cmd)
+
+    with NoLeakingProcesses(logdog_proc):
+      with NoLeakingProcesses(
+          subprocess.Popen(test_cmd, env=test_env)) as test_proc:
+        with signal_handler.SignalHandler(signal.SIGTERM,
+                                          CreateStopTestsMethod(test_proc)):
+          result = test_proc.wait()
+          if logdog_proc:
+            def logdog_stopped():
+              return logdog_proc.poll() is not None
+
+            logdog_proc.terminate()
+            timeout_retry.WaitFor(logdog_stopped, wait_period=1,
+                                  max_tries=LOGDOG_TERMINATION_TIMEOUT)
+
+            # If logdog_proc hasn't finished by this point, allow
+            # NoLeakingProcesses to kill it.
+
+
+  return result
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.pydeps b/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.pydeps
new file mode 100644
index 0000000000..0e8d039b99
--- /dev/null
+++ b/third_party/libwebrtc/build/android/test_wrapper/logdog_wrapper.pydeps
@@ -0,0 +1,12 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android/test_wrapper --output build/android/test_wrapper/logdog_wrapper.pydeps build/android/test_wrapper/logdog_wrapper.py
+../../../third_party/catapult/common/py_utils/py_utils/__init__.py
+../../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
+../../../third_party/catapult/devil/devil/__init__.py
+../../../third_party/catapult/devil/devil/base_error.py
+../../../third_party/catapult/devil/devil/utils/__init__.py
+../../../third_party/catapult/devil/devil/utils/reraiser_thread.py
+../../../third_party/catapult/devil/devil/utils/signal_handler.py
+../../../third_party/catapult/devil/devil/utils/timeout_retry.py
+../../../third_party/catapult/devil/devil/utils/watchdog_timer.py
+logdog_wrapper.py
diff --git a/third_party/libwebrtc/build/android/tests/symbolize/Makefile b/third_party/libwebrtc/build/android/tests/symbolize/Makefile
new file mode 100644
index 0000000000..4fc53dad56
--- /dev/null
+++ b/third_party/libwebrtc/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_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/third_party/libwebrtc/build/android/tests/symbolize/a.cc b/third_party/libwebrtc/build/android/tests/symbolize/a.cc
new file mode 100644
index 0000000000..f0c7ca4c67
--- /dev/null
+++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/tests/symbolize/b.cc b/third_party/libwebrtc/build/android/tests/symbolize/b.cc
new file mode 100644
index 0000000000..db8752099a
--- /dev/null
+++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/android/tests/symbolize/liba.so b/third_party/libwebrtc/build/android/tests/symbolize/liba.so
new file mode 100644
index 0000000000..79cb739121
Binary files /dev/null and b/third_party/libwebrtc/build/android/tests/symbolize/liba.so differ
diff --git a/third_party/libwebrtc/build/android/tests/symbolize/libb.so b/third_party/libwebrtc/build/android/tests/symbolize/libb.so
new file mode 100644
index 0000000000..7cf01d43c5
Binary files /dev/null and b/third_party/libwebrtc/build/android/tests/symbolize/libb.so differ
diff --git a/third_party/libwebrtc/build/android/tombstones.py b/third_party/libwebrtc/build/android/tombstones.py
new file mode 100755
index 0000000000..ae478bf689
--- /dev/null
+++ b/third_party/libwebrtc/build/android/tombstones.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env vpython3
+#
+# 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 argparse
+import datetime
+import logging
+import os
+import sys
+
+from multiprocessing.pool import ThreadPool
+
+import devil_chromium
+
+from devil.android import device_denylist
+from devil.android import device_errors
+from devil.android import device_utils
+from devil.utils import run_tests_helper
+from pylib import constants
+from pylib.symbols import stack_symbolizer
+
+
+_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:
+    if not device.PathExists('/data/tombstones', as_root=True):
+      return
+    entries = device.StatDirectory('/data/tombstones', as_root=True)
+    for entry in entries:
+      if 'tombstone' in entry['filename']:
+        yield (entry['filename'],
+               datetime.datetime.fromtimestamp(entry['st_mtime']))
+  except device_errors.CommandFailedError:
+    logging.exception('Could not retrieve tombstones.')
+  except device_errors.DeviceUnreachableError:
+    logging.exception('Device unreachable retrieving tombstones.')
+  except device_errors.CommandTimeoutError:
+    logging.exception('Timed out retrieving 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 _ResolveTombstone(args):
+  tombstone = args[0]
+  tombstone_symbolizer = args[1]
+  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 += tombstone_symbolizer.ExtractAndResolveNativeStackTraces(
+      tombstone['data'],
+      tombstone['device_abi'],
+      tombstone['stack'])
+  return lines
+
+
+def _ResolveTombstones(jobs, tombstones, tombstone_symbolizer):
+  """Resolve a list of tombstones.
+
+  Args:
+    jobs: the number of jobs to use with multithread.
+    tombstones: a list of tombstones.
+  """
+  if not tombstones:
+    logging.warning('No tombstones to resolve.')
+    return []
+  if len(tombstones) == 1:
+    data = [_ResolveTombstone([tombstones[0], tombstone_symbolizer])]
+  else:
+    pool = ThreadPool(jobs)
+    data = pool.map(
+        _ResolveTombstone,
+        [[tombstone, tombstone_symbolizer] for tombstone in tombstones])
+    pool.close()
+    pool.join()
+  resolved_tombstones = []
+  for tombstone in data:
+    resolved_tombstones.extend(tombstone)
+  return resolved_tombstones
+
+
+def _GetTombstonesForDevice(device, resolve_all_tombstones,
+                            include_stack_symbols,
+                            wipe_tombstones):
+  """Returns a list of tombstones on a given device.
+
+  Args:
+    device: An instance of DeviceUtils.
+    resolve_all_tombstone: Whether to resolve every tombstone.
+    include_stack_symbols: Whether to include symbols for stack data.
+    wipe_tombstones: Whether to wipe tombstones.
+  """
+  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(key=lambda a: a[1], reverse=True)
+
+  # Only resolve the most recent unless --all-tombstones given.
+  tombstones = all_tombstones if resolve_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': include_stack_symbols,
+               'data': _GetTombstoneData(device, tombstone_file)}]
+  except device_errors.CommandFailedError:
+    for entry in device.StatDirectory(
+        '/data/tombstones', as_root=True, timeout=60):
+      logging.info('%s: %s', str(device), entry)
+    raise
+
+  # Erase all the tombstones if desired.
+  if wipe_tombstones:
+    for tombstone_file, _ in all_tombstones:
+      _EraseTombstone(device, tombstone_file)
+
+  return ret
+
+
+def ClearAllTombstones(device):
+  """Clear all tombstones in the device.
+
+  Args:
+    device: An instance of DeviceUtils.
+  """
+  all_tombstones = list(_ListTombstones(device))
+  if not all_tombstones:
+    logging.warning('No tombstones to clear.')
+
+  for tombstone_file, _ in all_tombstones:
+    _EraseTombstone(device, tombstone_file)
+
+
+def ResolveTombstones(device, resolve_all_tombstones, include_stack_symbols,
+                      wipe_tombstones, jobs=4, apk_under_test=None,
+                      tombstone_symbolizer=None):
+  """Resolve tombstones in the device.
+
+  Args:
+    device: An instance of DeviceUtils.
+    resolve_all_tombstone: Whether to resolve every tombstone.
+    include_stack_symbols: Whether to include symbols for stack data.
+    wipe_tombstones: Whether to wipe tombstones.
+    jobs: Number of jobs to use when processing multiple crash stacks.
+
+  Returns:
+    A list of resolved tombstones.
+  """
+  return _ResolveTombstones(jobs,
+                            _GetTombstonesForDevice(device,
+                                                    resolve_all_tombstones,
+                                                    include_stack_symbols,
+                                                    wipe_tombstones),
+                            (tombstone_symbolizer
+                             or stack_symbolizer.Symbolizer(apk_under_test)))
+
+
+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 = argparse.ArgumentParser()
+  parser.add_argument('--device',
+                      help='The serial number of the device. If not specified '
+                           'will use all devices.')
+  parser.add_argument('--denylist-file', help='Device denylist JSON file.')
+  parser.add_argument('-a', '--all-tombstones', action='store_true',
+                      help='Resolve symbols for all tombstones, rather than '
+                           'just the most recent.')
+  parser.add_argument('-s', '--stack', action='store_true',
+                      help='Also include symbols for stack data')
+  parser.add_argument('-w', '--wipe-tombstones', action='store_true',
+                      help='Erase all tombstones from device after processing')
+  parser.add_argument('-j', '--jobs', type=int,
+                      default=4,
+                      help='Number of jobs to use when processing multiple '
+                           'crash stacks.')
+  parser.add_argument('--output-directory',
+                      help='Path to the root build directory.')
+  parser.add_argument('--adb-path', type=os.path.abspath,
+                      help='Path to the adb binary.')
+  args = parser.parse_args()
+
+  if args.output_directory:
+    constants.SetOutputDirectory(args.output_directory)
+
+  devil_chromium.Initialize(output_directory=constants.GetOutDirectory(),
+                            adb_path=args.adb_path)
+
+  denylist = (device_denylist.Denylist(args.denylist_file)
+              if args.denylist_file else None)
+
+  if args.device:
+    devices = [device_utils.DeviceUtils(args.device)]
+  else:
+    devices = device_utils.DeviceUtils.HealthyDevices(denylist)
+
+  # 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
+  for device in devices:
+    resolved_tombstones = ResolveTombstones(
+        device, args.all_tombstones,
+        args.stack, args.wipe_tombstones, args.jobs)
+    for line in resolved_tombstones:
+      logging.info(line)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/unused_resources/BUILD.gn b/third_party/libwebrtc/build/android/unused_resources/BUILD.gn
new file mode 100644
index 0000000000..15961048bd
--- /dev/null
+++ b/third_party/libwebrtc/build/android/unused_resources/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2021 The Chromium 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")
+
+java_binary("unused_resources") {
+  sources = [ "//build/android/unused_resources/UnusedResources.java" ]
+  main_class = "build.android.unused_resources.UnusedResources"
+  deps = [
+    "//third_party/android_deps:com_android_tools_common_java",
+    "//third_party/android_deps:com_android_tools_layoutlib_layoutlib_api_java",
+    "//third_party/android_deps:com_android_tools_sdk_common_java",
+    "//third_party/android_deps:com_google_guava_guava_java",
+    "//third_party/android_deps:org_jetbrains_kotlin_kotlin_stdlib_java",
+    "//third_party/r8:r8_java",
+  ]
+  wrapper_script_name = "helper/unused_resources"
+}
diff --git a/third_party/libwebrtc/build/android/unused_resources/UnusedResources.java b/third_party/libwebrtc/build/android/unused_resources/UnusedResources.java
new file mode 100644
index 0000000000..6334223b9f
--- /dev/null
+++ b/third_party/libwebrtc/build/android/unused_resources/UnusedResources.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Modifications are owned by the Chromium Authors.
+// Copyright 2021 The Chromium 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 build.android.unused_resources;
+
+import static com.android.ide.common.symbols.SymbolIo.readFromAapt;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.android.ide.common.resources.usage.ResourceUsageModel;
+import com.android.ide.common.resources.usage.ResourceUsageModel.Resource;
+import com.android.ide.common.symbols.Symbol;
+import com.android.ide.common.symbols.SymbolTable;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceShrinker;
+import com.android.tools.r8.ResourceShrinker.Command;
+import com.android.tools.r8.ResourceShrinker.ReferenceChecker;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.utils.XmlUtils;
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+  Copied with modifications from gradle core source
+  https://android.googlesource.com/platform/tools/base/+/master/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/ResourceUsageAnalyzer.java
+
+  Modifications are mostly to:
+    - Remove unused code paths to reduce complexity.
+    - Reduce dependencies unless absolutely required.
+*/
+
+public class UnusedResources {
+    private static final String ANDROID_RES = "android_res/";
+    private static final String DOT_DEX = ".dex";
+    private static final String DOT_CLASS = ".class";
+    private static final String DOT_XML = ".xml";
+    private static final String DOT_JAR = ".jar";
+    private static final String FN_RESOURCE_TEXT = "R.txt";
+
+    /* A source of resource classes to track, can be either a folder or a jar */
+    private final Iterable mRTxtFiles;
+    private final File mProguardMapping;
+    /** These can be class or dex files. */
+    private final Iterable mClasses;
+    private final Iterable mManifests;
+    private final Iterable mResourceDirs;
+
+    private final File mReportFile;
+    private final StringWriter mDebugOutput;
+    private final PrintWriter mDebugPrinter;
+
+    /** The computed set of unused resources */
+    private List mUnused;
+
+    /**
+     * Map from resource class owners (VM format class) to corresponding resource entries.
+     * This lets us map back from code references (obfuscated class and possibly obfuscated field
+     * reference) back to the corresponding resource type and name.
+     */
+    private Map>> mResourceObfuscation =
+            Maps.newHashMapWithExpectedSize(30);
+
+    /** Obfuscated name of android/support/v7/widget/SuggestionsAdapter.java */
+    private String mSuggestionsAdapter;
+
+    /** Obfuscated name of android/support/v7/internal/widget/ResourcesWrapper.java */
+    private String mResourcesWrapper;
+
+    /* A Pair class because java does not come with batteries included. */
+    private static class Pair {
+        private U mFirst;
+        private V mSecond;
+
+        Pair(U first, V second) {
+            this.mFirst = first;
+            this.mSecond = second;
+        }
+
+        public U getFirst() {
+            return mFirst;
+        }
+
+        public V getSecond() {
+            return mSecond;
+        }
+    }
+
+    public UnusedResources(Iterable rTxtFiles, Iterable classes,
+            Iterable manifests, File mapping, Iterable resources, File reportFile) {
+        mRTxtFiles = rTxtFiles;
+        mProguardMapping = mapping;
+        mClasses = classes;
+        mManifests = manifests;
+        mResourceDirs = resources;
+
+        mReportFile = reportFile;
+        if (reportFile != null) {
+            mDebugOutput = new StringWriter(8 * 1024);
+            mDebugPrinter = new PrintWriter(mDebugOutput);
+        } else {
+            mDebugOutput = null;
+            mDebugPrinter = null;
+        }
+    }
+
+    public void close() {
+        if (mDebugOutput != null) {
+            String output = mDebugOutput.toString();
+
+            if (mReportFile != null) {
+                File dir = mReportFile.getParentFile();
+                if (dir != null) {
+                    if ((dir.exists() || dir.mkdir()) && dir.canWrite()) {
+                        try {
+                            Files.asCharSink(mReportFile, Charsets.UTF_8).write(output);
+                        } catch (IOException ignore) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void analyze() throws IOException, ParserConfigurationException, SAXException {
+        gatherResourceValues(mRTxtFiles);
+        recordMapping(mProguardMapping);
+
+        for (File jarOrDir : mClasses) {
+            recordClassUsages(jarOrDir);
+        }
+        recordManifestUsages(mManifests);
+        recordResources(mResourceDirs);
+        dumpReferences();
+        mModel.processToolsAttributes();
+        mUnused = mModel.findUnused();
+    }
+
+    public void emitConfig(Path destination) throws IOException {
+        File destinationFile = destination.toFile();
+        if (!destinationFile.exists()) {
+            destinationFile.getParentFile().mkdirs();
+            boolean success = destinationFile.createNewFile();
+            if (!success) {
+                throw new IOException("Could not create " + destination);
+            }
+        }
+        StringBuilder sb = new StringBuilder();
+        Collections.sort(mUnused);
+        for (Resource resource : mUnused) {
+            sb.append(resource.type + "/" + resource.name + "#remove\n");
+        }
+        Files.asCharSink(destinationFile, UTF_8).write(sb.toString());
+    }
+
+    private void dumpReferences() {
+        if (mDebugPrinter != null) {
+            mDebugPrinter.print(mModel.dumpReferences());
+        }
+    }
+
+    private void recordResources(Iterable resources)
+            throws IOException, SAXException, ParserConfigurationException {
+        for (File resDir : resources) {
+            File[] resourceFolders = resDir.listFiles();
+            assert resourceFolders != null : "Invalid resource directory " + resDir;
+            for (File folder : resourceFolders) {
+                ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
+                if (folderType != null) {
+                    recordResources(folderType, folder);
+                }
+            }
+        }
+    }
+
+    private void recordResources(ResourceFolderType folderType, File folder)
+            throws ParserConfigurationException, SAXException, IOException {
+        File[] files = folder.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                String path = file.getPath();
+                mModel.file = file;
+                try {
+                    boolean isXml = endsWithIgnoreCase(path, DOT_XML);
+                    if (isXml) {
+                        String xml = Files.toString(file, UTF_8);
+                        Document document = XmlUtils.parseDocument(xml, true);
+                        mModel.visitXmlDocument(file, folderType, document);
+                    } else {
+                        mModel.visitBinaryResource(folderType, file);
+                    }
+                } finally {
+                    mModel.file = null;
+                }
+            }
+        }
+    }
+
+    void recordMapping(File mapping) throws IOException {
+        if (mapping == null || !mapping.exists()) {
+            return;
+        }
+        final String arrowString = " -> ";
+        final String resourceString = ".R$";
+        Map nameMap = null;
+        for (String line : Files.readLines(mapping, UTF_8)) {
+            if (line.startsWith(" ") || line.startsWith("\t")) {
+                if (nameMap != null) {
+                    // We're processing the members of a resource class: record names into the map
+                    int n = line.length();
+                    int i = 0;
+                    for (; i < n; i++) {
+                        if (!Character.isWhitespace(line.charAt(i))) {
+                            break;
+                        }
+                    }
+                    if (i < n && line.startsWith("int", i)) { // int or int[]
+                        int start = line.indexOf(' ', i + 3) + 1;
+                        int arrow = line.indexOf(arrowString);
+                        if (start > 0 && arrow != -1) {
+                            int end = line.indexOf(' ', start + 1);
+                            if (end != -1) {
+                                String oldName = line.substring(start, end);
+                                String newName =
+                                        line.substring(arrow + arrowString.length()).trim();
+                                if (!newName.equals(oldName)) {
+                                    nameMap.put(newName, oldName);
+                                }
+                            }
+                        }
+                    }
+                }
+                continue;
+            } else {
+                nameMap = null;
+            }
+            int index = line.indexOf(resourceString);
+            if (index == -1) {
+                // Record obfuscated names of a few known appcompat usages of
+                // Resources#getIdentifier that are unlikely to be used for general
+                // resource name reflection
+                if (line.startsWith("android.support.v7.widget.SuggestionsAdapter ")) {
+                    mSuggestionsAdapter =
+                            line.substring(line.indexOf(arrowString) + arrowString.length(),
+                                        line.indexOf(':') != -1 ? line.indexOf(':') : line.length())
+                                    .trim()
+                                    .replace('.', '/')
+                            + DOT_CLASS;
+                } else if (line.startsWith("android.support.v7.internal.widget.ResourcesWrapper ")
+                        || line.startsWith("android.support.v7.widget.ResourcesWrapper ")
+                        || (mResourcesWrapper == null // Recently wrapper moved
+                                && line.startsWith(
+                                        "android.support.v7.widget.TintContextWrapper$TintResources "))) {
+                    mResourcesWrapper =
+                            line.substring(line.indexOf(arrowString) + arrowString.length(),
+                                        line.indexOf(':') != -1 ? line.indexOf(':') : line.length())
+                                    .trim()
+                                    .replace('.', '/')
+                            + DOT_CLASS;
+                }
+                continue;
+            }
+            int arrow = line.indexOf(arrowString, index + 3);
+            if (arrow == -1) {
+                continue;
+            }
+            String typeName = line.substring(index + resourceString.length(), arrow);
+            ResourceType type = ResourceType.fromClassName(typeName);
+            if (type == null) {
+                continue;
+            }
+            int end = line.indexOf(':', arrow + arrowString.length());
+            if (end == -1) {
+                end = line.length();
+            }
+            String target = line.substring(arrow + arrowString.length(), end).trim();
+            String ownerName = target.replace('.', '/');
+
+            nameMap = Maps.newHashMap();
+            Pair> pair = new Pair(type, nameMap);
+            mResourceObfuscation.put(ownerName, pair);
+            // For fast lookup in isResourceClass
+            mResourceObfuscation.put(ownerName + DOT_CLASS, pair);
+        }
+    }
+
+    private void recordManifestUsages(File manifest)
+            throws IOException, ParserConfigurationException, SAXException {
+        String xml = Files.toString(manifest, UTF_8);
+        Document document = XmlUtils.parseDocument(xml, true);
+        mModel.visitXmlDocument(manifest, null, document);
+    }
+
+    private void recordManifestUsages(Iterable manifests)
+            throws IOException, ParserConfigurationException, SAXException {
+        for (File manifest : manifests) {
+            recordManifestUsages(manifest);
+        }
+    }
+
+    private void recordClassUsages(File file) throws IOException {
+        assert file.isFile();
+        if (file.getPath().endsWith(DOT_DEX)) {
+            byte[] bytes = Files.toByteArray(file);
+            recordClassUsages(file, file.getName(), bytes);
+        } else if (file.getPath().endsWith(DOT_JAR)) {
+            ZipInputStream zis = null;
+            try {
+                FileInputStream fis = new FileInputStream(file);
+                try {
+                    zis = new ZipInputStream(fis);
+                    ZipEntry entry = zis.getNextEntry();
+                    while (entry != null) {
+                        String name = entry.getName();
+                        if (name.endsWith(DOT_DEX)) {
+                            byte[] bytes = ByteStreams.toByteArray(zis);
+                            if (bytes != null) {
+                                recordClassUsages(file, name, bytes);
+                            }
+                        }
+
+                        entry = zis.getNextEntry();
+                    }
+                } finally {
+                    Closeables.close(fis, true);
+                }
+            } finally {
+                Closeables.close(zis, true);
+            }
+        }
+    }
+
+    private void recordClassUsages(File file, String name, byte[] bytes) {
+        assert name.endsWith(DOT_DEX);
+        ReferenceChecker callback = new ReferenceChecker() {
+            @Override
+            public boolean shouldProcess(String internalName) {
+                return !isResourceClass(internalName + DOT_CLASS);
+            }
+
+            @Override
+            public void referencedInt(int value) {
+                UnusedResources.this.referencedInt("dex", value, file, name);
+            }
+
+            @Override
+            public void referencedString(String value) {
+                // do nothing.
+            }
+
+            @Override
+            public void referencedStaticField(String internalName, String fieldName) {
+                Resource resource = getResourceFromCode(internalName, fieldName);
+                if (resource != null) {
+                    ResourceUsageModel.markReachable(resource);
+                }
+            }
+
+            @Override
+            public void referencedMethod(
+                    String internalName, String methodName, String methodDescriptor) {
+                // Do nothing.
+            }
+        };
+        ProgramResource resource = ProgramResource.fromBytes(
+                new PathOrigin(file.toPath()), ProgramResource.Kind.DEX, bytes, null);
+        ProgramResourceProvider provider = () -> Arrays.asList(resource);
+        try {
+            Command command =
+                    (new ResourceShrinker.Builder()).addProgramResourceProvider(provider).build();
+            ResourceShrinker.run(command, callback);
+        } catch (CompilationFailedException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (ExecutionException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Returns whether the given class file name points to an aapt-generated compiled R class. */
+    boolean isResourceClass(String name) {
+        if (mResourceObfuscation.containsKey(name)) {
+            return true;
+        }
+        int index = name.lastIndexOf('/');
+        if (index != -1 && name.startsWith("R$", index + 1) && name.endsWith(DOT_CLASS)) {
+            String typeName = name.substring(index + 3, name.length() - DOT_CLASS.length());
+            return ResourceType.fromClassName(typeName) != null;
+        }
+        return false;
+    }
+
+    Resource getResourceFromCode(String owner, String name) {
+        Pair> pair = mResourceObfuscation.get(owner);
+        if (pair != null) {
+            ResourceType type = pair.getFirst();
+            Map nameMap = pair.getSecond();
+            String renamedField = nameMap.get(name);
+            if (renamedField != null) {
+                name = renamedField;
+            }
+            return mModel.getResource(type, name);
+        }
+        if (isValidResourceType(owner)) {
+            ResourceType type =
+                    ResourceType.fromClassName(owner.substring(owner.lastIndexOf('$') + 1));
+            if (type != null) {
+                return mModel.getResource(type, name);
+            }
+        }
+        return null;
+    }
+
+    private Boolean isValidResourceType(String candidateString) {
+        return candidateString.contains("/")
+                && candidateString.substring(candidateString.lastIndexOf('/') + 1).contains("$");
+    }
+
+    private void gatherResourceValues(Iterable rTxts) throws IOException {
+        for (File rTxt : rTxts) {
+            assert rTxt.isFile();
+            assert rTxt.getName().endsWith(FN_RESOURCE_TEXT);
+            addResourcesFromRTxtFile(rTxt);
+        }
+    }
+
+    private void addResourcesFromRTxtFile(File file) {
+        try {
+            SymbolTable st = readFromAapt(file, null);
+            for (Symbol symbol : st.getSymbols().values()) {
+                String symbolValue = symbol.getValue();
+                if (symbol.getResourceType() == ResourceType.STYLEABLE) {
+                    if (symbolValue.trim().startsWith("{")) {
+                        // Only add the styleable parent, styleable children are not yet supported.
+                        mModel.addResource(symbol.getResourceType(), symbol.getName(), null);
+                    }
+                } else {
+                    mModel.addResource(symbol.getResourceType(), symbol.getName(), symbolValue);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    ResourceUsageModel getModel() {
+        return mModel;
+    }
+
+    private void referencedInt(String context, int value, File file, String currentClass) {
+        Resource resource = mModel.getResource(value);
+        if (ResourceUsageModel.markReachable(resource) && mDebugPrinter != null) {
+            mDebugPrinter.println("Marking " + resource + " reachable: referenced from " + context
+                    + " in " + file + ":" + currentClass);
+        }
+    }
+
+    private final ResourceShrinkerUsageModel mModel = new ResourceShrinkerUsageModel();
+
+    private class ResourceShrinkerUsageModel extends ResourceUsageModel {
+        public File file;
+
+        /**
+         * Whether we should ignore tools attribute resource references.
+         * 

+ * For example, for resource shrinking we want to ignore tools attributes, + * whereas for resource refactoring on the source code we do not. + * + * @return whether tools attributes should be ignored + */ + @Override + protected boolean ignoreToolsAttributes() { + return true; + } + + @Override + protected void onRootResourcesFound(List roots) { + if (mDebugPrinter != null) { + mDebugPrinter.println( + "\nThe root reachable resources are:\n" + Joiner.on(",\n ").join(roots)); + } + } + + @Override + protected Resource declareResource(ResourceType type, String name, Node node) { + Resource resource = super.declareResource(type, name, node); + resource.addLocation(file); + return resource; + } + + @Override + protected void referencedString(String string) { + // Do nothing + } + } + + public static void main(String[] args) throws Exception { + List rTxtFiles = null; // R.txt files + List classes = null; // Dex/jar w dex + List manifests = null; // manifests + File mapping = null; // mapping + List resources = null; // resources dirs + File log = null; // output log for debugging + Path configPath = null; // output config + for (int i = 0; i < args.length; i += 2) { + switch (args[i]) { + case "--rtxts": + rTxtFiles = Arrays.stream(args[i + 1].split(":")) + .map(s -> new File(s)) + .collect(Collectors.toList()); + break; + case "--dexes": + classes = Arrays.stream(args[i + 1].split(":")) + .map(s -> new File(s)) + .collect(Collectors.toList()); + break; + case "--manifests": + manifests = Arrays.stream(args[i + 1].split(":")) + .map(s -> new File(s)) + .collect(Collectors.toList()); + break; + case "--mapping": + mapping = new File(args[i + 1]); + break; + case "--resourceDirs": + resources = Arrays.stream(args[i + 1].split(":")) + .map(s -> new File(s)) + .collect(Collectors.toList()); + break; + case "--log": + log = new File(args[i + 1]); + break; + case "--outputConfig": + configPath = Paths.get(args[i + 1]); + break; + default: + throw new IllegalArgumentException(args[i] + " is not a valid arg."); + } + } + UnusedResources unusedResources = + new UnusedResources(rTxtFiles, classes, manifests, mapping, resources, log); + unusedResources.analyze(); + unusedResources.close(); + unusedResources.emitConfig(configPath); + } +} diff --git a/third_party/libwebrtc/build/android/update_deps/update_third_party_deps.py b/third_party/libwebrtc/build/android/update_deps/update_third_party_deps.py new file mode 100755 index 0000000000..c03fec5d88 --- /dev/null +++ b/third_party/libwebrtc/build/android/update_deps/update_third_party_deps.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# 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. + +""" +Uploads or downloads third party libraries to or from google cloud storage. + +This script will only work for Android checkouts. +""" + +import argparse +import logging +import os +import sys + + +sys.path.append(os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir))) +from pylib import constants +from pylib.constants import host_paths + +sys.path.append( + os.path.abspath( + os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', 'depot_tools'))) +import download_from_google_storage +import upload_to_google_storage + + +def _AddBasicArguments(parser): + parser.add_argument( + '--sdk-root', default=constants.ANDROID_SDK_ROOT, + help='base path to the Android SDK root') + parser.add_argument( + '-v', '--verbose', action='store_true', help='print debug information') + parser.add_argument( + '-b', '--bucket-path', required=True, + help='The path of the lib file in Google Cloud Storage.') + parser.add_argument( + '-l', '--local-path', required=True, + help='The base path of the third_party directory') + + +def _CheckPaths(bucket_path, local_path): + if bucket_path.startswith('gs://'): + bucket_url = bucket_path + else: + bucket_url = 'gs://%s' % bucket_path + local_path = os.path.join(host_paths.DIR_SOURCE_ROOT, local_path) + if not os.path.isdir(local_path): + raise IOError( + 'The library local path is not a valid directory: %s' % local_path) + return bucket_url, local_path + + +def _CheckFileList(local_path, file_list): + local_path = os.path.abspath(local_path) + abs_path_list = [os.path.abspath(f) for f in file_list] + for f in abs_path_list: + if os.path.commonprefix([f, local_path]) != local_path: + raise IOError( + '%s in the arguments is not descendant of the specified directory %s' + % (f, local_path)) + return abs_path_list + + +def _PurgeSymlinks(local_path): + for dirpath, _, filenames in os.walk(local_path): + for f in filenames: + path = os.path.join(dirpath, f) + if os.path.islink(path): + os.remove(path) + + +def Upload(arguments): + """Upload files in a third_party directory to google storage""" + bucket_url, local_path = _CheckPaths(arguments.bucket_path, + arguments.local_path) + file_list = _CheckFileList(local_path, arguments.file_list) + return upload_to_google_storage.upload_to_google_storage( + input_filenames=file_list, + base_url=bucket_url, + gsutil=arguments.gsutil, + force=False, + use_md5=False, + num_threads=1, + skip_hashing=False, + gzip=None) + + +def Download(arguments): + """Download files based on sha1 files in a third_party dir from gcs""" + bucket_url, local_path = _CheckPaths(arguments.bucket_path, + arguments.local_path) + _PurgeSymlinks(local_path) + return download_from_google_storage.download_from_google_storage( + local_path, + bucket_url, + gsutil=arguments.gsutil, + num_threads=1, + directory=True, + recursive=True, + force=False, + output=None, + ignore_errors=False, + sha1_file=None, + verbose=arguments.verbose, + auto_platform=False, + extract=False) + + +def main(argv): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title='commands') + download_parser = subparsers.add_parser( + 'download', help='download the library from the cloud storage') + _AddBasicArguments(download_parser) + download_parser.set_defaults(func=Download) + + upload_parser = subparsers.add_parser( + 'upload', help='find all jar files in a third_party directory and ' + + 'upload them to cloud storage') + _AddBasicArguments(upload_parser) + upload_parser.set_defaults(func=Upload) + upload_parser.add_argument( + '-f', '--file-list', nargs='+', required=True, + help='A list of base paths for files in third_party to upload.') + + arguments = parser.parse_args(argv) + if not os.path.isdir(arguments.sdk_root): + logging.debug('Did not find the Android SDK root directory at "%s".', + arguments.sdk_root) + logging.info('Skipping, not on an android checkout.') + return 0 + + arguments.gsutil = download_from_google_storage.Gsutil( + download_from_google_storage.GSUTIL_DEFAULT_PATH) + return arguments.func(arguments) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/android/update_verification.py b/third_party/libwebrtc/build/android/update_verification.py new file mode 100755 index 0000000000..123c575079 --- /dev/null +++ b/third_party/libwebrtc/build/android/update_verification.py @@ -0,0 +1,116 @@ +#!/usr/bin/env vpython3 +# +# 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 sys + +# import raw_input when converted to python3 +from six.moves import input # pylint: disable=redefined-builtin +import devil_chromium + +from devil.android import apk_helper +from devil.android import device_denylist +from devil.android import device_errors +from devil.android import device_utils +from devil.utils import run_tests_helper + + +def CreateAppData(device, old_apk, app_data, package_name): + device.Install(old_apk) + 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 + input('Select "Restore my data" on the device. Then press enter to ' + 'continue.') + if not device.IsApplicationInstalled(package_name): + 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.') + parser.add_argument('--denylist-file', help='Device denylist JSON file.') + 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) + + devil_chromium.Initialize() + + denylist = (device_denylist.Denylist(args.denylist_file) + if args.denylist_file else None) + + devices = device_utils.DeviceUtils.HealthyDevices(denylist) + 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/third_party/libwebrtc/build/android/video_recorder.py b/third_party/libwebrtc/build/android/video_recorder.py new file mode 100755 index 0000000000..dcda340a3b --- /dev/null +++ b/third_party/libwebrtc/build/android/video_recorder.py @@ -0,0 +1,13 @@ +#!/usr/bin/env vpython3 +# 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 sys + +import devil_chromium +from devil.android.tools import video_recorder + +if __name__ == '__main__': + devil_chromium.Initialize() + sys.exit(video_recorder.main()) diff --git a/third_party/libwebrtc/build/apple/OWNERS b/third_party/libwebrtc/build/apple/OWNERS new file mode 100644 index 0000000000..07d900ebed --- /dev/null +++ b/third_party/libwebrtc/build/apple/OWNERS @@ -0,0 +1,4 @@ +mark@chromium.org +rohitrao@chromium.org +rsesek@chromium.org +sdefresne@chromium.org diff --git a/third_party/libwebrtc/build/apple/README.md b/third_party/libwebrtc/build/apple/README.md new file mode 100644 index 0000000000..f60185dc97 --- /dev/null +++ b/third_party/libwebrtc/build/apple/README.md @@ -0,0 +1,12 @@ +# About + +`//build/apple` contains: + * GN templates and configurations shared by Apple platforms + * Python build scripts shared by Apple platforms + +This directory should only contain templates, configurations and scripts +that are used exclusively on Apple platforms (currently iOS and macOS). +They must also be independent of the specific platform. + +If a template, configuration or script is limited to only iOS or macOS, +then they should instead be located in `//build/ios` or `//build/mac`. diff --git a/third_party/libwebrtc/build/apple/apple_info_plist.gni b/third_party/libwebrtc/build/apple/apple_info_plist.gni new file mode 100644 index 0000000000..fe51773dd0 --- /dev/null +++ b/third_party/libwebrtc/build/apple/apple_info_plist.gni @@ -0,0 +1,60 @@ +# Copyright 2021 The Chromium 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/apple/compile_plist.gni") + +# The base template used to generate Info.plist files for iOS and Mac apps and +# frameworks. +# +# Arguments +# +# plist_templates: +# string array, paths to plist files which will be used for the bundle. +# +# executable_name: +# string, name of the generated target used for the product +# and executable name as specified in the output Info.plist. +# +# format: +# string, the format to `plutil -convert` the plist to when +# generating the output. +# +# extra_substitutions: +# (optional) string array, 'key=value' pairs for extra fields which are +# specified in a source Info.plist template. +# +# output_name: +# (optional) string, name of the generated plist file, default to +# "$target_gen_dir/$target_name.plist". +template("apple_info_plist") { + assert(defined(invoker.executable_name), + "The executable_name must be specified for $target_name") + executable_name = invoker.executable_name + + compile_plist(target_name) { + forward_variables_from(invoker, + [ + "plist_templates", + "testonly", + "deps", + "visibility", + "format", + ]) + + if (defined(invoker.output_name)) { + output_name = invoker.output_name + } else { + output_name = "$target_gen_dir/$target_name.plist" + } + + substitutions = [ + "EXECUTABLE_NAME=$executable_name", + "GCC_VERSION=com.apple.compilers.llvm.clang.1_0", + "PRODUCT_NAME=$executable_name", + ] + if (defined(invoker.extra_substitutions)) { + substitutions += invoker.extra_substitutions + } + } +} diff --git a/third_party/libwebrtc/build/apple/compile_entitlements.gni b/third_party/libwebrtc/build/apple/compile_entitlements.gni new file mode 100644 index 0000000000..006d5ac2d5 --- /dev/null +++ b/third_party/libwebrtc/build/apple/compile_entitlements.gni @@ -0,0 +1,51 @@ +# Copyright 2021 The Chromium 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/apple/compile_plist.gni") + +# Template to merge multiple .entitlements files performing variable +# substitutions. +# +# Arguments +# +# entitlements_templates: +# string array, paths to entitlements files which will be used for the +# bundle. +# +# substitutions: +# string array, 'key=value' pairs used to replace ${key} by value +# when generating the output plist file. +# +# output_name: +# string, name of the generated entitlements file. +template("compile_entitlements") { + assert(defined(invoker.entitlements_templates), + "A list of template plist files must be specified for $target_name") + + compile_plist(target_name) { + forward_variables_from(invoker, + "*", + [ + "entitlements_templates", + "format", + "plist_templates", + ]) + + plist_templates = invoker.entitlements_templates + + # Entitlements files are always encoded in xml1. + format = "xml1" + + # Entitlements files use unsubstitued variables, so define substitutions + # to leave those variables untouched. + if (!defined(substitutions)) { + substitutions = [] + } + + substitutions += [ + "AppIdentifierPrefix=\$(AppIdentifierPrefix)", + "CFBundleIdentifier=\$(CFBundleIdentifier)", + ] + } +} diff --git a/third_party/libwebrtc/build/apple/compile_plist.gni b/third_party/libwebrtc/build/apple/compile_plist.gni new file mode 100644 index 0000000000..90485b6a2a --- /dev/null +++ b/third_party/libwebrtc/build/apple/compile_plist.gni @@ -0,0 +1,76 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Template to merge multiple plist files and perform variable substitutions. +# +# Arguments +# +# plist_templates: +# string array, paths to plist files which will be used for the bundle. +# +# format: +# string, the format to `plutil -convert` the plist to when +# generating the output. +# +# substitutions: +# string array, 'key=value' pairs used to replace ${key} by value +# when generating the output plist file. +# +# output_name: +# string, name of the generated plist file. +template("compile_plist") { + assert(defined(invoker.plist_templates), + "A list of template plist files must be specified for $target_name") + assert(defined(invoker.format), + "The plist format must be specified for $target_name") + assert(defined(invoker.substitutions), + "A list of key=value pairs must be specified for $target_name") + assert(defined(invoker.output_name), + "The name of the output file must be specified for $target_name") + + _output_name = invoker.output_name + _merged_name = get_path_info(_output_name, "dir") + "/" + + get_path_info(_output_name, "name") + "_merged." + + get_path_info(_output_name, "extension") + + _merge_target = target_name + "_merge" + + action(_merge_target) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + + script = "//build/apple/plist_util.py" + sources = invoker.plist_templates + outputs = [ _merged_name ] + args = [ + "merge", + "-f=" + invoker.format, + "-o=" + rebase_path(_merged_name, root_build_dir), + ] + rebase_path(invoker.plist_templates, root_build_dir) + } + + action(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + script = "//build/apple/plist_util.py" + sources = [ _merged_name ] + outputs = [ _output_name ] + args = [ + "substitute", + "-f=" + invoker.format, + "-o=" + rebase_path(_output_name, root_build_dir), + "-t=" + rebase_path(_merged_name, root_build_dir), + ] + foreach(_substitution, invoker.substitutions) { + args += [ "-s=$_substitution" ] + } + deps = [ ":$_merge_target" ] + } +} diff --git a/third_party/libwebrtc/build/apple/convert_plist.gni b/third_party/libwebrtc/build/apple/convert_plist.gni new file mode 100644 index 0000000000..a1134d9b31 --- /dev/null +++ b/third_party/libwebrtc/build/apple/convert_plist.gni @@ -0,0 +1,41 @@ +# Copyright 2021 The Chromium 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 plist file to given format. +# +# Arguments +# +# source: +# string, path to the plist file to convert +# +# output: +# string, path to the converted plist, must be under $root_build_dir +# +# format: +# string, the format to convert the plist to. Either "binary1" or "xml1". +template("convert_plist") { + assert(defined(invoker.source), "source must be defined for $target_name") + assert(defined(invoker.output), "output must be defined for $target_name") + assert(defined(invoker.format), "format must be defined for $target_name") + + action(target_name) { + forward_variables_from(invoker, + [ + "visibility", + "testonly", + "deps", + ]) + + script = "//build/apple/plist_util.py" + sources = [ invoker.source ] + outputs = [ invoker.output ] + args = [ + "merge", + "--format=${invoker.format}", + "-o", + rebase_path(invoker.output, root_build_dir), + rebase_path(invoker.source, root_build_dir), + ] + } +} diff --git a/third_party/libwebrtc/build/apple/plist_util.py b/third_party/libwebrtc/build/apple/plist_util.py new file mode 100644 index 0000000000..54cf46176b --- /dev/null +++ b/third_party/libwebrtc/build/apple/plist_util.py @@ -0,0 +1,265 @@ +# 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. + +import argparse +import codecs +import plistlib +import os +import re +import subprocess +import sys +import tempfile +import shlex + +if sys.version_info.major < 3: + basestring_compat = basestring +else: + basestring_compat = str + +# Xcode substitutes variables like ${PRODUCT_NAME} or $(PRODUCT_NAME) when +# compiling Info.plist. It also supports supports modifiers like :identifier +# or :rfc1034identifier. SUBSTITUTION_REGEXP_LIST is a list of regular +# expressions matching a variable substitution pattern with an optional +# modifier, while INVALID_CHARACTER_REGEXP matches all characters that are +# not valid in an "identifier" value (used when applying the modifier). +INVALID_CHARACTER_REGEXP = re.compile(r'[_/\s]') +SUBSTITUTION_REGEXP_LIST = ( + re.compile(r'\$\{(?P[^}]*?)(?P:[^}]*)?\}'), + re.compile(r'\$\((?P[^}]*?)(?P:[^}]*)?\)'), +) + + +class SubstitutionError(Exception): + def __init__(self, key): + super(SubstitutionError, self).__init__() + self.key = key + + def __str__(self): + return "SubstitutionError: {}".format(self.key) + + +def InterpolateString(value, substitutions): + """Interpolates variable references into |value| using |substitutions|. + + Inputs: + value: a string + substitutions: a mapping of variable names to values + + Returns: + A new string with all variables references ${VARIABLES} replaced by their + value in |substitutions|. Raises SubstitutionError if a variable has no + substitution. + """ + + def repl(match): + variable = match.group('id') + if variable not in substitutions: + raise SubstitutionError(variable) + # Some values need to be identifier and thus the variables references may + # contains :modifier attributes to indicate how they should be converted + # to identifiers ("identifier" replaces all invalid characters by '_' and + # "rfc1034identifier" replaces them by "-" to make valid URI too). + modifier = match.group('modifier') + if modifier == ':identifier': + return INVALID_CHARACTER_REGEXP.sub('_', substitutions[variable]) + elif modifier == ':rfc1034identifier': + return INVALID_CHARACTER_REGEXP.sub('-', substitutions[variable]) + else: + return substitutions[variable] + + for substitution_regexp in SUBSTITUTION_REGEXP_LIST: + value = substitution_regexp.sub(repl, value) + return value + + +def Interpolate(value, substitutions): + """Interpolates variable references into |value| using |substitutions|. + + Inputs: + value: a value, can be a dictionary, list, string or other + substitutions: a mapping of variable names to values + + Returns: + A new value with all variables references ${VARIABLES} replaced by their + value in |substitutions|. Raises SubstitutionError if a variable has no + substitution. + """ + if isinstance(value, dict): + return {k: Interpolate(v, substitutions) for k, v in value.items()} + if isinstance(value, list): + return [Interpolate(v, substitutions) for v in value] + if isinstance(value, basestring_compat): + return InterpolateString(value, substitutions) + return value + + +def LoadPList(path): + """Loads Plist at |path| and returns it as a dictionary.""" + if sys.version_info.major == 2: + fd, name = tempfile.mkstemp() + try: + subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path]) + with os.fdopen(fd, 'rb') as f: + return plistlib.readPlist(f) + finally: + os.unlink(name) + else: + with open(path, 'rb') as f: + return plistlib.load(f) + + +def SavePList(path, format, data): + """Saves |data| as a Plist to |path| in the specified |format|.""" + # The below does not replace the destination file but update it in place, + # so if more than one hardlink points to destination all of them will be + # modified. This is not what is expected, so delete destination file if + # it does exist. + if os.path.exists(path): + os.unlink(path) + if sys.version_info.major == 2: + fd, name = tempfile.mkstemp() + try: + with os.fdopen(fd, 'wb') as f: + plistlib.writePlist(data, f) + subprocess.check_call(['plutil', '-convert', format, '-o', path, name]) + finally: + os.unlink(name) + else: + with open(path, 'wb') as f: + plist_format = {'binary1': plistlib.FMT_BINARY, 'xml1': plistlib.FMT_XML} + plistlib.dump(data, f, fmt=plist_format[format]) + + +def MergePList(plist1, plist2): + """Merges |plist1| with |plist2| recursively. + + Creates a new dictionary representing a Property List (.plist) files by + merging the two dictionary |plist1| and |plist2| recursively (only for + dictionary values). List value will be concatenated. + + Args: + plist1: a dictionary representing a Property List (.plist) file + plist2: a dictionary representing a Property List (.plist) file + + Returns: + A new dictionary representing a Property List (.plist) file by merging + |plist1| with |plist2|. If any value is a dictionary, they are merged + recursively, otherwise |plist2| value is used. If values are list, they + are concatenated. + """ + result = plist1.copy() + for key, value in plist2.items(): + if isinstance(value, dict): + old_value = result.get(key) + if isinstance(old_value, dict): + value = MergePList(old_value, value) + if isinstance(value, list): + value = plist1.get(key, []) + plist2.get(key, []) + result[key] = value + return result + + +class Action(object): + """Class implementing one action supported by the script.""" + + @classmethod + def Register(cls, subparsers): + parser = subparsers.add_parser(cls.name, help=cls.help) + parser.set_defaults(func=cls._Execute) + cls._Register(parser) + + +class MergeAction(Action): + """Class to merge multiple plist files.""" + + name = 'merge' + help = 'merge multiple plist files' + + @staticmethod + def _Register(parser): + parser.add_argument('-o', + '--output', + required=True, + help='path to the output plist file') + parser.add_argument('-f', + '--format', + required=True, + choices=('xml1', 'binary1'), + help='format of the plist file to generate') + parser.add_argument( + '-x', + '--xcode-version', + help='version of Xcode, ignored (can be used to force rebuild)') + parser.add_argument('path', nargs="+", help='path to plist files to merge') + + @staticmethod + def _Execute(args): + data = {} + for filename in args.path: + data = MergePList(data, LoadPList(filename)) + SavePList(args.output, args.format, data) + + +class SubstituteAction(Action): + """Class implementing the variable substitution in a plist file.""" + + name = 'substitute' + help = 'perform pattern substitution in a plist file' + + @staticmethod + def _Register(parser): + parser.add_argument('-o', + '--output', + required=True, + help='path to the output plist file') + parser.add_argument('-t', + '--template', + required=True, + help='path to the template file') + parser.add_argument('-s', + '--substitution', + action='append', + default=[], + help='substitution rule in the format key=value') + parser.add_argument('-f', + '--format', + required=True, + choices=('xml1', 'binary1'), + help='format of the plist file to generate') + parser.add_argument( + '-x', + '--xcode-version', + help='version of Xcode, ignored (can be used to force rebuild)') + + @staticmethod + def _Execute(args): + substitutions = {} + for substitution in args.substitution: + key, value = substitution.split('=', 1) + substitutions[key] = value + data = Interpolate(LoadPList(args.template), substitutions) + SavePList(args.output, args.format, data) + + +def Main(): + # Cache this codec so that plistlib can find it. See + # https://crbug.com/1005190#c2 for more details. + codecs.lookup('utf-8') + + parser = argparse.ArgumentParser(description='manipulate plist files') + subparsers = parser.add_subparsers() + + for action in [MergeAction, SubstituteAction]: + action.Register(subparsers) + + args = parser.parse_args() + args.func(args) + + +if __name__ == '__main__': + # TODO(https://crbug.com/941669): Temporary workaround until all scripts use + # python3 by default. + if sys.version_info[0] < 3: + os.execvp('python3', ['python3'] + sys.argv) + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/apple/tweak_info_plist.gni b/third_party/libwebrtc/build/apple/tweak_info_plist.gni new file mode 100644 index 0000000000..33f22ca2d0 --- /dev/null +++ b/third_party/libwebrtc/build/apple/tweak_info_plist.gni @@ -0,0 +1,86 @@ +# 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. + +import("//build/util/lastchange.gni") + +# Template to run the tweak_info_plist.py script on a plist. +# +# Arguments: +# +# info_plist: +# (optional), string, the plist to tweak. +# +# info_plists: +# (optional), list of string, the plist files to merge and tweak. +# +# args: +# (optional), list of string, the arguments to pass to the +# tweak_info_plist.py script. +# +# Callers should use get_target_outputs() to get the output name. One of +# info_plist or info_plists must be specified. +template("tweak_info_plist") { + _output_name = "$target_gen_dir/${target_name}_tweaked.plist" + + if (defined(invoker.info_plists)) { + assert(!defined(invoker.info_plist), + "Cannot have both info_plist and info_plists for $target_name") + + _source_name = "$target_gen_dir/${target_name}_merged.plist" + _deps = [ ":" + target_name + "_merge_plist" ] + + action(target_name + "_merge_plist") { + forward_variables_from(invoker, + [ + "testonly", + "deps", + ]) + script = "//build/apple/plist_util.py" + sources = invoker.info_plists + outputs = [ _source_name ] + args = [ + "merge", + "-f=xml1", + "-o=" + rebase_path(_source_name, root_build_dir), + ] + rebase_path(invoker.info_plists, root_build_dir) + } + } else { + assert(defined(invoker.info_plist), + "The info_plist must be specified in $target_name") + + _source_name = invoker.info_plist + _deps = [] + if (defined(invoker.deps)) { + _deps += invoker.deps + } + } + + action(target_name) { + forward_variables_from(invoker, + [ + "args", + "testonly", + ]) + script = "//build/apple/tweak_info_plist.py" + inputs = [ + script, + "//build/util/version.py", + lastchange_file, + "//chrome/VERSION", + ] + sources = [ _source_name ] + outputs = [ _output_name ] + if (!defined(args)) { + args = [] + } + args += [ + "--plist", + rebase_path(_source_name, root_build_dir), + "--output", + rebase_path(_output_name, root_build_dir), + "--platform=$current_os", + ] + deps = _deps + } +} diff --git a/third_party/libwebrtc/build/apple/tweak_info_plist.py b/third_party/libwebrtc/build/apple/tweak_info_plist.py new file mode 100755 index 0000000000..76f64dc37c --- /dev/null +++ b/third_party/libwebrtc/build/apple/tweak_info_plist.py @@ -0,0 +1,447 @@ +#!/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. +# + +from __future__ import print_function + +import optparse +import os +import plistlib +import re +import subprocess +import sys +import tempfile + +TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + + +def _ConvertPlist(source_plist, output_plist, fmt): + """Convert |source_plist| to |fmt| and save as |output_plist|.""" + assert sys.version_info.major == 2, "Use plistlib directly in Python 3" + return subprocess.call( + ['plutil', '-convert', fmt, '-o', output_plist, source_plist]) + + +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, _ = proc.communicate() + return stdout.decode('UTF-8'), 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 _ApplyVersionOverrides(version, keys, overrides, separator='.'): + """Applies version overrides. + + Given a |version| string as "a.b.c.d" (assuming a default separator) with + version components named by |keys| then overrides any value that is present + in |overrides|. + + >>> _ApplyVersionOverrides('a.b', ['major', 'minor'], {'minor': 'd'}) + 'a.d' + """ + if not overrides: + return version + version_values = version.split(separator) + for i, (key, value) in enumerate(zip(keys, version_values)): + if key in overrides: + version_values[i] = overrides[key] + return separator.join(version_values) + + +def _GetVersion(version_format, values, overrides=None): + """Generates a version number according to |version_format| using the values + from |values| or |overrides| if given.""" + result = version_format + for key in values: + if overrides and key in overrides: + value = overrides[key] + else: + value = values[key] + result = result.replace('@%s@' % key, value) + return result + + +def _AddVersionKeys(plist, version_format_for_key, version=None, + overrides=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 not version: + # 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, retval) = _GetOutput([ + VERSION_TOOL, '-f', VERSION_FILE, '-t', + '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + ]) + + # If the command finished with a non-zero return code, then report the + # error up. + if retval != 0: + return False + + version = stdout.strip() + + # Parse the given version number, that should be in MAJOR.MINOR.BUILD.PATCH + # format (where each value is a number). Note that str.isdigit() returns + # True if the string is composed only of digits (and thus match \d+ regexp). + groups = version.split('.') + if len(groups) != 4 or not all(element.isdigit() for element in groups): + print('Invalid version string specified: "%s"' % version, file=sys.stderr) + return False + values = dict(zip(('MAJOR', 'MINOR', 'BUILD', 'PATCH'), groups)) + + for key in version_format_for_key: + plist[key] = _GetVersion(version_format_for_key[key], values, overrides) + + # 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('Could not determine SCM revision. This may be OK.', file=sys.stderr) + + return True + + +def _AddBreakpadKeys(plist, branding, platform, staging): + """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_%s' % (branding, platform) + plist['BreakpadProductDisplay'] = branding + if staging: + plist['BreakpadURL'] = 'https://clients2.google.com/cr/staging_report' + else: + plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' + + # 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 = ('full', ) + assert tuple(sorted(components)) == components + + components_len = len(components) + combinations = 1 << components_len + tag_suffixes = [] + for combination in range(0, combinations): + tag_suffix = '' + for component_index in range(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, base_tag): + """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') + if base_tag != '': + plist['KSChannelID'] = base_tag + for tag_suffix in _TagSuffixes(): + if tag_suffix: + plist['KSChannelID' + tag_suffix] = base_tag + tag_suffix + + +def _RemoveKeystoneKeys(plist): + """Removes any set Keystone keys.""" + _RemoveKeys(plist, 'KSVersion', 'KSProductID', 'KSUpdateURL') + + tag_keys = ['KSChannelID'] + for tag_suffix in _TagSuffixes(): + tag_keys.append('KSChannelID' + tag_suffix) + _RemoveKeys(plist, *tag_keys) + + +def _AddGTMKeys(plist, platform): + """Adds the GTM metadata keys. This must be called AFTER _AddVersionKeys().""" + plist['GTMUserAgentID'] = plist['CFBundleName'] + if platform == 'ios': + plist['GTMUserAgentVersion'] = plist['CFBundleVersion'] + else: + plist['GTMUserAgentVersion'] = plist['CFBundleShortVersionString'] + + +def _RemoveGTMKeys(plist): + """Removes any set GTM metadata keys.""" + _RemoveKeys(plist, 'GTMUserAgentID', 'GTMUserAgentVersion') + + +def Main(argv): + parser = optparse.OptionParser('%prog [options]') + parser.add_option('--plist', + dest='plist_path', + action='store', + type='string', + default=None, + help='The path of the plist to tweak.') + parser.add_option('--output', dest='plist_output', action='store', + type='string', default=None, help='If specified, the path to output ' + \ + 'the tweaked plist, rather than overwriting the input.') + parser.add_option('--breakpad', + dest='use_breakpad', + action='store', + type='int', + default=False, + help='Enable Breakpad [1 or 0]') + parser.add_option( + '--breakpad_staging', + dest='use_breakpad_staging', + action='store_true', + default=False, + help='Use staging breakpad to upload reports. Ignored if --breakpad=0.') + parser.add_option('--keystone', + dest='use_keystone', + action='store', + type='int', + default=False, + help='Enable Keystone [1 or 0]') + parser.add_option('--keystone-base-tag', + default='', + help='Base Keystone tag to set') + 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('--platform', + choices=('ios', 'mac'), + default='mac', + help='The target platform of the bundle') + parser.add_option('--add-gtm-metadata', + dest='add_gtm_info', + action='store', + type='int', + default=False, + help='Add GTM metadata [1 or 0]') + # TODO(crbug.com/1140474): Remove once iOS 14.2 reaches mass adoption. + parser.add_option('--lock-to-version', + help='Set CFBundleVersion to given value + @MAJOR@@PATH@') + parser.add_option( + '--version-overrides', + action='append', + help='Key-value pair to override specific component of version ' + 'like key=value (can be passed multiple time to configure ' + 'more than one override)') + parser.add_option('--format', + choices=('binary1', 'xml1'), + default='xml1', + help='Format to use when writing property list ' + '(default: %(default)s)') + 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(parser.get_usage(), file=sys.stderr) + return 1 + + if not options.plist_path: + print('No --plist specified.', file=sys.stderr) + return 1 + + # Read the plist into its parsed format. Convert the file to 'xml1' as + # plistlib only supports that format in Python 2.7. + with tempfile.NamedTemporaryFile() as temp_info_plist: + if sys.version_info.major == 2: + retcode = _ConvertPlist(options.plist_path, temp_info_plist.name, 'xml1') + if retcode != 0: + return retcode + plist = plistlib.readPlist(temp_info_plist.name) + else: + with open(options.plist_path, 'rb') as f: + plist = plistlib.load(f) + + # Convert overrides. + overrides = {} + if options.version_overrides: + for pair in options.version_overrides: + if not '=' in pair: + print('Invalid value for --version-overrides:', pair, file=sys.stderr) + return 1 + key, value = pair.split('=', 1) + overrides[key] = value + if key not in ('MAJOR', 'MINOR', 'BUILD', 'PATCH'): + print('Unsupported key for --version-overrides:', key, file=sys.stderr) + return 1 + + if options.platform == 'mac': + version_format_for_key = { + # Add public version info so "Get Info" works. + 'CFBundleShortVersionString': '@MAJOR@.@MINOR@.@BUILD@.@PATCH@', + + # 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. + 'CFBundleVersion': '@BUILD@.@PATCH@', + } + else: + # TODO(crbug.com/1140474): Remove once iOS 14.2 reaches mass adoption. + if options.lock_to_version: + # Pull in the PATCH number and format it to 3 digits. + VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') + VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') + (stdout, + retval) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', '@PATCH@']) + if retval != 0: + return 2 + patch = '{:03d}'.format(int(stdout)) + version_format_for_key = { + 'CFBundleShortVersionString': '@MAJOR@.@BUILD@.@PATCH@', + 'CFBundleVersion': options.lock_to_version + '.@MAJOR@' + patch + } + else: + version_format_for_key = { + 'CFBundleShortVersionString': '@MAJOR@.@BUILD@.@PATCH@', + 'CFBundleVersion': '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + } + + if options.use_breakpad: + version_format_for_key['BreakpadVersion'] = \ + '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + + # Insert the product version. + if not _AddVersionKeys(plist, + version_format_for_key, + version=options.version, + overrides=overrides): + return 2 + + # Add Breakpad if configured to do so. + if options.use_breakpad: + if options.branding is None: + print('Use of Breakpad requires branding.', file=sys.stderr) + return 1 + # Map "target_os" passed from gn via the --platform parameter + # to the platform as known by breakpad. + platform = {'mac': 'Mac', 'ios': 'iOS'}[options.platform] + _AddBreakpadKeys(plist, options.branding, platform, + options.use_breakpad_staging) + else: + _RemoveBreakpadKeys(plist) + + # Add Keystone if configured to do so. + if options.use_keystone: + if options.bundle_identifier is None: + print('Use of Keystone requires the bundle id.', file=sys.stderr) + return 1 + _AddKeystoneKeys(plist, options.bundle_identifier, + options.keystone_base_tag) + else: + _RemoveKeystoneKeys(plist) + + # Adds or removes any SCM keys. + if not _DoSCMKeys(plist, options.add_scm_info): + return 3 + + # Add GTM metadata keys. + if options.add_gtm_info: + _AddGTMKeys(plist, options.platform) + else: + _RemoveGTMKeys(plist) + + output_path = options.plist_path + if options.plist_output is not None: + output_path = options.plist_output + + # Now that all keys have been mutated, rewrite the file. + # Convert Info.plist to the format requested by the --format flag. Any + # format would work on Mac but iOS requires specific format. + if sys.version_info.major == 2: + with tempfile.NamedTemporaryFile() as temp_info_plist: + plistlib.writePlist(plist, temp_info_plist.name) + return _ConvertPlist(temp_info_plist.name, output_path, options.format) + with open(output_path, 'wb') as f: + plist_format = {'binary1': plistlib.FMT_BINARY, 'xml1': plistlib.FMT_XML} + plistlib.dump(plist, f, fmt=plist_format[options.format]) + + +if __name__ == '__main__': + # TODO(https://crbug.com/941669): Temporary workaround until all scripts use + # python3 by default. + if sys.version_info[0] < 3: + os.execvp('python3', ['python3'] + sys.argv) + sys.exit(Main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/apple/write_pkg_info.py b/third_party/libwebrtc/build/apple/write_pkg_info.py new file mode 100644 index 0000000000..8d07cdb311 --- /dev/null +++ b/third_party/libwebrtc/build/apple/write_pkg_info.py @@ -0,0 +1,54 @@ +# 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. + +import argparse +import os +import plist_util +import sys + +# This script creates a PkgInfo file for an OS X .app bundle's plist. +# Usage: python write_pkg_info.py --plist Foo.app/Contents/Info.plist \ +# --output Foo.app/Contents/PkgInfo + + +def Main(): + parser = argparse.ArgumentParser( + description='A script to write PkgInfo files for .app bundles.') + parser.add_argument('--plist', + required=True, + help='Path to the Info.plist for the .app.') + parser.add_argument('--output', + required=True, + help='Path to the desired output file.') + args = parser.parse_args() + + # Remove the output if it exists already. + if os.path.exists(args.output): + os.unlink(args.output) + + plist = plist_util.LoadPList(args.plist) + package_type = plist['CFBundlePackageType'] + if package_type != 'APPL': + raise ValueError('Expected CFBundlePackageType to be %s, got %s' % \ + ('APPL', package_type)) + + # The format of PkgInfo is eight characters, representing the bundle type + # and bundle signature, each four characters. If that is missing, four + # '?' characters are used instead. + signature_code = plist.get('CFBundleSignature', '????') + if len(signature_code) != 4: + raise ValueError('CFBundleSignature should be exactly four characters, ' + + 'got %s' % signature_code) + + with open(args.output, 'w') as fp: + fp.write('%s%s' % (package_type, signature_code)) + return 0 + + +if __name__ == '__main__': + # TODO(https://crbug.com/941669): Temporary workaround until all scripts use + # python3 by default. + if sys.version_info[0] < 3: + os.execvp('python3', ['python3'] + sys.argv) + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/apple/xcrun.py b/third_party/libwebrtc/build/apple/xcrun.py new file mode 100755 index 0000000000..71bf50c352 --- /dev/null +++ b/third_party/libwebrtc/build/apple/xcrun.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 +# Copyright 2020 The Chromium 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 around xcrun adding support for --developer-dir parameter to set +the DEVELOPER_DIR environment variable, and for converting paths relative +to absolute (since this is required by most of the tool run via xcrun). +""" + +import argparse +import os +import subprocess +import sys + + +def xcrun(command, developer_dir): + environ = dict(os.environ) + if developer_dir: + environ['DEVELOPER_DIR'] = os.path.abspath(developer_dir) + + processed_args = ['/usr/bin/xcrun'] + for arg in command: + if os.path.exists(arg): + arg = os.path.abspath(arg) + processed_args.append(arg) + + process = subprocess.Popen(processed_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + env=environ) + + stdout, stderr = process.communicate() + sys.stdout.write(stdout) + if process.returncode: + sys.stderr.write(stderr) + sys.exit(process.returncode) + + +def main(args): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument( + '--developer-dir', + help='path to developer dir to use for the invocation of xcrun') + + parsed, remaining_args = parser.parse_known_args(args) + xcrun(remaining_args, parsed.developer_dir) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/args/OWNERS b/third_party/libwebrtc/build/args/OWNERS new file mode 100644 index 0000000000..d218b6bdbd --- /dev/null +++ b/third_party/libwebrtc/build/args/OWNERS @@ -0,0 +1 @@ +per-file headless.gn=file://headless/OWNERS diff --git a/third_party/libwebrtc/build/args/README.txt b/third_party/libwebrtc/build/args/README.txt new file mode 100644 index 0000000000..825bf64c69 --- /dev/null +++ b/third_party/libwebrtc/build/args/README.txt @@ -0,0 +1,31 @@ +This directory is here to hold .gni files that contain sets of GN build +arguments for given configurations. + +(Currently this directory is empty because we removed the only thing here, but +this has come up several times so I'm confident we'll need this again. If this +directory is still empty by 2017, feel free to delete it. --Brett) + +Some projects or bots may have build configurations with specific combinations +of flags. Rather than making a new global flag for your specific project and +adding it all over the build to each arg it should affect, you can add a .gni +file here with the variables. + +For example, for project foo you may put in build/args/foo.gni: + + target_os = "android" + use_pulseaudio = false + use_ozone = true + system_libdir = "foo" + +Users wanting to build this configuration would run: + + $ gn args out/mybuild + +And add the following line to their args for that build directory: + + import("//build/args/foo.gni") + # You can set any other args here like normal. + is_component_build = false + +This way everybody can agree on a set of flags for a project, and their builds +stay in sync as the flags in foo.gni are modified. diff --git a/third_party/libwebrtc/build/args/chromeos/README.md b/third_party/libwebrtc/build/args/chromeos/README.md new file mode 100644 index 0000000000..e02e185519 --- /dev/null +++ b/third_party/libwebrtc/build/args/chromeos/README.md @@ -0,0 +1,52 @@ +This directory is used to store GN arg mapping for Chrome OS boards. + +Files in this directory are populated by running `gclient sync` with specific +arguments set in the .gclient file. Specifically: +* The file must have a top-level variable set: `target_os = ["chromeos"]` +* The `"custom_vars"` parameter of the chromium/src.git solution must include + the parameter: `"cros_boards": "{BOARD_NAMES}"` where `{BOARD_NAMES}` is a + colon-separated list of boards you'd like to checkout. +* If you'd like to a checkout a QEMU-bootable image for a given board, include + it in the `cros_boards_with_qemu_images` var rather than the `cros_boards` + var. + +A typical .gclient file is a sibling of the src/ directory, and might look like +this: +``` +solutions = [ + { + "url": "https://chromium.googlesource.com/chromium/src.git", + "managed": False, + "name": "src", + "custom_deps": {}, + "custom_vars" : { + "checkout_src_internal": True, + "cros_boards": "eve:kevin", + # If a QEMU-bootable image is desired for any board, move it from + # the previous var to the following: + "cros_boards_with_qemu_images": "amd64-generic", + }, + }, +] +target_os = ["chromeos"] +``` + +To use these files in a build, simply add the following line to your GN args: +``` +import("//build/args/chromeos/${some_board}.gni") +``` + +That will produce a Chrome OS build of Chrome very similar to what is shipped +for that device. You can also supply additional args or even overwrite ones +supplied in the .gni file after the `import()` line. For example, the following +args will produce a debug build of Chrome for board=eve using goma: +``` +import("//build/args/chromeos/eve.gni") + +is_debug = true +use_goma = true +goma_dir = "/path/to/goma/" +``` + +TODO(bpastene): Make 'cros_boards' a first class citizen in gclient and replace +it with 'target_boards' instead. diff --git a/third_party/libwebrtc/build/args/headless.gn b/third_party/libwebrtc/build/args/headless.gn new file mode 100644 index 0000000000..ed6308ba22 --- /dev/null +++ b/third_party/libwebrtc/build/args/headless.gn @@ -0,0 +1,58 @@ +# GN args template for the Headless Chrome library +# +# Add import to arg.gn in out directory and run gn gen on the directory to use. +# E.g. for out directory out/foo: +# echo 'import("//build/args/headless.gn")' > out/foo/args.gn +# gn gen out/foo +# +# Use gn args to add your own build preference args. + +use_ozone = true +ozone_auto_platforms = false +ozone_platform = "headless" +ozone_platform_headless = true +angle_enable_vulkan = true +angle_enable_swiftshader = true + +# Embed resource.pak into binary to simplify deployment. +headless_use_embedded_resources = true + +# Expose headless bindings for freetype library bundled with Chromium. +headless_fontconfig_utils = true + +# Don't use Prefs component, disabling access to Local State prefs. +headless_use_prefs = false + +# Don't use Policy component, disabling all policies. +headless_use_policy = false + +# Remove a dependency on a system fontconfig library. +use_bundled_fontconfig = true + +# In order to simplify deployment we build ICU data file +# into binary. +icu_use_data_file = false + +# Use embedded data instead external files for headless in order +# to simplify deployment. +v8_use_external_startup_data = false + +enable_nacl = false +enable_print_preview = false +enable_remoting = false +use_alsa = false +use_cups = false +use_dbus = false +use_gio = false +use_kerberos = false +use_libpci = false +use_pulseaudio = false +use_udev = false +rtc_use_pipewire = false +v8_enable_lazy_source_positions = false +use_glib = false +use_gtk = false +use_pangocairo = false + +# TODO(1096425): Remove this once use_x11 goes away. +use_x11 = false diff --git a/third_party/libwebrtc/build/build-ctags.sh b/third_party/libwebrtc/build/build-ctags.sh new file mode 100755 index 0000000000..61e017e329 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/build_config.h b/third_party/libwebrtc/build/build_config.h new file mode 100644 index 0000000000..c39ae9da50 --- /dev/null +++ b/third_party/libwebrtc/build/build_config.h @@ -0,0 +1,245 @@ +// 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_AIX / OS_ANDROID / OS_ASMJS / OS_FREEBSD / OS_FUCHSIA / OS_IOS / +// OS_LINUX / OS_MAC / OS_NACL (SFI or NONSFI) / OS_NETBSD / OS_OPENBSD / +// OS_QNX / OS_SOLARIS / OS_WIN +// Operating System family: +// OS_APPLE: IOS or MAC +// OS_BSD: FREEBSD or NETBSD or OPENBSD +// OS_POSIX: AIX or ANDROID or ASMJS or CHROMEOS or FREEBSD or IOS or LINUX +// or MAC or NACL or NETBSD or OPENBSD or QNX or SOLARIS +// +// /!\ Note: OS_CHROMEOS is set by the build system, not this file +// +// Compiler: +// COMPILER_MSVC / COMPILER_GCC +// +// Processor: +// ARCH_CPU_ARM64 / ARCH_CPU_ARMEL / ARCH_CPU_MIPS / ARCH_CPU_MIPS64 / +// ARCH_CPU_MIPS64EL / ARCH_CPU_MIPSEL / ARCH_CPU_PPC64 / ARCH_CPU_S390 / +// ARCH_CPU_S390X / ARCH_CPU_X86 / ARCH_CPU_X86_64 +// Processor family: +// ARCH_CPU_ARM_FAMILY: ARMEL or ARM64 +// ARCH_CPU_MIPS_FAMILY: MIPS64EL or MIPSEL or MIPS64 or MIPS +// ARCH_CPU_PPC64_FAMILY: PPC64 +// ARCH_CPU_S390_FAMILY: S390 or S390X +// ARCH_CPU_X86_FAMILY: X86 or X86_64 +// Processor features: +// ARCH_CPU_31_BITS / ARCH_CPU_32_BITS / ARCH_CPU_64_BITS +// ARCH_CPU_BIG_ENDIAN / ARCH_CPU_LITTLE_ENDIAN + +#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 TargetConditionals after testing ANDROID as some Android builds +// on the Mac have this header available and it's not needed unless the target +// is really an Apple platform. +#include +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#define OS_IOS 1 +#else +#define OS_MAC 1 +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#elif defined(__linux__) +#if !defined(OS_CHROMEOS) +// Do not define OS_LINUX on Chrome OS build. +// The OS_CHROMEOS macro is defined in GN. +#define OS_LINUX 1 +#endif // !defined(OS_CHROMEOS) +// 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 +#elif defined(__Fuchsia__) +#define OS_FUCHSIA 1 +#elif defined(__FreeBSD__) +#define OS_FREEBSD 1 +#elif defined(__NetBSD__) +#define OS_NETBSD 1 +#elif defined(__OpenBSD__) +#define OS_OPENBSD 1 +#elif defined(__sun) +#define OS_SOLARIS 1 +#elif defined(__QNXNTO__) +#define OS_QNX 1 +#elif defined(_AIX) +#define OS_AIX 1 +#elif defined(__asmjs__) || defined(__wasm__) +#define OS_ASMJS 1 +#else +#error Please add support for your platform in build/build_config.h +#endif +// NOTE: Adding a new port? Please follow +// https://chromium.googlesource.com/chromium/src/+/main/docs/new_port_policy.md + +#if defined(OS_MAC) || defined(OS_IOS) +#define OS_APPLE 1 +#endif + +// For access to standard BSD features, use OS_BSD instead of a +// more specific macro. +#if defined(OS_FREEBSD) || defined(OS_NETBSD) || 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_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) || \ + defined(OS_FREEBSD) || defined(OS_IOS) || defined(OS_LINUX) || \ + defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NACL) || \ + defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_QNX) || \ + defined(OS_SOLARIS) +#define OS_POSIX 1 +#endif + +// Compiler detection. Note: clang masquerades as GCC on POSIX and as MSVC on +// Windows. +#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(__s390x__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390X 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__s390__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390 1 +#define ARCH_CPU_31_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_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__) || defined(_M_ARM64) +#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__) || defined(__asmjs__) || defined(__wasm__) +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__MIPSEL__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_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 +#elif defined(__MIPSEB__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#endif +#elif defined(__riscv) && __riscv_xlen == 64 +#define ARCH_CPU_RISCV64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__sparc__) +#if defined(__LP64__) +#define ARCH_CPU_SPARC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#endif +#define ARCH_CPU_SPARC 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#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_FUCHSIA) +#define WCHAR_T_IS_UTF32 +#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 std::u16string::const_iterator and "char16*" are +// equivalent types. +#define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER +#endif + +#endif // BUILD_BUILD_CONFIG_H_ diff --git a/third_party/libwebrtc/build/buildflag.h b/third_party/libwebrtc/build/buildflag.h new file mode 100644 index 0000000000..5776a754c4 --- /dev/null +++ b/third_party/libwebrtc/build/buildflag.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef BUILD_BUILDFLAG_H_ +#define BUILD_BUILDFLAG_H_ + +// These macros un-mangle the names of the build flags in a way that looks +// natural, and gives errors if the flag is not defined. Normally in the +// preprocessor it's easy to make mistakes that interpret "you haven't done +// the setup to know what the flag is" as "flag is off". Normally you would +// include the generated header rather than include this file directly. +// +// This is for use with generated headers. See build/buildflag_header.gni. + +// This dance of two macros does a concatenation of two preprocessor args using +// ## doubly indirectly because using ## directly prevents macros in that +// parameter from being expanded. +#define BUILDFLAG_CAT_INDIRECT(a, b) a ## b +#define BUILDFLAG_CAT(a, b) BUILDFLAG_CAT_INDIRECT(a, b) + +// Accessor for build flags. +// +// To test for a value, if the build file specifies: +// +// ENABLE_FOO=true +// +// Then you would check at build-time in source code with: +// +// #include "foo_flags.h" // The header the build file specified. +// +// #if BUILDFLAG(ENABLE_FOO) +// ... +// #endif +// +// There will no #define called ENABLE_FOO so if you accidentally test for +// whether that is defined, it will always be negative. You can also use +// the value in expressions: +// +// const char kSpamServerName[] = BUILDFLAG(SPAM_SERVER_NAME); +// +// Because the flag is accessed as a preprocessor macro with (), an error +// will be thrown if the proper header defining the internal flag value has +// not been included. +#define BUILDFLAG(flag) (BUILDFLAG_CAT(BUILDFLAG_INTERNAL_, flag)()) + +#endif // BUILD_BUILDFLAG_H_ diff --git a/third_party/libwebrtc/build/buildflag_header.gni b/third_party/libwebrtc/build/buildflag_header.gni new file mode 100644 index 0000000000..821c4efe45 --- /dev/null +++ b/third_party/libwebrtc/build/buildflag_header.gni @@ -0,0 +1,137 @@ +# 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 header with preprocessor defines specified by the build file. +# +# The flags are converted to function-style defines with mangled names and +# code uses an accessor macro to access the values. This is to try to +# minimize bugs where code checks whether something is defined or not, and +# the proper header isn't included, meaning the answer will always be silently +# false or might vary across the code base. +# +# In the GN template, specify build flags in the template as a list +# of strings that encode key/value pairs like this: +# +# flags = [ "ENABLE_FOO=1", "ENABLE_BAR=$enable_bar" ] +# +# The GN values "true" and "false" will be mapped to 0 and 1 for boolean +# #if flags to be expressed naturally. This means you can't directly make a +# define that generates C++ value of true or false for use in code. If you +# REALLY need this, you can also use the string "(true)" and "(false)" to +# prevent the rewriting. + +# To check the value of the flag in C code: +# +# #include "path/to/here/header_file.h" +# +# #if BUILDFLAG(ENABLE_FOO) +# ... +# #endif +# +# const char kSpamServerUrl[] = BUILDFLAG(SPAM_SERVER_URL); +# +# There will be no #define called ENABLE_FOO so if you accidentally test for +# that in an ifdef it will always be negative. +# +# +# Template parameters +# +# flags [required, list of strings] +# Flag values as described above. +# +# header [required, string] +# File name for generated header. By default, this will go in the +# generated file directory for this target, and you would include it +# with: +# #include "/

" +# +# header_dir [optional, string] +# Override the default location of the generated header. The string will +# be treated as a subdirectory of the root_gen_dir. For example: +# header_dir = "foo/bar" +# Then you can include the header as: +# #include "foo/bar/baz.h" +# +# deps, public_deps, testonly, visibility +# Normal meaning. +# +# +# Grit defines +# +# If one .grd file uses a flag, just add to the grit target: +# +# defines = [ +# "enable_doom_melon=$enable_doom_melon", +# ] +# +# If multiple .grd files use it, you'll want to put the defines in a .gni file +# so it can be shared. Generally this .gni file should include all grit defines +# for a given module (for some definition of "module"). Then do: +# +# defines = ui_grit_defines +# +# If you forget to do this, the flag will be implicitly false in the .grd file +# and those resources won't be compiled. You'll know because the resource +# #define won't be generated and any code that uses it won't compile. If you +# see a missing IDS_* string, this is probably the reason. +# +# +# Example +# +# buildflag_header("foo_buildflags") { +# header = "foo_buildflags.h" +# +# flags = [ +# # This uses the GN build flag enable_doom_melon as the definition. +# "ENABLE_DOOM_MELON=$enable_doom_melon", +# +# # This force-enables the flag. +# "ENABLE_SPACE_LASER=true", +# +# # This will expand to the quoted C string when used in source code. +# "SPAM_SERVER_URL=\"http://www.example.com/\"", +# ] +# } +template("buildflag_header") { + action(target_name) { + script = "//build/write_buildflag_header.py" + + if (defined(invoker.header_dir)) { + header_file = "${invoker.header_dir}/${invoker.header}" + } else { + # Compute the path from the root to this file. + header_file = rebase_path(".", "//") + "/${invoker.header}" + } + + outputs = [ "$root_gen_dir/$header_file" ] + + # Always write --flags to the file so it's not empty. Empty will confuse GN + # into thinking the response file isn't used. + response_file_contents = [ "--flags" ] + if (defined(invoker.flags)) { + response_file_contents += invoker.flags + } + + args = [ + "--output", + header_file, # Not rebased, Python script puts it inside gen-dir. + "--rulename", + get_label_info(":$target_name", "label_no_toolchain"), + "--gen-dir", + rebase_path(root_gen_dir, root_build_dir), + "--definitions", + "{{response_file_name}}", + ] + + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + "visibility", + ]) + + public_deps = [ "//build:buildflag_header_h" ] + } +} diff --git a/third_party/libwebrtc/build/check_gn_headers.py b/third_party/libwebrtc/build/check_gn_headers.py new file mode 100755 index 0000000000..9bdbba895f --- /dev/null +++ b/third_party/libwebrtc/build/check_gn_headers.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium 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 header files missing in GN. + +This script gets all the header files from ninja_deps, which is from the true +dependency generated by the compiler, and report if they don't exist in GN. +""" + +from __future__ import print_function + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +from multiprocessing import Process, Queue + +SRC_DIR = os.path.abspath( + os.path.join(os.path.abspath(os.path.dirname(__file__)), os.path.pardir)) +DEPOT_TOOLS_DIR = os.path.join(SRC_DIR, 'third_party', 'depot_tools') + + +def GetHeadersFromNinja(out_dir, skip_obj, q): + """Return all the header files from ninja_deps""" + + def NinjaSource(): + cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-t', 'deps'] + # A negative bufsize means to use the system default, which usually + # means fully buffered. + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=-1) + for line in iter(popen.stdout.readline, ''): + yield line.rstrip() + + popen.stdout.close() + return_code = popen.wait() + if return_code: + raise subprocess.CalledProcessError(return_code, cmd) + + ans, err = set(), None + try: + ans = ParseNinjaDepsOutput(NinjaSource(), out_dir, skip_obj) + except Exception as e: + err = str(e) + q.put((ans, err)) + + +def ParseNinjaDepsOutput(ninja_out, out_dir, skip_obj): + """Parse ninja output and get the header files""" + all_headers = {} + + # Ninja always uses "/", even on Windows. + prefix = '../../' + + is_valid = False + obj_file = '' + for line in ninja_out: + if line.startswith(' '): + if not is_valid: + continue + if line.endswith('.h') or line.endswith('.hh'): + f = line.strip() + if f.startswith(prefix): + f = f[6:] # Remove the '../../' prefix + # build/ only contains build-specific files like build_config.h + # and buildflag.h, and system header files, so they should be + # skipped. + if f.startswith(out_dir) or f.startswith('out'): + continue + if not f.startswith('build'): + all_headers.setdefault(f, []) + if not skip_obj: + all_headers[f].append(obj_file) + else: + is_valid = line.endswith('(VALID)') + obj_file = line.split(':')[0] + + return all_headers + + +def GetHeadersFromGN(out_dir, q): + """Return all the header files from GN""" + + tmp = None + ans, err = set(), None + try: + # Argument |dir| is needed to make sure it's on the same drive on Windows. + # dir='' means dir='.', but doesn't introduce an unneeded prefix. + tmp = tempfile.mkdtemp(dir='') + shutil.copy2(os.path.join(out_dir, 'args.gn'), + os.path.join(tmp, 'args.gn')) + # Do "gn gen" in a temp dir to prevent dirtying |out_dir|. + gn_exe = 'gn.bat' if sys.platform == 'win32' else 'gn' + subprocess.check_call([ + os.path.join(DEPOT_TOOLS_DIR, gn_exe), 'gen', tmp, '--ide=json', '-q']) + gn_json = json.load(open(os.path.join(tmp, 'project.json'))) + ans = ParseGNProjectJSON(gn_json, out_dir, tmp) + except Exception as e: + err = str(e) + finally: + if tmp: + shutil.rmtree(tmp) + q.put((ans, err)) + + +def ParseGNProjectJSON(gn, out_dir, tmp_out): + """Parse GN output and get the header files""" + all_headers = set() + + for _target, properties in gn['targets'].iteritems(): + sources = properties.get('sources', []) + public = properties.get('public', []) + # Exclude '"public": "*"'. + if type(public) is list: + sources += public + for f in sources: + if f.endswith('.h') or f.endswith('.hh'): + if f.startswith('//'): + f = f[2:] # Strip the '//' prefix. + if f.startswith(tmp_out): + f = out_dir + f[len(tmp_out):] + all_headers.add(f) + + return all_headers + + +def GetDepsPrefixes(q): + """Return all the folders controlled by DEPS file""" + prefixes, err = set(), None + try: + gclient_exe = 'gclient.bat' if sys.platform == 'win32' else 'gclient' + gclient_out = subprocess.check_output([ + os.path.join(DEPOT_TOOLS_DIR, gclient_exe), + 'recurse', '--no-progress', '-j1', + 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]'], + universal_newlines=True) + for i in gclient_out.split('\n'): + if i.startswith('src/'): + i = i[4:] + prefixes.add(i) + except Exception as e: + err = str(e) + q.put((prefixes, err)) + + +def IsBuildClean(out_dir): + cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-n'] + try: + out = subprocess.check_output(cmd) + return 'no work to do.' in out + except Exception as e: + print(e) + return False + +def ParseWhiteList(whitelist): + out = set() + for line in whitelist.split('\n'): + line = re.sub(r'#.*', '', line).strip() + if line: + out.add(line) + return out + + +def FilterOutDepsedRepo(files, deps): + return {f for f in files if not any(f.startswith(d) for d in deps)} + + +def GetNonExistingFiles(lst): + out = set() + for f in lst: + if not os.path.isfile(f): + out.add(f) + return out + + +def main(): + + def DumpJson(data): + if args.json: + with open(args.json, 'w') as f: + json.dump(data, f) + + def PrintError(msg): + DumpJson([]) + parser.error(msg) + + parser = argparse.ArgumentParser(description=''' + NOTE: Use ninja to build all targets in OUT_DIR before running + this script.''') + parser.add_argument('--out-dir', metavar='OUT_DIR', default='out/Release', + help='output directory of the build') + parser.add_argument('--json', + help='JSON output filename for missing headers') + parser.add_argument('--whitelist', help='file containing whitelist') + parser.add_argument('--skip-dirty-check', action='store_true', + help='skip checking whether the build is dirty') + parser.add_argument('--verbose', action='store_true', + help='print more diagnostic info') + + args, _extras = parser.parse_known_args() + + if not os.path.isdir(args.out_dir): + parser.error('OUT_DIR "%s" does not exist.' % args.out_dir) + + if not args.skip_dirty_check and not IsBuildClean(args.out_dir): + dirty_msg = 'OUT_DIR looks dirty. You need to build all there.' + if args.json: + # Assume running on the bots. Silently skip this step. + # This is possible because "analyze" step can be wrong due to + # underspecified header files. See crbug.com/725877 + print(dirty_msg) + DumpJson([]) + return 0 + else: + # Assume running interactively. + parser.error(dirty_msg) + + d_q = Queue() + d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, True, d_q,)) + d_p.start() + + gn_q = Queue() + gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,)) + gn_p.start() + + deps_q = Queue() + deps_p = Process(target=GetDepsPrefixes, args=(deps_q,)) + deps_p.start() + + d, d_err = d_q.get() + gn, gn_err = gn_q.get() + missing = set(d.keys()) - gn + nonexisting = GetNonExistingFiles(gn) + + deps, deps_err = deps_q.get() + missing = FilterOutDepsedRepo(missing, deps) + nonexisting = FilterOutDepsedRepo(nonexisting, deps) + + d_p.join() + gn_p.join() + deps_p.join() + + if d_err: + PrintError(d_err) + if gn_err: + PrintError(gn_err) + if deps_err: + PrintError(deps_err) + if len(GetNonExistingFiles(d)) > 0: + print('Non-existing files in ninja deps:', GetNonExistingFiles(d)) + PrintError('Found non-existing files in ninja deps. You should ' + + 'build all in OUT_DIR.') + if len(d) == 0: + PrintError('OUT_DIR looks empty. You should build all there.') + if any((('/gen/' in i) for i in nonexisting)): + PrintError('OUT_DIR looks wrong. You should build all there.') + + if args.whitelist: + whitelist = ParseWhiteList(open(args.whitelist).read()) + missing -= whitelist + nonexisting -= whitelist + + missing = sorted(missing) + nonexisting = sorted(nonexisting) + + DumpJson(sorted(missing + nonexisting)) + + if len(missing) == 0 and len(nonexisting) == 0: + return 0 + + if len(missing) > 0: + print('\nThe following files should be included in gn files:') + for i in missing: + print(i) + + if len(nonexisting) > 0: + print('\nThe following non-existing files should be removed from gn files:') + for i in nonexisting: + print(i) + + if args.verbose: + # Only get detailed obj dependency here since it is slower. + GetHeadersFromNinja(args.out_dir, False, d_q) + d, d_err = d_q.get() + print('\nDetailed dependency info:') + for f in missing: + print(f) + for cc in d[f]: + print(' ', cc) + + print('\nMissing headers sorted by number of affected object files:') + count = {k: len(v) for (k, v) in d.iteritems()} + for f in sorted(count, key=count.get, reverse=True): + if f in missing: + print(count[f], f) + + if args.json: + # Assume running on the bots. Temporarily return 0 before + # https://crbug.com/937847 is fixed. + return 0 + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/check_gn_headers_unittest.py b/third_party/libwebrtc/build/check_gn_headers_unittest.py new file mode 100755 index 0000000000..20c3b13897 --- /dev/null +++ b/third_party/libwebrtc/build/check_gn_headers_unittest.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium 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 json +import unittest +import check_gn_headers + + +ninja_input = r''' +obj/a.o: #deps 1, deps mtime 123 (VALID) + ../../a.cc + ../../dir/path/b.h + ../../c.hh + +obj/b.o: #deps 1, deps mtime 123 (STALE) + ../../b.cc + ../../dir2/path/b.h + ../../c2.hh + +obj/c.o: #deps 1, deps mtime 123 (VALID) + ../../c.cc + ../../build/a.h + gen/b.h + ../../out/Release/gen/no.h + ../../dir3/path/b.h + ../../c3.hh +''' + + +gn_input = json.loads(r''' +{ + "others": [], + "targets": { + "//:All": { + }, + "//:base": { + "public": [ "//base/p.h" ], + "sources": [ "//base/a.cc", "//base/a.h", "//base/b.hh" ], + "visibility": [ "*" ] + }, + "//:star_public": { + "public": "*", + "sources": [ "//base/c.h", "//tmp/gen/a.h" ], + "visibility": [ "*" ] + } + } +} +''') + + +whitelist = r''' + white-front.c +a/b/c/white-end.c # comment + dir/white-both.c #more comment + +# empty line above +a/b/c +''' + + +class CheckGnHeadersTest(unittest.TestCase): + def testNinja(self): + headers = check_gn_headers.ParseNinjaDepsOutput( + ninja_input.split('\n'), 'out/Release', False) + expected = { + 'dir/path/b.h': ['obj/a.o'], + 'c.hh': ['obj/a.o'], + 'dir3/path/b.h': ['obj/c.o'], + 'c3.hh': ['obj/c.o'], + } + self.assertEquals(headers, expected) + + def testGn(self): + headers = check_gn_headers.ParseGNProjectJSON(gn_input, + 'out/Release', 'tmp') + expected = set([ + 'base/a.h', + 'base/b.hh', + 'base/c.h', + 'base/p.h', + 'out/Release/gen/a.h', + ]) + self.assertEquals(headers, expected) + + def testWhitelist(self): + output = check_gn_headers.ParseWhiteList(whitelist) + expected = set([ + 'white-front.c', + 'a/b/c/white-end.c', + 'dir/white-both.c', + 'a/b/c', + ]) + self.assertEquals(output, expected) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.DEBUG) + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/check_gn_headers_whitelist.txt b/third_party/libwebrtc/build/check_gn_headers_whitelist.txt new file mode 100644 index 0000000000..8f312f7688 --- /dev/null +++ b/third_party/libwebrtc/build/check_gn_headers_whitelist.txt @@ -0,0 +1,264 @@ +# Do not add files to this whitelist unless you are adding a new OS or +# changing the GN arguments on bots. + +ash/accelerators/accelerator_table.h +ash/ash_export.h +ash/constants/ash_switches.h +ash/metrics/task_switch_metrics_recorder.h +ash/metrics/task_switch_source.h +ash/metrics/user_metrics_action.h +ash/metrics/user_metrics_recorder.h +ash/public/cpp/ash_public_export.h +ash/public/cpp/shelf_types.h +ash/session/session_observer.h +ash/shell.h +ash/wm/system_modal_container_event_filter_delegate.h +cc/cc_export.h +cc/input/browser_controls_state.h +cc/input/event_listener_properties.h +cc/input/scrollbar.h +cc/input/scroller_size_metrics.h +cc/layers/performance_properties.h +chrome/browser/android/android_theme_resources.h +chrome/browser/android/resource_id.h +chrome/browser/ash/certificate_provider/certificate_info.h +chrome/browser/ash/certificate_provider/certificate_provider.h +chrome/browser/ash/certificate_provider/certificate_provider_service.h +chrome/browser/ash/certificate_provider/certificate_provider_service_factory.h +chrome/browser/ash/certificate_provider/certificate_requests.h +chrome/browser/ash/certificate_provider/pin_dialog_manager.h +chrome/browser/ash/certificate_provider/sign_requests.h +chrome/browser/ash/certificate_provider/thread_safe_certificate_map.h +chrome/browser/ash/login/signin/oauth2_login_manager.h +chrome/browser/ash/login/signin/oauth2_login_verifier.h +chrome/browser/ash/login/signin/oauth2_token_fetcher.h +chrome/browser/ash/notifications/request_pin_view.h +chrome/browser/ash/profiles/profile_helper.h +chrome/browser/ash/settings/cros_settings.h +chrome/browser/component_updater/component_installer_errors.h +chrome/browser/download/download_file_icon_extractor.h +chrome/browser/extensions/api/networking_cast_private/chrome_networking_cast_private_delegate.h +chrome/browser/mac/bluetooth_utility.h +chrome/browser/media/router/mojo/media_route_provider_util_win.h +chrome/browser/media/webrtc/desktop_media_list_ash.h +chrome/browser/media/webrtc/desktop_media_list_observer.h +chrome/browser/media/webrtc/rtp_dump_type.h +chrome/browser/media_galleries/media_file_system_context.h +chrome/browser/notifications/displayed_notifications_dispatch_callback.h +chrome/browser/ui/app_icon_loader_delegate.h +chrome/browser/ui/app_list/app_list_syncable_service_factory.h +chrome/browser/ui/ash/ash_util.h +chrome/browser/ui/ash/multi_user/multi_user_util.h +chrome/browser/ui/network_profile_bubble.h +chrome/browser/ui/views/frame/browser_frame_header_ash.h +chrome/install_static/chromium_install_modes.h +chrome/install_static/install_constants.h +chrome/install_static/install_details.h +chrome/install_static/install_modes.h +chrome/install_static/install_util.h +chrome/install_static/test/scoped_install_details.h +chrome/installer/util/google_update_settings.h +components/browser_watcher/features.h +components/browser_watcher/stability_paths.h +components/cast_certificate/cast_crl_root_ca_cert_der-inc.h +components/cdm/browser/cdm_message_filter_android.h +components/device_event_log/device_event_log_export.h +components/login/login_export.h +components/nacl/browser/nacl_browser_delegate.h +components/nacl/renderer/ppb_nacl_private.h +components/omnibox/browser/autocomplete_i18n.h +components/omnibox/browser/autocomplete_provider_client.h +components/omnibox/browser/autocomplete_provider_listener.h +components/policy/core/browser/configuration_policy_handler_parameters.h +components/policy/proto/policy_proto_export.h +components/rlz/rlz_tracker_delegate.h +components/session_manager/session_manager_types.h +components/sessions/core/sessions_export.h +components/sync/engine/connection_status.h +components/sync/engine/net/network_time_update_callback.h +components/translate/core/browser/translate_infobar_delegate.h +components/user_manager/user.h +components/user_manager/user_image/user_image.h +components/user_manager/user_manager.h +components/wifi/wifi_export.h +components/wifi/wifi_service.h +content/browser/background_fetch/background_fetch_constants.h +content/common/mac/attributed_string_coder.h +content/public/browser/context_factory.h +content/public/browser/media_observer.h +content/public/common/gpu_stream_constants.h +content/renderer/external_popup_menu.h +content/shell/android/shell_descriptors.h +extensions/browser/api/clipboard/clipboard_api.h +extensions/browser/api/webcam_private/webcam.h +extensions/browser/api/webcam_private/webcam_private_api.h +extensions/browser/entry_info.h +extensions/browser/extension_event_histogram_value.h +extensions/browser/extension_function_histogram_value.h +google_apis/gcm/base/encryptor.h +google_apis/gcm/base/gcm_export.h +gpu/GLES2/gl2chromium.h +gpu/GLES2/gl2chromium_autogen.h +gpu/GLES2/gl2extchromium.h +gpu/command_buffer/client/context_support.h +gpu/command_buffer/client/gles2_implementation_unittest_autogen.h +gpu/command_buffer/client/gles2_interface_autogen.h +gpu/command_buffer/client/gles2_interface_stub_autogen.h +gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h +gpu/command_buffer/client/gpu_control_client.h +gpu/command_buffer/client/ref_counted.h +gpu/command_buffer/client/shared_memory_limits.h +gpu/command_buffer/common/command_buffer_shared.h +gpu/command_buffer/common/gles2_cmd_utils_autogen.h +gpu/command_buffer/common/gles2_cmd_utils_implementation_autogen.h +gpu/command_buffer/common/gpu_memory_allocation.h +gpu/command_buffer/service/gles2_cmd_decoder_unittest_extensions_autogen.h +gpu/command_buffer/service/memory_tracking.h +gpu/config/gpu_lists_version.h +gpu/gles2_conform_support/gtf/gtf_stubs.h +gpu/gpu_export.h +headless/lib/headless_macros.h +ipc/ipc_channel_proxy_unittest_messages.h +ipc/ipc_message_null_macros.h +media/audio/audio_logging.h +media/base/routing_token_callback.h +media/base/video_renderer_sink.h +media/cast/common/mod_util.h +media/cast/net/rtcp/rtcp_session.h +media/filters/ffmpeg_aac_bitstream_converter.h +media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h +media/filters/h264_to_annex_b_bitstream_converter.h +media/formats/mp4/avc.h +media/formats/mp4/bitstream_converter.h +media/formats/mp4/fourccs.h +media/formats/mp4/rcheck.h +media/formats/mpeg/adts_stream_parser.h +media/formats/mpeg/mpeg1_audio_stream_parser.h +media/formats/mpeg/mpeg_audio_stream_parser_base.h +media/gpu/media_gpu_export.h +mojo/core/broker_messages.h +mojo/core/system_impl_export.h +mojo/public/cpp/bindings/strong_associated_binding_set.h +mojo/public/cpp/bindings/tests/mojo_test_blink_export.h +mojo/public/cpp/test_support/test_support.h +net/base/winsock_init.h +net/cert/cert_type.h +net/cert/cert_verify_proc_android.h +net/cert/scoped_nss_types.h +net/dns/notify_watcher_mac.h +net/http/http_status_code_list.h +ppapi/cpp/pass_ref.h +ppapi/lib/gl/include/GLES2/gl2.h +ppapi/lib/gl/include/GLES2/gl2ext.h +ppapi/lib/gl/include/GLES2/gl2platform.h +ppapi/lib/gl/include/KHR/khrplatform.h +ppapi/nacl_irt/irt_manifest.h +ppapi/nacl_irt/public/irt_ppapi.h +ppapi/native_client/src/shared/ppapi_proxy/ppruntime.h +ppapi/native_client/src/untrusted/pnacl_irt_shim/irt_shim_ppapi.h +ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.h +ppapi/native_client/src/untrusted/pnacl_irt_shim/shim_ppapi.h +ppapi/proxy/dispatch_reply_message.h +ppapi/proxy/plugin_proxy_delegate.h +ppapi/proxy/plugin_resource_callback.h +ppapi/proxy/ppapi_proxy_export.h +ppapi/proxy/resource_message_filter.h +ppapi/proxy/video_decoder_constants.h +ppapi/shared_impl/api_id.h +ppapi/shared_impl/dir_contents.h +ppapi/shared_impl/ppapi_shared_export.h +ppapi/shared_impl/singleton_resource_id.h +remoting/base/chromoting_event_log_writer.h +remoting/base/logging.h +remoting/client/display/gl_renderer_delegate.h +remoting/client/display/gl_texture_ids.h +remoting/codec/webrtc_video_encoder.h +remoting/host/linux/x11_keyboard.h +remoting/host/worker_process_ipc_delegate.h +remoting/protocol/audio_source.h +remoting/protocol/audio_stream.h +remoting/protocol/cursor_shape_stub.h +remoting/protocol/message_channel_factory.h +remoting/protocol/test_event_matchers.h +remoting/protocol/video_feedback_stub.h +remoting/protocol/video_stream.h +sandbox/linux/system_headers/capability.h +skia/ext/convolver_mips_dspr2.h +skia/ext/skia_commit_hash.h +third_party/hunspell/src/hunspell/hunvisapi.h +third_party/khronos/EGL/egl.h +third_party/khronos/EGL/eglext.h +third_party/khronos/EGL/eglplatform.h +third_party/khronos/GLES2/gl2.h +third_party/khronos/GLES2/gl2ext.h +third_party/khronos/GLES2/gl2platform.h +third_party/khronos/GLES3/gl3.h +third_party/khronos/GLES3/gl3platform.h +third_party/khronos/KHR/khrplatform.h +third_party/leveldatabase/chromium_logger.h +third_party/libaddressinput/chromium/addressinput_util.h +third_party/libphonenumber/phonenumber_api.h +third_party/libudev/libudev0.h +third_party/libudev/libudev1.h +third_party/libvpx/source/config/linux/x64/vp8_rtcd.h +third_party/libvpx/source/config/linux/x64/vp9_rtcd.h +third_party/libvpx/source/config/linux/x64/vpx_config.h +third_party/libvpx/source/config/linux/x64/vpx_dsp_rtcd.h +third_party/libvpx/source/config/linux/x64/vpx_scale_rtcd.h +third_party/libvpx/source/config/nacl/vp8_rtcd.h +third_party/libvpx/source/config/nacl/vp9_rtcd.h +third_party/libvpx/source/config/nacl/vpx_config.h +third_party/libvpx/source/config/nacl/vpx_dsp_rtcd.h +third_party/libvpx/source/config/nacl/vpx_scale_rtcd.h +third_party/libvpx/source/config/vpx_version.h +third_party/opus/src/src/opus_private.h +third_party/opus/src/tests/test_opus_common.h +third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_names.h +third_party/qcms/src/halffloat.h +third_party/qcms/src/tests/qcms_test_util.h +third_party/qcms/src/tests/timing.h +third_party/snappy/linux/config.h +third_party/speech-dispatcher/libspeechd.h +third_party/sqlite/sqlite3.h +third_party/tcmalloc/chromium/src/addressmap-inl.h +third_party/tcmalloc/chromium/src/base/basictypes.h +third_party/tcmalloc/chromium/src/base/dynamic_annotations.h +third_party/tcmalloc/chromium/src/base/googleinit.h +third_party/tcmalloc/chromium/src/base/linux_syscall_support.h +third_party/tcmalloc/chromium/src/base/spinlock_linux-inl.h +third_party/tcmalloc/chromium/src/base/stl_allocator.h +third_party/tcmalloc/chromium/src/base/thread_annotations.h +third_party/tcmalloc/chromium/src/base/thread_lister.h +third_party/tcmalloc/chromium/src/gperftools/malloc_extension_c.h +third_party/tcmalloc/chromium/src/gperftools/malloc_hook_c.h +third_party/tcmalloc/chromium/src/gperftools/tcmalloc.h +third_party/tcmalloc/chromium/src/heap-profile-stats.h +third_party/tcmalloc/chromium/src/libc_override.h +third_party/tcmalloc/chromium/src/malloc_hook_mmap_linux.h +third_party/tcmalloc/chromium/src/packed-cache-inl.h +third_party/tcmalloc/chromium/src/page_heap_allocator.h +third_party/tcmalloc/chromium/src/pagemap.h +third_party/tcmalloc/chromium/src/stacktrace_x86-inl.h +third_party/tcmalloc/chromium/src/system-alloc.h +third_party/tcmalloc/chromium/src/tcmalloc_guard.h +third_party/wayland/include/config.h +third_party/wayland/include/src/wayland-version.h +third_party/woff2/src/port.h +third_party/yasm/source/config/linux/config.h +third_party/yasm/source/config/linux/libyasm-stdint.h +third_party/zlib/contrib/minizip/crypt.h +tools/ipc_fuzzer/message_lib/all_message_null_macros.h +ui/base/clipboard/clipboard_test_template.h +ui/events/keycodes/keyboard_codes_posix.h +ui/gfx/overlay_transform.h +ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h +ui/gfx/swap_result.h +ui/gfx/sys_color_change_listener.h +ui/gl/GL/glextchromium.h +ui/gl/gl_bindings_api_autogen_egl.h +ui/gl/gl_bindings_api_autogen_gl.h +ui/gl/gl_bindings_api_autogen_glx.h +ui/gl/gpu_preference.h +ui/gl/gpu_switching_observer.h +ui/gl/progress_reporter.h +ui/ozone/public/ozone_switches.h diff --git a/third_party/libwebrtc/build/check_return_value.py b/third_party/libwebrtc/build/check_return_value.py new file mode 100755 index 0000000000..9caa15f112 --- /dev/null +++ b/third_party/libwebrtc/build/check_return_value.py @@ -0,0 +1,19 @@ +#!/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.""" + +from __future__ import print_function + +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/third_party/libwebrtc/build/chromeos/.style.yapf b/third_party/libwebrtc/build/chromeos/.style.yapf new file mode 100644 index 0000000000..de0c6a70f3 --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/.style.yapf @@ -0,0 +1,2 @@ +[style] +based_on_style = chromium diff --git a/third_party/libwebrtc/build/chromeos/OWNERS b/third_party/libwebrtc/build/chromeos/OWNERS new file mode 100644 index 0000000000..e1058c853e --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/OWNERS @@ -0,0 +1 @@ +bpastene@chromium.org diff --git a/third_party/libwebrtc/build/chromeos/PRESUBMIT.py b/third_party/libwebrtc/build/chromeos/PRESUBMIT.py new file mode 100644 index 0000000000..0e829278fb --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/PRESUBMIT.py @@ -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. +"""Presubmit script for build/chromeos/. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for +details on the presubmit API built into depot_tools. +""" + + +USE_PYTHON3 = True + + +def CommonChecks(input_api, output_api): + results = [] + results += input_api.canned_checks.RunPylint( + input_api, output_api, pylintrc='pylintrc', version='2.6') + tests = input_api.canned_checks.GetUnitTestsInDirectory( + input_api, + output_api, + '.', [r'^.+_test\.py$'], + run_on_python2=False, + run_on_python3=True, + skip_shebang_check=True) + results += input_api.RunTests(tests) + return results + + +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/third_party/libwebrtc/build/chromeos/gen_skylab_runner.py b/third_party/libwebrtc/build/chromeos/gen_skylab_runner.py new file mode 100755 index 0000000000..3a5e0c0445 --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/gen_skylab_runner.py @@ -0,0 +1,108 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2021 The Chromium 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 sys + + +class SkylabClientTestTest: + + # The basic shell script for client test run in Skylab. The arguments listed + # here will be fed by autotest at the run time. + # + # * test-launcher-summary-output: the path for the json result. It will be + # assigned by autotest, who will upload it to GCS upon test completion. + # * test-launcher-shard-index: the index for this test run. + # * test-launcher-total-shards: the total test shards. + # * test_args: arbitrary runtime arguments configured in test_suites.pyl, + # attached after '--'. + BASIC_SHELL_SCRIPT = """ +#!/bin/sh + +while [[ $# -gt 0 ]]; do + case "$1" in + --test-launcher-summary-output) + summary_output=$2 + shift 2 + ;; + + --test-launcher-shard-index) + shard_index=$2 + shift 2 + ;; + + --test-launcher-total-shards) + total_shards=$2 + shift 2 + ;; + + --) + test_args=$2 + break + ;; + + *) + break + ;; + esac +done + +if [ ! -d $(dirname $summary_output) ] ; then + mkdir -p $(dirname $summary_output) +fi + +cd `dirname $0` && cd .. + """ + + def __init__(self, args): + self.test_exe = args.test_exe + self.output = args.output + + @property + def suite_name(self): + return self.test_exe + + def build_test_script(self): + # Build the shell script that will be used on the device to invoke the test. + # Stored here as a list of lines. + device_test_script_contents = self.BASIC_SHELL_SCRIPT.split('\n') + + test_invocation = ('LD_LIBRARY_PATH=./ ./%s ' + ' --test-launcher-summary-output=$summary_output' + ' --test-launcher-shard-index=$shard_index' + ' --test-launcher-total-shards=$total_shards' + ' $test_args' % self.test_exe) + + device_test_script_contents.append(test_invocation) + with open(self.output, 'w') as w: + w.write('\n'.join(device_test_script_contents) + '\n') + os.chmod(self.output, 0o755) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--test-exe', + type=str, + required=True, + help='Path to test executable to run inside the device.') + parser.add_argument('--verbose', '-v', action='store_true') + parser.add_argument( + '--output', + required=True, + type=str, + help='Path to create the runner script.') + + args = parser.parse_args() + + test = SkylabClientTestTest(args) + test.build_test_script() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/chromeos/pylintrc b/third_party/libwebrtc/build/chromeos/pylintrc new file mode 100644 index 0000000000..2a721bf270 --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/pylintrc @@ -0,0 +1,15 @@ +[FORMAT] + +max-line-length=80 + +[MESSAGES CONTROL] + +disable=abstract-class-not-used,bad-continuation,bad-indentation,duplicate-code,fixme,invalid-name,locally-disabled,locally-enabled,missing-docstring,star-args,too-few-public-methods,too-many-arguments,too-many-branches,too-many-instance-attributes,too-many-lines,too-many-locals,too-many-public-methods,too-many-statements,wrong-import-position + +[REPORTS] + +reports=no + +[VARIABLES] + +dummy-variables-rgx=^_.*$|dummy diff --git a/third_party/libwebrtc/build/chromeos/test_runner.py b/third_party/libwebrtc/build/chromeos/test_runner.py new file mode 100755 index 0000000000..cc8a5bd198 --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/test_runner.py @@ -0,0 +1,1044 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2018 The Chromium 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 collections +import json +import logging +import os +import pipes +import re +import shutil +import signal +import socket +import sys +import tempfile +import six + +# The following non-std imports are fetched via vpython. See the list at +# //.vpython +import dateutil.parser # pylint: disable=import-error +import jsonlines # pylint: disable=import-error +import psutil # pylint: disable=import-error + +CHROMIUM_SRC_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) + +# Use the android test-runner's gtest results support library for generating +# output json ourselves. +sys.path.insert(0, os.path.join(CHROMIUM_SRC_PATH, 'build', 'android')) +from pylib.base import base_test_result # pylint: disable=import-error +from pylib.results import json_results # pylint: disable=import-error + +sys.path.insert(0, os.path.join(CHROMIUM_SRC_PATH, 'build', 'util')) +from lib.results import result_sink # pylint: disable=import-error + +if six.PY2: + import subprocess32 as subprocess # pylint: disable=import-error +else: + import subprocess # pylint: disable=import-error,wrong-import-order + +DEFAULT_CROS_CACHE = os.path.abspath( + os.path.join(CHROMIUM_SRC_PATH, 'build', 'cros_cache')) +CHROMITE_PATH = os.path.abspath( + os.path.join(CHROMIUM_SRC_PATH, 'third_party', 'chromite')) +CROS_RUN_TEST_PATH = os.path.abspath( + os.path.join(CHROMITE_PATH, 'bin', 'cros_run_test')) + +LACROS_LAUNCHER_SCRIPT_PATH = os.path.abspath( + os.path.join(CHROMIUM_SRC_PATH, 'build', 'lacros', + 'mojo_connection_lacros_launcher.py')) + +# This is a special hostname that resolves to a different DUT in the lab +# depending on which lab machine you're on. +LAB_DUT_HOSTNAME = 'variable_chromeos_device_hostname' + +SYSTEM_LOG_LOCATIONS = [ + '/home/chronos/crash/', + '/var/log/chrome/', + '/var/log/messages', + '/var/log/ui/', +] + +TAST_DEBUG_DOC = 'https://bit.ly/2LgvIXz' + + +class TestFormatError(Exception): + pass + + +class RemoteTest: + + # This is a basic shell script that can be appended to in order to invoke the + # test on the device. + BASIC_SHELL_SCRIPT = [ + '#!/bin/sh', + + # /home and /tmp are mounted with "noexec" in the device, but some of our + # tools and tests use those dirs as a workspace (eg: vpython downloads + # python binaries to ~/.vpython-root and /tmp/vpython_bootstrap). + # /usr/local/tmp doesn't have this restriction, so change the location of + # the home and temp dirs for the duration of the test. + 'export HOME=/usr/local/tmp', + 'export TMPDIR=/usr/local/tmp', + ] + + def __init__(self, args, unknown_args): + self._additional_args = unknown_args + self._path_to_outdir = args.path_to_outdir + self._test_launcher_summary_output = args.test_launcher_summary_output + self._logs_dir = args.logs_dir + self._use_vm = args.use_vm + self._rdb_client = result_sink.TryInitClient() + + self._retries = 0 + self._timeout = None + self._test_launcher_shard_index = args.test_launcher_shard_index + self._test_launcher_total_shards = args.test_launcher_total_shards + + # The location on disk of a shell script that can be optionally used to + # invoke the test on the device. If it's not set, we assume self._test_cmd + # contains the test invocation. + self._on_device_script = None + + self._test_cmd = [ + CROS_RUN_TEST_PATH, + '--board', + args.board, + '--cache-dir', + args.cros_cache, + ] + if args.use_vm: + self._test_cmd += [ + '--start', + # Don't persist any filesystem changes after the VM shutsdown. + '--copy-on-write', + ] + else: + self._test_cmd += [ + '--device', args.device if args.device else LAB_DUT_HOSTNAME + ] + if args.logs_dir: + for log in SYSTEM_LOG_LOCATIONS: + self._test_cmd += ['--results-src', log] + self._test_cmd += [ + '--results-dest-dir', + os.path.join(args.logs_dir, 'system_logs') + ] + if args.flash: + self._test_cmd += ['--flash'] + if args.public_image: + self._test_cmd += ['--public-image'] + + # This environment variable is set for tests that have been instrumented + # for code coverage. Its incoming value is expected to be a location + # inside a subdirectory of result_dir above. This is converted to an + # absolute path that the vm is able to write to, and passed in the + # --results-src flag to cros_run_vm_test for copying out of the vm before + # its termination. + self._llvm_profile_var = None + if os.environ.get('LLVM_PROFILE_FILE'): + _, llvm_profile_file = os.path.split(os.environ['LLVM_PROFILE_FILE']) + self._llvm_profile_var = '/tmp/profraw/%s' % llvm_profile_file + + # This should make the vm test runner exfil the profiling data. + self._test_cmd += ['--results-src', '/tmp/profraw'] + + self._test_env = setup_env() + + @property + def suite_name(self): + raise NotImplementedError('Child classes need to define suite name.') + + @property + def test_cmd(self): + return self._test_cmd + + def write_test_script_to_disk(self, script_contents): + # Since we're using an on_device_script to invoke the test, we'll need to + # set cwd. + self._test_cmd += [ + '--remote-cmd', + '--cwd', + os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH), + ] + logging.info('Running the following command on the device:') + logging.info('\n%s', '\n'.join(script_contents)) + fd, tmp_path = tempfile.mkstemp(suffix='.sh', dir=self._path_to_outdir) + os.fchmod(fd, 0o755) + with os.fdopen(fd, 'w') as f: + f.write('\n'.join(script_contents) + '\n') + return tmp_path + + def run_test(self): + # Traps SIGTERM and kills all child processes of cros_run_test when it's + # caught. This will allow us to capture logs from the device if a test hangs + # and gets timeout-killed by swarming. See also: + # https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance + test_proc = None + + def _kill_child_procs(trapped_signal, _): + logging.warning('Received signal %d. Killing child processes of test.', + trapped_signal) + if not test_proc or not test_proc.pid: + # This shouldn't happen? + logging.error('Test process not running.') + return + for child in psutil.Process(test_proc.pid).children(): + logging.warning('Killing process %s', child) + child.kill() + + signal.signal(signal.SIGTERM, _kill_child_procs) + + for i in range(self._retries + 1): + logging.info('########################################') + logging.info('Test attempt #%d', i) + logging.info('########################################') + test_proc = subprocess.Popen( + self._test_cmd, + stdout=sys.stdout, + stderr=sys.stderr, + env=self._test_env) + try: + test_proc.wait(timeout=self._timeout) + except subprocess.TimeoutExpired: # pylint: disable=no-member + logging.error('Test timed out. Sending SIGTERM.') + # SIGTERM the proc and wait 10s for it to close. + test_proc.terminate() + try: + test_proc.wait(timeout=10) + except subprocess.TimeoutExpired: # pylint: disable=no-member + # If it hasn't closed in 10s, SIGKILL it. + logging.error('Test did not exit in time. Sending SIGKILL.') + test_proc.kill() + test_proc.wait() + logging.info('Test exitted with %d.', test_proc.returncode) + if test_proc.returncode == 0: + break + + self.post_run(test_proc.returncode) + # Allow post_run to override test proc return code. (Useful when the host + # side Tast bin returns 0 even for failed tests.) + return test_proc.returncode + + def post_run(self, return_code): + if self._on_device_script: + os.remove(self._on_device_script) + # Create a simple json results file for a test run. The results will contain + # only one test (suite_name), and will either be a PASS or FAIL depending on + # return_code. + if self._test_launcher_summary_output: + result = ( + base_test_result.ResultType.FAIL + if return_code else base_test_result.ResultType.PASS) + suite_result = base_test_result.BaseTestResult(self.suite_name, result) + run_results = base_test_result.TestRunResults() + run_results.AddResult(suite_result) + with open(self._test_launcher_summary_output, 'w') as f: + json.dump(json_results.GenerateResultsDict([run_results]), f) + if self._rdb_client: + self._rdb_client.Post(self.suite_name, result, None, None, None) + + @staticmethod + def get_artifacts(path): + """Crawls a given directory for file artifacts to attach to a test. + + Args: + path: Path to a directory to search for artifacts. + Returns: + A dict mapping name of the artifact to its absolute filepath. + """ + artifacts = {} + for dirpath, _, filenames in os.walk(path): + for f in filenames: + artifact_path = os.path.join(dirpath, f) + artifacts[os.path.relpath(artifact_path, path)] = { + 'filePath': artifact_path, + } + return artifacts + + +class TastTest(RemoteTest): + + def __init__(self, args, unknown_args): + super().__init__(args, unknown_args) + + self._suite_name = args.suite_name + self._tast_vars = args.tast_vars + self._tests = args.tests + # The CQ passes in '--gtest_filter' when specifying tests to skip. Store it + # here and parse it later to integrate it into Tast executions. + self._gtest_style_filter = args.gtest_filter + self._attr_expr = args.attr_expr + self._should_strip = args.strip_chrome + self._deploy_lacros = args.deploy_lacros + + if self._deploy_lacros and self._should_strip: + raise TestFormatError( + '--strip-chrome is only applicable to ash-chrome because ' + 'lacros-chrome deployment uses --nostrip by default, so it cannot ' + 'be specificed with --deploy-lacros.') + + if not self._llvm_profile_var and not self._logs_dir: + # The host-side Tast bin returns 0 when tests fail, so we need to capture + # and parse its json results to reliably determine if tests fail. + raise TestFormatError( + 'When using the host-side Tast bin, "--logs-dir" must be passed in ' + 'order to parse its results.') + + # If the first test filter is negative, it should be safe to assume all of + # them are, so just test the first filter. + if self._gtest_style_filter and self._gtest_style_filter[0] == '-': + raise TestFormatError('Negative test filters not supported for Tast.') + + @property + def suite_name(self): + return self._suite_name + + def build_test_command(self): + unsupported_args = [ + '--test-launcher-retry-limit', + '--test-launcher-batch-limit', + '--gtest_repeat', + ] + for unsupported_arg in unsupported_args: + if any(arg.startswith(unsupported_arg) for arg in self._additional_args): + logging.info( + '%s not supported for Tast tests. The arg will be ignored.', + unsupported_arg) + self._additional_args = [ + arg for arg in self._additional_args + if not arg.startswith(unsupported_arg) + ] + + # Lacros deployment mounts itself by default. + self._test_cmd.extend([ + '--deploy-lacros', '--lacros-launcher-script', + LACROS_LAUNCHER_SCRIPT_PATH + ] if self._deploy_lacros else ['--deploy', '--mount']) + self._test_cmd += [ + '--build-dir', + os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH) + ] + self._additional_args + + # Coverage tests require some special pre-test setup, so use an + # on_device_script in that case. For all other tests, use cros_run_test's + # built-in '--tast' option. This gives us much better results reporting. + if self._llvm_profile_var: + # Build the shell script that will be used on the device to invoke the + # test. + device_test_script_contents = self.BASIC_SHELL_SCRIPT[:] + device_test_script_contents += [ + 'echo "LLVM_PROFILE_FILE=%s" >> /etc/chrome_dev.conf' % + (self._llvm_profile_var) + ] + + local_test_runner_cmd = ['local_test_runner', '-waituntilready'] + if self._use_vm: + # If we're running tests in VMs, tell the test runner to skip tests that + # aren't compatible. + local_test_runner_cmd.append('-extrauseflags=tast_vm') + if self._attr_expr: + local_test_runner_cmd.append(pipes.quote(self._attr_expr)) + else: + local_test_runner_cmd.extend(self._tests) + device_test_script_contents.append(' '.join(local_test_runner_cmd)) + + self._on_device_script = self.write_test_script_to_disk( + device_test_script_contents) + + self._test_cmd += [ + '--files', + os.path.relpath(self._on_device_script), '--', + './' + os.path.relpath(self._on_device_script, self._path_to_outdir) + ] + else: + # Capture tast's results in the logs dir as well. + if self._logs_dir: + self._test_cmd += [ + '--results-dir', + self._logs_dir, + ] + self._test_cmd += [ + '--tast-total-shards=%d' % self._test_launcher_total_shards, + '--tast-shard-index=%d' % self._test_launcher_shard_index, + ] + # If we're using a test filter, replace the contents of the Tast + # conditional with a long list of "name:test" expressions, one for each + # test in the filter. + if self._gtest_style_filter: + if self._attr_expr or self._tests: + logging.warning( + 'Presence of --gtest_filter will cause the specified Tast expr' + ' or test list to be ignored.') + names = [] + for test in self._gtest_style_filter.split(':'): + names.append('"name:%s"' % test) + self._attr_expr = '(' + ' || '.join(names) + ')' + + if self._attr_expr: + # Don't use pipes.quote() here. Something funky happens with the arg + # as it gets passed down from cros_run_test to tast. (Tast picks up the + # escaping single quotes and complains that the attribute expression + # "must be within parentheses".) + self._test_cmd.append('--tast=%s' % self._attr_expr) + else: + self._test_cmd.append('--tast') + self._test_cmd.extend(self._tests) + + for v in self._tast_vars or []: + self._test_cmd.extend(['--tast-var', v]) + + # Mounting ash-chrome gives it enough disk space to not need stripping, + # but only for one not instrumented with code coverage. + # Lacros uses --nostrip by default, so there is no need to specify. + if not self._deploy_lacros and not self._should_strip: + self._test_cmd.append('--nostrip') + + def post_run(self, return_code): + # If we don't need to parse the host-side Tast tool's results, fall back to + # the parent method's default behavior. + if self._llvm_profile_var: + return super().post_run(return_code) + + tast_results_path = os.path.join(self._logs_dir, 'streamed_results.jsonl') + if not os.path.exists(tast_results_path): + logging.error( + 'Tast results not found at %s. Falling back to generic result ' + 'reporting.', tast_results_path) + return super().post_run(return_code) + + # See the link below for the format of the results: + # https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast.git/src/chromiumos/cmd/tast/run#TestResult + with jsonlines.open(tast_results_path) as reader: + tast_results = collections.deque(reader) + + suite_results = base_test_result.TestRunResults() + for test in tast_results: + errors = test['errors'] + start, end = test['start'], test['end'] + # Use dateutil to parse the timestamps since datetime can't handle + # nanosecond precision. + duration = dateutil.parser.parse(end) - dateutil.parser.parse(start) + # If the duration is negative, Tast has likely reported an incorrect + # duration. See https://issuetracker.google.com/issues/187973541. Round + # up to 0 in that case to avoid confusing RDB. + duration_ms = max(duration.total_seconds() * 1000, 0) + if bool(test['skipReason']): + result = base_test_result.ResultType.SKIP + elif errors: + result = base_test_result.ResultType.FAIL + else: + result = base_test_result.ResultType.PASS + primary_error_message = None + error_log = '' + if errors: + # See the link below for the format of these errors: + # https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast/src/chromiumos/tast/cmd/tast/internal/run/resultsjson/resultsjson.go + primary_error_message = errors[0]['reason'] + for err in errors: + error_log += err['stack'] + '\n' + error_log += ( + "\nIf you're unsure why this test failed, consult the steps " + 'outlined in\n%s\n' % TAST_DEBUG_DOC) + base_result = base_test_result.BaseTestResult( + test['name'], result, duration=duration_ms, log=error_log) + suite_results.AddResult(base_result) + self._maybe_handle_perf_results(test['name']) + + if self._rdb_client: + # Walk the contents of the test's "outDir" and atttach any file found + # inside as an RDB 'artifact'. (This could include system logs, screen + # shots, etc.) + artifacts = self.get_artifacts(test['outDir']) + self._rdb_client.Post( + test['name'], + result, + duration_ms, + error_log, + None, + artifacts=artifacts, + failure_reason=primary_error_message) + + if self._rdb_client and self._logs_dir: + # Attach artifacts from the device that don't apply to a single test. + artifacts = self.get_artifacts( + os.path.join(self._logs_dir, 'system_logs')) + artifacts.update( + self.get_artifacts(os.path.join(self._logs_dir, 'crashes'))) + self._rdb_client.ReportInvocationLevelArtifacts(artifacts) + + if self._test_launcher_summary_output: + with open(self._test_launcher_summary_output, 'w') as f: + json.dump(json_results.GenerateResultsDict([suite_results]), f) + + if not suite_results.DidRunPass(): + return 1 + if return_code: + logging.warning( + 'No failed tests found, but exit code of %d was returned from ' + 'cros_run_test.', return_code) + return return_code + return 0 + + def _maybe_handle_perf_results(self, test_name): + """Prepares any perf results from |test_name| for process_perf_results. + + - process_perf_results looks for top level directories containing a + perf_results.json file and a test_results.json file. The directory names + are used as the benchmark names. + - If a perf_results.json or results-chart.json file exists in the + |test_name| results directory, a top level directory is created and the + perf results file is copied to perf_results.json. + - A trivial test_results.json file is also created to indicate that the test + succeeded (this function would not be called otherwise). + - When process_perf_results is run, it will find the expected files in the + named directory and upload the benchmark results. + """ + + perf_results = os.path.join(self._logs_dir, 'tests', test_name, + 'perf_results.json') + # TODO(stevenjb): Remove check for crosbolt results-chart.json file. + if not os.path.exists(perf_results): + perf_results = os.path.join(self._logs_dir, 'tests', test_name, + 'results-chart.json') + if os.path.exists(perf_results): + benchmark_dir = os.path.join(self._logs_dir, test_name) + if not os.path.isdir(benchmark_dir): + os.makedirs(benchmark_dir) + shutil.copyfile(perf_results, + os.path.join(benchmark_dir, 'perf_results.json')) + # process_perf_results.py expects a test_results.json file. + test_results = {'valid': True, 'failures': []} + with open(os.path.join(benchmark_dir, 'test_results.json'), 'w') as out: + json.dump(test_results, out) + + +class GTestTest(RemoteTest): + + # The following list corresponds to paths that should not be copied over to + # the device during tests. In other words, these files are only ever used on + # the host. + _FILE_IGNORELIST = [ + re.compile(r'.*build/android.*'), + re.compile(r'.*build/chromeos.*'), + re.compile(r'.*build/cros_cache.*'), + # The following matches anything under //testing/ that isn't under + # //testing/buildbot/filters/. + re.compile(r'.*testing/(?!buildbot/filters).*'), + re.compile(r'.*third_party/chromite.*'), + ] + + def __init__(self, args, unknown_args): + super().__init__(args, unknown_args) + + self._test_exe = args.test_exe + self._runtime_deps_path = args.runtime_deps_path + self._vpython_dir = args.vpython_dir + + self._on_device_script = None + self._env_vars = args.env_var + self._stop_ui = args.stop_ui + self._trace_dir = args.trace_dir + + @property + def suite_name(self): + return self._test_exe + + def build_test_command(self): + # To keep things easy for us, ensure both types of output locations are + # the same. + if self._test_launcher_summary_output and self._logs_dir: + json_out_dir = os.path.dirname(self._test_launcher_summary_output) or '.' + if os.path.abspath(json_out_dir) != os.path.abspath(self._logs_dir): + raise TestFormatError( + '--test-launcher-summary-output and --logs-dir must point to ' + 'the same directory.') + + if self._test_launcher_summary_output: + result_dir, result_file = os.path.split( + self._test_launcher_summary_output) + # If args.test_launcher_summary_output is a file in cwd, result_dir will + # be an empty string, so replace it with '.' when this is the case so + # cros_run_test can correctly handle it. + if not result_dir: + result_dir = '.' + device_result_file = '/tmp/%s' % result_file + self._test_cmd += [ + '--results-src', + device_result_file, + '--results-dest-dir', + result_dir, + ] + + if self._trace_dir and self._logs_dir: + trace_path = os.path.dirname(self._trace_dir) or '.' + if os.path.abspath(trace_path) != os.path.abspath(self._logs_dir): + raise TestFormatError( + '--trace-dir and --logs-dir must point to the same directory.') + + if self._trace_dir: + trace_path, trace_dirname = os.path.split(self._trace_dir) + device_trace_dir = '/tmp/%s' % trace_dirname + self._test_cmd += [ + '--results-src', + device_trace_dir, + '--results-dest-dir', + trace_path, + ] + + # Build the shell script that will be used on the device to invoke the test. + # Stored here as a list of lines. + device_test_script_contents = self.BASIC_SHELL_SCRIPT[:] + if self._llvm_profile_var: + device_test_script_contents += [ + 'export LLVM_PROFILE_FILE=%s' % self._llvm_profile_var, + ] + + for var_name, var_val in self._env_vars: + device_test_script_contents += ['export %s=%s' % (var_name, var_val)] + + if self._vpython_dir: + vpython_path = os.path.join(self._path_to_outdir, self._vpython_dir, + 'vpython3') + cpython_path = os.path.join(self._path_to_outdir, self._vpython_dir, + 'bin', 'python3') + if not os.path.exists(vpython_path) or not os.path.exists(cpython_path): + raise TestFormatError( + '--vpython-dir must point to a dir with both ' + 'infra/3pp/tools/cpython3 and infra/tools/luci/vpython installed.') + vpython_spec_path = os.path.relpath( + os.path.join(CHROMIUM_SRC_PATH, '.vpython3'), self._path_to_outdir) + # Initialize the vpython cache. This can take 10-20s, and some tests + # can't afford to wait that long on the first invocation. + device_test_script_contents.extend([ + 'export PATH=$PWD/%s:$PWD/%s/bin/:$PATH' % + (self._vpython_dir, self._vpython_dir), + 'vpython3 -vpython-spec %s -vpython-tool install' % + (vpython_spec_path), + ]) + + test_invocation = ('LD_LIBRARY_PATH=./ ./%s --test-launcher-shard-index=%d ' + '--test-launcher-total-shards=%d' % + (self._test_exe, self._test_launcher_shard_index, + self._test_launcher_total_shards)) + if self._test_launcher_summary_output: + test_invocation += ' --test-launcher-summary-output=%s' % ( + device_result_file) + + if self._trace_dir: + device_test_script_contents.extend([ + 'rm -rf %s' % device_trace_dir, + 'su chronos -c -- "mkdir -p %s"' % device_trace_dir, + ]) + test_invocation += ' --trace-dir=%s' % device_trace_dir + + if self._additional_args: + test_invocation += ' %s' % ' '.join(self._additional_args) + + if self._stop_ui: + device_test_script_contents += [ + 'stop ui', + ] + # The UI service on the device owns the chronos user session, so shutting + # it down as chronos kills the entire execution of the test. So we'll have + # to run as root up until the test invocation. + test_invocation = 'su chronos -c -- "%s"' % test_invocation + # And we'll need to chown everything since cros_run_test's "--as-chronos" + # option normally does that for us. + device_test_script_contents.append('chown -R chronos: ../..') + else: + self._test_cmd += [ + # Some tests fail as root, so run as the less privileged user + # 'chronos'. + '--as-chronos', + ] + + device_test_script_contents.append(test_invocation) + + self._on_device_script = self.write_test_script_to_disk( + device_test_script_contents) + + runtime_files = [os.path.relpath(self._on_device_script)] + runtime_files += self._read_runtime_files() + if self._vpython_dir: + # --vpython-dir is relative to the out dir, but --files expects paths + # relative to src dir, so fix the path up a bit. + runtime_files.append( + os.path.relpath( + os.path.abspath( + os.path.join(self._path_to_outdir, self._vpython_dir)), + CHROMIUM_SRC_PATH)) + # TODO(bpastene): Add the vpython spec to the test's runtime deps instead + # of handling it here. + runtime_files.append('.vpython') + + for f in runtime_files: + self._test_cmd.extend(['--files', f]) + + self._test_cmd += [ + '--', + './' + os.path.relpath(self._on_device_script, self._path_to_outdir) + ] + + def _read_runtime_files(self): + if not self._runtime_deps_path: + return [] + + abs_runtime_deps_path = os.path.abspath( + os.path.join(self._path_to_outdir, self._runtime_deps_path)) + with open(abs_runtime_deps_path) as runtime_deps_file: + files = [l.strip() for l in runtime_deps_file if l] + rel_file_paths = [] + for f in files: + rel_file_path = os.path.relpath( + os.path.abspath(os.path.join(self._path_to_outdir, f))) + if not any(regex.match(rel_file_path) for regex in self._FILE_IGNORELIST): + rel_file_paths.append(rel_file_path) + return rel_file_paths + + def post_run(self, _): + if self._on_device_script: + os.remove(self._on_device_script) + + if self._test_launcher_summary_output and self._rdb_client: + if not os.path.exists(self._test_launcher_summary_output): + logging.error('Unable to locate %s in order to upload results to RDB.', + self._test_launcher_summary_output) + return + with open(self._test_launcher_summary_output) as f: + raw_results = json.load(f) + parsed_results = json_results.ParseResultsFromJson(raw_results) + for r in parsed_results: + self._rdb_client.Post( + r.GetName(), + r.GetType(), + r.GetDuration(), + r.GetLog(), + None, + failure_reason=r.GetFailureReason()) + + +def device_test(args, unknown_args): + # cros_run_test has trouble with relative paths that go up directories, + # so cd to src/, which should be the root of all data deps. + os.chdir(CHROMIUM_SRC_PATH) + + # TODO: Remove the above when depot_tool's pylint is updated to include the + # fix to https://github.com/PyCQA/pylint/issues/710. + if args.test_type == 'tast': + test = TastTest(args, unknown_args) + else: + test = GTestTest(args, unknown_args) + + test.build_test_command() + logging.info('Running the following command on the device:') + logging.info(' '.join(test.test_cmd)) + + return test.run_test() + + +def host_cmd(args, cmd_args): + if not cmd_args: + raise TestFormatError('Must specify command to run on the host.') + if args.deploy_chrome and not args.path_to_outdir: + raise TestFormatError( + '--path-to-outdir must be specified if --deploy-chrome is passed.') + + cros_run_test_cmd = [ + CROS_RUN_TEST_PATH, + '--board', + args.board, + '--cache-dir', + os.path.join(CHROMIUM_SRC_PATH, args.cros_cache), + ] + if args.use_vm: + cros_run_test_cmd += [ + '--start', + # Don't persist any filesystem changes after the VM shutsdown. + '--copy-on-write', + ] + else: + cros_run_test_cmd += [ + '--device', args.device if args.device else LAB_DUT_HOSTNAME + ] + if args.verbose: + cros_run_test_cmd.append('--debug') + if args.flash: + cros_run_test_cmd.append('--flash') + if args.public_image: + cros_run_test_cmd += ['--public-image'] + + if args.logs_dir: + for log in SYSTEM_LOG_LOCATIONS: + cros_run_test_cmd += ['--results-src', log] + cros_run_test_cmd += [ + '--results-dest-dir', + os.path.join(args.logs_dir, 'system_logs') + ] + + test_env = setup_env() + if args.deploy_chrome or args.deploy_lacros: + # Mounting ash-chrome gives it enough disk space to not need stripping. + cros_run_test_cmd.extend([ + '--deploy-lacros', '--lacros-launcher-script', + LACROS_LAUNCHER_SCRIPT_PATH + ] if args.deploy_lacros else ['--deploy', '--mount', '--nostrip']) + + cros_run_test_cmd += [ + '--build-dir', + os.path.join(CHROMIUM_SRC_PATH, args.path_to_outdir) + ] + + cros_run_test_cmd += [ + '--host-cmd', + '--', + ] + cmd_args + + logging.info('Running the following command:') + logging.info(' '.join(cros_run_test_cmd)) + + return subprocess.call( + cros_run_test_cmd, stdout=sys.stdout, stderr=sys.stderr, env=test_env) + + +def setup_env(): + """Returns a copy of the current env with some needed vars added.""" + env = os.environ.copy() + # Some chromite scripts expect chromite/bin to be on PATH. + env['PATH'] = env['PATH'] + ':' + os.path.join(CHROMITE_PATH, 'bin') + # deploy_chrome needs a set of GN args used to build chrome to determine if + # certain libraries need to be pushed to the device. It looks for the args via + # an env var. To trigger the default deploying behavior, give it a dummy set + # of args. + # TODO(crbug.com/823996): Make the GN-dependent deps controllable via cmd + # line args. + if not env.get('GN_ARGS'): + env['GN_ARGS'] = 'enable_nacl = true' + if not env.get('USE'): + env['USE'] = 'highdpi' + return env + + +def add_common_args(*parsers): + for parser in parsers: + parser.add_argument('--verbose', '-v', action='store_true') + parser.add_argument( + '--board', type=str, required=True, help='Type of CrOS device.') + parser.add_argument( + '--cros-cache', + type=str, + default=DEFAULT_CROS_CACHE, + help='Path to cros cache.') + parser.add_argument( + '--path-to-outdir', + type=str, + required=True, + help='Path to output directory, all of whose contents will be ' + 'deployed to the device.') + parser.add_argument( + '--runtime-deps-path', + type=str, + help='Runtime data dependency file from GN.') + parser.add_argument( + '--vpython-dir', + type=str, + help='Location on host of a directory containing a vpython binary to ' + 'deploy to the device before the test starts. The location of ' + 'this dir will be added onto PATH in the device. WARNING: The ' + 'arch of the device might not match the arch of the host, so ' + 'avoid using "${platform}" when downloading vpython via CIPD.') + parser.add_argument( + '--logs-dir', + type=str, + dest='logs_dir', + help='Will copy everything under /var/log/ from the device after the ' + 'test into the specified dir.') + # Shard args are parsed here since we might also specify them via env vars. + parser.add_argument( + '--test-launcher-shard-index', + type=int, + default=os.environ.get('GTEST_SHARD_INDEX', 0), + help='Index of the external shard to run.') + parser.add_argument( + '--test-launcher-total-shards', + type=int, + default=os.environ.get('GTEST_TOTAL_SHARDS', 1), + help='Total number of external shards.') + parser.add_argument( + '--flash', + action='store_true', + help='Will flash the device to the current SDK version before running ' + 'the test.') + parser.add_argument( + '--public-image', + action='store_true', + help='Will flash a public "full" image to the device.') + + vm_or_device_group = parser.add_mutually_exclusive_group() + vm_or_device_group.add_argument( + '--use-vm', + action='store_true', + help='Will run the test in the VM instead of a device.') + vm_or_device_group.add_argument( + '--device', + type=str, + help='Hostname (or IP) of device to run the test on. This arg is not ' + 'required if --use-vm is set.') + + +def main(): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='test_type') + # Host-side test args. + host_cmd_parser = subparsers.add_parser( + 'host-cmd', + help='Runs a host-side test. Pass the host-side command to run after ' + '"--". If --use-vm is passed, hostname and port for the device ' + 'will be 127.0.0.1:9222.') + host_cmd_parser.set_defaults(func=host_cmd) + host_cmd_parser.add_argument( + '--deploy-chrome', + action='store_true', + help='Will deploy a locally built ash-chrome binary to the device before ' + 'running the host-cmd.') + host_cmd_parser.add_argument( + '--deploy-lacros', + action='store_true', + help='Deploy a lacros-chrome instead of ash-chrome.') + + gtest_parser = subparsers.add_parser( + 'gtest', help='Runs a device-side gtest.') + gtest_parser.set_defaults(func=device_test) + gtest_parser.add_argument( + '--test-exe', + type=str, + required=True, + help='Path to test executable to run inside the device.') + + # GTest args. Some are passed down to the test binary in the device. Others + # are parsed here since they might need tweaking or special handling. + gtest_parser.add_argument( + '--test-launcher-summary-output', + type=str, + help='When set, will pass the same option down to the test and retrieve ' + 'its result file at the specified location.') + gtest_parser.add_argument( + '--stop-ui', + action='store_true', + help='Will stop the UI service in the device before running the test.') + gtest_parser.add_argument( + '--trace-dir', + type=str, + help='When set, will pass down to the test to generate the trace and ' + 'retrieve the trace files to the specified location.') + gtest_parser.add_argument( + '--env-var', + nargs=2, + action='append', + default=[], + help='Env var to set on the device for the duration of the test. ' + 'Expected format is "--env-var SOME_VAR_NAME some_var_value". Specify ' + 'multiple times for more than one var.') + + # Tast test args. + # pylint: disable=line-too-long + tast_test_parser = subparsers.add_parser( + 'tast', + help='Runs a device-side set of Tast tests. For more details, see: ' + 'https://chromium.googlesource.com/chromiumos/platform/tast/+/main/docs/running_tests.md' + ) + tast_test_parser.set_defaults(func=device_test) + tast_test_parser.add_argument( + '--suite-name', + type=str, + required=True, + help='Name to apply to the set of Tast tests to run. This has no effect ' + 'on what is executed, but is used mainly for test results reporting ' + 'and tracking (eg: flakiness dashboard).') + tast_test_parser.add_argument( + '--test-launcher-summary-output', + type=str, + help='Generates a simple GTest-style JSON result file for the test run.') + tast_test_parser.add_argument( + '--attr-expr', + type=str, + help='A boolean expression whose matching tests will run ' + '(eg: ("dep:chrome")).') + tast_test_parser.add_argument( + '--strip-chrome', + action='store_true', + help='Strips symbols from ash-chrome before deploying to the device.') + tast_test_parser.add_argument( + '--deploy-lacros', + action='store_true', + help='Deploy a lacros-chrome instead of ash-chrome.') + tast_test_parser.add_argument( + '--tast-var', + action='append', + dest='tast_vars', + help='Runtime variables for Tast tests, and the format are expected to ' + 'be "key=value" pairs.') + tast_test_parser.add_argument( + '--test', + '-t', + action='append', + dest='tests', + help='A Tast test to run in the device (eg: "login.Chrome").') + tast_test_parser.add_argument( + '--gtest_filter', + type=str, + help="Similar to GTest's arg of the same name, this will filter out the " + "specified tests from the Tast run. However, due to the nature of Tast's " + 'cmd-line API, this will overwrite the value(s) of "--test" above.') + + add_common_args(gtest_parser, tast_test_parser, host_cmd_parser) + + args = sys.argv[1:] + unknown_args = [] + # If a '--' is present in the args, treat everything to the right of it as + # args to the test and everything to the left as args to this test runner. + # Otherwise treat all known args as args to this test runner and all unknown + # args as test args. + if '--' in args: + unknown_args = args[args.index('--') + 1:] + args = args[0:args.index('--')] + if unknown_args: + args = parser.parse_args(args=args) + else: + args, unknown_args = parser.parse_known_args() + + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARN) + + if not args.use_vm and not args.device: + logging.warning( + 'The test runner is now assuming running in the lab environment, if ' + 'this is unintentional, please re-invoke the test runner with the ' + '"--use-vm" arg if using a VM, otherwise use the "--device=" arg ' + 'to specify a DUT.') + + # If we're not running on a VM, but haven't specified a hostname, assume + # we're on a lab bot and are trying to run a test on a lab DUT. See if the + # magic lab DUT hostname resolves to anything. (It will in the lab and will + # not on dev machines.) + try: + socket.getaddrinfo(LAB_DUT_HOSTNAME, None) + except socket.gaierror: + logging.error('The default lab DUT hostname of %s is unreachable.', + LAB_DUT_HOSTNAME) + return 1 + + if args.flash and args.public_image: + # The flashing tools depend on being unauthenticated with GS when flashing + # public images, so make sure the env var GS uses to locate its creds is + # unset in that case. + os.environ.pop('BOTO_CONFIG', None) + + return args.func(args, unknown_args) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/chromeos/test_runner_test.py b/third_party/libwebrtc/build/chromeos/test_runner_test.py new file mode 100755 index 0000000000..0fa3a511e7 --- /dev/null +++ b/third_party/libwebrtc/build/chromeos/test_runner_test.py @@ -0,0 +1,352 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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 shutil +import sys +import tempfile +import unittest +import six + +# The following non-std imports are fetched via vpython. See the list at +# //.vpython +import mock # pylint: disable=import-error +from parameterized import parameterized # pylint: disable=import-error + +import test_runner + +_TAST_TEST_RESULTS_JSON = { + "name": "login.Chrome", + "errors": None, + "start": "2020-01-01T15:41:30.799228462-08:00", + "end": "2020-01-01T15:41:53.318914698-08:00", + "skipReason": "" +} + + +class TestRunnerTest(unittest.TestCase): + + def setUp(self): + self._tmp_dir = tempfile.mkdtemp() + self.mock_rdb = mock.patch.object( + test_runner.result_sink, 'TryInitClient', return_value=None) + self.mock_rdb.start() + + def tearDown(self): + shutil.rmtree(self._tmp_dir, ignore_errors=True) + self.mock_rdb.stop() + + def safeAssertItemsEqual(self, list1, list2): + """A Py3 safe version of assertItemsEqual. + + See https://bugs.python.org/issue17866. + """ + if six.PY3: + self.assertSetEqual(set(list1), set(list2)) + else: + self.assertCountEqual(list1, list2) + + +class TastTests(TestRunnerTest): + + def get_common_tast_args(self, use_vm): + return [ + 'script_name', + 'tast', + '--suite-name=chrome_all_tast_tests', + '--board=eve', + '--flash', + '--path-to-outdir=out_eve/Release', + '--logs-dir=%s' % self._tmp_dir, + '--use-vm' if use_vm else '--device=localhost:2222', + ] + + def get_common_tast_expectations(self, use_vm, is_lacros=False): + expectation = [ + test_runner.CROS_RUN_TEST_PATH, + '--board', + 'eve', + '--cache-dir', + test_runner.DEFAULT_CROS_CACHE, + '--results-dest-dir', + '%s/system_logs' % self._tmp_dir, + '--flash', + '--build-dir', + 'out_eve/Release', + '--results-dir', + self._tmp_dir, + '--tast-total-shards=1', + '--tast-shard-index=0', + ] + expectation.extend(['--start', '--copy-on-write'] + if use_vm else ['--device', 'localhost:2222']) + for p in test_runner.SYSTEM_LOG_LOCATIONS: + expectation.extend(['--results-src', p]) + + if not is_lacros: + expectation += [ + '--mount', + '--deploy', + '--nostrip', + ] + return expectation + + def test_tast_gtest_filter(self): + """Tests running tast tests with a gtest-style filter.""" + with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f: + json.dump(_TAST_TEST_RESULTS_JSON, f) + + args = self.get_common_tast_args(False) + [ + '--attr-expr=( "group:mainline" && "dep:chrome" && !informational)', + '--gtest_filter=login.Chrome:ui.WindowControl', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + + test_runner.main() + # The gtest filter should cause the Tast expr to be replaced with a list + # of the tests in the filter. + expected_cmd = self.get_common_tast_expectations(False) + [ + '--tast=("name:login.Chrome" || "name:ui.WindowControl")' + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + @parameterized.expand([ + [True], + [False], + ]) + def test_tast_attr_expr(self, use_vm): + """Tests running a tast tests specified by an attribute expression.""" + with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f: + json.dump(_TAST_TEST_RESULTS_JSON, f) + + args = self.get_common_tast_args(use_vm) + [ + '--attr-expr=( "group:mainline" && "dep:chrome" && !informational)', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + + test_runner.main() + expected_cmd = self.get_common_tast_expectations(use_vm) + [ + '--tast=( "group:mainline" && "dep:chrome" && !informational)', + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + @parameterized.expand([ + [True], + [False], + ]) + def test_tast_lacros(self, use_vm): + """Tests running a tast tests for Lacros.""" + with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f: + json.dump(_TAST_TEST_RESULTS_JSON, f) + + args = self.get_common_tast_args(use_vm) + [ + '-t=lacros.Basic', + '--deploy-lacros', + ] + + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + + test_runner.main() + expected_cmd = self.get_common_tast_expectations( + use_vm, is_lacros=True) + [ + '--tast', + 'lacros.Basic', + '--deploy-lacros', + '--lacros-launcher-script', + test_runner.LACROS_LAUNCHER_SCRIPT_PATH, + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + @parameterized.expand([ + [True], + [False], + ]) + def test_tast_with_vars(self, use_vm): + """Tests running a tast tests with runtime variables.""" + with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f: + json.dump(_TAST_TEST_RESULTS_JSON, f) + + args = self.get_common_tast_args(use_vm) + [ + '-t=login.Chrome', + '--tast-var=key=value', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + test_runner.main() + expected_cmd = self.get_common_tast_expectations(use_vm) + [ + '--tast', 'login.Chrome', '--tast-var', 'key=value' + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + @parameterized.expand([ + [True], + [False], + ]) + def test_tast(self, use_vm): + """Tests running a tast tests.""" + with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f: + json.dump(_TAST_TEST_RESULTS_JSON, f) + + args = self.get_common_tast_args(use_vm) + [ + '-t=login.Chrome', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + + test_runner.main() + expected_cmd = self.get_common_tast_expectations(use_vm) + [ + '--tast', 'login.Chrome' + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + +class GTestTest(TestRunnerTest): + + @parameterized.expand([ + [True], + [False], + ]) + def test_gtest(self, use_vm): + """Tests running a gtest.""" + fd_mock = mock.mock_open() + + args = [ + 'script_name', + 'gtest', + '--test-exe=out_eve/Release/base_unittests', + '--board=eve', + '--path-to-outdir=out_eve/Release', + '--use-vm' if use_vm else '--device=localhost:2222', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen,\ + mock.patch.object(os, 'fdopen', fd_mock),\ + mock.patch.object(os, 'remove') as mock_remove,\ + mock.patch.object(tempfile, 'mkstemp', + return_value=(3, 'out_eve/Release/device_script.sh')),\ + mock.patch.object(os, 'fchmod'): + mock_popen.return_value.returncode = 0 + + test_runner.main() + self.assertEqual(1, mock_popen.call_count) + expected_cmd = [ + test_runner.CROS_RUN_TEST_PATH, '--board', 'eve', '--cache-dir', + test_runner.DEFAULT_CROS_CACHE, '--as-chronos', '--remote-cmd', + '--cwd', 'out_eve/Release', '--files', + 'out_eve/Release/device_script.sh' + ] + expected_cmd.extend(['--start', '--copy-on-write'] + if use_vm else ['--device', 'localhost:2222']) + expected_cmd.extend(['--', './device_script.sh']) + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + fd_mock().write.assert_called_once_with( + '#!/bin/sh\nexport HOME=/usr/local/tmp\n' + 'export TMPDIR=/usr/local/tmp\n' + 'LD_LIBRARY_PATH=./ ./out_eve/Release/base_unittests ' + '--test-launcher-shard-index=0 --test-launcher-total-shards=1\n') + mock_remove.assert_called_once_with('out_eve/Release/device_script.sh') + + def test_gtest_with_vpython(self): + """Tests building a gtest with --vpython-dir.""" + args = mock.MagicMock() + args.test_exe = 'base_unittests' + args.test_launcher_summary_output = None + args.trace_dir = None + args.runtime_deps_path = None + args.path_to_outdir = self._tmp_dir + args.vpython_dir = self._tmp_dir + args.logs_dir = self._tmp_dir + + # With vpython_dir initially empty, the test_runner should error out + # due to missing vpython binaries. + gtest = test_runner.GTestTest(args, None) + with self.assertRaises(test_runner.TestFormatError): + gtest.build_test_command() + + # Create the two expected tools, and the test should be ready to run. + with open(os.path.join(args.vpython_dir, 'vpython3'), 'w'): + pass # Just touch the file. + os.mkdir(os.path.join(args.vpython_dir, 'bin')) + with open(os.path.join(args.vpython_dir, 'bin', 'python3'), 'w'): + pass + gtest = test_runner.GTestTest(args, None) + gtest.build_test_command() + + +class HostCmdTests(TestRunnerTest): + + @parameterized.expand([ + [True], + [False], + ]) + def test_host_cmd(self, is_lacros): + args = [ + 'script_name', + 'host-cmd', + '--board=eve', + '--flash', + '--path-to-outdir=out/Release', + '--device=localhost:2222', + ] + if is_lacros: + args += ['--deploy-lacros'] + else: + args += ['--deploy-chrome'] + args += [ + '--', + 'fake_cmd', + ] + with mock.patch.object(sys, 'argv', args),\ + mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen: + mock_popen.return_value.returncode = 0 + + test_runner.main() + expected_cmd = [ + test_runner.CROS_RUN_TEST_PATH, + '--board', + 'eve', + '--cache-dir', + test_runner.DEFAULT_CROS_CACHE, + '--flash', + '--device', + 'localhost:2222', + '--build-dir', + os.path.join(test_runner.CHROMIUM_SRC_PATH, 'out/Release'), + '--host-cmd', + ] + if is_lacros: + expected_cmd += [ + '--deploy-lacros', + '--lacros-launcher-script', + test_runner.LACROS_LAUNCHER_SCRIPT_PATH, + ] + else: + expected_cmd += ['--mount', '--nostrip', '--deploy'] + + expected_cmd += [ + '--', + 'fake_cmd', + ] + + self.safeAssertItemsEqual(expected_cmd, mock_popen.call_args[0][0]) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/ciopfs.sha1 b/third_party/libwebrtc/build/ciopfs.sha1 new file mode 100644 index 0000000000..c1855a347e --- /dev/null +++ b/third_party/libwebrtc/build/ciopfs.sha1 @@ -0,0 +1 @@ +5454b3c4f1c9992047e7ae9d6d14d5b49b1b12f3 \ No newline at end of file diff --git a/third_party/libwebrtc/build/cipd/cipd.gni b/third_party/libwebrtc/build/cipd/cipd.gni new file mode 100644 index 0000000000..e7795c1062 --- /dev/null +++ b/third_party/libwebrtc/build/cipd/cipd.gni @@ -0,0 +1,140 @@ +# Copyright 2020 The Chromium 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 targets for constructing CIPD packages. +# +# Prepares a CIPD archive and generates a manifest file. +# +# TODO(crbug.com/1042819): Add support for including directories. +# +# Parameters: +# package_definition_yaml: CIPD package definition filename. "cipd.yaml" +# if unspecified. +# package: The path where the package will be located inside the CIPD +# repository. +# description: Sets the "description" field in CIPD package definition. +# install_mode: String, should be either "symlink" or "copy". Defaults to +# "symlink". +# deps: A list of targets to build prior to copying files. +# sources: A list of files to copy into the staging root. +# source_directories: A list of directories to include in the package. Should +# only be used when listing out all the files (in a given +# directory) in |sources| is unfeasible. +# +# Example: +# cipd_package_definition("chromedriver") { +# package = "path/to/cipd/package" +# description = "Prebuilt test binary." +# install_mode = "copy" +# deps = [ "//path/to:test_binary_target" ] +# sources = [ "//path/to:test_binary_file" ] +# } +# +template("cipd_package_definition") { + forward_variables_from(invoker, + [ + "deps", + "data", + "source_directories", + "data_deps", + "sources", + "testonly", + ]) + + assert(defined(sources) || defined(source_directories), + "At least one sources input must be specified.") + + _install_mode = "symlink" + if (defined(invoker.install_mode)) { + _install_mode = invoker.install_mode + } + assert(_install_mode == "copy" || _install_mode == "symlink", + "\"install_mode\" arg should be either \"copy\" or \"symlink\".") + + _cipd_definition_yaml = "cipd.yaml" + if (defined(invoker.package_definition_yaml)) { + _cipd_definition_yaml = invoker.package_definition_yaml + } + + _package_staging_dir = "${target_gen_dir}/${target_name}" + + _yaml_contents = [ + "package: ${invoker.package}", + "description: ${invoker.description}", + "root: " + rebase_path(_package_staging_dir), + "install_mode: ${_install_mode}", + "data:", + ] + + if (defined(sources)) { + foreach(source, sources) { + _yaml_contents += [ " - file: " + get_path_info(source, "file") ] + } + copy(target_name) { + outputs = [ "${_package_staging_dir}/{{source_file_part}}" ] + } + } + + if (defined(source_directories)) { + foreach(directory, source_directories) { + _yaml_contents += [ " - dir: " + directory ] + } + } + + write_file("${_package_staging_dir}/${_cipd_definition_yaml}", _yaml_contents) +} + +# Create a cipd file based on inputs and FILES.cfg config. Most of the arguments +# are similar with |cipd_package_definition| above. +# +# Additional parameters: +# +# package_definition_yaml: The output yaml file. Default is +# ${target_name}_cipd.yaml. +# files_file: The file defines what files and directories to include. +# Example: //tools/build/chromeos/FILES.cfg. +# buildtype: str, required. It can be "dev" or "official". +# Only when the file has the same buildtype, it will be included. +# arch: str, required. It can be "32bit", "64bit", "arm". +# +# Example: +# cipd_package_definition_by_file("chrome_cipd") { +# package = "path/to/cipd/package" +# description = "Prebuilt test binary." +# install_mode = "copy" +# files_file = "//chrome/tools/build/chromeos/FILES.json" +# buildtype = "dev" +# arch = "64bit" +# deps = [ "//path/to:test_binary_target" ] +# } +template("cipd_package_definition_by_file") { + forward_variables_from(invoker, + [ + "deps", + "data", + "data_deps", + "sources", + "testonly", + ]) + _output_yaml_filename = "${target_name}_cipd.yaml" + if (defined(invoker.package_definition_yaml)) { + _output_yaml_filename = invoker.package_definition_yaml + } + action(target_name) { + script = "//build/cipd/cipd_from_file.py" + inputs = [ "//build/cipd/cipd_from_file.py" ] + args = [ + "--description=" + invoker.description, + "--buildtype=" + invoker.buildtype, + "--arch=" + invoker.arch, + "--files_file=" + rebase_path(invoker.files_file, root_build_dir), + "--package=" + invoker.package, + "--install_mode=" + invoker.install_mode, + "--output_yaml_file=" + + rebase_path("${root_out_dir}/" + _output_yaml_filename, + root_build_dir), + ] + outputs = [ "${root_out_dir}/" + _output_yaml_filename ] + } +} diff --git a/third_party/libwebrtc/build/cipd/cipd_from_file.py b/third_party/libwebrtc/build/cipd/cipd_from_file.py new file mode 100755 index 0000000000..0f08f692e3 --- /dev/null +++ b/third_party/libwebrtc/build/cipd/cipd_from_file.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 generate yaml file based on FILES.cfg.""" + +import argparse +import os + + +def _ParseFilesCfg(files_file): + """Return the dictionary of archive file info read from the given file.""" + if not os.path.exists(files_file): + raise IOError('Files list does not exist (%s).' % files_file) + exec_globals = {'__builtins__': None} + + exec(open(files_file).read(), exec_globals) + return exec_globals['FILES'] + + +def _Process(args): + yaml_content = ('package: ' + args.package + '\ndescription: ' + + args.description + '\ninstall_mode: ' + args.install_mode + + '\ndata:\n') + fileobj = _ParseFilesCfg(args.files_file) + for item in fileobj: + if 'buildtype' in item: + if args.buildtype not in item['buildtype']: + continue + if 'arch' in item: + if args.arch not in item['arch']: + continue + if 'type' in item and item['type'] == 'folder': + yaml_content += ' - dir: ' + item['filename'] + '\n' + else: + yaml_content += ' - file: ' + item['filename'] + '\n' + + with open(args.output_yaml_file, 'w') as f: + f.write(yaml_content) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--output_yaml_file', help='File to create.') + parser.add_argument( + '--package', + help='The path where the package will be located inside the CIPD\ + repository.') + parser.add_argument( + '--description', + help='Sets the "description" field in CIPD package definition.') + parser.add_argument('--install_mode', + help='String, should be either "symlink" or "copy".') + parser.add_argument('--files_file', + help='FILES.cfg describes what files to include.') + parser.add_argument('--buildtype', help='buildtype for FILES.cfg.') + parser.add_argument('--arch', help='arch for FILES.cfg') + + args = parser.parse_args() + + _Process(args) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/cipd/clobber_cipd_root.py b/third_party/libwebrtc/build/cipd/clobber_cipd_root.py new file mode 100755 index 0000000000..5d36c72239 --- /dev/null +++ b/third_party/libwebrtc/build/cipd/clobber_cipd_root.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Clobbers a CIPD root.""" + +import argparse +import os +import shutil +import sys + + +def main(): + parser = argparse.ArgumentParser( + description='Clobbers the CIPD root in the given directory.') + + parser.add_argument( + '--root', + required=True, + help='Root directory for dependency.') + args = parser.parse_args() + + cipd_root_dir = os.path.join(args.root, '.cipd') + if os.path.exists(cipd_root_dir): + shutil.rmtree(cipd_root_dir) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/clobber.py b/third_party/libwebrtc/build/clobber.py new file mode 100755 index 0000000000..1de321234b --- /dev/null +++ b/third_party/libwebrtc/build/clobber.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. + +"""This script provides methods for clobbering build directories.""" + +import argparse +import os +import shutil +import subprocess +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 third blank line. The first thing GN writes to the file + # is "ninja_required_version = x.y.z", then the "rule gn" and the third + # is the section for "build build.ninja", separated by blank lines. + num_blank_lines = 0 + while num_blank_lines < 3: + 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_dir(build_dir): + if os.path.islink(build_dir): + return + # For unknown reasons (anti-virus?) rmtree of Chromium build directories + # often fails on Windows. + if sys.platform.startswith('win'): + subprocess.check_call(['rmdir', '/s', '/q', build_dir], shell=True) + else: + shutil.rmtree(build_dir) + + +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): + delete_dir(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 = '' + + e = None + try: + # delete_dir and os.mkdir() may fail, such as when chrome.exe is running, + # and we still want to restore args.gn/build.ninja/build.ninja.d, so catch + # the exception and rethrow it later. + delete_dir(build_dir) + os.mkdir(build_dir) + except Exception as e: + pass + + # Put back the args file (if any). + 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('''ninja_required_version = 1.7.2 + +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') + + if e: + # Rethrow the exception we caught earlier. + raise e + +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/third_party/libwebrtc/build/compiled_action.gni b/third_party/libwebrtc/build/compiled_action.gni new file mode 100644 index 0000000000..7e25a0b6fc --- /dev/null +++ b/third_party/libwebrtc/build/compiled_action.gni @@ -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. + +# This file introduces two related templates that act like action and +# action_foreach but instead of running a Python script, it will compile a +# given tool in the host toolchain and run that (either once or over the list +# of inputs, depending on the variant). +# +# Parameters +# +# tool (required) +# [label] Label of the tool to run. This should be an executable, and +# this label should not include a toolchain (anything in parens). The +# host compile of this tool will be used. +# +# outputs (required) +# [list of files] Like the outputs of action (if using "compiled_action", +# this would be just the list of outputs), or action_foreach (if using +# "compiled_action_foreach", this would contain source expansions mapping +# input to output files). +# +# args (required) +# [list of strings] Same meaning as action/action_foreach. +# +# inputs (optional) +# Files the binary takes as input. The step will be re-run whenever any +# of these change. If inputs is empty, the step will run only when the +# binary itself changes. +# +# depfile +# deps +# visibility (all optional) +# Same meaning as action/action_foreach. +# +# +# Example of usage: +# +# compiled_action("run_my_tool") { +# tool = "//tools/something:mytool" +# outputs = [ +# "$target_gen_dir/mysource.cc", +# "$target_gen_dir/mysource.h", +# ] +# +# # The tool takes this input. +# inputs = [ "my_input_file.idl" ] +# +# # In this case, the tool takes as arguments the input file and the output +# # build dir (both relative to the "cd" that the script will be run in) +# # and will produce the output files listed above. +# args = [ +# rebase_path("my_input_file.idl", root_build_dir), +# "--output-dir", rebase_path(target_gen_dir, root_build_dir), +# ] +# } +# +# You would typically declare your tool like this: +# if (host_toolchain == current_toolchain) { +# executable("mytool") { +# ... +# } +# } +# The if statement around the executable is optional. That says "I only care +# about this target in the host toolchain". Usually this is what you want, and +# saves unnecessarily compiling your tool for the target platform. But if you +# need a target build of your tool as well, just leave off the if statement. + +if (host_os == "win") { + _host_executable_suffix = ".exe" +} else { + _host_executable_suffix = "" +} + +template("compiled_action") { + assert(defined(invoker.tool), "tool must be defined for $target_name") + assert(defined(invoker.outputs), "outputs must be defined for $target_name") + assert(defined(invoker.args), "args must be defined for $target_name") + + assert(!defined(invoker.sources), + "compiled_action doesn't take a sources arg. Use inputs instead.") + + action(target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "depfile", + "inputs", + "outputs", + "testonly", + "visibility", + ]) + if (!defined(deps)) { + deps = [] + } + if (!defined(inputs)) { + inputs = [] + } + + script = "//build/gn_run_binary.py" + + # Constuct the host toolchain version of the tool. + host_tool = invoker.tool + "($host_toolchain)" + + # Get the path to the executable. Currently, this assumes that the tool + # does not specify output_name so that the target name is the name to use. + # If that's not the case, we'll need another argument to the script to + # specify this, since we can't know what the output name is (it might be in + # another file not processed yet). + host_executable = + get_label_info(host_tool, "root_out_dir") + "/" + + get_label_info(host_tool, "name") + _host_executable_suffix + + deps += [ host_tool ] + + # The script takes as arguments the binary to run, and then the arguments + # to pass it. + args = [ rebase_path(host_executable, root_build_dir) ] + invoker.args + } +} + +template("compiled_action_foreach") { + assert(defined(invoker.sources), "sources must be defined for $target_name") + assert(defined(invoker.tool), "tool must be defined for $target_name") + assert(defined(invoker.outputs), "outputs must be defined for $target_name") + assert(defined(invoker.args), "args must be defined for $target_name") + + action_foreach(target_name) { + forward_variables_from(invoker, + [ + "deps", + "depfile", + "inputs", + "outputs", + "sources", + "testonly", + "visibility", + ]) + if (!defined(deps)) { + deps = [] + } + if (!defined(inputs)) { + inputs = [] + } + + script = "//build/gn_run_binary.py" + + # Constuct the host toolchain version of the tool. + host_tool = invoker.tool + "($host_toolchain)" + + # Get the path to the executable. Currently, this assumes that the tool + # does not specify output_name so that the target name is the name to use. + # If that's not the case, we'll need another argument to the script to + # specify this, since we can't know what the output name is (it might be in + # another file not processed yet). + host_executable = + get_label_info(host_tool, "root_out_dir") + "/" + + get_label_info(host_tool, "name") + _host_executable_suffix + + deps += [ host_tool ] + + # The script takes as arguments the binary to run, and then the arguments + # to pass it. + args = [ rebase_path(host_executable, root_build_dir) ] + invoker.args + } +} diff --git a/third_party/libwebrtc/build/compute_build_timestamp.py b/third_party/libwebrtc/build/compute_build_timestamp.py new file mode 100755 index 0000000000..ceb507b26c --- /dev/null +++ b/third_party/libwebrtc/build/compute_build_timestamp.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Returns a timestamp that approximates the build date. + +build_type impacts the timestamp generated, both relative to the date of the +last recent commit: +- default: the build date is set to the most recent first Sunday of a month at + 5:00am. The reason is that it is a time where invalidating the build cache + shouldn't have major repercussions (due to lower load). +- official: the build date is set to the time of the most recent commit. +Either way, it is guaranteed to be in the past and always in UTC. +""" + +# The requirements for the timestamp: +# (1) for the purposes of continuous integration, longer duration +# between cache invalidation is better, but >=1mo is preferable. +# (2) for security purposes, timebombs would ideally be as close to +# the actual time of the build as possible. It must be in the past. +# (3) HSTS certificate pinning is valid for 70 days. To make CI builds enforce +# HTST pinning, <=1mo is preferable. +# +# On Windows, the timestamp is also written in the PE/COFF file header of +# executables of dlls. That timestamp and the executable's file size are +# the only two pieces of information that identify a given executable on +# the symbol server, so rarely changing timestamps can cause conflicts there +# as well. We only upload symbols for official builds to the symbol server. + +from __future__ import print_function + +import argparse +import calendar +import datetime +import doctest +import os +import sys + + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) + + +def GetFirstSundayOfMonth(year, month): + """Returns the first sunday of the given month of the given year. + + >>> GetFirstSundayOfMonth(2016, 2) + 7 + >>> GetFirstSundayOfMonth(2016, 3) + 6 + >>> GetFirstSundayOfMonth(2000, 1) + 2 + """ + weeks = calendar.Calendar().monthdays2calendar(year, month) + # Return the first day in the first week that is a Sunday. + return [date_day[0] for date_day in weeks[0] if date_day[1] == 6][0] + + +def GetUnofficialBuildDate(build_date): + """Gets the approximate build date given the specific build type. + + >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 6, 1, 2, 3)) + datetime.datetime(2016, 1, 3, 5, 0) + >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 7, 5)) + datetime.datetime(2016, 2, 7, 5, 0) + >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 8, 5)) + datetime.datetime(2016, 2, 7, 5, 0) + """ + + if build_date.hour < 5: + # The time is locked at 5:00 am in UTC to cause the build cache + # invalidation to not happen exactly at midnight. Use the same calculation + # as the day before. + # See //base/build_time.cc. + build_date = build_date - datetime.timedelta(days=1) + build_date = datetime.datetime(build_date.year, build_date.month, + build_date.day, 5, 0, 0) + + day = build_date.day + month = build_date.month + year = build_date.year + first_sunday = GetFirstSundayOfMonth(year, month) + # If our build is after the first Sunday, we've already refreshed our build + # cache on a quiet day, so just use that day. + # Otherwise, take the first Sunday of the previous month. + if day >= first_sunday: + day = first_sunday + else: + month -= 1 + if month == 0: + month = 12 + year -= 1 + day = GetFirstSundayOfMonth(year, month) + return datetime.datetime( + year, month, day, build_date.hour, build_date.minute, build_date.second) + + +def main(): + if doctest.testmod()[0]: + return 1 + argument_parser = argparse.ArgumentParser() + argument_parser.add_argument( + 'build_type', help='The type of build', choices=('official', 'default')) + args = argument_parser.parse_args() + + # The mtime of the revision in build/util/LASTCHANGE is stored in a file + # next to it. Read it, to get a deterministic time close to "now". + # That date is then modified as described at the top of the file so that + # it changes less frequently than with every commit. + # This intentionally always uses build/util/LASTCHANGE's commit time even if + # use_dummy_lastchange is set. + lastchange_file = os.path.join(THIS_DIR, 'util', 'LASTCHANGE.committime') + last_commit_timestamp = int(open(lastchange_file).read()) + build_date = datetime.datetime.utcfromtimestamp(last_commit_timestamp) + + # For official builds we want full fidelity time stamps because official + # builds are typically added to symbol servers and Windows symbol servers + # use the link timestamp as the prime differentiator, but for unofficial + # builds we do lots of quantization to avoid churn. + if args.build_type != 'official': + build_date = GetUnofficialBuildDate(build_date) + print(int(calendar.timegm(build_date.utctimetuple()))) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/config/BUILD.gn b/third_party/libwebrtc/build/config/BUILD.gn new file mode 100644 index 0000000000..05ae8a8c99 --- /dev/null +++ b/third_party/libwebrtc/build/config/BUILD.gn @@ -0,0 +1,391 @@ +# 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/c++/c++.gni") +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/crypto.gni") +import("//build/config/dcheck_always_on.gni") +import("//build/config/features.gni") + +# Subprojects need to override arguments in {mac,ios}_sdk_overrides.gni in their +# .gn config, but those arguments are only used on macOS. Including +# mac_sdk_overrides.gni insures that this doesn't trigger an unused argument +# warning. +import("//build/config/ios/ios_sdk_overrides.gni") +import("//build/config/mac/mac_sdk_overrides.gni") + +import("//build/config/pch.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/ui.gni") +import("//build/toolchain/goma.gni") +if (is_android) { + import("//build/config/android/abi.gni") +} + +# ============================================== +# PLEASE DO NOT ADD MORE THINGS TO THIS LIST +# ============================================== +# +# Legacy feature defines applied to all targets. +# +# These are applied to every single compile in the build and most of them are +# only relevant to a few files. This bloats command lines and causes +# unnecessary recompiles when flags are flipped. +# +# To pass defines to source code from the build, use the buildflag system which +# will write headers containing the defines you need. This isolates the define +# and means its definition can participate in the build graph, only recompiling +# things when it actually changes. +# +# See //build/buildflag_header.gni for instructions on generating headers. +# +# This will also allow you to scope your build flag to a BUILD.gn file (or a +# .gni file if you need it from more than one place) rather than making global +# flags. See //build/config/BUILDCONFIG.gn for advice on where to define +# build flags. +config("feature_flags") { + defines = [] + if (dcheck_always_on) { + defines += [ "DCHECK_ALWAYS_ON=1" ] + if (dcheck_is_configurable) { + defines += [ "DCHECK_IS_CONFIGURABLE" ] + } + } + if (use_udev) { + # TODO(brettw) should probably be "=1". + defines += [ "USE_UDEV" ] + } + if (use_aura) { + defines += [ "USE_AURA=1" ] + } + if (use_glib) { + defines += [ "USE_GLIB=1" ] + } + if (use_nss_certs) { + defines += [ "USE_NSS_CERTS=1" ] + } + if (use_ozone && !is_android) { + # Note that some Chrome OS builds unconditionally set |use_ozone| to true, + # but they also build some targets with the Android toolchain. This ensures + # that Android targets still build with USE_OZONE=0 in such cases. + # + # TODO(crbug.com/837032): Maybe this can be cleaned up if we can avoid + # setting use_ozone globally. + defines += [ "USE_OZONE=1" ] + } + if (use_x11) { + defines += [ "USE_X11=1" ] + } + if (is_asan || is_hwasan || is_lsan || is_tsan || is_msan) { + defines += [ "MEMORY_TOOL_REPLACES_ALLOCATOR" ] + } + if (is_asan) { + defines += [ "ADDRESS_SANITIZER" ] + } + if (is_lsan) { + defines += [ "LEAK_SANITIZER" ] + } + if (is_tsan) { + defines += [ + "THREAD_SANITIZER", + "DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL=1", + ] + } + if (is_msan) { + defines += [ "MEMORY_SANITIZER" ] + } + if (is_ubsan || is_ubsan_null || is_ubsan_vptr || is_ubsan_security) { + defines += [ "UNDEFINED_SANITIZER" ] + } + if (is_official_build) { + defines += [ "OFFICIAL_BUILD" ] + } + + # ============================================== + # PLEASE DO NOT ADD MORE THINGS TO THIS LIST + # ============================================== + # + # See the comment at the top. +} + +# Debug/release ---------------------------------------------------------------- + +config("debug") { + defines = [ + "DYNAMIC_ANNOTATIONS_ENABLED=1", + ] + if (!is_win) { + defines += [ "_DEBUG" ] + } + if (is_nacl) { + defines += [ "DYNAMIC_ANNOTATIONS_PREFIX=NACL_" ] + } + + if (is_win) { + if (!enable_iterator_debugging && !use_custom_libcxx) { + # Iterator debugging is enabled by default 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_chromeos) && target_cpu == "x64" && + enable_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. + if (is_tsan) { + defines += [ "DYNAMIC_ANNOTATIONS_ENABLED=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" ] + } + } + + if (is_ios) { + # Disable NSAssert and GTMDevAssert (from Google Toolbox for Mac). This + # follows XCode's default behavior for Release builds. + defines += [ "NS_BLOCK_ASSERTIONS=1" ] + } +} + +# 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", + "dnsapi.lib", + "gdi32.lib", + "msimg32.lib", + "odbc32.lib", + "odbccp32.lib", + "oleaut32.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. + ] + if (current_os == "winuwp") { + # These libraries are needed for Windows UWP (i.e. store apps). + libs += [ + "dloadhelper.lib", + "WindowsApp.lib", + ] + } else { + # These libraries are not compatible with Windows UWP (i.e. store apps.) + libs += [ + "delayimp.lib", + "kernel32.lib", + "ole32.lib", + ] + } + } else if (is_android) { + libs = [ + "dl", + "m", + ] + } else if (is_mac) { + # Targets should choose to explicitly link frameworks they require. Since + # linking can have run-time side effects, nothing should be listed here. + libs = [] + } else if (is_ios) { + # The libraries listed here will be specified for both the target and the + # host. Only the common ones should be listed here. + frameworks = [ + "CoreFoundation.framework", + "CoreGraphics.framework", + "CoreText.framework", + "Foundation.framework", + ] + } else if (is_linux || is_chromeos) { + libs = [ + "dl", + "pthread", + "rt", + ] + } +} + +group("common_deps") { + visibility = [ + ":executable_deps", + ":loadable_module_deps", + ":shared_library_deps", + ] + + # WARNING: This group is a dependency of **every executable and shared + # library**. Please be careful adding new dependencies here. + public_deps = [] + + if (using_sanitizer) { + public_deps += [ "//build/config/sanitizers:deps" ] + } + + #if (use_custom_libcxx) { + # public_deps += [ "//buildtools/third_party/libc++" ] + #} + + if (use_afl) { + public_deps += [ "//third_party/afl" ] + } + + if (is_android && use_order_profiling) { + public_deps += [ "//base/android/orderfile:orderfile_instrumentation" ] + } + + if (is_fuchsia) { + public_deps += + [ "//third_party/fuchsia-sdk/sdk/build/config:runtime_library_group" ] + } +} + +# Only the executable template in BUILDCONFIG.gn should reference this. +group("executable_deps") { + public_deps = [ ":common_deps" ] + if (export_libcxxabi_from_executables) { + public_deps += [ "//buildtools/third_party/libc++abi" ] + } +} + +# Only the loadable_module template in BUILDCONFIG.gn should reference this. +group("loadable_module_deps") { + public_deps = [ ":common_deps" ] +} + +# Only the shared_library template in BUILDCONFIG.gn should reference this. +group("shared_library_deps") { + public_deps = [ ":common_deps" ] +} + +# Executable configs ----------------------------------------------------------- + +# Windows linker setup for EXEs and DLLs. +if (is_win) { + _windows_linker_configs = [ + "//build/config/win:sdk_link", + "//build/config/win:common_linker_setup", + ] +} + +# This config defines the configs applied to all executables. +config("executable_config") { + configs = [] + + if (is_win) { + configs += _windows_linker_configs + } else if (is_mac) { + configs += [ "//build/config/mac:mac_dynamic_flags" ] + } else if (is_ios) { + configs += [ + "//build/config/ios:ios_dynamic_flags", + "//build/config/ios:ios_executable_flags", + ] + } else if (is_linux || is_chromeos || is_android || current_os == "aix") { + configs += [ "//build/config/gcc:executable_config" ] + if (is_chromecast) { + configs += [ "//build/config/chromecast:executable_config" ] + } else if (is_fuchsia) { + configs += [ "//build/config/fuchsia:executable_config" ] + } + } + + # If we're using the prebuilt instrumented libraries with the sanitizers, we + # need to add ldflags to every binary to make sure they are picked up. + if (prebuilt_instrumented_libraries_available) { + configs += [ "//third_party/instrumented_libraries:prebuilt_ldflags" ] + } + if (use_locally_built_instrumented_libraries) { + configs += [ "//third_party/instrumented_libraries:locally_built_ldflags" ] + } + configs += [ "//build/config/sanitizers:link_executable" ] +} + +# Shared library configs ------------------------------------------------------- + +# This config defines the configs applied to all shared libraries. +config("shared_library_config") { + configs = [] + + if (is_win) { + configs += _windows_linker_configs + } else if (is_mac) { + configs += [ "//build/config/mac:mac_dynamic_flags" ] + } else if (is_ios) { + configs += [ + "//build/config/ios:ios_dynamic_flags", + "//build/config/ios:ios_shared_library_flags", + ] + } else if (is_chromecast) { + configs += [ "//build/config/chromecast:shared_library_config" ] + } else if (is_linux || is_chromeos || current_os == "aix") { + configs += [ "//build/config/gcc:shared_library_config" ] + } + + # If we're using the prebuilt instrumented libraries with the sanitizers, we + # need to add ldflags to every binary to make sure they are picked up. + if (prebuilt_instrumented_libraries_available) { + configs += [ "//third_party/instrumented_libraries:prebuilt_ldflags" ] + } + if (use_locally_built_instrumented_libraries) { + configs += [ "//third_party/instrumented_libraries:locally_built_ldflags" ] + } + configs += [ "//build/config/sanitizers:link_shared_library" ] +} + +# Add this config to your target to enable precompiled headers. +# +# Precompiled headers are done on a per-target basis. If you have just a couple +# of files, the time it takes to precompile (~2 seconds) can actually be longer +# than the time saved. On a Z620, a 100 file target compiles about 2 seconds +# faster with precompiled headers, with greater savings for larger targets. +# +# Recommend precompiled headers for targets with more than 50 .cc files. +config("precompiled_headers") { + if (enable_precompiled_headers) { + if (is_win) { + # This is a string rather than a file GN knows about. It has to match + # exactly what's in the /FI flag below, and what might appear in the + # source code in quotes for an #include directive. + precompiled_header = "build/precompile.h" + + # This is a file that GN will compile with the above header. It will be + # implicitly added to the sources (potentially multiple times, with one + # variant for each language used in the target). + precompiled_source = "//build/precompile.cc" + + # Force include the header. + cflags = [ "/FI$precompiled_header" ] + } else if (is_mac || is_linux) { + precompiled_source = "//build/precompile.h" + } + } +} diff --git a/third_party/libwebrtc/build/config/BUILDCONFIG.gn b/third_party/libwebrtc/build/config/BUILDCONFIG.gn new file mode 100644 index 0000000000..571ae5f0d6 --- /dev/null +++ b/third_party/libwebrtc/build/config/BUILDCONFIG.gn @@ -0,0 +1,588 @@ +# 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. + +# ============================================================================= +# WHAT IS THIS FILE? +# ============================================================================= +# +# This is the master GN build configuration. This file is loaded after the +# build args (args.gn) for the build directory and after the toplevel ".gn" +# file (which points to this file as the build configuration). +# +# This file will be executed and the resulting context will be used to execute +# every other file in the build. So variables declared here (that don't start +# with an underscore) will be implicitly global. + +# ============================================================================= +# PLATFORM SELECTION +# ============================================================================= +# +# There are two main things to set: "os" and "cpu". The "toolchain" is the name +# of the GN thing that encodes combinations of these things. +# +# Users typically only set the variables "target_os" and "target_cpu" in "gn +# args", the rest are set up by our build and internal to GN. +# +# There are three different types of each of these things: The "host" +# represents the computer doing the compile and never changes. The "target" +# represents the main thing we're trying to build. The "current" represents +# which configuration is currently being defined, which can be either the +# host, the target, or something completely different (like nacl). GN will +# run the same build file multiple times for the different required +# configuration in the same build. +# +# This gives the following variables: +# - host_os, host_cpu, host_toolchain +# - target_os, target_cpu, default_toolchain +# - current_os, current_cpu, current_toolchain. +# +# Note the default_toolchain isn't symmetrical (you would expect +# target_toolchain). This is because the "default" toolchain is a GN built-in +# concept, and "target" is something our build sets up that's symmetrical with +# its GYP counterpart. Potentially the built-in default_toolchain variable +# could be renamed in the future. +# +# When writing build files, to do something only for the host: +# if (current_toolchain == host_toolchain) { ... + +if (target_os == "") { + target_os = host_os +} + +if (target_cpu == "") { + if (target_os == "android") { + # If we're building for Android, we should assume that we want to + # build for ARM by default, not the host_cpu (which is likely x64). + # This allows us to not have to specify both target_os and target_cpu + # on the command line. + target_cpu = "arm" + } else { + target_cpu = host_cpu + } +} + +if (current_cpu == "") { + current_cpu = target_cpu +} +if (current_os == "") { + current_os = target_os +} + +# ============================================================================= +# BUILD FLAGS +# ============================================================================= +# +# This block lists input arguments to the build, along with their default +# values. +# +# If a value is specified on the command line, it will overwrite the defaults +# given in a declare_args block, otherwise the default will be used. +# +# YOU SHOULD ALMOST NEVER NEED TO ADD FLAGS TO THIS FILE. GN allows any file in +# the build to declare build flags. If you need a flag for a single component, +# you can just declare it in the corresponding BUILD.gn file. +# +# - If your feature is a single target, say //components/foo, you can put +# a declare_args() block in //components/foo/BUILD.gn and use it there. +# Nobody else in the build needs to see the flag. +# +# - Defines based on build variables should be implemented via the generated +# build flag header system. See //build/buildflag_header.gni. You can put +# the buildflag_header target in the same file as the build flag itself. You +# should almost never set "defines" directly. +# +# - If your flag toggles a target on and off or toggles between different +# versions of similar things, write a "group" target that forwards to the +# right target (or no target) depending on the value of the build flag. This +# group can be in the same BUILD.gn file as the build flag, and targets can +# depend unconditionally on the group rather than duplicating flag checks +# across many targets. +# +# - If a semi-random set of build files REALLY needs to know about a define and +# the above pattern for isolating the build logic in a forwarding group +# doesn't work, you can put the argument in a .gni file. This should be put +# in the lowest level of the build that knows about this feature (which should +# almost always be outside of the //build directory!). +# +# Other flag advice: +# +# - Use boolean values when possible. If you need a default value that expands +# to some complex thing in the default case (like the location of the +# compiler which would be computed by a script), use a default value of -1 or +# the empty string. Outside of the declare_args block, conditionally expand +# the default value as necessary. +# +# - Use a name like "use_foo" or "is_foo" (whatever is more appropriate for +# your feature) rather than just "foo". +# +# - Write good comments directly above the declaration with no blank line. +# These comments will appear as documentation in "gn args --list". +# +# - Don't call exec_script inside declare_args. This will execute the script +# even if the value is overridden, which is wasteful. See first bullet. + +declare_args() { + # Set to enable the official build level of optimization. This has nothing + # to do with branding, but enables an additional level of optimization above + # release (!is_debug). This might be better expressed as a tri-state + # (debug, release, official) but for historical reasons there are two + # separate flags. + is_official_build = false + + # Set to true when compiling with the Clang compiler. + is_clang = current_os != "linux" || current_os == "openbsd" || + (current_cpu != "s390x" && current_cpu != "s390" && + current_cpu != "ppc64" && current_cpu != "ppc" && + current_cpu != "mips" && current_cpu != "mips64" && + current_cpu != "riscv64") + + # Allows the path to a custom target toolchain to be injected as a single + # argument, and set as the default toolchain. + custom_toolchain = "" + + # This should not normally be set as a build argument. It's here so that + # every toolchain can pass through the "global" value via toolchain_args(). + host_toolchain = "" + + # DON'T ADD MORE FLAGS HERE. Read the comment above. +} + +declare_args() { + # Debug build. Enabling official builds automatically sets is_debug to false. + is_debug = !is_official_build +} + +declare_args() { + # Component build. Setting to true compiles targets declared as "components" + # as shared libraries loaded dynamically. This speeds up development time. + # When false, components will be linked statically. + # + # For more information see + # https://chromium.googlesource.com/chromium/src/+/main/docs/component_build.md + is_component_build = is_debug && current_os != "ios" +} + +assert(!(is_debug && is_official_build), "Can't do official debug builds") + +# ============================================================================== +# TOOLCHAIN SETUP +# ============================================================================== +# +# Here we set the default toolchain, as well as the variable host_toolchain +# which will identify the toolchain corresponding to the local system when +# doing cross-compiles. When not cross-compiling, this will be the same as the +# default toolchain. +# +# We do this before anything else to make sure we complain about any +# unsupported os/cpu combinations as early as possible. + +if (host_toolchain == "") { + # This should only happen in the top-level context. + # In a specific toolchain context, the toolchain_args() + # block should have propagated a value down. + # TODO(dpranke): Add some sort of assert here that verifies that + # no toolchain omitted host_toolchain from its toolchain_args(). + + if (host_os == "linux" || host_os == "openbsd") { + if (target_os != "linux") { + host_toolchain = "//build/toolchain/linux:clang_$host_cpu" + } else if (is_clang) { + host_toolchain = "//build/toolchain/linux:clang_$host_cpu" + } else { + host_toolchain = "//build/toolchain/linux:$host_cpu" + } + } else if (host_os == "mac") { + host_toolchain = "//build/toolchain/mac:clang_$host_cpu" + } else if (host_os == "win") { + # On Windows always use the target CPU for host builds for x86/x64. On the + # configurations we support this will always work and it saves build steps. + # Windows ARM64 targets require an x64 host for cross build. + if (target_cpu == "x86" || target_cpu == "x64") { + if (is_clang) { + host_toolchain = "//build/toolchain/win:win_clang_$target_cpu" + } else { + host_toolchain = "//build/toolchain/win:$target_cpu" + } + } else if (is_clang) { + host_toolchain = "//build/toolchain/win:win_clang_$host_cpu" + } else { + host_toolchain = "//build/toolchain/win:$host_cpu" + } + } else if (host_os == "aix") { + host_toolchain = "//build/toolchain/aix:$host_cpu" + } else { + assert(false, "Unsupported host_os: $host_os") + } +} + +_default_toolchain = "" + +if (target_os == "android") { + assert(host_os == "linux" || host_os == "mac", + "Android builds are only supported on Linux and Mac hosts.") + _default_toolchain = "//build/toolchain/android:android_clang_$target_cpu" +} else if (target_os == "chromeos" || target_os == "linux" || target_os == "openbsd") { + # See comments in build/toolchain/cros/BUILD.gn about board compiles. + if (is_clang) { + _default_toolchain = "//build/toolchain/linux:clang_$target_cpu" + } else { + _default_toolchain = "//build/toolchain/linux:$target_cpu" + } +} else if (target_os == "fuchsia") { + _default_toolchain = "//build/toolchain/fuchsia:$target_cpu" +} else if (target_os == "ios") { + _default_toolchain = "//build/toolchain/ios:ios_clang_$target_cpu" +} else if (target_os == "mac") { + #assert(host_os == "mac" || host_os == "linux", + # "Mac cross-compiles are unsupported.") + _default_toolchain = "//build/toolchain/mac:clang_$target_cpu" +} else if (target_os == "win") { + # On Windows, we use the same toolchain for host and target by default. + # Beware, win cross builds have some caveats, see docs/win_cross.md + if (is_clang) { + _default_toolchain = "//build/toolchain/win:win_clang_$target_cpu" + } else { + _default_toolchain = "//build/toolchain/win:$target_cpu" + } +} else if (target_os == "winuwp") { + # Only target WinUWP on for a Windows store application and only + # x86, x64 and arm are supported target CPUs. + assert(target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" || + target_cpu == "arm64") + _default_toolchain = "//build/toolchain/win:uwp_$target_cpu" +} else if (target_os == "aix") { + _default_toolchain = "//build/toolchain/aix:$target_cpu" +} else { + assert(false, "Unsupported target_os: $target_os") +} + +# If a custom toolchain has been set in the args, set it as default. Otherwise, +# set the default toolchain for the platform (if any). +if (custom_toolchain != "") { + set_default_toolchain(custom_toolchain) +} else if (_default_toolchain != "") { + set_default_toolchain(_default_toolchain) +} + +# ============================================================================= +# OS DEFINITIONS +# ============================================================================= +# +# We set these various is_FOO booleans for convenience in writing OS-based +# conditions. +# +# - is_android, is_chromeos, is_ios, and is_win should be obvious. +# - is_mac is set only for desktop Mac. It is not set on iOS. +# - is_posix is true for mac and any Unix-like system (basically everything +# except Fuchsia and Windows). +# - is_linux is true for desktop Linux, but not for ChromeOS nor Android (which +# is generally too different despite being based on the Linux kernel). +# +# Do not add more is_* variants here for random lesser-used Unix systems like +# aix or one of the BSDs. If you need to check these, just check the +# current_os value directly. + +is_android = current_os == "android" +is_chromeos = current_os == "chromeos" +is_fuchsia = current_os == "fuchsia" +is_ios = current_os == "ios" +is_linux = current_os == "linux" +is_bsd = current_os == "openbsd" +is_mac = current_os == "mac" +is_nacl = current_os == "nacl" +is_win = current_os == "win" || current_os == "winuwp" + +is_apple = is_ios || is_mac +is_posix = !is_win && !is_fuchsia + +# ============================================================================= +# TARGET DEFAULTS +# ============================================================================= +# +# Set up the default configuration for every build target of the given type. +# The values configured here will be automatically set on the scope of the +# corresponding target. Target definitions can add or remove to the settings +# here as needed. +# +# WHAT GOES HERE? +# +# Other than the main compiler and linker configs, the only reason for a config +# to be in this list is if some targets need to explicitly override that config +# by removing it. This is how targets opt-out of flags. If you don't have that +# requirement and just need to add a config everywhere, reference it as a +# sub-config of an existing one, most commonly the main "compiler" one. + +# Holds all configs used for running the compiler. +default_compiler_configs = [ + "//build/config:feature_flags", + "//build/config/compiler:afdo", + "//build/config/compiler:afdo_optimize_size", + "//build/config/compiler:cet_shadow_stack", + "//build/config/compiler:compiler", + "//build/config/compiler:compiler_arm_fpu", + "//build/config/compiler:compiler_arm_thumb", + "//build/config/compiler:chromium_code", + "//build/config/compiler:default_include_dirs", + "//build/config/compiler:default_optimization", + "//build/config/compiler:default_stack_frames", + "//build/config/compiler:default_symbols", + "//build/config/compiler:export_dynamic", + "//build/config/compiler:no_exceptions", + "//build/config/compiler:no_rtti", + "//build/config/compiler:runtime_library", + "//build/config/compiler:thin_archive", + "//build/config/compiler:thinlto_optimize_default", + "//build/config/compiler:default_init_stack_vars", + "//build/config/compiler:no_unresolved_symbols", + "//build/config/compiler/pgo:default_pgo_flags", + "//build/config/coverage:default_coverage", + "//build/config/sanitizers:default_sanitizer_flags", +] + +if (is_win) { + default_compiler_configs += [ + "//build/config/win:default_cfg_compiler", + "//build/config/win:default_crt", + "//build/config/win:lean_and_mean", + "//build/config/win:nominmax", + "//build/config/win:unicode", + "//build/config/win:winver", + ] +} + +if (is_posix) { + if (current_os != "aix") { + default_compiler_configs += + [ "//build/config/gcc:symbol_visibility_hidden" ] + } +} + +if (is_fuchsia) { + default_compiler_configs += [ "//build/config/gcc:symbol_visibility_hidden" ] +} + +if (is_android) { + default_compiler_configs += + [ "//build/config/android:default_orderfile_instrumentation" ] +} + +if (is_clang && !is_nacl) { + default_compiler_configs += [ + "//build/config/clang:find_bad_constructs", + "//build/config/clang:extra_warnings", + ] +} + +# Debug/release-related defines. +if (is_debug) { + default_compiler_configs += [ "//build/config:debug" ] +} else { + default_compiler_configs += [ "//build/config:release" ] +} + +# Static libraries and source sets use only the compiler ones. +set_defaults("static_library") { + configs = default_compiler_configs +} +set_defaults("source_set") { + configs = default_compiler_configs +} +set_defaults("rust_library") { + configs = default_compiler_configs +} +set_defaults("rust_proc_macro") { + configs = default_compiler_configs +} + +# Compute the set of configs common to all linked targets (shared libraries, +# loadable modules, executables) to avoid duplication below. +if (is_win) { + # Many targets remove these configs, so they are not contained within + # //build/config:executable_config for easy removal. + _linker_configs = [ + "//build/config/win:default_incremental_linking", + + # Default to console-mode apps. Most of our targets are tests and such + # that shouldn't use the windows subsystem. + "//build/config/win:console", + ] +} else if (is_mac) { + _linker_configs = [ "//build/config/mac:strip_all" ] +} else { + _linker_configs = [] +} + +# Executable defaults. +default_executable_configs = default_compiler_configs + [ + "//build/config:default_libs", + "//build/config:executable_config", + ] + _linker_configs + +if (is_win) { + # Turn on linker CFI for executables, and position it so it can be removed + # if needed. + default_executable_configs += [ "//build/config/win:cfi_linker" ] +} + +set_defaults("executable") { + configs = default_executable_configs +} + +# Shared library and loadable module defaults (also for components in component +# mode). +default_shared_library_configs = default_compiler_configs + [ + "//build/config:default_libs", + "//build/config:shared_library_config", + ] + _linker_configs +if (is_win) { + # Turn on linker CFI for DLLs, and position it so it can be removed if needed. + default_shared_library_configs += [ "//build/config/win:cfi_linker" ] +} + +if (is_android) { + # Strip native JNI exports from shared libraries by default. Binaries that + # want this can remove this config. + default_shared_library_configs += + [ "//build/config/android:hide_all_but_jni_onload" ] +} +set_defaults("shared_library") { + configs = default_shared_library_configs +} +set_defaults("loadable_module") { + configs = default_shared_library_configs + + # loadable_modules are generally used by other libs, not just via JNI. + if (is_android) { + configs -= [ "//build/config/android:hide_all_but_jni_onload" ] + } +} + +# A helper for forwarding testonly and visibility. +# Forwarding "*" does not include variables from outer scopes (to avoid copying +# all globals into each template invocation), so it will not pick up +# file-scoped or outer-template-scoped variables. Normally this behavior is +# desired, but "visibility" and "testonly" are commonly defined in outer scopes. +# Explicitly forwarding them in forward_variables_from() works around this +# nuance. See //build/docs/writing_gn_templates.md#using-forward_variables_from +TESTONLY_AND_VISIBILITY = [ + "testonly", + "visibility", +] + +# Sets default dependencies for executable and shared_library targets. +# +# Variables +# no_default_deps: If true, no standard dependencies will be added. +# Targets that set this usually also want to remove +# "//build/config/compiler:runtime_library" from configs (to remove +# its subconfig "//build/config/c++:runtime_library"). +foreach(_target_type, + [ + "executable", + "loadable_module", + "shared_library", + ]) { + template(_target_type) { + # Alias "target_name" because it is clobbered by forward_variables_from(). + _target_name = target_name + target(_target_type, _target_name) { + forward_variables_from(invoker, + "*", + TESTONLY_AND_VISIBILITY + [ "no_default_deps" ]) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + if (!defined(deps)) { + deps = [] + } + if (!defined(invoker.no_default_deps) || !invoker.no_default_deps) { + # This pulls in one of: + # //build/config:executable_deps + # //build/config:loadable_module_deps + # //build/config:shared_library_deps + # (This explicit list is so that grepping for these configs finds where + # they are used.) + deps += [ "//build/config:${_target_type}_deps" ] + } + + # On Android, write shared library output file to metadata. We will use + # this information to, for instance, collect all shared libraries that + # should be packaged into an APK. + if (!defined(invoker.metadata) && is_android && + (_target_type == "shared_library" || + _target_type == "loadable_module")) { + _output_name = _target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + # Remove 'lib' prefix from output name if it exists. + _magic_prefix = "$0x01$0x01" + _output_name = string_replace("${_magic_prefix}${_output_name}", + "${_magic_prefix}lib", + _magic_prefix, + 1) + _output_name = string_replace(_output_name, _magic_prefix, "", 1) + + if (defined(output_extension)) { + _shlib_extension = ".$output_extension" + } else if (is_component_build && _target_type != "loadable_module") { + _shlib_extension = ".cr.so" + } else { + _shlib_extension = ".so" + } + + metadata = { + shared_libraries = + [ "$root_out_dir/lib${_output_name}${_shlib_extension}" ] + } + } + } + } +} + +# ============================================================================== +# COMPONENT SETUP +# ============================================================================== + +# Defines a component, which equates to a shared_library when +# is_component_build == true and a static_library otherwise. +# +# Use static libraries for the static build rather than source sets because +# many of of our test binaries link many large dependencies but often don't +# use large portions of them. The static libraries are much more efficient to +# link in this situation since only the necessary object files are linked. +# +# The invoker can override the type of the target in the non-component-build +# case by setting static_component_type to either "source_set" or +# "static_library". If unset, the default will be used. +template("component") { + if (is_component_build) { + _component_mode = "shared_library" + } else if (defined(invoker.static_component_type)) { + assert(invoker.static_component_type == "static_library" || + invoker.static_component_type == "source_set") + _component_mode = invoker.static_component_type + } else if (!defined(invoker.sources) || invoker.sources == []) { + # When there are no sources defined, use a source set to avoid creating + # an empty static library (which generally don't work). + _component_mode = "source_set" + } else { + _component_mode = "static_library" + } + target(_component_mode, target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + } +} + +# Component defaults +set_defaults("component") { + if (is_component_build) { + configs = default_shared_library_configs + if (is_android) { + configs -= [ "//build/config/android:hide_all_but_jni_onload" ] + } + } else { + configs = default_compiler_configs + } +} diff --git a/third_party/libwebrtc/build/config/OWNERS b/third_party/libwebrtc/build/config/OWNERS new file mode 100644 index 0000000000..eeb67065c2 --- /dev/null +++ b/third_party/libwebrtc/build/config/OWNERS @@ -0,0 +1,5 @@ +dpranke@google.com +scottmg@chromium.org + +per-file ozone.gni=file://ui/ozone/OWNERS +per-file ozone_extra.gni=file://ui/ozone/OWNERS diff --git a/third_party/libwebrtc/build/config/aix/BUILD.gn b/third_party/libwebrtc/build/config/aix/BUILD.gn new file mode 100644 index 0000000000..9079f6f53e --- /dev/null +++ b/third_party/libwebrtc/build/config/aix/BUILD.gn @@ -0,0 +1,61 @@ +# Copyright 2017 The Chromium 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") +import("//build/toolchain/toolchain.gni") + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic. + +config("compiler") { + # These flags are shared between the C compiler and linker. + defines = [ + "_LINUX_SOURCE_COMPAT=1", + "__STDC_FORMAT_MACROS", + "_ALL_SOURCE=1", + ] + + cflags = [ + "-Wall", + "-Wno-unused-parameter", + "-pthread", + "-Wmissing-field-initializers", + "-Wno-uninitialized", + "-mcpu=power5+", + "-mfprnd", + "-mno-popcntb", + "-maix64", + "-fdata-sections", + "-ffunction-sections", + "-O3", + + # "-Werror" + # We need to find a way to fix the TOC warnings if we want to enable this. + ] + + cflags_cc = [ + "-fno-rtti", + "-fno-exceptions", + "-Wno-narrowing", + "-Wno-non-virtual-dtor", + ] + + ldflags = [ + "-pthread", + "-maix64", + "-Wl,-bbigtoc", + ] + + if (is_component_build) { + cflags += [ + "-fpic", + ] + ldflags += [ + "-Wl,-brtl", + # -bnoipath so that only names of .so objects are stored in loader + # section, excluding leading "./" + "-Wl,-bnoipath", + ] + } +} diff --git a/third_party/libwebrtc/build/config/android/BUILD.gn b/third_party/libwebrtc/build/config/android/BUILD.gn new file mode 100644 index 0000000000..a77a628be4 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/BUILD.gn @@ -0,0 +1,161 @@ +# 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/config.gni") +import("//build/config/c++/c++.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/sanitizers/sanitizers.gni") + +if (current_toolchain == default_toolchain) { + import("//build/toolchain/concurrent_links.gni") +} + +assert(is_android) + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic that is +# Android-only. +config("compiler") { + cflags = [ + "-ffunction-sections", + "-fno-short-enums", + ] + defines = [ + "ANDROID", + + # The NDK has these things, but doesn't define the constants to say that it + # does. Define them here instead. + "HAVE_SYS_UIO_H", + + # Forces full rebuilds on NDK rolls. To rebuild everything when NDK version + # stays the same, increment the suffix number. + "ANDROID_NDK_VERSION_ROLL=${android_ndk_version}_1", + ] + + if (target_cpu == "mips64el") { + cflags += [ + # Have to force IAS for mips64. + "-fintegrated-as", + ] + } + + ldflags = [ + # Don't allow visible symbols from libgcc or libc++ to be + # re-exported. + "-Wl,--exclude-libs=libgcc.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", + ] + + # TODO(crbug.com/1184398): Move to compiler-rt when we are ready. + ldflags += [ "--rtlib=libgcc" ] + if (target_cpu == "arm64") { + # For outline atomics on AArch64 (can't pass this unconditionally + # due to unused flag warning on other targets). + cflags += [ "--rtlib=libgcc" ] + if (arm_control_flow_integrity == "standard") { + cflags += [ "-mbranch-protection=standard" ] + } + } + + # $compile_api_level corresponds to the API level used for the sysroot path + # calculation in //build/config/android/config.gni + if (android_64bit_target_cpu) { + compile_api_level = android64_ndk_api_level + } else { + compile_api_level = android32_ndk_api_level + } + + cflags += [ "--target=$android_abi_target$compile_api_level" ] + ldflags += [ "--target=$android_abi_target$compile_api_level" ] + + # Assign any flags set for the C compiler to asmflags so that they are sent + # to the assembler. + asmflags = cflags +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is Android-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + # Let the linker find libgcc.a. + ldflags = [ "--gcc-toolchain=" + + rebase_path(android_toolchain_root, root_build_dir) ] + + libs = [] + + # On 64-bit platforms, the only symbols provided by libandroid_support.a are + # strto{d,f,l,ul}_l. These symbols are not used by our libc++, and newer NDKs + # don't provide a libandroid_support.a on 64-bit platforms, so we only depend + # on this library on 32-bit platforms. + if (target_cpu == "arm" || target_cpu == "x86") { + libs += [ "android_support" ] + } + + # arm builds of libc++ starting in NDK r12 depend on unwind. + if (target_cpu == "arm") { + libs += [ "unwind" ] + } + + if (target_cpu == "arm" && arm_version == 6) { + libs += [ "atomic" ] + } + + if (target_cpu == "mipsel") { + libs += [ "atomic" ] + } + + # TODO(jdduke) Re-enable on mips after resolving linking + # issues with libc++ (crbug.com/456380). + if (target_cpu != "mipsel" && target_cpu != "mips64el") { + ldflags += [ "-Wl,--warn-shared-textrel" ] + } +} + +config("hide_all_but_jni_onload") { + ldflags = [ "-Wl,--version-script=" + rebase_path( + "//build/android/android_only_explicit_jni_exports.lst", + root_build_dir) ] +} + +config("hide_all_but_jni") { + ldflags = [ "-Wl,--version-script=" + + rebase_path("//build/android/android_only_jni_exports.lst", + root_build_dir) ] +} + +config("lld_pack_relocations") { + ldflags = [ "-Wl,--pack-dyn-relocs=android" ] +} + +# Used for instrumented build to generate the orderfile. +config("default_orderfile_instrumentation") { + if (use_order_profiling) { + cflags = [ "-finstrument-function-entry-bare" ] + if (use_thin_lto) { + # TODO(pcc): This should not be necessary. Remove once + # https://reviews.llvm.org/D50016 lands and gets rolled in. + ldflags = [ "-Wl,-u,__cyg_profile_func_enter_bare" ] + } + } +} + +if (current_toolchain == default_toolchain) { + pool("goma_javac_pool") { + # Override action_pool when goma is enabled for javac. + depth = 10000 + } + + # When defined, this pool should be used instead of link_pool for command + # that need 1-2GB of RAM. https://crbug.com/1078460 + if (defined(java_cmd_pool_size)) { + pool("java_cmd_pool") { + depth = java_cmd_pool_size + } + } +} diff --git a/third_party/libwebrtc/build/config/android/OWNERS b/third_party/libwebrtc/build/config/android/OWNERS new file mode 100644 index 0000000000..a74cfbe228 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/OWNERS @@ -0,0 +1 @@ +file://build/android/OWNERS diff --git a/third_party/libwebrtc/build/config/android/abi.gni b/third_party/libwebrtc/build/config/android/abi.gni new file mode 100644 index 0000000000..1dbbf0cd61 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/abi.gni @@ -0,0 +1,101 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Logic separated out from config.gni so that it can be used by compiler.gni +# without introducing a circular dependency. + +# NOTE: Because Chrome OS builds may depend on targets built with the Android +# toolchain, this GNI file may be read and processed from within Chrome OS +# toolchains. Checking |is_android| here would therefore be too restrictive. +assert(is_android || is_chromeos) + +declare_args() { + # Adds intrumentation to each function. Writes a file with the order that + # functions are called at startup. + use_order_profiling = false + + # Only effective if use_order_profiling = true. When this is true, + # instrumentation switches from startup profiling after a delay, and + # then waits for a devtools memory dump request to dump all + # profiling information. When false, the same delay is used to switch from + # startup, and then after a second delay all profiling information is dumped. + # See base::android::orderfile::StartDelayedDump for more information. + devtools_instrumentation_dumping = false + + # Only effective if use_order_profiling = true. When this is true the call + # graph based instrumentation is used. + use_call_graph = false + + # Build additional browser splits with HWASAN instrumentation enabled. + build_hwasan_splits = false + + # *For CQ puposes only* Leads to non-working APKs. + # Forces all APKs/bundles to be 64-bit only to improve build speed in the CQ + # (no need to also build 32-bit library). + skip_secondary_abi_for_cq = false +} + +assert(!devtools_instrumentation_dumping || use_order_profiling, + "devtools_instrumentation_dumping requires use_order_profiling") +assert(!use_call_graph || use_order_profiling, + "use_call_graph requires use_order_profiling") + +if (target_cpu == "x86") { + android_app_abi = "x86" + android_abi_target = "i686-linux-android" +} else if (target_cpu == "arm") { + import("//build/config/arm.gni") + if (arm_version < 7) { + android_app_abi = "armeabi" + } else { + android_app_abi = "armeabi-v7a" + } + android_abi_target = "arm-linux-androideabi" +} else if (target_cpu == "mipsel") { + android_app_abi = "mips" + android_abi_target = "mipsel-linux-android" +} else if (target_cpu == "x64") { + android_app_abi = "x86_64" + + # Place holder for x64 support, not tested. + # TODO: Enable clang support for Android x64. http://crbug.com/539781 + android_abi_target = "x86_64-linux-android" +} else if (target_cpu == "arm64") { + android_app_abi = "arm64-v8a" + android_abi_target = "aarch64-linux-android" +} else if (target_cpu == "mips64el") { + android_app_abi = "mips64" + + # Place holder for mips64 support, not tested. + android_abi_target = "mips64el-linux-android" +} else { + assert(false, "Unknown Android ABI: " + target_cpu) +} + +if (target_cpu == "arm64" || target_cpu == "x64" || target_cpu == "mips64el") { + android_64bit_target_cpu = true +} else if (target_cpu == "arm" || target_cpu == "x86" || + target_cpu == "mipsel") { + android_64bit_target_cpu = false +} else { + assert(false, "Unknown target CPU: $target_cpu") +} + +# Intentionally do not define android_app_secondary_abi_cpu and +# android_app_secondary_abi for 32-bit target_cpu, since they are not used. +if (target_cpu == "arm64") { + android_secondary_abi_cpu = "arm" + android_app_secondary_abi = "armeabi-v7a" +} else if (target_cpu == "x64") { + android_secondary_abi_cpu = "x86" + android_app_secondary_abi = "x86" +} else if (target_cpu == "mips64el") { + android_secondary_abi_cpu = "mipsel" + android_app_secondary_abi = "mips" +} + +if (defined(android_secondary_abi_cpu)) { + android_secondary_abi_toolchain = + "//build/toolchain/android:android_clang_${android_secondary_abi_cpu}" +} diff --git a/third_party/libwebrtc/build/config/android/android_nocompile.gni b/third_party/libwebrtc/build/config/android/android_nocompile.gni new file mode 100644 index 0000000000..8ffca0e138 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/android_nocompile.gni @@ -0,0 +1,113 @@ +# Copyright 2020 The Chromium 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") + +declare_args() { + # Used by tests to enable generating build files for GN targets which should + # not compile. + enable_android_nocompile_tests = false +} + +# Defines a test suite which checks that the 'test targets' fail to compile. The +# test suite runs 'gn gen' with a custom output directory and attempts to compile +# each test target. +# +# All of the tests should be defined in the same dedicated BUILD.gn file in order +# to minimize the number of targets that are processed by 'gn gen'. +# +# Variables +# tests: List of test configurations. A test configuration has the following +# keys: +# 'target': The GN target which should not compile when +# enable_android_nocompile_tests=true The target should compile when +# enable_android_nocompile_tests=false. +# 'expected_compile_output_regex': Error message regex to search for when compile fails. +# 'nocompile_sources': Source files which do not compile. This ensures that +# the test suite is re-run when one of these files change (as the test +# targets might not depend of the files when +# enable_android_nocompile_tests=false). +template("android_nocompile_test_suite") { + assert(!enable_android_nocompile_tests) + + action(target_name) { + testonly = true + script = "//build/android/gyp/nocompile_test.py" + + _tests = invoker.tests + _test0 = _tests[0] + _test0_dir = get_label_info(_test0["target"], "dir") + _test0_target_out_dir = get_label_info(_test0["target"], "target_out_dir") + foreach(_test_config, _tests) { + assert( + _test0_dir == get_label_info(_test_config["target"], "dir"), + "To avoid running 'gn gen' for each test, all tests in an android_nocompile_test_suite() should be declared in same BUILD.gn file") + } + + deps = [] + if (defined(invoker.deps)) { + deps += invoker.deps + } + + sources = [] + if (defined(invoker.sources)) { + sources += invoker.sources + } + + # Depend on compile_java Python scripts so that the action is re-run whenever the script is + # modified. + _pydeps = [ "//build/android/gyp/compile_java.pydeps" ] + if (defined(invoker.pydeps)) { + _pydeps += invoker.pydeps + } + + inputs = [] + foreach(_pydeps_file, _pydeps) { + _pydeps_file_lines = [] + _pydeps_file_lines = read_file(_pydeps_file, "list lines") + _pydeps_entries = [] + _pydeps_entries = filter_exclude(_pydeps_file_lines, [ "#*" ]) + _pydeps_file_dir = get_path_info(_pydeps_file, "dir") + inputs += rebase_path(_pydeps_entries, ".", _pydeps_file_dir) + } + + _json_test_configs = [] + foreach(_test_config, _tests) { + _test = _test_config["target"] + deps += [ _test ] + sources += _test_config["nocompile_sources"] + _dep_dir = get_label_info(_test, "dir") + _dep_name = get_label_info(_test, "name") + _json_test_configs += [ + { + target = "${_dep_dir}:${_dep_name}" + expect_regex = _test_config["expected_compile_output_regex"] + }, + ] + } + + _config_path = "$target_gen_dir/${target_name}.nocompile_config" + write_file(_config_path, _json_test_configs, "json") + + # Compute output directory for no-compile tests based on the directory containing test + # targets instead of based on the test suite target name. This avoids calling 'gn gen' for each + # android_nocompile_test_suite() for test suites whose tests are declared in the same BUILD.gn + # file. + _out_dir = "${_test0_target_out_dir}/nocompile_out" + + _stamp_path = "${target_gen_dir}/${target_name}.stamp" + args = [ + "--gn-args-path", + "args.gn", + "--out-dir", + rebase_path(_out_dir, root_build_dir), + "--test-configs-path", + rebase_path(_config_path, root_build_dir), + "--stamp", + rebase_path(_stamp_path, root_build_dir), + ] + inputs += [ _config_path ] + outputs = [ _stamp_path ] + } +} diff --git a/third_party/libwebrtc/build/config/android/build_vars.gni b/third_party/libwebrtc/build/config/android/build_vars.gni new file mode 100644 index 0000000000..a47607dc7c --- /dev/null +++ b/third_party/libwebrtc/build/config/android/build_vars.gni @@ -0,0 +1,29 @@ +# Copyright 2020 The Chromium 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") + +# Contains useful GN variables that may be used by scripts that take +# --output-directory as an arg. +build_vars_file = "$root_build_dir/build_vars.json" + +android_build_vars_json = { + if (enable_java_templates) { + android_ndk_root = rebase_path(android_ndk_root, root_build_dir) + android_sdk_build_tools = + rebase_path(android_sdk_build_tools, root_build_dir) + android_sdk_build_tools_version = android_sdk_build_tools_version + android_sdk_root = rebase_path(android_sdk_root, root_build_dir) + android_sdk_version = android_sdk_version + android_tool_prefix = rebase_path(android_tool_prefix, root_build_dir) + final_android_sdk = final_android_sdk + + if (defined(android_secondary_abi_cpu)) { + android_secondary_abi_toolchain = + rebase_path(get_label_info(":foo($android_secondary_abi_toolchain)", + "root_out_dir"), + root_build_dir) + } + } +} diff --git a/third_party/libwebrtc/build/config/android/channel.gni b/third_party/libwebrtc/build/config/android/channel.gni new file mode 100644 index 0000000000..6348bb996e --- /dev/null +++ b/third_party/libwebrtc/build/config/android/channel.gni @@ -0,0 +1,14 @@ +# 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() { + # The channel to build on Android: stable, beta, dev, canary, work, or + # default. "default" should be used on non-official builds. + android_channel = "default" +} + +assert(android_channel == "default" || android_channel == "canary" || + android_channel == "dev" || android_channel == "beta" || + android_channel == "stable", + "Invalid channel: " + android_channel) diff --git a/third_party/libwebrtc/build/config/android/config.gni b/third_party/libwebrtc/build/config/android/config.gni new file mode 100644 index 0000000000..13418299e6 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/config.gni @@ -0,0 +1,353 @@ +# 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 common system config stuff for the Android build. + +# NOTE: Because Chrome OS builds may depend on targets built with the Android +# toolchain, this GNI file may be read and processed from within Chrome OS +# toolchains. Checking |is_android| here would therefore be too restrictive. +if (is_android || is_chromeos) { + import("//build/config/chromecast_build.gni") + import("//build/config/dcheck_always_on.gni") + import("//build_overrides/build.gni") + import("abi.gni") + + if (build_with_chromium) { + # Some non-chromium projects (e.g. WebRTC) use our build configs + # heavily but don't write gclient args files. + + import("//build/config/gclient_args.gni") + if (defined(checkout_android_native_support)) { + n = "$0x0A" # Newline + assert(checkout_android_native_support, + "Missing native Android toolchain support. |target_os| in your " + + ".gclient configuration file (in the parent directory of " + + "src) must include \"android\" and/or \"chromeos\". For " + + "example:${n}${n}solutions = [${n}...${n}]${n}" + + "target_os=[\"chromeos\"]${n}${n}" + + "After adding |target_os| please re-run \"gclient sync\".${n}") + } + } + + has_chrome_android_internal = + exec_script("//build/dir_exists.py", + [ rebase_path("//clank", root_build_dir) ], + "string") == "True" + + # We are using a separate declare_args block for only this argument so that + # we can decide if we have to pull in definitions from the internal config + # early. + declare_args() { + # Enables using the internal Chrome for Android repository. The default + # value depends on whether the repository is available, and if it's not but + # this argument is manually set to True, the generation will fail. + # The main purpose of this argument is to avoid having to maintain 2 + # repositories to support both public only and internal builds. + enable_chrome_android_internal = has_chrome_android_internal + + # The default to use for android:minSdkVersion for targets that do + # not explicitly set it. + default_min_sdk_version = 21 + + # [WIP] Allows devs to achieve much faster edit-build-install cycles. + # Currently only works for ChromeModern apks due to incremental install. + # This needs to be in a separate declare_args as it determines some of the + # args in the main declare_args block below. + android_fast_local_dev = false + } + + declare_args() { + # Android API level for 32 bits platforms + android32_ndk_api_level = default_min_sdk_version + + # Android API level for 64 bits platforms + if (default_min_sdk_version < 21) { + android64_ndk_api_level = 21 + } else { + android64_ndk_api_level = default_min_sdk_version + } + } + + if (enable_chrome_android_internal) { + import("//clank/config.gni") + } else { + import("//build/config/android/sdk.gni") + declare_args() { + # Which Android SDK to use. + android_sdk_release = default_android_sdk_release + } + } + + if (!defined(default_android_ndk_root)) { + default_android_ndk_root = "//third_party/android_ndk" + default_android_ndk_version = "r22" + default_android_ndk_major_version = 22 + } else { + assert(defined(default_android_ndk_version)) + assert(defined(default_android_ndk_major_version)) + } + + public_android_sdk_root = "//third_party/android_sdk/public" + if (android_sdk_release == "s") { + default_android_sdk_root = public_android_sdk_root + default_android_sdk_version = "31" + default_android_sdk_build_tools_version = "31.0.0" + public_android_sdk = true + } + + # For use downstream when we are building with preview Android SDK + if (!defined(final_android_sdk)) { + final_android_sdk = public_android_sdk + } + + if (!defined(default_lint_android_sdk_root)) { + # Purposefully repeated so that downstream can change + # default_android_sdk_root without changing lint version. + default_lint_android_sdk_root = public_android_sdk_root + default_lint_android_sdk_version = 31 + } + + if (!defined(default_extras_android_sdk_root)) { + # Purposefully repeated so that downstream can change + # default_android_sdk_root without changing where we load the SDK extras + # from. (Google Play services, etc.) + default_extras_android_sdk_root = public_android_sdk_root + } + + if (!defined(default_android_keystore_path)) { + default_android_keystore_path = "//build/android/chromium-debug.keystore" + default_android_keystore_name = "chromiumdebugkey" + default_android_keystore_password = "chromium" + } + + # google_play_services_package contains the path where individual client + # targets (e.g. google_play_services_base_java) are located. + if (!defined(google_play_services_package)) { + if (is_chromecast && chromecast_branding != "public") { + google_play_services_package = "//chromecast/internal/android/prebuilt/google-play-services-first-party" + } else { + google_play_services_package = "//third_party/android_deps" + } + } + + if (!defined(dagger_java_target)) { + dagger_java_target = + "//third_party/android_deps:com_google_dagger_dagger_java" + } + + if (!defined(dagger_annotation_processor_target)) { + dagger_annotation_processor_target = + "//third_party/android_deps:com_google_dagger_dagger_compiler_java" + } + + if (!defined(guava_android_target)) { + guava_android_target = + "//third_party/android_deps:com_google_guava_guava_android_java" + } + + if (!defined(material_design_target)) { + material_design_target = + "//third_party/android_deps:com_google_android_material_material_java" + } + + if (!defined(android_protoc_bin)) { + android_protoc_bin = "//third_party/android_protoc/protoc" + android_proto_runtime = + "//third_party/android_deps:com_google_protobuf_protobuf_javalite_java" + } + + webview_public_framework_dep = + "//third_party/android_sdk:public_framework_system_java" + if (!defined(webview_framework_dep)) { + webview_framework_dep = webview_public_framework_dep + } + + assert(defined(default_android_sdk_root), + "SDK release " + android_sdk_release + " not recognized.") + + declare_args() { + android_ndk_root = default_android_ndk_root + android_ndk_version = default_android_ndk_version + android_ndk_major_version = default_android_ndk_major_version + + android_sdk_root = default_android_sdk_root + android_sdk_version = default_android_sdk_version + android_sdk_build_tools_version = default_android_sdk_build_tools_version + + lint_android_sdk_root = default_lint_android_sdk_root + lint_android_sdk_version = default_lint_android_sdk_version + + # Libc++ library directory. Override to use a custom libc++ binary. + android_libcpp_lib_dir = "" + + # Android versionCode for android_apk()s that don't explicitly set one. + android_default_version_code = "1" + + # Android versionName for android_apk()s that don't explicitly set one. + android_default_version_name = "Developer Build" + + # Forced Android versionCode + android_override_version_code = "" + + # Forced Android versionName + android_override_version_name = "" + + # The path to the keystore to use for signing builds. + android_keystore_path = default_android_keystore_path + + # The name of the keystore to use for signing builds. + android_keystore_name = default_android_keystore_name + + # The password for the keystore to use for signing builds. + android_keystore_password = default_android_keystore_password + + # Java debug on Android. Having this on enables multidexing, and turning it + # off will enable proguard. + is_java_debug = is_debug + + # Mark APKs as android:debuggable="true". + debuggable_apks = !is_official_build + + # Set to false to disable the Errorprone compiler. + # Defaults to false for official builds to reduce build times. + # Static analysis failures should have been already caught by normal bots. + # Disabled when fast_local_dev is turned on. + use_errorprone_java_compiler = !is_official_build && !android_fast_local_dev + + # Build incremental targets whenever possible. + # See //build/android/incremental_install/README.md for more details. + incremental_install = android_fast_local_dev + + # When true, updates all android_aar_prebuilt() .info files during gn gen. + # Refer to android_aar_prebuilt() for more details. + update_android_aar_prebuilts = false + + # Turns off android lint. Useful for prototyping or for faster local builds. + # Defaults to true for official builds to reduce build times. + # Static analysis failures should have been already caught by normal bots. + # Disabled when fast_local_dev is turned on. + disable_android_lint = is_official_build || android_fast_local_dev + + # Location of aapt2 used for app bundles. For now, a more recent version + # than the one distributed with the Android SDK is required. + android_sdk_tools_bundle_aapt2_dir = + "//third_party/android_build_tools/aapt2" + + # Causes expectation failures to break the build, otherwise, just warns on + # stderr and writes a failure file to $android_configuration_failure_dir: + fail_on_android_expectations = false + + # Controls whether proguard obfuscation is enabled for targets + # configured to use it. + enable_proguard_obfuscation = true + + # Controls whether |short_resource_paths| and |strip_resource_names| are + # respected. Useful when trying to analyze APKs using tools that do not + # support mapping these names. + enable_arsc_obfuscation = true + + # The target to use as the system WebView implementation. + if (android_64bit_target_cpu && skip_secondary_abi_for_cq) { + system_webview_apk_target = "//android_webview:system_webview_64_apk" + } else { + system_webview_apk_target = "//android_webview:system_webview_apk" + } + + # Where to write failed expectations for bots to read. + expectations_failure_dir = "$root_build_dir/failed_expectations" + } + + # We need a second declare_args block to make sure we are using the overridden + # value of the arguments set above. + declare_args() { + if (defined(default_android_sdk_platform_version)) { + android_sdk_platform_version = default_android_sdk_platform_version + } else { + android_sdk_platform_version = android_sdk_version + } + + # Whether java assertions and Preconditions checks are enabled. + enable_java_asserts = is_java_debug || dcheck_always_on + + # Reduce build time by using d8 incremental build. + enable_incremental_d8 = true + + # Use hashed symbol names to reduce JNI symbol overhead. + use_hashed_jni_names = !is_java_debug + + # Enables Java library desugaring. + # This will cause an extra classes.dex file to appear in every apk. + enable_jdk_library_desugaring = true + } + + # Host stuff ----------------------------------------------------------------- + + # Defines the name the Android build gives to the current host CPU + # architecture, which is different than the names GN uses. + if (host_cpu == "x64") { + android_host_arch = "x86_64" + } else if (host_cpu == "x86") { + android_host_arch = "x86" + } else { + assert(false, "Need Android toolchain support for your build CPU arch.") + } + + # Defines the name the Android build gives to the current host CPU + # architecture, which is different than the names GN uses. + if (host_os == "linux") { + android_host_os = "linux" + } else if (host_os == "mac") { + android_host_os = "darwin" + } else { + assert(false, "Need Android toolchain support for your build OS.") + } + + # Directories and files ------------------------------------------------------ + # + # We define may of the dirs strings here for each output architecture (rather + # than just the current one) since these are needed by the Android toolchain + # file to define toolchains for all possible targets in one pass. + + android_sdk = + "${android_sdk_root}/platforms/android-${android_sdk_platform_version}" + android_sdk_build_tools = + "${android_sdk_root}/build-tools/$android_sdk_build_tools_version" + + # Path to the SDK's android.jar + android_sdk_jar = "$android_sdk/android.jar" + + # Location of libgcc. This is only needed for the current GN toolchain, so we + # only need to define the current one, rather than one for every platform + # like the toolchain roots. + if (target_cpu == "x86") { + android_prebuilt_arch = "android-x86" + _binary_prefix = "i686-linux-android" + } else if (target_cpu == "arm") { + android_prebuilt_arch = "android-arm" + _binary_prefix = "arm-linux-androideabi" + } else if (target_cpu == "mipsel") { + android_prebuilt_arch = "android-mips" + _binary_prefix = "mipsel-linux-android" + } else if (target_cpu == "x64") { + android_prebuilt_arch = "android-x86_64" + _binary_prefix = "x86_64-linux-android" + } else if (target_cpu == "arm64") { + android_prebuilt_arch = "android-arm64" + _binary_prefix = "aarch64-linux-android" + } else if (target_cpu == "mips64el") { + android_prebuilt_arch = "android-mips64" + _binary_prefix = "mips64el-linux-android" + } else { + assert(false, "Need android libgcc support for your target arch.") + } + + android_toolchain_root = "$android_ndk_root/toolchains/llvm/prebuilt/${android_host_os}-${android_host_arch}" + android_tool_prefix = "$android_toolchain_root/bin/$_binary_prefix-" + android_readelf = "${android_tool_prefix}readelf" + android_objcopy = "${android_tool_prefix}objcopy" + android_gdbserver = + "$android_ndk_root/prebuilt/$android_prebuilt_arch/gdbserver/gdbserver" + + android_sdk_tools_bundle_aapt2 = "${android_sdk_tools_bundle_aapt2_dir}/aapt2" +} diff --git a/third_party/libwebrtc/build/config/android/copy_ex.gni b/third_party/libwebrtc/build/config/android/copy_ex.gni new file mode 100644 index 0000000000..d3705dd7ef --- /dev/null +++ b/third_party/libwebrtc/build/config/android/copy_ex.gni @@ -0,0 +1,72 @@ +# Copyright 2019 The Chromium 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 list of file into a destination directory. Potentially renaming +# files are they are copied. This also ensures that symlinks are followed +# during the copy (i.e. the symlinks are never copied, only their content). +# +# Variables: +# dest: Destination directory path. +# sources: List of source files or directories to copy to dest. +# renaming_sources: Optional list of source file paths that will be renamed +# during the copy operation. If provided, renaming_destinations is required. +# renaming_destinations: Optional list of destination file paths, required +# when renaming_sources is provided. Both lists should have the same size +# and matching entries. +# args: Optional. Additionnal arguments to the copy_ex.py script. +# +# The following variables have the usual GN meaning: data, deps, inputs, +# outputs, testonly, visibility. + +import("//build/config/python.gni") + +template("copy_ex") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + [ + "data", + "deps", + "public_deps", + "testonly", + "visibility", + ]) + sources = [] + if (defined(invoker.sources)) { + sources += invoker.sources + } + outputs = [] + if (defined(invoker.outputs)) { + outputs += invoker.outputs + } + if (defined(invoker.inputs)) { + inputs = invoker.inputs + } + + script = "//build/android/gyp/copy_ex.py" + + args = [ + "--dest", + rebase_path(invoker.dest, root_build_dir), + ] + rebased_sources = rebase_path(sources, root_build_dir) + args += [ "--files=$rebased_sources" ] + + if (defined(invoker.args)) { + args += invoker.args + } + + if (defined(invoker.renaming_sources) && + defined(invoker.renaming_destinations)) { + sources += invoker.renaming_sources + renaming_destinations = invoker.renaming_destinations + outputs += + get_path_info(rebase_path(renaming_destinations, ".", invoker.dest), + "abspath") + rebased_renaming_sources = + rebase_path(invoker.renaming_sources, root_build_dir) + args += [ "--renaming-sources=$rebased_renaming_sources" ] + args += [ "--renaming-destinations=$renaming_destinations" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/android/extract_unwind_tables.gni b/third_party/libwebrtc/build/config/android/extract_unwind_tables.gni new file mode 100644 index 0000000000..5444c5b972 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/extract_unwind_tables.gni @@ -0,0 +1,44 @@ +# Copyright 2018 The Chromium 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") + +template("unwind_table_asset") { + # Note: This file name is used in multiple monochrome build scripts. + _asset_path = "${target_gen_dir}/${target_name}/unwind_cfi_32" + _unwind_action = "${target_name}__extract" + + action(_unwind_action) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _root_dir = "$root_out_dir" + if (defined(android_secondary_abi_cpu)) { + _root_dir = get_label_info(":foo($android_secondary_abi_toolchain)", + "root_out_dir") + } + + script = "//build/android/gyp/extract_unwind_tables.py" + outputs = [ _asset_path ] + inputs = [ "${_root_dir}/lib.unstripped/$shlib_prefix${invoker.library_target}$shlib_extension" ] + + args = [ + "--input_path", + rebase_path( + "${_root_dir}/lib.unstripped/$shlib_prefix${invoker.library_target}$shlib_extension", + root_build_dir), + "--output_path", + rebase_path(_asset_path, root_build_dir), + "--dump_syms_path", + rebase_path("$root_out_dir/dump_syms", root_build_dir), + ] + deps = invoker.deps + deps += [ "//third_party/breakpad:dump_syms" ] + } + android_assets(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + sources = [ _asset_path ] + disable_compression = true + deps = [ ":$_unwind_action" ] + } +} diff --git a/third_party/libwebrtc/build/config/android/internal_rules.gni b/third_party/libwebrtc/build/config/android/internal_rules.gni new file mode 100644 index 0000000000..3ed306a9e7 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/internal_rules.gni @@ -0,0 +1,4368 @@ +# 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. + +# Do not add any imports to non-//build directories here. +# Some projects (e.g. V8) do not have non-build directories DEPS'ed in. +import("//build/config/android/config.gni") +import("//build/config/android/copy_ex.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/compute_inputs_for_analyze.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/python.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/kythe.gni") +import("//build/util/generate_wrapper.gni") +import("//build_overrides/build.gni") +if (current_toolchain == default_toolchain) { + import("//build/toolchain/concurrent_links.gni") +} +assert(is_android) + +# The following _java_*_types variables capture all the existing target types. +# If a new type is introduced, please add it to one of these categories, +# preferring the more specific resource/library types. +_java_resource_types = [ + "android_assets", + "android_resources", +] + +_java_library_types = [ + "java_library", + "system_java_library", + "android_app_bundle_module", +] + +# These are leaf java target types. They cannot be passed as deps to other +# targets. Thus their naming schemes are not enforced. +_java_leaf_types = [ + "android_apk", + "android_app_bundle", + "dist_aar", + "dist_jar", + "java_annotation_processor", + "java_binary", + "junit_binary", +] + +# All _java_resource_types targets must conform to these patterns. +_java_resource_patterns = [ + "*:*_assets", + "*android*:assets", + "*:*_apk_*resources", + "*android*:resources", + "*:*_resources", + "*:*_grd", + "*:*locale_paks", + "*:*_java_strings", + "*:*strings_java", +] + +# All _java_library_types targets must conform to these patterns. This includes +# all non-leaf targets that use java_library_impl. +_java_library_patterns = [ + "*:*_java", + "*:*_javalib", + "*:*_java_*", # e.g. chrome_java_test_support + "*:java", + "*:junit", + "*:junit_*", + "*:*_junit_*", + "*:*javatests", + "*:*_bundle_module", + + # TODO(agrieve): Rename targets below to match above patterns. + "//android_webview/glue:glue", +] + +# These identify all non-leaf targets that have .build_config.json files. This is the +# set of patterns that other targets can use to filter out java targets. +java_target_patterns = _java_library_patterns + _java_resource_patterns + +_r8_path = "//third_party/r8/lib/r8.jar" +_custom_d8_path = "//third_party/r8/custom_d8.jar" +_desugar_jdk_libs_json = "//third_party/r8/desugar_jdk_libs.json" +_desugar_jdk_libs_jar = "//third_party/android_deps/libs/com_android_tools_desugar_jdk_libs/desugar_jdk_libs-1.1.1.jar" +_desugar_jdk_libs_configuration_jar = "//third_party/android_deps/libs/com_android_tools_desugar_jdk_libs_configuration/desugar_jdk_libs_configuration-1.1.1.jar" +_desugar_runtime_jar = "$root_build_dir/obj/third_party/bazel/desugar/Desugar_runtime.processed.jar" + +_dexdump_path = "$android_sdk_build_tools/dexdump" +_dexlayout_path = "//third_party/android_build_tools/art/dexlayout" +_profman_path = "//third_party/android_build_tools/art/profman" +_art_lib_file_names = [ + "libartbase.so", + "libart-compiler.so", + "libart-dexlayout.so", + "libart-disassembler.so", + "libart-gtest.so", + "libart.so", + "libbacktrace.so", + "libbase.so", + "libcrypto-host.so", + "libc++.so", + "libcutils.so", + "libdexfile.so", + "libexpat-host.so", + "libicui18n-host.so", + "libicuuc-host.so", + "libjavacore.so", + "libjavacrypto.so", + "liblog.so", + "liblz4.so", + "liblzma.so", + "libnativebridge.so", + "libnativehelper.so", + "libnativeloader.so", + "libopenjdkjvm.so", + "libopenjdkjvmti.so", + "libopenjdk.so", + "libprofile.so", + "libsigchain.so", + "libssl-host.so", + "libunwindstack.so", + "libvixl-arm64.so", + "libvixl-arm.so", + "libvixld-arm64.so", + "libvixld-arm.so", + "libz-host.so", + "libziparchive.so", + "slicer.so", +] +_default_art_libs = [] +foreach(lib, _art_lib_file_names) { + _default_art_libs += [ "//third_party/android_build_tools/art/lib/$lib" ] +} + +# Put the bug number in the target name so that false-positives have a hint in +# the error message about why non-existent dependencies are there. +build_config_target_suffix = "__build_config_crbug_908819" + +# Write the target's .build_config.json file. This is a json file that contains a +# dictionary of information about how to build this target (things that +# require knowledge about this target's dependencies and cannot be calculated +# at gn-time). There is a special syntax to add a value in that dictionary to +# an action/action_foreachs args: +# --python-arg=@FileArg($rebased_build_config_path:key0:key1) +# At runtime, such an arg will be replaced by the value in the build_config. +# See build/android/gyp/write_build_config.py and +# build/android/gyp/util/build_utils.py:ExpandFileArgs +template("write_build_config") { + _type = invoker.type + _parent_invoker = invoker.invoker + _target_label = + get_label_info(":${_parent_invoker.target_name}", "label_no_toolchain") + + # Ensure targets match naming patterns so that __assetres, __header, __impl + # targets work properly. Those generated targets allow for effective deps + # filtering. + if (filter_exclude([ _type ], _java_resource_types) == []) { + if (filter_exclude([ _target_label ], _java_resource_patterns) != []) { + assert(false, "Invalid java resource target name: $_target_label") + } + } else if (filter_exclude([ _type ], _java_library_types) == []) { + if (filter_exclude([ _target_label ], _java_library_patterns) != [] || + filter_exclude([ _target_label ], _java_resource_patterns) == []) { + assert(false, "Invalid java library target name: $_target_label") + } + } else if (_type == "group") { + if (filter_exclude([ _target_label ], java_target_patterns) != []) { + assert(false, "Invalid java target name: $_target_label") + } + } else if (filter_exclude([ _type ], _java_leaf_types) != []) { + assert(false, "This java type needs a category: $_type") + } + + if (defined(invoker.public_target_label)) { + _target_label = invoker.public_target_label + } + + action_with_pydeps(target_name) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + if (!defined(deps)) { + deps = [] + } + if (defined(invoker.android_manifest_dep)) { + deps += [ invoker.android_manifest_dep ] + } + + script = "//build/android/gyp/write_build_config.py" + depfile = "$target_gen_dir/$target_name.d" + inputs = [] + outputs = [ invoker.build_config ] + + _deps_configs = [] + _public_deps_configs = [] + if (defined(invoker.possible_config_deps)) { + foreach(_possible_dep, invoker.possible_config_deps) { + _dep_label = get_label_info(_possible_dep, "label_no_toolchain") + if (filter_exclude([ _dep_label ], java_target_patterns) == []) { + # Put the bug number in the target name so that false-positives + # have a hint in the error message about non-existent dependencies. + deps += [ "$_dep_label$build_config_target_suffix" ] + _dep_gen_dir = get_label_info(_possible_dep, "target_gen_dir") + _dep_name = get_label_info(_possible_dep, "name") + _dep_config = "$_dep_gen_dir/$_dep_name.build_config.json" + + _deps_configs += [ _dep_config ] + if (defined(invoker.possible_config_public_deps)) { + if (filter_include([ _possible_dep ], + invoker.possible_config_public_deps) != []) { + _public_deps_configs += [ _dep_config ] + } + } + } + } + } + _rebased_deps_configs = rebase_path(_deps_configs, root_build_dir) + _rebased_public_deps_configs = + rebase_path(_public_deps_configs, root_build_dir) + + args = [ + "--type=$_type", + "--depfile", + rebase_path(depfile, root_build_dir), + "--deps-configs=$_rebased_deps_configs", + "--public-deps-configs=$_rebased_public_deps_configs", + "--build-config", + rebase_path(invoker.build_config, root_build_dir), + "--gn-target", + _target_label, + ] + + if (defined(invoker.ignore_dependency_public_deps) && + invoker.ignore_dependency_public_deps) { + args += [ "--ignore-dependency-public-deps" ] + } + + if (defined(invoker.aar_path)) { + args += [ + "--aar-path", + rebase_path(invoker.aar_path, root_build_dir), + ] + } + + if (defined(invoker.chromium_code) && !invoker.chromium_code) { + # Default to chromium code if invoker did not pass anything. + args += [ "--non-chromium-code" ] + } + + if (defined(invoker.device_jar_path)) { + args += [ + "--device-jar-path", + rebase_path(invoker.device_jar_path, root_build_dir), + ] + } + if (defined(invoker.host_jar_path)) { + args += [ + "--host-jar-path", + rebase_path(invoker.host_jar_path, root_build_dir), + ] + } + if (defined(invoker.unprocessed_jar_path)) { + args += [ + "--unprocessed-jar-path", + rebase_path(invoker.unprocessed_jar_path, root_build_dir), + ] + } + if (defined(invoker.ijar_path)) { + args += [ + "--interface-jar-path", + rebase_path(invoker.ijar_path, root_build_dir), + ] + } + if (defined(invoker.java_resources_jar)) { + args += [ + "--java-resources-jar-path", + rebase_path(invoker.java_resources_jar, root_build_dir), + ] + } + if (defined(invoker.annotation_processor_deps) && + invoker.annotation_processor_deps != []) { + _processor_configs = [] + foreach(_processor_dep, invoker.annotation_processor_deps) { + _dep_label = get_label_info(_processor_dep, "label_no_toolchain") + _dep_gen_dir = get_label_info(_processor_dep, "target_gen_dir") + _dep_name = get_label_info(_processor_dep, "name") + deps += [ "$_dep_label$build_config_target_suffix" ] + _processor_configs += [ "$_dep_gen_dir/$_dep_name.build_config.json" ] + } + _rebased_processor_configs = + rebase_path(_processor_configs, root_build_dir) + args += [ "--annotation-processor-configs=$_rebased_processor_configs" ] + } + + # Dex path for library targets, or the the intermediate library for apks. + if (defined(invoker.dex_path)) { + args += [ + "--dex-path", + rebase_path(invoker.dex_path, root_build_dir), + ] + } + + # Dex path for the final apk. + if (defined(invoker.final_dex_path)) { + args += [ + "--final-dex-path", + rebase_path(invoker.final_dex_path, root_build_dir), + ] + } + if (defined(invoker.supports_android) && invoker.supports_android) { + args += [ "--supports-android" ] + } + if (defined(invoker.requires_android) && invoker.requires_android) { + args += [ "--requires-android" ] + } + if (defined(invoker.is_prebuilt) && invoker.is_prebuilt) { + args += [ "--is-prebuilt" ] + } + if (defined(invoker.bypass_platform_checks) && + invoker.bypass_platform_checks) { + args += [ "--bypass-platform-checks" ] + } + + if (defined(invoker.apk_under_test)) { + deps += [ "${invoker.apk_under_test}$build_config_target_suffix" ] + apk_under_test_gen_dir = + get_label_info(invoker.apk_under_test, "target_gen_dir") + apk_under_test_name = get_label_info(invoker.apk_under_test, "name") + apk_under_test_config = + "$apk_under_test_gen_dir/$apk_under_test_name.build_config.json" + args += [ + "--tested-apk-config", + rebase_path(apk_under_test_config, root_build_dir), + ] + } + + if (defined(invoker.asset_sources)) { + _rebased_asset_sources = + rebase_path(invoker.asset_sources, root_build_dir) + args += [ "--asset-sources=$_rebased_asset_sources" ] + } + if (defined(invoker.asset_renaming_sources)) { + _rebased_asset_renaming_sources = + rebase_path(invoker.asset_renaming_sources, root_build_dir) + args += [ "--asset-renaming-sources=$_rebased_asset_renaming_sources" ] + + # These are zip paths, so no need to rebase. + args += [ + "--asset-renaming-destinations=${invoker.asset_renaming_destinations}", + ] + } + if (defined(invoker.disable_compression) && invoker.disable_compression) { + args += [ "--disable-asset-compression" ] + } + if (defined(invoker.treat_as_locale_paks) && invoker.treat_as_locale_paks) { + args += [ "--treat-as-locale-paks" ] + } + + if (defined(invoker.merged_android_manifest)) { + args += [ + "--merged-android-manifest", + rebase_path(invoker.merged_android_manifest, root_build_dir), + ] + } + if (defined(invoker.android_manifest)) { + inputs += [ invoker.android_manifest ] + args += [ + "--android-manifest", + rebase_path(invoker.android_manifest, root_build_dir), + ] + } + if (defined(invoker.resources_zip)) { + args += [ + "--resources-zip", + rebase_path(invoker.resources_zip, root_build_dir), + ] + } + + if (defined(invoker.resource_overlay) && invoker.resource_overlay) { + args += [ "--resource-overlay" ] + } + + if (defined(invoker.custom_package)) { + args += [ + "--package-name", + invoker.custom_package, + ] + } + if (defined(invoker.r_text)) { + args += [ + "--r-text-path", + rebase_path(invoker.r_text, root_build_dir), + ] + } + if (defined(invoker.res_size_info_path)) { + args += [ + "--res-size-info", + rebase_path(invoker.res_size_info_path, root_build_dir), + ] + } + if (defined(invoker.res_sources_path)) { + _res_sources_path = rebase_path(invoker.res_sources_path, root_build_dir) + args += [ "--res-sources-path=$_res_sources_path" ] + } + if (defined(invoker.proto_resources_path)) { + _rebased_proto_resources = + rebase_path(invoker.proto_resources_path, root_build_dir) + args += [ "--apk-proto-resources=$_rebased_proto_resources" ] + } + if (defined(invoker.r_text_path)) { + _rebased_rtxt_path = rebase_path(invoker.r_text_path, root_build_dir) + args += [ "--r-text-path=$_rebased_rtxt_path" ] + } + if (defined(invoker.module_pathmap_path)) { + _rebased_pathmap_path = + rebase_path(invoker.module_pathmap_path, root_build_dir) + args += [ "--module-pathmap-path=$_rebased_pathmap_path" ] + } + + if (defined(invoker.shared_libraries_runtime_deps_file)) { + # Don't list shared_libraries_runtime_deps_file as an input in order to + # avoid having to depend on the runtime_deps target. See comment in + # rules.gni for why we do this. + args += [ + "--shared-libraries-runtime-deps", + rebase_path(invoker.shared_libraries_runtime_deps_file, root_build_dir), + ] + } + + if (defined(invoker.base_allowlist_rtxt_path)) { + args += [ + "--base-allowlist-rtxt-path", + rebase_path(invoker.base_allowlist_rtxt_path, root_build_dir), + ] + } + + if (defined(invoker.is_base_module) && invoker.is_base_module) { + args += [ "--is-base-module" ] + } + + if (defined(invoker.loadable_modules)) { + _rebased_loadable_modules = + rebase_path(invoker.loadable_modules, root_build_dir) + args += [ "--loadable-modules=$_rebased_loadable_modules" ] + } + + if (defined(invoker.secondary_abi_shared_libraries_runtime_deps_file)) { + # Don't list secondary_abi_shared_libraries_runtime_deps_file as an + # input in order to avoid having to depend on the runtime_deps target. + # See comment in rules.gni for why we do this. + args += [ + "--secondary-abi-shared-libraries-runtime-deps", + rebase_path(invoker.secondary_abi_shared_libraries_runtime_deps_file, + root_build_dir), + ] + } + + if (defined(invoker.secondary_abi_loadable_modules) && + invoker.secondary_abi_loadable_modules != []) { + _rebased_secondary_abi_loadable_modules = + rebase_path(invoker.secondary_abi_loadable_modules, root_build_dir) + args += [ "--secondary-abi-loadable-modules=$_rebased_secondary_abi_loadable_modules" ] + } + + if (defined(invoker.native_lib_placeholders) && + invoker.native_lib_placeholders != []) { + args += [ "--native-lib-placeholders=${invoker.native_lib_placeholders}" ] + } + + if (defined(invoker.secondary_native_lib_placeholders) && + invoker.secondary_native_lib_placeholders != []) { + args += [ "--secondary-native-lib-placeholders=${invoker.secondary_native_lib_placeholders}" ] + } + + if (defined(invoker.uncompress_shared_libraries) && + invoker.uncompress_shared_libraries) { + args += [ "--uncompress-shared-libraries" ] + } + + if (defined(invoker.library_always_compress)) { + args += [ "--library-always-compress=${invoker.library_always_compress}" ] + } + + if (defined(invoker.library_renames)) { + args += [ "--library-renames=${invoker.library_renames}" ] + } + + if (defined(invoker.apk_path)) { + # TODO(tiborg): Remove APK path from build config and use + # install_artifacts from metadata instead. + _rebased_apk_path = rebase_path(invoker.apk_path, root_build_dir) + args += [ "--apk-path=$_rebased_apk_path" ] + if (defined(invoker.incremental_apk_path)) { + _rebased_incremental_apk_path = + rebase_path(invoker.incremental_apk_path, root_build_dir) + _rebased_incremental_install_json_path = + rebase_path(invoker.incremental_install_json_path, root_build_dir) + args += [ + "--incremental-install-json-path=$_rebased_incremental_install_json_path", + "--incremental-apk-path=$_rebased_incremental_apk_path", + ] + } + } + + if (defined(invoker.java_sources_file)) { + args += [ + "--java-sources-file", + rebase_path(invoker.java_sources_file, root_build_dir), + ] + } + if (defined(invoker.srcjar)) { + args += [ + "--srcjar", + rebase_path(invoker.srcjar, root_build_dir), + ] + } + if (defined(invoker.bundled_srcjars)) { + _rebased_bundled_srcjars = + rebase_path(invoker.bundled_srcjars, root_build_dir) + args += [ "--bundled-srcjars=$_rebased_bundled_srcjars" ] + } + if (defined(invoker.proguard_enabled) && invoker.proguard_enabled) { + args += [ "--proguard-enabled" ] + } + if (defined(invoker.proguard_mapping_path)) { + _rebased_proguard_mapping_path = + rebase_path(invoker.proguard_mapping_path, root_build_dir) + args += [ "--proguard-mapping-path=$_rebased_proguard_mapping_path" ] + } + if (defined(invoker.input_jars_paths)) { + _rebased_input_jars_paths = + rebase_path(invoker.input_jars_paths, root_build_dir) + args += [ "--extra-classpath-jars=$_rebased_input_jars_paths" ] + } + if (defined(invoker.low_classpath_priority) && + invoker.low_classpath_priority) { + args += [ "--low-classpath-priority" ] + } + if (defined(invoker.mergeable_android_manifests)) { + _rebased_mergeable_android_manifests = + rebase_path(invoker.mergeable_android_manifests, root_build_dir) + args += [ + "--mergeable-android-manifests=$_rebased_mergeable_android_manifests", + ] + } + if (defined(invoker.proguard_configs)) { + _rebased_proguard_configs = + rebase_path(invoker.proguard_configs, root_build_dir) + args += [ "--proguard-configs=$_rebased_proguard_configs" ] + } + if (defined(invoker.static_library_dependent_targets)) { + _dependent_configs = [] + foreach(_dep, invoker.static_library_dependent_targets) { + _dep_name = _dep.name + _dep_label = get_label_info(_dep_name, "label_no_toolchain") + deps += [ "$_dep_label$build_config_target_suffix" ] + _dep_gen_dir = get_label_info(_dep_name, "target_gen_dir") + _dep_name = get_label_info(_dep_name, "name") + _config = rebase_path("$_dep_gen_dir/$_dep_name.build_config.json", + root_build_dir) + _dependent_configs += [ _config ] + } + args += [ "--static-library-dependent-configs=$_dependent_configs" ] + } + if (defined(invoker.gradle_treat_as_prebuilt) && + invoker.gradle_treat_as_prebuilt) { + args += [ "--gradle-treat-as-prebuilt" ] + } + if (defined(invoker.main_class)) { + args += [ + "--main-class", + invoker.main_class, + ] + } + if (defined(invoker.base_module_target)) { + _base_label = + get_label_info(invoker.base_module_target, "label_no_toolchain") + _dep_gen_dir = get_label_info(_base_label, "target_gen_dir") + _dep_name = get_label_info(_base_label, "name") + deps += [ "$_base_label$build_config_target_suffix" ] + _base_module_build_config = "$_dep_gen_dir/$_dep_name.build_config.json" + inputs += [ _base_module_build_config ] + args += [ + "--base-module-build-config", + rebase_path(_base_module_build_config, root_build_dir), + ] + } + if (defined(invoker.module_build_configs)) { + inputs += invoker.module_build_configs + _rebased_configs = + rebase_path(invoker.module_build_configs, root_build_dir) + args += [ "--module-build-configs=$_rebased_configs" ] + } + if (defined(invoker.version_name)) { + args += [ + "--version-name", + invoker.version_name, + ] + } + if (defined(invoker.version_code)) { + args += [ + "--version-code", + invoker.version_code, + ] + } + if (defined(invoker.recursive_resource_deps) && + invoker.recursive_resource_deps) { + args += [ "--recursive-resource-deps" ] + } + if (current_toolchain != default_toolchain) { + # This has to be a built-time error rather than a GN assert because many + # packages have a mix of java and non-java targets. For example, the + # following would fail even though nothing depends on :bar(//baz): + # + # shared_library("foo") { + # } + # + # android_library("bar") { + # deps = [ ":foo(//baz)" ] + # assert(current_toolchain == default_toolchain) + # } + _msg = [ + "Tried to build an Android target in a non-default toolchain.", + "target: $_target_label", + "current_toolchain: $current_toolchain", + "default_toolchain: $default_toolchain", + ] + args += [ "--fail=$_msg" ] + } + } +} + +template("generate_android_wrapper") { + generate_wrapper(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + generator_script = "//build/android/gyp/generate_android_wrapper.py" + sources = [ + "//build/android/gyp/util/build_utils.py", + "//build/gn_helpers.py", + "//build/util/generate_wrapper.py", + ] + } +} + +template("generate_r_java") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + if (!defined(deps)) { + deps = [] + } + if (defined(invoker.possible_resource_deps)) { + foreach(_dep, invoker.possible_resource_deps) { + _target_label = get_label_info(_dep, "label_no_toolchain") + if (filter_exclude([ _target_label ], _java_library_patterns) == [] && + filter_exclude([ _target_label ], _java_resource_patterns) != []) { + # Depend on the java libraries' transitive __assetres target instead. + # This is required to ensure depending on java_groups works. + deps += [ "${_target_label}__assetres" ] + } else { + deps += [ _dep ] + } + } + } + depfile = "$target_gen_dir/${invoker.target_name}.d" + inputs = [ invoker.build_config ] + outputs = [ invoker.srcjar_path ] + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + script = "//build/android/gyp/create_r_java.py" + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--srcjar-out", + rebase_path(invoker.srcjar_path, root_build_dir), + "--deps-rtxts=@FileArg($_rebased_build_config:deps_info:dependency_r_txt_files)", + "--r-package=${invoker.package}", + ] + } +} + +# Generates a script in the build bin directory which runs the test +# target using the test runner script in build/android/test_runner.py. +template("test_runner_script") { + testonly = true + _test_name = invoker.test_name + _test_type = invoker.test_type + _incremental_apk = defined(invoker.incremental_apk) && invoker.incremental_apk + + _runtime_deps = + !defined(invoker.ignore_all_data_deps) || !invoker.ignore_all_data_deps + + if (_runtime_deps) { + # This runtime_deps file is used at runtime and thus cannot go in + # target_gen_dir. + _target_dir_name = get_label_info(":$target_name", "dir") + _runtime_deps_file = + "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.runtime_deps" + _runtime_deps_target = "${target_name}__write_deps" + group(_runtime_deps_target) { + forward_variables_from(invoker, + [ + "data", + "deps", + "public_deps", + ]) + data_deps = [] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + if (defined(invoker.additional_apks)) { + data_deps += invoker.additional_apks + } + write_runtime_deps = _runtime_deps_file + } + } + + if (defined(invoker.apk_under_test)) { + _install_artifacts_json = + "${target_gen_dir}/${target_name}.install_artifacts" + generated_file("${target_name}__install_artifacts") { + deps = [ invoker.apk_under_test ] + output_conversion = "json" + outputs = [ _install_artifacts_json ] + data_keys = [ "install_artifacts" ] + walk_keys = [ "install_artifacts_barrier" ] + rebase = root_build_dir + } + } + + generate_android_wrapper(target_name) { + wrapper_script = "$root_build_dir/bin/run_${_test_name}" + + executable = "//testing/test_env.py" + + if (defined(android_test_runner_script)) { + _runner_script = android_test_runner_script + } else { + _runner_script = "//build/android/test_runner.py" + } + + deps = [] + if (defined(invoker.deps)) { + deps = invoker.deps + } + data_deps = [ + "//build/android:test_runner_py", + "//testing:test_scripts_shared", + ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + data = [] + if (defined(invoker.data)) { + data += invoker.data + } + + executable_args = [ + "@WrappedPath(" + rebase_path(_runner_script, root_build_dir) + ")", + _test_type, + "--output-directory", + "@WrappedPath(.)", + ] + + if (_runtime_deps) { + deps += [ ":$_runtime_deps_target" ] + data += [ _runtime_deps_file ] + _rebased_runtime_deps_file = + rebase_path(_runtime_deps_file, root_build_dir) + executable_args += [ + "--runtime-deps-path", + "@WrappedPath(${_rebased_runtime_deps_file})", + ] + } + + # apk_target is not used for native executable tests + # (e.g. breakpad_unittests). + if (defined(invoker.apk_target)) { + assert(!defined(invoker.executable_dist_dir)) + deps += [ "${invoker.apk_target}$build_config_target_suffix" ] + _apk_build_config = + get_label_info(invoker.apk_target, "target_gen_dir") + "/" + + get_label_info(invoker.apk_target, "name") + ".build_config.json" + _rebased_apk_build_config = rebase_path(_apk_build_config, root_build_dir) + not_needed([ "_rebased_apk_build_config" ]) + } else if (_test_type == "gtest") { + assert( + defined(invoker.executable_dist_dir), + "Must define either apk_target or executable_dist_dir for test_runner_script()") + _rebased_executable_dist_dir = + rebase_path(invoker.executable_dist_dir, root_build_dir) + executable_args += [ + "--executable-dist-dir", + "@WrappedPath(${_rebased_executable_dist_dir})", + ] + } + + _device_test = true + if (_test_type == "gtest") { + assert(defined(invoker.test_suite)) + executable_args += [ + "--suite", + invoker.test_suite, + ] + if (use_clang_coverage) { + # Set a default coverage output directory (can be overridden by user + # passing the same flag). + _rebased_coverage_dir = + rebase_path("$root_out_dir/coverage", root_build_dir) + executable_args += [ + "--coverage-dir", + "@WrappedPath(${_rebased_coverage_dir})", + ] + } + } else if (_test_type == "instrumentation") { + _test_apk = "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:apk_path))" + if (_incremental_apk) { + _test_apk = "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:incremental_apk_path))" + } + _rebased_test_jar = rebase_path(invoker.test_jar, root_build_dir) + executable_args += [ + "--test-apk", + _test_apk, + "--test-jar", + "@WrappedPath(${_rebased_test_jar})", + ] + if (defined(invoker.apk_under_test)) { + if (_incremental_apk) { + deps += [ "${invoker.apk_under_test}$build_config_target_suffix" ] + _apk_under_test_build_config = + get_label_info(invoker.apk_under_test, "target_gen_dir") + "/" + + get_label_info(invoker.apk_under_test, "name") + + ".build_config.json" + _rebased_apk_under_test_build_config = + rebase_path(_apk_under_test_build_config, root_build_dir) + _apk_under_test = "@WrappedPath(@FileArg($_rebased_apk_under_test_build_config:deps_info:incremental_apk_path))" + } else { + deps += [ ":${target_name}__install_artifacts" ] + _rebased_install_artifacts_json = + rebase_path(_install_artifacts_json, root_build_dir) + _apk_under_test = + "@WrappedPath(@FileArg($_rebased_install_artifacts_json[]))" + } + executable_args += [ + "--apk-under-test", + _apk_under_test, + ] + } + if (defined(invoker.use_webview_provider)) { + deps += [ "${invoker.use_webview_provider}$build_config_target_suffix" ] + _build_config = + get_label_info(invoker.use_webview_provider, "target_gen_dir") + + "/" + get_label_info(invoker.use_webview_provider, "name") + + ".build_config.json" + _rebased_build_config = rebase_path(_build_config, root_build_dir) + executable_args += [ + "--use-webview-provider", + "@WrappedPath(@FileArg($_rebased_build_config:deps_info:apk_path))", + ] + } + if (defined(invoker.proguard_enabled) && invoker.proguard_enabled && + !_incremental_apk) { + executable_args += [ "--enable-java-deobfuscation" ] + } + if (use_jacoco_coverage) { + # Set a default coverage output directory (can be overridden by user + # passing the same flag). + _rebased_coverage_dir = + rebase_path("$root_out_dir/coverage", root_build_dir) + executable_args += [ + "--coverage-dir", + "@WrappedPath(${_rebased_coverage_dir})", + ] + } + } else if (_test_type == "junit") { + assert(defined(invoker.test_suite)) + _device_test = false + executable_args += [ + "--test-suite", + invoker.test_suite, + ] + + deps += [ ":${invoker.test_suite}$build_config_target_suffix" ] + _junit_binary_build_config = + "${target_gen_dir}/${invoker.test_suite}.build_config.json" + + _rebased_robolectric_runtime_deps_dir = + rebase_path("$root_build_dir/lib.java/third_party/robolectric", + root_build_dir) + _rebased_resource_apk = rebase_path(invoker.resource_apk, root_build_dir) + executable_args += [ + "--resource-apk", + "@WrappedPath(${_rebased_resource_apk})", + "--robolectric-runtime-deps-dir", + "@WrappedPath(${_rebased_robolectric_runtime_deps_dir})", + ] + if (use_jacoco_coverage) { + # Set a default coverage output directory (can be overridden by user + # passing the same flag). + _rebased_coverage_dir = + rebase_path("$root_out_dir/coverage", root_build_dir) + executable_args += [ + "--coverage-dir", + "@WrappedPath(${_rebased_coverage_dir})", + ] + } + } else if (_test_type == "linker") { + executable_args += [ + "--test-apk", + "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:apk_path))", + ] + } else { + assert(false, "Invalid test type: $_test_type.") + } + + if (defined(invoker.additional_apks)) { + foreach(additional_apk, invoker.additional_apks) { + deps += [ "$additional_apk$build_config_target_suffix" ] + _build_config = + get_label_info(additional_apk, "target_gen_dir") + "/" + + get_label_info(additional_apk, "name") + ".build_config.json" + _rebased_build_config = rebase_path(_build_config, root_build_dir) + executable_args += [ + "--additional-apk", + "@WrappedPath(@FileArg($_rebased_build_config:deps_info:apk_path))", + ] + } + } + if (defined(invoker.shard_timeout)) { + executable_args += [ "--shard-timeout=${invoker.shard_timeout}" ] + } + if (_incremental_apk) { + executable_args += [ + "--test-apk-incremental-install-json", + "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:incremental_install_json_path))", + ] + if (defined(invoker.apk_under_test)) { + executable_args += [ + "--apk-under-test-incremental-install-json", + "@WrappedPath(@FileArg($_rebased_apk_under_test_build_config:deps_info:incremental_install_json_path))", + ] + } + executable_args += [ "--fast-local-dev" ] + } + if (_device_test && is_asan) { + executable_args += [ "--tool=asan" ] + } + + if (defined(invoker.modules)) { + foreach(module, invoker.modules) { + executable_args += [ + "--module", + module, + ] + } + } + + if (defined(invoker.fake_modules)) { + foreach(fake_module, invoker.fake_modules) { + executable_args += [ + "--fake-module", + fake_module, + ] + } + } + + if (defined(invoker.additional_locales)) { + foreach(locale, invoker.additional_locales) { + executable_args += [ + "--additional-locale", + locale, + ] + } + } + + if (defined(invoker.extra_args)) { + executable_args += invoker.extra_args + } + } +} + +if (enable_java_templates) { + template("android_lint") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + if (!defined(deps)) { + deps = [] + } + + # https://crbug.com/1098752 Fix for bot OOM (https://crbug.com/1098333). + if (defined(java_cmd_pool_size)) { + pool = "//build/config/android:java_cmd_pool($default_toolchain)" + } else { + pool = "//build/toolchain:link_pool($default_toolchain)" + } + + # Lint requires generated sources and generated resources from the build. + # Turbine __header targets depend on all generated sources, and the + # __assetres targets depend on all generated resources. + if (defined(invoker.deps)) { + foreach(_dep, invoker.deps) { + _target_label = get_label_info(_dep, "label_no_toolchain") + if (filter_exclude([ _target_label ], _java_library_patterns) == [] && + filter_exclude([ _target_label ], _java_resource_patterns) != + []) { + deps += [ + "${_target_label}__assetres", + "${_target_label}__header", + ] + } else { + # Keep non-java deps as they may generate files used only by lint. + # e.g. generated suppressions.xml files. + deps += [ _dep ] + } + } + } + + if (defined(invoker.min_sdk_version)) { + _min_sdk_version = invoker.min_sdk_version + } else { + _min_sdk_version = default_min_sdk_version + } + + _lint_binary_path = "$lint_android_sdk_root/cmdline-tools/latest/bin/lint" + _cache_dir = "$root_build_dir/android_lint_cache" + + # Save generated xml files in a consistent location for debugging. + _lint_gen_dir = "$target_gen_dir/$target_name" + _backported_methods = "//third_party/r8/backported_methods.txt" + + script = "//build/android/gyp/lint.py" + depfile = "$target_gen_dir/$target_name.d" + inputs = [ + _lint_binary_path, + _backported_methods, + ] + + args = [ + "--target-name", + get_label_info(":${target_name}", "label_no_toolchain"), + "--depfile", + rebase_path(depfile, root_build_dir), + "--lint-binary-path", + rebase_path(_lint_binary_path, root_build_dir), + "--cache-dir", + rebase_path(_cache_dir, root_build_dir), + "--lint-gen-dir", + rebase_path(_lint_gen_dir, root_build_dir), + "--android-sdk-version=${lint_android_sdk_version}", + "--min-sdk-version=$_min_sdk_version", + "--android-sdk-root", + rebase_path(lint_android_sdk_root, root_build_dir), + "--backported-methods", + rebase_path(_backported_methods, root_build_dir), + ] + + if (defined(invoker.skip_build_server) && invoker.skip_build_server) { + # Nocompile tests need lint to fail through ninja. + args += [ "--skip-build-server" ] + } + + if (defined(invoker.lint_suppressions_file)) { + inputs += [ invoker.lint_suppressions_file ] + + args += [ + "--config-path", + rebase_path(invoker.lint_suppressions_file, root_build_dir), + ] + } + + if (defined(testonly) && testonly) { + # Allows us to ignore unnecessary checks when linting test targets. + args += [ "--testonly" ] + } + + if (defined(invoker.manifest_package)) { + args += [ "--manifest-package=${invoker.manifest_package}" ] + } + + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + + if (defined(invoker.lint_baseline_file)) { + if (compute_inputs_for_analyze) { + # The baseline file is included in lint.py as a depfile dep. Since + # removing it regenerates the file, it is useful to not have this as + # a gn input during local development. Add it only for bots' analyze. + inputs += [ invoker.lint_baseline_file ] + } + args += [ + # Baseline allows us to turn on lint warnings without fixing all the + # pre-existing issues. This stops the flood of new issues while the + # existing ones are being fixed. + "--baseline", + rebase_path(invoker.lint_baseline_file, root_build_dir), + ] + } + + if (defined(invoker.create_cache) && invoker.create_cache) { + # Putting the stamp file in the cache dir allows us to depend on ninja + # to create the cache dir for us. + _stamp_path = "$_cache_dir/build.lint.stamp" + args += [ "--create-cache" ] + } else { + _stamp_path = "$target_out_dir/$target_name/build.lint.stamp" + deps += [ + "//build/android:prepare_android_lint_cache", + invoker.build_config_dep, + ] + inputs += [ invoker.build_config ] + _rebased_build_config = + rebase_path(invoker.build_config, root_build_dir) + + args += [ + "--manifest-path=@FileArg($_rebased_build_config:deps_info:lint_android_manifest)", + "--extra-manifest-paths=@FileArg($_rebased_build_config:deps_info:lint_extra_android_manifests)", + + # Lint requires all source and all resource files to be passed in the + # same invocation for checks like UnusedResources. + "--java-sources=@FileArg($_rebased_build_config:deps_info:lint_java_sources)", + "--aars=@FileArg($_rebased_build_config:deps_info:lint_aars)", + "--srcjars=@FileArg($_rebased_build_config:deps_info:lint_srcjars)", + "--resource-sources=@FileArg($_rebased_build_config:deps_info:lint_resource_sources)", + "--resource-zips=@FileArg($_rebased_build_config:deps_info:lint_resource_zips)", + + # The full classpath is required for annotation checks like @IntDef. + "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)", + ] + } + + outputs = [ _stamp_path ] + args += [ + "--stamp", + rebase_path(_stamp_path, root_build_dir), + ] + } + } + + template("proguard") { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + "public_deps", + ]) + _script = "//build/android/gyp/proguard.py" + _deps = invoker.deps + + _inputs = [ + invoker.build_config, + _r8_path, + ] + if (defined(invoker.inputs)) { + _inputs += invoker.inputs + } + if (defined(invoker.proguard_mapping_path)) { + _mapping_path = invoker.proguard_mapping_path + } else { + _mapping_path = "${invoker.output_path}.mapping" + } + + _enable_jdk_library_desugaring = enable_jdk_library_desugaring + if (defined(invoker.supports_jdk_library_desugaring) && + !invoker.supports_jdk_library_desugaring) { + _enable_jdk_library_desugaring = false + } + + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + _args = [ + "--mapping-output", + rebase_path(_mapping_path, root_build_dir), + "--classpath", + "@FileArg($_rebased_build_config:deps_info:proguard_classpath_jars)", + "--classpath", + "@FileArg($_rebased_build_config:android:sdk_jars)", + "--r8-path", + rebase_path(_r8_path, root_build_dir), + ] + if (treat_warnings_as_errors) { + _args += [ "--warnings-as-errors" ] + } + if (defined(invoker.desugar_jars_paths)) { + _rebased_desugar_jars_paths = + rebase_path(invoker.desugar_jars_paths, root_build_dir) + args += [ "--classpath=${_rebased_desugar_jars_paths}" ] + } + + if ((!defined(invoker.proguard_enable_obfuscation) || + invoker.proguard_enable_obfuscation) && enable_proguard_obfuscation) { + _proguard_sourcefile_suffix = "" + if (defined(invoker.proguard_sourcefile_suffix)) { + _proguard_sourcefile_suffix = "-${invoker.proguard_sourcefile_suffix}" + } + + # This is generally the apk name, and serves to identify the mapping + # file that would be required to deobfuscate a stacktrace. + _mapping_id = get_path_info(_mapping_path, "name") + _args += [ + "--enable-obfuscation", + "--sourcefile", + "chromium-${_mapping_id}${_proguard_sourcefile_suffix}", + ] + } else if (defined(invoker.proguard_sourcefile_suffix)) { + not_needed(invoker, [ "proguard_sourcefile_suffix" ]) + } + + if (defined(invoker.modules)) { + foreach(_feature_module, invoker.modules) { + _rebased_module_build_config = + rebase_path(_feature_module.build_config, root_build_dir) + _args += [ + "--feature-name=${_feature_module.name}", + "--dex-dest=@FileArg($_rebased_module_build_config:final_dex:path)", + "--feature-jars=@FileArg($_rebased_module_build_config:deps_info:device_classpath)", + ] + if (defined(_feature_module.uses_split)) { + _args += [ "--uses-split=${_feature_module.name}:${_feature_module.uses_split}" ] + } + _deps += [ _feature_module.build_config_target ] + } + _stamp = "${target_gen_dir}/${target_name}.r8.stamp" + _outputs = [ _stamp ] + _output_arg = [ + "--stamp", + rebase_path(_stamp, root_build_dir), + ] + } else { + # We don't directly set the output arg on the _args variable since it is + # shared with the expectation target that uses its own stamp file and + # does not take an --output-path. + _output_arg = [ + "--output-path", + rebase_path(invoker.output_path, root_build_dir), + ] + _outputs = [ invoker.output_path ] + } + _outputs += [ _mapping_path ] + + if (defined(invoker.disable_r8_outlining) && invoker.disable_r8_outlining) { + _args += [ "--disable-outlining" ] + } + + if (defined(invoker.enable_proguard_checks) && + !invoker.enable_proguard_checks) { + _args += [ "--disable-checks" ] + } + + if (defined(invoker.is_static_library) && invoker.is_static_library) { + _args += [ + "--extra-mapping-output-paths", + "@FileArg($_rebased_build_config:deps_info:static_library_proguard_mapping_output_paths)", + ] + } + + if (_enable_jdk_library_desugaring) { + _args += [ + "--desugar-jdk-libs-json", + rebase_path(_desugar_jdk_libs_json, root_build_dir), + ] + _inputs += [ _desugar_jdk_libs_json ] + + _args += [ + "--desugar-jdk-libs-jar", + rebase_path(_desugar_jdk_libs_jar, root_build_dir), + "--desugar-jdk-libs-configuration-jar", + rebase_path(_desugar_jdk_libs_configuration_jar, root_build_dir), + ] + _inputs += [ + _desugar_jdk_libs_jar, + _desugar_jdk_libs_configuration_jar, + ] + + _desugared_library_keep_rule_output_path = + "$target_gen_dir/$target_name.desugared_library_keep_rules.flags" + _args += [ + "--desugared-library-keep-rule-output", + rebase_path(_desugared_library_keep_rule_output_path, root_build_dir), + ] + } + _ignore_desugar_missing_deps = + defined(invoker.ignore_desugar_missing_deps) && + invoker.ignore_desugar_missing_deps + if (!_ignore_desugar_missing_deps) { + _args += [ "--show-desugar-default-interface-warnings" ] + } + + if (enable_java_asserts) { + # The default for generating dex file format is + # --force-disable-assertions. + _args += [ "--force-enable-assertions" ] + } + + if (defined(invoker.args)) { + _args += invoker.args + } + + if (defined(invoker.expected_proguard_config)) { + _expectations_target = + "${invoker.top_target_name}_validate_proguard_config" + action_with_pydeps(_expectations_target) { + script = _script + + # Need to depend on all deps so that proguard.txt within .aar files get + # extracted. + deps = _deps + depfile = "${target_gen_dir}/${target_name}.d" + inputs = [ + invoker.build_config, + invoker.expected_proguard_config, + ] + _actual_file = "$target_gen_dir/$target_name.proguard_configs" + _failure_file = + "$expectations_failure_dir/" + + string_replace(invoker.expected_proguard_config, "/", "_") + outputs = [ + _actual_file, + _failure_file, + ] + args = _args + [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--failure-file", + rebase_path(_failure_file, root_build_dir), + "--expected-file", + rebase_path(invoker.expected_proguard_config, root_build_dir), + "--actual-file", + rebase_path(_actual_file, root_build_dir), + "--only-verify-expectations", + ] + if (defined(invoker.expected_proguard_config_base)) { + inputs += [ invoker.expected_proguard_config_base ] + args += [ + "--expected-file-base", + rebase_path(invoker.expected_proguard_config_base, root_build_dir), + ] + } + if (fail_on_android_expectations) { + args += [ "--fail-on-expectations" ] + } + } + _deps += [ ":$_expectations_target" ] + } + action_with_pydeps(target_name) { + script = _script + deps = _deps + inputs = _inputs + outputs = _outputs + depfile = "${target_gen_dir}/${target_name}.d" + args = _args + _output_arg + [ + "--depfile", + rebase_path(depfile, root_build_dir), + ] + + # http://crbug.com/725224. Fix for bots running out of memory. + if (defined(java_cmd_pool_size)) { + pool = "//build/config/android:java_cmd_pool($default_toolchain)" + } else { + pool = "//build/toolchain:link_pool($default_toolchain)" + } + } + } + + # Generates a script in the build bin directory to run a java binary. + # + # Variables + # main_class: The class containing the program entry point. + # build_config: Path to .build_config.json for the jar (contains classpath). + # script_name: Name of the script to generate. + # wrapper_script_args: List of extra arguments to pass to the executable. + # tiered_stop_at_level_one: Whether to pass --tiered-stop-at-level-one + # + template("java_binary_script") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + + _main_class = invoker.main_class + _build_config = invoker.build_config + _script_name = invoker.script_name + + script = "//build/android/gyp/create_java_binary_script.py" + inputs = [ _build_config ] + _java_script = "$root_build_dir/bin/$_script_name" + outputs = [ _java_script ] + _rebased_build_config = rebase_path(_build_config, root_build_dir) + args = [ + "--output", + rebase_path(_java_script, root_build_dir), + "--main-class", + _main_class, + ] + args += [ + "--classpath=@FileArg($_rebased_build_config:deps_info:host_classpath)", + ] + + if (use_jacoco_coverage) { + args += [ + "--classpath", + rebase_path("//third_party/jacoco/lib/jacocoagent.jar", + root_build_dir), + ] + } + if (use_jacoco_coverage || !treat_warnings_as_errors) { + args += [ "--noverify" ] + } + if (defined(invoker.tiered_stop_at_level_one) && + invoker.tiered_stop_at_level_one) { + args += [ "--tiered-stop-at-level-one" ] + } + if (defined(invoker.wrapper_script_args)) { + args += [ "--" ] + invoker.wrapper_script_args + } + } + } + + # Variables + # apply_mapping: The path to the ProGuard mapping file to apply. + # disable_incremental: Disable incremental dexing. + template("dex") { + _min_sdk_version = default_min_sdk_version + if (defined(invoker.min_sdk_version)) { + _min_sdk_version = invoker.min_sdk_version + } + + _proguard_enabled = + defined(invoker.proguard_enabled) && invoker.proguard_enabled + _is_dex_merging = defined(invoker.input_dex_filearg) + _enable_multidex = + !defined(invoker.enable_multidex) || invoker.enable_multidex + _enable_main_dex_list = _enable_multidex && _min_sdk_version < 21 + _enable_desugar = !defined(invoker.enable_desugar) || invoker.enable_desugar + _use_classic_desugar = + defined(invoker.use_classic_desugar) && invoker.use_classic_desugar + _desugar_needs_classpath = _enable_desugar && !_use_classic_desugar + + # It's not safe to dex merge with libraries dex'ed at higher api versions. + assert(!_is_dex_merging || _min_sdk_version >= default_min_sdk_version) + + # For D8's backported method desugaring to work properly, the dex merge step + # must not be set to a higher minSdkVersion than it was for the libraries. + if (_enable_desugar && _is_dex_merging) { + _min_sdk_version = default_min_sdk_version + } + + assert(defined(invoker.output) || + (_proguard_enabled && defined(invoker.modules))) + assert(!_proguard_enabled || !(defined(invoker.input_dex_filearg) || + defined(invoker.input_classes_filearg) || + defined(invoker.input_class_jars)), + "Cannot explicitly set inputs when proguarding a dex.") + + # Dex merging should not also be dexing. + assert(!(_is_dex_merging && defined(invoker.input_classes_filearg))) + assert(!(_is_dex_merging && defined(invoker.input_class_jars))) + + assert(!(defined(invoker.apply_mapping) && !_proguard_enabled), + "apply_mapping can only be specified if proguard is enabled.") + + if (_enable_main_dex_list) { + _main_dex_rules = "//build/android/main_dex_classes.flags" + } + + if (_desugar_needs_classpath || _proguard_enabled) { + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + } + + if (_proguard_enabled) { + _proguard_target_name = target_name + + proguard(_proguard_target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "build_config", + "data", + "data_deps", + "deps", + "desugar_jars_paths", + "disable_r8_outlining", + "enable_proguard_checks", + "expected_proguard_config", + "expected_proguard_config_base", + "ignore_desugar_missing_deps", + "is_static_library", + "modules", + "proguard_enable_obfuscation", + "proguard_mapping_path", + "proguard_sourcefile_suffix", + "supports_jdk_library_desugaring", + "top_target_name", + ]) + inputs = [] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + if (defined(invoker.proguard_configs)) { + inputs += invoker.proguard_configs + } + + args = [ + "--proguard-configs=@FileArg($_rebased_build_config:deps_info:proguard_all_configs)", + "--min-api=$_min_sdk_version", + ] + if (defined(invoker.has_apk_under_test) && invoker.has_apk_under_test) { + args += [ "--input-paths=@FileArg($_rebased_build_config:deps_info:device_classpath_extended)" ] + } else { + args += [ "--input-paths=@FileArg($_rebased_build_config:deps_info:device_classpath)" ] + } + if (_use_classic_desugar) { + deps += [ "//third_party/bazel/desugar:desugar_runtime_java" ] + inputs += [ _desugar_runtime_jar ] + args += [ + "--input-paths", + rebase_path(_desugar_runtime_jar, root_build_dir), + ] + } + if (defined(invoker.proguard_args)) { + args += invoker.proguard_args + } + + if (defined(invoker.apply_mapping)) { + _rebased_apply_mapping_path = + rebase_path(invoker.apply_mapping, root_build_dir) + args += [ "--apply-mapping=$_rebased_apply_mapping_path" ] + } + + if (_enable_main_dex_list) { + if (defined(invoker.extra_main_dex_proguard_config)) { + args += [ + "--main-dex-rules-path", + rebase_path(invoker.extra_main_dex_proguard_config, + root_build_dir), + ] + inputs += [ invoker.extra_main_dex_proguard_config ] + } + args += [ + "--main-dex-rules-path", + rebase_path(_main_dex_rules, root_build_dir), + ] + inputs += [ _main_dex_rules ] + } + + if (defined(invoker.output)) { + output_path = invoker.output + } else if (!defined(proguard_mapping_path)) { + proguard_mapping_path = "$target_out_dir/$target_name.mapping" + } + } + } else { # !_proguard_enabled + _is_library = defined(invoker.is_library) && invoker.is_library + _input_class_jars = [] + if (defined(invoker.input_class_jars)) { + _input_class_jars = invoker.input_class_jars + } + _deps = invoker.deps + + if (!_is_library && _use_classic_desugar) { + # It would be more efficient to use the pre-dex'ed copy of the runtime, + # but it's easier to add it in this way. + _deps += [ "//third_party/bazel/desugar:desugar_runtime_java" ] + _input_class_jars += [ _desugar_runtime_jar ] + } + if (_input_class_jars != []) { + _rebased_input_class_jars = + rebase_path(_input_class_jars, root_build_dir) + } + + action_with_pydeps(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + ]) + script = "//build/android/gyp/dex.py" + deps = _deps + depfile = "$target_gen_dir/$target_name.d" + outputs = [ invoker.output ] + inputs = [ + _r8_path, + _custom_d8_path, + ] + + if (!_is_library) { + # http://crbug.com/725224. Fix for bots running out of memory. + if (defined(java_cmd_pool_size)) { + pool = "//build/config/android:java_cmd_pool($default_toolchain)" + } else { + pool = "//build/toolchain:link_pool($default_toolchain)" + } + } + + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--output", + rebase_path(outputs[0], root_build_dir), + "--min-api=$_min_sdk_version", + "--r8-jar-path", + rebase_path(_r8_path, root_build_dir), + "--custom-d8-jar-path", + rebase_path(_custom_d8_path, root_build_dir), + + # Uncomment when rebuilding custom_d8.jar. + #"--skip-custom-d8", + ] + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + + if (enable_incremental_d8 && !(defined(invoker.disable_incremental) && + invoker.disable_incremental)) { + # Don't use incremental dexing for ProGuarded inputs as a precaution. + args += [ + "--incremental-dir", + rebase_path("$target_out_dir/$target_name", root_build_dir), + ] + } + + if (_enable_multidex) { + args += [ "--multi-dex" ] + if (_enable_main_dex_list) { + if (defined(invoker.extra_main_dex_proguard_config)) { + args += [ + "--main-dex-rules-path", + rebase_path(invoker.extra_main_dex_proguard_config, + root_build_dir), + ] + inputs += [ invoker.extra_main_dex_proguard_config ] + } + args += [ + "--main-dex-rules-path", + rebase_path(_main_dex_rules, root_build_dir), + ] + inputs += [ _main_dex_rules ] + } + } + if (_is_library) { + args += [ "--library" ] + } + if (defined(invoker.input_dex_filearg)) { + inputs += [ invoker.build_config ] + args += [ "--dex-inputs-filearg=${invoker.input_dex_filearg}" ] + } + if (defined(invoker.input_classes_filearg)) { + inputs += [ invoker.build_config ] + args += [ "--class-inputs-filearg=${invoker.input_classes_filearg}" ] + } + if (_input_class_jars != []) { + inputs += _input_class_jars + args += [ "--class-inputs=${_rebased_input_class_jars}" ] + } + + if (defined(invoker.dexlayout_profile)) { + args += [ + "--dexlayout-profile", + rebase_path(invoker.dexlayout_profile, root_build_dir), + "--dexlayout-path", + rebase_path(_dexlayout_path, root_build_dir), + "--profman-path", + rebase_path(_profman_path, root_build_dir), + "--dexdump-path", + rebase_path(_dexdump_path, root_build_dir), + ] + inputs += [ + _dexlayout_path, + _profman_path, + _dexdump_path, + invoker.dexlayout_profile, + ] + inputs += _default_art_libs + } + + # Never compile intemediates with --release in order to: + # 1) not require recompiles when toggling is_java_debug, + # 2) allow incremental_install=1 to still have local variable + # information even when is_java_debug=false. + if (!is_java_debug && !_is_library) { + args += [ "--release" ] + } + + if (_enable_desugar) { + args += [ "--desugar" ] + + # Passing the flag for dex merging causes invalid dex files to be created. + if (enable_jdk_library_desugaring && !_is_dex_merging) { + inputs += [ _desugar_jdk_libs_json ] + args += [ + "--desugar-jdk-libs-json", + rebase_path(_desugar_jdk_libs_json, root_build_dir), + ] + } + _ignore_desugar_missing_deps = + defined(invoker.ignore_desugar_missing_deps) && + invoker.ignore_desugar_missing_deps + if (!_ignore_desugar_missing_deps && !_use_classic_desugar) { + args += [ "--show-desugar-default-interface-warnings" ] + } + } + if (_desugar_needs_classpath) { + _desugar_dependencies_path = + "$target_gen_dir/$target_name.desugardeps" + args += [ + "--desugar-dependencies", + rebase_path(_desugar_dependencies_path, root_build_dir), + "--bootclasspath=@FileArg($_rebased_build_config:android:sdk_jars)", + + # Pass the full classpath to find new dependencies that are not in + # the .desugardeps file. + "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)", + ] + if (defined(invoker.desugar_jars_paths)) { + _rebased_desugar_jars_paths = + rebase_path(invoker.desugar_jars_paths, root_build_dir) + args += [ "--classpath=${_rebased_desugar_jars_paths}" ] + } + if (defined(invoker.final_ijar_path)) { + # Need to include the input .interface.jar on the classpath in order to make + # jar_excluded_patterns classes visible to desugar. + args += [ + "--classpath", + rebase_path(invoker.final_ijar_path, root_build_dir), + ] + inputs += [ invoker.final_ijar_path ] + } + } else { + not_needed(invoker, [ "desugar_jars_paths" ]) + } + + if (enable_java_asserts) { + # The default for generating dex file format is + # --force-disable-assertions. + args += [ "--force-enable-assertions" ] + } + } + } + } + + # Variables + # output: Path to output ".l8.dex". + # min_sdk_version: The minimum Android SDK version this target supports. + template("dex_jdk_libs") { + action_with_pydeps(target_name) { + script = "//build/android/gyp/dex_jdk_libs.py" + inputs = [ + _r8_path, + _desugar_jdk_libs_json, + _desugar_jdk_libs_jar, + _desugar_jdk_libs_configuration_jar, + ] + outputs = [ invoker.output ] + args = [ + "--r8-path", + rebase_path(_r8_path, root_build_dir), + "--desugar-jdk-libs-json", + rebase_path(_desugar_jdk_libs_json, root_build_dir), + "--desugar-jdk-libs-jar", + rebase_path(_desugar_jdk_libs_jar, root_build_dir), + "--desugar-jdk-libs-configuration-jar", + rebase_path(_desugar_jdk_libs_configuration_jar, root_build_dir), + "--output", + rebase_path(invoker.output, root_build_dir), + "--min-api=${invoker.min_sdk_version}", + ] + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + } + } + + template("jacoco_instr") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "deps", + "public_deps", + ]) + + # The name needs to match the SOURCES_JSON_FILES_SUFFIX in + # generate_coverage_metadata_for_java.py. + _sources_json_file = "$target_out_dir/${target_name}__jacoco_sources.json" + _jacococli_jar = "//third_party/jacoco/lib/jacococli.jar" + + script = "//build/android/gyp/jacoco_instr.py" + inputs = invoker.java_files + [ + _jacococli_jar, + invoker.input_jar_path, + ] + outputs = [ + _sources_json_file, + invoker.output_jar_path, + ] + args = [ + "--input-path", + rebase_path(invoker.input_jar_path, root_build_dir), + "--output-path", + rebase_path(invoker.output_jar_path, root_build_dir), + "--sources-json-file", + rebase_path(_sources_json_file, root_build_dir), + "--java-sources-file", + rebase_path(invoker.java_sources_file, root_build_dir), + "--jacococli-jar", + rebase_path(_jacococli_jar, root_build_dir), + ] + if (coverage_instrumentation_input_file != "") { + args += [ + "--files-to-instrument", + rebase_path(coverage_instrumentation_input_file, root_build_dir), + ] + } + } + } + + template("filter_jar") { + action_with_pydeps(target_name) { + script = "//build/android/gyp/filter_zip.py" + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + inputs = [ invoker.input_jar ] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + outputs = [ invoker.output_jar ] + + _jar_excluded_patterns = [] + if (defined(invoker.jar_excluded_patterns)) { + _jar_excluded_patterns = invoker.jar_excluded_patterns + } + _jar_included_patterns = [] + if (defined(invoker.jar_included_patterns)) { + _jar_included_patterns = invoker.jar_included_patterns + } + args = [ + "--input", + rebase_path(invoker.input_jar, root_build_dir), + "--output", + rebase_path(invoker.output_jar, root_build_dir), + "--exclude-globs=${_jar_excluded_patterns}", + "--include-globs=${_jar_included_patterns}", + ] + } + } + + template("process_java_library") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _deps = invoker.jar_deps + _previous_output_jar = invoker.input_jar_path + + if (invoker.is_device_jar && invoker.enable_desugar) { + _desugar_target = "${target_name}_device__desugar" + _desugar_output_jar = "$target_out_dir/$target_name.desugar.jar" + + action_with_pydeps(_desugar_target) { + script = "//build/android/gyp/desugar.py" + deps = _deps + invoker.classpath_deps + depfile = "$target_gen_dir/$target_name.d" + _rebased_build_config = + rebase_path(invoker.build_config, root_build_dir) + _desugar_jar = "//third_party/bazel/desugar/Desugar.jar" + + inputs = [ + invoker.build_config, + _previous_output_jar, + _desugar_jar, + ] + outputs = [ _desugar_output_jar ] + args = [ + "--desugar-jar", + rebase_path(_desugar_jar, root_build_dir), + "--input-jar", + rebase_path(_previous_output_jar, root_build_dir), + "--output-jar", + rebase_path(_desugar_output_jar, root_build_dir), + + # Temporarily using java_full_interface_classpath until classpath validation of targets + # is implemented, see http://crbug.com/885273 + "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)", + "--bootclasspath=@FileArg($_rebased_build_config:android:sdk_interface_jars)", + "--depfile", + rebase_path(depfile, root_build_dir), + ] + if (defined(invoker.desugar_jars_paths)) { + _rebased_desugar_jars_paths = + rebase_path(invoker.desugar_jars_paths, root_build_dir) + args += [ "--classpath=${_rebased_desugar_jars_paths}" ] + } + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + } + + _deps = [] + _deps = [ ":$_desugar_target" ] + _previous_output_jar = _desugar_output_jar + } + + if (invoker.jacoco_instrument) { + _filter_jar_target_name = "${target_name}__filter_jar" + _filter_jar_output_jar = "$target_out_dir/$target_name.filter.jar" + } else { + _filter_jar_target_name = target_name + _filter_jar_output_jar = invoker.output_jar_path + } + + filter_jar(_filter_jar_target_name) { + forward_variables_from(invoker, + [ + "jar_excluded_patterns", + "jar_included_patterns", + ]) + deps = _deps + input_jar = _previous_output_jar + output_jar = _filter_jar_output_jar + } + + if (invoker.jacoco_instrument) { + # Jacoco must run after desugar (or else desugar sometimes fails). + # It must run after filtering to avoid the same (filtered) class mapping + # to multiple .jar files. + # We run offline code coverage processing here rather than with a + # javaagent as the desired coverage data was not being generated. + # See crbug.com/1097815. + jacoco_instr(target_name) { + deps = [ ":$_filter_jar_target_name" ] + invoker.jar_deps + forward_variables_from(invoker, + [ + "java_files", + "java_sources_file", + ]) + + input_jar_path = _filter_jar_output_jar + output_jar_path = invoker.output_jar_path + } + } + } + + template("bytecode_processor") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + _bytecode_checker_script = "$root_build_dir/bin/helper/bytecode_processor" + script = "//build/android/gyp/bytecode_processor.py" + inputs = [ + invoker.build_config, + invoker.input_jar, + _bytecode_checker_script, + ] + outputs = [ "$target_out_dir/$target_name.bytecode.stamp" ] + deps = + invoker.deps + + [ "//build/android/bytecode:bytecode_processor($default_toolchain)" ] + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + args = [ + "--target-name", + get_label_info(":${target_name}", "label_no_toolchain"), + "--script", + rebase_path(_bytecode_checker_script, root_build_dir), + "--gn-target=${invoker.target_label}", + "--input-jar", + rebase_path(invoker.input_jar, root_build_dir), + "--stamp", + rebase_path(outputs[0], root_build_dir), + "--direct-classpath-jars=@FileArg($_rebased_build_config:javac:classpath)", + "--full-classpath-jars=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)", + "--full-classpath-gn-targets=@FileArg($_rebased_build_config:deps_info:javac_full_classpath_targets)", + ] + if (invoker.requires_android) { + args += [ "--sdk-classpath-jars=@FileArg($_rebased_build_config:android:sdk_jars)" ] + } + if (invoker.is_prebuilt) { + args += [ "--is-prebuilt" ] + } + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + if (defined(invoker.missing_classes_allowlist)) { + args += [ + "--missing-classes-allowlist=${invoker.missing_classes_allowlist}", + ] + } + } + } + + template("merge_manifests") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + script = "//build/android/gyp/merge_manifest.py" + depfile = "$target_gen_dir/$target_name.d" + + inputs = [ + invoker.build_config, + invoker.input_manifest, + ] + + outputs = [ invoker.output_manifest ] + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--android-sdk-cmdline-tools", + rebase_path("${public_android_sdk_root}/cmdline-tools/latest", + root_build_dir), + "--root-manifest", + rebase_path(invoker.input_manifest, root_build_dir), + "--output", + rebase_path(invoker.output_manifest, root_build_dir), + "--extras", + "@FileArg($_rebased_build_config:extra_android_manifests)", + "--min-sdk-version=${invoker.min_sdk_version}", + "--target-sdk-version=${invoker.target_sdk_version}", + ] + + if (defined(invoker.manifest_package)) { + args += [ "--manifest-package=${invoker.manifest_package}" ] + } + + if (defined(invoker.max_sdk_version)) { + args += [ "--max-sdk-version=${invoker.max_sdk_version}" ] + } + + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + } + } + + # This template is used to parse a set of resource directories and + # create the R.txt, .srcjar and .resources.zip for it. + # + # Input variables: + # deps: Specifies the input dependencies for this target. + # + # build_config: Path to the .build_config.json file corresponding to the target. + # + # sources: + # List of input resource files. + # + # custom_package: (optional) + # Package name for the generated R.java source file. Optional if + # android_manifest is not provided. + # + # android_manifest: (optional) + # If custom_package is not provided, path to an AndroidManifest.xml file + # that is only used to extract a package name out of it. + # + # r_text_in_path: (optional) + # Path to an input R.txt file to use to generate the R.java file. + # The default is to use 'aapt' to generate the file from the content + # of the resource directories. + # + # Output variables: + # resources_zip: + # Path to a .resources.zip that will simply contain all the + # input resources, collected in a single archive. + # + # r_text_out_path: Path for the generated R.txt file. + # + template("prepare_resources") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "deps", + "sources", + ]) + script = "//build/android/gyp/prepare_resources.py" + + depfile = "$target_gen_dir/${invoker.target_name}.d" + outputs = [ + invoker.resources_zip, + invoker.resources_zip + ".info", + invoker.r_text_out_path, + ] + + inputs = [ invoker.res_sources_path ] + + _rebased_res_sources_path = + rebase_path(invoker.res_sources_path, root_build_dir) + + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--res-sources-path=$_rebased_res_sources_path", + "--resource-zip-out", + rebase_path(invoker.resources_zip, root_build_dir), + "--r-text-out", + rebase_path(invoker.r_text_out_path, root_build_dir), + ] + + if (defined(invoker.r_text_in_path)) { + _r_text_in_path = invoker.r_text_in_path + inputs += [ _r_text_in_path ] + args += [ + "--r-text-in", + rebase_path(_r_text_in_path, root_build_dir), + ] + } + + if (defined(invoker.strip_drawables) && invoker.strip_drawables) { + args += [ "--strip-drawables" ] + } + if (defined(invoker.allow_missing_resources) && + invoker.allow_missing_resources) { + args += [ "--allow-missing-resources" ] + } + } + } + + # A template that is used to compile all resources needed by a binary + # (e.g. an android_apk or a junit_binary) into an intermediate .ar_ + # archive. It can also generate an associated .srcjar that contains the + # final R.java sources for all resource packages the binary depends on. + # + # Input variables: + # android_sdk_dep: The sdk dep that these resources should compile against. + # + # deps: Specifies the input dependencies for this target. + # + # build_config: Path to the .build_config.json file corresponding to the target. + # + # build_config_dep: Dep target to generate the .build_config.json file. + # + # android_manifest: Path to root manifest for the binary. + # + # version_code: (optional) + # + # version_name: (optional) + # + # shared_resources: (optional) + # If true, make all variables in each generated R.java file non-final, + # and provide an onResourcesLoaded() method that can be used to reset + # their package index at load time. Useful when the APK corresponds to + # a library that is loaded at runtime, like system_webview_apk or + # monochrome_apk. + # + # app_as_shared_lib: (optional) + # If true, same effect as shared_resources, but also ensures that the + # resources can be used by the APK when it is loaded as a regular + # application as well. Useful for the monochrome_public_apk target + # which is both an application and a shared runtime library that + # implements the system webview feature. + # + # shared_resources_allowlist: (optional) + # Path to an R.txt file. If provided, acts similar to shared_resources + # except that it restricts the list of non-final resource variables + # to the list from the input R.txt file. Overrides shared_resources + # when both are specified. + # + # shared_resources_allowlist_locales: (optional) + # If shared_resources_allowlist is used, provide an optional list of + # Chromium locale names to determine which localized shared string + # resources to put in the final output, even if aapt_locale_allowlist + # is defined to a smaller subset. + # + # aapt_locale_allowlist: (optional) + # Restrict compiled locale-dependent resources to a specific allowlist. + # NOTE: This is a list of Chromium locale names, not Android ones. + # + # r_java_root_package_name: (optional) + # Short package name for this target's root R java file (ex. input of + # "base" would become "gen.base_module" for the root R java package name). + # Optional as defaults to "base". + # + # resource_exclusion_regex: (optional) + # + # resource_exclusion_exceptions: (optional) + # + # resource_values_filter_rules: (optional) + # + # no_xml_namespaces: (optional) + # + # png_to_webp: (optional) + # If true, convert all PNG resources (except 9-patch files) to WebP. + # + # post_process_script: (optional) + # + # package_name: (optional) + # Name of the package for the purpose of creating R class. + # + # package_id: (optional) + # Use a custom package ID in resource IDs. + # + # arsc_package_name: (optional) + # Use this package name in the arsc file rather than the package name + # found in the AndroidManifest.xml. Does not affect the package name + # used in AndroidManifest.xml. + # + # resource_ids_provider_dep: (optional) + # Use resource IDs provided by another APK target when compiling resources + # (via. "aapt2 link --stable-ids") + # + # + # Output variables: + # arsc_output: Path to output .ap_ file (optional). + # + # proto_output: Path to output .proto.ap_ file (optional). + # + # r_text_out_path: (optional): + # Path for the corresponding generated R.txt file. + # + # proguard_file: (optional) + # Path to proguard configuration file for this apk target. + # + # proguard_file_main_dex: (optional) + # + template("compile_resources") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _deps = [ + invoker.android_sdk_dep, + invoker.build_config_dep, + ] + if (defined(invoker.android_manifest_dep)) { + _deps += [ invoker.android_manifest_dep ] + } + foreach(_dep, invoker.deps) { + _target_label = get_label_info(_dep, "label_no_toolchain") + if (filter_exclude([ _target_label ], _java_library_patterns) == [] && + filter_exclude([ _target_label ], _java_resource_patterns) != []) { + # Depend on the java libraries' transitive __assetres target instead. + _deps += [ "${_target_label}__assetres" ] + } else { + _deps += [ _dep ] + } + } + + if (defined(invoker.arsc_output)) { + _arsc_output = invoker.arsc_output + } + _final_srcjar_path = "${target_gen_dir}/${target_name}.srcjar" + + _script = "//build/android/gyp/compile_resources.py" + + _inputs = [ + invoker.build_config, + android_sdk_tools_bundle_aapt2, + ] + + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + + _args = [ + "--include-resources=@FileArg($_rebased_build_config:android:sdk_jars)", + "--aapt2-path", + rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir), + "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)", + "--extra-res-packages=@FileArg($_rebased_build_config:deps_info:extra_package_names)", + "--extra-main-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_main_r_text_files)", + "--min-sdk-version=${invoker.min_sdk_version}", + "--target-sdk-version=${invoker.target_sdk_version}", + "--webp-cache-dir=obj/android-webp-cache", + ] + + _inputs += [ invoker.android_manifest ] + _outputs = [ _final_srcjar_path ] + _args += [ + "--android-manifest", + rebase_path(invoker.android_manifest, root_build_dir), + "--srcjar-out", + rebase_path(_final_srcjar_path, root_build_dir), + ] + if (defined(invoker.no_xml_namespaces) && invoker.no_xml_namespaces) { + _args += [ "--no-xml-namespaces" ] + } + if (defined(invoker.version_code)) { + _args += [ + "--version-code", + invoker.version_code, + ] + } + if (defined(invoker.version_name)) { + _args += [ + "--version-name", + invoker.version_name, + ] + } + if (defined(_arsc_output)) { + _outputs += [ _arsc_output ] + _args += [ + "--arsc-path", + rebase_path(_arsc_output, root_build_dir), + ] + } + if (defined(invoker.proto_output)) { + _outputs += [ invoker.proto_output ] + _args += [ + "--proto-path", + rebase_path(invoker.proto_output, root_build_dir), + ] + } + if (defined(invoker.size_info_path)) { + _outputs += [ invoker.size_info_path ] + _args += [ + "--info-path", + rebase_path(invoker.size_info_path, root_build_dir), + ] + } + + if (defined(invoker.r_java_root_package_name)) { + _args += [ + "--r-java-root-package-name", + invoker.r_java_root_package_name, + ] + } + + # Useful to have android:debuggable in the manifest even for Release + # builds. Just omit it for officai + if (debuggable_apks) { + _args += [ "--debuggable" ] + } + + if (defined(invoker.r_text_out_path)) { + _outputs += [ invoker.r_text_out_path ] + _args += [ + "--r-text-out", + rebase_path(invoker.r_text_out_path, root_build_dir), + ] + } + + if (defined(invoker.rename_manifest_package)) { + _args += [ + "--rename-manifest-package", + invoker.rename_manifest_package, + ] + } + + # Define the flags related to shared resources. + # + # Note the small sanity check to ensure that the package ID of the + # generated resources table is correct. It should be 0x02 for runtime + # shared libraries, and 0x7f otherwise. + + if (defined(invoker.shared_resources) && invoker.shared_resources) { + _args += [ "--shared-resources" ] + } + if (defined(invoker.app_as_shared_lib) && invoker.app_as_shared_lib) { + _args += [ "--app-as-shared-lib" ] + } + if (defined(invoker.package_id)) { + _args += [ "--package-id=${invoker.package_id}" ] + } + if (defined(invoker.package_name)) { + _args += [ + "--package-name", + invoker.package_name, + ] + } + if (defined(invoker.arsc_package_name)) { + _args += [ + "--arsc-package-name", + invoker.arsc_package_name, + ] + } + + if (defined(invoker.shared_resources_allowlist)) { + _inputs += [ invoker.shared_resources_allowlist ] + _args += [ + "--shared-resources-allowlist", + rebase_path(invoker.shared_resources_allowlist, root_build_dir), + ] + } + if (defined(invoker.shared_resources_allowlist_locales)) { + _args += [ "--shared-resources-allowlist-locales=" + + "${invoker.shared_resources_allowlist_locales}" ] + } + + if (!defined(testonly) || !testonly || + (defined(invoker.enforce_resource_overlays_in_tests) && + invoker.enforce_resource_overlays_in_tests)) { + _args += [ "--dependencies-res-zip-overlays=@FileArg($_rebased_build_config:deps_info:dependency_zip_overlays)" ] + } else { + _args += [ "--dependencies-res-zip-overlays=@FileArg($_rebased_build_config:deps_info:dependency_zips)" ] + } + + if (defined(invoker.proguard_file)) { + _outputs += [ invoker.proguard_file ] + _args += [ + "--proguard-file", + rebase_path(invoker.proguard_file, root_build_dir), + ] + } + + if (defined(invoker.proguard_file_main_dex)) { + _outputs += [ invoker.proguard_file_main_dex ] + _args += [ + "--proguard-file-main-dex", + rebase_path(invoker.proguard_file_main_dex, root_build_dir), + ] + } + + if (defined(invoker.aapt_locale_allowlist)) { + _args += [ "--locale-allowlist=${invoker.aapt_locale_allowlist}" ] + } + if (defined(invoker.png_to_webp) && invoker.png_to_webp) { + _webp_target = "//third_party/libwebp:cwebp($host_toolchain)" + _webp_binary = get_label_info(_webp_target, "root_out_dir") + "/cwebp" + _deps += [ _webp_target ] + _inputs += [ _webp_binary ] + _args += [ + "--png-to-webp", + "--webp-binary", + rebase_path(_webp_binary, root_build_dir), + ] + } + if (defined(invoker.resource_exclusion_regex)) { + _args += + [ "--resource-exclusion-regex=${invoker.resource_exclusion_regex}" ] + if (defined(invoker.resource_exclusion_exceptions)) { + _args += [ "--resource-exclusion-exceptions=${invoker.resource_exclusion_exceptions}" ] + } + } + if (defined(invoker.resource_values_filter_rules)) { + _args += + [ "--values-filter-rules=${invoker.resource_values_filter_rules}" ] + } + + if (defined(invoker.include_resource)) { + _rebased_include_resources = + rebase_path(invoker.include_resource, root_build_dir) + _args += [ "--include-resources=$_rebased_include_resources" ] + } + + if (defined(invoker._args)) { + _args += invoker._args + } + + if (defined(invoker.emit_ids_out_path)) { + _outputs += [ invoker.emit_ids_out_path ] + _rebased_emit_ids_path = + rebase_path(invoker.emit_ids_out_path, root_out_dir) + _args += [ "--emit-ids-out=$_rebased_emit_ids_path" ] + } + + if (defined(invoker.resource_ids_provider_dep)) { + _compile_res_dep = + "${invoker.resource_ids_provider_dep}__compile_resources" + _gen_dir = get_label_info(_compile_res_dep, "target_gen_dir") + _name = get_label_info(_compile_res_dep, "name") + _resource_ids_path = "$_gen_dir/$_name.resource_ids" + _inputs += [ _resource_ids_path ] + _rebased_ids_path = rebase_path(_resource_ids_path, root_out_dir) + _args += [ "--use-resource-ids-path=$_rebased_ids_path" ] + _deps += [ _compile_res_dep ] + } + + if (defined(invoker.max_sdk_version)) { + _max_sdk_version = invoker.max_sdk_version + _args += [ "--max-sdk-version=$_max_sdk_version" ] + } + + if (defined(invoker.manifest_package)) { + _args += [ "--manifest-package=${invoker.manifest_package}" ] + } + + if (defined(invoker.is_bundle_module) && invoker.is_bundle_module) { + _args += [ "--is-bundle-module" ] + } + + if (defined(invoker.uses_split)) { + assert(invoker.is_bundle_module) + _args += [ "--uses-split=${invoker.uses_split}" ] + } + + if (defined(invoker.expected_android_manifest)) { + _expectations_target = + "${invoker.top_target_name}_validate_android_manifest" + action_with_pydeps(_expectations_target) { + _actual_file = "${invoker.android_manifest}.normalized" + _failure_file = + "$expectations_failure_dir/" + + string_replace(invoker.expected_android_manifest, "/", "_") + inputs = [ + invoker.android_manifest, + invoker.build_config, + invoker.expected_android_manifest, + ] + outputs = [ + _actual_file, + _failure_file, + ] + deps = [ + invoker.android_manifest_dep, + invoker.build_config_dep, + ] + script = _script + args = _args + [ + "--expected-file", + rebase_path(invoker.expected_android_manifest, root_build_dir), + "--actual-file", + rebase_path(_actual_file, root_build_dir), + "--failure-file", + rebase_path(_failure_file, root_build_dir), + "--only-verify-expectations", + ] + if (defined(invoker.expected_android_manifest_base)) { + args += [ + "--expected-file-base", + rebase_path(invoker.expected_android_manifest_base, root_build_dir), + ] + inputs += [ invoker.expected_android_manifest_base ] + } + if (fail_on_android_expectations) { + args += [ "--fail-on-expectations" ] + } + if (defined(invoker.extra_verification_manifest)) { + inputs += [ invoker.extra_verification_manifest ] + args += [ + "--extra-verification-manifest", + rebase_path(invoker.extra_verification_manifest, root_build_dir), + ] + if (defined(invoker.extra_verification_manifest_dep)) { + deps += [ invoker.extra_verification_manifest_dep ] + } + } + } + _deps += [ ":$_expectations_target" ] + } + + action_with_pydeps(target_name) { + script = _script + depfile = "$target_gen_dir/${target_name}.d" + inputs = _inputs + outputs = _outputs + deps = _deps + args = _args + [ + "--depfile", + rebase_path(depfile, root_build_dir), + ] + } + } + + # A template that is used to optimize compiled resources using aapt2 optimize. + # + # proto_input_path: + # Path to input compiled .proto.ap_ file. + # + # short_resource_paths: (optional) + # Rename the paths within a the apk to be randomly generated short + # strings to reduce binary size. + # + # strip_resource_names: (optional) + # Strip resource names from the resources table of the apk. + # + # resources_configs_paths: (optional) + # List of resource configs to use for optimization. + # + # optimized_proto_output: + # Path to output optimized .proto.ap_ file. + # + # resources_path_map_out_path: (optional): + # Path for the generated map between original resource paths and + # shortened resource paths. + template("optimize_resources") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + action_with_pydeps(target_name) { + forward_variables_from(invoker, [ "deps" ]) + script = "//build/android/gyp/optimize_resources.py" + outputs = [ invoker.optimized_proto_output ] + inputs = [ + android_sdk_tools_bundle_aapt2, + invoker.r_text_path, + invoker.proto_input_path, + ] + args = [ + "--aapt2-path", + rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir), + "--r-text-in", + rebase_path(invoker.r_text_path, root_build_dir), + "--proto-path", + rebase_path(invoker.proto_input_path, root_build_dir), + "--optimized-proto-path", + rebase_path(invoker.optimized_proto_output, root_build_dir), + ] + + if (defined(invoker.resources_config_paths)) { + inputs += invoker.resources_config_paths + _rebased_resource_configs = + rebase_path(invoker.resources_config_paths, root_build_dir) + args += [ "--resources-config-paths=${_rebased_resource_configs}" ] + } + + if (defined(invoker.short_resource_paths) && + invoker.short_resource_paths) { + args += [ "--short-resource-paths" ] + if (defined(invoker.resources_path_map_out_path)) { + outputs += [ invoker.resources_path_map_out_path ] + args += [ + "--resources-path-map-out-path", + rebase_path(invoker.resources_path_map_out_path, root_build_dir), + ] + } + } + + if (defined(invoker.strip_resource_names) && + invoker.strip_resource_names) { + args += [ "--strip-resource-names" ] + } + } + } + + # A template that is used to find unused resources. + template("unused_resources") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + script = "//build/android/gyp/unused_resources.py" + depfile = "$target_gen_dir/${target_name}.d" + _unused_resources_script = "$root_build_dir/bin/helper/unused_resources" + inputs = [ _unused_resources_script ] + outputs = [ invoker.output_config ] + if (!defined(deps)) { + deps = [] + } + deps += [ "//build/android/unused_resources:unused_resources" ] + _rebased_module_build_config = + rebase_path(invoker.build_config, root_build_dir) + args = [ + "--script", + rebase_path(_unused_resources_script, root_build_dir), + "--output-config", + rebase_path(invoker.output_config, root_build_dir), + "--r-text=@FileArg($_rebased_module_build_config:deps_info:r_text_path)", + "--dependencies-res-zips=@FileArg($_rebased_module_build_config:deps_info:dependency_zips)", + "--depfile", + rebase_path(depfile, root_build_dir), + ] + + if (defined(invoker.proguard_mapping_path)) { + inputs += [ invoker.proguard_mapping_path ] + args += [ + "--proguard-mapping", + rebase_path(invoker.proguard_mapping_path, root_build_dir), + ] + } + + foreach(_build_config, invoker.all_module_build_configs) { + inputs += [ _build_config ] + _rebased_build_config = rebase_path(_build_config, root_build_dir) + args += [ + "--dexes=@FileArg($_rebased_build_config:final_dex:path)", + "--android-manifests=@FileArg($_rebased_build_config:deps_info:merged_android_manifest)", + ] + } + } + } + + # Create an .jar.info file by merging several .jar.info files into one. + # + # Variables: + # build_config: Path to APK's build config file. Used to extract the + # list of input .jar files from its dependencies. + # name: Name of the apk or app bundle (e.g. "Foo.apk"). + # res_size_info_path: Path to input .ap_.info file (for apks). + # + template("create_size_info_files") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + script = "//build/android/gyp/create_size_info_files.py" + _jar_info_path = "$root_build_dir/size-info/${invoker.name}.jar.info" + _pak_info_path = "$root_build_dir/size-info/${invoker.name}.pak.info" + _res_info_path = "$root_build_dir/size-info/${invoker.name}.res.info" + outputs = [ + _jar_info_path, + _pak_info_path, + _res_info_path, + ] + depfile = "$target_gen_dir/$target_name.d" + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--jar-info-path", + rebase_path(_jar_info_path, root_build_dir), + "--pak-info-path", + rebase_path(_pak_info_path, root_build_dir), + "--res-info-path", + rebase_path(_res_info_path, root_build_dir), + ] + _is_bundle = defined(invoker.module_build_configs) + if (_is_bundle) { + inputs = invoker.module_build_configs + foreach(_build_config, invoker.module_build_configs) { + _rebased_build_config = rebase_path(_build_config, root_build_dir) + args += [ + "--jar-files=@FileArg($_rebased_build_config:deps_info:unprocessed_jar_path)", + "--jar-files=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)", + "--in-res-info-path=@FileArg($_rebased_build_config:deps_info:res_size_info)", + "--assets=@FileArg($_rebased_build_config:assets)", + "--uncompressed-assets=@FileArg($_rebased_build_config:uncompressed_assets)", + ] + } + } else { + inputs = [ + invoker.build_config, + invoker.res_size_info_path, + ] + _rebased_build_config = + rebase_path(invoker.build_config, root_build_dir) + args += [ + "--jar-files=@FileArg($_rebased_build_config:deps_info:unprocessed_jar_path)", + "--jar-files=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)", + "--in-res-info-path", + rebase_path(invoker.res_size_info_path, root_build_dir), + "--assets=@FileArg($_rebased_build_config:assets)", + "--uncompressed-assets=@FileArg($_rebased_build_config:uncompressed_assets)", + ] + } + } + } + + # Creates a signed and aligned .apk. + # + # Variables + # apk_name: (optional) APK name (without .apk suffix). If provided, will + # be used to generate .info files later used by the supersize tool. + # assets_build_config: Path to android_apk .build_config.json containing merged + # asset information. + # deps: Specifies the dependencies of this target. + # dex_path: Path to classes.dex file to include (optional). + # expected_libs_and_assets: Verify the list of included native libraries + # and assets is consistent with the given expectation file. + # expected_libs_and_assets_base: Treat expected_libs_and_assets as a diff + # with this file as the base. + # jdk_libs_dex: Path to classes.dex for desugar_jdk_libs. + # packaged_resources_path: Path to .ap_ to use. + # output_apk_path: Output path for the generated .apk. + # min_sdk_version: The minimum Android SDK version this target supports. + # native_lib_placeholders: List of placeholder filenames to add to the apk + # (optional). + # secondary_native_lib_placeholders: List of placeholder filenames to add to + # the apk for the secondary ABI (optional). + # loadable_modules: List of native libraries. + # native_libs_filearg: @FileArg() of additionally native libraries. + # secondary_abi_loadable_modules: (optional) List of native libraries for + # secondary ABI. + # secondary_abi_native_libs_filearg: (optional). @FileArg() of additional + # secondary ABI native libs. + # keystore_path: Path to keystore to use for signing. + # keystore_name: Key alias to use. + # keystore_password: Keystore password. + # uncompress_shared_libraries: (optional, default false) Whether to store + # native libraries inside the APK uncompressed and page-aligned. + template("package_apk") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "public_deps" ]) + _deps = invoker.deps + _native_lib_placeholders = [] + if (defined(invoker.native_lib_placeholders)) { + _native_lib_placeholders = invoker.native_lib_placeholders + } + _secondary_native_lib_placeholders = [] + if (defined(invoker.secondary_native_lib_placeholders)) { + _secondary_native_lib_placeholders = + invoker.secondary_native_lib_placeholders + } + + _script = "//build/android/gyp/apkbuilder.py" + _apksigner = "$android_sdk_build_tools/lib/apksigner.jar" + _zipalign = "$android_sdk_build_tools/zipalign" + + _inputs = [ + invoker.build_config, + invoker.keystore_path, + invoker.packaged_resources_path, + _apksigner, + _zipalign, + ] + + _outputs = [ invoker.output_apk_path ] + _data = [ invoker.output_apk_path ] + + _rebased_compiled_resources_path = + rebase_path(invoker.packaged_resources_path, root_build_dir) + _rebased_packaged_apk_path = + rebase_path(invoker.output_apk_path, root_build_dir) + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + _args = [ + "--resource-apk=$_rebased_compiled_resources_path", + "--output-apk=$_rebased_packaged_apk_path", + "--assets=@FileArg($_rebased_build_config:assets)", + "--uncompressed-assets=@FileArg($_rebased_build_config:uncompressed_assets)", + "--apksigner-jar", + rebase_path(_apksigner, root_build_dir), + "--zipalign-path", + rebase_path(_zipalign, root_build_dir), + "--key-path", + rebase_path(invoker.keystore_path, root_build_dir), + "--key-name", + invoker.keystore_name, + "--key-passwd", + invoker.keystore_password, + "--min-sdk-version=${invoker.min_sdk_version}", + + # TODO(mlopatkin) We are relying on the fact that build_config is an APK + # build_config. + "--java-resources=@FileArg($_rebased_build_config:java_resources_jars)", + ] + if (is_official_build) { + _args += [ "--best-compression" ] + } + if (defined(invoker.uncompress_dex) && invoker.uncompress_dex) { + _args += [ "--uncompress-dex" ] + } + if (defined(invoker.uncompress_shared_libraries) && + invoker.uncompress_shared_libraries) { + _args += [ "--uncompress-shared-libraries=True" ] + } + if (defined(invoker.library_always_compress)) { + _args += + [ "--library-always-compress=${invoker.library_always_compress}" ] + } + if (defined(invoker.library_renames)) { + _args += [ "--library-renames=${invoker.library_renames}" ] + } + if (defined(invoker.dex_path)) { + _inputs += [ invoker.dex_path ] + _args += [ + "--dex-file", + rebase_path(invoker.dex_path, root_build_dir), + ] + } + if (defined(invoker.jdk_libs_dex)) { + _inputs += [ invoker.jdk_libs_dex ] + _args += [ + "--jdk-libs-dex-file", + rebase_path(invoker.jdk_libs_dex, root_build_dir), + ] + } + if ((defined(invoker.loadable_modules) && invoker.loadable_modules != []) || + defined(invoker.native_libs_filearg) || + _native_lib_placeholders != []) { + _args += [ "--android-abi=$android_app_abi" ] + } + if (defined(android_app_secondary_abi)) { + _args += [ "--secondary-android-abi=$android_app_secondary_abi" ] + } + if (defined(invoker.loadable_modules) && invoker.loadable_modules != []) { + _inputs += invoker.loadable_modules + _rebased_loadable_modules = + rebase_path(invoker.loadable_modules, root_build_dir) + _args += [ "--native-libs=$_rebased_loadable_modules" ] + } + if (defined(invoker.native_libs_filearg)) { + _args += [ "--native-libs=${invoker.native_libs_filearg}" ] + } + if (_native_lib_placeholders != []) { + _args += [ "--native-lib-placeholders=$_native_lib_placeholders" ] + } + + if (defined(invoker.secondary_abi_native_libs_filearg)) { + _args += [ + "--secondary-native-libs=${invoker.secondary_abi_native_libs_filearg}", + ] + } + if (defined(invoker.secondary_abi_loadable_modules)) { + _rebased_secondary_abi_loadable_modules = + rebase_path(invoker.secondary_abi_loadable_modules, root_build_dir) + _args += + [ "--secondary-native-libs=$_rebased_secondary_abi_loadable_modules" ] + } + if (_secondary_native_lib_placeholders != []) { + _args += [ "--secondary-native-lib-placeholders=$_secondary_native_lib_placeholders" ] + } + if (treat_warnings_as_errors) { + _args += [ "--warnings-as-errors" ] + } + + if (defined(invoker.expected_libs_and_assets)) { + _expectations_target = + "${invoker.top_target_name}_validate_libs_and_assets" + action_with_pydeps(_expectations_target) { + _actual_file = "$target_gen_dir/$target_name.libs_and_assets" + _failure_file = + "$expectations_failure_dir/" + + string_replace(invoker.expected_libs_and_assets, "/", "_") + inputs = [ + invoker.build_config, + invoker.expected_libs_and_assets, + ] + deps = [ invoker.build_config_dep ] + outputs = [ + _actual_file, + _failure_file, + ] + script = _script + args = _args + [ + "--expected-file", + rebase_path(invoker.expected_libs_and_assets, root_build_dir), + "--actual-file", + rebase_path(_actual_file, root_build_dir), + "--failure-file", + rebase_path(_failure_file, root_build_dir), + "--only-verify-expectations", + ] + if (defined(invoker.expected_libs_and_assets_base)) { + inputs += [ invoker.expected_libs_and_assets_base ] + args += [ + "--expected-file-base", + rebase_path(invoker.expected_libs_and_assets_base, root_build_dir), + ] + } + if (fail_on_android_expectations) { + args += [ "--fail-on-expectations" ] + } + } + _deps += [ ":$_expectations_target" ] + } + action_with_pydeps(target_name) { + depfile = "$target_gen_dir/$target_name.d" + inputs = _inputs + deps = _deps + data = _data + outputs = _outputs + script = _script + args = _args + [ + "--depfile", + rebase_path(depfile, root_build_dir), + ] + } + } + + # Compile Java source files into a .jar file, potentially using an + # annotation processor, and/or the errorprone compiler. + # + # Note that the only way to specify custom annotation processors is + # by using build_config to point to a file that corresponds to a java-related + # target that includes javac:processor_classes entries (i.e. there is no + # variable here that can be used for this purpose). + # + # Note also the peculiar use of java_files / java_sources_file. The content + # of the java_files list and the java_sources_file file must match exactly. + # This rule uses java_files only to list the inputs to the action that + # calls compile_java.py, but will pass the list of Java source files + # with the '@${java_sources_file}" command-line syntax. Not a problem in + # practice since this is only called from java_library_impl() that sets up + # the variables properly. + # + # Variables: + # main_target_name: Used when extracting srcjars for codesearch. + # java_files: Optional list of Java source file paths. + # srcjar_deps: Optional list of .srcjar dependencies (not file paths). + # The corresponding source files they contain will be compiled too. + # java_sources_file: Optional path to file containing list of Java source + # file paths. This must always be provided if java_files is not empty + # and must match it exactly. + # build_config: Path to the .build_config.json file of the corresponding + # java_library_impl() target. The following entries will be used by this + # template: javac:srcjars, deps_info:javac_full_classpath, + # deps_info:javac_full_interface_classpath, javac:processor_classpath, + # javac:processor_classes + # javac_jar_path: Path to the final output .jar file. + # javac_args: Optional list of extra arguments to pass to javac. + # chromium_code: Whether this corresponds to Chromium-specific sources. + # requires_android: True if these sources can only run on Android. + # additional_jar_files: Optional list of files to copy into the resulting + # .jar file (by default, only .class files are put there). Each entry + # has the 'srcPath:dstPath' format. + # enable_errorprone: If True, use the errorprone compiler to check for + # error-prone constructs in the language. If not provided, whether this is + # enabled depends on chromium_code and the global + # use_errorprone_java_compiler variable. + # use_turbine: If True, compile headers using turbine.py. + # apk_name: Optional APK name. If provided, will tell compile_java.py to also + # generate an .apk.jar.info file under size-info/${apk_name}.apk.jar.info + # processor_args_javac: List of annotation processor arguments, each one + # will be passed to javac as -A. + # deps: Dependencies for the corresponding target. + # testonly: Usual meaning (should be True for test-only targets) + # + # [1] https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html + # + template("compile_java") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _build_config = invoker.build_config + _chromium_code = invoker.chromium_code + + _processor_args = [] + if (defined(invoker.processor_args_javac)) { + _processor_args = invoker.processor_args_javac + } + + _additional_jar_files = [] + if (defined(invoker.additional_jar_files)) { + _additional_jar_files = invoker.additional_jar_files + } + + _srcjar_deps = [] + if (defined(invoker.srcjar_deps)) { + _srcjar_deps += invoker.srcjar_deps + } + + _java_srcjars = [] + foreach(dep, _srcjar_deps) { + _dep_gen_dir = get_label_info(dep, "target_gen_dir") + _dep_name = get_label_info(dep, "name") + _java_srcjars += [ "$_dep_gen_dir/$_dep_name.srcjar" ] + } + + # generated_jar_path is an output when use_turbine and an input otherwise. + if (!invoker.use_turbine) { + _java_srcjars += [ invoker.generated_jar_path ] + } + + _javac_args = [] + if (defined(invoker.javac_args)) { + _javac_args = invoker.javac_args + } + + action_with_pydeps(target_name) { + if (invoker.use_turbine) { + script = "//build/android/gyp/turbine.py" + } else { + script = "//build/android/gyp/compile_java.py" + } + + if (target_name == "chrome_java__header") { + # Regression test for: https://crbug.com/1154302 + assert_no_deps = [ "//base:base_java__impl" ] + } + + depfile = "$target_gen_dir/$target_name.d" + deps = _srcjar_deps + if (defined(invoker.deps)) { + deps += invoker.deps + } + + outputs = [ invoker.output_jar_path ] + if (!invoker.enable_errorprone && !invoker.use_turbine) { + outputs += [ invoker.output_jar_path + ".info" ] + } + inputs = invoker.java_files + _java_srcjars + [ _build_config ] + if (invoker.java_files != []) { + inputs += [ invoker.java_sources_file ] + } + + _rebased_build_config = rebase_path(_build_config, root_build_dir) + _rebased_output_jar_path = + rebase_path(invoker.output_jar_path, root_build_dir) + _rebased_java_srcjars = rebase_path(_java_srcjars, root_build_dir) + _rebased_depfile = rebase_path(depfile, root_build_dir) + _rebased_generated_dir = rebase_path( + "$target_gen_dir/${invoker.main_target_name}/generated_java", + root_build_dir) + args = [ + "--depfile=$_rebased_depfile", + "--generated-dir=$_rebased_generated_dir", + "--jar-path=$_rebased_output_jar_path", + "--java-srcjars=$_rebased_java_srcjars", + "--target-name", + get_label_info(":${target_name}", "label_no_toolchain"), + ] + + if (defined(invoker.header_jar_path)) { + inputs += [ invoker.header_jar_path ] + args += [ + "--header-jar", + rebase_path(invoker.header_jar_path, root_build_dir), + ] + _header_jar_classpath = + [ rebase_path(invoker.header_jar_path, root_build_dir) ] + args += [ "--classpath=$_header_jar_classpath" ] + } + + if (invoker.use_turbine) { + # Prefer direct deps for turbine as much as possible. + args += [ "--classpath=@FileArg($_rebased_build_config:javac:interface_classpath)" ] + } else { + args += [ "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)" ] + } + + if (invoker.use_turbine) { + args += [ + "--processorpath=@FileArg($_rebased_build_config:javac:processor_classpath)", + "--processors=@FileArg($_rebased_build_config:javac:processor_classes)", + ] + } + + if (invoker.use_turbine) { + _turbine_jar_path = "//third_party/turbine/turbine.jar" + inputs += [ _turbine_jar_path ] + outputs += [ invoker.generated_jar_path ] + args += [ + "--turbine-jar-path", + rebase_path(_turbine_jar_path, root_build_dir), + "--generated-jar-path", + rebase_path(invoker.generated_jar_path, root_build_dir), + ] + } + + # Currently turbine does not support JDK11. + if (invoker.supports_android || invoker.use_turbine) { + args += [ "--java-version=1.8" ] + } + if (use_java_goma) { + args += [ "--gomacc-path=$goma_dir/gomacc" ] + + # Override the default action_pool when goma is enabled. + pool = "//build/config/android:goma_javac_pool" + } + + # Flag enable_kythe_annotations requires + # checkout_android_prebuilts_build_tools=True in .gclient. + if (enable_kythe_annotations && !invoker.enable_errorprone) { + args += [ "--enable-kythe-annotations" ] + } + if (invoker.requires_android) { + args += [ "--bootclasspath=@FileArg($_rebased_build_config:android:sdk_interface_jars)" ] + } + if (_chromium_code) { + args += [ "--chromium-code=1" ] + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + } + if (defined(invoker.jar_excluded_patterns)) { + args += [ "--jar-info-exclude-globs=${invoker.jar_excluded_patterns}" ] + } + + if (invoker.enable_errorprone) { + # Our custom plugin pulls in the main errorprone dep transitively. + _errorprone_dep = "//tools/android/errorprone_plugin:errorprone_plugin" + deps += [ _errorprone_dep ] + _dep_gen_dir = get_label_info(_errorprone_dep, "target_gen_dir") + _dep_name = get_label_info(_errorprone_dep, "name") + _rebased_errorprone_buildconfig = + rebase_path("$_dep_gen_dir/$_dep_name.build_config.json", + root_build_dir) + args += [ + "--processorpath=@FileArg($_rebased_errorprone_buildconfig:deps_info:host_classpath)", + "--enable-errorprone", + ] + } + if (defined(invoker.skip_build_server) && invoker.skip_build_server) { + # Nocompile tests need lint to fail through ninja. + args += [ "--skip-build-server" ] + } + + foreach(e, _processor_args) { + args += [ "--processor-arg=" + e ] + } + + foreach(file_tuple, _additional_jar_files) { + # Each element is of length two, [ path_to_file, path_to_put_in_jar ] + inputs += [ file_tuple[0] ] + args += + [ "--additional-jar-file=" + + rebase_path(file_tuple[0], root_build_dir) + ":" + file_tuple[1] ] + } + if (invoker.java_files != []) { + args += [ "@" + rebase_path(invoker.java_sources_file, root_build_dir) ] + } + foreach(e, _javac_args) { + args += [ "--javac-arg=" + e ] + } + } + } + + template("java_lib_group") { + forward_variables_from(invoker, [ "testonly" ]) + _group_name = invoker.group_name + not_needed([ "_group_name" ]) + group(target_name) { + if (defined(invoker.deps)) { + deps = [] + foreach(_dep, invoker.deps) { + _target_label = get_label_info(_dep, "label_no_toolchain") + if (filter_exclude([ _target_label ], _java_library_patterns) == [] && + filter_exclude([ _target_label ], _java_resource_patterns) != + []) { + # This is a java library dep, so replace it. + deps += [ "${_target_label}__${_group_name}" ] + } else { + # Transitive java group targets should also include direct deps. + deps += [ _dep ] + } + } + } + } + } + + # Create an interface jar from a normal jar. + # + # Variables + # input_jar: Path to input .jar. + # output_jar: Path to output .ijar. + # + template("generate_interface_jar") { + action_with_pydeps(target_name) { + _ijar_target = "//third_party/ijar:ijar($host_toolchain)" + _ijar_executable = get_label_info(_ijar_target, "root_out_dir") + "/ijar" + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + "public_deps", + ]) + script = "//build/android/gyp/ijar.py" + deps = [ _ijar_target ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + inputs = [ + invoker.input_jar, + _ijar_executable, + ] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + outputs = [ invoker.output_jar ] + args = [ + rebase_path(_ijar_executable, root_build_dir), + rebase_path(invoker.input_jar, root_build_dir), + rebase_path(invoker.output_jar, root_build_dir), + ] + } + } + + # A rule that will handle multiple Java-related targets. + # + # The caller can provide a list of source files with 'java_files' + # and 'srcjar_deps', or a prebuilt .jar file through 'jar_path'. + # + # In the case of a 'java_binary' target type, it can even provide none of + # that (and the rule will just generate its wrapper script). + # + # The template will process the input .jar file (either the prebuilt one, + # or the result of compiling the sources), for example to apply Proguard, + # but also other ranges of bytecode-level rewriting schemes. + # + # Variables: + # type: type of Java target, valid values: 'java_library', 'java_binary', + # 'junit_binary', 'java_annotation_processor', and 'android_apk' + # main_target_name: optional. If provided, overrides target_name when + # creating sub-targets (e.g. "${main_target_name}__dex") and + # some output files (e.g. "${main_target_name}.sources"). Only used + # for 'android_apk' types at the moment, where main_target_name will + # be the name of the main APK target. + # supports_android: Optional. True if target can run on Android. + # requires_android: Optional. True if target can only run on Android. + # java_files: Optional list of Java source file paths for this target. + # javac_args: Optional list of extra arguments to pass to javac. + # errorprone_args: Optional list of extra arguments to pass to. + # srcjar_deps: Optional list of .srcjar targets (not file paths). The Java + # source files they contain will also be compiled for this target. + # java_sources_file: Optional path to a file which will be written with + # the content of java_files. If not provided, the file will be written + # under $target_gen_dir/$main_target_name.sources. Ignored if + # java_files is empty. If not + # jar_path: Optional path to a prebuilt .jar file for this target. + # Mutually exclusive with java_files and srcjar_deps. + # output_name: Optional output name for the final jar path. Used to + # determine the name of the final jar. Default is to use the same + # name as jar_path, if provided, or main_target_name. + # main_class: Main Java class name for 'java_binary', 'junit_binary' and + # 'java_annotation_processor' target types. Should not be set for other + # ones. + # deps: Dependencies for this target. + # public_deps: Dependencies that this target exposes as part of its public API. + # public_deps do not need to be listed in both the 'deps' and 'public_deps' lists. + # testonly: True iff target should only be used for tests. + # chromium_code: Optional. Whether this is Chromium-specific code. If not + # provided, this is determined automatically, based on the location of + # the source files (i.e. anything under third_party/ is not + # Chromium-specific unless it is in a 'chromium' sub-directory). + # jacoco_never_instrument: Optional. If provided, whether to forbid + # instrumentation with the Jacoco coverage processor. If not provided, + # this is controlled by the global use_jacoco_coverage build arg variable + # and only used for non-test Chromium code. + # include_android_sdk: Optional. Whether or not the android SDK dep + # should be added to deps. Defaults to true for non-system libraries + # that support android. + # alternative_android_sdk_dep: Optional. Alternative Android system + # android java target to use. + # annotation_processor_deps: Optional list of dependencies corresponding + # to annotation processors used to compile these sources. + # input_jars_paths: Optional list of additional .jar file paths, which will + # be added to the compile-time classpath when building this target (but + # not to the runtime classpath). + # desugar_jars_paths: Optional list of additional .jar file paths, which will + # be added to the desugar classpath when building this target (but not to + # any other classpath). This is only used to break dependency cycles. + # gradle_treat_as_prebuilt: Cause generate_gradle.py to reference this + # library via its built .jar rather than including its .java sources. + # proguard_enabled: Optional. True to enable ProGuard obfuscation. + # proguard_configs: Optional list of additional proguard config file paths. + # bypass_platform_checks: Optional. If True, platform checks will not + # be performed. They are used to verify that every target with + # requires_android only depends on targets that, at least supports_android. + # Similarly, if a target has !supports_android, then it cannot depend on + # any other target that has requires_android. + # include_java_resources: Optional. If True, include Java (not Android) + # resources into final .jar file. + # jar_excluded_patterns: Optional list of .class file patterns to exclude + # from the final .jar file. + # jar_included_patterns: Optional list of .class file patterns to include + # in the final .jar file. jar_excluded_patterns take precedence over this. + # low_classpath_priority: Indicates that the library should be placed at the + # end of the classpath. The default classpath order has libraries ordered + # before the libraries that they depend on. 'low_classpath_priority' is + # useful when one java_library() overrides another via + # 'jar_excluded_patterns' and the overriding library does not depend on the + # overridee. + # + # For 'android_apk' and 'android_app_bundle_module' targets only: + # + # apk_path: Path to the final APK file. + # android_manifest: Path to AndroidManifest.xml file for the APK. + # android_manifest_dep: Optional. Dependency target that generates + # android_manifest. + # apk_under_test: For 'android_apk' targets used to test other APKs, + # this is the target name of APK being tested. + # incremental_apk_path: Path to the incremental APK. + # incremental_install_json_path: Path to the incremental install json. + # native_lib_placeholders: Optional. List of placeholder filenames to add to + # the APK. + # proguard_mapping_path: Path to .mapping file produced from ProGuard step. + # shared_libraries_runtime_deps_file: Optional. Path to a file listing the + # native shared libraries required at runtime by the APK. + # secondary_abi_shared_libraries_runtime_deps_file: + # secondary_native_lib_placeholders: Optional. List of placeholder filenames + # to add to the APK for the secondary ABI. + # loadable_modules: Optional list of extra native libraries to + # be stored in the APK. + # secondary_abi_loadable_modules: Optional list of native libraries for + # secondary ABI. + # uncompress_shared_libraries: Optional. True to store native shared + # libraries uncompressed and page-aligned. + # proto_resources_path: The path of an zip archive containing the APK's + # resources compiled to the protocol buffer format (instead of regular + # binary xml + resources.arsc). + # r_text_path: The path of the R.txt file generated when compiling the + # resources for this target. + # module_pathmap_path: The path of the pathmap file generated when compiling + # the resources for the bundle module, if path shortening is enabled. + # base_allowlist_rtxt_path: The path of the R.txt file containing the + # list of string resources to keep in the base split APK for any bundle + # that uses this target. + # + # For 'java_binary' and 'junit_binary' targets only. Ignored by others: + # + # wrapper_script_name: Optional name for the generated wrapper script. + # Default is main target name. + # wrapper_script_args: Optional list of extra arguments used by the + # generated wrapper script. + # + template("java_library_impl") { + # TODO(crbug.com/1042017): Remove. + not_needed(invoker, [ "no_build_hooks" ]) + + forward_variables_from(invoker, [ "testonly" ]) + _is_prebuilt = defined(invoker.jar_path) + _is_annotation_processor = invoker.type == "java_annotation_processor" + _is_java_binary = + invoker.type == "java_binary" || invoker.type == "junit_binary" + _supports_android = + defined(invoker.supports_android) && invoker.supports_android + _requires_android = + defined(invoker.requires_android) && invoker.requires_android + + _invoker_deps = [] + if (defined(invoker.deps)) { + _invoker_deps += invoker.deps + } + if (defined(invoker.public_deps)) { + foreach(_public_dep, invoker.public_deps) { + if (filter_include([ _public_dep ], _invoker_deps) != []) { + assert(false, "'public_deps' and 'deps' overlap: $_public_dep") + } + } + _invoker_deps += invoker.public_deps + } + + _main_target_name = target_name + if (defined(invoker.main_target_name)) { + _main_target_name = invoker.main_target_name + } + + if (defined(invoker.resources_package)) { + _resources_package = invoker.resources_package + } + + _java_files = [] + if (defined(invoker.sources)) { + _java_files = invoker.sources + } + _srcjar_deps = [] + if (defined(invoker.srcjar_deps)) { + _srcjar_deps = invoker.srcjar_deps + } + _has_sources = _java_files != [] || _srcjar_deps != [] + + if (_is_prebuilt) { + assert(!_has_sources) + } else { + # Allow java_binary to not specify any sources. This is needed when a prebuilt + # is needed as a library as well as a binary. + assert(_is_annotation_processor || _is_java_binary || _has_sources) + } + + if (_is_java_binary) { + assert(defined(invoker.main_class), + "${invoker.type}() must set main_class") + } else if (_is_annotation_processor) { + assert(defined(invoker.main_class), + "java_annotation_processor() must set main_class") + } else { + assert(!defined(invoker.main_class), + "main_class cannot be used for target of type ${invoker.type}") + } + + if (defined(invoker.chromium_code)) { + _chromium_code = invoker.chromium_code + } else { + # Default based on whether target is in third_party. + _chromium_code = + filter_exclude([ get_label_info(":$_main_target_name", "dir") ], + [ "*\bthird_party\b*" ]) != [] + if (!_chromium_code && !_is_prebuilt && _java_files != []) { + # Unless third_party code has an org.chromium file in it. + _chromium_code = + filter_exclude(_java_files, [ "*\bchromium\b*" ]) != _java_files + } + } + + # Define build_config_deps which will be a list of targets required to + # build the _build_config. + _build_config = "$target_gen_dir/$_main_target_name.build_config.json" + _build_config_target_name = + "${_main_target_name}$build_config_target_suffix" + + # The only target that might have no prebuilt and no sources is a java_binary. + _build_host_jar = false + _build_device_jar = false + if (_is_prebuilt || _has_sources) { + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } else if (_is_prebuilt) { + _output_name = get_path_info(invoker.jar_path, "name") + } else { + _output_name = _main_target_name + } + + _build_host_jar = _is_java_binary || _is_annotation_processor || + invoker.type == "java_library" + _build_device_jar = + invoker.type != "system_java_library" && _supports_android + + _jacoco_instrument = + use_jacoco_coverage && _chromium_code && _java_files != [] && + _build_device_jar && (!defined(invoker.testonly) || !invoker.testonly) + if (defined(invoker.jacoco_never_instrument)) { + _jacoco_instrument = + !invoker.jacoco_never_instrument && _jacoco_instrument + } + + if (_build_host_jar) { + # Jar files can be needed at runtime (by Robolectric tests or java binaries), + # so do not put them under obj/. + # TODO(agrieve): I suspect it would be better to use dist_jar for java_binary + # rather than archiving unnecessary .jar files within lib.java. + _target_dir_name = get_label_info(":$_main_target_name", "dir") + _host_processed_jar_path = + "$root_out_dir/lib.java$_target_dir_name/$_output_name.jar" + } + if (_build_device_jar) { + _dex_path = "$target_out_dir/$_main_target_name.dex.jar" + _enable_desugar = + !defined(invoker.enable_desugar) || invoker.enable_desugar + _use_classic_desugar = + defined(invoker.use_classic_desugar) && invoker.use_classic_desugar + + # Build speed optimization: Skip "process device" step if the step + # would be just a copy and avoid the copy. + _process_device_jar = + defined(invoker.bytecode_rewriter_target) || _jacoco_instrument || + (_enable_desugar && _use_classic_desugar) || + defined(invoker.jar_excluded_patterns) || + defined(invoker.jar_included_patterns) + if (!_process_device_jar && _is_prebuilt) { + _device_processed_jar_path = invoker.jar_path + } else { + _device_processed_jar_path = + "$target_out_dir/$_output_name.processed.jar" + } + } + + # For static libraries, the javac jar output is created at the intermediate + # path so that it can be processed by another target and moved to the final + # spot that the .build_config.json knows about. Technically this should be done + # for the ijar as well, but this is only used for APK targets where + # the ijar path isn't actually used. + if (_has_sources) { + _final_ijar_path = "$target_out_dir/$_output_name.turbine.jar" + } else { + _final_ijar_path = "$target_out_dir/$_output_name.ijar.jar" + } + + if (_has_sources) { + if (_build_device_jar && !_process_device_jar) { + _javac_jar_path = _device_processed_jar_path + } else { + _javac_jar_path = "$target_out_dir/$_main_target_name.javac.jar" + } + _generated_jar_path = + "$target_gen_dir/$_main_target_name.generated.srcjar" + } + + if (_is_prebuilt) { + _unprocessed_jar_path = invoker.jar_path + } else { + _unprocessed_jar_path = _javac_jar_path + } + } + + if (_is_prebuilt || _has_sources) { + _java_res_deps = [] + _java_header_deps = [] + _java_impl_deps = [] + _non_java_deps = [] + foreach(_dep, _invoker_deps) { + _target_label = get_label_info(_dep, "label_no_toolchain") + if (filter_exclude([ _target_label ], _java_resource_patterns) == []) { + _java_res_deps += [ _dep ] + } else if (filter_exclude([ _target_label ], _java_library_patterns) == + []) { + # This is a java library dep, so it has header and impl targets. + _java_header_deps += [ "${_target_label}__header" ] + _java_impl_deps += [ "${_target_label}__impl" ] + } else { + _non_java_deps += [ _dep ] + } + } + + # Don't need to depend on the apk-under-test to be packaged. + if (defined(invoker.apk_under_test)) { + _java_header_deps += [ "${invoker.apk_under_test}__java__header" ] + _java_impl_deps += [ "${invoker.apk_under_test}__java__impl" ] + } + + # These deps cannot be passed via invoker.deps since bundle_module targets + # have bundle_module.build_config without the __java suffix, so they are + # special and cannot be passed as regular deps to write_build_config. + if (defined(invoker.base_module_target)) { + _java_header_deps += [ "${invoker.base_module_target}__java__header" ] + _java_impl_deps += [ "${invoker.base_module_target}__java__impl" ] + } + + _extra_java_deps = [] + if (_jacoco_instrument) { + _extra_java_deps += [ "//third_party/jacoco:jacocoagent_java" ] + } + + _include_android_sdk = _build_device_jar + if (defined(invoker.include_android_sdk)) { + _include_android_sdk = invoker.include_android_sdk + } + if (_include_android_sdk) { + _sdk_java_dep = "//third_party/android_sdk:android_sdk_java" + if (defined(invoker.alternative_android_sdk_dep)) { + _sdk_java_dep = invoker.alternative_android_sdk_dep + } + + # This is an android_system_java_prebuilt target, so no headers. + _extra_java_deps += [ _sdk_java_dep ] + } + + # Classpath deps is used for header and dex targets, they do not need + # resource deps. + _classpath_deps = _java_header_deps + _non_java_deps + _extra_java_deps + + [ ":$_build_config_target_name" ] + + _full_classpath_deps = + _java_impl_deps + _java_res_deps + _non_java_deps + _extra_java_deps + + [ ":$_build_config_target_name" ] + } + + # Often needed, but too hard to figure out when ahead of time. + not_needed([ + "_classpath_deps", + "_full_classpath_deps", + ]) + + if (_java_files != []) { + _java_sources_file = "$target_gen_dir/$_main_target_name.sources" + if (defined(invoker.java_sources_file)) { + _java_sources_file = invoker.java_sources_file + } + write_file(_java_sources_file, rebase_path(_java_files, root_build_dir)) + } + + write_build_config(_build_config_target_name) { + forward_variables_from(invoker, + [ + "aar_path", + "annotation_processor_deps", + "base_allowlist_rtxt_path", + "gradle_treat_as_prebuilt", + "input_jars_paths", + "low_classpath_priority", + "main_class", + "proguard_configs", + "proguard_enabled", + "proguard_mapping_path", + "public_target_label", + "r_text_path", + "type", + ]) + if (type == "android_apk" || type == "android_app_bundle_module") { + forward_variables_from( + invoker, + [ + "android_manifest", + "android_manifest_dep", + "merged_android_manifest", + "final_dex_path", + "loadable_modules", + "native_lib_placeholders", + "res_size_info_path", + "secondary_abi_loadable_modules", + "secondary_abi_shared_libraries_runtime_deps_file", + "secondary_native_lib_placeholders", + "shared_libraries_runtime_deps_file", + "static_library_dependent_targets", + "uncompress_shared_libraries", + "library_always_compress", + "library_renames", + ]) + } + if (type == "android_apk") { + forward_variables_from(invoker, + [ + "apk_path", + "apk_under_test", + "incremental_apk_path", + "incremental_install_json_path", + ]) + } + if (type == "android_app_bundle_module") { + forward_variables_from(invoker, + [ + "base_module_target", + "is_base_module", + "module_pathmap_path", + "proto_resources_path", + "version_name", + "version_code", + ]) + } + chromium_code = _chromium_code + build_config = _build_config + is_prebuilt = _is_prebuilt + + # Specifically avoid passing in invoker.base_module_target as one of the + # possible_config_deps. + possible_config_deps = _invoker_deps + if (defined(_extra_java_deps)) { + possible_config_deps += _extra_java_deps + } + if (defined(apk_under_test)) { + possible_config_deps += [ apk_under_test ] + } + + if (defined(invoker.public_deps)) { + possible_config_public_deps = invoker.public_deps + } + + supports_android = _supports_android + requires_android = _requires_android + bypass_platform_checks = defined(invoker.bypass_platform_checks) && + invoker.bypass_platform_checks + + if (defined(_resources_package)) { + custom_package = _resources_package + } + if (_is_prebuilt || _has_sources) { + ijar_path = _final_ijar_path + unprocessed_jar_path = _unprocessed_jar_path + } + if (_build_host_jar) { + host_jar_path = _host_processed_jar_path + } + if (_build_device_jar) { + device_jar_path = _device_processed_jar_path + dex_path = _dex_path + } + if (_java_files != []) { + java_sources_file = _java_sources_file + } + + bundled_srcjars = [] + foreach(d, _srcjar_deps) { + _dep_gen_dir = get_label_info(d, "target_gen_dir") + _dep_name = get_label_info(d, "name") + bundled_srcjars += [ "$_dep_gen_dir/$_dep_name.srcjar" ] + } + if (defined(invoker.include_java_resources) && + invoker.include_java_resources) { + java_resources_jar = _unprocessed_jar_path + if (defined(invoker.jar_path)) { + # Use original jar_path because _jar_path points to a library without + # resources. + } else { + java_resources_jar = _device_processed_jar_path + } + } + } + + if (_is_prebuilt || _has_sources) { + _header_target_name = "${target_name}__header" + } + + _public_deps = [] + _analysis_public_deps = [] + _analysis_files = [] + + if (defined(invoker.proguard_configs)) { + assert(_build_host_jar || _build_device_jar) + + # proguard_configs listed on java_library targets need to be marked + # as inputs to at least one action so that "gn analyze" will know + # about them. Although this target doesn't use them, it's a convenient spot + # to list them. + # https://crbug.com/827197 + _analysis_files = invoker.proguard_configs + _analysis_public_deps = + _non_java_deps + _srcjar_deps # For the aapt-generated + # proguard rules. + } + + if (_has_sources) { + if (defined(invoker.enable_errorprone)) { + _enable_errorprone = invoker.enable_errorprone + } else { + _enable_errorprone = + _java_files != [] && _chromium_code && use_errorprone_java_compiler + } + + _type = invoker.type + + _uses_fake_rjava = _type == "java_library" && _requires_android + + if (_uses_fake_rjava && defined(_resources_package)) { + # has _resources at the end so it looks like a resources pattern, since + # it does act like one (and other resources patterns need to depend on + # this before they can read its output R.txt). + _fake_rjava_target = "${target_name}__rjava_resources" + _possible_resource_deps = _invoker_deps + generate_r_java(_fake_rjava_target) { + deps = [ ":$_build_config_target_name" ] + if (defined(_possible_resource_deps)) { + possible_resource_deps = _possible_resource_deps + } + build_config = _build_config + + # Filepath has to be exactly this because compile_java looks for the + # srcjar of srcjar_deps at this location $gen_dir/$target_name.srcjar + srcjar_path = "$target_gen_dir/$target_name.srcjar" + package = _resources_package + } + _srcjar_deps += [ ":$_fake_rjava_target" ] + } + + template("compile_java_helper") { + _enable_errorprone = + defined(invoker.enable_errorprone) && invoker.enable_errorprone + if (_enable_errorprone) { + # Rely on the header jar to provide all .class files so that it is + # safe to omit generated files entirely for errorprone. + _filtered_java_files = + filter_exclude(_java_files, [ "$root_gen_dir*" ]) + } + if (_enable_errorprone && _filtered_java_files == []) { + # Filtering out generated files resulted in no files left. + group(target_name) { + not_needed(invoker, "*") + } + } else { + compile_java(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + output_jar_path = invoker.output_jar_path + enable_errorprone = _enable_errorprone + use_turbine = defined(invoker.use_turbine) && invoker.use_turbine + + main_target_name = _main_target_name + build_config = _build_config + + if (_enable_errorprone) { + java_files = _filtered_java_files + } else { + java_files = _java_files + srcjar_deps = _srcjar_deps + } + + if (java_files != []) { + java_sources_file = _java_sources_file + } + chromium_code = _chromium_code + supports_android = _supports_android + requires_android = _requires_android + if (!defined(deps)) { + deps = [] + } + deps += _classpath_deps + } + } + } + _compile_java_forward_variables = [ + "additional_jar_files", + "apk_name", + "jar_excluded_patterns", + "javac_args", + "processor_args_javac", + "skip_build_server", + ] + _annotation_processor_deps = [] + if (defined(invoker.annotation_processor_deps)) { + _annotation_processor_deps = invoker.annotation_processor_deps + } + + compile_java_helper(_header_target_name) { + forward_variables_from(invoker, _compile_java_forward_variables) + use_turbine = true + output_jar_path = _final_ijar_path + generated_jar_path = _generated_jar_path + deps = _annotation_processor_deps + } + _public_deps += [ ":$_header_target_name" ] + + _compile_java_target = "${_main_target_name}__compile_java" + compile_java_helper(_compile_java_target) { + forward_variables_from(invoker, _compile_java_forward_variables) + output_jar_path = _javac_jar_path + deps = [ ":$_header_target_name" ] + header_jar_path = _final_ijar_path + generated_jar_path = _generated_jar_path + } + if (_enable_errorprone) { + _compile_java_errorprone_target = "${_main_target_name}__errorprone" + compile_java_helper(_compile_java_errorprone_target) { + forward_variables_from(invoker, _compile_java_forward_variables) + enable_errorprone = true + if (defined(invoker.errorprone_args)) { + if (!defined(javac_args)) { + javac_args = [] + } + javac_args += invoker.errorprone_args + } + deps = [ ":$_header_target_name" ] + header_jar_path = _final_ijar_path + generated_jar_path = _generated_jar_path + output_jar_path = "$target_out_dir/$target_name.errorprone.stamp" + } + _analysis_public_deps += [ ":$_compile_java_errorprone_target" ] + } + } # _has_sources + + if (_is_prebuilt || _build_device_jar || _build_host_jar) { + _unprocessed_jar_deps = [] + if (_has_sources) { + _unprocessed_jar_deps += [ ":$_compile_java_target" ] + } + } + + if (defined(invoker.bytecode_rewriter_target)) { + assert(_build_host_jar || _build_device_jar, + "A host or device jar must be created to use bytecode rewriting") + + _rewritten_jar = "$target_out_dir/${target_name}_rewritten.jar" + _rewritten_jar_target_name = "${target_name}__rewritten" + _rewriter_path = root_build_dir + "/bin/helper/" + + get_label_info(invoker.bytecode_rewriter_target, "name") + _rebased_build_config = rebase_path(_build_config, root_build_dir) + action_with_pydeps(_rewritten_jar_target_name) { + script = "//build/android/gyp/bytecode_rewriter.py" + inputs = [ + _rewriter_path, + _build_config, + _unprocessed_jar_path, + ] + outputs = [ _rewritten_jar ] + depfile = "$target_gen_dir/$target_name.d" + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--script", + rebase_path(_rewriter_path, root_build_dir), + "--classpath", + "@FileArg($_rebased_build_config:deps_info:javac_full_classpath)", + "--classpath", + "@FileArg($_rebased_build_config:android:sdk_jars)", + "--input-jar", + rebase_path(_unprocessed_jar_path, root_build_dir), + "--output-jar", + rebase_path(_rewritten_jar, root_build_dir), + ] + deps = _unprocessed_jar_deps + _full_classpath_deps + + [ invoker.bytecode_rewriter_target ] + } + + _unprocessed_jar_deps = [] + _unprocessed_jar_deps = [ ":$_rewritten_jar_target_name" ] + _unprocessed_jar_path = _rewritten_jar + } + + if (_is_prebuilt) { + generate_interface_jar(_header_target_name) { + # Always used the unfiltered .jar to create the interface jar so that + # other targets will resolve filtered classes when depending on + # BuildConfig, NativeLibraries, etc. + input_jar = _unprocessed_jar_path + output_jar = _final_ijar_path + + # Normally ijar does not require any deps, but: + # 1 - Some jars are bytecode rewritten by _unprocessed_jar_deps. + # 2 - Other jars need to be unzipped by _non_java_deps. + # 3 - It is expected that depending on a header target implies depending + # on its transitive header target deps via _java_header_deps. + deps = _unprocessed_jar_deps + _non_java_deps + _java_header_deps + } + _public_deps += [ ":$_header_target_name" ] + } + + if (_build_host_jar || _build_device_jar) { + _enable_bytecode_checks = !defined(invoker.enable_bytecode_checks) || + invoker.enable_bytecode_checks + if (_enable_bytecode_checks) { + _bytecode_checks_target = "${target_name}__validate_classpath" + bytecode_processor(_bytecode_checks_target) { + forward_variables_from(invoker, [ "missing_classes_allowlist" ]) + deps = _unprocessed_jar_deps + _full_classpath_deps + + [ ":$_build_config_target_name" ] + requires_android = _requires_android + target_label = + get_label_info(":${invoker.target_name}", "label_no_toolchain") + input_jar = _unprocessed_jar_path + build_config = _build_config + is_prebuilt = _is_prebuilt + } + _analysis_public_deps += [ ":$_bytecode_checks_target" ] + } + + if (_build_host_jar) { + _process_host_jar_target_name = "${target_name}__process_host" + process_java_library(_process_host_jar_target_name) { + forward_variables_from(invoker, + [ + "jar_excluded_patterns", + "jar_included_patterns", + ]) + input_jar_path = _unprocessed_jar_path + jar_deps = _unprocessed_jar_deps + _full_classpath_deps + is_device_jar = false + output_jar_path = _host_processed_jar_path + jacoco_instrument = _jacoco_instrument + if (_jacoco_instrument) { + java_files = _java_files + java_sources_file = _java_sources_file + } + } + _public_deps += [ ":${_process_host_jar_target_name}" ] + } + + if (_build_device_jar) { + if (_process_device_jar) { + _process_device_jar_target_name = "${target_name}__process_device" + process_java_library(_process_device_jar_target_name) { + forward_variables_from(invoker, + [ + "jar_excluded_patterns", + "jar_included_patterns", + ]) + input_jar_path = _unprocessed_jar_path + jar_deps = _unprocessed_jar_deps + _full_classpath_deps + is_device_jar = true + output_jar_path = _device_processed_jar_path + jacoco_instrument = _jacoco_instrument + if (_jacoco_instrument) { + java_files = _java_files + java_sources_file = _java_sources_file + } + enable_desugar = _enable_desugar && _use_classic_desugar + if (enable_desugar) { + build_config = _build_config + classpath_deps = _classpath_deps + forward_variables_from(invoker, [ "desugar_jars_paths" ]) + } + } + _process_device_jar_deps = [ ":${_process_device_jar_target_name}" ] + _public_deps += _process_device_jar_deps + } else { + assert(_unprocessed_jar_path == _device_processed_jar_path) + _process_device_jar_deps = + _unprocessed_jar_deps + _full_classpath_deps + } + + dex("${target_name}__dex") { + forward_variables_from(invoker, + [ + "desugar_jars_paths", + "proguard_enable_obfuscation", + "use_classic_desugar", + ]) + input_class_jars = [ _device_processed_jar_path ] + enable_desugar = _enable_desugar + ignore_desugar_missing_deps = !_enable_bytecode_checks + + # There's no value in per-class dexing prebuilts since they never + # change just one class at a time. + disable_incremental = _is_prebuilt + output = _dex_path + deps = _process_device_jar_deps + + if (enable_desugar && !_use_classic_desugar) { + # Desugaring with D8 requires full classpath. + build_config = _build_config + final_ijar_path = _final_ijar_path + deps += _classpath_deps + [ ":$_header_target_name" ] + } + + enable_multidex = false + is_library = true + } + _public_deps += [ ":${target_name}__dex" ] + } + } + + if (_is_java_binary) { + # Targets might use the generated script while building, so make it a dep + # rather than a data_dep. + java_binary_script("${target_name}__java_binary_script") { + forward_variables_from(invoker, + [ + "tiered_stop_at_level_one", + "main_class", + "wrapper_script_args", + ]) + build_config = _build_config + script_name = _main_target_name + if (defined(invoker.wrapper_script_name)) { + script_name = invoker.wrapper_script_name + } + deps = [ ":$_build_config_target_name" ] + } + _public_deps += [ ":${target_name}__java_binary_script" ] + } + + # The __impl target contains all non-analysis steps for this template. + # Having this separated out from the main target (which contains analysis + # steps) allows analysis steps for this target to be run concurrently with + # the non-analysis steps of other targets that depend on this one. + group("${target_name}__impl") { + public_deps = _public_deps + } + + java_lib_group("${target_name}__assetres") { + deps = _invoker_deps + group_name = "assetres" + + if (defined(_fake_rjava_target)) { + deps += [ ":$_fake_rjava_target" ] + } + } + + group(target_name) { + forward_variables_from(invoker, + [ + "assert_no_deps", + "data", + "data_deps", + "deps", + "public_deps", + "visibility", + ]) + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ ":${target_name}__impl" ] + if (!defined(data)) { + data = [] + } + data += _analysis_files + if (!defined(data_deps)) { + data_deps = [] + } + data_deps += _analysis_public_deps + } + } +} + +# Create a zip archive corresponding to an application bundle module. +# +# Compile all the components of a given android_apk_or_module() target into a +# zip archive suitable to later create an android_app_bundle() target. This +# archive's format is very similar to that on an APK, except for a few +# differences in internal directory layouts, and the fact that resources, as +# well as xml files, are compiled using a protocol-buffer based format (instead +# of the regular binary xml + resources.arsc). +# +# A final application bundle is built from one or more module bundle modules, +# plus some configuration file. +# +# Variables: +# module_zip_path: Output module path. +# build_config: Path to build_config of the android_apk_or_module() target. +# dex_path: If module is proguarded separately from the base module, dex_path +# is the path to its dex file and is passed directly to the creation script. +# Otherwise, dex_path is undefined and we retrieve the module's dex file +# using its build_config. +# expected_libs_and_assets: Verify the list of included native libraries +# and assets is consistent with the given expectation file. +# expected_libs_and_assets_base: Treat expected_libs_and_assets as a diff +# with this file as the base. +# is_multi_abi: If true will add a library placeholder for the missing ABI if +# either the primary or the secondary ABI has no native libraries set. +# module_name: The module's name. +# native_libraries_config: Path to file listing native libraries to be +# packaged into each module. +# proguard_enabled: Optional. True if proguarding is enabled for this +# bundle. Default is to enable this only for release builds. Note that +# this will always perform synchronized proguarding. +template("create_android_app_bundle_module") { + _rebased_build_config = rebase_path(invoker.build_config, root_build_dir) + _rebased_native_libraries_config = + rebase_path(invoker.native_libraries_config, root_build_dir) + _proguard_enabled = + defined(invoker.proguard_enabled) && invoker.proguard_enabled + + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + _deps = invoker.deps + _script = "//build/android/gyp/apkbuilder.py" + + # NOTE: Compared to the inputs of the "package_apk" template action, + # this list is much smaller, since finalize_apk is never called + # by apkbuild.py --format=bundle-module. This means not using + # apksigner and zipalign as well, nor the keystore. Other + # dependencies like extra native libraries are all pulled from the + # .build_config.json through @FileArg() references (see below) and + # will be listed in the generated depfile instead. + _inputs = [ + invoker.build_config, + invoker.native_libraries_config, + ] + _outputs = [ invoker.module_zip_path ] + _args = [ + "--format=bundle-module", + "--output-apk", + rebase_path(invoker.module_zip_path, root_build_dir), + "--resource-apk=@FileArg(" + + "$_rebased_build_config:deps_info:proto_resources_path)", + "--assets=@FileArg($_rebased_build_config:assets)", + "--uncompressed-assets=@FileArg(" + + "$_rebased_build_config:uncompressed_assets)", + "--native-libs=@FileArg($_rebased_native_libraries_config" + + ":${invoker.module_name})", + "--native-lib-placeholders=@FileArg($_rebased_build_config" + + ":native:native_library_placeholders)", + "--secondary-native-lib-placeholders=@FileArg($_rebased_build_config" + + ":native:secondary_native_library_placeholders)", + "--android-abi=$android_app_abi", + "--min-sdk-version=${invoker.min_sdk_version}", + "--uncompress-shared-libraries=@FileArg(" + + "$_rebased_build_config:native:uncompress_shared_libraries)", + "--library-always-compress=@FileArg($_rebased_build_config:native:library_always_compress)", + "--library-renames=@FileArg($_rebased_build_config:native:library_renames)", + ] + if (defined(android_app_secondary_abi)) { + _rebased_secondary_abi_native_libraries_config = + rebase_path(invoker.secondary_abi_native_libraries_config, + root_build_dir) + _args += [ + "--secondary-native-libs", + "@FileArg($_rebased_secondary_abi_native_libraries_config" + + ":${invoker.module_name})", + "--secondary-android-abi=$android_app_secondary_abi", + ] + } + if (defined(invoker.is_multi_abi) && invoker.is_multi_abi) { + _args += [ "--is-multi-abi" ] + } + if (defined(invoker.uncompress_dex) && invoker.uncompress_dex) { + _args += [ "--uncompress-dex" ] + } + + # Use either provided dex path or build config path based on type of module. + if (defined(invoker.dex_path)) { + _inputs += [ invoker.dex_path ] + _rebased_dex_path = rebase_path(invoker.dex_path, root_build_dir) + _args += [ "--dex-file=$_rebased_dex_path" ] + } else { + _args += [ "--dex-file=@FileArg($_rebased_build_config:final_dex:path)" ] + } + + # The library is imported via proguard when proguard is enabled. + if (!_proguard_enabled && enable_jdk_library_desugaring && + invoker.module_name == "base") { + _all_jdk_libs = "//build/android:all_jdk_libs" + _deps += [ _all_jdk_libs ] + _jdk_libs_dex = + get_label_info(_all_jdk_libs, "target_out_dir") + "/all_jdk_libs.l8.dex" + _inputs += [ _jdk_libs_dex ] + _args += [ + "--jdk-libs-dex-file", + rebase_path(_jdk_libs_dex, root_build_dir), + ] + } + + if (treat_warnings_as_errors) { + _args += [ "--warnings-as-errors" ] + } + + if (defined(invoker.expected_libs_and_assets)) { + _expectations_target = "${invoker.top_target_name}_validate_libs_and_assets" + action_with_pydeps(_expectations_target) { + _actual_file = "$target_gen_dir/$target_name.libs_and_assets" + _failure_file = "$expectations_failure_dir/" + + string_replace(invoker.expected_libs_and_assets, "/", "_") + inputs = [ + invoker.expected_libs_and_assets, + invoker.build_config, + invoker.native_libraries_config, + ] + deps = [ + invoker.build_config_target, + invoker.native_libraries_config_target, + ] + if (defined(android_app_secondary_abi)) { + inputs += [ invoker.secondary_abi_native_libraries_config ] + deps += [ invoker.secondary_abi_native_libraries_config_target ] + } + outputs = [ + _actual_file, + _failure_file, + ] + script = _script + args = _args + [ + "--expected-file", + rebase_path(invoker.expected_libs_and_assets, root_build_dir), + "--actual-file", + rebase_path(_actual_file, root_build_dir), + "--failure-file", + rebase_path(_failure_file, root_build_dir), + "--only-verify-expectations", + ] + if (defined(invoker.expected_libs_and_assets_base)) { + inputs += [ invoker.expected_libs_and_assets_base ] + args += [ + "--expected-file-base", + rebase_path(invoker.expected_libs_and_assets_base, root_build_dir), + ] + } + if (fail_on_android_expectations) { + args += [ "--fail-on-expectations" ] + } + } + _deps += [ ":$_expectations_target" ] + } + + action_with_pydeps(target_name) { + deps = _deps + inputs = _inputs + outputs = _outputs + script = _script + depfile = "$target_gen_dir/$target_name.d" + args = _args + [ + "--depfile", + rebase_path(depfile, root_build_dir), + ] + } +} + +# Splits input dex file(s) based on given feature jars into seperate dex files +# for each feature. +# +# Variables: +# proguard_mapping: Path to input proguard mapping produced by synchronized +# proguarding. +# input_dex_zip: Path to zipped dex files to split. +# all_modules: Path to list of all modules. Each Module must have +# build_config, name, and build_config_target properties. +# feature_jars_args: Optional list of args to be passed to dexsplitter.py. +# If used should include the jars owned by each feature (in the same order +# as all_modules). Allows invoker to pull the list of jars from a different +# .build_config.json than the module's .build_config. +template("dexsplitter") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, [ "deps" ]) + script = "//build/android/gyp/dexsplitter.py" + _stamp = "${target_gen_dir}/${target_name}.stamp" + outputs = [ _stamp ] + + depfile = "${target_gen_dir}/${target_name}.d" + args = [ + "--stamp", + rebase_path(_stamp, root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + "--r8-path", + rebase_path(_r8_path, root_build_dir), + "--input-dex-zip", + rebase_path(invoker.input_dex_zip, root_build_dir), + "--proguard-mapping-file", + rebase_path(invoker.proguard_mapping, root_build_dir), + ] + + foreach(_feature_module, invoker.all_modules) { + _rebased_module_build_config = + rebase_path(_feature_module.build_config, root_build_dir) + args += [ + "--feature-name", + _feature_module.name, + "--dex-dest=@FileArg($_rebased_module_build_config:final_dex:path)", + ] + if (!defined(invoker.feature_jars_args)) { + args += [ "--feature-jars=@FileArg($_rebased_module_build_config:deps_info:device_classpath)" ] + } + deps += [ _feature_module.build_config_target ] + } + if (defined(invoker.feature_jars_args)) { + args += invoker.feature_jars_args + } + } +} + +# Allots native libraries depended on by feature modules to the module the +# libraries should be packaged into. The packaging module may be different from +# the dependee module in case a library is depended on by multiple modules. In +# that case the library will be allotted to the closest ancestor given a module +# dependency tree (see |parent| below). +# +# Variables: +# modules: List of scopes with the following format: +# name: The module's name. +# parent: The module's parent's name. +# build_config: Path to the module's build config. +# build_config_target: Target creating |build_config|. +# native_libraries_filearg_keys: Keys to be used in +# @FileArg(|build_config|:) expressions pointing to a list of native +# libraries to consider in |build_config|. +# output: Path to native libraries config. +template("allot_native_libraries") { + action_with_pydeps(target_name) { + script = "//build/android/gyp/allot_native_libraries.py" + args = [ + "--output", + rebase_path(invoker.output, root_build_dir), + ] + outputs = [ invoker.output ] + deps = [] + if (defined(invoker.deps)) { + deps += invoker.deps + } + inputs = [] + foreach(_module, invoker.modules) { + deps += [ _module.build_config_target ] + inputs += [ _module.build_config ] + _rebased_build_config = rebase_path(_module.build_config, root_out_dir) + foreach(_key, invoker.native_libraries_filearg_keys) { + args += [ + "--libraries", + "${_module.name},@FileArg($_rebased_build_config:$_key)", + ] + } + if (defined(_module.parent)) { + args += [ + "--dep", + "${_module.parent}:${_module.name}", + ] + } + } + } +} diff --git a/third_party/libwebrtc/build/config/android/linker_version_script.gni b/third_party/libwebrtc/build/config/android/linker_version_script.gni new file mode 100644 index 0000000000..96d8b665d1 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/linker_version_script.gni @@ -0,0 +1,43 @@ +# Copyright 2018 The Chromium 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/python.gni") + +# Generate a custom linker version script that can later be used with +# "-Wl,--version-script=" ldflags. +# +# Variables: +# export_java_symbols: Optional. If true, also export all Java_* symbols +# exported for JNI. +# export_symbol_allowlist_files: Optional. List of paths to input files containing +# lists of symbols to export. +# linker_script: Path to output linker version script. +# +template("generate_linker_version_script") { + action_with_pydeps(target_name) { + script = "//build/android/gyp/generate_linker_version_script.py" + outputs = [ invoker.linker_script ] + inputs = [] + args = [ "--output=" + rebase_path(invoker.linker_script, root_build_dir) ] + + if (defined(invoker.export_java_symbols) && invoker.export_java_symbols) { + args += [ "--export-java-symbols" ] + } + + if (defined(invoker.export_feature_registrations) && + invoker.export_feature_registrations) { + args += [ "--export-feature-registrations" ] + } + + if (defined(invoker.export_symbol_allowlist_files)) { + foreach(file_, invoker.export_symbol_allowlist_files) { + inputs += [ file_ ] + args += [ + "--export-symbol-allowlist-file", + rebase_path(file_, root_build_dir), + ] + } + } + } +} diff --git a/third_party/libwebrtc/build/config/android/rules.gni b/third_party/libwebrtc/build/config/android/rules.gni new file mode 100644 index 0000000000..45499a3781 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/rules.gni @@ -0,0 +1,5183 @@ +# 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. + +# Do not add any imports to non-//build directories here. +# Some projects (e.g. V8) do not have non-build directories DEPS'ed in. + +import("//build/config/android/channel.gni") +import("//build/config/android/config.gni") +import("//build/config/android/internal_rules.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/python.gni") +import("//build/config/rts.gni") +import("//build/config/zip.gni") +import("//build/toolchain/toolchain.gni") + +assert(is_android) + +declare_args() { + enable_jni_tracing = false +} + +if (target_cpu == "arm") { + _sanitizer_arch = "arm" +} else if (target_cpu == "arm64") { + _sanitizer_arch = "aarch64" +} else if (target_cpu == "x86") { + _sanitizer_arch = "i686" +} + +_sanitizer_runtimes = [] +if (use_cfi_diag || is_ubsan || is_ubsan_security || is_ubsan_vptr) { + _sanitizer_runtimes = [ "$clang_base_path/lib/clang/$clang_version/lib/linux/libclang_rt.ubsan_standalone-$_sanitizer_arch-android.so" ] +} + +# 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. +# extra_files: List of extra files to copy in (optional). +# +# Example +# create_native_executable_dist("foo_dist") { +# dist_dir = "$root_build_dir/foo_dist" +# binary = "$root_build_dir/foo" +# deps = [ ":the_thing_that_makes_foo" ] +# } +template("create_native_executable_dist") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _libraries_list = "${target_gen_dir}/${target_name}_library_dependencies.list" + + _sanitizer_runtimes_target_name = "${target_name}__sanitizer_runtimes" + group(_sanitizer_runtimes_target_name) { + metadata = { + shared_libraries = _sanitizer_runtimes + } + } + + generated_file("${target_name}__library_list") { + forward_variables_from(invoker, [ "deps" ]) + if (!defined(deps)) { + deps = [] + } + deps += [ ":${_sanitizer_runtimes_target_name}" ] + output_conversion = "json" + outputs = [ _libraries_list ] + data_keys = [ "shared_libraries" ] + walk_keys = [ "shared_libraries_barrier" ] + rebase = root_build_dir + } + + copy_ex(target_name) { + inputs = [ + _libraries_list, + invoker.binary, + ] + + dest = invoker.dist_dir + data = [ "${invoker.dist_dir}/" ] + + _rebased_libraries_list = rebase_path(_libraries_list, root_build_dir) + _rebased_binaries_list = rebase_path([ invoker.binary ], root_build_dir) + args = [ + "--clear", + "--files=@FileArg($_rebased_libraries_list)", + "--files=$_rebased_binaries_list", + ] + if (defined(invoker.extra_files)) { + _rebased_extra_files = rebase_path(invoker.extra_files, root_build_dir) + args += [ "--files=$_rebased_extra_files" ] + } + + _depfile = "$target_gen_dir/$target_name.d" + _stamp_file = "$target_gen_dir/$target_name.stamp" + outputs = [ _stamp_file ] + args += [ + "--depfile", + rebase_path(_depfile, root_build_dir), + "--stamp", + rebase_path(_stamp_file, root_build_dir), + ] + + deps = [ ":${target_name}__library_list" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +} + +if (enable_java_templates) { + import("//build/config/sanitizers/sanitizers.gni") + + # JNI target implementation. See generate_jni or generate_jar_jni for usage. + template("generate_jni_impl") { + _jni_output_dir = "${target_gen_dir}/${target_name}" + if (defined(invoker.jni_generator_include)) { + _jni_generator_include = invoker.jni_generator_include + _jni_generator_include_deps = [] + } else { + _jni_generator_include = + "//base/android/jni_generator/jni_generator_helper.h" + _jni_generator_include_deps = [ + # Using //base/android/jni_generator/jni_generator_helper.h introduces + # a dependency on buildflags targets indirectly through + # base/android/jni_android.h, which is part of the //base target. + # This can't depend directly on //base without causing a dependency + # cycle, though. + "//base:debugging_buildflags", + "//base:logging_buildflags", + "//build:chromeos_buildflags", + ] + } + + action_with_pydeps(target_name) { + # The sources aren't compiled so don't check their dependencies. + check_includes = false + script = "//base/android/jni_generator/jni_generator.py" + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "deps", + "public_deps", + ]) + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += _jni_generator_include_deps + inputs = [] + args = [ + "--ptr_type=long", + "--includes", + rebase_path(_jni_generator_include, _jni_output_dir), + ] + + if (defined(invoker.classes)) { + if (defined(invoker.jar_file)) { + _jar_file = invoker.jar_file + } else { + _jar_file = android_sdk_jar + } + inputs += [ _jar_file ] + args += [ + "--jar_file", + rebase_path(_jar_file, root_build_dir), + ] + _input_args = invoker.classes + _input_names = invoker.classes + if (defined(invoker.always_mangle) && invoker.always_mangle) { + args += [ "--always_mangle" ] + } + } else { + assert(defined(invoker.sources)) + inputs += invoker.sources + _input_args = rebase_path(invoker.sources, root_build_dir) + _input_names = invoker.sources + if (use_hashed_jni_names) { + args += [ "--use_proxy_hash" ] + } + if (defined(invoker.namespace)) { + args += [ "-n ${invoker.namespace}" ] + } + } + if (defined(invoker.split_name)) { + args += [ "--split_name=${invoker.split_name}" ] + } + + outputs = [] + foreach(_name, _input_names) { + _name_part = get_path_info(_name, "name") + outputs += [ "${_jni_output_dir}/${_name_part}_jni.h" ] + } + + # Avoid passing GN lists because not all webrtc embedders use //build. + foreach(_output, outputs) { + args += [ + "--output_file", + rebase_path(_output, root_build_dir), + ] + } + foreach(_input, _input_args) { + args += [ "--input_file=$_input" ] + } + + if (enable_profiling) { + args += [ "--enable_profiling" ] + } + if (enable_jni_tracing) { + args += [ "--enable_tracing" ] + } + } + } + + # Declare a jni target + # + # This target generates the native jni bindings for a set of .java files. + # + # See base/android/jni_generator/jni_generator.py for more info about the + # format of generating JNI bindings. + # + # Variables + # sources: list of .java files to generate jni for + # namespace: Specify the namespace for the generated header file. + # deps, public_deps: As normal + # + # Example + # # Target located in base/BUILD.gn. + # generate_jni("foo_jni") { + # # Generates gen/base/foo_jni/Foo_jni.h + # # To use: #include "base/foo_jni/Foo_jni.h" + # sources = [ + # "android/java/src/org/chromium/foo/Foo.java", + # ..., + # ] + # } + template("generate_jni") { + generate_jni_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + } + } + + # Declare a jni target for a prebuilt jar + # + # This target generates the native jni bindings for a set of classes in a .jar. + # + # See base/android/jni_generator/jni_generator.py for more info about the + # format of generating JNI bindings. + # + # Variables + # classes: list of .class files in the jar to generate jni for. These should + # include the full path to the .class file. + # jar_file: the path to the .jar. If not provided, will default to the sdk's + # android.jar + # always_mangle: Mangle all generated method names. By default, the script + # only mangles methods that cause ambiguity due to method overload. + # deps, public_deps: As normal + # + # Example + # # Target located in base/BUILD.gn. + # generate_jar_jni("foo_jni") { + # # Generates gen/base/foo_jni/Runnable_jni.h + # # To use: #include "base/foo_jni/Runnable_jni.h" + # classes = [ + # "android/view/Foo.class", + # ] + # } + template("generate_jar_jni") { + generate_jni_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + } + } + + # Declare a jni registration target. + # + # This target generates a srcjar containing a copy of GEN_JNI.java, which has + # the native methods of all dependent java files. It can also create a .h file + # for use with manual JNI registration. + # + # The script does not scan any generated sources (those within .srcjars, or + # within root_build_dir). This could be fixed by adding deps & logic to scan + # .srcjars, but isn't currently needed. + # + # See base/android/jni_generator/jni_registration_generator.py for more info + # about the format of the header file. + # + # Variables + # targets: List of .build_config.json supported targets to provide java sources. + # header_output: Path to the generated .h file (optional). + # sources_exclusions: List of .java files that should be skipped. (optional) + # namespace: Registration functions will be wrapped into this. (optional) + # require_native_mocks: Enforce that any native calls using + # org.chromium.base.annotations.NativeMethods must have a mock set + # (optional). + # enable_native_mocks: Allow native calls using + # org.chromium.base.annotations.NativeMethods to be mocked in tests + # (optional). + # no_transitive_deps: Generate registration for only the Java source in the + # specified target(s). This is useful for generating registration for + # feature modules, without including base module dependencies. + # + # Example + # generate_jni_registration("chrome_jni_registration") { + # targets = [ ":chrome_public_apk" ] + # header_output = "$target_gen_dir/$target_name.h" + # sources_exclusions = [ + # "//path/to/Exception.java", + # ] + # } + template("generate_jni_registration") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + script = "//base/android/jni_generator/jni_registration_generator.py" + inputs = [] + deps = [] + _srcjar_output = "$target_gen_dir/$target_name.srcjar" + outputs = [ _srcjar_output ] + depfile = "$target_gen_dir/$target_name.d" + + args = [ + "--srcjar-path", + rebase_path(_srcjar_output, root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + ] + foreach(_target, invoker.targets) { + deps += [ "${_target}$build_config_target_suffix($default_toolchain)" ] + _build_config = + get_label_info("${_target}($default_toolchain)", "target_gen_dir") + + "/" + get_label_info("${_target}($default_toolchain)", "name") + + ".build_config.json" + _rebased_build_config = rebase_path(_build_config, root_build_dir) + inputs += [ _build_config ] + + if (defined(invoker.no_transitive_deps) && invoker.no_transitive_deps) { + args += [ "--sources-files=@FileArg($_rebased_build_config:deps_info:java_sources_file)" ] + } else { + args += [ + # This is a list of .sources files. + "--sources-files=@FileArg($_rebased_build_config:deps_info:jni:all_source)", + ] + } + } + + if (use_hashed_jni_names) { + args += [ "--use_proxy_hash" ] + } + + if (defined(invoker.enable_native_mocks) && invoker.enable_native_mocks) { + args += [ "--enable_proxy_mocks" ] + + if (defined(invoker.require_native_mocks) && + invoker.require_native_mocks) { + args += [ "--require_mocks" ] + } + } + + if (defined(invoker.header_output)) { + outputs += [ invoker.header_output ] + args += [ + "--header-path", + rebase_path(invoker.header_output, root_build_dir), + ] + } + + if (defined(invoker.sources_exclusions)) { + _rebase_sources_exclusions = + rebase_path(invoker.sources_exclusions, root_build_dir) + args += [ "--sources-exclusions=$_rebase_sources_exclusions" ] + } + + if (defined(invoker.namespace)) { + args += [ "--namespace=${invoker.namespace}" ] + } + } + } + + # Declare a target for c-preprocessor-generated java files + # + # NOTE: For generating Java conterparts to enums prefer using the java_cpp_enum + # rule instead. + # + # This target generates java files using the host C pre-processor. Each file in + # sources will be compiled using the C pre-processor. If include_path is + # specified, it will be passed (with --I) to the pre-processor. + # + # This target will create a single .srcjar. Adding this target to an + # android_library target's srcjar_deps will make the generated java files be + # included in that library's final outputs. + # + # Variables + # sources: list of files to be processed by the C pre-processor. For each + # file in sources, there will be one .java file in the final .srcjar. For a + # file named FooBar.template, a java file will be created with name + # FooBar.java. + # inputs: additional compile-time dependencies. Any files + # `#include`-ed in the templates should be listed here. + # defines: List of -D arguments for the preprocessor. + # + # Example + # java_cpp_template("foo_generated_enum") { + # sources = [ + # "android/java/templates/Foo.template", + # ] + # inputs = [ + # "android/java/templates/native_foo_header.h", + # ] + # } + template("java_cpp_template") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "inputs", + "public_deps", + "sources", + "testonly", + "visibility", + ]) + script = "//build/android/gyp/gcc_preprocess.py" + outputs = [ "$target_gen_dir/$target_name.srcjar" ] + + _include_dirs = [ + "//", + root_gen_dir, + ] + _rebased_include_dirs = rebase_path(_include_dirs, root_build_dir) + args = [ + "--include-dirs=$_rebased_include_dirs", + "--output", + rebase_path(outputs[0], root_build_dir), + ] + if (defined(invoker.defines)) { + foreach(_define, invoker.defines) { + args += [ + "--define", + _define, + ] + } + } + args += rebase_path(sources, root_build_dir) + } + } + + # Declare a target for generating Java classes from C++ enums. + # + # This target generates Java files from C++ enums using a script. + # + # This target will create a single .srcjar. Adding this target to an + # android_library target's srcjar_deps will make the generated java files be + # included in that library's final outputs. + # + # Variables + # sources: list of files to be processed by the script. For each annotated + # enum contained in the sources files the script will generate a .java + # file with the same name as the name of the enum. + # + # Example + # java_cpp_enum("foo_generated_enum") { + # sources = [ + # "src/native_foo_header.h", + # ] + # } + template("java_cpp_enum") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "sources" ]) + + # The sources aren't compiled so don't check their dependencies. + check_includes = false + script = "//build/android/gyp/java_cpp_enum.py" + + _srcjar_path = "${target_gen_dir}/${target_name}.srcjar" + _rebased_srcjar_path = rebase_path(_srcjar_path, root_build_dir) + _rebased_sources = rebase_path(invoker.sources, root_build_dir) + + args = [ "--srcjar=$_rebased_srcjar_path" ] + _rebased_sources + outputs = [ _srcjar_path ] + } + } + + # Declare a target for generating Java classes with string constants matching + # those found in C++ files using a python script. + # + # This target will create a single .srcjar. Adding this target to an + # android_library target's srcjar_deps will make the generated java files be + # included in that library's final outputs. + # + # Variables + # sources: list of files to be processed by the script. For each string + # constant in the source files, the script will add a corresponding + # Java string to the specified template file. + # Example + # java_cpp_strings("foo_switches") { + # sources = [ + # "src/foo_switches.cc", + # ] + # template = "src/templates/FooSwitches.java.tmpl + # } + # + # foo_switches.cc: + # + # // A switch. + # const char kASwitch = "a-switch"; + # + # FooSwitches.java.tmpl + # + # // Copyright {YEAR} The Chromium 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}, and + # // {TEMPLATE_PATH} + # + # package my.java.package; + # + # public abstract class FooSwitches {{ + # // ...snip... + # {NATIVE_STRINGS} + # // ...snip... + # }} + # + # result: + # A FooSwitches.java file, defining a class named FooSwitches in the package + # my.java.package. + template("java_cpp_strings") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "sources" ]) + + # The sources aren't compiled so don't check their dependencies. + check_includes = false + script = "//build/android/gyp/java_cpp_strings.py" + + _srcjar_path = "${target_gen_dir}/${target_name}.srcjar" + _rebased_srcjar_path = rebase_path(_srcjar_path, root_build_dir) + _rebased_sources = rebase_path(invoker.sources, root_build_dir) + _rebased_template = rebase_path(invoker.template, root_build_dir) + + args = [ + "--srcjar=$_rebased_srcjar_path", + "--template=$_rebased_template", + ] + args += _rebased_sources + sources += [ invoker.template ] + + outputs = [ _srcjar_path ] + } + } + + # Declare a target for generating Java classes with string constants matching + # those found in C++ base::Feature declarations, using a python script. + # + # This target will create a single .srcjar. Adding this target to an + # android_library target's srcjar_deps will make the generated java files be + # included in that library's final outputs. + # + # Variables + # sources: list of files to be processed by the script. For each + # base::Feature in the source files, the script will add a + # corresponding Java string for that feature's name to the + # specified template file. + # Example + # java_cpp_features("foo_features") { + # sources = [ + # "src/foo_features.cc", + # ] + # template = "src/templates/FooFeatures.java.tmpl + # } + # + # foo_features.cc: + # + # // A feature. + # const base::Feature kSomeFeature{"SomeFeature", + # base::FEATURE_DISABLED_BY_DEFAULT}; + # + # FooFeatures.java.tmpl + # + # // Copyright $YEAR The Chromium 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 my.java.package; + # + # public final class FooFeatures {{ + # // ...snip... + # {NATIVE_STRINGS} + # // ...snip... + # // Do not instantiate this class. + # private FooFeatures() {{}} + # }} + # + # result: + # A FooFeatures.java file, defining a class named FooFeatures in the package + # my.java.package. + template("java_cpp_features") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "sources" ]) + + # The sources aren't compiled so don't check their dependencies. + check_includes = false + script = "//build/android/gyp/java_cpp_features.py" + + _srcjar_path = "${target_gen_dir}/${target_name}.srcjar" + _rebased_srcjar_path = rebase_path(_srcjar_path, root_build_dir) + _rebased_sources = rebase_path(invoker.sources, root_build_dir) + _rebased_template = rebase_path(invoker.template, root_build_dir) + + args = [ + "--srcjar=$_rebased_srcjar_path", + "--template=$_rebased_template", + ] + args += _rebased_sources + sources += [ invoker.template ] + + outputs = [ _srcjar_path ] + } + } + + # Declare a target for processing a Jinja template. + # + # Variables + # input: The template file to be processed. + # includes: List of files {% include %}'ed by input. + # output: Where to save the result. + # variables: (Optional) A list of variables to make available to the template + # processing environment, e.g. ["name=foo", "color=red"]. + # + # Example + # jinja_template("chrome_public_manifest") { + # input = "java/AndroidManifest.xml" + # output = "$target_gen_dir/AndroidManifest.xml" + # } + template("jinja_template") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + inputs = [ invoker.input ] + if (defined(invoker.includes)) { + inputs += invoker.includes + } + script = "//build/android/gyp/jinja_template.py" + + outputs = [ invoker.output ] + + args = [ + "--loader-base-dir", + rebase_path("//", root_build_dir), + "--inputs", + rebase_path(invoker.input, root_build_dir), + "--output", + rebase_path(invoker.output, root_build_dir), + "--check-includes", + ] + if (defined(invoker.includes)) { + _rebased_includes = rebase_path(invoker.includes, root_build_dir) + args += [ "--includes=$_rebased_includes" ] + } + if (defined(invoker.variables)) { + args += [ "--variables=${invoker.variables}" ] + } + } + } + + # Writes native libraries to a NativeLibaries.java file. + # + # This target will create a single .srcjar. Adding this target to an + # android_library target's srcjar_deps will make the generated java files be + # included in that library's final outputs. + # + # Variables: + # native_libraries_list_file: (Optional) Path to file listing all native + # libraries to write. + # version_number: (Optional) String of expected version of 'main' native + # library. + # enable_chromium_linker: (Optional) Whether to use the Chromium linker. + # load_library_from_apk: (Optional) Whether libraries should be loaded from + # the APK without uncompressing. + # use_final_fields: True to use final fields. When false, all other + # variables must not be set. + template("write_native_libraries_java") { + _native_libraries_file = "$target_gen_dir/$target_name.srcjar" + if (target_cpu == "arm" || target_cpu == "arm64") { + _cpu_family = "CPU_FAMILY_ARM" + } else if (target_cpu == "x86" || target_cpu == "x64") { + _cpu_family = "CPU_FAMILY_X86" + } else if (target_cpu == "mipsel" || target_cpu == "mips64el") { + _cpu_family = "CPU_FAMILY_MIPS" + } else { + assert(false, "Unsupported CPU family") + } + + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + script = "//build/android/gyp/write_native_libraries_java.py" + outputs = [ _native_libraries_file ] + args = [ + "--output", + rebase_path(_native_libraries_file, root_build_dir), + "--cpu-family", + _cpu_family, + ] + if (invoker.use_final_fields) { + # Write native_libraries_list_file via depfile rather than specifyin it + # as a dep in order allow R8 to run in parallel with native compilation. + depfile = "$target_gen_dir/$target_name.d" + args += [ + "--final", + "--depfile", + rebase_path(depfile, root_build_dir), + "--native-libraries-list", + rebase_path(invoker.native_libraries_list_file, root_build_dir), + ] + if (defined(invoker.main_component_library)) { + args += [ + "--main-component-library", + invoker.main_component_library, + ] + } + if (defined(invoker.enable_chromium_linker) && + invoker.enable_chromium_linker) { + args += [ "--enable-chromium-linker" ] + } + if (defined(invoker.load_library_from_apk) && + invoker.load_library_from_apk) { + args += [ "--load-library-from-apk" ] + } + if (defined(invoker.use_modern_linker) && invoker.use_modern_linker) { + args += [ "--use-modern-linker" ] + } + } + } + } + + # Declare a target for a set of Android resources generated at build + # time and stored in a single zip archive. The content of the archive + # should match the layout of a regular Android res/ folder (but the + # archive should not include a top-level res/ directory). + # + # Note that there is no associated .srcjar, R.txt or package name + # associated with this target. + # + # Variables: + # generated_resources_zip: Generated zip archive path. + # generating_target: Name of the target generating + # generated_resources_zip. This rule will check that it is part + # of its outputs. + # deps: Specifies the dependencies of this target. Any Android resources + # listed here will be also be included *after* this one when compiling + # all resources for a final apk or junit binary. This is useful to + # ensure that the resources of the current target override those of the + # dependency as well (and would not work if you have these deps to the + # generating target's dependencies). + # + # Example + # _zip_archive = "$target_gen_dir/${target_name}.resources_zip" + # + # action("my_resources__create_zip") { + # _depfile = "$target_gen_dir/${target_name}.d" + # script = "//build/path/to/create_my_resources_zip.py" + # args = [ + # "--depfile", rebase_path(_depfile, root_build_dir), + # "--output-zip", rebase_path(_zip_archive, root_build_dir), + # ] + # inputs = [] + # outputs = _zip_archive + # depfile = _depfile + # } + # + # android_generated_resources("my_resources") { + # generated_resources_zip = _zip_archive + # generating_target = ":my_resources__create_zip" + # } + # + template("android_generated_resources") { + forward_variables_from(invoker, [ "testonly" ]) + _build_config = "$target_gen_dir/${target_name}.build_config.json" + _rtxt_out_path = "$target_gen_dir/${target_name}.R.txt" + write_build_config("$target_name$build_config_target_suffix") { + forward_variables_from(invoker, [ "resource_overlay" ]) + + build_config = _build_config + resources_zip = invoker.generated_resources_zip + type = "android_resources" + if (defined(invoker.deps)) { + possible_config_deps = invoker.deps + } + r_text = _rtxt_out_path + } + action_with_pydeps(target_name) { + forward_variables_from(invoker, [ "visibility" ]) + public_deps = [ + ":$target_name$build_config_target_suffix", + invoker.generating_target, + ] + inputs = [ invoker.generated_resources_zip ] + outputs = [ _rtxt_out_path ] + script = "//build/android/gyp/create_r_txt.py" + args = [ + "--resources-zip-path", + rebase_path(invoker.generated_resources_zip, root_build_dir), + "--rtxt-path", + rebase_path(_rtxt_out_path, root_build_dir), + ] + } + } + + # Declare a target for processing Android resources as Jinja templates. + # + # This takes an Android resource directory where each resource is a Jinja + # template, processes each template, then packages the results in a zip file + # which can be consumed by an android resources, library, or apk target. + # + # If this target is included in the deps of an android resources/library/apk, + # the resources will be included with that target. + # + # Variables + # resources: The list of resources files to process. + # res_dir: The resource directory containing the resources. + # variables: (Optional) A list of variables to make available to the template + # processing environment, e.g. ["name=foo", "color=red"]. + # + # Example + # jinja_template_resources("chrome_public_template_resources") { + # res_dir = "res_template" + # resources = ["res_template/xml/syncable.xml"] + # variables = ["color=red"] + # } + template("jinja_template_resources") { + _resources_zip = "$target_out_dir/${target_name}.resources.zip" + _generating_target_name = "${target_name}__template" + + action_with_pydeps(_generating_target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + inputs = invoker.resources + script = "//build/android/gyp/jinja_template.py" + + outputs = [ _resources_zip ] + + _rebased_resources = rebase_path(invoker.resources, root_build_dir) + args = [ + "--inputs=${_rebased_resources}", + "--inputs-base-dir", + rebase_path(invoker.res_dir, root_build_dir), + "--outputs-zip", + rebase_path(_resources_zip, root_build_dir), + "--check-includes", + ] + if (defined(invoker.variables)) { + variables = invoker.variables + args += [ "--variables=${variables}" ] + } + } + + android_generated_resources(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "deps", + "resource_overlay", + ]) + generating_target = ":$_generating_target_name" + generated_resources_zip = _resources_zip + } + } + + # Declare a prebuilt android native library. + # + # This takes a base directory and library name and then looks for the library + # in /$android_app_abi/. + # + # If you depend on this target, the library is stripped and output to the + # same locations non-prebuilt libraries are output. + # + # Variables + # base_dir: Directory where all ABIs of the library live. + # library_name: Name of the library .so file. + # + # Example + # android_native_prebuilt("elements_native") { + # base_dir = "//third_party/elements" + # lib_name = "elements.so" + # } + template("android_native_prebuilt") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + script = "//build/android/gyp/process_native_prebuilt.py" + _lib_path = "${invoker.base_dir}/$android_app_abi/${invoker.lib_name}" + _stripped_output_path = "$root_out_dir/${invoker.lib_name}" + _unstripped_output_path = + "$root_out_dir/lib.unstripped/${invoker.lib_name}" + inputs = [ _lib_path ] + outputs = [ + _stripped_output_path, + _unstripped_output_path, + ] + + # Add unstripped output to runtime deps for use by bots during stacktrace + # symbolization. + data = [ _unstripped_output_path ] + + _rebased_lib_path = rebase_path(_lib_path, root_build_dir) + _rebased_stripped_ouput_path = + rebase_path(_stripped_output_path, root_build_dir) + _rebased_unstripped_ouput_path = + rebase_path(_unstripped_output_path, root_build_dir) + _strip_tool_path = + rebase_path("//buildtools/third_party/eu-strip/bin/eu-strip", + root_build_dir) + + args = [ + "--strip-path=$_strip_tool_path", + "--input-path=$_rebased_lib_path", + "--stripped-output-path=$_rebased_stripped_ouput_path", + "--unstripped-output-path=$_rebased_unstripped_ouput_path", + ] + } + } + + # 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 + # sources: List of resource files for this target. + # deps: Specifies the dependencies of this target. Any Android resources + # listed in deps will be included by libraries/apks that depend on this + # target. + # alternative_android_sdk_dep: Optional. Alternative Android system + # android java target to use. + # android_manifest: AndroidManifest.xml for this target (optional). Will be + # merged into apks that directly or indirectly depend on this target. + # android_manifest_dep: Target that generates AndroidManifest (if applicable) + # custom_package: java package for generated .java files. + # allow_missing_resources: Do not fail if a resource exists in a directory + # but is not listed in sources. + # shared_resources: If true make a resource package that can be loaded by a + # different application at runtime to access the package's resources. + # resource_overlay: Whether the resources in 'sources' should override + # resources with the same name. Does not affect the behaviour of any + # android_resources() deps of this target. If a target with + # resource_overlay=true depends on another target with + # resource_overlay=true the target with the dependency overrides the + # other. + # r_text_file: (optional) path to pre-generated R.txt to be used when + # generating R.java instead of resource-based aapt-generated one. + # recursive_resource_deps: (optional) whether deps should be walked + # recursively to find resource deps. + # + # Example: + # android_resources("foo_resources") { + # deps = [":foo_strings_grd"] + # sources = [ + # "res/drawable/foo1.xml", + # "res/drawable/foo2.xml", + # ] + # custom_package = "org.chromium.foo" + # } + # + # android_resources("foo_resources_overrides") { + # deps = [":foo_resources"] + # sources = [ + # "res_overrides/drawable/foo1.xml", + # "res_overrides/drawable/foo2.xml", + # ] + # } + template("android_resources") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _base_path = "$target_gen_dir/$target_name" + if (defined(invoker.v14_skip)) { + not_needed(invoker, [ "v14_skip" ]) + } + + _res_sources_path = "$target_gen_dir/${invoker.target_name}.res.sources" + + _resources_zip = "$target_out_dir/$target_name.resources.zip" + _r_text_out_path = _base_path + "_R.txt" + _build_config = _base_path + ".build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + + _deps = [] + if (defined(invoker.deps)) { + _deps += invoker.deps + } + + if (defined(invoker.alternative_android_sdk_dep)) { + _deps += [ invoker.alternative_android_sdk_dep ] + } else { + _deps += [ "//third_party/android_sdk:android_sdk_java" ] + } + + _resource_files = [] + if (defined(invoker.sources)) { + _resource_files += invoker.sources + } + + _rebased_resource_files = rebase_path(_resource_files, root_build_dir) + write_file(_res_sources_path, _rebased_resource_files) + + # This is necessary so we only lint chromium resources. + if (defined(invoker.chromium_code)) { + _chromium_code = invoker.chromium_code + } else { + # Default based on whether target is in third_party. + _chromium_code = + filter_exclude([ get_label_info(":$target_name", "dir") ], + [ "*\bthird_party\b*" ]) != [] + } + + write_build_config(_build_config_target_name) { + type = "android_resources" + build_config = _build_config + resources_zip = _resources_zip + res_sources_path = _res_sources_path + chromium_code = _chromium_code + + forward_variables_from(invoker, + [ + "android_manifest", + "android_manifest_dep", + "custom_package", + "mergeable_android_manifests", + "resource_overlay", + "recursive_resource_deps", + ]) + + r_text = _r_text_out_path + possible_config_deps = _deps + + # Always merge manifests from resources. + # * Might want to change this at some point for consistency and clarity, + # but keeping for backwards-compatibility. + if (!defined(mergeable_android_manifests) && defined(android_manifest)) { + mergeable_android_manifests = [ android_manifest ] + } + } + + prepare_resources(target_name) { + forward_variables_from(invoker, + [ + "strip_drawables", + "allow_missing_resources", + "visibility", + ]) + deps = _deps + + res_sources_path = _res_sources_path + sources = _resource_files + + resources_zip = _resources_zip + r_text_out_path = _r_text_out_path + + if (defined(invoker.r_text_file)) { + r_text_in_path = invoker.r_text_file + } + } + } + + # Declare an Android assets target. + # + # Defines a set of files to include as assets in a dependent apk. + # + # To include these assets in an apk, this target should be listed in + # the apk's deps, or in the deps of a library target used by an apk. + # + # Variables + # deps: Specifies the dependencies of this target. Any Android assets + # listed in deps will be included by libraries/apks that depend on this + # target. + # sources: List of files to include as assets. + # renaming_sources: List of files to include as assets and be renamed. + # renaming_destinations: List of asset paths for files in renaming_sources. + # disable_compression: Whether to disable compression for files that are + # known to be compressable (default: false). + # treat_as_locale_paks: Causes base's BuildConfig.java to consider these + # assets to be locale paks. + # + # Example: + # android_assets("content_shell_assets") { + # deps = [ + # ":generates_foo", + # ":other_assets", + # ] + # sources = [ + # "//path/asset1.png", + # "//path/asset2.png", + # "$target_gen_dir/foo.dat", + # ] + # } + # + # android_assets("overriding_content_shell_assets") { + # deps = [ ":content_shell_assets" ] + # # Override foo.dat from content_shell_assets. + # sources = [ "//custom/foo.dat" ] + # renaming_sources = [ "//path/asset2.png" ] + # renaming_destinations = [ "renamed/asset2.png" ] + # } + template("android_assets") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + + write_build_config(_build_config_target_name) { + type = "android_assets" + build_config = _build_config + + forward_variables_from(invoker, + [ + "disable_compression", + "treat_as_locale_paks", + ]) + + if (defined(invoker.deps)) { + possible_config_deps = invoker.deps + } + + if (defined(invoker.sources)) { + asset_sources = invoker.sources + } + if (defined(invoker.renaming_sources)) { + assert(defined(invoker.renaming_destinations)) + _source_count = 0 + foreach(_, invoker.renaming_sources) { + _source_count += 1 + } + _dest_count = 0 + foreach(_, invoker.renaming_destinations) { + _dest_count += 1 + } + assert( + _source_count == _dest_count, + "android_assets() renaming_sources.length != renaming_destinations.length") + asset_renaming_sources = invoker.renaming_sources + asset_renaming_destinations = invoker.renaming_destinations + } + } + + group(target_name) { + forward_variables_from(invoker, [ "deps" ]) + public_deps = [ ":$_build_config_target_name" ] + } + } + + # Declare a group() that supports forwarding java dependency information. + # + # Example + # java_group("conditional_deps") { + # if (enable_foo) { + # deps = [":foo_java"] + # } + # } + template("java_group") { + _build_config_vars = [ + "input_jars_paths", + "mergeable_android_manifests", + "proguard_configs", + ] + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + write_build_config("$target_name$build_config_target_suffix") { + forward_variables_from(invoker, _build_config_vars) + type = "group" + build_config = "$target_gen_dir/${invoker.target_name}.build_config.json" + supports_android = true + if (defined(invoker.deps)) { + possible_config_deps = invoker.deps + } + } + foreach(_group_name, + [ + "header", + "impl", + "assetres", + ]) { + java_lib_group("${target_name}__${_group_name}") { + forward_variables_from(invoker, [ "deps" ]) + group_name = _group_name + } + } + group(target_name) { + forward_variables_from(invoker, + "*", + _build_config_vars + TESTONLY_AND_VISIBILITY) + if (!defined(deps)) { + deps = [] + } + deps += [ ":$target_name$build_config_target_suffix" ] + } + } + + # Declare a Java executable target + # + # Same as java_library, but also creates a wrapper script within + # $root_out_dir/bin. + # + # Supports all variables of java_library(), plus: + # main_class: When specified, a wrapper script is created within + # $root_build_dir/bin to launch the binary with the given class as the + # entrypoint. + # wrapper_script_name: Filename for the wrapper script (default=target_name) + # wrapper_script_args: List of additional arguments for the wrapper script. + # + # Example + # java_binary("foo") { + # sources = [ "org/chromium/foo/FooMain.java" ] + # deps = [ ":bar_java" ] + # main_class = "org.chromium.foo.FooMain" + # } + # + # java_binary("foo") { + # jar_path = "lib/prebuilt.jar" + # deps = [ ":bar_java" ] + # main_class = "org.chromium.foo.FooMain" + # } + template("java_binary") { + java_library_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + type = "java_binary" + if (!defined(data_deps)) { + data_deps = [] + } + data_deps += [ "//third_party/jdk:java_data" ] + } + } + + # Declare a Java Annotation Processor. + # + # Supports all variables of java_library(), plus: + # jar_path: Path to a prebuilt jar. Mutually exclusive with sources & + # srcjar_deps. + # main_class: The fully-quallified class name of the processor's entry + # point. + # + # Example + # java_annotation_processor("foo_processor") { + # sources = [ "org/chromium/foo/FooProcessor.java" ] + # deps = [ ":bar_java" ] + # main_class = "org.chromium.foo.FooProcessor" + # } + # + # java_annotation_processor("foo_processor") { + # jar_path = "lib/prebuilt.jar" + # main_class = "org.chromium.foo.FooMain" + # } + # + # java_library("...") { + # annotation_processor_deps = [":foo_processor"] + # } + # + template("java_annotation_processor") { + java_library_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + type = "java_annotation_processor" + } + } + + # 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. + # + # Supports all variables of java_binary(). + # + # Example + # junit_binary("foo") { + # sources = [ "org/chromium/foo/FooTest.java" ] + # deps = [ ":bar_java" ] + # } + template("junit_binary") { + testonly = true + + _java_binary_target_name = "${target_name}__java_binary" + _test_runner_target_name = "${target_name}__test_runner_script" + _main_class = "org.chromium.testing.local.JunitTestMain" + + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + _deps = [ + "//testing/android/junit:junit_test_support", + "//third_party/android_deps:robolectric_all_java", + "//third_party/junit", + "//third_party/mockito:mockito_java", + ] + if (defined(invoker.deps)) { + _deps += invoker.deps + } + if (defined(invoker.alternative_android_sdk_dep)) { + _android_sdk_dep = invoker.alternative_android_sdk_dep + } else { + _android_sdk_dep = "//third_party/android_sdk:android_sdk_java" + } + + # a package name or a manifest is required to have resources. This is + # added so that junit tests that do not care about the package name can + # still use resources without having to explicitly set one. + if (defined(invoker.package_name)) { + _package_name = invoker.package_name + } else if (!defined(invoker.android_manifest)) { + _package_name = "org.chromium.test" + } + + _resource_arsc_output = "${target_gen_dir}/${target_name}.ap_" + _compile_resources_target = "${target_name}__compile_resources" + compile_resources(_compile_resources_target) { + forward_variables_from(invoker, [ "android_manifest" ]) + deps = _deps + android_sdk_dep = _android_sdk_dep + build_config_dep = ":$_build_config_target_name" + build_config = _build_config + if (defined(_package_name)) { + rename_manifest_package = _package_name + } + if (!defined(android_manifest)) { + android_manifest = "//build/android/AndroidManifest.xml" + } + arsc_output = _resource_arsc_output + min_sdk_version = default_min_sdk_version + target_sdk_version = android_sdk_version + } + + _jni_srcjar_target = "${target_name}__final_jni" + _outer_target_name = target_name + generate_jni_registration(_jni_srcjar_target) { + enable_native_mocks = true + require_native_mocks = true + targets = [ ":$_outer_target_name" ] + } + + java_library_impl(_java_binary_target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "deps" ]) + type = "junit_binary" + main_target_name = invoker.target_name + + # Include the android SDK jar(s) for resource processing. + include_android_sdk = true + + # Robolectric can handle deps that set !supports_android as well those + # that set requires_android. + bypass_platform_checks = true + deps = _deps + testonly = true + main_class = _main_class + wrapper_script_name = "helper/$main_target_name" + + # As of April 2021, adding -XX:TieredStopAtLevel=1 does not affect the + # wall time of a single robolectric shard, but does reduce the CPU time by + # 66%, which makes sharding more effective. + tiered_stop_at_level_one = true + + if (!defined(srcjar_deps)) { + srcjar_deps = [] + } + srcjar_deps += [ + ":$_compile_resources_target", + ":$_jni_srcjar_target", + + # This dep is required for any targets that depend on //base:base_java. + "//build/android:build_config_gen", + ] + } + + test_runner_script(_test_runner_target_name) { + test_name = invoker.target_name + test_suite = invoker.target_name + test_type = "junit" + ignore_all_data_deps = true + resource_apk = _resource_arsc_output + } + + group(target_name) { + forward_variables_from(invoker, + [ + "assert_no_deps", + "visibility", + ]) + public_deps = [ + ":$_build_config_target_name", + ":$_java_binary_target_name", + ":$_test_runner_target_name", + ] + } + } + + # 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. + # public_deps: Dependencies that this target exposes as part of its public API. + # public_deps do not need to be listed in both the 'deps' and 'public_deps' lists. + # annotation_processor_deps: List of java_annotation_processor targets to + # use when compiling. + # + # jar_path: Path to a prebuilt jar. Mutually exclusive with sources & + # srcjar_deps. + # sources: List of .java files included in this library. + # srcjar_deps: List of srcjar dependencies. The .java files in the srcjars + # will be added to sources and be included in this library. + # + # input_jars_paths: A list of paths to the jars that should be included + # in the compile-time classpath. These are in addition to library .jars + # that appear in deps. + # + # chromium_code: If true, extra analysis warning/errors will be enabled. + # enable_errorprone: If true, enables the errorprone compiler. + # skip_build_server: If true, avoids sending tasks to the build server. + # + # jar_excluded_patterns: List of patterns of .class files to exclude. + # jar_included_patterns: List of patterns of .class files to include. + # When omitted, all classes not matched by jar_excluded_patterns are + # included. When specified, all non-matching .class files are stripped. + # + # low_classpath_priority: Indicates that the library should be placed at the + # end of the classpath. The default classpath order has libraries ordered + # before the libraries that they depend on. 'low_classpath_priority' is + # useful when one java_library() overrides another via + # 'jar_excluded_patterns' and the overriding library does not depend on + # the overridee. + # + # output_name: File name for the output .jar (not including extension). + # Defaults to the input .jar file name. + # + # proguard_configs: List of proguard configs to use in final apk step for + # any apk that depends on this library. + # + # 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. + # enable_desugar: If false, disables desugaring of lambdas, etc. Use this + # only when you are sure the library does not require desugaring. E.g. + # to hide warnings shown from desugaring. + # + # additional_jar_files: Use to package additional files (Java resources) + # into the output jar. Pass a list of length-2 lists with format: + # [ [ path_to_file, path_to_put_in_jar ] ] + # + # javac_args: Additional arguments to pass to javac. + # errorprone_args: Additional arguments to pass to errorprone. + # + # data_deps, testonly + # + # Example + # java_library("foo_java") { + # sources = [ + # "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", "org/chromium/FooService\$*.class" + # ] + # } + template("java_library") { + java_library_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + type = "java_library" + } + } + + # Declare a java library target for a prebuilt jar + # + # Supports all variables of java_library(). + # + # Example + # java_prebuilt("foo_java") { + # jar_path = "foo.jar" + # deps = [ + # ":foo_resources", + # ":bar_java" + # ] + # } + template("java_prebuilt") { + java_library_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + type = "java_library" + } + } + + # Combines all dependent .jar files into a single .jar file. + # + # Variables: + # output: Path to the output jar. + # override_build_config: Use a pre-existing .build_config. Must be of type + # "apk". + # use_interface_jars: Use all dependent interface .jars rather than + # implementation .jars. + # use_unprocessed_jars: Use unprocessed / undesugared .jars. + # direct_deps_only: Do not recurse on deps. + # jar_excluded_patterns (optional) + # List of globs for paths to exclude. + # + # Example + # dist_jar("lib_fatjar") { + # deps = [ ":my_java_lib" ] + # output = "$root_build_dir/MyLibrary.jar" + # } + template("dist_jar") { + # TODO(crbug.com/1042017): Remove. + not_needed(invoker, [ "no_build_hooks" ]) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + _supports_android = + !defined(invoker.supports_android) || invoker.supports_android + _use_interface_jars = + defined(invoker.use_interface_jars) && invoker.use_interface_jars + _use_unprocessed_jars = + defined(invoker.use_unprocessed_jars) && invoker.use_unprocessed_jars + _direct_deps_only = + defined(invoker.direct_deps_only) && invoker.direct_deps_only + assert(!(_use_unprocessed_jars && _use_interface_jars), + "Cannot set both use_interface_jars and use_unprocessed_jars") + + _jar_target_name = target_name + + _deps = [] + if (defined(invoker.deps)) { + _deps = invoker.deps + } + if (_supports_android) { + _deps += [ "//third_party/android_sdk:android_sdk_java" ] + } + + if (defined(invoker.override_build_config)) { + _build_config = invoker.override_build_config + } else { + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + + write_build_config(_build_config_target_name) { + type = "dist_jar" + supports_android = _supports_android + requires_android = + defined(invoker.requires_android) && invoker.requires_android + possible_config_deps = _deps + ignore_dependency_public_deps = _direct_deps_only + build_config = _build_config + } + + _deps += [ ":$_build_config_target_name" ] + } + + _rebased_build_config = rebase_path(_build_config, root_build_dir) + action_with_pydeps(_jar_target_name) { + forward_variables_from(invoker, [ "data" ]) + script = "//build/android/gyp/zip.py" + depfile = "$target_gen_dir/$target_name.d" + deps = _deps + + inputs = [ _build_config ] + + outputs = [ invoker.output ] + + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--output", + rebase_path(invoker.output, root_build_dir), + "--no-compress", + ] + + if (_direct_deps_only) { + if (_use_interface_jars) { + args += [ "--input-zips=@FileArg($_rebased_build_config:javac:interface_classpath)" ] + } else if (_use_unprocessed_jars) { + args += [ + "--input-zips=@FileArg($_rebased_build_config:javac:classpath)", + ] + } else { + assert( + false, + "direct_deps_only does not work without use_interface_jars or use_unprocessed_jars") + } + } else { + if (_use_interface_jars) { + args += [ "--input-zips=@FileArg($_rebased_build_config:dist_jar:all_interface_jars)" ] + } else if (_use_unprocessed_jars) { + args += [ "--input-zips=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)" ] + } else { + args += [ "--input-zips=@FileArg($_rebased_build_config:deps_info:device_classpath)" ] + } + } + _excludes = [] + if (defined(invoker.jar_excluded_patterns)) { + _excludes += invoker.jar_excluded_patterns + } + if (_use_interface_jars) { + # Turbine adds files like: META-INF/TRANSITIVE/.../Foo.class + # These confuse proguard: https://crbug.com/1081443 + _excludes += [ "META-INF/*" ] + } + if (_excludes != []) { + args += [ "--input-zips-excluded-globs=$_excludes" ] + } + } + } + + # Combines all dependent .jar files into a single proguarded .dex file. + # + # Variables: + # output: Path to the output dex. + # proguard_enabled: Whether to enable R8. + # proguard_configs: List of proguard configs. + # proguard_enable_obfuscation: Whether to enable obfuscation (default=true). + # + # Example + # dist_dex("lib_fatjar") { + # deps = [ ":my_java_lib" ] + # output = "$root_build_dir/MyLibrary.jar" + # } + template("dist_dex") { + _deps = [ "//third_party/android_sdk:android_sdk_java" ] + if (defined(invoker.deps)) { + _deps += invoker.deps + } + + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + + write_build_config(_build_config_target_name) { + type = "dist_jar" + forward_variables_from(invoker, + [ + "proguard_configs", + "proguard_enabled", + ]) + supports_android = true + requires_android = true + possible_config_deps = _deps + build_config = _build_config + } + + _deps += [ ":$_build_config_target_name" ] + + dex(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + "proguard_configs", + "proguard_enabled", + "proguard_enable_obfuscation", + "min_sdk_version", + ]) + deps = _deps + build_config = _build_config + enable_multidex = false + output = invoker.output + if (defined(proguard_enabled) && proguard_enabled) { + # The individual dependencies would have caught real missing deps in + # their respective dex steps. False positives that were suppressed at + # per-target dex steps are emitted here since this is using jar files + # rather than dex files. + ignore_desugar_missing_deps = true + + # When trying to build a stand-alone .dex, don't add in jdk_libs_dex. + supports_jdk_library_desugaring = false + } else { + _rebased_build_config = rebase_path(_build_config, root_build_dir) + input_dex_filearg = + "@FileArg(${_rebased_build_config}:final_dex:all_dex_files)" + } + } + } + + # Creates an Android .aar library. + # + # Currently supports: + # * AndroidManifest.xml + # * classes.jar + # * jni/ + # * res/ + # * R.txt + # * proguard.txt + # Does not yet support: + # * public.txt + # * annotations.zip + # * assets/ + # See: https://developer.android.com/studio/projects/android-library.html#aar-contents + # + # Variables: + # output: Path to the output .aar. + # proguard_configs: List of proguard configs (optional). + # android_manifest: Path to AndroidManifest.xml (optional). + # native_libraries: list of native libraries (optional). + # direct_deps_only: Do not recurse on deps. (optional, defaults false). + # jar_excluded_patterns (optional): List of globs for paths to exclude. + # jar_included_patterns (optional): List of globs for paths to include. + # + # Example + # dist_aar("my_aar") { + # deps = [ ":my_java_lib" ] + # output = "$root_build_dir/MyLibrary.aar" + # } + template("dist_aar") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _deps = [] + if (defined(invoker.deps)) { + _deps = invoker.deps + } + + _direct_deps_only = + defined(invoker.direct_deps_only) && invoker.direct_deps_only + + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target_name = "$target_name$build_config_target_suffix" + + write_build_config(_build_config_target_name) { + type = "dist_aar" + forward_variables_from(invoker, [ "proguard_configs" ]) + possible_config_deps = _deps + supports_android = true + requires_android = true + ignore_dependency_public_deps = _direct_deps_only + build_config = _build_config + } + + _deps += [ ":$_build_config_target_name" ] + + _rebased_build_config = rebase_path(_build_config, root_build_dir) + + action_with_pydeps(target_name) { + forward_variables_from(invoker, [ "data" ]) + depfile = "$target_gen_dir/$target_name.d" + deps = _deps + script = "//build/android/gyp/dist_aar.py" + + inputs = [ _build_config ] + + # Although these will be listed as deps in the depfile, they must also + # appear here so that "gn analyze" knows about them. + # https://crbug.com/827197 + if (defined(invoker.proguard_configs)) { + inputs += invoker.proguard_configs + } + + outputs = [ invoker.output ] + + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--output", + rebase_path(invoker.output, root_build_dir), + "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)", + "--r-text-files=@FileArg($_rebased_build_config:deps_info:dependency_r_txt_files)", + "--proguard-configs=@FileArg($_rebased_build_config:deps_info:proguard_all_configs)", + ] + if (_direct_deps_only) { + args += [ "--jars=@FileArg($_rebased_build_config:javac:classpath)" ] + } else { + args += [ "--jars=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)" ] + } + if (defined(invoker.android_manifest)) { + args += [ + "--android-manifest", + rebase_path(invoker.android_manifest, root_build_dir), + ] + } + if (defined(invoker.native_libraries) && invoker.native_libraries != []) { + inputs += invoker.native_libraries + _rebased_native_libraries = + rebase_path(invoker.native_libraries, root_build_dir) + + args += [ + "--native-libraries=$_rebased_native_libraries", + "--abi=$android_app_abi", + ] + } + if (defined(invoker.jar_excluded_patterns)) { + args += [ "--jar-excluded-globs=${invoker.jar_excluded_patterns}" ] + } + if (defined(invoker.jar_included_patterns)) { + args += [ "--jar-included-globs=${invoker.jar_included_patterns}" ] + } + if (defined(invoker.resource_included_patterns)) { + args += [ + "--resource-included-globs=${invoker.resource_included_patterns}", + ] + } + } + } + + # Declare an Android library target + # + # This target creates an Android library containing java code and Android + # resources. + # + # Supports all variables of java_library(), plus: + # deps: In addition to defining java deps, this can also include + # android_assets() and android_resources() targets. + # alternative_android_sdk_ijar: if set, the given android_sdk_ijar file + # replaces the default android_sdk_ijar. + # alternative_android_sdk_ijar_dep: the target that generates + # alternative_android_sdk_ijar, must be set if alternative_android_sdk_ijar + # is used. + # alternative_android_sdk_jar: actual jar corresponding to + # alternative_android_sdk_ijar, must be set if alternative_android_sdk_ijar + # is used. + # + # Example + # android_library("foo_java") { + # sources = [ + # "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", "org/chromium/FooService\$*.class" + # ] + # } + template("android_library") { + java_library(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + 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", + "*/GEN_JNI.class", + ] + } + } + + # Declare an Android library target for a prebuilt jar + # + # This target creates an Android library containing java code and Android + # resources. + # + # Supports all variables of android_library(). + # + # Example + # android_java_prebuilt("foo_java") { + # jar_path = "foo.jar" + # deps = [ + # ":foo_resources", + # ":bar_java" + # ] + # } + template("android_java_prebuilt") { + android_library(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + } + } + + template("android_system_java_prebuilt") { + java_library_impl(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + supports_android = true + type = "system_java_library" + } + } + + # Creates org/chromium/build/BuildConfig.java + # This doesn't really belong in //build since it genates a file for //base. + # However, we don't currently have a better way to include this file in all + # apks that depend on //base:base_java. + # + # Variables: + # use_final_fields: True to use final fields. When false, all other + # variables must not be set. + # enable_multidex: Value for ENABLE_MULTIDEX. + # min_sdk_version: Value for MIN_SDK_VERSION. + # bundles_supported: Whether or not this target can be treated as a bundle. + # resources_version_variable: + # is_incremental_install: + # isolated_splits_enabled: Value for ISOLATED_SPLITS_ENABLED. + template("generate_build_config_srcjar") { + java_cpp_template(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + sources = [ "//build/android/java/templates/BuildConfig.template" ] + defines = [] + + # Set these even when !use_final_fields so that they have correct default + # values within junit_binary(), which ignores jar_excluded_patterns. + if (enable_java_asserts) { + defines += [ "_ENABLE_ASSERTS" ] + } + if (use_cfi_diag || is_ubsan || is_ubsan_security || is_ubsan_vptr) { + defines += [ "_IS_UBSAN" ] + } + + if (is_chrome_branded) { + defines += [ "_IS_CHROME_BRANDED" ] + } + + if (is_chromecast && chromecast_branding == "internal") { + defines += [ "_IS_CHROMECAST_BRANDING_INTERNAL" ] + } + + if (defined(invoker.bundles_supported) && invoker.bundles_supported) { + defines += [ "_BUNDLES_SUPPORTED" ] + } + + if (defined(invoker.isolated_splits_enabled) && + invoker.isolated_splits_enabled) { + defines += [ "_ISOLATED_SPLITS_ENABLED" ] + } + + if (defined(invoker.is_incremental_install) && + invoker.is_incremental_install) { + defines += [ "_IS_INCREMENTAL_INSTALL" ] + } + + if (invoker.use_final_fields) { + forward_variables_from(invoker, [ "deps" ]) + defines += [ "USE_FINAL" ] + if (invoker.enable_multidex) { + defines += [ "ENABLE_MULTIDEX" ] + } + if (defined(invoker.min_sdk_version)) { + defines += [ "_MIN_SDK_VERSION=${invoker.min_sdk_version}" ] + } + if (defined(invoker.resources_version_variable)) { + defines += [ + "_RESOURCES_VERSION_VARIABLE=${invoker.resources_version_variable}", + ] + } + } + } + } + + # Creates ProductConfig.java, a file containing product-specific configuration. + # + # Currently, this includes the list of locales, both in their compressed and + # uncompressed format, as well as library loading + # + # Variables: + # build_config: Path to build_config used for locale lists. + # is_bundle_module: Whether or not this target is part of a bundle build. + # java_package: Java package for the generated class. + # use_chromium_linker: + # use_modern_linker: + template("generate_product_config_srcjar") { + java_cpp_template(target_name) { + defines = [] + _use_final = + defined(invoker.build_config) || + defined(invoker.use_chromium_linker) || + defined(invoker.use_modern_linker) || defined(invoker.is_bundle) + if (_use_final) { + defines += [ "USE_FINAL" ] + } + + sources = [ "//build/android/java/templates/ProductConfig.template" ] + defines += [ "PACKAGE=${invoker.java_package}" ] + + _use_chromium_linker = + defined(invoker.use_chromium_linker) && invoker.use_chromium_linker + _use_modern_linker = + defined(invoker.use_modern_linker) && invoker.use_modern_linker + _is_bundle = defined(invoker.is_bundle_module) && invoker.is_bundle_module + defines += [ + "USE_CHROMIUM_LINKER_VALUE=$_use_chromium_linker", + "USE_MODERN_LINKER_VALUE=$_use_modern_linker", + "IS_BUNDLE_VALUE=$_is_bundle", + ] + if (defined(invoker.build_config)) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + _rebased_build_config = + rebase_path(invoker.build_config, root_build_dir) + defines += [ "LOCALE_LIST=@FileArg($_rebased_build_config:deps_info:locales_java_list)" ] + } + } + } + + # Declare an Android app module target, which is used as the basis for an + # Android APK or an Android app bundle module. + # + # Supports all variables of android_library(), plus: + # android_manifest: Path to AndroidManifest.xml. NOTE: This manifest must + # not contain a element. Use [min|target|max]_sdk_version + # instead. + # android_manifest_dep: Target that generates AndroidManifest (if applicable) + # png_to_webp: If true, pngs (with the exception of 9-patch) are + # converted to webp during resource packaging. + # loadable_modules: List of paths to native libraries to include. Different + # from |shared_libraries| in that: + # * dependencies of this .so are not automatically included + # * ".cr.so" is never added + # * they are not side-loaded when incremental_install=true. + # * load_library_from_apk, use_chromium_linker, + # and enable_relocation_packing do not apply + # Use this instead of shared_libraries when you are going to load the library + # conditionally, and only when shared_libraries doesn't work for you. + # secondary_abi_loadable_modules: This is the loadable_modules analog to + # secondary_abi_shared_libraries. + # shared_libraries: List shared_library targets to bundle. If these + # libraries depend on other shared_library targets, those dependencies will + # also be included in the apk (e.g. for is_component_build). + # secondary_abi_shared_libraries: secondary abi shared_library targets to + # bundle. If these libraries depend on other shared_library targets, those + # dependencies will also be included in the apk (e.g. for is_component_build). + # native_lib_placeholders: List of placeholder filenames to add to the apk + # (optional). + # secondary_native_lib_placeholders: List of placeholder filenames to add to + # the apk for the secondary ABI (optional). + # generate_buildconfig_java: If defined and false, skip generating the + # BuildConfig java class describing the build configuration. The default + # is true when building with Chromium for non-test APKs. + # generate_final_jni: If defined and false, skip generating the + # GEN_JNI srcjar. + # jni_registration_header: If specified, causes the + # ${target_name}__final_jni target to additionally output a + # header file to this path for use with manual JNI registration. + # jni_sources_exclusions: List of source path to exclude from the + # final_jni step. + # aapt_locale_allowlist: If set, all locales not in this list will be + # stripped from resources.arsc. + # resource_exclusion_regex: Causes all drawable images matching the regex to + # be excluded (mipmaps are still included). + # resource_exclusion_exceptions: A list of globs used when + # resource_exclusion_regex is set. Files that match this list will + # still be included. + # resource_values_filter_rules: List of "source_path:name_regex" used to + # filter out unwanted values/ resources. + # shared_resources: True if this is a runtime shared library APK, like + # the system_webview_apk target. Ensures that its resources can be + # used by the loading application process. + # app_as_shared_lib: True if this is a regular application apk that can + # also serve as a runtime shared library, like the monochrome_public_apk + # target. Ensures that the resources are usable both by the APK running + # as an application, or by another process that loads it at runtime. + # shared_resources_allowlist_target: Optional name of a target specifying + # an input R.txt file that lists the resources that can be exported + # by the APK when shared_resources or app_as_shared_lib is defined. + # uncompress_shared_libraries: True if shared libraries should be stored + # uncompressed in the APK. Must be unset or true if load_library_from_apk + # is set to true. + # uncompress_dex: Store final .dex files uncompressed in the apk. + # strip_resource_names: True if resource names should be stripped from the + # resources.arsc file in the apk or module. + # strip_unused_resources: True if unused resources should be stripped from + # the apk or module. + # short_resource_paths: True if resource paths should be shortened in the + # apk or module. + # resources_config_paths: List of paths to the aapt2 optimize config files + # that tags resources with acceptable/non-acceptable optimizations. + # expected_android_manifest: Enables verification of expected merged + # manifest based on a golden file. + # resource_ids_provider_dep: If passed, this target will use the resource + # IDs generated by {resource_ids_provider_dep}__compile_res during + # resource compilation. + # enforce_resource_overlays_in_tests: Enables check for testonly targets that + # dependent resource targets which override another target set + # overlay_resources=true. This check is on for non-test targets and + # cannot be disabled. + # static_library_dependent_targets: A list of scopes describing targets that + # use this target as a static library. Common Java code from the targets + # listed in static_library_dependent_targets will be moved into this + # target. Scope members are name and is_resource_ids_provider. + # static_library_provider: Specifies a single target that this target will + # use as a static library APK. + # static_library_synchronized_proguard: When proguard is enabled, the + # static_library_provider target will provide the dex file(s) for this + # target. + # min_sdk_version: The minimum Android SDK version this target supports. + # Optional, default $default_min_sdk_version. + # target_sdk_version: The target Android SDK version for this target. + # Optional, default to android_sdk_version. + # max_sdk_version: The maximum Android SDK version this target supports. + # Optional, default not set. + # require_native_mocks: Enforce that any native calls using + # org.chromium.base.annotations.NativeMethods must have a mock set + # (optional). + # enable_native_mocks: Allow native calls using + # org.chromium.base.annotations.NativeMethods to be mocked in tests + # (optional). + # product_config_java_packages: Optional list of java packages. If given, a + # ProductConfig.java file will be generated for each package. + # enable_proguard_checks: Turns on -checkdiscard directives and missing + # symbols check in the proguard step (default=true). + # disable_r8_outlining: Turn off outlining during the proguard step. + # annotation_processor_deps: List of java_annotation_processor targets to + # use when compiling the sources given to this target (optional). + # processor_args_javac: List of args to pass to annotation processors when + # compiling sources given to this target (optional). + # bundles_supported: Enable Java code to treat this target as a bundle + # whether (by default determined by the target type). + # main_component_library: Specifies the name of the base component's library + # in a component build. If given, the system will find dependent native + # libraries at runtime by inspecting this library (optional). + # expected_libs_and_assets: Verify the list of included native libraries + # and assets is consistent with the given expectation file. + # expected_libs_and_assets_base: Treat expected_libs_and_assets as a diff + # with this file as the base. + # expected_proguard_config: Checks that the merged set of proguard flags + # matches the given config. + # expected_proguard_config_base: Treat expected_proguard_config as a diff + # with this file as the base. + template("android_apk_or_module") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + assert(defined(invoker.android_manifest)) + _base_path = "$target_out_dir/$target_name/$target_name" + _build_config = "$target_gen_dir/$target_name.build_config.json" + _build_config_target = "$target_name$build_config_target_suffix" + + _min_sdk_version = default_min_sdk_version + _target_sdk_version = android_sdk_version + if (defined(invoker.min_sdk_version)) { + _min_sdk_version = invoker.min_sdk_version + } + if (defined(invoker.target_sdk_version)) { + _target_sdk_version = invoker.target_sdk_version + } + + _template_name = target_name + + _is_bundle_module = + defined(invoker.is_bundle_module) && invoker.is_bundle_module + if (_is_bundle_module) { + _is_base_module = + defined(invoker.is_base_module) && invoker.is_base_module + } + + _enable_multidex = + !defined(invoker.enable_multidex) || invoker.enable_multidex + + if (!_is_bundle_module) { + _final_apk_path = invoker.final_apk_path + _final_rtxt_path = "${_final_apk_path}.R.txt" + } + + _res_size_info_path = "$target_out_dir/$target_name.ap_.info" + if (!_is_bundle_module) { + _final_apk_path_no_ext_list = + process_file_template([ _final_apk_path ], + "{{source_dir}}/{{source_name_part}}") + _final_apk_path_no_ext = _final_apk_path_no_ext_list[0] + not_needed([ "_final_apk_path_no_ext" ]) + } + + # Non-base bundle modules create only proto resources. + if (!_is_bundle_module || _is_base_module) { + _arsc_resources_path = "$target_out_dir/$target_name.ap_" + } + if (_is_bundle_module) { + # Path to the intermediate proto-format resources zip file. + _proto_resources_path = "$target_out_dir/$target_name.proto.ap_" + } else { + # resource_sizes.py needs to be able to find the unpacked resources.arsc + # file based on apk name to compute normatlized size. + _resource_sizes_arsc_path = + "$root_out_dir/arsc/" + + rebase_path(_final_apk_path_no_ext, root_build_dir) + ".ap_" + } + + if (defined(invoker.version_code)) { + _version_code = invoker.version_code + } else { + _version_code = android_default_version_code + } + + if (android_override_version_code != "") { + _version_code = android_override_version_code + } + + if (defined(invoker.version_name)) { + _version_name = invoker.version_name + } else { + _version_name = android_default_version_name + } + + if (android_override_version_name != "") { + _version_name = android_override_version_name + } + + _deps = [] + if (defined(invoker.deps)) { + _deps = invoker.deps + } + + _srcjar_deps = [] + if (defined(invoker.srcjar_deps)) { + _srcjar_deps = invoker.srcjar_deps + } + + _android_root_manifest_deps = [] + if (defined(invoker.android_manifest_dep)) { + _android_root_manifest_deps = [ invoker.android_manifest_dep ] + } + _android_root_manifest = invoker.android_manifest + + _use_chromium_linker = + defined(invoker.use_chromium_linker) && invoker.use_chromium_linker + _use_modern_linker = + defined(invoker.use_modern_linker) && invoker.use_modern_linker + + _load_library_from_apk = + defined(invoker.load_library_from_apk) && invoker.load_library_from_apk + + not_needed([ + "_use_chromium_linker", + "_use_modern_linker", + ]) + assert(!_load_library_from_apk || _use_chromium_linker, + "load_library_from_apk requires use_chromium_linker") + + # Make sure that uncompress_shared_libraries is set to true if + # load_library_from_apk is true. + if (defined(invoker.uncompress_shared_libraries)) { + _uncompress_shared_libraries = invoker.uncompress_shared_libraries + assert(!_load_library_from_apk || _uncompress_shared_libraries) + } else { + _uncompress_shared_libraries = _load_library_from_apk + } + + # The dependency that makes the chromium linker, if any is needed. + _native_libs_deps = [] + _shared_libraries_is_valid = + defined(invoker.shared_libraries) && invoker.shared_libraries != [] + + if (_shared_libraries_is_valid) { + _native_libs_deps += invoker.shared_libraries + + # Write shared library output files of all dependencies to a file. Those + # will be the shared libraries packaged into the APK. + _shared_library_list_file = + "$target_gen_dir/${_template_name}.native_libs" + generated_file("${_template_name}__shared_library_list") { + deps = _native_libs_deps + outputs = [ _shared_library_list_file ] + data_keys = [ "shared_libraries" ] + walk_keys = [ "shared_libraries_barrier" ] + rebase = root_build_dir + } + } else { + # Must exist for instrumentation_test_apk() to depend on. + group("${_template_name}__shared_library_list") { + } + } + + _secondary_abi_native_libs_deps = [] + + if (defined(invoker.secondary_abi_shared_libraries) && + invoker.secondary_abi_shared_libraries != []) { + _secondary_abi_native_libs_deps = invoker.secondary_abi_shared_libraries + + # Write shared library output files of all dependencies to a file. Those + # will be the shared libraries packaged into the APK. + _secondary_abi_shared_library_list_file = + "$target_gen_dir/${_template_name}.secondary_abi_native_libs" + generated_file("${_template_name}__secondary_abi_shared_library_list") { + deps = _secondary_abi_native_libs_deps + outputs = [ _secondary_abi_shared_library_list_file ] + data_keys = [ "shared_libraries" ] + walk_keys = [ "shared_libraries_barrier" ] + rebase = root_build_dir + } + } else { + # Must exist for instrumentation_test_apk() to depend on. + group("${_template_name}__secondary_abi_shared_library_list") { + } + } + + _rebased_build_config = rebase_path(_build_config, root_build_dir) + assert(_rebased_build_config != "") # Mark as used. + + _generate_buildconfig_java = !defined(invoker.apk_under_test) + if (defined(invoker.generate_buildconfig_java)) { + _generate_buildconfig_java = invoker.generate_buildconfig_java + } + + _generate_productconfig_java = defined(invoker.product_config_java_packages) + + # JNI generation usually goes hand-in-hand with buildconfig generation. + _generate_final_jni = _generate_buildconfig_java + if (defined(invoker.generate_final_jni)) { + _generate_final_jni = invoker.generate_final_jni + } + + _proguard_enabled = + defined(invoker.proguard_enabled) && invoker.proguard_enabled + + if (!_is_bundle_module && _proguard_enabled) { + _proguard_mapping_path = "$_final_apk_path.mapping" + } + + # TODO(http://crbug.com/901465): Move shared Java code to static libraries + # when !_proguard_enabled too. + _is_static_library_provider = + defined(invoker.static_library_dependent_targets) && _proguard_enabled + if (_is_static_library_provider) { + _static_library_sync_dex_path = "$_base_path.synchronized.r8dex.jar" + _resource_ids_provider_deps = [] + foreach(_target, invoker.static_library_dependent_targets) { + if (_target.is_resource_ids_provider) { + assert(_resource_ids_provider_deps == [], + "Can only have 1 resource_ids_provider_dep") + _resource_ids_provider_deps += [ _target.name ] + } + } + _resource_ids_provider_dep = _resource_ids_provider_deps[0] + } else if (defined(invoker.resource_ids_provider_dep)) { + _resource_ids_provider_dep = invoker.resource_ids_provider_dep + } + + if (_is_static_library_provider) { + _shared_resources_allowlist_target = _resource_ids_provider_dep + } else if (defined(invoker.shared_resources_allowlist_target)) { + _shared_resources_allowlist_target = + invoker.shared_resources_allowlist_target + } + + _uses_static_library = defined(invoker.static_library_provider) + _uses_static_library_synchronized_proguard = + defined(invoker.static_library_synchronized_proguard) && + invoker.static_library_synchronized_proguard + + if (_uses_static_library_synchronized_proguard) { + assert(_uses_static_library) + + # These will be provided by the static library APK. + _generate_buildconfig_java = false + _generate_final_jni = false + } + + # TODO(crbug.com/864142): Allow incremental installs of bundle modules. + _incremental_apk = !_is_bundle_module && + !(defined(invoker.never_incremental) && + invoker.never_incremental) && incremental_install + if (_incremental_apk) { + _target_dir_name = get_label_info(target_name, "dir") + _incremental_install_json_path = "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.incremental.json" + _incremental_apk_path = "${_final_apk_path_no_ext}_incremental.apk" + } + + if (!_incremental_apk) { + # Bundle modules don't build the dex here, but need to write this path + # to their .build_config.json file. + if (_proguard_enabled) { + _final_dex_path = "$_base_path.r8dex.jar" + } else { + _final_dex_path = "$_base_path.mergeddex.jar" + } + } + + _android_manifest = + "$target_gen_dir/${_template_name}_manifest/AndroidManifest.xml" + _merge_manifest_target = "${_template_name}__merge_manifests" + merge_manifests(_merge_manifest_target) { + forward_variables_from(invoker, + [ + "manifest_package", + "max_sdk_version", + ]) + input_manifest = _android_root_manifest + output_manifest = _android_manifest + build_config = _build_config + min_sdk_version = _min_sdk_version + target_sdk_version = _target_sdk_version + deps = _android_root_manifest_deps + [ ":$_build_config_target" ] + } + + _final_deps = [] + + _enable_main_dex_list = _enable_multidex && _min_sdk_version < 21 + if (_enable_main_dex_list) { + _generated_proguard_main_dex_config = + "$_base_path.resources.main-dex-proguard.txt" + } + _generated_proguard_config = "$_base_path.resources.proguard.txt" + + if (_generate_buildconfig_java && + defined(invoker.product_version_resources_dep)) { + # Needs to be added as a .build_config.json dep to pick up resources. + _deps += [ invoker.product_version_resources_dep ] + } + + if (defined(invoker.alternative_android_sdk_dep)) { + _android_sdk_dep = invoker.alternative_android_sdk_dep + } else { + _android_sdk_dep = "//third_party/android_sdk:android_sdk_java" + } + + if (defined(_shared_resources_allowlist_target)) { + _allowlist_gen_dir = + get_label_info(_shared_resources_allowlist_target, "target_gen_dir") + _allowlist_target_name = + get_label_info(_shared_resources_allowlist_target, "name") + _allowlist_r_txt_path = + "${_allowlist_gen_dir}/${_allowlist_target_name}" + + "__compile_resources_R.txt" + _allowlist_deps = + "${_shared_resources_allowlist_target}__compile_resources" + } + + _compile_resources_target = "${_template_name}__compile_resources" + _compile_resources_rtxt_out = + "${target_gen_dir}/${_compile_resources_target}_R.txt" + _compile_resources_emit_ids_out = + "${target_gen_dir}/${_compile_resources_target}.resource_ids" + compile_resources(_compile_resources_target) { + forward_variables_from(invoker, + [ + "aapt_locale_allowlist", + "app_as_shared_lib", + "enforce_resource_overlays_in_tests", + "expected_android_manifest", + "expected_android_manifest_base", + "extra_verification_manifest", + "extra_verification_manifest_dep", + "manifest_package", + "max_sdk_version", + "no_xml_namespaces", + "package_id", + "package_name", + "png_to_webp", + "r_java_root_package_name", + "resource_exclusion_exceptions", + "resource_exclusion_regex", + "resource_values_filter_rules", + "shared_resources", + "shared_resources_allowlist_locales", + "uses_split", + ]) + android_manifest = _android_manifest + android_manifest_dep = ":$_merge_manifest_target" + version_code = _version_code + version_name = _version_name + min_sdk_version = _min_sdk_version + target_sdk_version = _target_sdk_version + + if (defined(expected_android_manifest)) { + top_target_name = _template_name + } + + if (defined(_resource_ids_provider_dep)) { + resource_ids_provider_dep = _resource_ids_provider_dep + } + + if (defined(invoker.post_process_package_resources_script)) { + post_process_script = invoker.post_process_package_resources_script + } + r_text_out_path = _compile_resources_rtxt_out + emit_ids_out_path = _compile_resources_emit_ids_out + size_info_path = _res_size_info_path + proguard_file = _generated_proguard_config + if (_enable_main_dex_list) { + proguard_file_main_dex = _generated_proguard_main_dex_config + } + + build_config = _build_config + build_config_dep = ":$_build_config_target" + android_sdk_dep = _android_sdk_dep + deps = _deps + + # The static library uses the R.txt files generated by the + # static_library_dependent_targets when generating the final R.java file. + if (_is_static_library_provider) { + foreach(_dep, invoker.static_library_dependent_targets) { + deps += [ "${_dep.name}__compile_resources" ] + } + } + + if (defined(invoker.apk_under_test)) { + # Set the arsc package name to match the apk_under_test package name + # So that test resources can references under_test resources via + # @type/name syntax. + r_java_root_package_name = "test" + arsc_package_name = + "@FileArg($_rebased_build_config:deps_info:arsc_package_name)" + + # Passing in the --emit-ids mapping will cause aapt2 to assign resources + # IDs that do not conflict with those from apk_under_test. + assert(!defined(resource_ids_provider_dep)) + resource_ids_provider_dep = invoker.apk_under_test + + include_resource = + get_label_info(invoker.apk_under_test, "target_out_dir") + "/" + + get_label_info(invoker.apk_under_test, "name") + ".ap_" + _link_against = invoker.apk_under_test + } + + if (_is_bundle_module) { + is_bundle_module = true + proto_output = _proto_resources_path + + if (defined(invoker.base_module_target)) { + include_resource = + get_label_info(invoker.base_module_target, "target_out_dir") + + "/" + get_label_info(invoker.base_module_target, "name") + ".ap_" + _link_against = invoker.base_module_target + } + } + + if (defined(_link_against)) { + deps += [ "${_link_against}__compile_resources" ] + include_resource = get_label_info(_link_against, "target_out_dir") + + "/" + get_label_info(_link_against, "name") + ".ap_" + } + + # Bundle modules have to reference resources from the base module. + if (!_is_bundle_module || _is_base_module) { + arsc_output = _arsc_resources_path + } + + if (defined(_shared_resources_allowlist_target)) { + # Used to ensure that the WebView resources are properly shared + # (i.e. are non-final and with package ID 0). + shared_resources_allowlist = _allowlist_r_txt_path + deps += [ _allowlist_deps ] + } + } + _srcjar_deps += [ ":$_compile_resources_target" ] + + # We don't ship apks anymore, only optimize bundle builds + if (_is_bundle_module) { + _short_resource_paths = + defined(invoker.short_resource_paths) && + invoker.short_resource_paths && enable_arsc_obfuscation + _strip_resource_names = + defined(invoker.strip_resource_names) && + invoker.strip_resource_names && enable_arsc_obfuscation + _strip_unused_resources = defined(invoker.strip_unused_resources) && + invoker.strip_unused_resources + _optimize_resources = _strip_resource_names || _short_resource_paths || + _strip_unused_resources + } + + if (_is_bundle_module && _optimize_resources) { + _optimized_proto_resources_path = + "$target_out_dir/$target_name.optimized.proto.ap_" + if (_short_resource_paths) { + _resources_path_map_out_path = + "${target_gen_dir}/${_template_name}_resources_path_map.txt" + } + _optimize_resources_target = "${_template_name}__optimize_resources" + optimize_resources(_optimize_resources_target) { + deps = _deps + [ ":$_compile_resources_target" ] + short_resource_paths = _short_resource_paths + strip_resource_names = _strip_resource_names + if (_short_resource_paths) { + resources_path_map_out_path = _resources_path_map_out_path + } + r_text_path = _compile_resources_rtxt_out + proto_input_path = _proto_resources_path + optimized_proto_output = _optimized_proto_resources_path + if (_strip_unused_resources) { + # These need to be kept in sync with the target names + output paths + # in the android_app_bundle template. + _unused_resources_target = "${_template_name}__unused_resources" + _unused_resources_config_path = + "$target_gen_dir/${_template_name}_unused_resources.config" + resources_config_paths = [ _unused_resources_config_path ] + deps += [ ":$_unused_resources_target" ] + } else { + resources_config_paths = [] + } + if (defined(invoker.resources_config_paths)) { + resources_config_paths += invoker.resources_config_paths + } + } + } + + if (!_is_bundle_module) { + # Output the R.txt file to a more easily discoverable location for + # archiving. This is necessary when stripping resource names so that we + # have an archive of resource names to ids for shipped apks (for + # debugging purposes). We copy the file rather than change the location + # of the original because other targets rely on the location of the R.txt + # file. + _copy_rtxt_target = "${_template_name}__copy_rtxt" + copy(_copy_rtxt_target) { + deps = [ ":$_compile_resources_target" ] + sources = [ _compile_resources_rtxt_out ] + outputs = [ _final_rtxt_path ] + } + _final_deps += [ ":$_copy_rtxt_target" ] + } + + if (defined(_resource_sizes_arsc_path)) { + _copy_arsc_target = "${_template_name}__copy_arsc" + copy(_copy_arsc_target) { + deps = [ ":$_compile_resources_target" ] + + # resource_sizes.py doesn't care if it gets the optimized .arsc. + sources = [ _arsc_resources_path ] + outputs = [ _resource_sizes_arsc_path ] + } + _final_deps += [ ":$_copy_arsc_target" ] + } + + _generate_native_libraries_java = + (!_is_bundle_module || _is_base_module) && + (_native_libs_deps != [] || _secondary_abi_native_libs_deps != []) && + !_uses_static_library_synchronized_proguard + if (_generate_native_libraries_java) { + write_native_libraries_java("${_template_name}__native_libraries") { + forward_variables_from(invoker, [ "main_component_library" ]) + + # Do not add a dep on the generated_file target in order to avoid having + # to build the native libraries before this target. The dependency is + # instead captured via a depfile. + if (_native_libs_deps != []) { + native_libraries_list_file = _shared_library_list_file + } else { + native_libraries_list_file = _secondary_abi_shared_library_list_file + } + enable_chromium_linker = _use_chromium_linker + load_library_from_apk = _load_library_from_apk + use_modern_linker = _use_modern_linker + use_final_fields = true + } + _srcjar_deps += [ ":${_template_name}__native_libraries" ] + } + + _loadable_modules = [] + if (defined(invoker.loadable_modules)) { + _loadable_modules = invoker.loadable_modules + } + + if (_native_libs_deps != []) { + _loadable_modules += _sanitizer_runtimes + } + + if (_generate_buildconfig_java) { + generate_build_config_srcjar("${_template_name}__build_config_srcjar") { + forward_variables_from(invoker, + [ + "min_sdk_version", + "isolated_splits_enabled", + ]) + _bundles_supported = _is_bundle_module || _is_static_library_provider + if (defined(invoker.bundles_supported)) { + _bundles_supported = invoker.bundles_supported + } + bundles_supported = _bundles_supported + use_final_fields = true + enable_multidex = _enable_multidex + is_incremental_install = _incremental_apk + if (defined(invoker.product_version_resources_dep)) { + resources_version_variable = + "org.chromium.base.R.string.product_version" + } + deps = [ ":$_build_config_target" ] + } + _srcjar_deps += [ ":${_template_name}__build_config_srcjar" ] + } + + if (_generate_productconfig_java) { + foreach(_package, invoker.product_config_java_packages) { + _locale_target_name = + "${_template_name}_${_package}__product_config_srcjar" + generate_product_config_srcjar("$_locale_target_name") { + forward_variables_from(invoker, [ "is_bundle_module" ]) + build_config = _build_config + java_package = _package + use_chromium_linker = _use_chromium_linker + use_modern_linker = _use_modern_linker + deps = [ ":$_build_config_target" ] + } + _srcjar_deps += [ ":$_locale_target_name" ] + } + } + + if (_generate_final_jni) { + generate_jni_registration("${_template_name}__final_jni") { + forward_variables_from(invoker, + [ + "enable_native_mocks", + "require_native_mocks", + ]) + if (defined(invoker.bundle_target)) { + targets = [ invoker.bundle_target ] + } else { + targets = [ ":$_template_name" ] + } + if (_is_static_library_provider) { + foreach(_target, invoker.static_library_dependent_targets) { + targets += [ _target.name ] + } + } + if (defined(invoker.jni_registration_header)) { + header_output = invoker.jni_registration_header + } + if (defined(invoker.jni_sources_exclusions)) { + sources_exclusions = invoker.jni_sources_exclusions + } + } + _srcjar_deps += [ ":${_template_name}__final_jni" ] + } else { + not_needed(invoker, + [ + "enable_native_mocks", + "jni_registration_header", + ]) + } + + _java_target = "${_template_name}__java" + + java_library_impl(_java_target) { + forward_variables_from(invoker, + [ + "alternative_android_sdk_dep", + "android_manifest", + "android_manifest_dep", + "annotation_processor_deps", + "apk_under_test", + "base_module_target", + "chromium_code", + "jacoco_never_instrument", + "jar_excluded_patterns", + "javac_args", + "native_lib_placeholders", + "processor_args_javac", + "secondary_abi_loadable_modules", + "secondary_native_lib_placeholders", + "sources", + "static_library_dependent_targets", + "use_classic_desugar", + "library_always_compress", + "library_renames", + ]) + deps = _deps + if (_uses_static_library_synchronized_proguard) { + if (!defined(jar_excluded_patterns)) { + jar_excluded_patterns = [] + } + + # The static library will provide all R.java files, but we still need to + # make the base module R.java files available at compile time since DFM + # R.java classes extend base module classes. + jar_excluded_patterns += [ + "*/R.class", + "*/R\$*.class", + ] + } + if (_is_bundle_module) { + type = "android_app_bundle_module" + res_size_info_path = _res_size_info_path + is_base_module = _is_base_module + forward_variables_from(invoker, + [ + "version_code", + "version_name", + ]) + } else { + type = "android_apk" + } + r_text_path = _compile_resources_rtxt_out + main_target_name = _template_name + supports_android = true + requires_android = true + srcjar_deps = _srcjar_deps + merged_android_manifest = _android_manifest + if (defined(_final_dex_path)) { + final_dex_path = _final_dex_path + } + + if (_is_bundle_module) { + proto_resources_path = _proto_resources_path + if (_optimize_resources) { + proto_resources_path = _optimized_proto_resources_path + if (_short_resource_paths) { + module_pathmap_path = _resources_path_map_out_path + } + } + } else { + apk_path = _final_apk_path + if (_incremental_apk) { + incremental_apk_path = _incremental_apk_path + incremental_install_json_path = _incremental_install_json_path + } + } + + proguard_enabled = _proguard_enabled + if (_proguard_enabled) { + proguard_configs = [ _generated_proguard_config ] + if (defined(invoker.proguard_configs)) { + proguard_configs += invoker.proguard_configs + } + if (!enable_java_asserts && (!defined(testonly) || !testonly) && + # Injected JaCoCo code causes -checkdiscards to fail. + !use_jacoco_coverage) { + proguard_configs += [ "//build/android/dcheck_is_off.flags" ] + } + if (!_is_bundle_module) { + proguard_mapping_path = _proguard_mapping_path + } + } + + # Do not add a dep on the generated_file target in order to avoid having + # to build the native libraries before this target. The dependency is + # instead captured via a depfile. + if (_native_libs_deps != []) { + shared_libraries_runtime_deps_file = _shared_library_list_file + } + if (defined(_secondary_abi_shared_library_list_file)) { + secondary_abi_shared_libraries_runtime_deps_file = + _secondary_abi_shared_library_list_file + } + + loadable_modules = _loadable_modules + + uncompress_shared_libraries = _uncompress_shared_libraries + + if (defined(_allowlist_r_txt_path) && _is_bundle_module) { + # Used to write the file path to the target's .build_config.json only. + base_allowlist_rtxt_path = _allowlist_r_txt_path + } + } + + # 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. + if (defined(invoker.dist_ijar_path)) { + _dist_ijar_path = invoker.dist_ijar_path + dist_jar("${_template_name}_dist_ijar") { + override_build_config = _build_config + output = _dist_ijar_path + data = [ _dist_ijar_path ] + use_interface_jars = true + deps = [ + ":$_build_config_target", + ":$_java_target", + ] + } + } + + if (_uses_static_library_synchronized_proguard) { + _final_dex_target_dep = "${invoker.static_library_provider}__dexsplitter" + } else if (_is_bundle_module && _proguard_enabled) { + _final_deps += [ ":$_java_target" ] + } else if (_incremental_apk) { + if (defined(invoker.enable_proguard_checks)) { + not_needed(invoker, [ "enable_proguard_checks" ]) + } + if (defined(invoker.disable_r8_outlining)) { + not_needed(invoker, [ "disable_r8_outlining" ]) + } + if (defined(invoker.dexlayout_profile)) { + not_needed(invoker, [ "dexlayout_profile" ]) + } + } else { + # Dex generation for app bundle modules with proguarding enabled takes + # place later due to synchronized proguarding. + _final_dex_target_name = "${_template_name}__final_dex" + dex(_final_dex_target_name) { + forward_variables_from(invoker, + [ + "disable_r8_outlining", + "dexlayout_profile", + "enable_proguard_checks", + "proguard_enable_obfuscation", + ]) + min_sdk_version = _min_sdk_version + proguard_enabled = _proguard_enabled + build_config = _build_config + deps = [ + ":$_build_config_target", + ":$_java_target", + ] + if (_proguard_enabled) { + deps += _deps + [ ":$_compile_resources_target" ] + proguard_mapping_path = _proguard_mapping_path + proguard_sourcefile_suffix = "$android_channel-$_version_code" + has_apk_under_test = defined(invoker.apk_under_test) + } else if (_min_sdk_version >= default_min_sdk_version) { + # Enable dex merging only when min_sdk_version is >= what the library + # .dex files were created with. + input_dex_filearg = + "@FileArg(${_rebased_build_config}:final_dex:all_dex_files)" + } else { + input_classes_filearg = + "@FileArg($_rebased_build_config:deps_info:device_classpath)" + } + + if (_is_static_library_provider) { + # The list of input jars is already recorded in the .build_config.json, but + # we need to explicitly add the java deps here to ensure they're + # available to be used as inputs to the dex step. + foreach(_dep, invoker.static_library_dependent_targets) { + _target_label = get_label_info(_dep.name, "label_no_toolchain") + deps += [ "${_target_label}__java" ] + } + output = _static_library_sync_dex_path + is_static_library = true + } else { + output = _final_dex_path + } + enable_multidex = _enable_multidex + + # The individual dependencies would have caught real missing deps in + # their respective dex steps. False positives that were suppressed at + # per-target dex steps are emitted here since this may use jar files + # rather than dex files. + ignore_desugar_missing_deps = true + + if (_enable_main_dex_list) { + extra_main_dex_proguard_config = _generated_proguard_main_dex_config + deps += [ ":$_compile_resources_target" ] + } + } + + _final_dex_target_dep = ":$_final_dex_target_name" + + # For static libraries, a single Proguard run is performed that includes + # code from the static library APK and the APKs that use the static + # library (done via. classpath merging in write_build_config.py). + # This dexsplitter target splits the synchronized dex output into dex + # files for each APK/Bundle. In the Bundle case, another dexsplitter step + # is later performed to split the dex further for each feature module. + if (_is_static_library_provider && _proguard_enabled) { + _static_library_modules = [] + foreach(_target, invoker.static_library_dependent_targets) { + _apk_as_module = _target.name + _module_config_target = "${_apk_as_module}$build_config_target_suffix" + _module_gen_dir = get_label_info(_apk_as_module, "target_gen_dir") + _module_name = get_label_info(_apk_as_module, "name") + _module_config = "$_module_gen_dir/$_module_name.build_config.json" + _static_library_modules += [ + { + name = _module_name + build_config = _module_config + build_config_target = _module_config_target + }, + ] + } + + _static_library_dexsplitter_target = "${_template_name}__dexsplitter" + dexsplitter(_static_library_dexsplitter_target) { + input_dex_zip = _static_library_sync_dex_path + proguard_mapping = _proguard_mapping_path + deps = [ + ":$_build_config_target", + "$_final_dex_target_dep", + ] + all_modules = [ + { + name = "base" + build_config = _build_config + build_config_target = ":$_build_config_target" + }, + ] + _static_library_modules + feature_jars_args = [ + "--feature-jars", + "@FileArg($_rebased_build_config:deps_info:" + + "static_library_dependent_classpath_configs:" + + "$_rebased_build_config)", + ] + foreach(_module, _static_library_modules) { + _rebased_module_config = + rebase_path(_module.build_config, root_build_dir) + feature_jars_args += [ + "--feature-jars", + "@FileArg($_rebased_build_config:deps_info:" + + "static_library_dependent_classpath_configs:" + + "$_rebased_module_config)", + ] + } + } + _final_deps += [ ":$_static_library_dexsplitter_target" ] + _validate_dex_target = "${_template_name}__validate_dex" + action_with_pydeps(_validate_dex_target) { + depfile = "$target_gen_dir/$target_name.d" + script = + "//build/android/gyp/validate_static_library_dex_references.py" + inputs = [ _build_config ] + _stamp = "$target_gen_dir/$target_name.stamp" + outputs = [ _stamp ] + deps = [ + ":$_build_config_target", + ":$_static_library_dexsplitter_target", + ] + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--stamp", + rebase_path(_stamp, root_build_dir), + "--static-library-dex", + "@FileArg($_rebased_build_config:final_dex:path)", + ] + foreach(_module, _static_library_modules) { + inputs += [ _module.build_config ] + _rebased_config = rebase_path(_module.build_config, root_build_dir) + deps += [ _module.build_config_target ] + args += [ + "--static-library-dependent-dex", + "@FileArg($_rebased_config:final_dex:path)", + ] + } + } + + # TODO(crbug.com/1032609): Switch to using R8's support for feature + # aware ProGuard and get rid of "_validate_dex_target" or figure out + # why some classes aren't properly being kept. + # _final_deps += [ ":$_validate_dex_target" ] + _final_dex_target_dep = ":$_static_library_dexsplitter_target" + } + } + + _all_native_libs_deps = _native_libs_deps + _secondary_abi_native_libs_deps + if (_all_native_libs_deps != []) { + _native_libs_filearg_dep = ":$_build_config_target" + _all_native_libs_deps += [ _native_libs_filearg_dep ] + + if (!_is_bundle_module) { + _native_libs_filearg = + "@FileArg($_rebased_build_config:native:libraries)" + } + } + + if (_is_bundle_module) { + _final_deps += [ + ":$_merge_manifest_target", + ":$_build_config_target", + ":$_compile_resources_target", + ] + _all_native_libs_deps + if (_optimize_resources) { + _final_deps += [ ":$_optimize_resources_target" ] + } + if (defined(_final_dex_target_dep)) { + not_needed([ "_final_dex_target_dep" ]) + } + } else { + # Generate size-info/*.jar.info files. + if (defined(invoker.name)) { + # Create size info files for targets that care about size + # (have proguard enabled). + _include_size_info = + defined(invoker.include_size_info) && invoker.include_size_info + if (_include_size_info || _proguard_enabled) { + _size_info_target = "${target_name}__size_info" + create_size_info_files(_size_info_target) { + name = "${invoker.name}.apk" + build_config = _build_config + res_size_info_path = _res_size_info_path + deps = _deps + [ + ":$_build_config_target", + ":$_compile_resources_target", + ":$_java_target", + ] + } + _final_deps += [ ":$_size_info_target" ] + } else { + not_needed(invoker, [ "name" ]) + } + } + + _keystore_path = android_keystore_path + _keystore_name = android_keystore_name + _keystore_password = android_keystore_password + + if (defined(invoker.keystore_path)) { + _keystore_path = invoker.keystore_path + _keystore_name = invoker.keystore_name + _keystore_password = invoker.keystore_password + } + + if (_incremental_apk) { + _incremental_compiled_resources_path = "${_base_path}_incremental.ap_" + _incremental_compile_resources_target_name = + "${target_name}__compile_incremental_resources" + + action_with_pydeps(_incremental_compile_resources_target_name) { + deps = [ + ":$_build_config_target", + ":$_compile_resources_target", + ":$_merge_manifest_target", + ] + script = + "//build/android/incremental_install/generate_android_manifest.py" + inputs = [ + _android_manifest, + _build_config, + _arsc_resources_path, + ] + outputs = [ _incremental_compiled_resources_path ] + + args = [ + "--disable-isolated-processes", + "--src-manifest", + rebase_path(_android_manifest, root_build_dir), + "--in-apk", + rebase_path(_arsc_resources_path, root_build_dir), + "--out-apk", + rebase_path(_incremental_compiled_resources_path, root_build_dir), + "--aapt2-path", + rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir), + "--android-sdk-jars=@FileArg($_rebased_build_config:android:sdk_jars)", + ] + } + } + + _create_apk_target = "${_template_name}__create" + _final_deps += [ ":$_create_apk_target" ] + package_apk("$_create_apk_target") { + forward_variables_from(invoker, + [ + "expected_libs_and_assets", + "expected_libs_and_assets_base", + "native_lib_placeholders", + "secondary_abi_loadable_modules", + "secondary_native_lib_placeholders", + "uncompress_dex", + "uncompress_shared_libraries", + "library_always_compress", + "library_renames", + ]) + + if (defined(expected_libs_and_assets)) { + build_config_dep = ":$_build_config_target" + top_target_name = _template_name + } + + build_config = _build_config + keystore_name = _keystore_name + keystore_path = _keystore_path + keystore_password = _keystore_password + min_sdk_version = _min_sdk_version + uncompress_shared_libraries = _uncompress_shared_libraries + + deps = _deps + [ ":$_build_config_target" ] + + if ((!_proguard_enabled || _incremental_apk) && + enable_jdk_library_desugaring) { + _all_jdk_libs = "//build/android:all_jdk_libs" + deps += [ _all_jdk_libs ] + jdk_libs_dex = get_label_info(_all_jdk_libs, "target_out_dir") + + "/all_jdk_libs.l8.dex" + } + + if (_incremental_apk) { + _dex_target = "//build/android/incremental_install:apk_dex" + + deps += [ + ":${_incremental_compile_resources_target_name}", + _dex_target, + ] + + dex_path = get_label_info(_dex_target, "target_out_dir") + "/apk.dex" + + # All native libraries are side-loaded, so use a placeholder to force + # the proper bitness for the app. + _has_native_libs = + defined(_native_libs_filearg) || _loadable_modules != [] + if (_has_native_libs && !defined(native_lib_placeholders)) { + native_lib_placeholders = [ "libfix.crbug.384638.so" ] + } + + packaged_resources_path = _incremental_compiled_resources_path + output_apk_path = _incremental_apk_path + } else { + loadable_modules = _loadable_modules + deps += _all_native_libs_deps + [ + ":$_merge_manifest_target", + ":$_compile_resources_target", + ] + + if (defined(_final_dex_path)) { + dex_path = _final_dex_path + deps += [ _final_dex_target_dep ] + } + + packaged_resources_path = _arsc_resources_path + + if (defined(_native_libs_filearg)) { + native_libs_filearg = _native_libs_filearg + secondary_abi_native_libs_filearg = "@FileArg($_rebased_build_config:native:secondary_abi_libraries)" + } + output_apk_path = _final_apk_path + } + } + } + + if (_incremental_apk) { + _write_installer_json_rule_name = "${_template_name}__incremental_json" + action_with_pydeps(_write_installer_json_rule_name) { + script = "//build/android/incremental_install/write_installer_json.py" + deps = [ ":$_build_config_target" ] + _all_native_libs_deps + + data = [ _incremental_install_json_path ] + inputs = [ _build_config ] + outputs = [ _incremental_install_json_path ] + + _rebased_incremental_apk_path = + rebase_path(_incremental_apk_path, root_build_dir) + _rebased_incremental_install_json_path = + rebase_path(_incremental_install_json_path, root_build_dir) + args = [ + "--apk-path=$_rebased_incremental_apk_path", + "--output-path=$_rebased_incremental_install_json_path", + "--dex-file=@FileArg($_rebased_build_config:final_dex:all_dex_files)", + ] + if (_proguard_enabled) { + args += [ "--show-proguard-warning" ] + } + if (defined(_native_libs_filearg)) { + args += [ "--native-libs=$_native_libs_filearg" ] + deps += [ _native_libs_filearg_dep ] + } + if (_loadable_modules != []) { + _rebased_loadable_modules = + rebase_path(_loadable_modules, root_build_dir) + args += [ "--native-libs=$_rebased_loadable_modules" ] + } + } + _final_deps += [ + ":$_java_target", + ":$_write_installer_json_rule_name", + ] + } + + # Generate apk operation related script. + if (!_is_bundle_module && + (!defined(invoker.create_apk_script) || invoker.create_apk_script)) { + if (_uses_static_library) { + _install_artifacts_target = "${target_name}__install_artifacts" + _install_artifacts_json = + "${target_gen_dir}/${target_name}.install_artifacts" + generated_file(_install_artifacts_target) { + output_conversion = "json" + deps = [ invoker.static_library_provider ] + outputs = [ _install_artifacts_json ] + data_keys = [ "install_artifacts" ] + rebase = root_build_dir + } + } + _apk_operations_target_name = "${target_name}__apk_operations" + action_with_pydeps(_apk_operations_target_name) { + _generated_script = "$root_build_dir/bin/${invoker.target_name}" + script = "//build/android/gyp/create_apk_operations_script.py" + outputs = [ _generated_script ] + args = [ + "--script-output-path", + rebase_path(_generated_script, root_build_dir), + "--target-cpu=$target_cpu", + ] + if (defined(invoker.command_line_flags_file)) { + args += [ + "--command-line-flags-file", + invoker.command_line_flags_file, + ] + } + if (_incremental_apk) { + args += [ + "--incremental-install-json-path", + rebase_path(_incremental_install_json_path, root_build_dir), + ] + } else { + args += [ + "--apk-path", + rebase_path(_final_apk_path, root_build_dir), + ] + } + if (_uses_static_library) { + deps = [ ":$_install_artifacts_target" ] + _rebased_install_artifacts_json = + rebase_path(_install_artifacts_json, root_build_dir) + _static_library_apk_path = + "@FileArg($_rebased_install_artifacts_json[])" + args += [ + "--additional-apk", + _static_library_apk_path, + ] + } + data = [] + data_deps = [ + "//build/android:apk_operations_py", + "//build/android:stack_tools", + ] + + if (_proguard_enabled && !_incremental_apk) { + # Required by logcat command. + data_deps += [ "//build/android/stacktrace:java_deobfuscate" ] + data += [ "$_final_apk_path.mapping" ] + args += [ + "--proguard-mapping-path", + rebase_path("$_final_apk_path.mapping", root_build_dir), + ] + } + } + _final_deps += [ ":$_apk_operations_target_name" ] + } + + _enable_lint = defined(invoker.enable_lint) && invoker.enable_lint && + !disable_android_lint + if (_enable_lint) { + android_lint("${target_name}__lint") { + forward_variables_from(invoker, + [ + "lint_baseline_file", + "lint_suppressions_file", + "min_sdk_version", + ]) + build_config = _build_config + build_config_dep = ":$_build_config_target" + deps = [ ":$_java_target" ] + if (defined(invoker.lint_suppressions_dep)) { + deps += [ invoker.lint_suppressions_dep ] + } + if (defined(invoker.lint_min_sdk_version)) { + min_sdk_version = invoker.lint_min_sdk_version + } + } + } else { + not_needed(invoker, + [ + "lint_baseline_file", + "lint_min_sdk_version", + "lint_suppressions_dep", + "lint_suppressions_file", + ]) + } + + group(target_name) { + forward_variables_from(invoker, + [ + "assert_no_deps", + "data", + "data_deps", + "metadata", + ]) + + # Generate apk related operations at runtime. + public_deps = _final_deps + + if (!defined(data_deps)) { + data_deps = [] + } + + # Include unstripped native libraries so tests can symbolize stacks. + data_deps += _all_native_libs_deps + + if (_enable_lint) { + data_deps += [ ":${target_name}__lint" ] + } + + if (_uses_static_library) { + data_deps += [ invoker.static_library_provider ] + } + } + } + + # Declare an Android APK target + # + # This target creates an Android APK containing java code, resources, assets, + # and (possibly) native libraries. + # + # Supports all variables of android_apk_or_module(), plus: + # apk_name: Name for final apk. + # final_apk_path: (Optional) path to output APK. + # + # Example + # android_apk("foo_apk") { + # android_manifest = "AndroidManifest.xml" + # sources = [ + # "android/org/chromium/foo/FooApplication.java", + # "android/org/chromium/foo/FooActivity.java", + # ] + # deps = [ + # ":foo_support_java" + # ":foo_resources" + # ] + # srcjar_deps = [ + # ":foo_generated_enum" + # ] + # shared_libraries = [ + # ":my_shared_lib", + # ] + # } + template("android_apk") { + # TODO(crbug.com/1042017): Remove. + not_needed(invoker, [ "no_build_hooks" ]) + android_apk_or_module(target_name) { + forward_variables_from(invoker, + [ + "aapt_locale_allowlist", + "additional_jar_files", + "alternative_android_sdk_dep", + "android_manifest", + "android_manifest_dep", + "annotation_processor_deps", + "apk_under_test", + "app_as_shared_lib", + "assert_no_deps", + "bundles_supported", + "chromium_code", + "command_line_flags_file", + "create_apk_script", + "data", + "data_deps", + "deps", + "dexlayout_profile", + "disable_r8_outlining", + "dist_ijar_path", + "enable_lint", + "enable_multidex", + "enable_native_mocks", + "enable_proguard_checks", + "enforce_resource_overlays_in_tests", + "expected_android_manifest", + "expected_android_manifest_base", + "expected_libs_and_assets", + "expected_libs_and_assets_base", + "generate_buildconfig_java", + "generate_final_jni", + "include_size_info", + "input_jars_paths", + "use_modern_linker", + "jacoco_never_instrument", + "javac_args", + "jni_registration_header", + "jni_sources_exclusions", + "keystore_name", + "keystore_password", + "keystore_path", + "lint_baseline_file", + "lint_min_sdk_version", + "lint_suppressions_dep", + "lint_suppressions_file", + "load_library_from_apk", + "loadable_modules", + "manifest_package", + "max_sdk_version", + "product_config_java_packages", + "main_component_library", + "min_sdk_version", + "native_lib_placeholders", + "never_incremental", + "no_xml_namespaces", + "png_to_webp", + "post_process_package_resources_script", + "processor_args_javac", + "product_version_resources_dep", + "proguard_configs", + "proguard_enabled", + "proguard_enable_obfuscation", + "r_java_root_package_name", + "resource_exclusion_exceptions", + "resource_exclusion_regex", + "resource_ids_provider_dep", + "resource_values_filter_rules", + "require_native_mocks", + "secondary_abi_loadable_modules", + "secondary_abi_shared_libraries", + "secondary_native_lib_placeholders", + "shared_libraries", + "shared_resources", + "shared_resources_allowlist_locales", + "shared_resources_allowlist_target", + "sources", + "srcjar_deps", + "static_library_dependent_targets", + "static_library_provider", + "static_library_synchronized_proguard", + "target_sdk_version", + "testonly", + "uncompress_dex", + "uncompress_shared_libraries", + "use_classic_desugar", + "library_always_compress", + "library_renames", + "use_chromium_linker", + "version_code", + "version_name", + "visibility", + ]) + is_bundle_module = false + name = invoker.apk_name + if (defined(invoker.final_apk_path)) { + final_apk_path = invoker.final_apk_path + } else { + final_apk_path = "$root_build_dir/apks/${invoker.apk_name}.apk" + } + metadata = { + install_artifacts = [ final_apk_path ] + } + if (defined(invoker.static_library_provider)) { + metadata.install_artifacts_barrier = [] + } + } + } + + # Declare an Android app bundle module target. + # + # The module can be used for an android_apk_or_module(). + # + # Supports all variables of android_library(), plus: + # module_name: Name of the module. + # is_base_module: If defined and true, indicates that this is the bundle's + # base module (optional). + # base_module_target: Base module target of the bundle this module will be + # added to (optional). Can only be specified for non-base modules. + # bundle_target: Bundle target that this module belongs to (optional). + # Can only be specified for base modules. + template("android_app_bundle_module") { + _is_base_module = defined(invoker.is_base_module) && invoker.is_base_module + + if (_is_base_module) { + assert(!defined(invoker.base_module_target)) + } else { + assert(!defined(invoker.app_as_shared_lib)) + assert(!defined(invoker.shared_resources)) + assert(!defined(invoker.shared_resources_allowlist_target)) + assert(!defined(invoker.shared_resources_allowlist_locales)) + assert(defined(invoker.base_module_target)) + assert(!defined(invoker.bundle_target)) + } + + # TODO(tiborg): We have several flags that are necessary for workarounds + # that come from the fact that the resources get compiled in the bundle + # module target, but bundle modules have to have certain flags in + # common or bundle modules have to know information about the base module. + # Those flags include version_code, version_name, and base_module_target. + # It would be better to move the resource compile target into the bundle + # target. Doing so would keep the bundle modules independent from the bundle + # and potentially reuse the same bundle modules for multiple bundles. + android_apk_or_module(target_name) { + forward_variables_from(invoker, + [ + "aapt_locale_allowlist", + "additional_jar_files", + "alternative_android_sdk_dep", + "android_manifest", + "android_manifest_dep", + "annotation_processor_deps", + "app_as_shared_lib", + "assert_no_deps", + "base_module_target", + "bundle_target", + "chromium_code", + "data", + "data_deps", + "deps", + "enable_multidex", + "expected_android_manifest", + "expected_android_manifest_base", + "extra_verification_manifest", + "extra_verification_manifest_dep", + "generate_buildconfig_java", + "generate_final_jni", + "input_jars_paths", + "isolated_splits_enabled", + "is_base_module", + "jacoco_never_instrument", + "jar_excluded_patterns", + "javac_args", + "jni_registration_header", + "jni_sources_exclusions", + "load_library_from_apk", + "loadable_modules", + "product_config_java_packages", + "manifest_package", + "max_sdk_version", + "min_sdk_version", + "native_lib_placeholders", + "no_xml_namespaces", + "package_id", + "package_name", + "png_to_webp", + "processor_args_javac", + "product_version_resources_dep", + "proguard_configs", + "proguard_enabled", + "proguard_enable_obfuscation", + "resource_exclusion_exceptions", + "resource_exclusion_regex", + "resource_ids_provider_dep", + "resource_values_filter_rules", + "resources_config_paths", + "secondary_abi_loadable_modules", + "secondary_abi_shared_libraries", + "secondary_native_lib_placeholders", + "shared_libraries", + "shared_resources", + "shared_resources_allowlist_locales", + "shared_resources_allowlist_target", + "short_resource_paths", + "srcjar_deps", + "static_library_provider", + "static_library_synchronized_proguard", + "strip_resource_names", + "strip_unused_resources", + "target_sdk_version", + "testonly", + "uncompress_shared_libraries", + "library_always_compress", + "library_renames", + "use_chromium_linker", + "use_modern_linker", + "uses_split", + "version_code", + "version_name", + "visibility", + ]) + is_bundle_module = true + generate_buildconfig_java = _is_base_module + } + } + + # Declare an Android instrumentation test runner. + # + # This target creates a wrapper script to run Android instrumentation tests. + # + # Arguments: + # android_test_apk: The target containing the tests. + # android_test_apk_name: The apk_name in android_test_apk + # + # The following args are optional: + # apk_under_test: The target being tested. + # additional_apks: Additional targets to install on device. + # data: List of runtime data file dependencies. + # data_deps: List of non-linked dependencies. + # deps: List of private dependencies. + # extra_args: Extra arguments set for test runner. + # ignore_all_data_deps: Don't build data_deps and additional_apks. + # modules: Extra dynamic feature modules to install for test target. Can + # only be used if |apk_under_test| is an Android app bundle. + # fake_modules: Similar to |modules| but fake installed instead. + # never_incremental: Disable incremental builds. + # proguard_enabled: Enable proguard + # public_deps: List of public dependencies + # + # Example + # instrumentation_test_runner("foo_test_for_bar") { + # android_test_apk: ":foo" + # android_test_apk_name: "Foo" + # apk_under_test: ":bar" + # } + template("instrumentation_test_runner") { + if (use_rts) { + action("${invoker.target_name}__rts_filters") { + script = "//build/add_rts_filters.py" + rts_file = "${root_build_dir}/gen/rts/${invoker.target_name}.filter" + args = [ rebase_path(rts_file, root_build_dir) ] + outputs = [ rts_file ] + } + } + test_runner_script(target_name) { + forward_variables_from(invoker, + [ + "additional_apks", + "additional_locales", + "apk_under_test", + "data", + "data_deps", + "deps", + "extra_args", + "fake_modules", + "ignore_all_data_deps", + "modules", + "proguard_enabled", + "public_deps", + "use_webview_provider", + ]) + test_name = invoker.target_name + test_type = "instrumentation" + _apk_target_name = get_label_info(invoker.android_test_apk, "name") + apk_target = ":$_apk_target_name" + test_jar = "$root_build_dir/test.lib.java/" + + invoker.android_test_apk_name + ".jar" + incremental_apk = !(defined(invoker.never_incremental) && + invoker.never_incremental) && incremental_install + + public_deps = [ + ":$_apk_target_name", + + # Required by test runner to enumerate test list. + ":${_apk_target_name}_dist_ijar", + ] + if (defined(invoker.apk_under_test)) { + public_deps += [ invoker.apk_under_test ] + } + if (defined(invoker.additional_apks)) { + public_deps += invoker.additional_apks + } + if (use_rts) { + if (!defined(data_deps)) { + data_deps = [] + } + data_deps += [ ":${invoker.target_name}__rts_filters" ] + } + } + } + + # Declare an Android instrumentation test apk + # + # This target creates an Android instrumentation test apk. + # + # Supports all variables of android_apk(), plus: + # apk_under_test: The apk being tested (optional). + # + # Example + # android_test_apk("foo_test_apk") { + # android_manifest = "AndroidManifest.xml" + # apk_name = "FooTest" + # apk_under_test = "Foo" + # sources = [ + # "android/org/chromium/foo/FooTestCase.java", + # "android/org/chromium/foo/FooExampleTest.java", + # ] + # deps = [ + # ":foo_test_support_java" + # ] + # } + template("android_test_apk") { + android_apk(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + testonly = true + + # The size info enables the test_runner to find the source file location + # of a test after it is ran. + include_size_info = true + data = [ "$root_build_dir/size-info/${invoker.apk_name}.apk.jar.info" ] + if (defined(invoker.data)) { + data += invoker.data + } + + deps = [ "//testing/android/broker:broker_java" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + data_deps = [ + # Ensure unstripped libraries are included in runtime deps so that + # symbolization can be done. + ":${target_name}__secondary_abi_shared_library_list", + ":${target_name}__shared_library_list", + ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + if (defined(invoker.apk_under_test)) { + data_deps += [ invoker.apk_under_test ] + } else { + enable_native_mocks = true + } + + if (defined(invoker.apk_under_test)) { + _under_test_label = + get_label_info(invoker.apk_under_test, "label_no_toolchain") + data_deps += [ + "${_under_test_label}__secondary_abi_shared_library_list", + "${_under_test_label}__shared_library_list", + ] + } + + if (defined(invoker.additional_apks)) { + data_deps += invoker.additional_apks + } + if (defined(invoker.use_webview_provider)) { + data_deps += [ invoker.use_webview_provider ] + } + + if (defined(invoker.proguard_enabled) && invoker.proguard_enabled && + !incremental_install) { + # When ProGuard is on, we use ProGuard to combine the under test java + # code and the test java code. This is to allow us to apply all ProGuard + # optimizations that we ship with, but not have them break tests. The + # apk under test will still have the same resources, assets, and + # manifest, all of which are the ones used in the tests. + proguard_configs = [ "//testing/android/proguard_for_test.flags" ] + if (defined(invoker.proguard_configs)) { + proguard_configs += invoker.proguard_configs + } + enable_proguard_checks = false + if (defined(invoker.final_apk_path)) { + _final_apk_path = final_apk_path + } else { + _final_apk_path = "$root_build_dir/apks/${invoker.apk_name}.apk" + } + data += [ "$_final_apk_path.mapping" ] + } + + dist_ijar_path = "$root_build_dir/test.lib.java/${invoker.apk_name}.jar" + create_apk_script = false + + forward_variables_from(invoker, + "*", + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + "deps", + "proguard_configs", + ]) + } + } + + # Declare an Android instrumentation test apk with wrapper script. + # + # This target creates an Android instrumentation test apk with wrapper script + # to run the test. + # + # Supports all variables of android_test_apk. + template("instrumentation_test_apk") { + assert(defined(invoker.apk_name)) + _apk_target_name = "${target_name}__test_apk" + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + android_test_apk(_apk_target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + } + instrumentation_test_runner(target_name) { + forward_variables_from(invoker, + [ + "additional_apks", + "apk_under_test", + "data", + "data_deps", + "deps", + "extra_args", + "ignore_all_data_deps", + "modules", + "never_incremental", + "proguard_enabled", + "proguard_enable_obfuscation", + "public_deps", + "use_webview_provider", + ]) + android_test_apk = ":${_apk_target_name}" + android_test_apk_name = invoker.apk_name + } + } + + # 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. + # shared_library: shared_library target that contains the unit tests. + # apk_name: The name of the produced apk. If unspecified, it uses the name + # of the shared_library target suffixed with "_apk". + # use_default_launcher: Whether the default activity (NativeUnitTestActivity) + # should be used for launching tests. + # allow_cleartext_traffic: (Optional) Whether to allow cleartext network + # requests during the test. + # use_native_activity: Test implements ANativeActivity_onCreate(). + # + # Example + # unittest_apk("foo_unittests_apk") { + # deps = [ ":foo_java", ":foo_resources" ] + # shared_library = ":foo_unittests" + # } + template("unittest_apk") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + _use_native_activity = + defined(invoker.use_native_activity) && invoker.use_native_activity + _android_manifest = "$target_gen_dir/$target_name/AndroidManifest.xml" + assert(invoker.shared_library != "") + + # This trivial assert is needed in case android_manifest is defined, + # as otherwise _use_native_activity and _android_manifest would not be used. + assert(_use_native_activity != "" && _android_manifest != "") + + if (!defined(invoker.android_manifest)) { + _allow_cleartext_traffic = defined(invoker.allow_cleartext_traffic) && + invoker.allow_cleartext_traffic + jinja_template("${target_name}_manifest") { + _native_library_name = get_label_info(invoker.shared_library, "name") + if (defined(invoker.android_manifest_template)) { + input = invoker.android_manifest_template + } else { + input = + "//testing/android/native_test/java/AndroidManifest.xml.jinja2" + } + output = _android_manifest + variables = [ + "is_component_build=${is_component_build}", + "native_library_name=${_native_library_name}", + "use_native_activity=${_use_native_activity}", + "allow_cleartext_traffic=${_allow_cleartext_traffic}", + ] + } + } + + android_apk(target_name) { + data_deps = [] + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + testonly = true + create_apk_script = false + enable_native_mocks = true + + # TODO(crbug.com/1099849): Figure out why angle tests fail to launch + # with newer target_sdk_version. + if (!defined(invoker.target_sdk_version) && _use_native_activity) { + target_sdk_version = 24 + } + + assert(!defined(invoker.proguard_enabled) || !invoker.proguard_enabled || + invoker.proguard_configs != []) + + if (!defined(apk_name)) { + apk_name = get_label_info(invoker.shared_library, "name") + } + + if (!defined(android_manifest)) { + android_manifest_dep = ":${target_name}_manifest" + android_manifest = _android_manifest + } + + final_apk_path = "$root_build_dir/${apk_name}_apk/${apk_name}-debug.apk" + + if (!defined(use_default_launcher) || use_default_launcher) { + deps += [ + "//base:base_java", + "//build/android/gtest_apk:native_test_instrumentation_test_runner_java", + "//testing/android/native_test:native_test_java", + ] + } + shared_libraries = [ invoker.shared_library ] + deps += [ + ":${target_name}__secondary_abi_shared_library_list", + ":${target_name}__shared_library_list", + ] + } + } + + # 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") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + script = "//build/android/gyp/aidl.py" + depfile = "$target_gen_dir/$target_name.d" + sources = invoker.sources + + _srcjar_path = "${target_gen_dir}/${target_name}.srcjar" + _aidl_path = "${android_sdk_build_tools}/aidl" + _framework_aidl = "$android_sdk/framework.aidl" + _imports = [ _framework_aidl ] + if (defined(invoker.interface_file)) { + assert(invoker.interface_file != "") + _imports += [ invoker.interface_file ] + } + + inputs = [ _aidl_path ] + _imports + + outputs = [ _srcjar_path ] + _rebased_imports = rebase_path(_imports, root_build_dir) + args = [ + "--aidl-path", + rebase_path(_aidl_path, root_build_dir), + "--imports=$_rebased_imports", + "--srcjar", + rebase_path(_srcjar_path, root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + ] + if (defined(invoker.import_include) && invoker.import_include != []) { + _rebased_import_paths = [] + foreach(_import_path, invoker.import_include) { + _rebased_import_path = [] + _rebased_import_path = [ rebase_path(_import_path, root_build_dir) ] + _rebased_import_paths += _rebased_import_path + } + args += [ "--includes=$_rebased_import_paths" ] + } + args += rebase_path(sources, root_build_dir) + } + } + + # Compile a protocol buffer to java. + # + # This generates java files from protocol buffers and creates an Android library + # containing the classes. + # + # Variables + # sources (required) + # Paths to .proto files to compile. + # + # proto_path (required) + # Root directory of .proto files. + # + # deps (optional) + # Additional dependencies. Passed through to both the action and the + # android_library targets. + # + # import_dirs (optional) + # A list of extra import directories to be passed to protoc compiler. + # WARNING: This circumvents proto checkdeps, and should only be used + # when needed, typically when proto files cannot cleanly import through + # absolute paths, such as for third_party or generated .proto files. + # http://crbug.com/691451 tracks fixing this. + # + # Example: + # proto_java_library("foo_proto_java") { + # proto_path = "src/foo" + # sources = [ "$proto_path/foo.proto" ] + # } + template("proto_java_library") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + + _template_name = target_name + + action_with_pydeps("${_template_name}__protoc_java") { + # The suffix "__protoc_java.srcjar" is used by SuperSize to identify + # protobuf symbols. + _srcjar_path = "$target_gen_dir/$target_name.srcjar" + script = "//build/protoc_java.py" + + deps = [] + if (defined(invoker.deps)) { + deps += invoker.deps + } + + sources = invoker.sources + depfile = "$target_gen_dir/$target_name.d" + outputs = [ _srcjar_path ] + args = [ + "--depfile", + rebase_path(depfile, root_build_dir), + "--protoc", + rebase_path(android_protoc_bin, root_build_dir), + "--proto-path", + rebase_path(invoker.proto_path, root_build_dir), + "--srcjar", + rebase_path(_srcjar_path, root_build_dir), + ] + rebase_path(sources, root_build_dir) + + if (defined(invoker.import_dirs)) { + foreach(_import_dir, invoker.import_dirs) { + args += [ + "--import-dir", + rebase_path(_import_dir, root_build_dir), + ] + } + } + } + + android_library(target_name) { + chromium_code = false + sources = [] + srcjar_deps = [ ":${_template_name}__protoc_java" ] + deps = [ "//third_party/android_deps:protobuf_lite_runtime_java" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + } + } + + # Declare an Android library target for a prebuilt AAR. + # + # This target creates an Android library containing java code and Android + # resources. For libraries without resources, it will not generate + # corresponding android_resources targets. + # + # To avoid slowing down "gn gen", an associated .info file must be committed + # along with the .aar file. In order to create this file, define the target + # and then run once with the gn arg "update_android_aar_prebuilts = true". + # + # Variables + # aar_path: Path to the AAR. + # info_path: Path to the .aar.info file (generated via + # update_android_aar_prebuilts GN arg). + # proguard_configs: List of proguard configs to use in final apk step for + # any apk that depends on this library. + # ignore_aidl: Whether to ignore .aidl files found with the .aar. + # ignore_assets: Whether to ignore assets found in the .aar. + # ignore_manifest: Whether to ignore creating manifest. + # ignore_native_libraries: Whether to ignore .so files found in the .aar. + # See also extract_native_libraries. + # ignore_proguard_configs: Whether to ignore proguard configs. + # ignore_info_updates: Whether to ignore the info file when + # update_android_aar_prebuilts gn arg is true. However, the info file + # will still be verified regardless of the value of this flag. + # strip_resources: Whether to ignore android resources found in the .aar. + # custom_package: Java package for generated R.java files. + # extract_native_libraries: Whether to extract .so files found in the .aar. + # If the file contains .so, either extract_native_libraries or + # ignore_native_libraries must be set. + # TODO(jbudorick@): remove this arguments after crbug.com/522043 is fixed. + # requires_android: Whether this target can only be used for compiling + # Android related targets. + # + # Example + # android_aar_prebuilt("foo_java") { + # aar_path = "foo.aar" + # } + template("android_aar_prebuilt") { + _info_path = "$target_name.info" + if (defined(invoker.info_path)) { + _info_path = invoker.info_path + } + _output_path = "${target_out_dir}/${target_name}" + + # Some targets only differ by _java with other targets so _java and _junit + # need to be replaced by non-empty strings to avoid duplicate targets. (e.g. + # androidx_window_window_java vs androidx_window_window_java_java). + _target_name_without_java_or_junit = + string_replace(string_replace(target_name, "_java", "_J"), + "_junit", + "_U") + + # This unpack target is a python action, not a valid java target. Since the + # java targets below depend on it, its name must not match the java patterns + # in internal_rules.gni. + _unpack_target_name = "${_target_name_without_java_or_junit}__unpack_aar" + _ignore_aidl = defined(invoker.ignore_aidl) && invoker.ignore_aidl + _ignore_assets = defined(invoker.ignore_assets) && invoker.ignore_assets + _ignore_manifest = + defined(invoker.ignore_manifest) && invoker.ignore_manifest + _ignore_native_libraries = defined(invoker.ignore_native_libraries) && + invoker.ignore_native_libraries + _ignore_proguard_configs = defined(invoker.ignore_proguard_configs) && + invoker.ignore_proguard_configs + _extract_native_libraries = defined(invoker.extract_native_libraries) && + invoker.extract_native_libraries + _strip_resources = + defined(invoker.strip_resources) && invoker.strip_resources + + # Allow 'resource_overlay' parameter even if there are no resources in order + # to keep the logic for generated 'android_aar_prebuilt' rules simple. + not_needed(invoker, [ "resource_overlay" ]) + + _ignore_info_updates = + defined(invoker.ignore_info_updates) && invoker.ignore_info_updates + + # Scan the AAR file and determine the resources and jar files. + # Some libraries might not have resources; others might have two jars. + if (!_ignore_info_updates && update_android_aar_prebuilts) { + print("Writing " + rebase_path(_info_path, "//")) + exec_script("//build/android/gyp/aar.py", + [ + "list", + rebase_path(invoker.aar_path, root_build_dir), + "--output", + rebase_path(_info_path, root_build_dir), + ]) + } + + # If "gn gen" is failing on the following line, you need to generate an + # .info file for your new target by running: + # gn gen --args='target_os="android" update_android_aar_prebuilts=true' out/tmp + # rm -r out/tmp + _scanned_files = read_file(_info_path, "scope") + + _use_scanned_assets = !_ignore_assets && _scanned_files.assets != [] + + assert(_ignore_aidl || _scanned_files.aidl == [], + "android_aar_prebuilt() aidl not yet supported." + + " Implement or use ignore_aidl = true." + + " http://crbug.com/644439") + assert( + !_scanned_files.has_native_libraries || + (_ignore_native_libraries || _extract_native_libraries), + "android_aar_prebuilt() contains .so files." + + " Please set ignore_native_libraries or extract_native_libraries.") + assert( + !(_ignore_native_libraries && _extract_native_libraries), + "ignore_native_libraries and extract_native_libraries cannot both be set.") + assert(!_scanned_files.has_native_libraries || + _scanned_files.native_libraries != []) + assert(_scanned_files.has_classes_jar || _scanned_files.subjars == []) + + action_with_pydeps(_unpack_target_name) { + script = "//build/android/gyp/aar.py" # Unzips the AAR + args = [ + "extract", + rebase_path(invoker.aar_path, root_build_dir), + "--output-dir", + rebase_path(_output_path, root_build_dir), + "--assert-info-file", + rebase_path(_info_path, root_build_dir), + ] + if (_strip_resources) { + args += [ "--ignore-resources" ] + } + inputs = [ invoker.aar_path ] + outputs = [ "${_output_path}/AndroidManifest.xml" ] + if (!_strip_resources && _scanned_files.has_r_text_file) { + # Certain packages, in particular Play Services have no R.txt even + # though its presence is mandated by AAR spec. Such packages cause + # spurious rebuilds if this output is specified unconditionally. + outputs += [ "${_output_path}/R.txt" ] + } + + if (!_strip_resources && _scanned_files.resources != []) { + outputs += get_path_info( + rebase_path(_scanned_files.resources, "", _output_path), + "abspath") + } + if (_scanned_files.has_classes_jar) { + outputs += [ "${_output_path}/classes.jar" ] + } + outputs += + get_path_info(rebase_path(_scanned_files.subjars, "", _output_path), + "abspath") + if (!_ignore_proguard_configs) { + if (_scanned_files.has_proguard_flags) { + outputs += [ "${_output_path}/proguard.txt" ] + } + } + + if (_extract_native_libraries && _scanned_files.has_native_libraries) { + outputs += get_path_info( + rebase_path(_scanned_files.native_libraries, "", _output_path), + "abspath") + } + if (_use_scanned_assets) { + outputs += + get_path_info(rebase_path(_scanned_files.assets, "", _output_path), + "abspath") + } + } + + _has_unignored_resources = + !_strip_resources && + (_scanned_files.resources != [] || _scanned_files.has_r_text_file) + + _should_process_manifest = + !_ignore_manifest && !_scanned_files.is_manifest_empty + + # Create the android_resources target for resources. + if (_has_unignored_resources || _should_process_manifest) { + _res_target_name = "${target_name}__resources" + android_resources(_res_target_name) { + forward_variables_from(invoker, + [ + "custom_package", + "resource_overlay", + "testonly", + "strip_drawables", + ]) + deps = [ ":$_unpack_target_name" ] + if (_should_process_manifest) { + android_manifest_dep = ":$_unpack_target_name" + android_manifest = "${_output_path}/AndroidManifest.xml" + } else if (defined(_scanned_files.manifest_package) && + !defined(custom_package)) { + custom_package = _scanned_files.manifest_package + } + + sources = [] + if (!_strip_resources) { + sources = rebase_path(_scanned_files.resources, "", _output_path) + } + if (!_strip_resources && _scanned_files.has_r_text_file) { + r_text_file = "${_output_path}/R.txt" + } + } + } else if (defined(invoker.strip_drawables)) { + not_needed(invoker, [ "strip_drawables" ]) + } + + if (_ignore_manifest) { + # Having this available can be useful for DFMs that depend on AARs. It + # provides a way to have manifest entries go into the base split while + # the code goes into a DFM. + java_group("${target_name}__ignored_manifest") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + deps = [ ":$_unpack_target_name" ] + mergeable_android_manifests = [ "${_output_path}/AndroidManifest.xml" ] + } + } + + # Create the android_assets target for assets + if (_use_scanned_assets) { + _assets_target_name = "${target_name}__assets" + android_assets(_assets_target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + renaming_sources = [] + renaming_destinations = [] + foreach(_asset_file, _scanned_files.assets) { + _original_path = + get_path_info(rebase_path(_asset_file, "", _output_path), + "abspath") + _updated_path = string_replace(_asset_file, "assets/", "", 1) + renaming_sources += [ _original_path ] + renaming_destinations += [ _updated_path ] + } + } + } + + # Create android_java_prebuilt target for classes.jar. + if (_scanned_files.has_classes_jar) { + _java_library_vars = [ + "bytecode_rewriter_target", + "enable_bytecode_checks", + "jar_excluded_patterns", + "jar_included_patterns", + "missing_classes_allowlist", + "requires_android", + "testonly", + "use_classic_desugar", + ] + + # Create android_java_prebuilt target for extra jars within jars/. + _subjar_targets = [] + foreach(_tuple, _scanned_files.subjar_tuples) { + _current_target = "${target_name}__subjar_${_tuple[0]}" + _subjar_targets += [ ":$_current_target" ] + java_prebuilt(_current_target) { + forward_variables_from(invoker, _java_library_vars) + deps = [ ":$_unpack_target_name" ] + if (!defined(requires_android)) { + requires_android = true + } + supports_android = true + jar_path = "$_output_path/${_tuple[1]}" + _base_output_name = get_path_info(jar_path, "name") + output_name = "${invoker.target_name}-$_base_output_name" + public_target_label = invoker.target_name + } + } + + _jar_target_name = "${target_name}__classes" + java_prebuilt(_jar_target_name) { + forward_variables_from(invoker, _java_library_vars) + forward_variables_from(invoker, + [ + "deps", + "input_jars_paths", + "proguard_configs", + ]) + if (!defined(deps)) { + deps = [] + } + deps += _subjar_targets + [ ":$_unpack_target_name" ] + if (defined(_res_target_name)) { + deps += [ ":$_res_target_name" ] + } + if (!defined(requires_android)) { + requires_android = true + } + supports_android = true + jar_path = "$_output_path/classes.jar" + aar_path = invoker.aar_path + output_name = invoker.target_name + + if (!_ignore_proguard_configs) { + if (!defined(proguard_configs)) { + proguard_configs = [] + } + if (_scanned_files.has_proguard_flags) { + proguard_configs += [ "$_output_path/proguard.txt" ] + } + } + public_target_label = invoker.target_name + } + } + + java_group(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + public_deps = [ ":$_unpack_target_name" ] + deps = [] + if (defined(_jar_target_name)) { + deps += [ ":$_jar_target_name" ] + + # Although subjars are meant to be private, we add them as deps here + # because in practice they seem to contain classes required to be in the + # classpath. + deps += _subjar_targets + } + if (defined(_res_target_name)) { + deps += [ ":$_res_target_name" ] + } + if (defined(_assets_target_name)) { + deps += [ ":$_assets_target_name" ] + } + } + } + + # Create an Android application bundle from one base android_apk target, + # and zero or more associated android_apk. + # + # Variables: + # base_module_target: Name of the android_app_bundle_module target + # corresponding to the base module for this application bundle. The + # bundle file will include the same content in its base module, though in + # a slightly different format. + # + # bundle_base_path: Optional. If set, the bundle will be output to this + # directory. Defaults to "$root_build_dir/apks". + # + # bundle_name: Optional. If set, the bundle will be output to the + # filename "${bundle_name}.aab". + # + # extra_modules: Optional list of scopes, one per extra module used by + # this bundle. Each scope must have a 'name' field that specifies the + # module name (which cannot be 'base', since this is reserved for the + # base module), and an 'apk_target' field that specified the + # corresponding android_apk target name the module is modeled on. + # + # enable_language_splits: Optional. If true, enable APK splits based + # on languages. + # + # keystore_path: optional keystore path, used only when generating APKs. + # keystore_name: optional keystore name, used only when generating APKs. + # keystore_password: optional keystore password, used only when + # generating APKs. + # + # command_line_flags_file: Optional. If provided, named of the on-device + # file that will be used to store command-line arguments. The default + # is 'command_line_flags_file', but this is typically redefined to + # something more specific for certain bundles (e.g. the Chromium based + # APKs use 'chrome-command-line', the WebView one uses + # 'webview-command-line'). + # + # proguard_enabled: Optional. True if proguarding is enabled for this + # bundle. Default is to enable this only for release builds. Note that + # this will always perform synchronized proguarding. + # + # proguard_enable_obfuscation: Whether to enable obfuscation (default=true) + # + # enable_multidex: Optional. Enable multidexing of optimized modules jars + # when using synchronized proguarding. Only applies to base module. + # + # proguard_android_sdk_dep: Optional. android_system_java_prebuilt() target + # used as a library jar for synchronized proguarding. + # + # compress_shared_libraries: Optional. Whether to compress shared libraries + # such that they are extracted upon install. Libraries prefixed with + # "crazy." are never compressed. + # + # system_image_locale_allowlist: List of locales that should be included + # on system APKs generated from this bundle. + # + # static_library_provider: Specifies a single target that this target will + # use as a static library APK. + # Additionally, when allotting libraries to be packaged into modules, the + # libraries packaged into the static library will be accounted for to + # avoid library duplication. Effectively, the static library will be + # treated as the parent of the base module. + # + # expected_libs_and_assets: Verify the list of included native libraries + # and assets is consistent with the given expectation file. + # expected_libs_and_assets_base: Treat expected_libs_and_assets as a diff + # with this file as the base. + # expected_proguard_config: Checks that the merged set of proguard flags + # matches the given config. + # expected_proguard_config_base: Treat expected_proguard_config as a diff + # with this file as the base. + # + # version_code: Optional. Version code of the target. + # + # is_multi_abi: If true will add a library placeholder for the missing ABI + # if either the primary or the secondary ABI has no native libraries set. + # + # default_modules_for_testing: (optional): A list of DFM that the wrapper + # script should install. This is for local testing only, and does not + # affect the actual DFM in production. + # Example: + # android_app_bundle("chrome_public_bundle") { + # base_module_target = "//chrome/android:chrome_public_apk" + # extra_modules = [ + # { # NOTE: Scopes require one field per line, and no comma separators. + # name = "my_module" + # module_target = ":my_module" + # }, + # ] + # } + # + template("android_app_bundle") { + _target_name = target_name + _uses_static_library = defined(invoker.static_library_provider) + _proguard_enabled = + defined(invoker.proguard_enabled) && invoker.proguard_enabled + + if (defined(invoker.version_code)) { + _version_code = invoker.version_code + } else { + _version_code = android_default_version_code + } + + if (android_override_version_code != "") { + _version_code = android_override_version_code + } + + # Prevent "unused variable". + not_needed([ "_version_code" ]) + + _bundle_base_path = "$root_build_dir/apks" + if (defined(invoker.bundle_base_path)) { + _bundle_base_path = invoker.bundle_base_path + } + + _bundle_name = _target_name + if (defined(invoker.bundle_name)) { + _bundle_name = invoker.bundle_name + } + _bundle_path = "$_bundle_base_path/${_bundle_name}.aab" + _rebased_bundle_path = rebase_path(_bundle_path, root_build_dir) + + _base_target_name = get_label_info(invoker.base_module_target, "name") + _base_target_gen_dir = + get_label_info(invoker.base_module_target, "target_gen_dir") + _base_module_build_config = + "$_base_target_gen_dir/${_base_target_name}.build_config.json" + _base_module_build_config_target = + "${invoker.base_module_target}$build_config_target_suffix" + _rebased_base_module_build_config = + rebase_path(_base_module_build_config, root_build_dir) + + _modules = [ + { + name = "base" + module_target = invoker.base_module_target + build_config = _base_module_build_config + build_config_target = _base_module_build_config_target + if (_uses_static_library) { + parent = "lib" + } + }, + ] + + _enable_multidex = + !defined(invoker.enable_multidex) || invoker.enable_multidex + + # Prevent "unused variable". + not_needed([ "_enable_multidex" ]) + + if (_proguard_enabled) { + _uses_static_library_synchronized_proguard = + defined(invoker.static_library_synchronized_proguard) && + invoker.static_library_synchronized_proguard + + # TODO(crbug.com/1032609): Remove dexsplitter from Trichrome Proguard. + _dex_target = "${_target_name}__dex" + _proguard_mapping_path = "${_bundle_path}.mapping" + } + + assert(_proguard_enabled || !defined(invoker.enable_multidex), + "Bundle only adds dexing step if proguarding is enabled.") + + if (defined(invoker.extra_modules)) { + _module_count = 0 + not_needed([ "_module_count" ]) + + foreach(_module, invoker.extra_modules) { + _module_count += 1 + assert(defined(_module.name), + "Missing 'name' field for extra module #${_module_count}.") + assert(_module.name != "base", + "Module name 'base' is reserved for the main bundle module") + assert( + defined(_module.module_target), + "Missing 'module_target' field for extra module ${_module.name}.") + _module_target = _module.module_target + _module_target_name = get_label_info(_module_target, "name") + _module_target_gen_dir = + get_label_info(_module_target, "target_gen_dir") + _module.build_config = + "$_module_target_gen_dir/${_module_target_name}.build_config.json" + _module.build_config_target = + "$_module_target$build_config_target_suffix" + _module.parent = "base" + _modules += [ _module ] + } + } + + # Make build config, which is required for synchronized proguarding. + _module_java_targets = [] + _module_build_configs = [] + _module_targets = [] + foreach(_module, _modules) { + _module_targets += [ _module.module_target ] + _module_java_targets += [ "${_module.module_target}__java" ] + _module_build_configs += [ _module.build_config ] + } + + if (_uses_static_library) { + _lib_proxy_module = { + name = "lib" + } + _static_library_target_name = + get_label_info(invoker.static_library_provider, "name") + _static_library_gen_dir = + get_label_info(invoker.static_library_provider, "target_gen_dir") + _lib_proxy_module.build_config = "$_static_library_gen_dir/$_static_library_target_name.build_config.json" + _lib_proxy_module.build_config_target = + "${invoker.static_library_provider}$build_config_target_suffix" + } + + # Allot native libraries to modules they should be packaged into. This is + # necessary since all libraries that are depended on by multiple modules + # have to go into base or the static shared library if it exists. + # TODO(crbug.com/1021565): It would be nice if this lived outside the + # android_app_bundle template and the static shared library would pull in + # the libs as allotted by this step. + _native_libraries_config = + "$target_gen_dir/$_target_name.native_libraries_config" + _native_libraries_config_target = "${_target_name}__allot_native_libraries" + allot_native_libraries(_native_libraries_config_target) { + modules = _modules + native_libraries_filearg_keys = [ + "native:libraries", + "native:loadable_modules", + ] + output = _native_libraries_config + if (_uses_static_library) { + modules += [ _lib_proxy_module ] + } + } + if (defined(android_app_secondary_abi)) { + _secondary_abi_native_libraries_config = + "$target_gen_dir/$_target_name.secondary_abi_native_libraries_config" + _secondary_abi_native_libraries_config_target = + "${_target_name}__allot_secondary_abi_native_libraries" + allot_native_libraries(_secondary_abi_native_libraries_config_target) { + modules = _modules + native_libraries_filearg_keys = [ + "native:secondary_abi_libraries", + "native:secondary_abi_loadable_modules", + ] + output = _secondary_abi_native_libraries_config + if (_uses_static_library) { + modules += [ _lib_proxy_module ] + } + } + } + + # Used to expose the module Java targets of the bundle. + group("${_target_name}__java") { + deps = _module_java_targets + } + group("${_target_name}__compile_resources") { + deps = [ "${invoker.base_module_target}__compile_resources" ] + } + + _build_config = "$target_gen_dir/${_target_name}.build_config.json" + _rebased_build_config = rebase_path(_build_config, root_build_dir) + _build_config_target = "$_target_name$build_config_target_suffix" + if (defined(invoker.proguard_android_sdk_dep)) { + proguard_android_sdk_dep_ = invoker.proguard_android_sdk_dep + } else { + proguard_android_sdk_dep_ = "//third_party/android_sdk:android_sdk_java" + } + + if (_proguard_enabled) { + _proguard_mapping_path = "${_bundle_path}.mapping" + } + + write_build_config(_build_config_target) { + type = "android_app_bundle" + possible_config_deps = _module_targets + [ proguard_android_sdk_dep_ ] + build_config = _build_config + proguard_enabled = _proguard_enabled + module_build_configs = _module_build_configs + + if (_proguard_enabled) { + proguard_mapping_path = _proguard_mapping_path + } + } + + if (_proguard_enabled) { + # If this Bundle uses a static library, the static library APK will + # create the synchronized dex file path. + if (!_uses_static_library_synchronized_proguard) { + dex(_dex_target) { + forward_variables_from(invoker, + [ + "expected_proguard_config", + "expected_proguard_config_base", + "min_sdk_version", + "proguard_enable_obfuscation", + ]) + if (defined(expected_proguard_config)) { + top_target_name = _target_name + } + enable_multidex = _enable_multidex + proguard_enabled = true + proguard_mapping_path = _proguard_mapping_path + proguard_sourcefile_suffix = "$android_channel-$_version_code" + build_config = _build_config + + deps = _module_java_targets + [ ":$_build_config_target" ] + modules = _modules + } + } + } + + _all_create_module_targets = [] + _all_module_zip_paths = [] + _all_module_build_configs = [] + _all_module_unused_resources_deps = [] + foreach(_module, _modules) { + _module_target = _module.module_target + _module_build_config = _module.build_config + _module_build_config_target = _module.build_config_target + + if (!_proguard_enabled) { + _dex_target_for_module = "${_module_target}__final_dex" + } else { + _dex_target_for_module = ":$_dex_target" + } + + # Generate one module .zip file per bundle module. + # + # Important: the bundle tool uses the module's zip filename as + # the internal module name inside the final bundle, in other words, + # this file *must* be named ${_module.name}.zip + _create_module_target = "${_target_name}__${_module.name}__create" + _module_zip_path = "$target_gen_dir/$target_name/${_module.name}.zip" + create_android_app_bundle_module(_create_module_target) { + forward_variables_from(invoker, + [ + "is_multi_abi", + "min_sdk_version", + "uncompress_dex", + "proguard_enabled", + ]) + module_name = _module.name + build_config = _module_build_config + module_zip_path = _module_zip_path + native_libraries_config = _native_libraries_config + + if (module_name == "base" && + defined(invoker.expected_libs_and_assets)) { + forward_variables_from(invoker, + [ + "expected_libs_and_assets", + "expected_libs_and_assets_base", + ]) + top_target_name = _target_name + build_config_target = _module_build_config_target + native_libraries_config_target = ":$_native_libraries_config_target" + if (defined(android_app_secondary_abi)) { + secondary_abi_native_libraries_config_target = + ":$_secondary_abi_native_libraries_config_target" + } + } + + deps = [ + ":$_native_libraries_config_target", + _dex_target_for_module, + _module_build_config_target, + _module_target, + ] + + if (defined(android_app_secondary_abi)) { + secondary_abi_native_libraries_config = + _secondary_abi_native_libraries_config + deps += [ ":$_secondary_abi_native_libraries_config_target" ] + } + } + + _all_create_module_targets += [ + ":$_create_module_target", + _module_build_config_target, + "${_module_target}__compile_resources", + ] + _all_module_zip_paths += [ _module_zip_path ] + _all_module_build_configs += [ _module_build_config ] + _all_module_unused_resources_deps += [ + "${_module_target}__compile_resources", + _dex_target_for_module, + _module_build_config_target, + ] + } + if (defined(invoker.strip_unused_resources) && + invoker.strip_unused_resources) { + # Resources only live in the base module so we define the unused resources + # target only on the base module target. + _unused_resources_target = "${_base_target_name}__unused_resources" + _unused_resources_config = + "${_base_target_gen_dir}/${_base_target_name}_unused_resources.config" + unused_resources(_unused_resources_target) { + deps = _all_module_unused_resources_deps + all_module_build_configs = _all_module_build_configs + build_config = _base_module_build_config + if (_proguard_enabled) { + proguard_mapping_path = _proguard_mapping_path + } + output_config = _unused_resources_config + } + } + + _all_rebased_module_zip_paths = + rebase_path(_all_module_zip_paths, root_build_dir) + + _enable_language_splits = defined(invoker.enable_language_splits) && + invoker.enable_language_splits + + _split_dimensions = [] + if (_enable_language_splits) { + _split_dimensions += [ "language" ] + } + + _keystore_path = android_keystore_path + _keystore_password = android_keystore_password + _keystore_name = android_keystore_name + + if (defined(invoker.keystore_path)) { + _keystore_path = invoker.keystore_path + _keystore_password = invoker.keystore_password + _keystore_name = invoker.keystore_name + } + + _rebased_keystore_path = rebase_path(_keystore_path, root_build_dir) + + _bundle_target_name = "${_target_name}__bundle" + action_with_pydeps(_bundle_target_name) { + script = "//build/android/gyp/create_app_bundle.py" + inputs = _all_module_zip_paths + _all_module_build_configs + outputs = [ _bundle_path ] + deps = _all_create_module_targets + [ ":$_build_config_target" ] + args = [ + "--out-bundle=$_rebased_bundle_path", + "--rtxt-out-path=$_rebased_bundle_path.R.txt", + "--pathmap-out-path=$_rebased_bundle_path.pathmap.txt", + "--module-zips=$_all_rebased_module_zip_paths", + ] + if (_split_dimensions != []) { + args += [ "--split-dimensions=$_split_dimensions" ] + } + if (defined(invoker.compress_shared_libraries) && + invoker.compress_shared_libraries) { + args += [ "--compress-shared-libraries" ] + } + _min_sdk_version = default_min_sdk_version + if (defined(invoker.min_sdk_version)) { + _min_sdk_version = invoker.min_sdk_version + } + + # Android P+ support loading from stored dex. + if (_min_sdk_version < 27) { + args += [ "--compress-dex" ] + } + + if (treat_warnings_as_errors) { + args += [ "--warnings-as-errors" ] + } + + if (_enable_language_splits) { + args += [ + "--base-allowlist-rtxt-path=@FileArg(" + "${_rebased_base_module_build_config}:deps_info:base_allowlist_rtxt_path)", + "--base-module-rtxt-path=@FileArg(" + + "${_rebased_base_module_build_config}:deps_info:r_text_path)", + ] + } + if (defined(invoker.validate_services) && invoker.validate_services) { + args += [ "--validate-services" ] + } + + foreach(_module, _modules) { + _rebased_build_config = + rebase_path(_module.build_config, root_build_dir) + args += [ + "--uncompressed-assets=@FileArg(" + + "$_rebased_build_config:uncompressed_assets)", + "--rtxt-in-paths=@FileArg(" + + "$_rebased_build_config:deps_info:r_text_path)", + "--pathmap-in-paths=@FileArg(" + + "$_rebased_build_config:deps_info:module_pathmap_path)", + "--module-name=" + _module.name, + ] + } + + # http://crbug.com/725224. Fix for bots running out of memory. + if (defined(java_cmd_pool_size)) { + pool = "//build/config/android:java_cmd_pool($default_toolchain)" + } else { + pool = "//build/toolchain:link_pool($default_toolchain)" + } + } + + # Create size info files for targets that care about size + # (have proguard enabled). + if (_proguard_enabled) { + # Merge all module targets to obtain size info files for all targets. + _all_module_targets = _module_targets + + _size_info_target = "${_target_name}__size_info" + create_size_info_files(_size_info_target) { + name = "$_bundle_name.aab" + deps = _all_module_targets + [ ":$_build_config_target" ] + module_build_configs = _all_module_build_configs + } + } + + if (_uses_static_library) { + _install_artifacts_target = "${target_name}__install_artifacts" + _install_artifacts_json = + "${target_gen_dir}/${target_name}.install_artifacts" + generated_file(_install_artifacts_target) { + output_conversion = "json" + deps = [ invoker.static_library_provider ] + outputs = [ _install_artifacts_json ] + data_keys = [ "install_artifacts" ] + rebase = root_build_dir + } + } + + # Generate a wrapper script for the bundle. + _android_aapt2_path = android_sdk_tools_bundle_aapt2 + + _bundle_apks_path = "$_bundle_base_path/$_bundle_name.apks" + _bundle_wrapper_script_dir = "$root_build_dir/bin" + _bundle_wrapper_script_path = "$_bundle_wrapper_script_dir/$_target_name" + + action_with_pydeps("${_target_name}__wrapper_script") { + script = "//build/android/gyp/create_bundle_wrapper_script.py" + inputs = [ _base_module_build_config ] + outputs = [ _bundle_wrapper_script_path ] + + # Telemetry for bundles uses the wrapper script for installation. + data = [ + _bundle_wrapper_script_path, + _android_aapt2_path, + _keystore_path, + _bundle_path, + ] + data_deps = [ + "//build/android:apk_operations_py", + "//build/android:stack_tools", + ] + + deps = [ _base_module_build_config_target ] + args = [ + "--script-output-path", + rebase_path(_bundle_wrapper_script_path, root_build_dir), + "--package-name=@FileArg(" + + "$_rebased_base_module_build_config:deps_info:package_name)", + "--aapt2", + rebase_path(_android_aapt2_path, root_build_dir), + "--bundle-path", + _rebased_bundle_path, + "--bundle-apks-path", + rebase_path(_bundle_apks_path, root_build_dir), + "--target-cpu=$target_cpu", + "--keystore-path", + _rebased_keystore_path, + "--keystore-password", + _keystore_password, + "--key-name", + _keystore_name, + ] + if (defined(invoker.default_modules_for_testing)) { + args += [ "--default-modules" ] + invoker.default_modules_for_testing + } + if (defined(invoker.system_image_locale_allowlist)) { + args += [ + "--system-image-locales=${invoker.system_image_locale_allowlist}", + ] + } + if (defined(invoker.command_line_flags_file)) { + args += [ + "--command-line-flags-file", + invoker.command_line_flags_file, + ] + } + if (_uses_static_library) { + deps += [ ":$_install_artifacts_target" ] + _rebased_install_artifacts_json = + rebase_path(_install_artifacts_json, root_build_dir) + _static_library_apk_path = + "@FileArg($_rebased_install_artifacts_json[])" + args += [ + "--additional-apk", + _static_library_apk_path, + ] + } + + if (_proguard_enabled) { + args += [ + "--proguard-mapping-path", + rebase_path(_proguard_mapping_path, root_build_dir), + ] + + # Required by logcat command. + data_deps += [ "//build/android/stacktrace:java_deobfuscate" ] + data += [ _proguard_mapping_path ] + } + } + + _enable_lint = defined(invoker.enable_lint) && invoker.enable_lint && + !disable_android_lint + if (_enable_lint) { + android_lint("${target_name}__lint") { + forward_variables_from(invoker, + [ + "lint_baseline_file", + "lint_suppressions_file", + "min_sdk_version", + ]) + build_config = _build_config + build_config_dep = ":$_build_config_target" + deps = _module_java_targets + if (defined(invoker.lint_suppressions_dep)) { + deps += [ invoker.lint_suppressions_dep ] + } + if (defined(invoker.lint_min_sdk_version)) { + min_sdk_version = invoker.lint_min_sdk_version + } + } + } else { + not_needed(invoker, + [ + "lint_baseline_file", + "lint_min_sdk_version", + "lint_suppressions_dep", + "lint_suppressions_file", + ]) + } + + group(_target_name) { + public_deps = [ + ":$_bundle_target_name", + ":${_target_name}__wrapper_script", + ] + if (defined(_size_info_target)) { + public_deps += [ ":$_size_info_target" ] + } + if (_enable_lint) { + if (!defined(data_deps)) { + data_deps = [] + } + data_deps += [ ":${target_name}__lint" ] + } + } + + _apks_path = "$root_build_dir/apks/$_bundle_name.apks" + action_with_pydeps("${_target_name}_apks") { + script = "//build/android/gyp/create_app_bundle_apks.py" + inputs = [ _bundle_path ] + outputs = [ _apks_path ] + data = [ _apks_path ] + args = [ + "--bundle", + _rebased_bundle_path, + "--output", + rebase_path(_apks_path, root_build_dir), + "--aapt2-path", + rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir), + "--keystore-path", + rebase_path(android_keystore_path, root_build_dir), + "--keystore-name", + android_keystore_name, + "--keystore-password", + android_keystore_password, + ] + if (debuggable_apks) { + args += [ "--local-testing" ] + } + deps = [ ":$_bundle_target_name" ] + metadata = { + install_artifacts = [ _apks_path ] + } + if (defined(invoker.static_library_provider)) { + metadata.install_artifacts_barrier = [] + } + + # http://crbug.com/725224. Fix for bots running out of memory. + if (defined(java_cmd_pool_size)) { + pool = "//build/config/android:java_cmd_pool($default_toolchain)" + } else { + pool = "//build/toolchain:link_pool($default_toolchain)" + } + } + } + + # Create an .apks file from an .aab file. The .apks file will contain the + # minimal set of .apk files needed for tracking binary size. + # The file will be created at "$bundle_path_without_extension.minimal.apks". + # + # Variables: + # bundle_path: Path to the input .aab file. + # + # Example: + # create_app_bundle_minimal_apks("minimal_apks") { + # deps = [ + # ":bundle_target", + # ] + # bundle_path = "$root_build_dir/apks/Bundle.aab" + # } + template("create_app_bundle_minimal_apks") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ]) + script = "//build/android/gyp/create_app_bundle_apks.py" + _dir = get_path_info(invoker.bundle_path, "dir") + _name = get_path_info(invoker.bundle_path, "name") + _output_path = "$_dir/$_name.minimal.apks" + outputs = [ _output_path ] + inputs = [ invoker.bundle_path ] + args = [ + "--bundle", + rebase_path(invoker.bundle_path, root_build_dir), + "--output", + rebase_path(_output_path, root_build_dir), + "--aapt2-path", + rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir), + "--keystore-path", + rebase_path(android_keystore_path, root_build_dir), + "--keystore-name", + android_keystore_name, + "--keystore-password", + android_keystore_password, + "--minimal", + ] + } + } +} + +# Generate an Android resources target that contains localized strings +# describing the current locale used by the Android framework to display +# UI strings. These are used by +# org.chromium.chrome.browser.ChromeLocalizationUtils. +# +# Variables: +# ui_locales: List of Chromium locale names to generate resources for. +# +template("generate_ui_locale_resources") { + _generating_target_name = "${target_name}__generate" + _rebased_output_zip_path = rebase_path(target_gen_dir, root_gen_dir) + _output_zip = "${root_out_dir}/resource_zips/${_rebased_output_zip_path}/" + + "${target_name}.zip" + + action_with_pydeps(_generating_target_name) { + script = "//build/android/gyp/create_ui_locale_resources.py" + outputs = [ _output_zip ] + args = [ + "--locale-list=${invoker.ui_locales}", + "--output-zip", + rebase_path(_output_zip, root_build_dir), + ] + } + + android_generated_resources(target_name) { + generating_target = ":$_generating_target_name" + generated_resources_zip = _output_zip + } +} diff --git a/third_party/libwebrtc/build/config/android/sdk.gni b/third_party/libwebrtc/build/config/android/sdk.gni new file mode 100644 index 0000000000..8547170167 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/sdk.gni @@ -0,0 +1,10 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# The default SDK release used by public builds. Value may differ in +# internal builds. +default_android_sdk_release = "s" + +# SDK releases against which public builds are supported. +public_sdk_releases = [ "s" ] diff --git a/third_party/libwebrtc/build/config/android/test/classpath_order/BUILD.gn b/third_party/libwebrtc/build/config/android/test/classpath_order/BUILD.gn new file mode 100644 index 0000000000..decd1a84d2 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/classpath_order/BUILD.gn @@ -0,0 +1,111 @@ +# Copyright 2021 The Chromium 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") + +template("test_resources") { + jinja_template_resources(target_name) { + forward_variables_from(invoker, "*") + testonly = true + variables = [ "resource_name=$resource_name" ] + res_dir = "java/res_template" + resources = [ "java/res_template/values/values.xml" ] + } +} + +template("generate_dummy_android_library") { + # No underscores to avoid crbug.com/908819. + _generate_java_source_target_name = "${target_name}generatejavasource" + jinja_template(_generate_java_source_target_name) { + testonly = true + input = "java/src/org/chromium/build/classpath_order/Dummy.java.jinja2" + output = "$target_gen_dir/java/src/org/chromium/build/classpath_order/${invoker.class_name}.java" + variables = [ "class_name=${invoker.class_name}" ] + } + + android_library(target_name) { + forward_variables_from(invoker, "*") + + if (!defined(invoker.deps)) { + deps = [] + } + + sources = get_target_outputs(":${_generate_java_source_target_name}") + deps += [ ":${_generate_java_source_target_name}" ] + } +} + +# Test that classpath order keeps resources accessible when multiple targets generate +# resources for the same package. Specifically, test that an android_library precedes +# its dependencies regardless of the relative lexographic order. + +test_resources("a1_dependency_resources") { + resource_name = "a1_dependency_resource" +} + +generate_dummy_android_library("a1_dependency_java") { + testonly = true + class_name = "A1Dependency" + resources_package = "org.chromium.build.classpath_order.test1" + deps = [ ":a1_dependency_resources" ] +} + +test_resources("z1_master_resources") { + resource_name = "z1_master_resource" + deps = [ ":a1_dependency_resources" ] +} + +generate_dummy_android_library("z1_master_java") { + testonly = true + class_name = "Z1Master" + resources_package = "org.chromium.build.classpath_order.test1" + deps = [ + ":a1_dependency_java", + ":z1_master_resources", + ] +} + +test_resources("z2_dependency_resources") { + resource_name = "z2_dependency_resource" +} + +generate_dummy_android_library("z2_dependency_java") { + testonly = true + class_name = "Z2Dependency" + resources_package = "org.chromium.build.classpath_order.test2" + deps = [ ":z2_dependency_resources" ] +} + +test_resources("a2_master_resources") { + resource_name = "a2_master_resource" + deps = [ ":z2_dependency_resources" ] +} + +generate_dummy_android_library("a2_master_java") { + testonly = true + class_name = "A2Master" + resources_package = "org.chromium.build.classpath_order.test2" + deps = [ + ":a2_master_resources", + ":z2_dependency_java", + ] +} + +java_library("junit_tests") { + bypass_platform_checks = true + testonly = true + sources = + [ "java/src/org/chromium/build/classpath_order/ClassPathOrderTest.java" ] + deps = [ + ":a1_dependency_java", + ":a2_master_java", + ":z1_master_java", + ":z2_dependency_java", + "//testing/android/junit:junit_test_support", + "//third_party/android_deps:robolectric_all_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/androidx:androidx_test_runner_java", + "//third_party/junit", + ] +} diff --git a/third_party/libwebrtc/build/config/android/test/classpath_order/java/res_template/values/values.xml b/third_party/libwebrtc/build/config/android/test/classpath_order/java/res_template/values/values.xml new file mode 100644 index 0000000000..ee706b289b --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/classpath_order/java/res_template/values/values.xml @@ -0,0 +1,9 @@ + + + + + + 42 + diff --git a/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/ClassPathOrderTest.java b/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/ClassPathOrderTest.java new file mode 100644 index 0000000000..c5a9202605 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/ClassPathOrderTest.java @@ -0,0 +1,32 @@ +// Copyright 2021 The Chromium 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.build.classpath_order; + +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import org.chromium.testing.local.LocalRobolectricTestRunner; + +/** + * Test that resources defined in different android_resources() targets but with the same + * package are accessible. + */ +@RunWith(LocalRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public final class ClassPathOrderTest { + @Test + @SmallTest + public void testAll() { + assertTrue(org.chromium.build.classpath_order.test1.R.integer.a1_dependency_resource >= 0); + assertTrue(org.chromium.build.classpath_order.test1.R.integer.z1_master_resource >= 0); + assertTrue(org.chromium.build.classpath_order.test2.R.integer.z2_dependency_resource >= 0); + assertTrue(org.chromium.build.classpath_order.test2.R.integer.a2_master_resource >= 0); + } +} diff --git a/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/Dummy.java.jinja2 b/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/Dummy.java.jinja2 new file mode 100644 index 0000000000..0ccf28b284 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/classpath_order/java/src/org/chromium/build/classpath_order/Dummy.java.jinja2 @@ -0,0 +1,8 @@ +// Copyright 2021 The Chromium 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.build.classpath_order; + +public class {{class_name}} { +} diff --git a/third_party/libwebrtc/build/config/android/test/proto/BUILD.gn b/third_party/libwebrtc/build/config/android/test/proto/BUILD.gn new file mode 100644 index 0000000000..a28111a66a --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright 2020 The Chromium 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") +import("//third_party/protobuf/proto_library.gni") + +# The purpose of these targets is test that |deps| satisfies java compilation +# dependencies, and that |import_dirs| allows us to deal with various relative +# imports to other proto dependencies. Although we should strive to avoid using +# |import_dirs| and relative import paths, preferring to use absolute imports +# whenever possible. See https://crbug.com/691451. While this target is +# primarily to test that the Java proto targets build correctly, also build the +# C++ versions of the protos as well. There are currently some configurations of +# Java protos that can be built but will not work for C++, see +# https://crbug.com/1039014, so make sure we don't create any tests that would +# violate that. +group("test_build_protos") { + deps = [ + ":absolute_root_proto", + ":absolute_root_proto_java", + ":relative_root_proto", + ":relative_root_proto_java", + ] +} + +proto_java_library("absolute_root_proto_java") { + proto_path = "//" + import_dirs = [ "relative_dep/" ] + sources = [ + "root/absolute_child.proto", + "root/absolute_root.proto", + ] + deps = [ + ":absolute_dep_proto_java", + ":relative_dep_proto_java", + ] +} + +proto_java_library("relative_root_proto_java") { + proto_path = "root/" + import_dirs = [ + "relative_dep/", + "//", + ] + sources = [ + "root/relative_child.proto", + "root/relative_root.proto", + ] + deps = [ + ":absolute_dep_proto_java", + ":relative_dep_proto_java", + ] +} + +proto_java_library("absolute_dep_proto_java") { + proto_path = "//" + sources = [ "absolute_dep/absolute_dep.proto" ] +} + +proto_java_library("relative_dep_proto_java") { + proto_path = "relative_dep/" + sources = [ "relative_dep/relative_dep.proto" ] +} + +proto_library("absolute_root_proto") { + proto_in_dir = "//" + import_dirs = [ "relative_dep/" ] + sources = [ + "root/absolute_child.proto", + "root/absolute_root.proto", + ] + link_deps = [ + ":absolute_dep_proto", + ":relative_dep_proto", + ] +} + +proto_library("relative_root_proto") { + proto_in_dir = "root/" + import_dirs = [ + "relative_dep/", + "//", + ] + sources = [ + "root/relative_child.proto", + "root/relative_root.proto", + ] + link_deps = [ + ":absolute_dep_proto", + ":relative_dep_proto", + ] +} + +proto_library("absolute_dep_proto") { + proto_in_dir = "//" + sources = [ "absolute_dep/absolute_dep.proto" ] +} + +proto_library("relative_dep_proto") { + proto_in_dir = "relative_dep/" + sources = [ "relative_dep/relative_dep.proto" ] +} diff --git a/third_party/libwebrtc/build/config/android/test/proto/absolute_dep/absolute_dep.proto b/third_party/libwebrtc/build/config/android/test/proto/absolute_dep/absolute_dep.proto new file mode 100644 index 0000000000..46dcce7679 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/absolute_dep/absolute_dep.proto @@ -0,0 +1,10 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +message AbsoluteDep {} diff --git a/third_party/libwebrtc/build/config/android/test/proto/relative_dep/relative_dep.proto b/third_party/libwebrtc/build/config/android/test/proto/relative_dep/relative_dep.proto new file mode 100644 index 0000000000..600b6ca7fe --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/relative_dep/relative_dep.proto @@ -0,0 +1,10 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +message RelativeDep {} diff --git a/third_party/libwebrtc/build/config/android/test/proto/root/absolute_child.proto b/third_party/libwebrtc/build/config/android/test/proto/root/absolute_child.proto new file mode 100644 index 0000000000..d6a6a13f36 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/root/absolute_child.proto @@ -0,0 +1,10 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +message AbsoluteChild {} diff --git a/third_party/libwebrtc/build/config/android/test/proto/root/absolute_root.proto b/third_party/libwebrtc/build/config/android/test/proto/root/absolute_root.proto new file mode 100644 index 0000000000..3e200978d9 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/root/absolute_root.proto @@ -0,0 +1,18 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +import "build/config/android/test/proto/root/absolute_child.proto"; +import "build/config/android/test/proto/absolute_dep/absolute_dep.proto"; +import "relative_dep.proto"; + +message AbsoluteRoot { + optional AbsoluteChild absolute_child = 1; + optional AbsoluteDep absolute_dep = 2; + optional RelativeDep relative_dep = 3; +} diff --git a/third_party/libwebrtc/build/config/android/test/proto/root/relative_child.proto b/third_party/libwebrtc/build/config/android/test/proto/root/relative_child.proto new file mode 100644 index 0000000000..10f7ed4277 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/root/relative_child.proto @@ -0,0 +1,10 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +message RelativeChild {} diff --git a/third_party/libwebrtc/build/config/android/test/proto/root/relative_root.proto b/third_party/libwebrtc/build/config/android/test/proto/root/relative_root.proto new file mode 100644 index 0000000000..a37a268b4f --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/proto/root/relative_root.proto @@ -0,0 +1,18 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package build.config.android.test; +option java_package = "build.config.android.test"; + +import "relative_child.proto"; +import "build/config/android/test/proto/absolute_dep/absolute_dep.proto"; +import "relative_dep.proto"; + +message RelativeRoot { + optional RelativeChild relative_child = 1; + optional AbsoluteDep absolute_dep = 2; + optional RelativeDep relative_dep = 3; +} diff --git a/third_party/libwebrtc/build/config/android/test/resource_overlay/BUILD.gn b/third_party/libwebrtc/build/config/android/test/resource_overlay/BUILD.gn new file mode 100644 index 0000000000..4a063d2215 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/resource_overlay/BUILD.gn @@ -0,0 +1,60 @@ +# Copyright 2020 The Chromium 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") + +# Tests for 'resource_overlay' parameter in android_resources() template. + +template("test_resources") { + jinja_template_resources(target_name) { + forward_variables_from(invoker, "*") + testonly = true + variables = [ + "resource_name=$resource_name", + "resource_value=$resource_value", + ] + res_dir = "java/res_template" + resources = [ "java/res_template/values/values.xml" ] + } +} + +test_resources("dependency_tagged_dependency_resources") { + resource_overlay = true + resource_name = "resource_overlay_dependency_tagged_secret" + resource_value = 41 +} + +test_resources("dependency_tagged_root_resources") { + resource_name = "resource_overlay_dependency_tagged_secret" + resource_value = 42 + deps = [ ":dependency_tagged_dependency_resources" ] +} + +test_resources("root_tagged_dependency_resources") { + resource_name = "resource_overlay_root_tagged_secret" + resource_value = 41 +} + +test_resources("root_tagged_root_resources") { + resource_overlay = true + resource_name = "resource_overlay_root_tagged_secret" + resource_value = 42 + deps = [ ":root_tagged_dependency_resources" ] +} + +android_library("javatests") { + testonly = true + sources = [ + "java/src/org/chromium/build/resource_overlay/ResourceOverlayTest.java", + ] + resources_package = "org.chromium.build.resource_overlay" + deps = [ + ":dependency_tagged_root_resources", + ":root_tagged_root_resources", + "//base:base_java_test_support", + "//third_party/android_support_test_runner:runner_java", + "//third_party/androidx:androidx_test_runner_java", + "//third_party/junit", + ] +} diff --git a/third_party/libwebrtc/build/config/android/test/resource_overlay/java/res_template/values/values.xml b/third_party/libwebrtc/build/config/android/test/resource_overlay/java/res_template/values/values.xml new file mode 100644 index 0000000000..973f855206 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/resource_overlay/java/res_template/values/values.xml @@ -0,0 +1,10 @@ + + + + + {{resource_value}} + \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/android/test/resource_overlay/java/src/org/chromium/build/resource_overlay/ResourceOverlayTest.java b/third_party/libwebrtc/build/config/android/test/resource_overlay/java/src/org/chromium/build/resource_overlay/ResourceOverlayTest.java new file mode 100644 index 0000000000..794cafac53 --- /dev/null +++ b/third_party/libwebrtc/build/config/android/test/resource_overlay/java/src/org/chromium/build/resource_overlay/ResourceOverlayTest.java @@ -0,0 +1,49 @@ +// Copyright 2020 The Chromium 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.build.resource_overlay; + +import static org.junit.Assert.assertEquals; + +import android.content.res.Resources; +import android.support.test.InstrumentationRegistry; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Batch; + +/** + * Test for resource_overlay parameter in android_resources() build rule. + */ +@RunWith(BaseJUnit4ClassRunner.class) +@Batch(Batch.UNIT_TESTS) +public class ResourceOverlayTest { + /** + * Test that when an android_resources() target with resource_overlay=false has a resource with + * the same name but a different value as a dependency with resource_overlay=true that the value + * of the resource in the dependency is used. + */ + @Test + @SmallTest + public void testDependencyTagged() { + Resources resources = InstrumentationRegistry.getTargetContext().getResources(); + assertEquals(41, resources.getInteger(R.integer.resource_overlay_dependency_tagged_secret)); + } + + /** + * Test that when an android_resources() target with resource_overlay=true has a resource with + * the same name but different value as one of its dependencies that the value of resource in + * the target with resource_overlay=true is used. + */ + @Test + @SmallTest + public void testRootTagged() { + Resources resources = InstrumentationRegistry.getTargetContext().getResources(); + assertEquals(42, resources.getInteger(R.integer.resource_overlay_root_tagged_secret)); + } +} diff --git a/third_party/libwebrtc/build/config/apple/OWNERS b/third_party/libwebrtc/build/config/apple/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/config/apple/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/config/apple/sdk_info.py b/third_party/libwebrtc/build/config/apple/sdk_info.py new file mode 100644 index 0000000000..fea680101b --- /dev/null +++ b/third_party/libwebrtc/build/config/apple/sdk_info.py @@ -0,0 +1,177 @@ +# 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 __future__ import print_function + +import argparse +import doctest +import itertools +import os +import plistlib +import re +import subprocess +import sys + +if sys.version_info.major < 3: + basestring_compat = basestring +else: + basestring_compat = str + +# src directory +ROOT_SRC_DIR = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname( + os.path.realpath(__file__))))) + +# This script prints information about the build system, the operating +# system and the iOS or Mac SDK (depending on the platform "iphonesimulator", +# "iphoneos" or "macosx" generally). + + +def LoadPList(path): + """Loads Plist at |path| and returns it as a dictionary.""" + # Cloned from //build/apple/plist_util.py. + if sys.version_info.major == 2: + return plistlib.readPlist(path) + with open(path, 'rb') as f: + return plistlib.load(f) + + +def SplitVersion(version): + """Splits the Xcode version to 3 values. + + >>> list(SplitVersion('8.2.1.1')) + ['8', '2', '1'] + >>> list(SplitVersion('9.3')) + ['9', '3', '0'] + >>> list(SplitVersion('10.0')) + ['10', '0', '0'] + """ + version = version.split('.') + return itertools.islice(itertools.chain(version, itertools.repeat('0')), 0, 3) + + +def FormatVersion(version): + """Converts Xcode version to a format required for DTXcode in Info.plist + + >>> FormatVersion('8.2.1') + '0821' + >>> FormatVersion('9.3') + '0930' + >>> FormatVersion('10.0') + '1000' + """ + major, minor, patch = SplitVersion(version) + return ('%2s%s%s' % (major, minor, patch)).replace(' ', '0') + + +def FillXcodeVersion(settings, developer_dir): + """Fills the Xcode version and build number into |settings|.""" + if developer_dir: + xcode_version_plist_path = os.path.join(developer_dir, + 'Contents/version.plist') + version_plist = LoadPList(xcode_version_plist_path) + settings['xcode_version'] = FormatVersion( + version_plist['CFBundleShortVersionString']) + settings['xcode_version_int'] = int(settings['xcode_version'], 10) + settings['xcode_build'] = version_plist['ProductBuildVersion'] + return + + lines = subprocess.check_output(['xcodebuild', + '-version']).decode('UTF-8').splitlines() + settings['xcode_version'] = FormatVersion(lines[0].split()[-1]) + settings['xcode_version_int'] = int(settings['xcode_version'], 10) + settings['xcode_build'] = lines[-1].split()[-1] + + +def FillMachineOSBuild(settings): + """Fills OS build number into |settings|.""" + machine_os_build = subprocess.check_output(['sw_vers', '-buildVersion' + ]).decode('UTF-8').strip() + settings['machine_os_build'] = machine_os_build + + +def FillSDKPathAndVersion(settings, platform, xcode_version): + """Fills the SDK path and version for |platform| into |settings|.""" + settings['sdk_path'] = subprocess.check_output( + ['xcrun', '-sdk', platform, '--show-sdk-path']).decode('UTF-8').strip() + settings['sdk_version'] = subprocess.check_output( + ['xcrun', '-sdk', platform, + '--show-sdk-version']).decode('UTF-8').strip() + settings['sdk_platform_path'] = subprocess.check_output( + ['xcrun', '-sdk', platform, + '--show-sdk-platform-path']).decode('UTF-8').strip() + settings['sdk_build'] = subprocess.check_output( + ['xcrun', '-sdk', platform, + '--show-sdk-build-version']).decode('UTF-8').strip() + settings['toolchains_path'] = os.path.join( + subprocess.check_output(['xcode-select', + '-print-path']).decode('UTF-8').strip(), + 'Toolchains/XcodeDefault.xctoolchain') + + +def CreateXcodeSymlinkAt(src, dst): + """Create symlink to Xcode directory at target location.""" + + if not os.path.isdir(dst): + os.makedirs(dst) + + dst = os.path.join(dst, os.path.basename(src)) + updated_value = '//' + os.path.relpath(dst, ROOT_SRC_DIR) + + # Update the symlink only if it is different from the current destination. + if os.path.islink(dst): + current_src = os.readlink(dst) + if current_src == src: + return updated_value + os.unlink(dst) + sys.stderr.write('existing symlink %s points %s; want %s. Removed.' % + (dst, current_src, src)) + os.symlink(src, dst) + return updated_value + + +if __name__ == '__main__': + doctest.testmod() + + parser = argparse.ArgumentParser() + parser.add_argument("--developer_dir", dest="developer_dir", required=False) + parser.add_argument("--get_sdk_info", + action="store_true", + dest="get_sdk_info", + default=False, + help="Returns SDK info in addition to xcode info.") + parser.add_argument("--get_machine_info", + action="store_true", + dest="get_machine_info", + default=False, + help="Returns machine info in addition to xcode info.") + parser.add_argument("--create_symlink_at", + action="store", + dest="create_symlink_at", + help="Create symlink of SDK at given location and " + "returns the symlinked paths as SDK info instead " + "of the original location.") + args, unknownargs = parser.parse_known_args() + if args.developer_dir: + os.environ['DEVELOPER_DIR'] = args.developer_dir + + if len(unknownargs) != 1: + sys.stderr.write('usage: %s [iphoneos|iphonesimulator|macosx]\n' % + os.path.basename(sys.argv[0])) + sys.exit(1) + + settings = {} + if args.get_machine_info: + FillMachineOSBuild(settings) + FillXcodeVersion(settings, args.developer_dir) + if args.get_sdk_info: + FillSDKPathAndVersion(settings, unknownargs[0], settings['xcode_version']) + + for key in sorted(settings): + value = settings[key] + if args.create_symlink_at and '_path' in key: + value = CreateXcodeSymlinkAt(value, args.create_symlink_at) + if isinstance(value, basestring_compat): + value = '"%s"' % value + print('%s=%s' % (key, value)) diff --git a/third_party/libwebrtc/build/config/apple/symbols.gni b/third_party/libwebrtc/build/config/apple/symbols.gni new file mode 100644 index 0000000000..dd1d796fa6 --- /dev/null +++ b/third_party/libwebrtc/build/config/apple/symbols.gni @@ -0,0 +1,30 @@ +# 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. + +import("//build/config/chrome_build.gni") +import("//build/config/sanitizers/sanitizers.gni") + +# This file declares arguments and configs that control whether dSYM debug +# info is produced and whether build products are stripped. + +declare_args() { + # Produce dSYM files for targets that are configured to do so. dSYM + # generation is controlled globally as it is a linker output (produced via + # the //build/toolchain/apple/linker_driver.py. Enabling this will result in + # all shared library, loadable module, and executable targets having a dSYM + # generated. + enable_dsyms = is_official_build || using_sanitizer + + # Strip symbols from linked targets by default. If this is enabled, the + # //build/config/mac:strip_all config will be applied to all linked targets. + # If custom stripping parameters are required, remove that config from a + # linked target and apply custom -Wcrl,strip flags. See + # //build/toolchain/apple/linker_driver.py for more information. + enable_stripping = is_official_build +} + +# Save unstripped copies of targets with a ".unstripped" suffix. This is +# useful to preserve the original output when enable_stripping=true but +# we're not actually generating real dSYMs. +save_unstripped_output = enable_stripping && !enable_dsyms diff --git a/third_party/libwebrtc/build/config/arm.gni b/third_party/libwebrtc/build/config/arm.gni new file mode 100644 index 0000000000..a0ada42453 --- /dev/null +++ b/third_party/libwebrtc/build/config/arm.gni @@ -0,0 +1,136 @@ +# 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/v8_target_cpu.gni") + +# These are primarily relevant in target_cpu == "arm" contexts, where +# ARM code is being compiled. But they can also be relevant in the +# other contexts when the code will change its behavior based on the +# cpu it wants to generate code for. +if (target_cpu == "arm" || v8_target_cpu == "arm") { + declare_args() { + # Version of the ARM processor when compiling on ARM. Ignored on non-ARM + # platforms. + arm_version = 7 + + # The ARM architecture. This will be a string like "armv6" or "armv7-a". + # An empty string means to use the default for the arm_version. + arm_arch = "" + + # The ARM floating point hardware. This will be a string like "neon" or + # "vfpv3". An empty string means to use the default for the arm_version. + arm_fpu = "" + + # 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 = "" + + # Whether to enable optional NEON code paths. + arm_optionally_use_neon = false + + # Thumb is a reduced instruction set available on some ARM processors that + # has increased code density. + arm_use_thumb = true + } + + if (current_os == "android" || target_os == "android") { + arm_float_abi = "softfp" + } else { + declare_args() { + # 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 = "" + } + } + assert(arm_float_abi == "" || arm_float_abi == "hard" || + arm_float_abi == "soft" || arm_float_abi == "softfp") + + if (arm_use_neon == "") { + if (current_os == "linux" && target_cpu != v8_target_cpu) { + # Don't use neon on V8 simulator builds as a default. + arm_use_neon = false + } else { + arm_use_neon = true + } + } + + if (arm_version == 6) { + if (arm_arch == "") { + arm_arch = "armv6" + } + if (arm_tune != "") { + arm_tune = "" + } + if (arm_float_abi == "") { + arm_float_abi = "softfp" + } + if (arm_fpu == "") { + arm_fpu = "vfp" + } + arm_use_thumb = false + arm_use_neon = false + } else if (arm_version == 7) { + if (arm_arch == "") { + arm_arch = "armv7-a" + } + if (arm_tune == "") { + arm_tune = "generic-armv7-a" + } + + if (arm_float_abi == "") { + if (current_os == "linux" && target_cpu != v8_target_cpu) { + # Default to the same as Android for V8 simulator builds. + arm_float_abi = "softfp" + } else { + arm_float_abi = "hard" + } + } + + if (arm_fpu == "") { + if (arm_use_neon) { + arm_fpu = "neon" + } else { + arm_fpu = "vfpv3-d16" + } + } + } else if (arm_version == 8) { + if (arm_arch == "") { + arm_arch = "armv8-a" + } + if (arm_tune == "") { + arm_tune = "generic-armv8-a" + } + + if (arm_float_abi == "") { + arm_float_abi = "hard" + } + + if (arm_fpu == "") { + if (arm_use_neon) { + arm_fpu = "neon" + } else { + arm_fpu = "vfpv3-d16" + } + } + } +} else if (target_cpu == "arm64" || v8_target_cpu == "arm64") { + # arm64 supports only "hard". + arm_float_abi = "hard" + arm_use_neon = true + declare_args() { + # Enables the new Armv8 branch protection features. Valid strings are: + # - "standard": Enables both Pointer Authentication Code (featured in + # Armv8.3) and Branch Target Identification (Armv8.5). + # - "none": No branch protection. + arm_control_flow_integrity = "none" + } + assert(arm_control_flow_integrity == "none" || + arm_control_flow_integrity == "standard", + "Invalid branch protection option") +} diff --git a/third_party/libwebrtc/build/config/buildflags_paint_preview.gni b/third_party/libwebrtc/build/config/buildflags_paint_preview.gni new file mode 100644 index 0000000000..7129e76634 --- /dev/null +++ b/third_party/libwebrtc/build/config/buildflags_paint_preview.gni @@ -0,0 +1,16 @@ +# Copyright 2019 The Chromium 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/chromecast_build.gni") +import("//build/config/features.gni") + +declare_args() { + # Enable basic paint preview support. Does not work on iOS or Fuchsia. Should + # not be included with Chromecast. Not ready for shipping builds yet so + # include in unofficial builds. + # Used by //components/paint_preview and //third_party/harfbuzz-ng. + # TODO(bug/webrtc:11223) Move back this file in //components/paint_preview/ + # once WebRTC doesn't roll harfbuzz-ng anymore, for consistency sake. + enable_paint_preview = !is_chromecast && !is_ios && !is_fuchsia +} diff --git a/third_party/libwebrtc/build/config/c++/BUILD.gn b/third_party/libwebrtc/build/config/c++/BUILD.gn new file mode 100644 index 0000000000..e7ed69ec81 --- /dev/null +++ b/third_party/libwebrtc/build/config/c++/BUILD.gn @@ -0,0 +1,145 @@ +import("//build/config/c++/c++.gni") +import("//build/config/chrome_build.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/dcheck_always_on.gni") +import("//buildtools/deps_revisions.gni") + +assert(use_custom_libcxx, "should only be used if use_custom_libcxx is set") + +declare_args() { + # lldb pretty printing only works when libc++ is built in the __1 (or __ndk1) + # namespaces. For pretty printing to work out-of-the-box on Mac (where lldb + # is primarily used), this flag is set to false to build with the __1 + # namespace (to maintain ABI compatibility, this implies building without + # _LIBCPP_ABI_UNSTABLE). This is not necessary on non-component builds + # because we leave the ABI version set to __1 in that case because libc++ + # symbols are not exported. + # TODO(thomasanderson): Set this to true by default once rL352899 is available + # in MacOS's lldb. + libcxx_abi_unstable = !(is_apple && is_debug && is_component_build) +} + +# TODO(xiaohuic): https://crbug/917533 Crashes on internal ChromeOS build. +# Do unconditionally once the underlying problem is fixed. +if (is_chromeos_ash && is_chrome_branded) { + libcxx_abi_unstable = false +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is specific to libc++. Please see that target for advice on what should +# go in :runtime_library vs. :compiler. +config("runtime_library") { + cflags = [] + cflags_cc = [] + defines = [] + include_dirs = [] + ldflags = [] + libs = [] + + if (libcxx_abi_unstable) { + defines += [ "_LIBCPP_ABI_UNSTABLE" ] + } + + if (libcxx_is_shared) { + # When libcxx_is_shared is true, symbols from libc++.so are exported for + # all DSOs to use. If the system libc++ gets loaded (indirectly through + # a system library), then it will conflict with our libc++.so. Add a + # custom ABI version if we're building with _LIBCPP_ABI_UNSTABLE to avoid + # conflicts. + # + # Windows doesn't need to set _LIBCPP_ABI_VERSION since there's no system + # C++ library we could conflict with. + if (libcxx_abi_unstable && !is_win) { + defines += [ "_LIBCPP_ABI_VERSION=Cr" ] + } + } else { + # Don't leak any symbols on a static build. + defines += [ "_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS" ] + if (!export_libcxxabi_from_executables && !is_win) { + defines += [ "_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS" ] + } + } + + defines += [ "_LIBCPP_ENABLE_NODISCARD" ] + + include_dirs += [ "//buildtools/third_party/libc++" ] + + # Work around a symbol conflict between GRPC and the Fuchsia SDK. + # TODO(crbug.com/1166970): Remove this when resolved. + if (is_fuchsia) { + defines += [ "_LIBCPP_NO_NATIVE_SEMAPHORES" ] + } + + # The Windows component build fails to link with libc++'s debug mode. See + # https://crbug.com/923166#c33, https://crbug.com/923166#c44, and + # https://llvm.org/PR41018. + if (!(is_win && is_component_build)) { + # libc++ has two levels of debug mode. Setting _LIBCPP_DEBUG to zero + # enables most assertions. Setting it to one additionally enables iterator + # debugging. See https://libcxx.llvm.org/docs/DesignDocs/DebugMode.html + if (enable_iterator_debugging) { + defines += [ "_LIBCPP_DEBUG=1" ] + } else if (is_debug || dcheck_always_on) { + defines += [ "_LIBCPP_DEBUG=0" ] + } + } + + if (is_win) { + # Intentionally not using libc++abi on Windows because libc++abi only + # implements the Itanium C++ ABI, and not the Microsoft ABI which we use on + # Windows (and we need to use in order to interoperate correctly with COM + # among other things). + assert(!export_libcxxabi_from_executables, + "Don't use libcxxabi on Windows.") + + cflags_cc += + [ "-I" + rebase_path("$libcxx_prefix/include", root_build_dir) ] + + # Prevent libc++ from embedding linker flags to try to automatically link + # against its runtime library. This is unnecessary with our build system, + # and can also result in build failures if libc++'s name for a library + # does not match ours. + defines += [ "_LIBCPP_NO_AUTO_LINK" ] + + # Add a debug visualizer for Microsoft's debuggers so that they can display + # libc++ types well. + if (libcxx_natvis_include) { + # chrome.natvis listed as an input in //buildtools/third_party/libc++ to + # guarantee relinking on changes. + ldflags += [ "/NATVIS:" + rebase_path("libc++.natvis", root_build_dir) ] + } + } else { + cflags_cc += [ + "-nostdinc++", + "-isystem" + rebase_path("$libcxx_prefix/include", root_build_dir), + "-isystem" + rebase_path("$libcxxabi_prefix/include", root_build_dir), + ] + cflags_objcc = cflags_cc + + defines += [ "CR_LIBCXX_REVISION=$libcxx_revision" ] + + # Make sure we don't link against the system libstdc++ or libc++. + if (is_clang) { + ldflags += [ "-nostdlib++" ] + } else { + # Gcc has a built-in abs() definition with default visibility. + # If it was not disabled, it would conflict with libc++'s abs() + # with hidden visibility. + cflags += [ "-fno-builtin-abs" ] + + ldflags += [ "-nodefaultlibs" ] + + # Unfortunately, there's no way to disable linking against just libc++ + # (gcc doesn't have -notstdlib++: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83931); -nodefaultlibs + # removes all of the default libraries, so add back the ones that we need. + libs += [ + "c", + "gcc_s", + "m", + "rt", + ] + } + } +} diff --git a/third_party/libwebrtc/build/config/c++/c++.gni b/third_party/libwebrtc/build/config/c++/c++.gni new file mode 100644 index 0000000000..10a780c19b --- /dev/null +++ b/third_party/libwebrtc/build/config/c++/c++.gni @@ -0,0 +1,81 @@ +# Copyright 2017 The Chromium 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/chromeos/ui_mode.gni") +import("//build/config/sanitizers/sanitizers.gni") + +declare_args() { + # Use in-tree libc++ (buildtools/third_party/libc++ and + # buildtools/third_party/libc++abi) instead of the system C++ library for C++ + # standard library support. + # Don't check in changes that set this to false for more platforms; doing so + # is not supported. + use_custom_libcxx = + is_fuchsia || is_android || is_mac || is_linux || is_chromeos_lacros || + (is_ios && !use_xcode_clang) || (is_win && is_clang) || + (is_chromeos && default_toolchain != "//build/toolchain/cros:target") + + # Use libc++ instead of stdlibc++ when using the host_cpu toolchain, even if + # use_custom_libcxx is false. This is useful for cross-compiles where a custom + # toolchain for the target_cpu has been set as the default toolchain, but + # use_custom_libcxx should still be true when building for the host. The + # expected usage is to set use_custom_libcxx=false and + # use_custom_libcxx_for_host=true in the passed in buildargs. + use_custom_libcxx_for_host = false + + # Builds libcxx Natvis into the symbols for type visualization. + # Set to false to workaround http://crbug.com/966676 and + # http://crbug.com/966687. + libcxx_natvis_include = true + + # When set, enables libc++ debug mode with iterator debugging. + # + # Iterator debugging is generally useful for catching bugs. But it can + # introduce extra locking to check the state of an iterator against the state + # of the current object. For iterator- and thread-heavy code, this can + # significantly slow execution - two orders of magnitude slowdown has been + # seen (crbug.com/903553) and iterator debugging also slows builds by making + # generation of snapshot_blob.bin take ~40-60 s longer. Therefore this + # defaults to off. + enable_iterator_debugging = false +} + +use_custom_libcxx = + use_custom_libcxx || (use_custom_libcxx_for_host && !is_a_target_toolchain) +use_custom_libcxx = use_custom_libcxx && !is_nacl + +declare_args() { + # WARNING: Setting this to a non-default value is highly discouraged. + # If true, libc++ will be built as a shared library; otherwise libc++ will be + # linked statically. Setting this to something other than the default is + # unsupported and can be broken by libc++ rolls. Note that if this is set to + # true, you must also set libcxx_abi_unstable=false, which is bad for + # performance and memory use. + libcxx_is_shared = use_custom_libcxx && is_component_build +} + +# libc++abi needs to be exported from executables to be picked up by shared +# libraries on certain instrumented builds. +export_libcxxabi_from_executables = + use_custom_libcxx && !is_apple && !is_win && !is_component_build && + (is_asan || is_ubsan_vptr) + +# On Android, many shared libraries get loaded from the context of a JRE. In +# this case, there's no "main executable" to export libc++abi from. We could +# export libc++abi from each "toplevel" shared library instead, but that would +# require adding an explicit dependency for each one, and might introduce +# subtle, hard-to-fix problems down the line if the dependency is missing. +# +# export_libcxxabi_from_executables was added to avoid having an RPATH set in +# static sanitizer builds just for executables to find libc++. But on Android, +# the Bionic dynamic loader doesn't even look at RPATH; instead, LD_LIBRARY_PATH +# is set for tests. Because of this, we make libc++ a shared library on android +# since it should get loaded properly. +if (is_android && export_libcxxabi_from_executables) { + export_libcxxabi_from_executables = false + libcxx_is_shared = true +} + +libcxx_prefix = "//buildtools/third_party/libc++/trunk" +libcxxabi_prefix = "//buildtools/third_party/libc++abi/trunk" diff --git a/third_party/libwebrtc/build/config/c++/libc++.natvis b/third_party/libwebrtc/build/config/c++/libc++.natvis new file mode 100644 index 0000000000..9a49a2935a --- /dev/null +++ b/third_party/libwebrtc/build/config/c++/libc++.natvis @@ -0,0 +1,435 @@ + + + + + + {__value_} + + __value_ + + + + {*($T1*)this} + + *($T1*)this + + + + + {{ size={$T2} }} + + + $T2 + __elems_ + + + + + + + + + + + {*(char**)this} + {(char*)this} + *(char**)this + (char*)this + + ((size_t*)this)[1] + *(((char*)this) + 3*sizeof(size_t) - 1) + + ((size_t*)this)[2] & (~((size_t)0) >> 1) + + 22 + + ((size_t*)this)[1] + + *(((char*)this) + 3*sizeof(size_t) - 1) + + *(char**)this + (char*)this + + + + + + + {*(wchar_t**)this} + {(wchar_t*)this} + *(wchar_t**)this + (wchar_t*)this + + ((size_t*)this)[1] + *(((char*)this) + 3*sizeof(size_t) - 1) + + ((size_t*)this)[2] & (~((size_t)0) >> 1) + + 10 + + ((size_t*)this)[1] + + *(((char*)this) + 3*sizeof(size_t) - 1) + + *(wchar_t**)this + (wchar_t*)this + + + + + + + + {{ size={size()} }} + + + size() + + *(*(__map_.__begin_ + ($i + __start_) / block_size()) + + ($i + __start_) % block_size()) + + + + + + + + empty + non-empty + + + head() + __next_ + __value_ + + + + + + + {{ size={__size_} }} + + + __size_ + __begin_ + + + + + + + {{ size={size()} }} + + + size() + __end_.__next_ + __next_ + + ((std::__1::list<$T1,$T2>::__node_pointer)this) + ->__value_ + + + + + + + + {{ size={size()} }} + + size() + + size() + + ((__node_pointer)&__tree_.__pair1_)->__left_ + + + ((std::__1::map<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__left_ + + + ((std::__1::map<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__right_ + + + ((std::__1::map<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__value_.__cc + + + + + + + + {{ size={size()} }} + + size() + + size() + + ((__node_pointer)&__tree_.__pair1_)->__left_ + + + ((std::__1::multimap<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__left_ + + + ((std::__1::multimap<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__right_ + + + ((std::__1::multimap<$T1,$T2,$T3,$T4>::__node_pointer)this) + ->__value_.__cc + + + + + + + + {{ size={size()} }} + + size() + + size() + + ((__base::__node_pointer)&__tree_.__pair1_)->__left_ + + + ((std::__1::multiset<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__left_ + + + ((std::__1::multiset<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__right_ + + + ((std::__1::multiset<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__value_ + + + + + + + {c} + + c + comp + + + + + + {{ size={size()} }} + + size() + + size() + + ((__base::__node_pointer)&__tree_.__pair1_)->__left_ + + + ((std::__1::set<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__left_ + + + ((std::__1::set<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__right_ + + + ((std::__1::set<$T1,$T2,$T3>::__base::__node_pointer)this) + ->__value_ + + + + + + + + {c} + + c + + + + + {__value_} + + + + () + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}, {(std::__1::__tuple_leaf<2,$T3,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + (std::__1::__tuple_leaf<2,$T3,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}, {(std::__1::__tuple_leaf<2,$T3,0>)__base_}, {(std::__1::__tuple_leaf<3,$T4,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + (std::__1::__tuple_leaf<2,$T3,0>)__base_ + (std::__1::__tuple_leaf<3,$T4,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}, {(std::__1::__tuple_leaf<2,$T3,0>)__base_}, {(std::__1::__tuple_leaf<3,$T4,0>)__base_}, {(std::__1::__tuple_leaf<4,$T5,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + (std::__1::__tuple_leaf<2,$T3,0>)__base_ + (std::__1::__tuple_leaf<3,$T4,0>)__base_ + (std::__1::__tuple_leaf<4,$T5,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}, {(std::__1::__tuple_leaf<2,$T3,0>)__base_}, {(std::__1::__tuple_leaf<3,$T4,0>)__base_}, {(std::__1::__tuple_leaf<4,$T5,0>)__base_}, {(std::__1::__tuple_leaf<5,$T6,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + (std::__1::__tuple_leaf<2,$T3,0>)__base_ + (std::__1::__tuple_leaf<3,$T4,0>)__base_ + (std::__1::__tuple_leaf<4,$T5,0>)__base_ + (std::__1::__tuple_leaf<5,$T6,0>)__base_ + + + + + ({(std::__1::__tuple_leaf<0,$T1,0>)__base_}, {(std::__1::__tuple_leaf<1,$T2,0>)__base_}, {(std::__1::__tuple_leaf<2,$T3,0>)__base_}, {(std::__1::__tuple_leaf<3,$T4,0>)__base_}, {(std::__1::__tuple_leaf<4,$T5,0>)__base_}, {(std::__1::__tuple_leaf<5,$T6,0>)__base_}, {(std::__1::__tuple_leaf<6,$T7,0>)__base_}) + + (std::__1::__tuple_leaf<0,$T1,0>)__base_ + (std::__1::__tuple_leaf<1,$T2,0>)__base_ + (std::__1::__tuple_leaf<2,$T3,0>)__base_ + (std::__1::__tuple_leaf<3,$T4,0>)__base_ + (std::__1::__tuple_leaf<4,$T5,0>)__base_ + (std::__1::__tuple_leaf<5,$T6,0>)__base_ + (std::__1::__tuple_leaf<6,$T7,0>)__base_ + + + + + + value() + empty + + unique_ptr {value()} + + value() + + + + + + + + + + {{ size={size()} }} + + bucket_count() + + bucket_count() != 0 ? (float)size() / bucket_count() : 0.f + *(float*)&__table_.__p3_ + + + + size() + + (*(__table::__node_pointer*)&node)->__value_ + node = node->__next_ + + + + + + + {__cc} + + __cc + + + + + + {{ size={size()} }} + + + size() + __begin_ + + + + diff --git a/third_party/libwebrtc/build/config/chrome_build.gni b/third_party/libwebrtc/build/config/chrome_build.gni new file mode 100644 index 0000000000..5c51d7f2cc --- /dev/null +++ b/third_party/libwebrtc/build/config/chrome_build.gni @@ -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. + +declare_args() { + # 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 +} + +declare_args() { + # Refers to the subdirectory for branding in various places including + # chrome/app/theme. + if (is_chrome_branded) { + branding_path_component = "google_chrome" + } else { + branding_path_component = "chromium" + } +} + +declare_args() { + # The path to the BRANDING file in chrome/app/theme. + branding_file_path = "//chrome/app/theme/$branding_path_component/BRANDING" +} diff --git a/third_party/libwebrtc/build/config/chromecast/BUILD.gn b/third_party/libwebrtc/build/config/chromecast/BUILD.gn new file mode 100644 index 0000000000..d52d19cad8 --- /dev/null +++ b/third_party/libwebrtc/build/config/chromecast/BUILD.gn @@ -0,0 +1,95 @@ +# 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/chromecast_build.gni") + +assert(is_chromecast) + +config("static_config") { + if (!is_clang) { + ldflags = [ + # Don't allow visible symbols from libraries that contain + # assembly code with symbols that aren't hidden properly. + # http://b/26390825 + "-Wl,--exclude-libs=libffmpeg.a", + ] + + if (!is_android) { + ldflags += [ + # We want to statically link libstdc++/libgcc on Linux. + # (On Android, libstdc++ and libgcc aren't used.) + "-static-libstdc++", + "-static-libgcc", + ] + } + } +} + +config("ldconfig") { + visibility = [ ":*" ] + configs = [] + + # Chromecast executables depend on several shared libraries in + # /oem_cast_shlib, $ORIGIN, and $ORIGIN/lib. Add these rpaths to each binary. + # This is explicitly disabled in Chrome for security reasons (see comments in + # //build/config/gcc/BUILD.gn), but necessary on Chromecast so that OEM's may + # override the default libraries shipped in the Cast receiver package. + if (target_rpath == "") { + ldflags = [ + "-Wl,-rpath=/oem_cast_shlib", + "-Wl,-rpath=\$ORIGIN/lib", + "-Wl,-rpath=\$ORIGIN", + ] + } else { + ldflags = [ "-Wl,-rpath=${target_rpath}" ] + } + + if (chromecast_branding != "public") { + # Some internal x64 builds need additional rpath and dynamic-linker config. + configs += [ "//chromecast/internal/build/config/ldconfig" ] + } + + # Binaries which don't live in the same directory as Chrome component + # libraries may still depend on them. Explicitly add the component library + # directory to the rpath for the component build. + if (is_component_build) { + ldflags += [ "-Wl,-rpath=/system/chrome" ] + } +} + +config("executable_config") { + configs = [ ":ldconfig" ] + + if (!is_clang && target_cpu == "arm") { + ldflags = [ + # Export stdlibc++ and libgcc symbols to force shlibs to refer to these + # symbols from the executable. + "-Wl,--export-dynamic", + + "-lm", # stdlibc++ requires math.h + + # In case we redefined stdlibc++ symbols (e.g. tc_malloc) + "-Wl,--allow-multiple-definition", + + "-Wl,--whole-archive", + "-l:libstdc++.a", + "-l:libgcc.a", + "-Wl,--no-whole-archive", + ] + + # Despite including libstdc++/libgcc archives, we still need to specify + # static linking for them in order to prevent the executable from having a + # dynamic dependency on them. + configs += [ ":static_config" ] + } +} + +# Shared libaries should not have RPATH or RUNPATH set. This allows the +# shared libs to inherit RPATH from the parent executable that is loading +# the shared library. (See internal b/37514052 for more details.) +config("shared_library_config") { + if (target_cpu == "arm") { + configs = [ ":static_config" ] + } +} diff --git a/third_party/libwebrtc/build/config/chromecast_build.gni b/third_party/libwebrtc/build/config/chromecast_build.gni new file mode 100644 index 0000000000..b309e29d96 --- /dev/null +++ b/third_party/libwebrtc/build/config/chromecast_build.gni @@ -0,0 +1,121 @@ +# 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. + +# The args declared in this file should be referenced by components outside of +# //chromecast. Args needed only in //chromecast should be declared in +# //chromecast/chromecast.gni. +declare_args() { + # Set this true for a Chromecast build. Chromecast builds are supported on + # Linux and Android. + is_chromecast = false + + # If true, IS_CAST_DEBUG_BUILD() will evaluate to 1 in version.h. Otherwise, + # it will evaluate to 0. Overriding this when is_debug=false is useful for + # doing engineering builds. + cast_is_debug = is_debug + + # chromecast_branding is used to include or exclude Google-branded components. + # Set it to "public" for a Chromium build. + chromecast_branding = "public" + + # Set this true for an audio-only Chromecast build. + is_cast_audio_only = false + + # If non empty, rpath of executables is set to this. + # If empty, default value is used. + target_rpath = "" + + # Set true to enable modular_updater. + enable_modular_updater = false +} + +# Note(slan): This arg depends on the value of is_chromecast, and thus must be +# declared in a separate block. These blocks can be combined when/if +# crbug.com/542846 is resolved. +declare_args() { + # True if Chromecast build is targeted for linux desktop. This type of build + # is useful for testing and development, but currently supports only a subset + # of Cast functionality. Though this defaults to true for x86 Linux devices, + # this should be overriden manually for an embedded x86 build. + # TODO(slan): Remove instances of this when x86 is a fully supported platform. + is_cast_desktop_build = is_chromecast && target_os == "linux" && + (target_cpu == "x86" || target_cpu == "x64") + + # Mozilla added: Until upstream's build repo is updated to + # 56e01bffd1de3e5b27e0da87f8019d44aa55a562, we'll default this to false + # to keep the build happy. + is_castos = false +} + +declare_args() { + # True to enable the cast renderer. It is enabled by default for linux and + # android audio only builds. + enable_cast_renderer = is_chromecast && (is_linux || is_chromeos || + (is_cast_audio_only && is_android)) +} + +declare_args() { + # True to enable the cast audio renderer. + enable_cast_audio_renderer = false +} + +# Configures media options for cast. See media/media_options.gni +cast_mojo_media_services = [] +cast_mojo_media_host = "" + +if (enable_cast_audio_renderer) { + if (is_android) { + cast_mojo_media_services = [ + "cdm", + "audio_decoder", + ] + } + + if (!is_cast_audio_only) { + cast_mojo_media_services += [ "video_decoder" ] + } + + if (is_android && is_cast_audio_only) { + cast_mojo_media_host = "browser" + } else { + cast_mojo_media_host = "gpu" + } +} else if (enable_cast_renderer) { + # In this path, mojo media services are hosted in two processes: + # 1. "renderer" and "cdm" run in browser process. This is hard coded in the + # code. + # 2. "video_decoder" runs in the process specified by "cast_mojo_media_host". + cast_mojo_media_services = [ + "cdm", + "renderer", + ] + if (!is_cast_audio_only) { + cast_mojo_media_services += [ "video_decoder" ] + } + + cast_mojo_media_host = "gpu" +} else if (is_android) { + # On Android, all the enabled mojo media services run in the process specified + # by "cast_mojo_media_host". + cast_mojo_media_services = [ + "cdm", + "audio_decoder", + ] + if (!is_cast_audio_only) { + # These are Cast/Android devices with Video capabilities (and GPU) + cast_mojo_media_services += [ "video_decoder" ] + cast_mojo_media_host = "gpu" + } else { + # These are Cast/Android devices with only Audio capabilities (no GPU) + cast_mojo_media_host = "browser" + } +} + +# Assert that Chromecast is being built for a supported platform. +assert(is_linux || is_chromeos || is_android || is_fuchsia || !is_chromecast, + "Chromecast builds are not supported on $target_os") + +# Assert that is_cast_audio_only and is_cast_desktop_build are both false on a +# non-Chromecast build. +assert(is_chromecast || (!is_cast_audio_only && !is_cast_desktop_build)) diff --git a/third_party/libwebrtc/build/config/chromeos/BUILD.gn b/third_party/libwebrtc/build/config/chromeos/BUILD.gn new file mode 100644 index 0000000000..f3dfe70d42 --- /dev/null +++ b/third_party/libwebrtc/build/config/chromeos/BUILD.gn @@ -0,0 +1,34 @@ +# Copyright 2019 The Chromium 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/chromeos/ui_mode.gni") + +assert(is_chromeos_ash) + +declare_args() { + # The location to a file used to dump symbols ordered by Call-Chain Clustering (C3) + # https://research.fb.com/wp-content/uploads/2017/01/cgo2017-hfsort-final1.pdf? + # to a file, used for generating orderfiles in Chrome OS + dump_call_chain_clustering_order = "" +} + +declare_args() { + # Whether or not we're using new pass manager to build and link Chrome + use_new_pass_manager = dump_call_chain_clustering_order != "" +} + +config("print_orderfile") { + if (dump_call_chain_clustering_order != "") { + _output_orderfile = + rebase_path(dump_call_chain_clustering_order, root_build_dir) + ldflags = [ "-Wl,--print-symbol-order=$_output_orderfile" ] + } +} + +config("compiler") { + if (use_new_pass_manager) { + cflags = [ "-fexperimental-new-pass-manager" ] + ldflags = [ "-fexperimental-new-pass-manager" ] + } +} diff --git a/third_party/libwebrtc/build/config/chromeos/args.gni b/third_party/libwebrtc/build/config/chromeos/args.gni new file mode 100644 index 0000000000..99c7d601a3 --- /dev/null +++ b/third_party/libwebrtc/build/config/chromeos/args.gni @@ -0,0 +1,30 @@ +# Copyright 2019 The Chromium 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() { + # This is used only by Simple Chrome to bind its value to test-runner scripts + # generated at build-time. + cros_board = "" + + # Similar to cros_board above, this used only by test-runner scripts in + # Simple Chrome. + cros_sdk_version = "" +} + +# Ensure that if one is set, the other is as well. +assert(cros_board == "" == (cros_sdk_version == "")) + +declare_args() { + # Determines if we're building for a Chrome OS device (or VM) and not just + # linux-chromeos. NOTE: Most test targets in Chrome expect to run under + # linux-chromeos, so some have compile-time asserts that intentionally fail + # when this build flag is set. Build and run the tests for linux-chromeos + # instead. + # https://chromium.googlesource.com/chromium/src/+/main/docs/chromeos_build_instructions.md + # https://chromium.googlesource.com/chromiumos/docs/+/main/simple_chrome_workflow.md + is_chromeos_device = false + + # Determines if we run the test in skylab, aka the CrOS labs. + is_skylab = false +} diff --git a/third_party/libwebrtc/build/config/chromeos/rules.gni b/third_party/libwebrtc/build/config/chromeos/rules.gni new file mode 100644 index 0000000000..09f9701a7a --- /dev/null +++ b/third_party/libwebrtc/build/config/chromeos/rules.gni @@ -0,0 +1,510 @@ +# Copyright 2018 The Chromium 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") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/gclient_args.gni") +import("//build/config/python.gni") +import("//build/util/generate_wrapper.gni") + +assert((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device) + +# Determine the real paths for various items in the SDK, which may be used +# in the 'generate_runner_script' template below. We do so outside the template +# to confine exec_script to a single invocation. +if (cros_sdk_version != "") { + # Ideally these should be maps, however, gn doesn't support map, so using a + # list of list to simulate a map: + # [key1, [value1, value2, ...]], [key2, [value1, value2, ...]], where + # the keys are boards and values are symlinks or symlink targets, and the + # mapping shouldn't be used for anything else. + # + # A sample usage is: + # foreach(m, _symlink_targets_map) { + # if(m[0] == target_key) { + # target_value = m[1] + # } + # } + # + _symlink_map = [] + _symlink_targets_map = [] + + if (is_chromeos_ash) { + _potential_test_boards = [ cros_board ] + } else { + _potential_test_boards = [] + if (cros_boards != "") { + _potential_test_boards += string_split(cros_boards, ":") + } + if (cros_boards_with_qemu_images != "") { + _potential_test_boards += string_split(cros_boards_with_qemu_images, ":") + } + } + + foreach(b, _potential_test_boards) { + _cache_path_prefix = + "//build/cros_cache/chrome-sdk/symlinks/${b}+${cros_sdk_version}" + + _cros_is_vm = false + foreach(b1, string_split(cros_boards_with_qemu_images, ":")) { + if (b == b1) { + _cros_is_vm = true + } + } + + _symlinks = [] + _symlinks = [ + # Tast harness & test data. + rebase_path("${_cache_path_prefix}+chromeos-base/tast-cmd"), + rebase_path("${_cache_path_prefix}+chromeos-base/tast-remote-tests-cros"), + + # Binutils (and other toolchain tools) used to deploy Chrome to the device. + rebase_path( + "${_cache_path_prefix}+environment_chromeos-base_chromeos-chrome.tar.xz"), + rebase_path("${_cache_path_prefix}+target_toolchain"), + ] + if (_cros_is_vm) { + # VM-related tools. + _symlinks += [ + rebase_path("${_cache_path_prefix}+sys-firmware/seabios"), + rebase_path("${_cache_path_prefix}+chromiumos_test_image.tar.xz"), + rebase_path("${_cache_path_prefix}+app-emulation/qemu"), + ] + } + _symlink_map += [ [ + b, + _symlinks, + ] ] + } + + _all_symlinks = [] + foreach(m, _symlink_map) { + _all_symlinks += m[1] + } + _all_symlink_targets = + exec_script("//build/get_symlink_targets.py", _all_symlinks, "list lines") + _index = 0 + foreach(m, _symlink_map) { + _symlink_targets = [] + foreach(_, m[1]) { + _symlink_targets += [ _all_symlink_targets[_index] ] + _index += 1 + } + + _symlink_targets_map += [ [ + m[0], + _symlink_targets, + ] ] + } +} + +# Creates a script at $generated_script that can be used to launch a cros VM +# and optionally run a test within it. +# Args: +# test_exe: Name of test binary located in the out dir. This will get copied +# to the VM and executed there. +# tast_attr_expr: Tast expression to pass to local_test_runner on the VM. +# tast_tests: List of Tast tests to run on the VM. Note that when this is +# specified, the target name used to invoke this template will be +# designated as the "name" of this test and will primarly used for test +# results tracking and displaying (eg: flakiness dashboard). +# generated_script: Path to place the generated script. +# deploy_chrome: If true, deploys a locally built chrome located in the root +# build dir to the VM or DUT after launching it. +# deploy_lacros: If true, deploys a locally built Lacros located in the root +# build dir to the VM or DUT after launching it. +# runtime_deps_file: Path to file listing runtime deps for the test. If set, +# all files listed will be copied to the VM before testing. +# skip_generating_board_args: By default, this template generates an '--board' +# arg with corresponding '--flash' or '--use-vm' args for device and vm +# respectively. This argument instructs the template to skip generating +# them, and it's designed for use cases where one builds for one board +# (e.g. amd64-generic), but tests on a different board (e.g. eve). +# tast_vars: A list of "key=value" runtime variable pairs to pass to invoke +# the Tast tests. For more details, please see: +# https://chromium.googlesource.com/chromiumos/platform/tast/+/HEAD/docs/writing_tests.md#Runtime-variables +template("generate_runner_script") { + forward_variables_from(invoker, + [ + "deploy_chrome", + "deploy_lacros", + "generated_script", + "runtime_deps_file", + "skip_generating_board_args", + "tast_attr_expr", + "tast_tests", + "tast_vars", + "testonly", + "test_exe", + ]) + + if (!defined(skip_generating_board_args)) { + skip_generating_board_args = false + } + + if (skip_generating_board_args) { + # cros_board is not needed, so setting it to empty to avoid being used + # accidentally below. + cros_board = "" + not_needed([ cros_board ]) + } + + if (!defined(deploy_chrome)) { + deploy_chrome = false + } + if (!defined(deploy_lacros)) { + deploy_lacros = false + } + assert(!(deploy_chrome && deploy_lacros), + "deploy_chrome and deploy_lacros are exclusive.") + + is_tast = defined(tast_attr_expr) || defined(tast_tests) + assert(!(is_tast && defined(test_exe)), + "Tast tests are invoked from binaries shipped with the VM image. " + + "There should be no locally built binary needed.") + assert(is_tast || !defined(tast_vars), + "tast_vars is only support for Tast tests") + + # If we're in the cros chrome-sdk (and not the raw ebuild), the test will + # need some additional runtime data located in the SDK cache. + _sdk_data = [] + if (cros_sdk_version != "") { + assert(defined(generated_script), + "Must specify where to place generated test launcher script via " + + "'generated_script'") + + foreach(b, _potential_test_boards) { + _cros_is_vm = false + foreach(b1, string_split(cros_boards_with_qemu_images, ":")) { + if (b == b1) { + _cros_is_vm = true + } + } + + # Determine the real paths for various items in the SDK, which may be used + # in the 'generate_runner_script' template below. + if (is_tast || _cros_is_vm || deploy_chrome) { + _symlink_targets = [] + foreach(m, _symlink_targets_map) { + if (b == m[0]) { + _symlink_targets = [] + _symlink_targets = m[1] + } + } + + if (is_tast) { + # Add tast sdk items. + _sdk_data += [ + _symlink_targets[0], + _symlink_targets[1], + ] + } + if (deploy_chrome) { + # To deploy chrome to the VM, it needs to be stripped down to fit into + # the VM. This is done by using binutils in the toolchain. So add the + # toolchain to the data. + _sdk_data += [ + _symlink_targets[2], + _symlink_targets[3], + ] + } + if (_cros_is_vm) { + # Add vm sdk items. + _sdk_data += [ + _symlink_targets[4], + _symlink_targets[5], + _symlink_targets[6], + ] + } + } + } + } + + generate_wrapper(target_name) { + executable = "//build/chromeos/test_runner.py" + use_vpython3 = true + wrapper_script = generated_script + executable_args = [] + + if (defined(runtime_deps_file)) { + write_runtime_deps = runtime_deps_file + } + + # Build executable_args for the three different test types: GTest, Tast, + # and host-side commands (eg telemetry). + if (defined(test_exe)) { + executable_args += [ + "gtest", + "--test-exe", + test_exe, + ] + if (defined(runtime_deps_file)) { + executable_args += [ + "--runtime-deps-path", + rebase_path(runtime_deps_file, root_build_dir), + ] + } + } else if (is_tast) { + # When --tast-tests is specified, test_runner.py will call + # local_test_runner on the VM to run the set of tests. + executable_args += [ + "tast", + "--suite-name", + target_name, + ] + if (defined(tast_attr_expr)) { + executable_args += [ + "--attr-expr", + tast_attr_expr, + ] + } else { + foreach(test, tast_tests) { + executable_args += [ + "-t", + test, + ] + } + } + if (defined(tast_vars)) { + foreach(var, tast_vars) { + executable_args += [ + "--tast-var", + var, + ] + } + } + } else { + executable_args += [ "host-cmd" ] + } + executable_args += [ + "--cros-cache", + "build/cros_cache/", + "--path-to-outdir", + rebase_path(root_out_dir, "//"), + "-v", + ] + + if (!skip_generating_board_args) { + executable_args += [ + "--board", + cros_board, + ] + + _cros_is_vm = false + foreach(b, string_split(cros_boards_with_qemu_images, ":")) { + if (cros_board == b) { + _cros_is_vm = true + } + } + if (_cros_is_vm) { + executable_args += [ "--use-vm" ] + } else { + executable_args += [ "--flash" ] + } + } + + # If we have public Chromium builds, use public Chromium OS images when + # flashing the test device. + if (!is_chrome_branded) { + executable_args += [ "--public-image" ] + } + + if (deploy_lacros) { + executable_args += [ "--deploy-lacros" ] + } + + if (deploy_chrome && !defined(test_exe) && !is_tast) { + executable_args += [ "--deploy-chrome" ] + } + + # executable_args should be finished, now build the data and deps lists. + deps = [ "//testing/buildbot/filters:chromeos_filters" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + data = [ + "//.vpython", + "//.vpython3", + + # We use android test-runner's results libs to construct gtest output + # json. + "//build/android/pylib/__init__.py", + "//build/android/pylib/base/", + "//build/android/pylib/results/", + "//build/chromeos/", + "//build/util/", + + # Needed for various SDK components used below. + "//build/cros_cache/chrome-sdk/misc/", + "//build/cros_cache/chrome-sdk/symlinks/", + + # The LKGM file controls what version of the VM image to download. Add it + # as data here so that changes to it will trigger analyze. + "//chromeos/CHROMEOS_LKGM", + "//third_party/chromite/", + ] + + data += _sdk_data + + if (defined(invoker.data)) { + data += invoker.data + } + + data_deps = [ "//testing:test_scripts_shared" ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + } +} + +template("tast_test") { + forward_variables_from(invoker, "*") + + # Default the expression to match any chrome-related test. + if (!defined(tast_attr_expr) && !defined(tast_tests)) { + # The following expression filters out all non-critical tests. See the link + # below for more details: + # https://chromium.googlesource.com/chromiumos/platform/tast/+/main/docs/test_attributes.md + tast_attr_expr = "\"group:mainline\" && \"dep:chrome\"" + + if (defined(enable_tast_informational_tests) && + enable_tast_informational_tests) { + tast_attr_expr += " && informational" + } else { + tast_attr_expr += " && !informational" + } + if (!is_chrome_branded) { + tast_attr_expr += " && !\"dep:chrome_internal\"" + } + } else { + assert(defined(tast_attr_expr) != defined(tast_tests), + "Specify one of tast_tests or tast_attr_expr.") + } + + # Append any disabled tests to the expression. + if (defined(tast_disabled_tests)) { + assert(defined(tast_attr_expr), + "tast_attr_expr must be used when specifying tast_disabled_tests.") + foreach(test, tast_disabled_tests) { + tast_attr_expr += " && !\"name:${test}\"" + } + } + if (defined(tast_attr_expr)) { + tast_attr_expr = "( " + tast_attr_expr + " )" + } + generate_runner_script(target_name) { + testonly = true + generated_script = "$root_build_dir/bin/run_${target_name}" + runtime_deps_file = "$root_out_dir/${target_name}.runtime_deps" + deploy_chrome = true + data_deps = [ + "//:chromiumos_preflight", # Builds the browser. + "//chromeos:cros_chrome_deploy", # Adds additional browser run-time deps. + + # Tools used to symbolize Chrome crash dumps. + # TODO(crbug.com/1156772): Remove these if/when all tests pick them up by + # default. + "//third_party/breakpad:dump_syms", + "//third_party/breakpad:minidump_dump", + "//third_party/breakpad:minidump_stackwalk", + ] + + data = [ "//components/crash/content/tools/generate_breakpad_symbols.py" ] + } +} + +template("lacros_tast_tests") { + forward_variables_from(invoker, + [ + "tast_attr_expr", + "tast_disabled_tests", + "tast_tests", + ]) + assert(defined(tast_attr_expr) != defined(tast_tests), + "Specify one of tast_tests or tast_attr_expr.") + + # Append any disabled tests to the expression. + if (defined(tast_disabled_tests)) { + assert(defined(tast_attr_expr), + "tast_attr_expr must be used when specifying tast_disabled_tests.") + foreach(test, tast_disabled_tests) { + tast_attr_expr += " && !\"name:${test}\"" + } + } + if (defined(tast_attr_expr)) { + tast_attr_expr = "( " + tast_attr_expr + " )" + } + + generate_runner_script(target_name) { + testonly = true + deploy_lacros = true + generated_script = "$root_build_dir/bin/run_${target_name}" + runtime_deps_file = "$root_out_dir/${target_name}.runtime_deps" + + # At build time, Lacros tests don't know whether they'll run on VM or HW, + # and instead, these flags are specified at runtime when invoking the + # generated runner script. + skip_generating_board_args = true + + # By default, tast tests download a lacros-chrome from a gcs location and + # use it for testing. To support running lacros tast tests from Chromium CI, + # a Var is added to support pointing the tast tests to use a specified + # pre-deployed lacros-chrome. The location is decided by: + # https://source.chromium.org/chromium/chromium/src/+/main:third_party/chromite/scripts/deploy_chrome.py;l=80;drc=86f1234a4be8e9574442e076cdc835897f7bea61 + tast_vars = [ "lacrosDeployedBinary=/usr/local/lacros-chrome" ] + + data_deps = [ + "//chrome", # Builds the browser. + + # Tools used to symbolize Chrome crash dumps. + # TODO(crbug.com/1156772): Remove these if/when all tests pick them up by + # default. + "//third_party/breakpad:dump_syms", + "//third_party/breakpad:minidump_dump", + "//third_party/breakpad:minidump_stackwalk", + ] + + data = [ + "//components/crash/content/tools/generate_breakpad_symbols.py", + + # A script needed to launch Lacros in Lacros Tast tests. + "//build/lacros/mojo_connection_lacros_launcher.py", + ] + } +} + +template("generate_skylab_runner_script") { + forward_variables_from(invoker, + [ + "generated_script", + "test_exe", + ]) + + action(target_name) { + script = "//build/chromeos/gen_skylab_runner.py" + outputs = [ generated_script ] + args = [ + "--test-exe", + test_exe, + "--output", + rebase_path(generated_script, root_build_dir), + ] + + deps = [ "//testing/buildbot/filters:chromeos_filters" ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + + data = [ generated_script ] + if (defined(invoker.data)) { + data += invoker.data + } + + data_deps = [ "//testing:test_scripts_shared" ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + } +} diff --git a/third_party/libwebrtc/build/config/chromeos/ui_mode.gni b/third_party/libwebrtc/build/config/chromeos/ui_mode.gni new file mode 100644 index 0000000000..aff59afa66 --- /dev/null +++ b/third_party/libwebrtc/build/config/chromeos/ui_mode.gni @@ -0,0 +1,40 @@ +# Copyright 2020 The Chromium 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() { + # Deprecated, use is_lacros. + # + # This controls UI configuration for Chrome. + # If this flag is set, we assume Chrome runs on Chrome OS devices, using + # Wayland (instead of X11). + # + # TODO(crbug.com/1052397): + # Define chromeos_product instead, which takes either "browser" or "ash". + # Re-define the following variables as: + # is_lacros = chromeos_product == "browser" + # is_ash = chromeos_product == "ash" + chromeos_is_browser_only = false + + # Setting this to true when building LaCrOS-chrome will cause it to + # *also* build ash-chrome in a subdirectory using an alternate toolchain. + # Don't set this unless you're sure you want it, because it'll double + # your build time. + also_build_ash_chrome = false + + # Setting this to true when building ash-chrome will cause it to + # *also* build lacros-chrome in a subdirectory using an alternate toolchain. + also_build_lacros_chrome = false +} + +# is_chromeos_{ash,lacros} is used to specify that it is specific to either +# ash or lacros chrome for chromeos. For platform specific logic that +# applies to both, use is_chromeos instead. +# .gn files are evaluated multiple times for each toolchain. +# is_chromeos_{ash,lacros} should be set true only for builds with target +# toolchains. +is_chromeos_ash = is_chromeos && !chromeos_is_browser_only +is_chromeos_lacros = is_chromeos && chromeos_is_browser_only + +# also_build_ash_chrome and also_build_lacros_chrome cannot be both true. +assert(!(also_build_ash_chrome && also_build_lacros_chrome)) diff --git a/third_party/libwebrtc/build/config/clang/BUILD.gn b/third_party/libwebrtc/build/config/clang/BUILD.gn new file mode 100644 index 0000000000..9512c66246 --- /dev/null +++ b/third_party/libwebrtc/build/config/clang/BUILD.gn @@ -0,0 +1,59 @@ +# 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 = [] + + # The plugin is built directly into clang, so there's no need to load it + # dynamically. + cflags += [ + "-Xclang", + "-add-plugin", + "-Xclang", + "find-bad-constructs", + "-Xclang", + "-plugin-arg-find-bad-constructs", + "-Xclang", + "raw-ptr-template-as-trivial-member", + ] + + if (is_linux || is_chromeos || is_android || is_fuchsia) { + cflags += [ + "-Xclang", + "-plugin-arg-find-bad-constructs", + "-Xclang", + "check-ipc", + ] + } + } +} + +# 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", + + "-Wtautological-overlap-compare", + ] +} + +group("llvm-symbolizer_data") { + if (is_win) { + data = [ "$clang_base_path/bin/llvm-symbolizer.exe" ] + } else { + data = [ "$clang_base_path/bin/llvm-symbolizer" ] + + if (!is_apple) { + # llvm-symbolizer uses libstdc++ from the clang package. + data += [ "$clang_base_path/lib/libstdc++.so.6" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/clang/clang.gni b/third_party/libwebrtc/build/config/clang/clang.gni new file mode 100644 index 0000000000..588864586d --- /dev/null +++ b/third_party/libwebrtc/build/config/clang/clang.gni @@ -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. + +import("//build/toolchain/toolchain.gni") + +default_clang_base_path = "//third_party/llvm-build/Release+Asserts" + +declare_args() { + # Indicates if the build should use the Chrome-specific plugins for enforcing + # coding guidelines, etc. Only used when compiling with Chrome's Clang, not + # Chrome OS's. + clang_use_chrome_plugins = + is_clang && !is_nacl && !use_xcode_clang && + default_toolchain != "//build/toolchain/cros:target" + + clang_base_path = default_clang_base_path +} diff --git a/third_party/libwebrtc/build/config/compiler/BUILD.gn b/third_party/libwebrtc/build/config/compiler/BUILD.gn new file mode 100644 index 0000000000..a1293bc431 --- /dev/null +++ b/third_party/libwebrtc/build/config/compiler/BUILD.gn @@ -0,0 +1,2648 @@ +# 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/buildflag_header.gni") +import("//build/config/android/config.gni") +import("//build/config/c++/c++.gni") +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/dcheck_always_on.gni") +#import("//build/config/gclient_args.gni") +import("//build/config/host_byteorder.gni") +import("//build/config/rust.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/ui.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +if (target_cpu == "arm" || target_cpu == "arm64") { + import("//build/config/arm.gni") +} +if (target_cpu == "mipsel" || target_cpu == "mips64el" || + target_cpu == "mips" || target_cpu == "mips64") { + import("//build/config/mips.gni") +} +if (target_cpu == "x64") { + import("//build/config/x64.gni") +} +if (is_mac) { + import("//build/config/apple/symbols.gni") +} +if (is_ios) { + import("//build/config/ios/ios_sdk.gni") +} +if (is_nacl) { + # To keep NaCl variables out of builds that don't include NaCl, all + # variables defined in nacl/config.gni referenced here should be protected by + # is_nacl conditions. + import("//build/config/nacl/config.gni") +} + +lld_path = "" +if (!is_clang) { + declare_args() { + # This allows overriding the location of lld. + lld_path = rebase_path("$clang_base_path/bin", root_build_dir) + } +} else { + # clang looks for lld next to it, no need for -B. + lld_path = "" +} + +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 + + # 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 + + # When we are going to use gold we need to find it. + # This is initialized below, after use_gold might have been overridden. + gold_path = "" + + # Enable fatal linker warnings. Building Chromium with certain versions + # of binutils can cause linker warning. + fatal_linker_warnings = true + + # Build with C++ RTTI enabled. Chromium builds without RTTI by default, + # but some sanitizers are known to require it, like CFI diagnostics + # and UBsan variants. + use_rtti = use_cfi_diag || is_ubsan_vptr || is_ubsan_security + + # AFDO (Automatic Feedback Directed Optimizer) is a form of profile-guided + # optimization that GCC supports. It used by ChromeOS in their official + # builds. To use it, set auto_profile_path to the path to a file containing + # the needed gcov profiling data. + auto_profile_path = "" + + # Allow projects that wish to stay on C++11 to override Chromium's default. + use_cxx11 = false + + # Path to an AFDO profile to use while building with clang, if any. Empty + # implies none. + clang_sample_profile_path = "" + + # Some configurations have default sample profiles. If this is true and + # clang_sample_profile_path is empty, we'll fall back to the default. + # + # We currently only have default profiles for Chromium in-tree, so we disable + # this by default for all downstream projects, since these profiles are likely + # nonsensical for said projects. + clang_use_default_sample_profile = + chrome_pgo_phase == 0 && build_with_chromium && is_official_build && + (is_android || chromeos_is_browser_only || is_chromecast) + + # This configuration is used to select a default profile in Chrome OS based on + # the microarchitectures we are using. This is only used if + # clang_use_default_sample_profile is true and clang_sample_profile_path is + # empty. + chromeos_afdo_platform = "atom" + + # Emit debug information for profiling wile building with clang. + clang_emit_debug_info_for_profiling = false + + # Turn this on to have the compiler output extra timing information. + compiler_timing = false + + # Turn this on to use ghash feature of lld for faster debug link on Windows. + # http://blog.llvm.org/2018/01/improving-link-time-on-windows-with.html + use_ghash = true + + # Whether to enable ThinLTO optimizations. Turning ThinLTO optimizations on + # can substantially increase link time and binary size, but they generally + # also make binaries a fair bit faster. + # + # TODO(gbiv): We disable optimizations by default on most platforms because + # the space overhead is too great. We should use some mixture of profiles and + # optimization settings to better tune the size increase. + thin_lto_enable_optimizations = + (is_chromeos_ash || is_android || is_win || is_linux) && is_official_build + + # Initialize all local variables with a pattern. This flag will fill + # uninitialized floating-point types (and 32-bit pointers) with 0xFF and the + # rest with 0xAA. This makes behavior of uninitialized memory bugs consistent, + # recognizable in the debugger, and crashes on memory accesses through + # uninitialized pointers. + # + # TODO(crbug.com/1131993): Enabling this when 'is_android' is true breaks + # content_shell_test_apk on both ARM and x86. + init_stack_vars = !is_android + + # This argument is to control whether enabling text section splitting in the + # final binary. When enabled, the separated text sections with prefix + # '.text.hot', '.text.unlikely', '.text.startup' and '.text.exit' will not be + # merged to '.text' section. This allows us to identify the hot code section + # ('.text.hot') in the binary which may be mlocked or mapped to huge page to + # reduce TLB misses which gives performance improvement on cpu usage. + # The gold linker by default has text section splitting enabled. + use_text_section_splitting = false + + # Token limits may not be accurate for build configs not covered by the CQ, + # so only enable them by default for mainstream build configs. + enable_wmax_tokens = + !is_official_build && !(is_component_build && !is_debug) && + ((is_mac && target_cpu == "x64" && !use_system_xcode) || + (is_linux && !is_chromeos && target_cpu == "x64") || + (is_win && target_cpu == "x86") || (is_win && target_cpu == "x64") || + (is_android && target_cpu == "arm") || + (is_android && target_cpu == "arm64")) + + # Turn off the --call-graph-profile-sort flag for lld by default. Enable + # selectively for targets where it's beneficial. + enable_call_graph_profile_sort = chrome_pgo_phase == 2 + + # Enable DWARF v5. + use_dwarf5 = false + + # Override this to put full paths to PDBs in Windows PE files. This helps + # windbg and Windows Performance Analyzer with finding the PDBs in some local- + # build scenarios. This is never needed for bots or official builds. Because + # this puts the output directory in the DLLs/EXEs it breaks build determinism. + # Bugs have been reported to the windbg/WPA teams and this workaround will be + # removed when they are fixed. + use_full_pdb_paths = false + + # Enable -H, which prints the include tree during compilation. + # For use by tools/clang/scripts/analyze_includes.py + show_includes = false +} + +declare_args() { + # C++11 may not be an option if Android test infrastructure is used. + use_cxx11_on_android = use_cxx11 +} + +declare_args() { + # Set to true to use icf, Identical Code Folding. + # + # icf=all is broken in older golds, see + # https://sourceware.org/bugzilla/show_bug.cgi?id=17704 + # chromeos binutils has been patched with the fix, so always use icf there. + # The bug only affects x86 and x64, so we can still use ICF when targeting + # other architectures. + # + # lld doesn't have the bug. + use_icf = + (is_posix || is_fuchsia) && !is_debug && !using_sanitizer && + !use_clang_coverage && !(is_android && use_order_profiling) && + (use_lld || (use_gold && (is_chromeos_ash || !(target_cpu == "x86" || + target_cpu == "x64")))) +} + +if (is_android || (is_chromeos_ash && is_chromeos_device)) { + # Set the path to use orderfile for linking Chrome + # Note that this is for using only one orderfile for linking + # the Chrome binary/library. + declare_args() { + chrome_orderfile_path = "" + + if (defined(default_chrome_orderfile)) { + # Allow downstream tools to set orderfile path with + # another variable. + chrome_orderfile_path = default_chrome_orderfile + } else if (is_chromeos_ash && is_chromeos_device) { + chrome_orderfile_path = "//chromeos/profiles/chromeos.orderfile.txt" + } + } +} + +assert(!(llvm_force_head_revision && use_goma), + "can't use goma with trunk clang") +assert(!(llvm_force_head_revision && use_rbe), "can't use rbe with trunk clang") + +# 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, + ] +} + +# Compiler instrumentation can introduce dependencies in DSOs to symbols in +# the executable they are loaded into, so they are unresolved at link-time. +config("no_unresolved_symbols") { + if (!using_sanitizer && + (is_linux || is_chromeos || is_android || is_fuchsia)) { + ldflags = [ + "-Wl,-z,defs", + "-Wl,--as-needed", + ] + } +} + +# 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") { + asmflags = [] + cflags = [] + cflags_c = [] + cflags_cc = [] + cflags_objc = [] + cflags_objcc = [] + ldflags = [] + defines = [] + configs = [] + + # System-specific flags. If your compiler flags apply to one of the + # categories here, add it to the associated file to keep this shared config + # smaller. + if (is_win) { + configs += [ "//build/config/win:compiler" ] + } else if (is_android) { + configs += [ "//build/config/android:compiler" ] + } else if (is_linux || is_chromeos) { + configs += [ "//build/config/linux:compiler" ] + if (is_chromeos_ash) { + configs += [ "//build/config/chromeos:compiler" ] + } + } else if (is_nacl) { + configs += [ "//build/config/nacl:compiler" ] + } else if (is_mac) { + configs += [ "//build/config/mac:compiler" ] + } else if (is_ios) { + configs += [ "//build/config/ios:compiler" ] + } else if (is_fuchsia) { + configs += [ "//build/config/fuchsia:compiler" ] + } else if (current_os == "aix") { + configs += [ "//build/config/aix:compiler" ] + } + + configs += [ + # See the definitions below. + ":clang_revision", + ":compiler_cpu_abi", + ":compiler_codegen", + ":compiler_deterministic", + ] + + # Here we enable -fno-delete-null-pointer-checks, which makes various nullptr + # operations (e.g. dereferencing) into defined behavior. This avoids deletion + # of some security-critical code: see https://crbug.com/1139129. + # Nacl does not support the flag. And, we still want UBSAN to catch undefined + # behavior related to nullptrs, so do not add this flag if UBSAN is enabled. + # GCC seems to have some bugs compiling constexpr code when this is defined, + # so only enable it if using_clang. See: https://gcc.gnu.org/PR97913 + # TODO(mpdenton): remove is_clang once GCC bug is fixed. + if (!is_nacl && !is_ubsan && is_clang) { + cflags += [ "-fno-delete-null-pointer-checks" ] + } + + # Don't emit the GCC version ident directives, they just end up in the + # .comment section or debug info taking up binary size, and makes comparing + # .o files built with different compiler versions harder. + if (!is_win || is_clang) { + cflags += [ "-fno-ident" ] + } + + # In general, Windows is totally different, but all the other builds share + # some common compiler and linker configuration. + if (!is_win) { + # Common POSIX compiler flags setup. + # -------------------------------- + cflags += [ "-fno-strict-aliasing" ] # See http://crbug.com/32204 + + # Stack protection. + if (is_mac) { + # The strong variant of the stack protector significantly increases + # binary size, so only enable it in debug mode. + if (is_debug) { + cflags += [ "-fstack-protector-strong" ] + } else { + cflags += [ "-fstack-protector" ] + } + } else if ((is_posix && !is_chromeos_ash && !is_nacl) || is_fuchsia) { + # TODO(phajdan.jr): Use -fstack-protector-strong when our gcc supports it. + # See also https://crbug.com/533294 + cflags += [ "--param=ssp-buffer-size=4" ] + + # The x86 toolchain currently has problems with stack-protector. + if (is_android && target_cpu == "x86") { + cflags += [ "-fno-stack-protector" ] + } else if (current_os != "aix") { + # Not available on aix. + cflags += [ "-fstack-protector" ] + } + } + + if (use_lld) { + ldflags += [ "-fuse-ld=lld" ] + if (lld_path != "") { + ldflags += [ "-B$lld_path" ] + } + } + + # Linker warnings. + if (fatal_linker_warnings && !is_apple && current_os != "aix") { + ldflags += [ "-Wl,--fatal-warnings" ] + } + if (fatal_linker_warnings && is_apple) { + ldflags += [ "-Wl,-fatal_warnings" ] + } + } + + 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. + cflags_cc += [ + "-Wno-undefined-bool-conversion", + "-Wno-tautological-undefined-compare", + ] + } + + # Non-Mac Posix and Fuchsia compiler flags setup. + # ----------------------------------- + if ((is_posix && !is_apple) || is_fuchsia) { + if (enable_profiling) { + if (!is_debug) { + cflags += [ "-g" ] + + if (enable_full_stack_frames_for_profiling) { + cflags += [ + "-fno-inline", + "-fno-optimize-sibling-calls", + ] + } + } + } + + # Explicitly pass --build-id to ld. Compilers used to always pass this + # implicitly but don't any more (in particular clang when built without + # ENABLE_LINKER_BUILD_ID=ON). + if (is_official_build) { + # The sha1 build id has lower risk of collision but is more expensive to + # compute, so only use it in the official build to avoid slowing down + # links. + ldflags += [ "-Wl,--build-id=sha1" ] + } else if (current_os != "aix") { + ldflags += [ "-Wl,--build-id" ] + } + + if (!is_android) { + defines += [ + # _FILE_OFFSET_BITS=64 should not be set on Android in order to maintain + # the behavior of the Android NDK from earlier versions. + # See https://android-developers.googleblog.com/2017/09/introducing-android-native-development.html + "_FILE_OFFSET_BITS=64", + "_LARGEFILE_SOURCE", + "_LARGEFILE64_SOURCE", + ] + } + + if (!is_nacl) { + if (exclude_unwind_tables) { + cflags += [ + "-fno-unwind-tables", + "-fno-asynchronous-unwind-tables", + ] + defines += [ "NO_UNWIND_TABLES" ] + } else { + cflags += [ "-funwind-tables" ] + } + } + } + + # Linux/Android/Fuchsia common flags setup. + # --------------------------------- + if (is_linux || is_chromeos || is_android || is_fuchsia) { + asmflags += [ "-fPIC" ] + cflags += [ "-fPIC" ] + ldflags += [ "-fPIC" ] + + if (!is_clang) { + # Use pipes for communicating between sub-processes. Faster. + # (This flag doesn't do anything with Clang.) + cflags += [ "-pipe" ] + } + + ldflags += [ + "-Wl,-z,noexecstack", + "-Wl,-z,relro", + ] + + if (!is_component_build) { + ldflags += [ "-Wl,-z,now" ] + } + } + + # Linux-specific compiler flags setup. + # ------------------------------------ + if ((is_posix || is_fuchsia) && !is_apple && use_lld) { + if (target_cpu == "arm64") { + # Reduce the page size from 65536 in order to reduce binary size slightly + # by shrinking the alignment gap between segments. This also causes all + # segments to be mapped adjacently, which breakpad relies on. + ldflags += [ "-Wl,-z,max-page-size=4096" ] + } + } else if (use_gold) { + ldflags += [ "-fuse-ld=gold" ] + if (!is_android) { + # On Android, this isn't needed. gcc in the NDK knows to look next to + # it with -fuse-ld=gold, and clang gets a --gcc-toolchain flag passed + # above. + if (gold_path != "") { + ldflags += [ "-B$gold_path" ] + } + + ldflags += [ + # 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. + "-Wl,--threads", + "-Wl,--thread-count=4", + ] + } + + # 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 (use_icf && !is_apple) { + ldflags += [ "-Wl,--icf=all" ] + } + + if (is_linux || is_chromeos) { + cflags += [ "-pthread" ] + # Do not use the -pthread ldflag here since it becomes a no-op + # when using -nodefaultlibs, which would cause an unused argument + # error. "-lpthread" is added in //build/config:default_libs. + } + + # Clang-specific compiler flags setup. + # ------------------------------------ + if (is_clang) { + cflags += [ "-fcolor-diagnostics" ] + + # Enable -fmerge-all-constants. This used to be the default in clang + # for over a decade. It makes clang non-conforming, but is fairly safe + # in practice and saves some binary size. We might want to consider + # disabling this (https://bugs.llvm.org/show_bug.cgi?id=18538#c13), + # but for now it looks like our build might rely on it + # (https://crbug.com/829795). + cflags += [ "-fmerge-all-constants" ] + } + + if (use_lld) { + # TODO(thakis): Make the driver pass --color-diagnostics to the linker + # if -fcolor-diagnostics is passed to it, and pass -fcolor-diagnostics + # in ldflags instead. + if (is_win) { + # On Windows, we call the linker directly, instead of calling it through + # the driver. + ldflags += [ "--color-diagnostics" ] + } else { + ldflags += [ "-Wl,--color-diagnostics" ] + } + } + + # Enable text section splitting only on linux when using lld for now. Other + # platforms can be added later if needed. + if ((is_linux || is_chromeos) && use_lld && use_text_section_splitting) { + ldflags += [ "-Wl,-z,keep-text-section-prefix" ] + } + + if (is_clang && !is_nacl && !use_xcode_clang) { + cflags += [ "-fcrash-diagnostics-dir=" + clang_diagnostic_dir ] + + # TODO(hans): Remove this once Clang generates better optimized debug info + # by default. https://crbug.com/765793 + cflags += [ + "-mllvm", + "-instcombine-lower-dbg-declare=0", + ] + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + if (is_win) { + ldflags += [ "-mllvm:-instcombine-lower-dbg-declare=0" ] + } else { + ldflags += [ "-Wl,-mllvm,-instcombine-lower-dbg-declare=0" ] + } + } + + # TODO(crbug.com/1235145): Investigate why/if this should be needed. + if (is_win) { + cflags += [ "/clang:-ffp-contract=off" ] + } else { + cflags += [ "-ffp-contract=off" ] + } + } + + # C11/C++11 compiler flags setup. + # --------------------------- + if (is_linux || is_chromeos || is_android || (is_nacl && is_clang) || + current_os == "aix") { + if (target_os == "android") { + cxx11_override = use_cxx11_on_android + } else { + cxx11_override = use_cxx11 + } + + if (is_clang) { + standard_prefix = "c" + + # Since we build with -std=c* and not -std=gnu*, _GNU_SOURCE will not be + # defined by the compiler. However, lots of code relies on the + # non-standard features that _GNU_SOURCE enables, so define it manually. + defines += [ "_GNU_SOURCE" ] + + if (is_nacl) { + # Undefine __STRICT_ANSI__ to get non-standard features which would + # otherwise not be enabled by NaCl's sysroots. + cflags += [ "-U__STRICT_ANSI__" ] + } + } else { + # Gcc does not support ##__VA_ARGS__ when in standards-conforming mode, + # but we use this feature in several places in Chromium. + # TODO(thomasanderson): Replace usages of ##__VA_ARGS__ with the + # standard-compliant __VA_OPT__ added by C++20, and switch the gcc build + # to -std=c*. + standard_prefix = "gnu" + } + + cflags_c += [ "-std=${standard_prefix}11" ] + if (cxx11_override) { + # Override Chromium's default for projects that wish to stay on C++11. + cflags_cc += [ "-std=${standard_prefix}++11" ] + } else { + cflags_cc += [ "-std=${standard_prefix}++14" ] + } + } else if (!is_win && !is_nacl) { + if (target_os == "android") { + cxx11_override = use_cxx11_on_android + } else { + cxx11_override = use_cxx11 + } + + # TODO(mcgrathr) - the NaCl GCC toolchain doesn't support either gnu11/gnu++11 + # or c11/c++11; we technically don't need this toolchain any more, but there + # are still a few buildbots using it, so until those are turned off + # we need the !is_nacl clause and the (is_nacl && is_clang) clause, above. + cflags_c += [ "-std=c11" ] + if (cxx11_override) { + cflags_cc += [ "-std=c++11" ] + } else { + cflags_cc += [ "-std=c++14" ] + } + } + + # C++17 removes trigraph support, so preemptively disable trigraphs. This is + # especially useful given the collision with ecmascript's logical assignment + # operators: https://github.com/tc39/proposal-logical-assignment + if (is_clang) { + # clang-cl disables trigraphs by default + if (!is_win) { + # The gnu variants of C++11 and C++14 already disable trigraph support, + # but when building with clang, we use -std=c++11 / -std=c++14, which + # enables trigraph support: override that here. + cflags_cc += [ "-fno-trigraphs" ] + } + + # Don't warn that trigraphs are ignored, since trigraphs are disabled + # anyway. + cflags_cc += [ "-Wno-trigraphs" ] + } + + if (is_mac) { + # The system libc++ on Mac doesn't have aligned allocation in C++17. + defines += [ "_LIBCPP_HAS_NO_ALIGNED_ALLOCATION" ] + cflags_cc += [ "-stdlib=libc++" ] + ldflags += [ "-stdlib=libc++" ] + } + + # Add flags for link-time optimization. These flags enable + # optimizations/transformations that require whole-program visibility at link + # time, so they need to be applied to all translation units, and we may end up + # with miscompiles if only part of the program is compiled with LTO flags. For + # that reason, we cannot allow targets to enable or disable these flags, for + # example by disabling the optimize configuration. + # TODO(pcc): Make this conditional on is_official_build rather than on gn + # flags for specific features. + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + assert(use_lld, "LTO is only supported with lld") + + cflags += [ + "-flto=thin", + "-fsplit-lto-unit", + ] + + # Limit the size of the ThinLTO cache to the lesser of 10% of + # available disk space, 40GB and 100000 files. + cache_policy = "cache_size=10%:cache_size_bytes=40g:cache_size_files=100000" + + # TODO(gbiv): We ideally shouldn't need to specify this; ThinLTO + # should be able to better manage binary size increases on its own. + import_instr_limit = 5 + + if (is_win) { + ldflags += [ + "/opt:lldltojobs=all", + "-mllvm:-import-instr-limit=$import_instr_limit", + "/lldltocache:" + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir), + "/lldltocachepolicy:$cache_policy", + ] + } else { + ldflags += [ "-flto=thin" ] + + # Enabling ThinLTO on Chrome OS too, in an effort to reduce the memory + # usage in crbug.com/1038040. Note this will increase build time in + # Chrome OS. + + # In ThinLTO builds, we run at most one link process at a time, + # and let it use all cores. + # TODO(thakis): Check if '=0' (that is, number of cores, instead + # of "all" which means number of hardware threads) is faster. + ldflags += [ "-Wl,--thinlto-jobs=all" ] + if (is_mac) { + ldflags += + [ "-Wl,-cache_path_lto," + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir) ] + } else { + ldflags += + [ "-Wl,--thinlto-cache-dir=" + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir) ] + } + + ldflags += [ "-Wl,--thinlto-cache-policy=$cache_policy" ] + + if (is_chromeos_ash) { + # Not much performance difference was noted between the default (100) + # and these. ARM was originally set lower than x86 to keep the size + # bloat of ThinLTO to <10%, but that's potentially no longer true. + # FIXME(inglorion): maybe tune these? + if (target_cpu == "arm" || target_cpu == "arm64") { + import_instr_limit = 20 + } else { + import_instr_limit = 30 + } + } + + ldflags += [ "-Wl,-mllvm,-import-instr-limit=$import_instr_limit" ] + } + + # TODO(https://crbug.com/1211155): investigate why this isn't effective on arm32. + if (!is_android || target_cpu == "arm64") { + cflags += [ "-fwhole-program-vtables" ] + if (!is_win) { + ldflags += [ "-fwhole-program-vtables" ] + } + } + + # This flag causes LTO to create an .ARM.attributes section with the correct + # architecture. This is necessary because LLD will refuse to link a program + # unless the architecture revision in .ARM.attributes is sufficiently new. + # TODO(pcc): The contents of .ARM.attributes should be based on the + # -march flag passed at compile time (see llvm.org/pr36291). + if (target_cpu == "arm") { + ldflags += [ "-march=$arm_arch" ] + } + } + + if (compiler_timing) { + if (is_clang && !is_nacl) { + cflags += [ "-ftime-trace" ] + } else if (is_win) { + cflags += [ + # "Documented" here: + # http://aras-p.info/blog/2017/10/23/Best-unknown-MSVC-flag-d2cgsummary/ + "/d2cgsummary", + ] + } + } + + # Pass flag to LLD so Android builds can allow debuggerd to properly symbolize + # stack crashes (http://crbug.com/919499). + if (use_lld && is_android) { + ldflags += [ "-Wl,--no-rosegment" ] + } + + # LLD does call-graph-sorted binary layout by default when profile data is + # present. On Android this increases binary size due to more thinks for long + # jumps. Turn it off by default and enable selectively for targets where it's + # beneficial. + if (use_lld && !enable_call_graph_profile_sort) { + if (is_win) { + ldflags += [ "/call-graph-profile-sort:no" ] + } else if (!is_apple) { + # TODO(thakis): Once LLD's Mach-O port basically works, implement call + # graph profile sorting for it, add an opt-out flag, and pass it here. + ldflags += [ "-Wl,--no-call-graph-profile-sort" ] + } + } + + if (is_clang && !is_nacl && show_includes) { + if (is_win) { + # TODO(crbug.com/1223741): Goma mixes the -H and /showIncludes output. + assert(!use_goma, "show_includes on Windows is not reliable with goma") + cflags += [ + "/clang:-H", + "/clang:-fshow-skipped-includes", + ] + } else { + cflags += [ + "-H", + "-fshow-skipped-includes", + ] + } + } + + # This flag enforces that member pointer base types are complete. It helps + # prevent us from running into problems in the Microsoft C++ ABI (see + # https://crbug.com/847724). + # TODO(crbug/1052397): Remove is_chromeos_lacros once lacros-chrome switches + # to target_os="chromeos". + if (is_clang && !is_nacl && target_os != "chromeos" && !use_xcode_clang && + !is_chromeos_lacros && (is_win || use_custom_libcxx)) { + cflags += [ "-fcomplete-member-pointers" ] + } + + # Pass the same C/C++ flags to the objective C/C++ compiler. + cflags_objc += cflags_c + cflags_objcc += cflags_cc + + # Assign any flags set for the C compiler to asmflags so that they are sent + # to the assembler. The Windows assembler takes different types of flags + # so only do so for posix platforms. + if (is_posix || is_fuchsia) { + asmflags += cflags + asmflags += cflags_c + } + + # Rust compiler flags setup. + # --------------------------- + rustflags = [ + # Overflow checks are optional in Rust, but even if switched + # off they do not cause undefined behavior (the overflowing + # behavior is defined). Because containers are bounds-checked + # in safe Rust, they also can't provoke buffer overflows. + # As such these checks may be less important in Rust than C++. + # But in (simplistic) testing they have negligible performance + # overhead, and this helps to provide consistent behavior + # between different configurations, so we'll keep them on until + # we discover a reason to turn them off. + "-Coverflow-checks=on", + + # To make Rust .d files compatible with ninja + "-Z", + "dep-info-omit-d-target", + ] + if (rust_abi_target != "") { + rustflags += [ "--target=$rust_abi_target" ] + } + if (use_lto_in_rustc_linking) { + rustflags += [ "-Clinker-plugin-lto" ] + } + if (!use_thin_lto) { + # Optimization - don't include bitcode if it won't be used. + rustflags += [ "-Cembed-bitcode=no" ] + } +} + +# The BUILDCONFIG file sets this config on targets by default, which means when +# building with ThinLTO, no optimization is performed in the link step. +config("thinlto_optimize_default") { + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + lto_opt_level = 0 + + if (is_win) { + ldflags = [ "/opt:lldlto=" + lto_opt_level ] + } else { + ldflags = [ "-Wl,--lto-O" + lto_opt_level ] + } + } +} + +# Use this to enable optimization in the ThinLTO link step for select targets +# when thin_lto_enable_optimizations is set by doing: +# +# configs -= [ "//build/config/compiler:thinlto_optimize_default" ] +# configs += [ "//build/config/compiler:thinlto_optimize_max" ] +# +# Since it makes linking significantly slower and more resource intensive, only +# use it on important targets such as the main browser executable or dll. +config("thinlto_optimize_max") { + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + if (thin_lto_enable_optimizations) { + lto_opt_level = 2 + } else { + lto_opt_level = 0 + } + + if (is_win) { + ldflags = [ "/opt:lldlto=" + lto_opt_level ] + } else { + ldflags = [ "-Wl,--lto-O" + lto_opt_level ] + } + } +} + +# This provides the basic options to select the target CPU and ABI. +# It is factored out of "compiler" so that special cases can use this +# without using everything that "compiler" brings in. Options that +# tweak code generation for a particular CPU do not belong here! +# See "compiler_codegen", below. +config("compiler_cpu_abi") { + cflags = [] + ldflags = [] + defines = [] + + if ((is_posix && !is_apple) || is_fuchsia) { + # CPU architecture. We may or may not be doing a cross compile now, so for + # simplicity we always explicitly set the architecture. + if (target_cpu == "x64") { + cflags += [ + "-m64", + "-march=$x64_arch", + "-msse3", + ] + ldflags += [ "-m64" ] + } else if (target_cpu == "x86") { + cflags += [ "-m32" ] + ldflags += [ "-m32" ] + if (!is_nacl) { + cflags += [ + "-mfpmath=sse", + "-msse2", + ] + } + } else if (target_cpu == "arm") { + if (is_clang && !is_android && !is_nacl) { + cflags += [ "--target=arm-linux-gnueabihf" ] + ldflags += [ "--target=arm-linux-gnueabihf" ] + } + if (!is_nacl) { + cflags += [ + "-march=$arm_arch", + "-mfloat-abi=$arm_float_abi", + ] + } + if (arm_tune != "") { + cflags += [ "-mtune=$arm_tune" ] + } + } else if (target_cpu == "arm64") { + if (is_clang && !is_android && !is_nacl && !is_fuchsia) { + cflags += [ "--target=aarch64-linux-gnu" ] + ldflags += [ "--target=aarch64-linux-gnu" ] + } + } else if (target_cpu == "mipsel" && !is_nacl) { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + if (is_android) { + cflags += [ "--target=mipsel-linux-android" ] + ldflags += [ "--target=mipsel-linux-android" ] + } else { + cflags += [ "--target=mipsel-linux-gnu" ] + ldflags += [ "--target=mipsel-linux-gnu" ] + } + } else { + cflags += [ "-EL" ] + ldflags += [ "-EL" ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ "-mno-odd-spreg" ] + ldflags += [ "-mips32r6" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32r6", + ] + } else { + cflags += [ + "-mips32r6", + "-Wa,-mips32r6", + ] + if (is_android) { + ldflags += [ "-Wl,-melf32ltsmip" ] + } + } + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + ldflags += [ "-mips32r2" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32r2", + ] + } else { + cflags += [ + "-mips32r2", + "-Wa,-mips32r2", + ] + if (mips_float_abi == "hard" && mips_fpu_mode != "") { + cflags += [ "-m$mips_fpu_mode" ] + } + } + } else if (mips_arch_variant == "r1") { + ldflags += [ "-mips32" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32", + ] + } else { + cflags += [ + "-mips32", + "-Wa,-mips32", + ] + } + } else if (mips_arch_variant == "loongson3") { + defines += [ "_MIPS_ARCH_LOONGSON" ] + cflags += [ + "-march=loongson3a", + "-mno-branch-likely", + "-Wa,-march=loongson3a", + ] + } + + if (mips_dsp_rev == 1) { + cflags += [ "-mdsp" ] + } else if (mips_dsp_rev == 2) { + cflags += [ "-mdspr2" ] + } + + cflags += [ "-m${mips_float_abi}-float" ] + } else if (target_cpu == "mips" && !is_nacl) { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + cflags += [ "--target=mips-linux-gnu" ] + ldflags += [ "--target=mips-linux-gnu" ] + } else { + cflags += [ "-EB" ] + ldflags += [ "-EB" ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ + "-mips32r6", + "-Wa,-mips32r6", + ] + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } 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 (target_cpu == "mips64el") { + cflags += [ "-D__SANE_USERSPACE_TYPES__" ] + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + if (is_android) { + cflags += [ "--target=mips64el-linux-android" ] + ldflags += [ "--target=mips64el-linux-android" ] + } else { + cflags += [ "--target=mips64el-linux-gnuabi64" ] + ldflags += [ "--target=mips64el-linux-gnuabi64" ] + } + } else { + cflags += [ + "-EL", + "-mabi=64", + ] + ldflags += [ + "-EL", + "-mabi=64", + ] + } + } + + if (mips_arch_variant == "r6") { + if (is_clang) { + cflags += [ + "-march=mips64el", + "-mcpu=mips64r6", + ] + } else { + cflags += [ + "-mips64r6", + "-Wa,-mips64r6", + ] + ldflags += [ "-mips64r6" ] + } + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + ldflags += [ "-mips64r2" ] + if (is_clang) { + cflags += [ + "-march=mips64el", + "-mcpu=mips64r2", + ] + } else { + cflags += [ + "-mips64r2", + "-Wa,-mips64r2", + ] + } + } else if (mips_arch_variant == "loongson3") { + defines += [ "_MIPS_ARCH_LOONGSON" ] + cflags += [ + "-march=loongson3a", + "-mno-branch-likely", + "-Wa,-march=loongson3a", + ] + } + } else if (target_cpu == "mips64") { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + cflags += [ "--target=mips64-linux-gnuabi64" ] + ldflags += [ "--target=mips64-linux-gnuabi64" ] + } else { + cflags += [ + "-EB", + "-mabi=64", + ] + ldflags += [ + "-EB", + "-mabi=64", + ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ + "-mips64r6", + "-Wa,-mips64r6", + ] + ldflags += [ "-mips64r6" ] + + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + cflags += [ + "-mips64r2", + "-Wa,-mips64r2", + ] + ldflags += [ "-mips64r2" ] + } + } else if (target_cpu == "pnacl" && is_nacl_nonsfi) { + if (target_cpu == "x86" || target_cpu == "x64") { + cflags += [ + "-arch", + "x86-32-nonsfi", + "--pnacl-bias=x86-32-nonsfi", + "--target=i686-unknown-nacl", + ] + ldflags += [ + "-arch", + "x86-32-nonsfi", + "--target=i686-unknown-nacl", + ] + } else if (target_cpu == "arm") { + cflags += [ + "-arch", + "arm-nonsfi", + "-mfloat-abi=hard", + "--pnacl-bias=arm-nonsfi", + "--target=armv7-unknown-nacl-gnueabihf", + ] + ldflags += [ + "-arch", + "arm-nonsfi", + "--target=armv7-unknown-nacl-gnueabihf", + ] + } + } else if (target_cpu == "ppc64") { + if (current_os == "aix") { + cflags += [ "-maix64" ] + ldflags += [ "-maix64" ] + } else { + cflags += [ "-m64" ] + ldflags += [ "-m64" ] + } + } else if (target_cpu == "s390x") { + cflags += [ "-m64" ] + ldflags += [ "-m64" ] + } + } + + asmflags = cflags +} + +# This provides options to tweak code generation that are necessary +# for particular Chromium code or for working around particular +# compiler bugs (or the combination of the two). +config("compiler_codegen") { + configs = [] + cflags = [] + ldflags = [] + + if (is_nacl) { + configs += [ "//build/config/nacl:compiler_codegen" ] + } + + if (target_cpu == "arm64" && is_android) { + # On arm64 disable outlining for Android. See crbug.com/931297 for more + # information. + cflags += [ "-mno-outline" ] + + # This can be removed once https://bugs.llvm.org/show_bug.cgi?id=40348 + # has been resolved, and -mno-outline is obeyed by the linker during + # ThinLTO. + ldflags += [ "-Wl,-mllvm,-enable-machine-outliner=never" ] + } + + asmflags = cflags +} + +# This provides options that make the build deterministic, so that the same +# revision produces the same output, independent of the name of the build +# directory and of the computer the build is done on. +# The relative path from build dir to source dir makes it into the build +# outputs, so it's recommended that you use a build dir two levels deep +# (e.g. "out/Release") so that you get the same "../.." path as all the bots +# in your build outputs. +config("compiler_deterministic") { + cflags = [] + ldflags = [] + + # Eliminate build metadata (__DATE__, __TIME__ and __TIMESTAMP__) for + # deterministic build. See https://crbug.com/314403 + if (!is_official_build) { + if (is_win && !is_clang) { + cflags += [ + "/wd4117", # Trying to define or undefine a predefined macro. + "/D__DATE__=", + "/D__TIME__=", + "/D__TIMESTAMP__=", + ] + } else { + cflags += [ + "-Wno-builtin-macro-redefined", + "-D__DATE__=", + "-D__TIME__=", + "-D__TIMESTAMP__=", + ] + } + } + + # Makes builds independent of absolute file path. + if (is_clang && strip_absolute_paths_from_debug_symbols) { + # If debug option is given, clang includes $cwd in debug info by default. + # For such build, this flag generates reproducible obj files even we use + # different build directory like "out/feature_a" and "out/feature_b" if + # we build same files with same compile flag. + # Other paths are already given in relative, no need to normalize them. + if (is_nacl) { + # TODO(https://crbug.com/1231236): Use -ffile-compilation-dir= here. + cflags += [ + "-Xclang", + "-fdebug-compilation-dir", + "-Xclang", + ".", + ] + } else { + # -ffile-compilation-dir is an alias for both -fdebug-compilation-dir= + # and -fcoverage-compilation-dir=. + cflags += [ "-ffile-compilation-dir=." ] + } + if (!is_win) { + # We don't use clang -cc1as on Windows (yet? https://crbug.com/762167) + asmflags = [ "-Wa,-fdebug-compilation-dir,." ] + } + + if (is_win && use_lld) { + if (symbol_level == 2 || (is_clang && using_sanitizer)) { + # Absolutize source file paths for PDB. Pass the real build directory + # if the pdb contains source-level debug information and if linker + # reproducibility is not critical. + ldflags += [ "/PDBSourcePath:" + rebase_path(root_build_dir) ] + } else { + # Use a fake fixed base directory for paths in the pdb to make the pdb + # output fully deterministic and independent of the build directory. + ldflags += [ "/PDBSourcePath:o:\fake\prefix" ] + } + } + } + + # Tells the compiler not to use absolute paths when passing the default + # paths to the tools it invokes. We don't want this because we don't + # really need it and it can mess up the goma cache entries. + if (is_clang && !is_nacl) { + cflags += [ "-no-canonical-prefixes" ] + + # Same for links: Let the compiler driver invoke the linker + # with a relative path and pass relative paths to built-in + # libraries. Not needed on Windows because we call the linker + # directly there, not through the compiler driver. + # We don't link on goma, so this change is just for cleaner + # internal linker invocations, for people who work on the build. + if (!is_win) { + ldflags += [ "-no-canonical-prefixes" ] + } + } +} + +config("clang_revision") { + if (false && is_clang && clang_base_path == default_clang_base_path) { + update_args = [ + "--print-revision", + "--verify-version=$clang_version", + ] + if (llvm_force_head_revision) { + update_args += [ "--llvm-force-head-revision" ] + } + clang_revision = exec_script("//tools/clang/scripts/update.py", + update_args, + "trim string") + + # 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=\"$clang_revision\"" ] + } +} + +config("compiler_arm_fpu") { + if (target_cpu == "arm" && !is_ios && !is_nacl) { + cflags = [ "-mfpu=$arm_fpu" ] + if (!arm_use_thumb) { + cflags += [ "-marm" ] + } + asmflags = cflags + } +} + +config("compiler_arm_thumb") { + if (target_cpu == "arm" && arm_use_thumb && is_posix && + !(is_apple || is_nacl)) { + cflags = [ "-mthumb" ] + } +} + +config("compiler_arm") { + if (target_cpu == "arm" && (is_chromeos_ash || is_chromeos_lacros)) { + # arm is normally the default mode for clang, but on chromeos a wrapper + # is used to pass -mthumb, and therefor change the default. + cflags = [ "-marm" ] + } +} + +# 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") { + configs = [] + + # The order of this config is important: it must appear before + # android:runtime_library. This is to ensure libc++ appears before + # libandroid_support in the -isystem include order. Otherwise, there will be + # build errors related to symbols declared in math.h. + if (use_custom_libcxx) { + configs += [ "//build/config/c++:runtime_library" ] + } + + # TODO(crbug.com/830987): Come up with a better name for is POSIX + Fuchsia + # configuration. + if (is_posix || is_fuchsia) { + configs += [ "//build/config/posix:runtime_library" ] + } + + # System-specific flags. If your compiler flags apply to one of the + # categories here, add it to the associated file to keep this shared config + # smaller. + if (is_win) { + configs += [ "//build/config/win:runtime_library" ] + } else if (is_linux || is_chromeos) { + configs += [ "//build/config/linux:runtime_library" ] + } else if (is_ios) { + configs += [ "//build/config/ios:runtime_library" ] + } else if (is_mac) { + configs += [ "//build/config/mac:runtime_library" ] + } else if (is_android) { + configs += [ "//build/config/android:runtime_library" ] + } + + if (is_component_build) { + defines = [ "COMPONENT_BUILD" ] + } +} + +# default_warnings ------------------------------------------------------------ +# +# Collects all warning flags that are used by default. This is used as a +# subconfig of both chromium_code and no_chromium_code. This way these +# flags are guaranteed to appear on the compile command line after -Wall. +config("default_warnings") { + cflags = [] + cflags_c = [] + cflags_cc = [] + ldflags = [] + + if (is_win) { + if (treat_warnings_as_errors) { + cflags += [ "/WX" ] + } + if (fatal_linker_warnings) { + ldflags = [ "/WX" ] + } + + cflags += [ + # Warnings permanently disabled: + + # C4091: 'typedef ': ignored on left of 'X' when no variable is + # declared. + # This happens in a number of Windows headers. Dumb. + "/wd4091", + + # 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", + + # C4275: non dll-interface class used as base for dll-interface class + # This points out a potential (but rare) problem with referencing static + # fields of a non-exported base, through the base's non-exported inline + # functions, or directly. The warning is subtle enough that people just + # suppressed it when they saw it, so it's not worth it. + "/wd4275", + + # C4312 is a VS 2015 64-bit warning for integer to larger pointer. + # TODO(brucedawson): fix warnings, crbug.com/554200 + "/wd4312", + + # C4324 warns when padding is added to fulfill alignas requirements, + # but can trigger in benign cases that are difficult to individually + # suppress. + "/wd4324", + + # 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. + "/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. + "/wd4838", # Narrowing conversion. Doesn't seem to be very useful. + "/wd4995", # 'X': name was marked as #pragma deprecated + "/wd4996", # Deprecated function warning. + + # 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. + "/wd4456", + "/wd4457", + "/wd4458", + "/wd4459", + + # All of our compilers support the extensions below. + "/wd4200", # nonstandard extension used: zero-sized array in struct/union + "/wd4201", # nonstandard extension used: nameless struct/union + "/wd4204", # nonstandard extension used : non-constant aggregate + # initializer + + "/wd4221", # nonstandard extension used : 'identifier' : cannot be + # initialized using address of automatic variable + + # http://crbug.com/588506 - Conversion suppressions waiting on Clang + # -Wconversion. + "/wd4245", # 'conversion' : conversion from 'type1' to 'type2', + # signed/unsigned mismatch + + "/wd4267", # 'var' : conversion from 'size_t' to 'type', possible loss of + # data + + "/wd4305", # 'identifier' : truncation from 'type1' to 'type2' + "/wd4389", # 'operator' : signed/unsigned mismatch + + "/wd4702", # unreachable code + + # http://crbug.com/848979 - MSVC is more conservative than Clang with + # regards to variables initialized and consumed in different branches. + "/wd4701", # Potentially uninitialized local variable 'name' used + "/wd4703", # Potentially uninitialized local pointer variable 'name' used + + # http://crbug.com/848979 - Remaining Clang permitted warnings. + "/wd4661", # 'identifier' : no suitable definition provided for explicit + # template instantiation request + + "/wd4706", # assignment within conditional expression + # MSVC is stricter and requires a boolean expression. + + "/wd4715", # 'function' : not all control paths return a value' + # MSVC does not analyze switch (enum) for completeness. + ] + + cflags_cc += [ + # Allow "noexcept" annotations even though we compile with exceptions + # disabled. + "/wd4577", + ] + + if (target_cpu == "x86") { + cflags += [ + # VC++ 2015 changes 32-bit size_t truncation warnings from 4244 to + # 4267. Example: short TruncTest(size_t x) { return x; } + # Since we disable 4244 we need to disable 4267 during migration. + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + "/wd4267", + ] + } + } else { + if (is_apple && !is_nacl) { + # When compiling Objective-C, warns if a method is used whose + # availability is newer than the deployment target. + cflags += [ "-Wunguarded-availability" ] + } + + if (is_ios) { + # When compiling Objective-C, warns if a selector named via @selector has + # not been defined in any visible interface. + cflags += [ "-Wundeclared-selector" ] + } + + # Suppress warnings about ABI changes on ARM (Clang doesn't give this + # warning). + if (target_cpu == "arm" && !is_clang) { + cflags += [ "-Wno-psabi" ] + } + + if (!is_clang) { + cflags_cc += [ + # See comment for -Wno-c++11-narrowing. + "-Wno-narrowing", + ] + + # -Wno-class-memaccess warns about hash table and vector in blink. + # But the violation is intentional. + if (!is_nacl) { + cflags_cc += [ "-Wno-class-memaccess" ] + } + + # -Wunused-local-typedefs is broken in gcc, + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63872 + cflags += [ "-Wno-unused-local-typedefs" ] + + # Don't warn about "maybe" uninitialized. Clang doesn't include this + # in -Wall but gcc does, and it gives false positives. + cflags += [ "-Wno-maybe-uninitialized" ] + cflags += [ "-Wno-deprecated-declarations" ] + + # -Wcomment gives too many false positives in the case a + # backslash ended comment line is followed by a new line of + # comments + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61638 + cflags += [ "-Wno-comments" ] + + # -Wpacked-not-aligned complains all generated mojom-shared-internal.h + # files. + cflags += [ "-Wno-packed-not-aligned" ] + } + } + + # Common Clang and GCC warning setup. + if (!is_win || is_clang) { + cflags += [ + # Disables. + "-Wno-missing-field-initializers", # "struct foo f = {0};" + "-Wno-unused-parameter", # Unused function parameters. + ] + } + + if (is_clang) { + cflags += [ + "-Wloop-analysis", + + # TODO(thakis): This used to be implied by -Wno-unused-function, + # which we no longer use. Check if it makes sense to remove + # this as well. http://crbug.com/316352 + "-Wno-unneeded-internal-declaration", + ] + + # use_xcode_clang only refers to the iOS toolchain, host binaries use + # chromium's clang always. + if (!is_nacl || is_nacl_saigo) { + if (is_win) { + # TODO(thakis): https://crbug.com/617318 + # Currently goma can not handle case sensitiveness for windows well. + cflags += [ "-Wno-nonportable-include-path" ] + + # Warns in ATL headers; see https://crbug.com/1208419. + cflags += [ "-Wno-null-pointer-subtraction" ] + } + + if (current_toolchain == host_toolchain || !use_xcode_clang) { + # Flags Xcode 9.2 (Clang clang-900.0.39.2) does not recognize. + cflags += [ + "-Wenum-compare-conditional", + + # An ABI compat warning we don't care about, https://crbug.com/1102157 + # TODO(thakis): Push this to the (few) targets that need it, + # instead of having a global flag. + "-Wno-psabi", + + # Ignore warnings about MSVC optimization pragmas. + # TODO(thakis): Only for no_chromium_code? http://crbug.com/912662 + "-Wno-ignored-pragma-optimize", + + # TODO(https://crbug.com/1016945) Clean up, enable. + "-Wno-builtin-assume-aligned-alignment", + ] + + # NaCl does not support flags from ToT. + if (!is_nacl) { + cflags += [ + # TODO(https://crbug.com/1203071): Clean up and enable. + "-Wno-unused-but-set-parameter", + "-Wno-unused-but-set-variable", + ] + } + + if (is_fuchsia) { + # TODO(https://bugs.chromium.org/p/fuchsia/issues/detail?id=77383) + cflags += [ "-Wno-deprecated-copy" ] + } + + if (enable_wmax_tokens) { + cflags += [ "-Wmax-tokens" ] + } + } + } + } +} + +# 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. + + if (is_clang) { + # Opt in to additional [[nodiscard]] on standard library methods. + defines = [ "_HAS_NODISCARD" ] + } + } else { + cflags = [ "-Wall" ] + if (treat_warnings_as_errors) { + cflags += [ "-Werror" ] + + # The compiler driver can sometimes (rarely) emit warnings before calling + # the actual linker. Make sure these warnings are treated as errors as + # well. + ldflags = [ "-Werror" ] + } + if (is_clang) { + # Enable extra warnings for chromium_code when we control the compiler. + cflags += [ "-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 (!is_debug && !using_sanitizer && target_cpu != "s390x" && + target_cpu != "s390" && target_cpu != "ppc64" && + target_cpu != "mips" && target_cpu != "mips64") { + # 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" ] + } + + if (is_mac) { + cflags_objc = [ "-Wobjc-missing-property-synthesis" ] + cflags_objcc = [ "-Wobjc-missing-property-synthesis" ] + } + + if (is_ios) { + cflags_objc = [ "-Wimplicit-retain-self" ] + cflags_objcc = cflags_objc + } + } + + if (is_clang) { + cflags += [ + # Warn on missing break statements at the end of switch cases. + # For intentional fallthrough, use FALLTHROUGH; from + # base/compiler_specific.h + "-Wimplicit-fallthrough", + ] + + # TODO(thakis): Enable this more often, https://crbug.com/346399 + # use_libfuzzer: https://crbug.com/1063180 + if (!is_nacl && !use_libfuzzer) { + cflags += [ "-Wunreachable-code-aggressive" ] + } + + # Thread safety analysis is broken under nacl: https://crbug.com/982423. + if (!is_nacl) { + cflags += [ + # Thread safety analysis. See base/thread_annotations.h and + # https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + "-Wthread-safety", + ] + } + + # TODO(thakis): Enable this for more platforms, https://crbug.com/926235 + # ChromeOS: http://crbug.com/940863 + # Chromecast: http://crbug.com/942554 + has_dchecks = is_debug || dcheck_always_on + if (!has_dchecks && is_chromeos_ash && is_chrome_branded) { + # Temporarily disable -Wextra-semi for Chrome on Chrome OS. + } else if (is_chromecast && chromecast_branding != "public") { + # Temporarily disable -Wextra-semi for Chromecast. + } else { + cflags += [ "-Wextra-semi" ] + } + } + + configs = [ ":default_warnings" ] +} + +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", + ] + } else { + # GCC may emit unsuppressible warnings so don't add -Werror for no chromium + # code. crbug.com/589724 + if (treat_warnings_as_errors && is_clang) { + cflags += [ "-Werror" ] + ldflags = [ "-Werror" ] + } + if (is_clang && !is_nacl) { + # TODO(thakis): Remove !is_nacl once + # https://codereview.webrtc.org/1552863002/ made its way into chromium. + cflags += [ "-Wall" ] + } + } + + if (is_clang) { + cflags += [ + # Lots of third-party libraries have unused variables. Instead of + # suppressing them individually, we just blanket suppress them here. + "-Wno-unused-variable", + + # Similarly, we're not going to fix all the C++11 narrowing issues in + # third-party libraries. + "-Wno-c++11-narrowing", + ] + if (!is_nacl && (current_toolchain == host_toolchain || !use_xcode_clang)) { + cflags += [ + # TODO(https://crbug.com/1202159): Clean up and enable. + "-Wno-misleading-indentation", + ] + } + } + + configs = [ ":default_warnings" ] +} + +# noshadowing ----------------------------------------------------------------- +# +# Allows turning -Wshadow on. + +config("noshadowing") { + # This flag has to be disabled for nacl because the nacl compiler is too + # strict about shadowing. + if (is_clang && !is_nacl) { + cflags = [ "-Wshadow" ] + } +} + +# rtti ------------------------------------------------------------------------ +# +# Allows turning Run-Time Type Identification on or off. + +config("rtti") { + if (is_win) { + cflags_cc = [ "/GR" ] + } else { + cflags_cc = [ "-frtti" ] + } +} + +config("no_rtti") { + # Some sanitizer configs may require RTTI to be left enabled globally + if (!use_rtti) { + if (is_win) { + cflags_cc = [ "/GR-" ] + } else { + cflags_cc = [ "-fno-rtti" ] + cflags_objcc = cflags_cc + } + } +} + +# export_dynamic --------------------------------------------------------------- +# +# Ensures all exported symbols are added to the dynamic symbol table. This is +# necessary to expose Chrome's custom operator new() and operator delete() (and +# other memory-related symbols) to libraries. Otherwise, they might +# (de)allocate memory on a different heap, which would spell trouble if pointers +# to heap-allocated memory are passed over shared library boundaries. +config("export_dynamic") { + # TODO(crbug.com/1052397): Revisit after target_os flip is completed. + if (is_linux || is_bsd || is_chromeos_lacros || export_libcxxabi_from_executables) { + ldflags = [ "-rdynamic" ] + } +} + +# thin_archive ----------------------------------------------------------------- +# +# Enables thin archives on posix, and on windows when the lld linker is used. +# Regular archives directly include the object files used to generate it. +# Thin archives merely reference the object files. +# This makes building them faster since it requires less disk IO, but is +# inappropriate if you wish to redistribute your static library. +# This config is added to the global config, so thin archives should already be +# enabled. If you want to make a distributable static library, you need to do 2 +# things: +# 1. Set complete_static_lib so that all dependencies of the library make it +# into the library. See `gn help complete_static_lib` for details. +# 2. Remove the thin_archive config, so that the .a file actually contains all +# .o files, instead of just references to .o files in the build directoy +config("thin_archive") { + # The macOS and iOS default linker ld64 does not support reading thin + # archives. + # TODO(crbug.com/1221615): Enable on is_apple if use_lld once that no longer + # confuses lldb. + if ((is_posix && !is_nacl && !is_apple) || is_fuchsia) { + arflags = [ "-T" ] + } else if (is_win && use_lld) { + arflags = [ "/llvmlibthin" ] + } +} + +# exceptions ------------------------------------------------------------------- +# +# Allows turning Exceptions on or off. +# Note: exceptions are disallowed in Google code. + +config("exceptions") { + if (is_win) { + # Enables exceptions in the STL. + if (!use_custom_libcxx) { + defines = [ "_HAS_EXCEPTIONS=1" ] + } + cflags_cc = [ "/EHsc" ] + } else { + cflags_cc = [ "-fexceptions" ] + cflags_objcc = cflags_cc + } +} + +config("no_exceptions") { + if (is_win) { + # Disables exceptions in the STL. + # libc++ uses the __has_feature macro to control whether to use exceptions, + # so defining this macro is unnecessary. Defining _HAS_EXCEPTIONS to 0 also + # breaks libc++ because it depends on MSVC headers that only provide certain + # declarations if _HAS_EXCEPTIONS is 1. Those MSVC headers do not use + # exceptions, despite being conditional on _HAS_EXCEPTIONS. + if (!use_custom_libcxx) { + defines = [ "_HAS_EXCEPTIONS=0" ] + } + } else { + cflags_cc = [ "-fno-exceptions" ] + cflags_objcc = cflags_cc + } +} + +# Warnings --------------------------------------------------------------------- + +# Generate a warning for code that might emit a static initializer. +# See: //docs/static_initializers.md +# See: https://groups.google.com/a/chromium.org/d/topic/chromium-dev/B9Q5KTD7iCo/discussion +config("wglobal_constructors") { + if (is_clang) { + cflags = [ "-Wglobal-constructors" ] + } +} + +# 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 blocklist instead, http://crbug.com/101600 +config("wexit_time_destructors") { + if (is_clang) { + cflags = [ "-Wexit-time-destructors" ] + } +} + +# Some code presumes that pointers to structures/objects are compatible +# regardless of whether what they point to is already known to be valid. +# gcc 4.9 and earlier had no way of suppressing this warning without +# suppressing the rest of them. Here we centralize the identification of +# the gcc 4.9 toolchains. +config("no_incompatible_pointer_warnings") { + cflags = [] + if (is_clang) { + cflags += [ "-Wno-incompatible-pointer-types" ] + } else if (target_cpu == "mipsel" || target_cpu == "mips64el") { + cflags += [ "-w" ] + } else if (is_chromeos_ash && target_cpu == "arm") { + cflags += [ "-w" ] + } +} + +# Optimization ----------------------------------------------------------------- +# +# The BUILDCONFIG file sets the "default_optimization" config on targets by +# default. It will be equivalent to either "optimize" (release) or +# "no_optimize" (debug) optimization configs. +# +# You can override the optimization level on a per-target basis by removing the +# default config and then adding the named one you want: +# +# configs -= [ "//build/config/compiler:default_optimization" ] +# 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. + "/Zc:inline", # Remove unreferenced COMDAT (faster links). + ] + 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:ICF is not desirable in Debug builds, since code-folding can result in + # misleading symbols in stack traces. + if (!is_debug && !is_component_build) { + common_optimize_on_ldflags += [ "/OPT:ICF" ] # Redundant COMDAT folding. + } + + if (is_official_build) { + common_optimize_on_ldflags += [ "/OPT:REF" ] # Remove unreferenced data. + # TODO(thakis): Add LTO/PGO clang flags eventually, https://crbug.com/598772 + } +} else { + common_optimize_on_cflags = [] + common_optimize_on_ldflags = [] + + if (is_android) { + # TODO(jdduke) Re-enable on mips after resolving linking + # issues with libc++ (crbug.com/456380). + if (target_cpu != "mipsel" && target_cpu != "mips64el") { + common_optimize_on_ldflags += [ + # Warn in case of text relocations. + "-Wl,--warn-shared-textrel", + ] + } + } + + if (is_apple) { + common_optimize_on_ldflags += [ "-Wl,-dead_strip" ] + + if (is_official_build) { + common_optimize_on_ldflags += [ + "-Wl,-no_data_in_code_info", + "-Wl,-no_function_starts", + ] + } + } else if (current_os != "aix") { + # Non-Mac Posix flags. + # Aix does not support these. + + common_optimize_on_cflags += [ + # 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", + ] + if (!is_nacl && is_clang) { + # We don't care about unique section names, this makes object files a bit + # smaller. + common_optimize_on_cflags += [ "-fno-unique-section-names" ] + } + + common_optimize_on_ldflags += [ + # Specifically tell the linker to perform optimizations. + # See http://lwn.net/Articles/192624/ . + # -O2 enables string tail merge optimization in gold and lld. + "-Wl,-O2", + "-Wl,--gc-sections", + ] + } +} + +config("default_stack_frames") { + if (is_posix || is_fuchsia) { + if (enable_frame_pointers) { + cflags = [ "-fno-omit-frame-pointer" ] + + # Omit frame pointers for leaf functions on x86, otherwise building libyuv + # gives clang's register allocator issues, see llvm.org/PR15798 / + # crbug.com/233709 + if (is_clang && target_cpu == "x86" && !is_apple) { + cflags += [ "-momit-leaf-frame-pointer" ] + } + } else { + cflags = [ "-fomit-frame-pointer" ] + } + } + # On Windows, the flag to enable framepointers "/Oy-" must always come after + # the optimization flag [e.g. "/O2"]. The optimization flag is set by one of + # the "optimize" configs, see rest of this file. The ordering that cflags are + # applied is well-defined by the GN spec, and there is no way to ensure that + # cflags set by "default_stack_frames" is applied after those set by an + # "optimize" config. Similarly, there is no way to propagate state from this + # config into the "optimize" config. We always apply the "/Oy-" config in the + # definition for common_optimize_on_cflags definition, even though this may + # not be correct. +} + +# Default "optimization on" config. +config("optimize") { + if (is_win) { + if (chrome_pgo_phase != 2) { + # Favor size over speed, /O1 must be before the common flags. + # /O1 implies /Os and /GF. + cflags = [ "/O1" ] + common_optimize_on_cflags + [ "/Oi" ] + } else { + # PGO requires all translation units to be compiled with /O2. The actual + # optimization level will be decided based on the profiling data. + cflags = [ "/O2" ] + common_optimize_on_cflags + [ "/Oi" ] + } + } else if (optimize_for_size && !is_nacl) { + # Favor size over speed. + # TODO(crbug.com/718650): Fix -Os in PNaCl compiler and remove the is_nacl + # guard above. + if (is_clang) { + cflags = [ "-Oz" ] + common_optimize_on_cflags + } else { + cflags = [ "-Os" ] + common_optimize_on_cflags + } + } else if (is_chromeos_ash) { + # TODO(gbiv): This is partially favoring size over speed. CrOS exclusively + # uses clang, and -Os in clang is more of a size-conscious -O2 than "size at + # any cost" (AKA -Oz). It'd be nice to: + # - Make `optimize_for_size` apply to all platforms where we're optimizing + # for size by default (so, also Windows) + # - Investigate -Oz here, maybe just for ARM? + cflags = [ "-Os" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + if (optimize_for_size) { + rustflags = [ "-Copt-level=s" ] + } else { + rustflags = [ "-Copt-level=3" ] + } + 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). + "/GF", # Enable string pooling (off by default). + ] + + if (target_cpu == "arm64") { + # Disable omitting frame pointers for no_optimize build because stack + # traces on Windows ARM64 rely on it. + cflags += [ "/Oy-" ] + } + } 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. + if (is_clang) { + cflags = [ "-Oz" ] + common_optimize_on_cflags + } else { + cflags = [ "-Os" ] + common_optimize_on_cflags + } + + if (!is_component_build) { + # Required for library partitions. Without this all symbols just end up + # in the base partition. + ldflags = [ "-Wl,--gc-sections" ] + } + } else if (is_fuchsia) { + # On Fuchsia, we optimize for size here to reduce the size of debug build + # packages so they can be run in a KVM. See crbug.com/910243 for details. + cflags = [ "-Og" ] + } else { + cflags = [ "-O0" ] + ldflags = [] + } +} + +# 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") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # Various components do: + # if (!is_debug) { + # configs -= [ "//build/config/compiler:default_optimization" ] + # configs += [ "//build/config/compiler:optimize_max" ] + # } + # So this config has to have the selection logic just like + # "default_optimization", below. + configs = [ "//build/config/nacl:irt_optimize" ] + } else { + ldflags = common_optimize_on_ldflags + if (is_win) { + # Favor speed over size, /O2 must be before the common flags. + # /O2 implies /Ot, /Oi, and /GF. + cflags = [ "/O2" ] + common_optimize_on_cflags + } else if (optimize_for_fuzzing) { + cflags = [ "-O1" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + rustflags = [ "-Copt-level=3" ] + } +} + +# This config can be used to override the default settings for per-component +# and whole-program optimization, optimizing the particular target for speed +# instead of code size. This config is exactly the same as "optimize_max" +# except that we use -O3 instead of -O2 on non-win, non-IRT platforms. +# +# TODO(crbug.com/621335) - rework how all of these configs are related +# so that we don't need this disclaimer. +config("optimize_speed") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # Various components do: + # if (!is_debug) { + # configs -= [ "//build/config/compiler:default_optimization" ] + # configs += [ "//build/config/compiler:optimize_max" ] + # } + # So this config has to have the selection logic just like + # "default_optimization", below. + configs = [ "//build/config/nacl:irt_optimize" ] + } else { + ldflags = common_optimize_on_ldflags + if (is_win) { + # Favor speed over size, /O2 must be before the common flags. + # /O2 implies /Ot, /Oi, and /GF. + cflags = [ "/O2" ] + common_optimize_on_cflags + } else if (optimize_for_fuzzing) { + cflags = [ "-O1" ] + common_optimize_on_cflags + } else { + cflags = [ "-O3" ] + common_optimize_on_cflags + } + rustflags = [ "-Copt-level=3" ] + } +} + +config("optimize_fuzzing") { + cflags = [ "-O1" ] + common_optimize_on_cflags + rustflags = [ "-Copt-level=1" ] + ldflags = common_optimize_on_ldflags + visibility = [ ":default_optimization" ] +} + +# The default optimization applied to all targets. This will be equivalent to +# either "optimize" or "no_optimize", depending on the build flags. +config("default_optimization") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # It gets optimized the same way regardless of the type of build. + configs = [ "//build/config/nacl:irt_optimize" ] + } else if (is_debug) { + configs = [ ":no_optimize" ] + } else if (optimize_for_fuzzing) { + assert(!is_win, "Fuzzing optimize level not supported on Windows") + + # Coverage build is quite slow. Using "optimize_for_fuzzing" makes it even + # slower as it uses "-O1" instead of "-O3". Prevent that from happening. + assert(!use_clang_coverage, + "optimize_for_fuzzing=true should not be used with " + + "use_clang_coverage=true.") + configs = [ ":optimize_fuzzing" ] + } else { + configs = [ ":optimize" ] + } +} + +_clang_sample_profile = "" +if (is_clang && is_a_target_toolchain) { + if (clang_sample_profile_path != "") { + _clang_sample_profile = clang_sample_profile_path + } else if (clang_use_default_sample_profile) { + assert(build_with_chromium, + "Our default profiles currently only apply to Chromium") + assert(is_android || is_chromeos_lacros || is_chromeos_ash || is_chromecast, + "The current platform has no default profile") + if (is_android || is_chromecast) { + _clang_sample_profile = "//chrome/android/profiles/afdo.prof" + } else { + assert(chromeos_afdo_platform == "atom" || + chromeos_afdo_platform == "bigcore", + "Only atom and bigcore are valid Chrome OS profiles.") + _clang_sample_profile = + "//chromeos/profiles/${chromeos_afdo_platform}.afdo.prof" + } + } +} + +# Clang offers a way to assert that AFDO profiles are accurate, which causes it +# to optimize functions not represented in a profile more aggressively for size. +# This config can be toggled in cases where shaving off binary size hurts +# performance too much. +config("afdo_optimize_size") { + if (_clang_sample_profile != "" && sample_profile_is_accurate) { + cflags = [ "-fprofile-sample-accurate" ] + } +} + +# GCC and clang support a form of profile-guided optimization called AFDO. +# There are some targeted places that AFDO regresses (and an icky interaction +# between //base/allocator:tcmalloc and AFDO on GCC), so we provide a separate +# config to allow AFDO to be disabled per-target. +config("afdo") { + if (is_clang) { + cflags = [] + if (clang_emit_debug_info_for_profiling) { + # Add the following flags to generate debug info for profiling. + cflags += [ "-gline-tables-only" ] + if (!is_nacl) { + cflags += [ "-fdebug-info-for-profiling" ] + } + } + if (_clang_sample_profile != "") { + assert(chrome_pgo_phase == 0, "AFDO can't be used in PGO builds") + rebased_clang_sample_profile = + rebase_path(_clang_sample_profile, root_build_dir) + cflags += [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ] + inputs = [ _clang_sample_profile ] + } + } else if (auto_profile_path != "" && is_a_target_toolchain) { + cflags = [ "-fauto-profile=${auto_profile_path}" ] + inputs = [ auto_profile_path ] + } +} + +# Symbols ---------------------------------------------------------------------- + +# The BUILDCONFIG file sets the "default_symbols" config on targets by +# default. It will be equivalent to one the three specific symbol levels. +# +# You can override the symbol level on a per-target basis by removing the +# default config and then adding the named one you want: +# +# configs -= [ "//build/config/compiler:default_symbols" ] +# configs += [ "//build/config/compiler:symbols" ] + +# A helper config that all configs passing /DEBUG to the linker should +# include as sub-config. +config("win_pdbaltpath") { + visibility = [ + ":minimal_symbols", + ":symbols", + ] + + # /DEBUG causes the linker to generate a pdb file, and to write the absolute + # path to it in the executable file it generates. This flag turns that + # absolute path into just the basename of the pdb file, which helps with + # build reproducibility. Debuggers look for pdb files next to executables, + # so there's minimal downside to always using this. However, post-mortem + # debugging of Chromium crash dumps and ETW tracing can be complicated by this + # switch so an option to omit it is important. + if (!use_full_pdb_paths) { + ldflags = [ "/pdbaltpath:%_PDB%" ] + } +} + +# Full symbols. +config("symbols") { + if (is_win) { + if (is_clang) { + cflags = [ "/Z7" ] # Debug information in the .obj files. + } else { + cflags = [ "/Zi" ] # Produce PDB file, no edit and continue. + } + + if (is_clang && use_lld && use_ghash) { + cflags += [ "-gcodeview-ghash" ] + ldflags = [ "/DEBUG:GHASH" ] + } else { + ldflags = [ "/DEBUG" ] + } + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + } else { + cflags = [] + if (is_mac && enable_dsyms) { + # If generating dSYMs, specify -fno-standalone-debug. This was + # originally specified for https://crbug.com/479841 because dsymutil + # could not handle a 4GB dSYM file. But dsymutil from Xcodes prior to + # version 7 also produces debug data that is incompatible with Breakpad + # dump_syms, so this is still required (https://crbug.com/622406). + cflags += [ "-fno-standalone-debug" ] + } else if (is_mac && !use_dwarf5) { + # clang defaults to DWARF2 on macOS unless mac_deployment_target is + # at least 10.11. + # TODO(thakis): Remove this once mac_deployment_target is 10.11. + cflags += [ "-gdwarf-4" ] + } + + if (use_dwarf5 && !is_nacl) { + cflags += [ "-gdwarf-5" ] + } + + # The gcc-based nacl compilers don't support -fdebug-compilation-dir (see + # elsewhere in this file), so they can't have build-dir-independent output. + # Disable symbols for nacl object files to get deterministic, + # build-directory-independent output. pnacl and nacl-clang do support that + # flag, so we can use use -g1 for pnacl and nacl-clang compiles. + # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang. + if (!is_nacl || is_clang) { + cflags += [ "-g2" ] + } + + if (!is_nacl && is_clang && !is_tsan && !is_asan) { + # gcc generates dwarf-aranges by default on -g1 and -g2. On clang it has + # to be manually enabled. + # + # It is skipped in tsan and asan because enabling it causes some + # formatting changes in the output which would require fixing bunches + # of expectation regexps. + cflags += [ "-gdwarf-aranges" ] + } + + if (is_apple) { + swiftflags = [ "-g" ] + } + + if (use_debug_fission) { + cflags += [ "-gsplit-dwarf" ] + } + asmflags = cflags + ldflags = [] + + # Split debug info with all thinlto builds except nacl and apple. + # thinlto requires -gsplit-dwarf in ldflags. + if (use_debug_fission && use_thin_lto && !is_nacl && !is_apple) { + ldflags += [ "-gsplit-dwarf" ] + } + + # TODO(thakis): Figure out if there's a way to make this go for 32-bit, + # currently we get "warning: + # obj/native_client/src/trusted/service_runtime/sel_asm/nacl_switch_32.o: + # DWARF info may be corrupt; offsets in a range list entry are in different + # sections" there. Maybe just a bug in nacl_switch_32.S. + if (!is_apple && !is_nacl && target_cpu != "x86" && + (use_gold || use_lld)) { + if (is_clang) { + # This flag enables the GNU-format pubnames and pubtypes sections, + # which lld needs in order to generate a correct GDB index. + # TODO(pcc): Try to make lld understand non-GNU-format pubnames + # sections (llvm.org/PR34820). + cflags += [ "-ggnu-pubnames" ] + } + ldflags += [ "-Wl,--gdb-index" ] + } + } + + if (is_clang && !is_nacl && !use_xcode_clang) { + if (is_apple) { + # TODO(https://crbug.com/1050118): Investigate missing debug info on mac. + # Make sure we don't use constructor homing on mac. + cflags += [ + "-Xclang", + "-debug-info-kind=limited", + ] + } else { + # Use constructor homing for debug info. This option reduces debug info + # by emitting class type info only when constructors are emitted. + cflags += [ + "-Xclang", + "-fuse-ctor-homing", + ] + } + } + rustflags = [ "-g" ] +} + +# Minimal symbols. +# This config guarantees to hold symbol for stack trace which are shown to user +# when crash happens in unittests running on buildbot. +config("minimal_symbols") { + if (is_win) { + # Functions, files, and line tables only. + cflags = [] + + if (is_clang && use_lld && use_ghash) { + cflags += [ "-gcodeview-ghash" ] + ldflags = [ "/DEBUG:GHASH" ] + } else { + ldflags = [ "/DEBUG" ] + } + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + + # Enable line tables for clang. MSVC doesn't have an equivalent option. + if (is_clang) { + # -gline-tables-only is the same as -g1, but clang-cl only exposes the + # former. + cflags += [ "-gline-tables-only" ] + } + } else { + cflags = [] + if (is_mac && !use_dwarf5) { + # clang defaults to DWARF2 on macOS unless mac_deployment_target is + # at least 10.11. + # TODO(thakis): Remove this once mac_deployment_target is 10.11. + cflags += [ "-gdwarf-4" ] + } + + if (use_dwarf5 && !is_nacl) { + cflags += [ "-gdwarf-5" ] + } + + # The gcc-based nacl compilers don't support -fdebug-compilation-dir (see + # elsewhere in this file), so they can't have build-dir-independent output. + # Disable symbols for nacl object files to get deterministic, + # build-directory-independent output. pnacl and nacl-clang do support that + # flag, so we can use use -g1 for pnacl and nacl-clang compiles. + # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang. + if (!is_nacl || is_clang) { + cflags += [ "-g1" ] + } + + if (!is_nacl && is_clang && !is_tsan && !is_asan) { + # See comment for -gdwarf-aranges in config("symbols"). + cflags += [ "-gdwarf-aranges" ] + } + + ldflags = [] + if (is_android && is_clang) { + # Android defaults to symbol_level=1 builds in production builds + # (https://crbug.com/648948), but clang, unlike gcc, doesn't emit + # DW_AT_linkage_name in -g1 builds. -fdebug-info-for-profiling enables + # that (and a bunch of other things we don't need), so that we get + # qualified names in stacks. + # TODO(thakis): Consider making clang emit DW_AT_linkage_name in -g1 mode; + # failing that consider doing this on non-Android too. + cflags += [ "-fdebug-info-for-profiling" ] + } + + # Note: debug_fission is no-op with symbol_level=1 since all -g1 debug_info + # will stay in the executable. + + asmflags = cflags + } + rustflags = [ "-Cdebuginfo=1" ] +} + +# This configuration contains function names only. That is, the compiler is +# told to not generate debug information and the linker then just puts function +# names in the final debug information. +config("no_symbols") { + if (is_win) { + ldflags = [ "/DEBUG" ] + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + } else { + cflags = [ "-g0" ] + asmflags = cflags + } +} + +# Default symbols. +config("default_symbols") { + if (symbol_level == 0) { + configs = [ ":no_symbols" ] + } else if (symbol_level == 1) { + configs = [ ":minimal_symbols" ] + } else if (symbol_level == 2) { + configs = [ ":symbols" ] + } else { + assert(false) + } + + # This config is removed by base unittests apk. + if (is_android && is_clang && strip_debug_info) { + configs += [ ":strip_debug" ] + } +} + +config("strip_debug") { + if (!defined(ldflags)) { + ldflags = [] + } + ldflags += [ "-Wl,--strip-debug" ] +} + +if (is_apple) { + # On Mac and iOS, this enables support for ARC (automatic ref-counting). + # See http://clang.llvm.org/docs/AutomaticReferenceCounting.html. + config("enable_arc") { + common_flags = [ "-fobjc-arc" ] + cflags_objc = common_flags + cflags_objcc = common_flags + } +} + +if (is_chromeos_ash && is_chromeos_device) { + # This config is intended to be a temporary to facilitate + # the transition to use orderfile in Chrome OS. Once orderfile + # use becomes a default in Chrome OS, this config should not + # be needed. + config("use_orderfile_for_hugepage") { + if (chrome_orderfile_path != "") { + defines = [ "CHROMEOS_ORDERFILE_USE" ] + } + } +} + +if (is_android || (is_chromeos_ash && is_chromeos_device)) { + # Use orderfile for linking Chrome on Android and Chrome OS. + # This config enables using an orderfile for linking in LLD. + # TODO: Consider using call graph sort instead, at least on Android. + config("chrome_orderfile_config") { + if (chrome_orderfile_path != "" && !enable_call_graph_profile_sort) { + assert(use_lld) + _rebased_orderfile = rebase_path(chrome_orderfile_path, root_build_dir) + ldflags = [ + "-Wl,--symbol-ordering-file", + "-Wl,$_rebased_orderfile", + "-Wl,--no-warn-symbol-ordering", + ] + inputs = [ chrome_orderfile_path ] + } + } +} + +# Initialize all variables on the stack if needed. +config("default_init_stack_vars") { + cflags = [] + if (init_stack_vars && is_clang && !is_nacl && !using_sanitizer) { + cflags += [ "-ftrivial-auto-var-init=pattern" ] + } +} + +buildflag_header("compiler_buildflags") { + header = "compiler_buildflags.h" + + flags = [ + "CLANG_PGO=$chrome_pgo_phase", + "SYMBOL_LEVEL=$symbol_level", + ] +} + +config("cet_shadow_stack") { + if (enable_cet_shadow_stack && is_win) { + assert(target_cpu == "x64") + ldflags = [ "/CETCOMPAT" ] + } +} diff --git a/third_party/libwebrtc/build/config/compiler/compiler.gni b/third_party/libwebrtc/build/config/compiler/compiler.gni new file mode 100644 index 0000000000..55ae571ab7 --- /dev/null +++ b/third_party/libwebrtc/build/config/compiler/compiler.gni @@ -0,0 +1,324 @@ +# 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/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/compiler/pgo/pgo.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +if (is_android) { + import("//build/config/android/abi.gni") +} +if (target_cpu == "arm" || target_cpu == "arm64") { + import("//build/config/arm.gni") +} + +if (is_apple) { + import("//build/config/apple/symbols.gni") +} + +if (is_ios) { + import("//build/config/ios/config.gni") +} + +declare_args() { + # Default to warnings as errors for default workflow, where we catch + # warnings with known toolchains. Allow overriding this e.g. for Chromium + # builds on Linux that could use a different version of the compiler. + # With GCC, warnings in no-Chromium code are always not treated as errors. + treat_warnings_as_errors = true + + # How many symbols to include in the build. This affects the performance of + # the build since the symbols are large and dealing with them is slow. + # 2 means regular build with symbols. + # 1 means minimal symbols, usually enough for backtraces only. Symbols with + # internal linkage (static functions or those in anonymous namespaces) may not + # appear when using this level. + # 0 means no symbols. + # -1 means auto-set according to debug/release and platform. + symbol_level = -1 + + # Android-only: Strip the debug info of libraries within lib.unstripped to + # reduce size. As long as symbol_level > 0, this will still allow stacks to be + # symbolized. + strip_debug_info = false + + # 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 + + # 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 + # + # This is a placeholder value indicating that the code below should set + # the default. This is necessary to delay the evaluation of the default + # value expression until after its input values such as use_gold have + # been set, e.g. by a toolchain_args() block. + use_debug_fission = "default" + + # Enables support for ThinLTO, which links 3x-10x faster than full LTO. See + # also http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html + # Use it by default on official-optimized android and Chrome OS builds, but + # not ARC or linux-chromeos since it's been seen to not play nicely with + # Chrome's clang. crbug.com/1033839 + use_thin_lto = + is_cfi || + (is_official_build && chrome_pgo_phase != 1 && + (is_linux || is_win || (is_android && target_os != "chromeos") || + ((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device))) + + # If true, use Goma for ThinLTO code generation where applicable. + use_goma_thin_lto = false + + # Whether we're using a sample profile collected on an architecture different + # than the one we're compiling for. + # + # It's currently not possible to collect AFDO profiles on anything but + # x86{,_64}. + using_mismatched_sample_profile = target_cpu != "x64" && target_cpu != "x86" + + # Whether an error should be raised on attempts to make debug builds with + # is_component_build=false. Very large debug symbols can have unwanted side + # effects so this is enforced by default for chromium. + forbid_non_component_debug_builds = build_with_chromium + + # Exclude unwind tables by default for official builds as unwinding can be + # done from stack dumps produced by Crashpad at a later time "offline" in the + # crash server. Since this increases binary size, we don't recommend including + # them in shipping builds. + # For unofficial (e.g. development) builds and non-Chrome branded (e.g. Cronet + # which doesn't use Crashpad, crbug.com/479283) builds it's useful to be able + # to unwind at runtime. + # Include the unwind tables on Android even for official builds, as otherwise + # the crash dumps generated by Android's debuggerd are largely useless, and + # having this additional mechanism to understand issues is particularly helpful + # to WebView. + exclude_unwind_tables = is_official_build && !is_android + + # Where to redirect clang crash diagnoses + clang_diagnostic_dir = + rebase_path("//tools/clang/crashreports", root_build_dir) + + # Mark binaries as compatible with Shadow Stack of Control-flow Enforcement + # Technology (CET). If Windows version and hardware supports the feature and + # it's enabled by OS then additional validation of return address will be + # performed as mitigation against Return-oriented programming (ROP). + # https://chromium.googlesource.com/chromium/src/+/main/docs/design/sandbox.md#cet-shadow-stack + enable_cet_shadow_stack = target_cpu == "x64" +} + +assert(!is_cfi || use_thin_lto, "CFI requires ThinLTO") + +# If true, optimize for size. Does not affect windows builds. +# Linux & Mac favor speed over size. +# TODO(brettw) it's weird that Mac and desktop Linux are different. We should +# explore favoring size over speed in this case as well. +optimize_for_size = is_android || is_chromecast || is_fuchsia || is_ios + +declare_args() { + # Whether we should consider the profile we're using to be accurate. Accurate + # profiles have the benefit of (potentially substantial) binary size + # reductions, by instructing the compiler to optimize cold and uncovered + # functions heavily for size. This often comes at the cost of performance. + sample_profile_is_accurate = optimize_for_size +} + +# Determine whether to enable or disable frame pointers, based on the platform +# and build arguments. +# TODO(crbug.com/1052397): Consider changing is_chromeos_ash to is_chromeos after +# lacros-chrome switches to target_os="chromeos". +if (is_chromeos_ash || is_chromeos_lacros) { + # ChromeOS generally prefers frame pointers, to support CWP. + # However, Clang does not currently generate usable frame pointers in ARM + # 32-bit builds (https://bugs.llvm.org/show_bug.cgi?id=18505) so disable them + # there to avoid the unnecessary overhead. + enable_frame_pointers = target_cpu != "arm" +} else if (is_apple || is_linux || is_chromeos) { + enable_frame_pointers = true +} else if (is_win) { + # 64-bit Windows ABI doesn't support frame pointers. + if (target_cpu == "x64") { + enable_frame_pointers = false + } else { + enable_frame_pointers = true + } +} else if (is_android) { + enable_frame_pointers = + enable_profiling || + # Ensure that stacks from arm64 crash dumps are usable (crbug.com/391706). + target_cpu == "arm64" || + # For x86 Android, unwind tables are huge without frame pointers + # (crbug.com/762629). Enabling frame pointers grows the code size slightly + # but overall shrinks binaries considerably by avoiding huge unwind + # tables. + (target_cpu == "x86" && !exclude_unwind_tables && optimize_for_size) || + using_sanitizer || + # For caller-callee instrumentation version which needs frame pointers to + # get the caller address. + use_call_graph +} else { + # Explicitly ask for frame pointers, otherwise: + # * Stacks may be missing for sanitizer and profiling builds. + # * Debug tcmalloc can crash (crbug.com/636489). + enable_frame_pointers = using_sanitizer || enable_profiling || is_debug +} + +# In general assume that if we have frame pointers then we can use them to +# unwind the stack. However, this requires that they are enabled by default for +# most translation units, that they are emitted correctly, and that the +# compiler or platform provides a way to access them. +can_unwind_with_frame_pointers = enable_frame_pointers +if (target_cpu == "arm" && arm_use_thumb) { + # We cannot currently unwind ARM Thumb frame pointers correctly. + # See https://bugs.llvm.org/show_bug.cgi?id=18505 + can_unwind_with_frame_pointers = false +} else if (is_win) { + # Windows 32-bit does provide frame pointers, but the compiler does not + # provide intrinsics to access them, so we don't use them. + can_unwind_with_frame_pointers = false +} + +assert(!can_unwind_with_frame_pointers || enable_frame_pointers) + +# Unwinding with CFI table is only possible on static library builds and +# requried only when frame pointers are not enabled. +can_unwind_with_cfi_table = is_android && !is_component_build && + !enable_frame_pointers && target_cpu == "arm" + +# Whether or not cfi table should be enabled on arm. +# TODO(crbug.com/1090409): Replace can_unwind_with_cfi_table with this once +# sampling profiler is enabled on android. +enable_arm_cfi_table = is_android && !is_component_build && target_cpu == "arm" + +declare_args() { + # If this running on a GPU FYI bot. + # TODO(https://crbug.com/1233871): Remove this again. + is_gpu_fyi_bot = false +} + +declare_args() { + # Set to true to use lld, the LLVM linker. + # In late bring-up on macOS (see docs/mac_lld.md). + # Tentatively used on iOS, except in cronet builds (cronet still supports 32-bit builds, which + # lld doesn't support). + # The default linker everywhere else. + use_lld = + is_clang && !(is_ios && is_cronet_build) && !(is_mac && is_gpu_fyi_bot) +} + +declare_args() { + # Whether to use the gold linker from binutils instead of lld or bfd. + use_gold = !use_lld && !(is_chromecast && is_linux && + (target_cpu == "arm" || target_cpu == "mipsel")) && + (((is_linux || is_chromeos_lacros) && + (target_cpu == "x64" || target_cpu == "x86" || + target_cpu == "arm" || target_cpu == "arm64" || + target_cpu == "mipsel" || target_cpu == "mips64el")) || + (is_android && (target_cpu == "x86" || target_cpu == "x64" || + target_cpu == "arm" || target_cpu == "arm64"))) +} + +# Use relative paths for debug info. This is important to make the build +# results independent of the checkout and build directory names, which +# in turn is important for goma compile hit rate. +# Setting this to true may make it harder to debug binaries on Linux, see +# https://chromium.googlesource.com/chromium/src/+/main/docs/linux/debugging.md#Source-level-debug-with-fdebug_compilation_dir +# It's not clear if the crash server will correctly handle dSYMs with relative +# paths, so we disable this feature for official benefit. The main benefit is +# deterministic builds to reduce compile times, so this is less relevant for +# official builders. +strip_absolute_paths_from_debug_symbols_default = + is_android || is_fuchsia || is_nacl || (is_win && use_lld) || is_linux || + is_chromeos || (is_apple && !enable_dsyms) + +# If the platform uses stripped absolute paths by default, then we don't expose +# it as a configuration option. If this is causing problems, please file a bug. +if (strip_absolute_paths_from_debug_symbols_default) { + strip_absolute_paths_from_debug_symbols = true +} else { + declare_args() { + strip_absolute_paths_from_debug_symbols = false + } +} + +# If it wasn't manually set, then default use_debug_fission to false. +assert( + use_debug_fission == "default" || use_debug_fission || !use_debug_fission, + "Invalid use_debug_fission.") +if (use_debug_fission == "default") { + use_debug_fission = is_debug && !is_android && !is_fuchsia && !is_apple && + !is_win && (use_gold || use_lld) && cc_wrapper == "" +} + +# If it wasn't manually set, set to an appropriate default. +assert(symbol_level >= -1 && symbol_level <= 2, "Invalid symbol_level") +if (symbol_level == -1) { + if (is_android && !is_component_build && !use_debug_fission) { + # Reduce symbol level when it will cause invalid elf files to be created + # (due to file size). https://crbug.com/648948. + symbol_level = 1 + } else if (is_chromeos_device) { + # Use lower symbol level in Simple Chrome build for faster link time. + # For Simple Chrome, this should take precedence over is_official_build, + # turned on by --internal. + if ((target_cpu == "x64" || target_cpu == "x86") && !is_debug) { + # For release x86/x64 build, specify symbol_level=0 for faster link time. + # x86/x64 shows backtraces with symbol_level=0 (arm requires + # symbol_level=1). + symbol_level = 0 + } else { + symbol_level = 1 + } + } else if (using_sanitizer) { + # Sanitizers need line table info for stack traces. They don't need type + # info or variable info, so we can leave that out to speed up the build. + # Sanitizers also require symbols for filename suppressions to work. + symbol_level = 1 + } else if ((!is_nacl && !is_linux && !is_chromeos && !is_fuchsia && + current_os != "aix") || is_debug || is_official_build || + is_chromecast) { + # Linux builds slower by having symbols as part of the target binary, + # whereas Mac and Windows have them separate, so in Release Linux, default + # them off, but keep them on for Official builds and Chromecast builds. + symbol_level = 2 + } else { + symbol_level = 0 + } +} + +# Split dwarf works only for symbol_level == 2. +use_debug_fission = use_debug_fission && symbol_level == 2 + +# Non-component debug builds with symbol_level = 2 are an undesirable (very slow +# build times) and unsupported (some test binaries will fail with > 4 GB PDBs) +# combination. This is only checked when current_toolchain == default_toolchain +# because the is_component_build flag is set to false in various components of +# the build (like nacl) and we don't want to assert on those. +# iOS does not support component builds so add an exception for this platform. +if (forbid_non_component_debug_builds) { + assert(symbol_level != 2 || current_toolchain != default_toolchain || + is_component_build || !is_debug || is_ios, + "Can't do non-component debug builds at symbol_level=2") +} + +# Assert that the configuration isn't going to hit https://crbug.com/648948. +# An exception is made when target_os == "chromeos" as we only use the Android +# toolchain there to build relatively small binaries. +assert( + ignore_elf32_limitations || !is_android || target_os == "chromeos" || + is_component_build || symbol_level < 2 || use_debug_fission || + (android_64bit_target_cpu && skip_secondary_abi_for_cq), + "Android 32-bit non-component builds without DWARF Fission cannot " + + "have symbol_level=2 due to 4GiB file size limit, see " + + "https://crbug.com/648948. " + "If you really want to try this out, " + + "set ignore_elf32_limitations=true.") diff --git a/third_party/libwebrtc/build/config/compiler/pgo/BUILD.gn b/third_party/libwebrtc/build/config/compiler/pgo/BUILD.gn new file mode 100644 index 0000000000..3e8502ed77 --- /dev/null +++ b/third_party/libwebrtc/build/config/compiler/pgo/BUILD.gn @@ -0,0 +1,100 @@ +# 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. + +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/compiler/pgo/pgo.gni") +import("//build/toolchain/toolchain.gni") + +# Configuration that enables PGO instrumentation. +config("pgo_instrumentation_flags") { + visibility = [ ":default_pgo_flags" ] + + # Only add flags when chrome_pgo_phase == 1, so that variables we would use + # are not required to be defined when we're not actually using PGO. + if (chrome_pgo_phase == 1 && is_clang && !is_nacl && is_a_target_toolchain) { + cflags = [ "-fprofile-generate" ] + if (!is_win) { + # Windows directly calls link.exe instead of the compiler driver when + # linking, and embeds the path to the profile runtime library as + # dependent library into each object file. + ldflags = [ "-fprofile-generate" ] + } + } +} + +# Configuration that enables optimization using profile data. +config("pgo_optimization_flags") { + visibility = [ ":default_pgo_flags" ] + + # Only add flags when chrome_pgo_phase == 2, so that variables we would use + # are not required to be defined when we're not actually using PGO. + if (chrome_pgo_phase == 2 && is_clang && !is_nacl && is_a_target_toolchain) { + _pgo_target = "" + + # There are txt files used by //tools/update_pgo_profiles.py to decide which + # profiles to use, adding them as inputs so that analyzer recognizes the + # dependencies. + inputs = [] + + if (is_win) { + if (target_cpu == "x64") { + _pgo_target = "win64" + inputs = [ "//chrome/build/win64.pgo.txt" ] + } else { + _pgo_target = "win32" + inputs = [ "//chrome/build/win32.pgo.txt" ] + } + } else if (is_mac) { + _pgo_target = "mac" + inputs = [ "//chrome/build/mac.pgo.txt" ] + } else if (is_linux || is_chromeos_lacros) { + _pgo_target = "linux" + inputs = [ "//chrome/build/linux.pgo.txt" ] + } + + if (pgo_data_path == "" && _pgo_target != "") { + pgo_data_path = rebase_path(exec_script("//tools/update_pgo_profiles.py", + [ + "--target", + _pgo_target, + "get_profile_path", + ], + "value"), + root_build_dir) + } + assert(pgo_data_path != "", + "Please set pgo_data_path to point at the profile data") + cflags = [ + "-fprofile-instr-use=$pgo_data_path", + + # It's possible to have some profile data legitimately missing, + # and at least some profile data always ends up being considered + # out of date, so make sure we don't error for those cases. + "-Wno-profile-instr-unprofiled", + "-Wno-profile-instr-out-of-date", + + # Some hashing conflict results in a lot of warning like this when doing + # a PGO build: + # warning: foo.cc: Function control flow change detected (hash mismatch) + # [-Wbackend-plugin] + # See https://crbug.com/978401 + "-Wno-backend-plugin", + ] + } +} + +# Applies flags necessary when profile-guided optimization is used. +# Flags are only added if PGO is enabled, so that this config is safe to +# include by default. +config("default_pgo_flags") { + if (chrome_pgo_phase == 0) { + # Nothing. This config should be a no-op when chrome_pgo_phase == 0. + } else if (chrome_pgo_phase == 1) { + configs = [ ":pgo_instrumentation_flags" ] + } else if (chrome_pgo_phase == 2) { + configs = [ ":pgo_optimization_flags" ] + } +} diff --git a/third_party/libwebrtc/build/config/compiler/pgo/pgo.gni b/third_party/libwebrtc/build/config/compiler/pgo/pgo.gni new file mode 100644 index 0000000000..c053eb530b --- /dev/null +++ b/third_party/libwebrtc/build/config/compiler/pgo/pgo.gni @@ -0,0 +1,25 @@ +# 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. + +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/ui_mode.gni") + +declare_args() { + # Specify the current PGO phase. + # Here's the different values that can be used: + # 0 : Means that PGO is turned off. + # 1 : Used during the PGI (instrumentation) phase. + # 2 : Used during the PGO (optimization) phase. + chrome_pgo_phase = 0 + if (is_official_build && + # TODO(crbug.com/1052397): Remove chromeos_is_browser_only once + # target_os switch for lacros-chrome is completed. + (is_win || is_mac || + (is_linux && !chromeos_is_browser_only && !is_chromecast))) { + chrome_pgo_phase = 2 + } + + # When using chrome_pgo_phase = 2, read profile data from this path. + pgo_data_path = "" +} diff --git a/third_party/libwebrtc/build/config/compute_inputs_for_analyze.gni b/third_party/libwebrtc/build/config/compute_inputs_for_analyze.gni new file mode 100644 index 0000000000..050ab70a3f --- /dev/null +++ b/third_party/libwebrtc/build/config/compute_inputs_for_analyze.gni @@ -0,0 +1,14 @@ +# Copyright 2018 The Chromium 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() { + # Enable this flag when running "gn analyze". + # + # This causes some gn actions to compute inputs immediately (via exec_script) + # where they would normally compute them only when executed (and write them to + # a depfile). + # + # This flag will slow down GN, but is required for analyze to work properly. + compute_inputs_for_analyze = false +} diff --git a/third_party/libwebrtc/build/config/coverage/BUILD.gn b/third_party/libwebrtc/build/config/coverage/BUILD.gn new file mode 100644 index 0000000000..09c227dce8 --- /dev/null +++ b/third_party/libwebrtc/build/config/coverage/BUILD.gn @@ -0,0 +1,34 @@ +# Copyright 2017 The Chromium 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/coverage/coverage.gni") + +config("default_coverage") { + if (use_clang_coverage) { + ldflags = [] + if (!is_win) { + # Windows directly calls link.exe instead of the compiler driver when + # linking, and embeds the path to the profile runtime library as + # dependent library into each object file. + ldflags += [ "-fprofile-instr-generate" ] + } + + cflags = [ + "-fprofile-instr-generate", + "-fcoverage-mapping", + + # Following experimental flags removes unused header functions from the + # coverage mapping data embedded in the test binaries, and the reduction + # of binary size enables building Chrome's large unit test targets on + # MacOS. Please refer to crbug.com/796290 for more details. + "-mllvm", + "-limited-coverage-experimental=true", + ] + + if (is_linux || is_chromeos) { + # TODO(crbug.com/1194301): Remove this flag. + cflags += [ "-fno-use-cxa-atexit" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/coverage/OWNERS b/third_party/libwebrtc/build/config/coverage/OWNERS new file mode 100644 index 0000000000..0fc481f477 --- /dev/null +++ b/third_party/libwebrtc/build/config/coverage/OWNERS @@ -0,0 +1,3 @@ +inferno@chromium.org +liaoyuke@chromium.org +ochang@chromium.org diff --git a/third_party/libwebrtc/build/config/coverage/coverage.gni b/third_party/libwebrtc/build/config/coverage/coverage.gni new file mode 100644 index 0000000000..b0e01c1d4e --- /dev/null +++ b/third_party/libwebrtc/build/config/coverage/coverage.gni @@ -0,0 +1,40 @@ +# Copyright 2017 The Chromium 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/toolchain/toolchain.gni") +if (is_fuchsia) { + import("//third_party/fuchsia-sdk/sdk/build/component.gni") +} + +# There are two ways to enable code coverage instrumentation: +# 1. When |use_clang_coverage| or |use_jacoco_coverage| is true and +# |coverage_instrumentation_input_file| is empty, all source files or +# Java class files are instrumented. +# 2. When |use_clang_coverage| or |use_jacoco_coverage| is true and +# |coverage_instrumentation_input_file| is NOT empty and points to +# a text file on the file system, ONLY source files specified in the +# input file or Java class files related to source files are instrumented. +declare_args() { + # Enable Clang's Source-based Code Coverage. + if (is_fuchsia) { + use_clang_coverage = fuchsia_code_coverage + } else { + use_clang_coverage = false + } + + # Enables JaCoCo Java code coverage. + use_jacoco_coverage = false + + # The path to the coverage instrumentation input file should be a source root + # absolute path (e.g. //out/Release/coverage_instrumentation_input.txt), and + # the file consists of multiple lines where each line represents a path to a + # source file, and the paths must be relative to the root build directory. + # e.g. ../../base/task/post_task.cc for build directory 'out/Release'. + # + # NOTE that this arg will be non-op if use_clang_coverage is false. + coverage_instrumentation_input_file = "" +} + +assert(!use_clang_coverage || is_clang, + "Clang Source-based Code Coverage requires clang.") diff --git a/third_party/libwebrtc/build/config/crypto.gni b/third_party/libwebrtc/build/config/crypto.gni new file mode 100644 index 0000000000..4d2c01128d --- /dev/null +++ b/third_party/libwebrtc/build/config/crypto.gni @@ -0,0 +1,15 @@ +# 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. + +# True if NSS is used for certificate handling. +use_nss_certs = is_linux || is_chromeos diff --git a/third_party/libwebrtc/build/config/dcheck_always_on.gni b/third_party/libwebrtc/build/config/dcheck_always_on.gni new file mode 100644 index 0000000000..0e8443f907 --- /dev/null +++ b/third_party/libwebrtc/build/config/dcheck_always_on.gni @@ -0,0 +1,40 @@ +# Copyright (c) 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. + +# TODO(crbug.com/1233050): Until the bug is resolved we need to include +# gclient_args for the definition of build_with_chromium and build_overrides +# for client overrides of that flag. The latter should go away. +import("//build/config/gclient_args.gni") +import("//build_overrides/build.gni") +declare_args() { + # Enables DCHECKs to be built-in, but to default to being non-fatal/log-only. + # DCHECKS can then be set as fatal/non-fatal via the DCheckIsFatal feature. + # See https://bit.ly/dcheck-albatross for details on how this is used. + dcheck_is_configurable = false +} + +declare_args() { + # Set to false to disable DCHECK in Release builds. This is enabled by default + # for non-official builds on the below platforms. + # This default only affects Chromium as indicated by build_with_chromium. + # Other clients typically set this to false. If another client wants to use + # the same default value as Chromium, we'd need to add a separate gclient + # variable to replace build_with_chromium here. + # Note: If you are here to revert because DCHECKs are failing on a specific OS + # please prefer excluding OSes rather than reverting . I.e. if Mac builds + # break badly but other platforms are reasonably stable, add "&& !is_mac" + # instead of reverting. + dcheck_always_on = + (build_with_chromium && !is_official_build) || dcheck_is_configurable +} + +declare_args() { + # Set to false to disable EXPENSIVE_DCHECK()s. + # TODO(crbug.com/1225701): Hash out whether expensive DCHECKs need to be + # disabled for developers by default. There's concern that disabling these + # globally by default effectively reduces them to zero coverage. This is + # in place so that you can disable expensive DCHECKs while retaining some + # DCHECK coverage, which is especially important in user-facing builds. + enable_expensive_dchecks = is_debug || dcheck_always_on +} diff --git a/third_party/libwebrtc/build/config/devtools.gni b/third_party/libwebrtc/build/config/devtools.gni new file mode 100644 index 0000000000..7a6d2b2f06 --- /dev/null +++ b/third_party/libwebrtc/build/config/devtools.gni @@ -0,0 +1,37 @@ +# Copyright (c) 2021 The Chromium 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") +import("//build_overrides/build.gni") + +declare_args() { + if (build_with_chromium) { + # devtools_location is used in DevTools to resolve to the correct location + # for any script/file referenced in the DevTools build scripts. Since + # DevTools supports both a standalone build and build integration with + # Chromium, we need to differentiate between the two versions. + # devtools_location points to the Chromium version in both Chrome-branded + # and not Chrome-branded builds. devtools_root_location points to the root + # of the Chrome-branded version when is_chrome_branded is true and to the root + # of the Chromium version when is_chrome_branded is false. + # devtools_grd_location is the location of the GRD file listing all DevTools + # resources. + if (is_chrome_branded) { + devtools_root_location = "third_party/devtools-frontend-internal" + devtools_location = "$devtools_root_location/devtools-frontend/" + devtools_grd_location = + "$devtools_root_location/chrome_devtools_resources.grd" + } else { + devtools_root_location = "third_party/devtools-frontend/src" + devtools_location = "third_party/devtools-frontend/src/" + devtools_grd_location = + "$devtools_root_location/front_end/devtools_resources.grd" + } + } else { + # DevTools is building a standalone version + devtools_location = "" + devtools_root_location = "" + devtools_grd_location = "" + } +} diff --git a/third_party/libwebrtc/build/config/features.gni b/third_party/libwebrtc/build/config/features.gni new file mode 100644 index 0000000000..62bf4bcb35 --- /dev/null +++ b/third_party/libwebrtc/build/config/features.gni @@ -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. + +# ============================================= +# PLEASE DO NOT ADD MORE FLAGS TO THIS FILE +# ============================================= +# +# These flags are effectively global. Your feature flag should go near the +# code it controls. Most of these items are here now because they control +# legacy global #defines passed to the compiler (now replaced with generated +# buildflag headers -- see //build/buildflag_header.gni). +# +# There is more advice on where to put build flags in the "Build flag" section +# of //build/config/BUILDCONFIG.gn. + +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") + +declare_args() { + # Enables proprietary codecs and demuxers; e.g. H264, AAC, MP3, and MP4. + # We always build Google Chrome and Chromecast with proprietary codecs. + # + # Note: this flag is used by WebRTC which is DEPSed into Chrome. Moving it + # out of //build will require using the build_overrides directory. + proprietary_codecs = is_chrome_branded || is_chromecast + + # libudev usage. This currently only affects the content layer. + use_udev = (is_linux || is_chromeos) && !is_chromecast + + use_dbus = (is_linux || is_chromeos) && !is_chromecast + + use_gio = is_linux && !is_chromecast +} +# +# ============================================= +# PLEASE DO NOT ADD MORE FLAGS TO THIS FILE +# ============================================= +# +# See comment at the top. diff --git a/third_party/libwebrtc/build/config/freetype/BUILD.gn b/third_party/libwebrtc/build/config/freetype/BUILD.gn new file mode 100644 index 0000000000..76cb025fbb --- /dev/null +++ b/third_party/libwebrtc/build/config/freetype/BUILD.gn @@ -0,0 +1,14 @@ +# Copyright 2017 The Chromium 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/features.gni") +import("//build/config/freetype/freetype.gni") + +group("freetype") { + if (use_system_freetype) { + public_configs = [ "//build/linux:freetype_from_pkgconfig" ] + } else { + public_deps = [ "//third_party:freetype_harfbuzz" ] + } +} diff --git a/third_party/libwebrtc/build/config/freetype/OWNERS b/third_party/libwebrtc/build/config/freetype/OWNERS new file mode 100644 index 0000000000..3277f87312 --- /dev/null +++ b/third_party/libwebrtc/build/config/freetype/OWNERS @@ -0,0 +1,2 @@ +bungeman@chromium.org +drott@chromium.org diff --git a/third_party/libwebrtc/build/config/freetype/freetype.gni b/third_party/libwebrtc/build/config/freetype/freetype.gni new file mode 100644 index 0000000000..b4eced2d65 --- /dev/null +++ b/third_party/libwebrtc/build/config/freetype/freetype.gni @@ -0,0 +1,14 @@ +# Copyright 2017 The Chromium 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() { + # Blink needs a recent and properly build-configured FreeType version to + # support OpenType variations, color emoji and avoid security bugs. By default + # we ship and link such a version as part of Chrome. For distributions that + # prefer to keep linking to the version the system, FreeType must be newer + # than version 2.7.1 and have color bitmap support compiled in. WARNING: + # System FreeType configurations other than as described WILL INTRODUCE TEXT + # RENDERING AND SECURITY REGRESSIONS. + use_system_freetype = false +} diff --git a/third_party/libwebrtc/build/config/fuchsia/BUILD.gn b/third_party/libwebrtc/build/config/fuchsia/BUILD.gn new file mode 100644 index 0000000000..88922a11e8 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/BUILD.gn @@ -0,0 +1,30 @@ +# Copyright 2017 The Chromium 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/chromecast_build.gni") + +assert(is_fuchsia) +assert(!is_posix) + +config("compiler") { + configs = [ "//third_party/fuchsia-sdk/sdk/build/config:compiler" ] + + # TODO(https://crbug.com/706592): The stack defaults to 256k on Fuchsia (see + # https://fuchsia.googlesource.com/zircon/+/master/system/private/zircon/stack.h#9), + # but on other platforms it's much higher, so a variety of code assumes more + # will be available. Raise to 8M which matches e.g. macOS. + ldflags = [ "-Wl,-z,stack-size=0x800000" ] + + # Allow this in chromium-only builds, but do not allow this in Chromecast + # builds. + if (!is_chromecast) { + cflags_cc = [ "-fexperimental-relative-c++-abi-vtables" ] + ldflags += [ "-fexperimental-relative-c++-abi-vtables" ] + } +} + +# Settings for executables. +config("executable_config") { + ldflags = [ "-pie" ] +} diff --git a/third_party/libwebrtc/build/config/fuchsia/DIR_METADATA b/third_party/libwebrtc/build/config/fuchsia/DIR_METADATA new file mode 100644 index 0000000000..6d8f079aa5 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/DIR_METADATA @@ -0,0 +1,7 @@ +monorail { + component: "Fuchsia" +} + +team_email: "cr-fuchsia@chromium.org" + +os: FUCHSIA diff --git a/third_party/libwebrtc/build/config/fuchsia/OWNERS b/third_party/libwebrtc/build/config/fuchsia/OWNERS new file mode 100644 index 0000000000..3a1056b296 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/OWNERS @@ -0,0 +1,4 @@ +file://build/fuchsia/OWNERS + +per-file *.cmx=set noparent +per-file *.cmx=file://fuchsia/SECURITY_OWNERS diff --git a/third_party/libwebrtc/build/config/fuchsia/build_cmx_from_fragment.py b/third_party/libwebrtc/build/config/fuchsia/build_cmx_from_fragment.py new file mode 100644 index 0000000000..ac7e34988d --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/build_cmx_from_fragment.py @@ -0,0 +1,49 @@ +# Copyright 2020 The Chromium 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 complete CMX (v1) component manifest, from a program name and + manifest fragment file.""" + +import argparse +import json +import sys + + +def BuildCmxFromFragment(output_file, fragment_file, program_binary): + """Reads a CMX fragment specifying e.g. features & sandbox, and a program + binary's filename, and writes out the full CMX. + + output_file: Build-relative filename at which to write the full CMX. + fragment_file: Build-relative filename of the CMX fragment to read from. + program_binary: Package-relative filename of the program binary. + """ + + with open(output_file, 'w') as component_manifest_file: + component_manifest = json.load(open(fragment_file, 'r')) + component_manifest.update({ + 'program': { + 'binary': program_binary + }, + }) + json.dump(component_manifest, component_manifest_file) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--cmx-fragment', + required=True, + help='Path to the CMX fragment to read from') + parser.add_argument( + '--cmx', required=True, help='Path to write the complete CMX file to') + parser.add_argument( + '--program', + required=True, + help='Package-relative path to the program binary') + args = parser.parse_args() + + return BuildCmxFromFragment(args.cmx, args.cmx_fragment, args.program) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/config/fuchsia/build_symbol_archive.py b/third_party/libwebrtc/build/config/fuchsia/build_symbol_archive.py new file mode 100755 index 0000000000..c763627bf9 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/build_symbol_archive.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# Copyright 2018 The Chromium 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 compressed archive of unstripped binaries cataloged by +"ids.txt".""" + +import argparse +import os +import subprocess +import sys +import tarfile + + +def main(args): + parser = argparse.ArgumentParser() + parser.add_argument('ids_txt', type=str, nargs=1, + help='Path to ids.txt files.') + parser.add_argument('-o', '--output_tarball', nargs=1, type=str, + help='Path which the tarball will be written to.') + parser.add_argument('--fuchsia-build-id-dir', type=str, required=True, + help='Directory containing symbols for SDK prebuilts.') + args = parser.parse_args(args) + + ids_txt = args.ids_txt[0] + build_ids_archive = tarfile.open(args.output_tarball[0], 'w:bz2') + for line in open(ids_txt, 'r'): + build_id, binary_path = line.strip().split(' ') + + # Look for prebuilt symbols in the SDK first. + symbol_source_path = os.path.join(args.fuchsia_build_id_dir, + build_id[:2], + build_id[2:] + '.debug') + if not os.path.exists(symbol_source_path): + symbol_source_path = os.path.abspath( + os.path.join(os.path.dirname(ids_txt), binary_path)) + + if os.path.getsize(symbol_source_path) == 0: + # This is a prebuilt which wasn't accompanied by SDK symbols. + continue + + # Exclude stripped binaries (indicated by their lack of symbol tables). + readelf_output = subprocess.check_output( + ['readelf', '-S', symbol_source_path], universal_newlines=True) + if not '.symtab' in readelf_output: + continue + + # Archive the unstripped ELF binary, placing it in a hierarchy keyed to the + # GNU build ID. The binary resides in a directory whose name is the first + # two characters of the build ID, with the binary file itself named after + # the remaining characters of the build ID. So, a binary file with the build + # ID "deadbeef" would be located at the path 'de/adbeef.debug'. + build_ids_archive.add(symbol_source_path, + '%s/%s.debug' % (build_id[:2], build_id[2:])) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/config/fuchsia/config.gni b/third_party/libwebrtc/build/config/fuchsia/config.gni new file mode 100644 index 0000000000..5a00cc5a85 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/config.gni @@ -0,0 +1,11 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +assert(is_fuchsia) + +# Compute the AEMU path. +aemu_root = "//third_party/aemu-${host_os}-${target_cpu}" + +# Compute the path to the arch-specific boot image directory. +boot_image_root = "//third_party/fuchsia-sdk/images/${target_cpu}" diff --git a/third_party/libwebrtc/build/config/fuchsia/extend_fvm.py b/third_party/libwebrtc/build/config/fuchsia/extend_fvm.py new file mode 100644 index 0000000000..44e5ee30e1 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/extend_fvm.py @@ -0,0 +1,26 @@ +# Copyright 2018 The Chromium 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 FVM file and extends it by a specified amount. + +Arg #1: path to 'fvm'. + #2: the path to the source fvm.blk. + #3: the path that the extended FVM file will be written to. + #4: the additional number of bytes to grow fvm.blk by.""" + +import os +import shutil +import subprocess +import sys + +def ExtendFVM(fvm_tool_path, src_path, dest_path, delta): + old_size = os.path.getsize(src_path) + new_size = old_size + int(delta) + shutil.copyfile(src_path, dest_path) + subprocess.check_call([fvm_tool_path, dest_path, 'extend', '--length', + str(new_size)]) + return 0 + +if __name__ == '__main__': + sys.exit(ExtendFVM(*sys.argv[1:])) diff --git a/third_party/libwebrtc/build/config/fuchsia/generate_runner_scripts.gni b/third_party/libwebrtc/build/config/fuchsia/generate_runner_scripts.gni new file mode 100644 index 0000000000..3349b2b869 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/generate_runner_scripts.gni @@ -0,0 +1,284 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +assert(is_fuchsia) + +import("//build/config/chromecast_build.gni") +import("//build/config/fuchsia/config.gni") +import("//build/config/fuchsia/package.gni") +import("//build/config/gclient_args.gni") +import("//build/config/sysroot.gni") +import("//build/util/generate_wrapper.gni") + +declare_args() { + # Sets the Fuchsia Amber repository which will be used by default by the + # generated installation scripts. If not specified, then no default directory + # will be used. + default_fuchsia_build_dir_for_installation = "" + + # Sets the Fuchsia device node name which will be used by default by the + # generated runner scripts. If not specficed, then no default node name will + # be used. + default_fuchsia_device_node_name = "" + + # CPU architecture of the host used to run the tests. + test_host_cpu = host_cpu + + # Sets whether emulators need to be included in the test isolates + test_isolate_uses_emulator = true + + # A list of additional Fuchsia boot images to include in the test isolates. + fuchsia_additional_boot_images = [] +} + +# Generates a script which deploys and optionally executes a package on a +# device. +# +# Parameters: +# package: The package() target which will be run. +# package_name_override: Specifies the name of the generated package, if its +# name is different than the |package| target name. This value must match +# package_name_override in the |package| target. +# package_deps: An array of [package, package_name_override] array pairs +# which specify additional dependency packages to be installed +# prior to execution. +# runner_script: The runner script implementation to use, relative to +# "build/fuchsia". Defaults to "test_runner.py". +# install_only: If true, executing the script will only install the package +# on the device, but not run it. +# is_test_exe: If true, the generated script will run the command under +# test_env.py and add arguments expected to be passed to test exes. +template("fuchsia_package_runner") { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "runner_script" ]) + + if (defined(invoker.package_name_override)) { + _pkg_shortname = invoker.package_name_override + } else { + _pkg_shortname = get_label_info(invoker.package, "name") + } + + _pkg_dir = "$root_out_dir/gen/" + get_label_info(invoker.package, "dir") + + "/" + _pkg_shortname + _package_path = "$_pkg_dir/${_pkg_shortname}.far" + + generated_run_pkg_script_path = "$root_build_dir/bin/run_${_pkg_shortname}" + generated_install_pkg_script_path = + "$root_build_dir/bin/install_$_pkg_shortname" + + _generate_runner_target = "${target_name}__generate_runner" + _generate_installer_target = "${target_name}__generate_installer" + + # Generates a script which installs and runs a test. + generate_wrapper(_generate_runner_target) { + forward_variables_from(invoker, [ "target" ]) + + _is_test_exe = defined(invoker.is_test_exe) && invoker.is_test_exe + + if (defined(runner_script)) { + _runner_script = runner_script + } else { + _runner_script = "//build/fuchsia/test_runner.py" + } + + if (_is_test_exe) { + executable = "//testing/test_env.py" + executable_args = + [ "@WrappedPath(" + rebase_path(_runner_script, root_out_dir) + ")" ] + data = [ + _runner_script, + "//.vpython", + ] + data_deps = [ "//testing:test_scripts_shared" ] + } else { + executable = rebase_path(_runner_script) + executable_args = [] + data = [] + data_deps = [] + } + + if (defined(invoker.data)) { + data += invoker.data + } + + wrapper_script = generated_run_pkg_script_path + + data_deps += [ + invoker.package, + + # Runner scripts require access to "ids.txt" for symbolization, and to + # the "package" from which to get the name & version to deploy, which + # are outputs of the archive manifest generation action. + "${invoker.package}__archive-manifest", + + # Runner scripts require access to "meta.far" from which to calculate the + # expected Merkle root for the package, to verify it has been cached. + "${invoker.package}__archive-metadata", + ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + + # Declares the files that are needed for test execution on the + # swarming test client. + data += [ + "//build/fuchsia/", + "//build/util/lib/", + "//third_party/fuchsia-sdk/sdk/.build-id/", + "//third_party/fuchsia-sdk/sdk/bin/fpave.sh", + "//third_party/fuchsia-sdk/sdk/bin/fuchsia-common.sh", + "//third_party/fuchsia-sdk/sdk/meta/manifest.json", + ] + + # TODO(crbug.com/1137662): Remove checkout_fuchsia_for_arm64_host from + # gclient_gn_args in //DEPS as well as this condition when builders have + # test_host_cpu set correctly. + if (checkout_fuchsia_for_arm64_host) { + test_host_cpu = "arm64" + } + + if (test_host_cpu == "x64") { + data_deps += + [ "//build/config/clang:llvm-symbolizer_data($host_toolchain)" ] + } + + data += [ + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/device-finder", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/ffx", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/fvm", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/merkleroot", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/pm", + + # TODO(crbug.com/1162314) Remove "symbolize" when transition to + # "symbolizer" is complete. + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/symbolize", + + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/symbolizer", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/zbi", + ] + + if (test_isolate_uses_emulator) { + data += [ + "${boot_image_root}/qemu/qemu-kernel.kernel", + "${boot_image_root}/qemu/storage-full.blk", + "${boot_image_root}/qemu/zircon-a.zbi", + "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/fvdl", + "//third_party/qemu-${host_os}-${test_host_cpu}/", + "${aemu_root}/", + ] + } + + foreach(fuchsia_additional_boot_image, fuchsia_additional_boot_images) { + data += [ "${fuchsia_additional_boot_image}/" ] + } + + package_paths = [ rebase_path(_package_path, root_build_dir) ] + if (defined(invoker.package_deps)) { + foreach(package_dep, invoker.package_deps) { + package_dep_target = package_dep[0] + package_dep_name = package_dep[1] + + data_deps += [ + package_dep_target, + package_dep_target + "__archive-manifest", + package_dep_target + "__archive-metadata", + ] + package_dep_path = rebase_path( + get_label_info(package_dep_target, "target_gen_dir") + "/" + + package_dep_name + "/" + package_dep_name + ".far", + root_build_dir) + package_paths += [ package_dep_path ] + } + } + + foreach(package_path, package_paths) { + executable_args += [ + "--package", + "@WrappedPath(${package_path})", + ] + } + + executable_args += [ + "--out-dir", + "@WrappedPath(.)", + "--target-cpu", + target_cpu, + "--package-name", + _pkg_shortname, + ] + + if (defined(invoker.use_run_test_component) && + invoker.use_run_test_component) { + executable_args += [ "--use-run-test-component" ] + } + + if (defined(invoker.use_test_server) && invoker.use_test_server) { + executable_args += [ "--enable-test-server" ] + } + + if (default_fuchsia_build_dir_for_installation != "") { + executable_args += [ + "--fuchsia-out-dir", + default_fuchsia_build_dir_for_installation, + ] + } + + if (default_fuchsia_device_node_name != "") { + executable_args += [ + "--node-name", + default_fuchsia_device_node_name, + ] + } + } + + # Produces a script which installs a package and its dependencies into the + # package repository of a pre-existing Fuchsia build directory. + generate_wrapper(_generate_installer_target) { + executable = rebase_path("//build/fuchsia/deploy_to_pkg_repo.py") + wrapper_script = generated_install_pkg_script_path + + data_deps = [ invoker.package ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + + # Build a list of all packages to install, and pass the list to the runner + # script. + package_paths = [ rebase_path(_package_path, root_build_dir) ] + if (defined(invoker.package_deps)) { + foreach(package_dep, invoker.package_deps) { + package_dep_target = package_dep[0] + package_dep_name = package_dep[1] + + data_deps += [ package_dep_target ] + package_dep_path = rebase_path( + get_label_info(package_dep_target, "target_gen_dir") + "/" + + package_dep_name + "/" + package_dep_name + ".far", + root_build_dir) + package_paths += [ package_dep_path ] + } + } + executable_args = [] + foreach(package_path, package_paths) { + executable_args += [ + "--package", + "@WrappedPath(${package_path})", + ] + + if (default_fuchsia_build_dir_for_installation != "") { + executable_args += [ + "--fuchsia-out-dir", + default_fuchsia_build_dir_for_installation, + ] + } + } + } + + group(target_name) { + deps = [ ":${_generate_installer_target}" ] + + if (!defined(invoker.install_only) || invoker.install_only == false) { + deps += [ ":${_generate_runner_target}" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/gfx_tests.cmx b/third_party/libwebrtc/build/config/fuchsia/gfx_tests.cmx new file mode 100644 index 0000000000..0786fb0b98 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/gfx_tests.cmx @@ -0,0 +1,31 @@ +{ + "sandbox": { + "features": [ + "deprecated-ambient-replace-as-executable", + "isolated-persistent-storage", + "isolated-temp", + "vulkan" + ], + "dev": [ + "null", + "zero" + ], + "services": [ + "fuchsia.accessibility.semantics.SemanticsManager", + "fuchsia.device.NameProvider", + "fuchsia.fonts.Provider", + "fuchsia.intl.PropertyProvider", + "fuchsia.logger.LogSink", + "fuchsia.media.ProfileProvider", + "fuchsia.memorypressure.Provider", + "fuchsia.process.Launcher", + "fuchsia.sys.Environment", + "fuchsia.sys.Loader", + "fuchsia.sysmem.Allocator", + "fuchsia.tracing.provider.Registry", + "fuchsia.ui.policy.Presenter", + "fuchsia.ui.scenic.Scenic", + "fuchsia.vulkan.loader.Loader" + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/package.gni b/third_party/libwebrtc/build/config/fuchsia/package.gni new file mode 100644 index 0000000000..ff6ffd0a3d --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/package.gni @@ -0,0 +1,114 @@ +# Copyright 2018 The Chromium 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("//third_party/fuchsia-sdk/sdk/build/component.gni") +import("//third_party/fuchsia-sdk/sdk/build/package.gni") + +# DEPRECATED: Use the Fuchsia SDK's fuchsia_component() and fuchsia_package() +# templates directly, in new code. +# +# Creates a Fuchsia .far package file containing a Fuchsia component. +# +# Parameters are: +# package_name_override: Specifies the name of the package to generate, +# if different than |target_name|. +# binary: The executable target which should be launched. +# manifest: A path to the manifest that will be used. +# "testonly" targets default to using +# //build/config/fuchsia/tests-with-exec.cmx. +# Non-test targets must explicitly specify a |manifest|. +# additional_manifests: Manifest files that should be included in the package in +# the /meta directory. This allows to package more than one component per +# manifest. These manifest files must specify program/binary to run, which +# is not required for the main manifest file where this parameter is added +# during build. +# component_name_override: If set, specifies the name of the component. +# By default, the component name is the same as the package name. +# deps: Additional targets to build and include in the package (optional). +# +# TODO(https://crbug.com/1050703): Migrate consumers to GN SDK equivalents. +template("cr_fuchsia_package") { + assert(defined(invoker.binary)) + + if (defined(invoker.package_name_override)) { + _package_name = invoker.package_name_override + } else { + _package_name = invoker.target_name + } + + _package_contents = [ invoker.binary ] + if (defined(invoker.deps)) { + _package_contents += invoker.deps + } + + _component_cmx_target = target_name + "__cr-component-cmx" + _component_target = target_name + "__cr-component" + _package_components = [ ":${_component_target}" ] + _component_manifest = "${target_gen_dir}/${target_name}.cmx" + + # Process the CMX fragment in |manifest| to get a full manifest. + action(_component_cmx_target) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + + script = "//build/config/fuchsia/build_cmx_from_fragment.py" + + inputs = [ invoker.manifest ] + outputs = [ _component_manifest ] + + args = [ + "--cmx-fragment", + rebase_path(invoker.manifest), + "--cmx", + rebase_path(_component_manifest), + "--program", + get_label_info(invoker.binary, "name"), + ] + } + + # Declare the primary component for this package. + fuchsia_component(_component_target) { + forward_variables_from(invoker, [ "testonly" ]) + + deps = [ ":${_component_cmx_target}" ] + data_deps = _package_contents + manifest = _component_manifest + + if (defined(invoker.component_name_override)) { + manifest_output_name = "${invoker.component_name_override}" + } else { + manifest_output_name = "${_package_name}" + } + } + + # Bundle manifests providing additional entrypoints into the package. + if (defined(invoker.additional_manifests)) { + foreach(filename, invoker.additional_manifests) { + _additional_component_target = + target_name + "_" + get_path_info(filename, "name") + _package_components += [ ":${_additional_component_target}" ] + fuchsia_component(_additional_component_target) { + forward_variables_from(invoker, [ "testonly" ]) + data_deps = _package_contents + manifest = filename + + # Depend upon the invoker's |deps|, in case they include a dependency + # responsible for generating this additional component's manifest file. + deps = _package_contents + } + } + } + + fuchsia_package(target_name) { + forward_variables_from(invoker, [ "testonly" ]) + package_name = _package_name + if (defined(invoker.excluded_files)) { + excluded_files = invoker.excluded_files + } + deps = _package_components + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/rules.gni b/third_party/libwebrtc/build/config/fuchsia/rules.gni new file mode 100644 index 0000000000..689e130b65 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/rules.gni @@ -0,0 +1,5 @@ +# Copyright 2019 The Chromium 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/fuchsia/generate_runner_scripts.gni") diff --git a/third_party/libwebrtc/build/config/fuchsia/sizes.gni b/third_party/libwebrtc/build/config/fuchsia/sizes.gni new file mode 100644 index 0000000000..20a5bf831e --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/sizes.gni @@ -0,0 +1,46 @@ +# Copyright 2020 The Chromium 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/util/generate_wrapper.gni") + +template("compute_fuchsia_package_sizes") { + generate_wrapper(target_name) { + forward_variables_from(invoker, + [ + "data", + "data_deps", + ]) + testonly = true + executable = "//build/fuchsia/binary_sizes.py" + wrapper_script = "$root_out_dir/bin/run_${target_name}" + + assert(target_cpu == "arm64" || target_cpu == "x64", + "target_cpu must be arm64 or x64") + + if (!defined(data)) { + data = [] + } + + if (!defined(data_deps)) { + data_deps = [] + } + + # Declares the files that are needed for test execution on the + # swarming test client. + data += [ + "//build/fuchsia/", + "//fuchsia/release/size_tests/", + "//third_party/fuchsia-sdk/sdk/arch/", + "//third_party/fuchsia-sdk/sdk/tools/${target_cpu}/", + ] + + executable_args = [ + "--output-directory", + "@WrappedPath(.)", + ] + if (defined(invoker.executable_args)) { + executable_args += invoker.executable_args + } + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/symbol_archive.gni b/third_party/libwebrtc/build/config/fuchsia/symbol_archive.gni new file mode 100644 index 0000000000..9dcb53cae0 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/symbol_archive.gni @@ -0,0 +1,47 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +assert(is_fuchsia) + +# Creates a tarball of unstripped binaries, structured according to the +# ".build_ids" convention used by the symbolizer and GNU GDB. +# +# Parameters: +# deps: Must all be cr_fuchsia_package() or fuchsia_package() targets. +# ids_txt: The "ids.txt" file which lists the relative paths to unstripped +# executables and libraries, along with their build IDs. +# archive_name: The path to the compressed tarball that will be generated. +template("symbol_archive") { + assert(!is_debug) + + action(target_name) { + _ids_txt = invoker.ids_txt + _build_ids = invoker.archive_name + + script = "//build/config/fuchsia/build_symbol_archive.py" + + inputs = [ _ids_txt ] + + outputs = [ _build_ids ] + + # For each package in |deps| it is necessary to additionally depend upon + # the corresponding archive-manifest target, which is what creates the + # ids.txt file. + deps = [] + foreach(package, invoker.deps) { + deps += [ + package, + package + "__archive-manifest", + ] + } + + args = [ + rebase_path(_ids_txt), + "-o", + rebase_path(_build_ids), + "--fuchsia-build-id-dir", + rebase_path("//third_party/fuchsia-sdk/sdk/.build-id"), + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/test/OWNERS b/third_party/libwebrtc/build/config/fuchsia/test/OWNERS new file mode 100644 index 0000000000..3be17de545 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/OWNERS @@ -0,0 +1,7 @@ +file://build/fuchsia/OWNERS + +per-file *.test-cmx=set noparent +per-file *.test-cmx=ddorwin@chromium.org +per-file *.test-cmx=wez@chromium.org +# Please prefer the above when possible. +per-file *.test-cmx=file://fuchsia/SECURITY_OWNERS diff --git a/third_party/libwebrtc/build/config/fuchsia/test/README.md b/third_party/libwebrtc/build/config/fuchsia/test/README.md new file mode 100644 index 0000000000..8427f4a49f --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/README.md @@ -0,0 +1,61 @@ +## CMX Fragments + +This directory contains the cmx fragments that are required for running +Fuchsia tests hermetically. Tests start from `minimum_capabilities.test-cmx` +and add additional capabilities as necessary by providing the +`additional_manifest_fragments` argument. Some fragments are explained in detail +below: + +### General Purpose Fragments + +#### font_capabilities.test-cmx +For tests that test fonts by providing `fuchsia.fonts.Provider`. + +#### jit_capabilities.test-cmx +Required by tests that execute JavaScript. Should only be required in a small +number of tests. + +#### minimum_capabilites.test-cmx +Capabilities required by anything that uses `//base/test`, used as the base +fragment for all test suites. + +#### read_debug_data.test-cmx +Required by tests that need access to its debug directory. Should only be +required in a small number of tests. + +#### test_logger_capabilities.test-cmx +For tests that test logging functionality by providing `fuchsia.logger.Log`. + +### WebEngine Fragments +The following fragments are specific to WebEngine functionality as documented +documentation at +https://fuchsia.dev/reference/fidl/fuchsia.web#CreateContextParams and +https://fuchsia.dev/reference/fidl/fuchsia.web#ContextFeatureFlags. +Any test-specific exceptions are documented for each file. + +#### audio_capabilities.test-cmx +Corresponds to the `AUDIO` flag. Required for enabling audio input and output. + +#### network_capabilities.test-cmx +Corresponds to the `NETWORK` flag. Required for enabling network access. Note +that access to the root SSL certificates is not needed if ContextProvider is +used to launch the `Context`. The `fuchsia.device.NameProvider` dependency comes +from fdio. + +#### present_view_capabilities.test-cmx +Services that are needed to render web content in a Scenic view and present it. +Most services are required per the FIDL documentation. +`fuchsia.ui.policy.Presenter` is additionally required by tests that create +views. + +#### vulkan_capabilities.test-cmx +Corresponds to the `VULKAN` flag. Required for enabling GPU-accelerated +rendering of the web content. + +#### web_engine_required_capabilities.test-cmx +Contains services that need to be present when creating a +`fuchsia.web.Context`. Note that the `fuchsia.scheduler.ProfileProvider` service +is only used in tests that encounter memory pressure code. + +#### web_instance_host_capabilities.test-cmx +Contains services that need to be present to use `WebInstanceHost`. \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/audio_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/audio_capabilities.test-cmx new file mode 100644 index 0000000000..2e2013f9ea --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/audio_capabilities.test-cmx @@ -0,0 +1,18 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.mediacodec.CodecFactory": "fuchsia-pkg://fuchsia.com/codec_factory#meta/codec_factory.cmx" + }, + "system-services": [ + "fuchsia.media.Audio" + ] + } + }, + "sandbox": { + "services": [ + "fuchsia.media.Audio", + "fuchsia.mediacodec.CodecFactory" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/font_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/font_capabilities.test-cmx new file mode 100644 index 0000000000..4c8661bb80 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/font_capabilities.test-cmx @@ -0,0 +1,14 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx", + } + } + }, + "sandbox": { + "services": [ + "fuchsia.fonts.Provider" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/jit_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/jit_capabilities.test-cmx new file mode 100644 index 0000000000..ff70e256be --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/jit_capabilities.test-cmx @@ -0,0 +1,7 @@ +{ + "sandbox": { + "features": [ + "deprecated-ambient-replace-as-executable" + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/test/minimum_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/minimum_capabilities.test-cmx new file mode 100644 index 0000000000..5b9f7bf705 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/minimum_capabilities.test-cmx @@ -0,0 +1,30 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.intl.PropertyProvider": "fuchsia-pkg://fuchsia.com/intl_property_manager#meta/intl_property_manager.cmx" + }, + "system-services": [ + "fuchsia.boot.ReadOnlyLog" + ] + } + }, + "sandbox": { + "dev": [ + "null", + "zero" + ], + "features": [ + "isolated-persistent-storage", + "isolated-temp" + ], + "services": [ + "fuchsia.intl.PropertyProvider", + "fuchsia.logger.LogSink", + "fuchsia.media.ProfileProvider", + "fuchsia.process.Launcher", + "fuchsia.sys.Launcher", + "fuchsia.sys.Loader" + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/test/network_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/network_capabilities.test-cmx new file mode 100644 index 0000000000..7218c3f875 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/network_capabilities.test-cmx @@ -0,0 +1,25 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.net.interfaces.State": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx", + "fuchsia.net.name.Lookup": "fuchsia-pkg://fuchsia.com/dns-resolver#meta/dns-resolver.cmx", + "fuchsia.posix.socket.Provider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx" + }, + "system-services": [ + "fuchsia.device.NameProvider" + ] + } + }, + "sandbox": { + "features": [ + "root-ssl-certificates" + ], + "services": [ + "fuchsia.device.NameProvider", + "fuchsia.net.name.Lookup", + "fuchsia.net.interfaces.State", + "fuchsia.posix.socket.Provider" + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/test/present_view_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/present_view_capabilities.test-cmx new file mode 100644 index 0000000000..201c8b2212 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/present_view_capabilities.test-cmx @@ -0,0 +1,24 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.accessibility.semantics.SemanticsManager": "fuchsia-pkg://fuchsia.com/a11y-manager#meta/a11y-manager.cmx", + "fuchsia.ui.input3.Keyboard": "fuchsia-pkg://fuchsia.com/ime_service#meta/ime_service.cmx", + }, + "system-services": [ + "fuchsia.sysmem.Allocator", + "fuchsia.ui.policy.Presenter", + "fuchsia.ui.scenic.Scenic" + ] + } + }, + "sandbox": { + "services": [ + "fuchsia.accessibility.semantics.SemanticsManager", + "fuchsia.sysmem.Allocator", + "fuchsia.ui.input3.Keyboard", + "fuchsia.ui.policy.Presenter", + "fuchsia.ui.scenic.Scenic" + ] + } +} diff --git a/third_party/libwebrtc/build/config/fuchsia/test/read_debug_data.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/read_debug_data.test-cmx new file mode 100644 index 0000000000..b0c95b0883 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/read_debug_data.test-cmx @@ -0,0 +1,7 @@ +{ + "sandbox": { + "features": [ + "hub" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/test_logger_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/test_logger_capabilities.test-cmx new file mode 100644 index 0000000000..68b2a67012 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/test_logger_capabilities.test-cmx @@ -0,0 +1,7 @@ +{ + "sandbox": { + "services": [ + "fuchsia.logger.Log" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/vulkan_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/vulkan_capabilities.test-cmx new file mode 100644 index 0000000000..0436ffd5e3 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/vulkan_capabilities.test-cmx @@ -0,0 +1,19 @@ +{ + "facets": { + "fuchsia.test": { + "system-services": [ + "fuchsia.sysmem.Allocator", + "fuchsia.vulkan.loader.Loader" + ] + } + }, + "sandbox": { + "features": [ + "vulkan" + ], + "services": [ + "fuchsia.sysmem.Allocator", + "fuchsia.vulkan.loader.Loader" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/web_engine_required_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/web_engine_required_capabilities.test-cmx new file mode 100644 index 0000000000..4cb61fe7f1 --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/web_engine_required_capabilities.test-cmx @@ -0,0 +1,25 @@ +{ + "facets": { + "fuchsia.test": { + "injected-services": { + "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx", + "fuchsia.memorypressure.Provider": "fuchsia-pkg://fuchsia.com/memory_monitor#meta/memory_monitor.cmx", + "fuchsia.web.ContextProvider": "fuchsia-pkg://fuchsia.com/web_engine#meta/context_provider.cmx", + }, + "system-services": [ + "fuchsia.device.NameProvider", + "fuchsia.scheduler.ProfileProvider", + "fuchsia.sysmem.Allocator" + ] + } + }, + "sandbox": { + "services": [ + "fuchsia.device.NameProvider", + "fuchsia.fonts.Provider", + "fuchsia.memorypressure.Provider", + "fuchsia.sysmem.Allocator", + "fuchsia.web.ContextProvider" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/fuchsia/test/web_instance_host_capabilities.test-cmx b/third_party/libwebrtc/build/config/fuchsia/test/web_instance_host_capabilities.test-cmx new file mode 100644 index 0000000000..762916ba9f --- /dev/null +++ b/third_party/libwebrtc/build/config/fuchsia/test/web_instance_host_capabilities.test-cmx @@ -0,0 +1,8 @@ +{ + "sandbox": { + "services": [ + "fuchsia.sys.Environment", + "fuchsia.sys.Loader" + ] + } +} \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/gcc/BUILD.gn b/third_party/libwebrtc/build/config/gcc/BUILD.gn new file mode 100644 index 0000000000..ff47e2542c --- /dev/null +++ b/third_party/libwebrtc/build/config/gcc/BUILD.gn @@ -0,0 +1,116 @@ +# 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/c++/c++.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/sysroot.gni") +import("//build/toolchain/toolchain.gni") + +declare_args() { + # When non empty, overrides the target rpath value. This allows a user to + # make a Chromium build where binaries and shared libraries are meant to be + # installed into separate directories, like /usr/bin/chromium and + # /usr/lib/chromium for instance. It is useful when a build system that + # generates a whole target root filesystem (like Yocto) is used on top of gn, + # especially when cross-compiling. + # Note: this gn arg is similar to gyp target_rpath generator flag. + gcc_target_rpath = "" + ldso_path = "" +} + +# 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 functions 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") { + cflags = [ "-fvisibility=hidden" ] + + # Visibility attribute is not supported on AIX. + if (current_os != "aix") { + cflags_cc = [ "-fvisibility-inlines-hidden" ] + cflags_objcc = cflags_cc + } +} + +# This config is usually set when :symbol_visibility_hidden is removed. +# It's often a good idea to set visibility explicitly, as there're flags +# which would error out otherwise (e.g. -fsanitize=cfi-unrelated-cast) +config("symbol_visibility_default") { + cflags = [ "-fvisibility=default" ] +} + +# The rpath is the dynamic library search path. Setting this config on a link +# step will put the directory where the build generates shared libraries into +# the rpath. +# +# This is required for component builds since the build generates many shared +# libraries in the build directory that we expect to be automatically loaded. +# It will be automatically applied in this case by :executable_config. +# +# In non-component builds, certain test binaries may expect to load dynamic +# libraries from the current directory. As long as these aren't distributed, +# this is OK. For these cases use something like this: +# +# if ((is_linux || is_chromeos) && !is_component_build) { +# configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ] +# } +config("rpath_for_built_shared_libraries") { + if (!is_android && current_os != "aix") { + # Note: Android, Aix don't support rpath. + if (current_toolchain != default_toolchain || gcc_target_rpath == "") { + ldflags = [ + # Want to pass "\$". GN will re-escape as required for ninja. + "-Wl,-rpath=\$ORIGIN", + ] + } else { + ldflags = [ "-Wl,-rpath=${gcc_target_rpath}" ] + } + if (current_toolchain == default_toolchain && ldso_path != "") { + ldflags += [ "-Wl,--dynamic-linker=${ldso_path}" ] + } + } +} + +if (is_component_build && !is_android) { + # See the rpath_for... config above for why this is necessary for component + # builds. + executable_and_shared_library_configs_ = + [ ":rpath_for_built_shared_libraries" ] +} else { + executable_and_shared_library_configs_ = [] +} + +# Settings for executables. +config("executable_config") { + configs = executable_and_shared_library_configs_ + ldflags = [ "-pie" ] + if (is_android) { + ldflags += [ + "-Bdynamic", + "-Wl,-z,nocopyreloc", + ] + } + + if (!is_android && current_os != "aix") { + ldflags += [ + # TODO(GYP): Do we need a check on the binutils version here? + # + # 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", + ] + } +} + +# Settings for shared libraries. +config("shared_library_config") { + configs = executable_and_shared_library_configs_ +} diff --git a/third_party/libwebrtc/build/config/gclient_args.gni b/third_party/libwebrtc/build/config/gclient_args.gni new file mode 100644 index 0000000000..fa77ecc6d4 --- /dev/null +++ b/third_party/libwebrtc/build/config/gclient_args.gni @@ -0,0 +1 @@ +# Generated from 'DEPS' \ No newline at end of file diff --git a/third_party/libwebrtc/build/config/get_host_byteorder.py b/third_party/libwebrtc/build/config/get_host_byteorder.py new file mode 100755 index 0000000000..fc01d85718 --- /dev/null +++ b/third_party/libwebrtc/build/config/get_host_byteorder.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Get Byteorder of host architecture""" + +from __future__ import print_function + +import sys + +print(sys.byteorder) diff --git a/third_party/libwebrtc/build/config/host_byteorder.gni b/third_party/libwebrtc/build/config/host_byteorder.gni new file mode 100644 index 0000000000..48a1a7f1e3 --- /dev/null +++ b/third_party/libwebrtc/build/config/host_byteorder.gni @@ -0,0 +1,27 @@ +# Copyright (c) 2017 The Chromium 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 "host_byteorder" variable. +# Not that this is currently used only for building v8. +# The chromium code generally assumes little-endianness. +declare_args() { + host_byteorder = "undefined" +} + +# Detect host byteorder +# ppc64 can be either BE or LE +if (host_cpu == "ppc64") { + if (current_os == "aix") { + host_byteorder = "big" + } else { + # Only use the script when absolutely necessary + host_byteorder = + exec_script("//build/config/get_host_byteorder.py", [], "trim string") + } +} else if (host_cpu == "ppc" || host_cpu == "s390" || host_cpu == "s390x" || + host_cpu == "mips" || host_cpu == "mips64") { + host_byteorder = "big" +} else { + host_byteorder = "little" +} diff --git a/third_party/libwebrtc/build/config/ios/BUILD.gn b/third_party/libwebrtc/build/config/ios/BUILD.gn new file mode 100644 index 0000000000..525c439e7e --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/BUILD.gn @@ -0,0 +1,264 @@ +# 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/ios/ios_sdk.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +declare_args() { + # Enabling this option makes clang compile to an intermediate + # representation ("bitcode"), and not to native code. This is preferred + # when including WebRTC in the apps that will be sent to Apple's App Store + # and mandatory for the apps that run on watchOS or tvOS. + # The option only works when building with Xcode (use_xcode_clang = true). + # Mimicking how Xcode handles it, the production builds (is_debug = false) + # get real bitcode sections added, while the debug builds (is_debug = true) + # only get bitcode-section "markers" added in them. + enable_ios_bitcode = false +} + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic. +config("compiler") { + # These flags are shared between the C compiler and linker. + common_flags = [] + + # CPU architecture. + if (target_cpu == "x64") { + triplet_cpu = "x86_64" + } else if (target_cpu == "x86") { + triplet_cpu = "i386" + } else if (target_cpu == "arm" || target_cpu == "armv7") { + triplet_cpu = "armv7" + } else if (target_cpu == "arm64") { + triplet_cpu = "arm64" + } else { + assert(false, "unsupported cpu: $target_cpu") + } + + # Environment. + if (target_environment == "simulator") { + triplet_environment = "-simulator" + } else if (target_environment == "device") { + triplet_environment = "" + } else if (target_environment == "catalyst") { + triplet_environment = "-macabi" + } else { + assert(false, "unsupported environment: $target_environment") + } + + # OS. + triplet_os = "apple-ios" + + # Set target. + common_flags = [ + "-target", + "$triplet_cpu-$triplet_os$ios_deployment_target$triplet_environment", + ] + + # This is here so that all files get recompiled after an Xcode update. + # (defines are passed via the command line, and build system rebuild things + # when their commandline changes). Nothing should ever read this define. + defines = [ "CR_XCODE_VERSION=$xcode_version" ] + + asmflags = common_flags + cflags = common_flags + swiftflags = common_flags + + swiftflags += [ + "-swift-version", + "5", + ] + + # 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" ] + + ldflags = common_flags +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is iOS-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + # The variable ios_sdk_path is relative to root_build_dir when using Goma RBE + # and system Xcode (since Goma RBE only supports paths relative to source). + # Rebase the value in that case since gn does not convert paths in compiler + # flags (since it is not aware they are paths). + _sdk_root = ios_sdk_path + if (use_system_xcode && use_goma) { + _sdk_root = rebase_path(ios_sdk_path, root_build_dir) + } + + common_flags = [ + "-isysroot", + _sdk_root, + ] + swiftflags = [ + "-sdk", + _sdk_root, + ] + + if (target_environment == "catalyst") { + common_flags += [ + "-isystem", + "$_sdk_root/System/iOSSupport/usr/include", + "-iframework", + "$_sdk_root/System/iOSSupport/System/Library/Frameworks", + ] + } + + if (use_xcode_clang && enable_ios_bitcode) { + if (is_debug) { + common_flags += [ "-fembed-bitcode-marker" ] + } else { + common_flags += [ "-fembed-bitcode" ] + } + } + + asmflags = common_flags + cflags = common_flags + ldflags = common_flags +} + +config("ios_executable_flags") { + ldflags = [] + + # On "catalyst", the bundle structure is different (uses the same structure + # as a regular macOS app), so an additional -rpath is required. + if (target_environment == "catalyst") { + ldflags += [ "-Wl,-rpath,@loader_path/../Frameworks" ] + } + + ldflags += [ "-Wl,-rpath,@executable_path/Frameworks" ] +} + +config("ios_extension_executable_flags") { + configs = default_executable_configs + + ldflags = [ + "-e", + "_NSExtensionMain", + "-fapplication-extension", + ] + + # On "catalyst", the bundle structure is different (uses the same structure + # as a regular macOS app), so an additional -rpath is required. + if (target_environment == "catalyst") { + ldflags += [ "-Wl,-rpath,@loader_path/../../../../Frameworks" ] + } + + ldflags += [ "-Wl,-rpath,@executable_path/../../Frameworks" ] +} + +config("ios_dynamic_flags") { + ldflags = [ + # Always load Objective-C categories and class. + "-Wl,-ObjC", + ] + + # The path to the Swift compatibility libraries (required to run code built + # with version N of the SDK on older version of the OS) is relative to the + # toolchains directory and changes with the environment. + _swift_compatibility_libs_dir_prefix = "$ios_toolchains_path/usr/lib/swift" + if (target_environment == "simulator") { + _swift_compatibility_libs_dir = + "$_swift_compatibility_libs_dir_prefix/iphonesimulator" + } else if (target_environment == "device") { + _swift_compatibility_libs_dir = + "$_swift_compatibility_libs_dir_prefix/iphoneos" + } else if (target_environment == "catalyst") { + _swift_compatibility_libs_dir = + "$_swift_compatibility_libs_dir_prefix/maccatalyst" + } + + lib_dirs = [ + "$ios_sdk_path/usr/lib/swift", + _swift_compatibility_libs_dir, + ] +} + +config("ios_shared_library_flags") { + ldflags = [ + "-Wl,-rpath,@executable_path/Frameworks", + "-Wl,-rpath,@loader_path/Frameworks", + ] +} + +config("disable_implicit_retain_self_warning") { + cflags_objc = [ "-Wno-implicit-retain-self" ] + cflags_objcc = cflags_objc +} + +config("xctest_config") { + # Use -iframework for XCTest.framework so that the compiler will treat it as a + # system framework and ignore warnings in XCTest framework headers. + common_flags = [ + "-iframework", + rebase_path("$ios_sdk_platform_path/Developer/Library/Frameworks", + root_build_dir), + ] + cflags = common_flags + ldflags = common_flags + + frameworks = [ + "Foundation.framework", + "XCTest.framework", + ] +} + +group("xctest") { + public_configs = [ ":xctest_config" ] +} + +_xctrunner_path = + "$ios_sdk_platform_path/Developer/Library/Xcode/Agents/XCTRunner.app" + +# When building with Goma RBE, $ios_sdk_platform_path corresponds to a symlink +# below $root_build_dir that points to the real SDK to use. Because the files +# are below $root_build_dir, it is not possible to list them as a target input +# without gn complaining (as it can't find a target creating those files). +# +# The symlinks are created by //build/config/apple/sdk_info.py script invoked +# via exec_script() from //build/config/{ios/ios_sdk.gni,mac/mac_sdk.gni}. +# As the invocation is done by exec_script, there is no target that can list +# those files as output. +# +# To workaround this, add a target that pretends to create those files +# (but does nothing). See https://crbug.com/1061487 for why this is needed. +if (use_system_xcode && use_goma) { + action("copy_xctrunner_app") { + testonly = true + script = "//build/noop.py" + outputs = [ + "$_xctrunner_path/Info.plist", + "$_xctrunner_path/PkgInfo", + "$_xctrunner_path/XCTRunner", + ] + } +} + +# When creating the test runner for an XCUITest, the arm64e slice of the binary +# must be removed (at least until the app ships with arm64e slice which is not +# yet supported by Apple). +action("xctest_runner_without_arm64e") { + testonly = true + script = "//build/config/ios/strip_arm64e.py" + sources = [ "$_xctrunner_path/XCTRunner" ] + outputs = [ "$target_out_dir/XCTRunner" ] + args = [ + "--output", + rebase_path(outputs[0], root_build_dir), + "--input", + rebase_path(sources[0], root_build_dir), + "--xcode-version", + xcode_version, + ] + + if (use_system_xcode && use_goma) { + deps = [ ":copy_xctrunner_app" ] + } +} diff --git a/third_party/libwebrtc/build/config/ios/BuildInfo.plist b/third_party/libwebrtc/build/config/ios/BuildInfo.plist new file mode 100644 index 0000000000..3595e5aefb --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/BuildInfo.plist @@ -0,0 +1,35 @@ + + + + + BuildMachineOSBuild + ${BUILD_MACHINE_OS_BUILD} + CFBundleSupportedPlatforms + + ${IOS_SUPPORTED_PLATFORM} + + DTCompiler + ${GCC_VERSION} + DTPlatformName + ${IOS_PLATFORM_NAME} + DTPlatformVersion + ${IOS_PLATFORM_VERSION} + DTPlatformBuild + ${IOS_PLATFORM_BUILD} + DTSDKBuild + ${IOS_SDK_BUILD} + DTSDKName + ${IOS_SDK_NAME} + MinimumOSVersion + ${IOS_DEPLOYMENT_TARGET} + DTXcode + ${XCODE_VERSION} + DTXcodeBuild + ${XCODE_BUILD} + UIDeviceFamily + + 1 + 2 + + + diff --git a/third_party/libwebrtc/build/config/ios/Host-Info.plist b/third_party/libwebrtc/build/config/ios/Host-Info.plist new file mode 100644 index 0000000000..9f6f5deef9 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/Host-Info.plist @@ -0,0 +1,126 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${IOS_BUNDLE_ID_PREFIX}.test.${EXECUTABLE_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIRequiredDeviceCapabilities + + armv7 + + UILaunchImages + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 480} + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 568} + + + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {375, 667} + + + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {414, 736} + + + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageName + Default + UILaunchImageOrientation + Landscape + UILaunchImageSize + {414, 736} + + + UILaunchImages~ipad + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {768, 1024} + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Landscape + UILaunchImageSize + {768, 1024} + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/third_party/libwebrtc/build/config/ios/Module-Info.plist b/third_party/libwebrtc/build/config/ios/Module-Info.plist new file mode 100644 index 0000000000..d1bf77faf0 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/Module-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${IOS_BUNDLE_ID_PREFIX}.${MODULE_BUNDLE_ID:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + ${XCTEST_BUNDLE_PRINCIPAL_CLASS} + + diff --git a/third_party/libwebrtc/build/config/ios/OWNERS b/third_party/libwebrtc/build/config/ios/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/config/ios/asset_catalog.gni b/third_party/libwebrtc/build/config/ios/asset_catalog.gni new file mode 100644 index 0000000000..84dd92cce2 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/asset_catalog.gni @@ -0,0 +1,150 @@ +# Copyright 2017 The Chromium 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/ios/ios_sdk.gni") + +# This template declares a bundle_data target that references an asset +# catalog so that it is compiled to the asset catalog of the generated +# bundle. +# +# The create_bundle target requires that all asset catalogs are part of an +# .xcasset bundle. This requirement comes from actool that only receives +# the path to the .xcasset bundle directory and not to the individual +# .imageset directories. +# +# The requirement is a bit problematic as it prevents compiling only a +# subset of the asset catakig that are contained in a .xcasset. This template +# fixes that by instead copying the content of the asset catalog to temporary +# .xcasset directory (below $root_out_dir) and defining a bundle_data +# target that refers to those copies (this is efficient as the "copy" is +# implemented by hardlinking if possible on macOS). +# +# Since the create_data target will only refer to the .xcasset directory +# and additional "action" target that runs a dummy script is defined. It +# does nothing but pretends to generate the .xcassets directory (while +# it is really created as a side-effect of the "copy" step). This allows +# to workaround the check in "gn" that all inputs below $root_out_dir have +# to be outputs of another target with a public dependency path. +# +# This template also ensures that the file are only copied once when the +# build targets multiple architectures at the same time (aka "fat build"). +# +# Arguments +# +# sources: +# required, list of strings, paths to the file contained in the +# asset catalog directory; this must contain the Contents.json file +# and all the image referenced by it (not enforced by the template). +# +# asset_type: +# required, string, type of the asset catalog, that is the extension +# of the directory containing the images and the Contents.json file. +# +template("asset_catalog") { + assert(defined(invoker.sources) && invoker.sources != [], + "sources must be defined and not empty for $target_name") + + assert(defined(invoker.asset_type) && invoker.asset_type != "", + "asset_type must be defined and not empty for $target_name") + + if (is_fat_secondary_toolchain) { + group(target_name) { + public_deps = [ ":$target_name($primary_fat_toolchain_name)" ] + } + } else { + _copy_target_name = target_name + "__copy" + _data_target_name = target_name + + _sources = invoker.sources + _outputs = [] + + # The compilation of resources into Assets.car is enabled automatically + # by the "create_bundle" target if any of the "bundle_data" sources's + # path is in a .xcassets directory and matches one of the know asset + # catalog type. + _xcassets_dir = "$target_gen_dir/${target_name}.xcassets" + _output_dir = "$_xcassets_dir/" + + get_path_info(get_path_info(_sources[0], "dir"), "file") + + foreach(_source, invoker.sources) { + _dir = get_path_info(_source, "dir") + _outputs += [ "$_output_dir/" + get_path_info(_source, "file") ] + + assert(get_path_info(_dir, "extension") == invoker.asset_type, + "$_source dirname must have .${invoker.asset_type} extension") + } + + action(_copy_target_name) { + # Forward "deps", "public_deps" and "testonly" in case some of the + # source files are generated. + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + ]) + + script = "//build/config/ios/hardlink.py" + + visibility = [ ":$_data_target_name" ] + sources = _sources + outputs = _outputs + [ _xcassets_dir ] + + args = [ + rebase_path(get_path_info(_sources[0], "dir"), root_build_dir), + rebase_path(_output_dir, root_build_dir), + ] + } + + bundle_data(_data_target_name) { + forward_variables_from(invoker, + "*", + [ + "deps", + "outputs", + "public_deps", + "sources", + ]) + + sources = _outputs + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + public_deps = [ ":$_copy_target_name" ] + } + } +} + +# Those templates are specialisation of the asset_catalog template for known +# types of asset catalog types (imageset, launchimage, appiconset). +# +# Arguments +# +# sources: +# required, list of strings, paths to the file contained in the +# asset catalog directory; this must contain the Contents.json file +# and all the image referenced by it (not enforced by the template). +# +template("appiconset") { + asset_catalog(target_name) { + forward_variables_from(invoker, "*", [ "asset_type" ]) + asset_type = "appiconset" + } +} +template("colorset") { + asset_catalog(target_name) { + forward_variables_from(invoker, "*", [ "asset_type" ]) + asset_type = "colorset" + } +} +template("imageset") { + asset_catalog(target_name) { + forward_variables_from(invoker, "*", [ "asset_type" ]) + asset_type = "imageset" + } +} +template("launchimage") { + asset_catalog(target_name) { + forward_variables_from(invoker, "*", [ "asset_type" ]) + asset_type = "launchimage" + } +} diff --git a/third_party/libwebrtc/build/config/ios/codesign.py b/third_party/libwebrtc/build/config/ios/codesign.py new file mode 100644 index 0000000000..15d25a78df --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/codesign.py @@ -0,0 +1,691 @@ +# 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. + +from __future__ import print_function + +import argparse +import codecs +import datetime +import fnmatch +import glob +import json +import os +import plistlib +import shutil +import subprocess +import sys +import tempfile + +if sys.version_info.major < 3: + basestring_compat = basestring +else: + basestring_compat = str + + +def GetProvisioningProfilesDir(): + """Returns the location of the installed mobile provisioning profiles. + + Returns: + The path to the directory containing the installed mobile provisioning + profiles as a string. + """ + return os.path.join( + os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') + + +def ReadPlistFromString(plist_bytes): + """Parse property list from given |plist_bytes|. + + Args: + plist_bytes: contents of property list to load. Must be bytes in python 3. + + Returns: + The contents of property list as a python object. + """ + if sys.version_info.major == 2: + return plistlib.readPlistFromString(plist_bytes) + else: + return plistlib.loads(plist_bytes) + + +def LoadPlistFile(plist_path): + """Loads property list file at |plist_path|. + + Args: + plist_path: path to the property list file to load. + + Returns: + The content of the property list file as a python object. + """ + if sys.version_info.major == 2: + return plistlib.readPlistFromString( + subprocess.check_output( + ['xcrun', 'plutil', '-convert', 'xml1', '-o', '-', plist_path])) + else: + with open(plist_path, 'rb') as fp: + return plistlib.load(fp) + + +def CreateSymlink(value, location): + """Creates symlink with value at location if the target exists.""" + target = os.path.join(os.path.dirname(location), value) + if os.path.exists(location): + os.unlink(location) + os.symlink(value, location) + + +class Bundle(object): + """Wraps a bundle.""" + + def __init__(self, bundle_path, platform): + """Initializes the Bundle object with data from bundle Info.plist file.""" + self._path = bundle_path + self._kind = Bundle.Kind(platform, os.path.splitext(bundle_path)[-1]) + self._data = None + + def Load(self): + self._data = LoadPlistFile(self.info_plist_path) + + @staticmethod + def Kind(platform, extension): + if platform == 'iphonesimulator' or platform == 'iphoneos': + return 'ios' + if platform == 'macosx': + if extension == '.framework': + return 'mac_framework' + return 'mac' + raise ValueError('unknown bundle type %s for %s' % (extension, platform)) + + @property + def kind(self): + return self._kind + + @property + def path(self): + return self._path + + @property + def contents_dir(self): + if self._kind == 'mac': + return os.path.join(self.path, 'Contents') + if self._kind == 'mac_framework': + return os.path.join(self.path, 'Versions/A') + return self.path + + @property + def executable_dir(self): + if self._kind == 'mac': + return os.path.join(self.contents_dir, 'MacOS') + return self.contents_dir + + @property + def resources_dir(self): + if self._kind == 'mac' or self._kind == 'mac_framework': + return os.path.join(self.contents_dir, 'Resources') + return self.path + + @property + def info_plist_path(self): + if self._kind == 'mac_framework': + return os.path.join(self.resources_dir, 'Info.plist') + return os.path.join(self.contents_dir, 'Info.plist') + + @property + def signature_dir(self): + return os.path.join(self.contents_dir, '_CodeSignature') + + @property + def identifier(self): + return self._data['CFBundleIdentifier'] + + @property + def binary_name(self): + return self._data['CFBundleExecutable'] + + @property + def binary_path(self): + return os.path.join(self.executable_dir, self.binary_name) + + def Validate(self, expected_mappings): + """Checks that keys in the bundle have the expected value. + + Args: + expected_mappings: a dictionary of string to object, each mapping will + be looked up in the bundle data to check it has the same value (missing + values will be ignored) + + Returns: + A dictionary of the key with a different value between expected_mappings + and the content of the bundle (i.e. errors) so that caller can format the + error message. The dictionary will be empty if there are no errors. + """ + errors = {} + for key, expected_value in expected_mappings.items(): + if key in self._data: + value = self._data[key] + if value != expected_value: + errors[key] = (value, expected_value) + return errors + + +class ProvisioningProfile(object): + """Wraps a mobile provisioning profile file.""" + + def __init__(self, provisioning_profile_path): + """Initializes the ProvisioningProfile with data from profile file.""" + self._path = provisioning_profile_path + self._data = ReadPlistFromString( + subprocess.check_output([ + 'xcrun', 'security', 'cms', '-D', '-u', 'certUsageAnyCA', '-i', + provisioning_profile_path + ])) + + @property + def path(self): + return self._path + + @property + def team_identifier(self): + return self._data.get('TeamIdentifier', [''])[0] + + @property + def name(self): + return self._data.get('Name', '') + + @property + def application_identifier_pattern(self): + return self._data.get('Entitlements', {}).get('application-identifier', '') + + @property + def application_identifier_prefix(self): + return self._data.get('ApplicationIdentifierPrefix', [''])[0] + + @property + def entitlements(self): + return self._data.get('Entitlements', {}) + + @property + def expiration_date(self): + return self._data.get('ExpirationDate', datetime.datetime.now()) + + def ValidToSignBundle(self, bundle_identifier): + """Checks whether the provisioning profile can sign bundle_identifier. + + Args: + bundle_identifier: the identifier of the bundle that needs to be signed. + + Returns: + True if the mobile provisioning profile can be used to sign a bundle + with the corresponding bundle_identifier, False otherwise. + """ + return fnmatch.fnmatch( + '%s.%s' % (self.application_identifier_prefix, bundle_identifier), + self.application_identifier_pattern) + + def Install(self, installation_path): + """Copies mobile provisioning profile info to |installation_path|.""" + shutil.copy2(self.path, installation_path) + + +class Entitlements(object): + """Wraps an Entitlement plist file.""" + + def __init__(self, entitlements_path): + """Initializes Entitlements object from entitlement file.""" + self._path = entitlements_path + self._data = LoadPlistFile(self._path) + + @property + def path(self): + return self._path + + def ExpandVariables(self, substitutions): + self._data = self._ExpandVariables(self._data, substitutions) + + def _ExpandVariables(self, data, substitutions): + if isinstance(data, basestring_compat): + for key, substitution in substitutions.items(): + data = data.replace('$(%s)' % (key,), substitution) + return data + + if isinstance(data, dict): + for key, value in data.items(): + data[key] = self._ExpandVariables(value, substitutions) + return data + + if isinstance(data, list): + for i, value in enumerate(data): + data[i] = self._ExpandVariables(value, substitutions) + + return data + + def LoadDefaults(self, defaults): + for key, value in defaults.items(): + if key not in self._data: + self._data[key] = value + + def WriteTo(self, target_path): + with open(target_path, 'wb') as fp: + if sys.version_info.major == 2: + plistlib.writePlist(self._data, fp) + else: + plistlib.dump(self._data, fp) + + +def FindProvisioningProfile(bundle_identifier, required): + """Finds mobile provisioning profile to use to sign bundle. + + Args: + bundle_identifier: the identifier of the bundle to sign. + + Returns: + The ProvisioningProfile object that can be used to sign the Bundle + object or None if no matching provisioning profile was found. + """ + provisioning_profile_paths = glob.glob( + os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision')) + + # Iterate over all installed mobile provisioning profiles and filter those + # that can be used to sign the bundle, ignoring expired ones. + now = datetime.datetime.now() + valid_provisioning_profiles = [] + one_hour = datetime.timedelta(0, 3600) + for provisioning_profile_path in provisioning_profile_paths: + provisioning_profile = ProvisioningProfile(provisioning_profile_path) + if provisioning_profile.expiration_date - now < one_hour: + sys.stderr.write( + 'Warning: ignoring expired provisioning profile: %s.\n' % + provisioning_profile_path) + continue + if provisioning_profile.ValidToSignBundle(bundle_identifier): + valid_provisioning_profiles.append(provisioning_profile) + + if not valid_provisioning_profiles: + if required: + sys.stderr.write( + 'Error: no mobile provisioning profile found for "%s".\n' % + bundle_identifier) + sys.exit(1) + return None + + # Select the most specific mobile provisioning profile, i.e. the one with + # the longest application identifier pattern (prefer the one with the latest + # expiration date as a secondary criteria). + selected_provisioning_profile = max( + valid_provisioning_profiles, + key=lambda p: (len(p.application_identifier_pattern), p.expiration_date)) + + one_week = datetime.timedelta(7) + if selected_provisioning_profile.expiration_date - now < 2 * one_week: + sys.stderr.write( + 'Warning: selected provisioning profile will expire soon: %s' % + selected_provisioning_profile.path) + return selected_provisioning_profile + + +def CodeSignBundle(bundle_path, identity, extra_args): + process = subprocess.Popen( + ['xcrun', 'codesign', '--force', '--sign', identity, '--timestamp=none'] + + list(extra_args) + [bundle_path], + stderr=subprocess.PIPE, + universal_newlines=True) + _, stderr = process.communicate() + if process.returncode: + sys.stderr.write(stderr) + sys.exit(process.returncode) + for line in stderr.splitlines(): + if line.endswith(': replacing existing signature'): + # Ignore warning about replacing existing signature as this should only + # happen when re-signing system frameworks (and then it is expected). + continue + sys.stderr.write(line) + sys.stderr.write('\n') + + +def InstallSystemFramework(framework_path, bundle_path, args): + """Install framework from |framework_path| to |bundle| and code-re-sign it.""" + installed_framework_path = os.path.join( + bundle_path, 'Frameworks', os.path.basename(framework_path)) + + if os.path.isfile(framework_path): + shutil.copy(framework_path, installed_framework_path) + elif os.path.isdir(framework_path): + if os.path.exists(installed_framework_path): + shutil.rmtree(installed_framework_path) + shutil.copytree(framework_path, installed_framework_path) + + CodeSignBundle(installed_framework_path, args.identity, + ['--deep', '--preserve-metadata=identifier,entitlements,flags']) + + +def GenerateEntitlements(path, provisioning_profile, bundle_identifier): + """Generates an entitlements file. + + Args: + path: path to the entitlements template file + provisioning_profile: ProvisioningProfile object to use, may be None + bundle_identifier: identifier of the bundle to sign. + """ + entitlements = Entitlements(path) + if provisioning_profile: + entitlements.LoadDefaults(provisioning_profile.entitlements) + app_identifier_prefix = \ + provisioning_profile.application_identifier_prefix + '.' + else: + app_identifier_prefix = '*.' + entitlements.ExpandVariables({ + 'CFBundleIdentifier': bundle_identifier, + 'AppIdentifierPrefix': app_identifier_prefix, + }) + return entitlements + + +def GenerateBundleInfoPlist(bundle, plist_compiler, partial_plist): + """Generates the bundle Info.plist for a list of partial .plist files. + + Args: + bundle: a Bundle instance + plist_compiler: string, path to the Info.plist compiler + partial_plist: list of path to partial .plist files to merge + """ + + # Filter empty partial .plist files (this happens if an application + # does not compile any asset catalog, in which case the partial .plist + # file from the asset catalog compilation step is just a stamp file). + filtered_partial_plist = [] + for plist in partial_plist: + plist_size = os.stat(plist).st_size + if plist_size: + filtered_partial_plist.append(plist) + + # Invoke the plist_compiler script. It needs to be a python script. + subprocess.check_call([ + 'python', + plist_compiler, + 'merge', + '-f', + 'binary1', + '-o', + bundle.info_plist_path, + ] + filtered_partial_plist) + + +class Action(object): + """Class implementing one action supported by the script.""" + + @classmethod + def Register(cls, subparsers): + parser = subparsers.add_parser(cls.name, help=cls.help) + parser.set_defaults(func=cls._Execute) + cls._Register(parser) + + +class CodeSignBundleAction(Action): + """Class implementing the code-sign-bundle action.""" + + name = 'code-sign-bundle' + help = 'perform code signature for a bundle' + + @staticmethod + def _Register(parser): + parser.add_argument( + '--entitlements', '-e', dest='entitlements_path', + help='path to the entitlements file to use') + parser.add_argument( + 'path', help='path to the iOS bundle to codesign') + parser.add_argument( + '--identity', '-i', required=True, + help='identity to use to codesign') + parser.add_argument( + '--binary', '-b', required=True, + help='path to the iOS bundle binary') + parser.add_argument( + '--framework', '-F', action='append', default=[], dest='frameworks', + help='install and resign system framework') + parser.add_argument( + '--disable-code-signature', action='store_true', dest='no_signature', + help='disable code signature') + parser.add_argument( + '--disable-embedded-mobileprovision', action='store_false', + default=True, dest='embedded_mobileprovision', + help='disable finding and embedding mobileprovision') + parser.add_argument( + '--platform', '-t', required=True, + help='platform the signed bundle is targeting') + parser.add_argument( + '--partial-info-plist', '-p', action='append', default=[], + help='path to partial Info.plist to merge to create bundle Info.plist') + parser.add_argument( + '--plist-compiler-path', '-P', action='store', + help='path to the plist compiler script (for --partial-info-plist)') + parser.set_defaults(no_signature=False) + + @staticmethod + def _Execute(args): + if not args.identity: + args.identity = '-' + + bundle = Bundle(args.path, args.platform) + + if args.partial_info_plist: + GenerateBundleInfoPlist(bundle, args.plist_compiler_path, + args.partial_info_plist) + + # The bundle Info.plist may have been updated by GenerateBundleInfoPlist() + # above. Load the bundle information from Info.plist after the modification + # have been written to disk. + bundle.Load() + + # According to Apple documentation, the application binary must be the same + # as the bundle name without the .app suffix. See crbug.com/740476 for more + # information on what problem this can cause. + # + # To prevent this class of error, fail with an error if the binary name is + # incorrect in the Info.plist as it is not possible to update the value in + # Info.plist at this point (the file has been copied by a different target + # and ninja would consider the build dirty if it was updated). + # + # Also checks that the name of the bundle is correct too (does not cause the + # build to be considered dirty, but still terminate the script in case of an + # incorrect bundle name). + # + # Apple documentation is available at: + # https://developer.apple.com/library/content/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html + bundle_name = os.path.splitext(os.path.basename(bundle.path))[0] + errors = bundle.Validate({ + 'CFBundleName': bundle_name, + 'CFBundleExecutable': bundle_name, + }) + if errors: + for key in sorted(errors): + value, expected_value = errors[key] + sys.stderr.write('%s: error: %s value incorrect: %s != %s\n' % ( + bundle.path, key, value, expected_value)) + sys.stderr.flush() + sys.exit(1) + + # Delete existing embedded mobile provisioning. + embedded_provisioning_profile = os.path.join( + bundle.path, 'embedded.mobileprovision') + if os.path.isfile(embedded_provisioning_profile): + os.unlink(embedded_provisioning_profile) + + # Delete existing code signature. + if os.path.exists(bundle.signature_dir): + shutil.rmtree(bundle.signature_dir) + + # Install system frameworks if requested. + for framework_path in args.frameworks: + InstallSystemFramework(framework_path, args.path, args) + + # Copy main binary into bundle. + if not os.path.isdir(bundle.executable_dir): + os.makedirs(bundle.executable_dir) + shutil.copy(args.binary, bundle.binary_path) + + if bundle.kind == 'mac_framework': + # Create Versions/Current -> Versions/A symlink + CreateSymlink('A', os.path.join(bundle.path, 'Versions/Current')) + + # Create $binary_name -> Versions/Current/$binary_name symlink + CreateSymlink(os.path.join('Versions/Current', bundle.binary_name), + os.path.join(bundle.path, bundle.binary_name)) + + # Create optional symlinks. + for name in ('Headers', 'Resources', 'Modules'): + target = os.path.join(bundle.path, 'Versions/A', name) + if os.path.exists(target): + CreateSymlink(os.path.join('Versions/Current', name), + os.path.join(bundle.path, name)) + else: + obsolete_path = os.path.join(bundle.path, name) + if os.path.exists(obsolete_path): + os.unlink(obsolete_path) + + if args.no_signature: + return + + codesign_extra_args = [] + + if args.embedded_mobileprovision: + # Find mobile provisioning profile and embeds it into the bundle (if a + # code signing identify has been provided, fails if no valid mobile + # provisioning is found). + provisioning_profile_required = args.identity != '-' + provisioning_profile = FindProvisioningProfile( + bundle.identifier, provisioning_profile_required) + if provisioning_profile and args.platform != 'iphonesimulator': + provisioning_profile.Install(embedded_provisioning_profile) + + if args.entitlements_path is not None: + temporary_entitlements_file = \ + tempfile.NamedTemporaryFile(suffix='.xcent') + codesign_extra_args.extend( + ['--entitlements', temporary_entitlements_file.name]) + + entitlements = GenerateEntitlements( + args.entitlements_path, provisioning_profile, bundle.identifier) + entitlements.WriteTo(temporary_entitlements_file.name) + + CodeSignBundle(bundle.path, args.identity, codesign_extra_args) + + +class CodeSignFileAction(Action): + """Class implementing code signature for a single file.""" + + name = 'code-sign-file' + help = 'code-sign a single file' + + @staticmethod + def _Register(parser): + parser.add_argument( + 'path', help='path to the file to codesign') + parser.add_argument( + '--identity', '-i', required=True, + help='identity to use to codesign') + parser.add_argument( + '--output', '-o', + help='if specified copy the file to that location before signing it') + parser.set_defaults(sign=True) + + @staticmethod + def _Execute(args): + if not args.identity: + args.identity = '-' + + install_path = args.path + if args.output: + + if os.path.isfile(args.output): + os.unlink(args.output) + elif os.path.isdir(args.output): + shutil.rmtree(args.output) + + if os.path.isfile(args.path): + shutil.copy(args.path, args.output) + elif os.path.isdir(args.path): + shutil.copytree(args.path, args.output) + + install_path = args.output + + CodeSignBundle(install_path, args.identity, + ['--deep', '--preserve-metadata=identifier,entitlements']) + + +class GenerateEntitlementsAction(Action): + """Class implementing the generate-entitlements action.""" + + name = 'generate-entitlements' + help = 'generate entitlements file' + + @staticmethod + def _Register(parser): + parser.add_argument( + '--entitlements', '-e', dest='entitlements_path', + help='path to the entitlements file to use') + parser.add_argument( + 'path', help='path to the entitlements file to generate') + parser.add_argument( + '--info-plist', '-p', required=True, + help='path to the bundle Info.plist') + + @staticmethod + def _Execute(args): + info_plist = LoadPlistFile(args.info_plist) + bundle_identifier = info_plist['CFBundleIdentifier'] + provisioning_profile = FindProvisioningProfile(bundle_identifier, False) + entitlements = GenerateEntitlements( + args.entitlements_path, provisioning_profile, bundle_identifier) + entitlements.WriteTo(args.path) + + +class FindProvisioningProfileAction(Action): + """Class implementing the find-codesign-identity action.""" + + name = 'find-provisioning-profile' + help = 'find provisioning profile for use by Xcode project generator' + + @staticmethod + def _Register(parser): + parser.add_argument('--bundle-id', + '-b', + required=True, + help='bundle identifier') + + @staticmethod + def _Execute(args): + provisioning_profile_info = {} + provisioning_profile = FindProvisioningProfile(args.bundle_id, False) + for key in ('team_identifier', 'name'): + if provisioning_profile: + provisioning_profile_info[key] = getattr(provisioning_profile, key) + else: + provisioning_profile_info[key] = '' + print(json.dumps(provisioning_profile_info)) + + +def Main(): + # Cache this codec so that plistlib can find it. See + # https://crbug.com/999461#c12 for more details. + codecs.lookup('utf-8') + + parser = argparse.ArgumentParser('codesign iOS bundles') + subparsers = parser.add_subparsers() + + actions = [ + CodeSignBundleAction, + CodeSignFileAction, + GenerateEntitlementsAction, + FindProvisioningProfileAction, + ] + + for action in actions: + action.Register(subparsers) + + args = parser.parse_args() + args.func(args) + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/config/ios/compile_ib_files.py b/third_party/libwebrtc/build/config/ios/compile_ib_files.py new file mode 100644 index 0000000000..84781c1773 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/compile_ib_files.py @@ -0,0 +1,57 @@ +# 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. + +from __future__ import print_function + +import argparse +import logging +import os +import re +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser( + description='A script to compile xib and storyboard.', + fromfile_prefix_chars='@') + parser.add_argument('-o', '--output', required=True, + help='Path to output bundle.') + parser.add_argument('-i', '--input', required=True, + help='Path to input xib or storyboard.') + args, unknown_args = parser.parse_known_args() + + ibtool_args = [ + 'xcrun', 'ibtool', + '--errors', '--warnings', '--notices', + '--output-format', 'human-readable-text' + ] + ibtool_args += unknown_args + ibtool_args += [ + '--compile', + os.path.abspath(args.output), + os.path.abspath(args.input) + ] + + ibtool_section_re = re.compile(r'/\*.*\*/') + ibtool_re = re.compile(r'.*note:.*is clipping its content') + try: + stdout = subprocess.check_output(ibtool_args) + except subprocess.CalledProcessError as e: + print(e.output) + raise + current_section_header = None + for line in stdout.splitlines(): + if ibtool_section_re.match(line): + current_section_header = line + elif not ibtool_re.match(line): + if current_section_header: + print(current_section_header) + current_section_header = None + print(line) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/config/ios/compile_xcassets_unittests.py b/third_party/libwebrtc/build/config/ios/compile_xcassets_unittests.py new file mode 100644 index 0000000000..7655df8c05 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/compile_xcassets_unittests.py @@ -0,0 +1,141 @@ +# Copyright 2017 The Chromium 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 +import compile_xcassets + + +class TestFilterCompilerOutput(unittest.TestCase): + + relative_paths = { + '/Users/janedoe/chromium/src/Chromium.xcassets': + '../../Chromium.xcassets', + '/Users/janedoe/chromium/src/out/Default/Chromium.app/Assets.car': + 'Chromium.app/Assets.car', + } + + def testNoError(self): + self.assertEquals( + '', + compile_xcassets.FilterCompilerOutput( + '/* com.apple.actool.compilation-results */\n' + '/Users/janedoe/chromium/src/out/Default/Chromium.app/Assets.car\n', + self.relative_paths)) + + def testNoErrorRandomMessages(self): + self.assertEquals( + '', + compile_xcassets.FilterCompilerOutput( + '2017-07-04 04:59:19.460 ibtoold[23487:41214] CoreSimulator is att' + 'empting to unload a stale CoreSimulatorService job. Existing' + ' job (com.apple.CoreSimulator.CoreSimulatorService.179.1.E8tt' + 'yeDeVgWK) is from an older version and is being removed to pr' + 'event problems.\n' + '/* com.apple.actool.compilation-results */\n' + '/Users/janedoe/chromium/src/out/Default/Chromium.app/Assets.car\n', + self.relative_paths)) + + def testWarning(self): + self.assertEquals( + '/* com.apple.actool.document.warnings */\n' + '../../Chromium.xcassets:./image1.imageset/[universal][][][1x][][][][' + '][][]: warning: The file "image1.png" for the image set "image1"' + ' does not exist.\n', + compile_xcassets.FilterCompilerOutput( + '/* com.apple.actool.document.warnings */\n' + '/Users/janedoe/chromium/src/Chromium.xcassets:./image1.imageset/[' + 'universal][][][1x][][][][][][]: warning: The file "image1.png' + '" for the image set "image1" does not exist.\n' + '/* com.apple.actool.compilation-results */\n' + '/Users/janedoe/chromium/src/out/Default/Chromium.app/Assets.car\n', + self.relative_paths)) + + def testError(self): + self.assertEquals( + '/* com.apple.actool.errors */\n' + '../../Chromium.xcassets: error: The output directory "/Users/janedoe/' + 'chromium/src/out/Default/Chromium.app" does not exist.\n', + compile_xcassets.FilterCompilerOutput( + '/* com.apple.actool.errors */\n' + '/Users/janedoe/chromium/src/Chromium.xcassets: error: The output ' + 'directory "/Users/janedoe/chromium/src/out/Default/Chromium.a' + 'pp" does not exist.\n' + '/* com.apple.actool.compilation-results */\n', + self.relative_paths)) + + def testSpurious(self): + self.assertEquals( + '/* com.apple.actool.document.warnings */\n' + '../../Chromium.xcassets:./AppIcon.appiconset: warning: A 1024x1024 ap' + 'p store icon is required for iOS apps\n', + compile_xcassets.FilterCompilerOutput( + '/* com.apple.actool.document.warnings */\n' + '/Users/janedoe/chromium/src/Chromium.xcassets:./AppIcon.appiconse' + 't: warning: A 1024x1024 app store icon is required for iOS ap' + 'ps\n' + '/* com.apple.actool.document.notices */\n' + '/Users/janedoe/chromium/src/Chromium.xcassets:./AppIcon.appiconse' + 't/[][ipad][76x76][][][1x][][]: notice: (null)\n', + self.relative_paths)) + + def testComplexError(self): + self.assertEquals( + '/* com.apple.actool.errors */\n' + ': error: Failed to find a suitable device for the type SimDeviceType ' + ': com.apple.dt.Xcode.IBSimDeviceType.iPad-2x with runtime SimRunt' + 'ime : 10.3.1 (14E8301) - com.apple.CoreSimulator.SimRuntime.iOS-1' + '0-3\n' + ' Failure Reason: Failed to create SimDeviceSet at path /Users/jane' + 'doe/Library/Developer/Xcode/UserData/IB Support/Simulator Devices' + '. You\'ll want to check the logs in ~/Library/Logs/CoreSimulator ' + 'to see why creating the SimDeviceSet failed.\n' + ' Underlying Errors:\n' + ' Description: Failed to initialize simulator device set.\n' + ' Failure Reason: Failed to subscribe to notifications from Cor' + 'eSimulatorService.\n' + ' Underlying Errors:\n' + ' Description: Error returned in reply to notification requ' + 'est: Connection invalid\n' + ' Failure Reason: Software caused connection abort\n', + compile_xcassets.FilterCompilerOutput( + '2017-07-07 10:37:27.367 ibtoold[88538:12553239] CoreSimulator det' + 'ected Xcode.app relocation or CoreSimulatorService version ch' + 'ange. Framework path (/Applications/Xcode.app/Contents/Devel' + 'oper/Library/PrivateFrameworks/CoreSimulator.framework) and v' + 'ersion (375.21) does not match existing job path (/Library/De' + 'veloper/PrivateFrameworks/CoreSimulator.framework/Versions/A/' + 'XPCServices/com.apple.CoreSimulator.CoreSimulatorService.xpc)' + ' and version (459.13). Attempting to remove the stale servic' + 'e in order to add the expected version.\n' + '2017-07-07 10:37:27.625 ibtoold[88538:12553256] CoreSimulatorServ' + 'ice connection interrupted. Resubscribing to notifications.\n' + '2017-07-07 10:37:27.632 ibtoold[88538:12553264] CoreSimulatorServ' + 'ice connection became invalid. Simulator services will no lo' + 'nger be available.\n' + '2017-07-07 10:37:27.642 ibtoold[88538:12553274] CoreSimulatorServ' + 'ice connection became invalid. Simulator services will no lo' + 'nger be available.\n' + '/* com.apple.actool.errors */\n' + ': error: Failed to find a suitable device for the type SimDeviceT' + 'ype : com.apple.dt.Xcode.IBSimDeviceType.iPad-2x with runtime' + ' SimRuntime : 10.3.1 (14E8301) - com.apple.CoreSimulator.SimR' + 'untime.iOS-10-3\n' + ' Failure Reason: Failed to create SimDeviceSet at path /Users/' + 'janedoe/Library/Developer/Xcode/UserData/IB Support/Simulator' + ' Devices. You\'ll want to check the logs in ~/Library/Logs/Co' + 'reSimulator to see why creating the SimDeviceSet failed.\n' + ' Underlying Errors:\n' + ' Description: Failed to initialize simulator device set.\n' + ' Failure Reason: Failed to subscribe to notifications from' + ' CoreSimulatorService.\n' + ' Underlying Errors:\n' + ' Description: Error returned in reply to notification ' + 'request: Connection invalid\n' + ' Failure Reason: Software caused connection abort\n' + '/* com.apple.actool.compilation-results */\n', + self.relative_paths)) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/config/ios/config.gni b/third_party/libwebrtc/build/config/ios/config.gni new file mode 100644 index 0000000000..358c7ccd2a --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/config.gni @@ -0,0 +1,28 @@ +# Copyright 2020 The Chromium 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() { + # Configure the environment for which to build. Could be either "device", + # "simulator" or "catalyst". If unspecified, then it will be assumed to be + # "simulator" if the target_cpu is "x68" or "x64", "device" otherwise. The + # default is only there for compatibility reasons and will be removed (see + # crbug.com/1138425 for more details). + target_environment = "" + + # Control whether cronet is built (this is usually set by the script + # components/cronet/tools/cr_cronet.py as cronet requires specific + # gn args to build correctly). + is_cronet_build = false +} + +if (target_environment == "") { + if (target_cpu == "x86" || target_cpu == "x64") { + target_environment = "simulator" + } else { + target_environment = "device" + } +} + +assert(target_environment == "simulator" || target_environment == "device" || + target_environment == "catalyst") diff --git a/third_party/libwebrtc/build/config/ios/dummy.py b/third_party/libwebrtc/build/config/ios/dummy.py new file mode 100644 index 0000000000..b23b7dab96 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/dummy.py @@ -0,0 +1,15 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Empty script that does nothing and return success error code. + +This script is used by some gn targets that pretend creating some output +but instead depend on another target creating the output indirectly (in +general this output is a directory that is used as input by a bundle_data +target). + +It ignores all parameters and terminate with a success error code. It +does the same thing as the unix command "true", but gn can only invoke +python scripts. +""" diff --git a/third_party/libwebrtc/build/config/ios/entitlements.plist b/third_party/libwebrtc/build/config/ios/entitlements.plist new file mode 100644 index 0000000000..429762e3a3 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/entitlements.plist @@ -0,0 +1,12 @@ + + + + + application-identifier + $(AppIdentifierPrefix)$(CFBundleIdentifier) + keychain-access-groups + + $(AppIdentifierPrefix)$(CFBundleIdentifier) + + + diff --git a/third_party/libwebrtc/build/config/ios/find_signing_identity.py b/third_party/libwebrtc/build/config/ios/find_signing_identity.py new file mode 100644 index 0000000000..d508e2bb46 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/find_signing_identity.py @@ -0,0 +1,89 @@ +# 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. + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys +import re + + +def Redact(value, from_nth_char=5): + """Redact value past the N-th character.""" + return value[:from_nth_char] + '*' * (len(value) - from_nth_char) + + +class Identity(object): + """Represents a valid identity.""" + + def __init__(self, identifier, name, team): + self.identifier = identifier + self.name = name + self.team = team + + def redacted(self): + return Identity(Redact(self.identifier), self.name, Redact(self.team)) + + def format(self): + return '%s: "%s (%s)"' % (self.identifier, self.name, self.team) + + +def ListIdentities(): + return subprocess.check_output([ + 'xcrun', + 'security', + 'find-identity', + '-v', + '-p', + 'codesigning', + ]).decode('utf8') + + +def FindValidIdentity(pattern): + """Find all identities matching the pattern.""" + lines = list(l.strip() for l in ListIdentities().splitlines()) + # Look for something like "2) XYZ "iPhone Developer: Name (ABC)"" + regex = re.compile('[0-9]+\) ([A-F0-9]+) "([^"(]*) \(([^)"]*)\)"') + + result = [] + for line in lines: + res = regex.match(line) + if res is None: + continue + if pattern is None or pattern in res.group(2): + result.append(Identity(*res.groups())) + return result + + +def Main(args): + parser = argparse.ArgumentParser('codesign iOS bundles') + parser.add_argument('--matching-pattern', + dest='pattern', + help='Pattern used to select the code signing identity.') + parsed = parser.parse_args(args) + + identities = FindValidIdentity(parsed.pattern) + if len(identities) == 1: + print(identities[0].identifier, end='') + return 0 + + all_identities = FindValidIdentity(None) + + print('Automatic code signing identity selection was enabled but could not') + print('find exactly one codesigning identity matching "%s".' % parsed.pattern) + print('') + print('Check that the keychain is accessible and that there is exactly one') + print('valid codesigning identity matching the pattern. Here is the parsed') + print('output of `xcrun security find-identity -v -p codesigning`:') + print() + for i, identity in enumerate(all_identities): + print(' %d) %s' % (i + 1, identity.redacted().format())) + print(' %d valid identities found' % (len(all_identities))) + return 1 + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/config/ios/generate_umbrella_header.py b/third_party/libwebrtc/build/config/ios/generate_umbrella_header.py new file mode 100644 index 0000000000..8547e18aa7 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/generate_umbrella_header.py @@ -0,0 +1,75 @@ +# Copyright 2018 The Chromium 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 an umbrella header for an iOS framework.""" + +import argparse +import datetime +import os +import re +import string + + +HEADER_TEMPLATE = string.Template('''\ +// Copyright $year The Chromium 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 auto-generated by //build/ios/config/generate_umbrella_header.py + +#ifndef $header_guard +#define $header_guard + +$imports + +#endif // $header_guard +''') + + +def ComputeHeaderGuard(file_path): + """Computes the header guard for a file path. + + Args: + file_path: The path to convert into an header guard. + Returns: + The header guard string for the file_path. + """ + return re.sub(r'[.+/\\]', r'_', file_path.upper()) + '_' + + +def WriteUmbrellaHeader(output_path, imported_headers): + """Writes the umbrella header. + + Args: + output_path: The path to the umbrella header. + imported_headers: A list of headers to #import in the umbrella header. + """ + year = datetime.date.today().year + header_guard = ComputeHeaderGuard(output_path) + imports = '\n'.join([ + '#import "%s"' % os.path.basename(header) + for header in sorted(imported_headers) + ]) + with open(output_path, 'w') as output_file: + output_file.write( + HEADER_TEMPLATE.safe_substitute({ + 'year': year, + 'header_guard': header_guard, + 'imports': imports, + })) + + +def Main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output-path', required=True, type=str, + help='Path to the generated umbrella header.') + parser.add_argument('imported_headers', type=str, nargs='+', + help='Headers to #import in the umbrella header.') + options = parser.parse_args() + + return WriteUmbrellaHeader(options.output_path, options.imported_headers) + + +if __name__ == '__main__': + Main() diff --git a/third_party/libwebrtc/build/config/ios/hardlink.py b/third_party/libwebrtc/build/config/ios/hardlink.py new file mode 100644 index 0000000000..38f60d4013 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/hardlink.py @@ -0,0 +1,71 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Recursively create hardlink to target named output.""" + + +import argparse +import os +import shutil + + +def CreateHardlinkHelper(target, output): + """Recursively create a hardlink named output pointing to target. + + Args: + target: path to an existing file or directory + output: path to the newly created hardlink + + This function assumes that output does not exists but that the parent + directory containing output does. If those conditions are false, then + the function will fails with an exception corresponding to an OS error. + """ + if os.path.islink(target): + os.symlink(os.readlink(target), output) + elif not os.path.isdir(target): + try: + os.link(target, output) + except: + shutil.copy(target, output) + else: + os.mkdir(output) + for name in os.listdir(target): + CreateHardlinkHelper( + os.path.join(target, name), + os.path.join(output, name)) + + +def CreateHardlink(target, output): + """Recursively create a hardlink named output pointing to target. + + Args: + target: path to an existing file or directory + output: path to the newly created hardlink + + If output already exists, it is first removed. In all cases, the + parent directory containing output is created. + """ + if os.path.isdir(output): + shutil.rmtree(output) + elif os.path.exists(output): + os.unlink(output) + + parent_dir = os.path.dirname(os.path.abspath(output)) + if not os.path.isdir(parent_dir): + os.makedirs(parent_dir) + + CreateHardlinkHelper(target, output) + + +def Main(): + parser = argparse.ArgumentParser() + parser.add_argument('target', help='path to the file or directory to link to') + parser.add_argument('output', help='name of the hardlink to create') + args = parser.parse_args() + + CreateHardlink(args.target, args.output) + + +if __name__ == '__main__': + Main() diff --git a/third_party/libwebrtc/build/config/ios/ios_sdk.gni b/third_party/libwebrtc/build/config/ios/ios_sdk.gni new file mode 100644 index 0000000000..ffff5921d8 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/ios_sdk.gni @@ -0,0 +1,185 @@ +# 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/ios/config.gni") +import("//build/config/ios/ios_sdk_overrides.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +assert(current_os == "ios") +assert(use_system_xcode, "Hermetic xcode doesn't work for ios.") + +declare_args() { + # SDK path to use. When empty this will use the default SDK based on the + # value of target_environment. + ios_bin_path = "" + ios_sdk_path = "" + ios_sdk_name = "" + ios_sdk_version = "" + ios_sdk_platform = "" + ios_sdk_platform_path = "" + ios_toolchains_path = "" + xcode_version = "" + xcode_version_int = 0 + xcode_build = "" + machine_os_build = "" + + # Set DEVELOPER_DIR while running sdk_info.py. + ios_sdk_developer_dir = "" + + # Control whether codesiging is enabled (ignored for simulator builds). + ios_enable_code_signing = true + + # Explicitly select the identity to use for codesigning. If defined, must + # be set to a non-empty string that will be passed to codesigning. Can be + # left unspecified if ios_code_signing_identity_description is used instead. + ios_code_signing_identity = "" + + # Pattern used to select the identity to use for codesigning. If defined, + # must be a substring of the description of exactly one of the identities by + # `security find-identity -v -p codesigning`. + ios_code_signing_identity_description = "Apple Development" + + # Prefix for CFBundleIdentifier property of iOS bundles (correspond to the + # "Organization Identifier" in Xcode). Code signing will fail if no mobile + # provisioning for the selected code signing identify support that prefix. + ios_app_bundle_id_prefix = "org.chromium" + + # If non-empty, this list must contain valid cpu architecture, and the final + # build will be a multi-architecture build (aka fat build) supporting the + # main $target_cpu architecture and all of $additional_target_cpus. + # + # For example to build an application that will run on both arm64 and armv7 + # devices, you would use the following in args.gn file when running "gn args": + # + # target_os = "ios" + # target_cpu = "arm64" + # additional_target_cpus = [ "arm" ] + # + # You can also pass the value via "--args" parameter for "gn gen" command by + # using the syntax --args='additional_target_cpus=["arm"] target_cpu="arm64"'. + additional_target_cpus = [] +} + +declare_args() { + # This variable is set by the toolchain. It is set to true if the toolchain + # is a secondary toolchain as part of a "fat" build. + is_fat_secondary_toolchain = false + + # This variable is set by the toolchain. It is the name of the primary + # toolchain for the fat build (could be current_toolchain). + primary_fat_toolchain_name = "" +} + +# Official builds may not use goma. +assert(!(use_goma && is_chrome_branded && is_official_build && + target_cpu == "arm64"), + "goma use is forbidden for official iOS builds.") + +assert(custom_toolchain == "" || additional_target_cpus == [], + "cannot define both custom_toolchain and additional_target_cpus") + +# If codesigning is enabled, use must configure either a codesigning identity +# or a filter to automatically select the codesigning identity. +if (target_environment == "device" && ios_enable_code_signing) { + assert(ios_code_signing_identity == "" || + ios_code_signing_identity_description == "", + "You should either specify the precise identity to use with " + + "ios_code_signing_identity or let the code select an identity " + + "automatically (via find_signing_identity.py which use the " + + "variable ios_code_signing_identity_description to set the " + + "pattern to match the identity to use).") +} + +# Initialize additional_toolchains from additional_target_cpus. Assert here +# that the list does not contains $target_cpu nor duplicates as this would +# cause weird errors during the build. +additional_toolchains = [] +if (additional_target_cpus != [] && !is_fat_secondary_toolchain) { + foreach(_additional_target_cpu, additional_target_cpus) { + assert(_additional_target_cpu != target_cpu, + "target_cpu must not be listed in additional_target_cpus") + + _toolchain = "${current_toolchain}_fat_${_additional_target_cpu}" + foreach(_additional_toolchain, additional_toolchains) { + assert(_toolchain != _additional_toolchain, + "additional_target_cpus must not contains duplicate values") + } + + additional_toolchains += [ _toolchain ] + } +} + +if (ios_sdk_path == "") { + # Compute default target. + if (target_environment == "simulator") { + ios_sdk_name = "iphonesimulator" + ios_sdk_platform = "iPhoneSimulator" + } else if (target_environment == "device") { + ios_sdk_name = "iphoneos" + ios_sdk_platform = "iPhoneOS" + } else if (target_environment == "catalyst") { + ios_sdk_name = "macosx" + ios_sdk_platform = "MacOSX" + } else { + assert(false, "unsupported environment: $target_environment") + } + + ios_sdk_info_args = [ + "--get_sdk_info", + "--get_machine_info", + ] + ios_sdk_info_args += [ ios_sdk_name ] + if (ios_sdk_developer_dir != "") { + ios_sdk_info_args += [ + "--developer_dir", + ios_sdk_developer_dir, + ] + } + if (use_system_xcode && use_goma) { + ios_sdk_info_args += [ + "--create_symlink_at", + "sdk/xcode_links", + ] + } + script_name = "//build/config/apple/sdk_info.py" + _ios_sdk_result = exec_script(script_name, ios_sdk_info_args, "scope") + ios_bin_path = + rebase_path("${_ios_sdk_result.toolchains_path}/usr/bin/", root_build_dir) + ios_sdk_path = _ios_sdk_result.sdk_path + ios_sdk_platform_path = _ios_sdk_result.sdk_platform_path + ios_sdk_version = _ios_sdk_result.sdk_version + ios_sdk_build = _ios_sdk_result.sdk_build + ios_toolchains_path = _ios_sdk_result.toolchains_path + xcode_version = _ios_sdk_result.xcode_version + xcode_version_int = _ios_sdk_result.xcode_version_int + xcode_build = _ios_sdk_result.xcode_build + machine_os_build = _ios_sdk_result.machine_os_build + if (target_environment == "simulator") { + # This is weird, but Xcode sets DTPlatformBuild to an empty field for + # simulator builds. + ios_platform_build = "" + } else { + ios_platform_build = ios_sdk_build + } +} + +if (target_environment == "device" && ios_enable_code_signing) { + # Automatically select a codesigning identity if no identity is configured. + # This only applies to device build as simulator builds are not signed. + if (ios_code_signing_identity == "") { + find_signing_identity_args = [] + if (ios_code_signing_identity_description != "") { + find_signing_identity_args = [ + "--matching-pattern", + ios_code_signing_identity_description, + ] + } + ios_code_signing_identity = exec_script("find_signing_identity.py", + find_signing_identity_args, + "trim string") + } +} diff --git a/third_party/libwebrtc/build/config/ios/ios_sdk_overrides.gni b/third_party/libwebrtc/build/config/ios/ios_sdk_overrides.gni new file mode 100644 index 0000000000..12d52521da --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/ios_sdk_overrides.gni @@ -0,0 +1,17 @@ +# Copyright 2017 The Chromium 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 arguments that subprojects may choose to override. It +# asserts that those overrides are used, to prevent unused args warnings. + +declare_args() { + # Version of iOS that we're targeting. + ios_deployment_target = "14.0" +} + +# Always assert that ios_deployment_target is used on non-iOS platforms to +# prevent unused args warnings. +if (!is_ios) { + assert(ios_deployment_target == "14.0" || true) +} diff --git a/third_party/libwebrtc/build/config/ios/ios_test_runner_wrapper.gni b/third_party/libwebrtc/build/config/ios/ios_test_runner_wrapper.gni new file mode 100644 index 0000000000..115db7ffdb --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/ios_test_runner_wrapper.gni @@ -0,0 +1,143 @@ +# Copyright 2020 The Chromium 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/coverage/coverage.gni") +import("//build/config/ios/ios_sdk.gni") +import("//build/util/generate_wrapper.gni") + +# Invokes generate_wrapper to create an executable script wrapping iOS' +# run.py with baked in arguments. Only takes effect when test entry in +# gn_isolate_map.pyl is updated to type="generated_script" with script +# set to the wrapper output path. +# +# Arguments: +# +# data +# (optional, default [ "//ios/build/bots/scripts/" ]) list of files or +# directories required to run target +# +# data_deps +# (optional) list of target non-linked labels +# +# deps +# (optional) list of files or directories required to run target +# +# executable_args +# (optional) a list of string arguments to pass to run.py +# +# retries +# (optional, default 3) number of retry attempts +# +# shards +# (optional, default 1) number of shards to execute tests in parallel. not +# the same as swarming shards. +# +# wrapper_output_name +# (optional, default "run_${target_name}") name of the wrapper script +# +template("ios_test_runner_wrapper") { + generate_wrapper(target_name) { + forward_variables_from(invoker, + [ + "deps", + "retries", + "shards", + "wrapper_output_name", + ]) + testonly = true + executable = "//testing/test_env.py" + + # iOS main test runner + _runner_path = + rebase_path("//ios/build/bots/scripts/run.py", root_build_dir) + + executable_args = [ "@WrappedPath(${_runner_path})" ] + + # arguments passed to run.py + if (defined(invoker.executable_args)) { + executable_args += invoker.executable_args + } + + _rebased_mac_toolchain = rebase_path("//mac_toolchain", root_build_dir) + _rebased_xcode_path = rebase_path("//Xcode.app", root_build_dir) + _rebased_ios_runtime_cache_prefix = + rebase_path("//Runtime-ios-", root_build_dir) + + # --out-dir argument is specified in gn_isolate_map.pyl because + # ${ISOLATED_OUTDIR} doesn't get resolved through this wrapper. + executable_args += [ + "--xcode-path", + "@WrappedPath(${_rebased_xcode_path})", + "--mac-toolchain-cmd", + "@WrappedPath(${_rebased_mac_toolchain})", + "--runtime-cache-prefix", + "@WrappedPath(${_rebased_ios_runtime_cache_prefix})", + ] + + # Default retries to 3 + if (!defined(retries)) { + retries = 3 + } + executable_args += [ + "--retries", + "${retries}", + ] + + # Default shards to 1 + if (!defined(shards)) { + shards = 1 + } + executable_args += [ + "--shards", + "${shards}", + ] + + data_deps = [ "//testing:test_scripts_shared" ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + + # test runner relies on iossim for simulator builds. + if (target_environment == "simulator") { + _rebased_root_build_dir = rebase_path("${root_build_dir}", root_build_dir) + data_deps += [ "//testing/iossim" ] + + executable_args += [ + "--iossim", + "@WrappedPath(${_rebased_root_build_dir}/iossim)", + ] + } + + if (use_clang_coverage) { + executable_args += [ "--use-clang-coverage" ] + } + + if (!is_debug) { + executable_args += [ "--release" ] + } + + # wrapper script output name and path + if (!defined(wrapper_output_name)) { + _wrapper_output_name = "run_${target_name}" + } else { + _wrapper_output_name = wrapper_output_name + } + + # Test targets may attempt to generate multiple wrappers for a suite with + # multiple different toolchains when running with additional_target_cpus. + # Generate the wrapper script into root_out_dir rather than root_build_dir + # to ensure those wrappers are distinct. + wrapper_script = "${root_out_dir}/bin/${_wrapper_output_name}" + + data = [] + if (defined(invoker.data)) { + data += invoker.data + } + data += [ + "//.vpython", + "//ios/build/bots/scripts/", + "//testing/test_env.py", + ] + } +} diff --git a/third_party/libwebrtc/build/config/ios/resources/XCTRunnerAddition+Info.plist b/third_party/libwebrtc/build/config/ios/resources/XCTRunnerAddition+Info.plist new file mode 100644 index 0000000000..cf9463f694 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/resources/XCTRunnerAddition+Info.plist @@ -0,0 +1,12 @@ + + + + + CFBundleIdentifier + com.apple.test.${EXECUTABLE_NAME} + CFBundleName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + + diff --git a/third_party/libwebrtc/build/config/ios/rules.gni b/third_party/libwebrtc/build/config/ios/rules.gni new file mode 100644 index 0000000000..04d605dd97 --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/rules.gni @@ -0,0 +1,2173 @@ +# 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/apple/apple_info_plist.gni") +import("//build/config/apple/symbols.gni") +import("//build/config/ios/ios_sdk.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +declare_args() { + # Set to true if an Xcode project is generated for this build. Set this to + # false if you do not plan to run `gn gen --ide=xcode` in this directory. + # This will speed up the generation at the cost of generating an invalid + # Xcode project if `gn gen --ide=xcode` is used. Defaults to true (favor + # correctness over speed). + ios_set_attributes_for_xcode_project_generation = true +} + +# Constants corresponding to the bundle type identifiers use application, +# application extension, XCTest and XCUITest targets respectively. +_ios_xcode_app_bundle_id = "com.apple.product-type.application" +_ios_xcode_appex_bundle_id = "com.apple.product-type.app-extension" +_ios_xcode_xctest_bundle_id = "com.apple.product-type.bundle.unit-test" +_ios_xcode_xcuitest_bundle_id = "com.apple.product-type.bundle.ui-testing" + +# Invokes lipo on multiple arch-specific binaries to create a fat binary. +# +# Arguments +# +# arch_binary_target +# name of the target generating the arch-specific binaries, they must +# be named $target_out_dir/$toolchain_cpu/$arch_binary_output. +# +# arch_binary_output +# (optional, defaults to the name of $arch_binary_target) base name of +# the arch-specific binary generated by arch_binary_target. +# +# output_name +# (optional, defaults to $target_name) base name of the target output, +# the full path will be $target_out_dir/$output_name. +# +# configs +# (optional) a list of configurations, this is used to check whether +# the binary should be stripped, when "enable_stripping" is true. +# +template("lipo_binary") { + assert(defined(invoker.arch_binary_target), + "arch_binary_target must be defined for $target_name") + + assert(!is_fat_secondary_toolchain, + "lipo_binary can only be used in the primary toolchain of a fat build") + + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _all_target_cpu = [ target_cpu ] + additional_target_cpus + _all_toolchains = [ current_toolchain ] + additional_toolchains + + _arch_binary_target = invoker.arch_binary_target + _arch_binary_output = get_label_info(_arch_binary_target, "name") + if (defined(invoker.arch_binary_output)) { + _arch_binary_output = invoker.arch_binary_output + } + + action(_target_name) { + forward_variables_from(invoker, + "*", + [ + "arch_binary_output", + "arch_binary_target", + "configs", + "output_name", + ]) + + script = "//build/toolchain/apple/linker_driver.py" + + # http://crbug.com/762840. Fix for bots running out of memory. + pool = "//build/toolchain:link_pool($default_toolchain)" + + outputs = [ "$target_out_dir/$_output_name" ] + + deps = [] + _index = 0 + inputs = [] + foreach(_cpu, _all_target_cpu) { + _toolchain = _all_toolchains[_index] + _index = _index + 1 + + inputs += + [ get_label_info("$_arch_binary_target($_toolchain)", + "target_out_dir") + "/$_cpu/$_arch_binary_output" ] + + deps += [ "$_arch_binary_target($_toolchain)" ] + } + + args = [ + "xcrun", + "lipo", + "-create", + "-output", + rebase_path("$target_out_dir/$_output_name", root_build_dir), + ] + rebase_path(inputs, root_build_dir) + + if (enable_dsyms) { + _dsyms_output_dir = "$root_out_dir/$_output_name.dSYM" + outputs += [ + "$_dsyms_output_dir/", + "$_dsyms_output_dir/Contents/Info.plist", + "$_dsyms_output_dir/Contents/Resources/DWARF/$_output_name", + ] + args += [ "-Wcrl,dsym," + rebase_path("$root_out_dir/.", root_build_dir) ] + if (!use_xcode_clang) { + args += [ "-Wcrl,dsymutilpath," + + rebase_path("//tools/clang/dsymutil/bin/dsymutil", + root_build_dir) ] + } + } + + if (enable_stripping) { + args += [ "-Wcrl,strip,-x,-S" ] + if (save_unstripped_output) { + outputs += [ "$root_out_dir/$_output_name.unstripped" ] + args += [ "-Wcrl,unstripped," + + rebase_path("$root_out_dir/.", root_build_dir) ] + } + } + } +} + +# Wrapper around create_bundle taking care of code signature settings. +# +# Arguments +# +# product_type +# string, product type for the generated Xcode project. +# +# bundle_gen_dir +# (optional) directory where the bundle is generated; must be below +# root_out_dir and defaults to root_out_dir if omitted. +# +# bundle_deps +# (optional) list of additional dependencies. +# +# bundle_deps_filter +# (optional) list of dependencies to filter (for more information +# see "gn help bundle_deps_filter"). +# +# bundle_extension +# string, extension of the bundle, used to generate bundle name. +# +# bundle_binary_target +# (optional) string, label of the target generating the bundle main +# binary. This target and bundle_binary_path are mutually exclusive. +# +# bundle_binary_output +# (optional) string, base name of the binary generated by the +# bundle_binary_target target, defaults to the target name. +# +# bundle_binary_path +# (optional) string, path to the bundle main binary. This target and +# bundle_binary_target are mutually exclusive. +# +# output_name: +# (optional) string, name of the generated application, if omitted, +# defaults to the target_name. +# +# extra_system_frameworks +# (optional) list of system framework to copy to the bundle. +# +# enable_code_signing +# (optional) boolean, control whether code signing is enabled or not, +# default to ios_enable_code_signing if not defined. +# +# entitlements_path: +# (optional) path to the template to use to generate the application +# entitlements by performing variable substitutions, defaults to +# //build/config/ios/entitlements.plist. +# +# entitlements_target: +# (optional) label of the target generating the application +# entitlements (must generate a single file as output); cannot be +# defined if entitlements_path is set. +# +# has_public_headers: +# (optional) boolean, defaults to false; only meaningful if the bundle +# is a framework bundle; if true, then the frameworks includes public +# headers +# +# disable_entitlements +# (optional, defaults to false) boolean, control whether entitlements willi +# be embedded in the application during signature. If false and no +# entitlements are provided, default empty entitlements will be used. +# +# disable_embedded_mobileprovision +# (optional, default to false) boolean, control whether mobile provisions +# will be embedded in the bundle. If true, the existing +# embedded.mobileprovision will be deleted. +# +# xcode_extra_attributes +# (optional) scope, extra attributes for Xcode projects. +# +# xcode_test_application_name: +# (optional) string, name of the test application for Xcode unit or ui +# test target. +# +# xcode_product_bundle_id: +# (optional) string, the bundle ID that will be added in the XCode +# attributes to enable some features when debugging (e.g. MetricKit). +# +# primary_info_plist: +# (optional) path to Info.plist to merge with the $partial_info_plist +# generated by the compilation of the asset catalog. +# +# partial_info_plist: +# (optional) path to the partial Info.plist generated by the asset +# catalog compiler; if defined $primary_info_plist must also be defined. +# +template("create_signed_bundle") { + assert(defined(invoker.product_type), + "product_type must be defined for $target_name") + assert(defined(invoker.bundle_extension), + "bundle_extension must be defined for $target_name") + assert(defined(invoker.bundle_binary_target) != + defined(invoker.bundle_binary_path), + "Only one of bundle_binary_target or bundle_binary_path may be " + + "specified for $target_name") + assert(!defined(invoker.partial_info_plist) || + defined(invoker.primary_info_plist), + "primary_info_plist must be defined when partial_info_plist is " + + "defined for $target_name") + + if (defined(invoker.xcode_test_application_name)) { + assert( + invoker.product_type == _ios_xcode_xctest_bundle_id || + invoker.product_type == _ios_xcode_xcuitest_bundle_id, + "xcode_test_application_name can be only defined for Xcode unit or ui test target.") + } + + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + if (defined(invoker.bundle_binary_path)) { + _bundle_binary_path = invoker.bundle_binary_path + } else { + _bundle_binary_target = invoker.bundle_binary_target + _bundle_binary_output = get_label_info(_bundle_binary_target, "name") + if (defined(invoker.bundle_binary_output)) { + _bundle_binary_output = invoker.bundle_binary_output + } + _bundle_binary_path = + get_label_info(_bundle_binary_target, "target_out_dir") + + "/$_bundle_binary_output" + } + + _bundle_gen_dir = root_out_dir + if (defined(invoker.bundle_gen_dir)) { + _bundle_gen_dir = invoker.bundle_gen_dir + } + + _bundle_extension = invoker.bundle_extension + + _enable_embedded_mobileprovision = true + if (defined(invoker.disable_embedded_mobileprovision)) { + _enable_embedded_mobileprovision = !invoker.disable_embedded_mobileprovision + } + + if (target_environment == "catalyst") { + _enable_embedded_mobileprovision = false + } + + _enable_entitlements = true + if (defined(invoker.disable_entitlements)) { + _enable_entitlements = !invoker.disable_entitlements + } + + if (_enable_entitlements) { + if (!defined(invoker.entitlements_target)) { + _entitlements_path = "//build/config/ios/entitlements.plist" + if (defined(invoker.entitlements_path)) { + _entitlements_path = invoker.entitlements_path + } + } else { + assert(!defined(invoker.entitlements_path), + "Cannot define both entitlements_path and entitlements_target " + + "for $target_name") + + _entitlements_target_outputs = + get_target_outputs(invoker.entitlements_target) + _entitlements_path = _entitlements_target_outputs[0] + } + } + + _enable_code_signing = ios_enable_code_signing + if (defined(invoker.enable_code_signing)) { + _enable_code_signing = invoker.enable_code_signing + } + + if (!ios_set_attributes_for_xcode_project_generation) { + not_needed(invoker, + [ + "xcode_product_bundle_id", + "xcode_extra_attributes", + ]) + } + + create_bundle(_target_name) { + forward_variables_from(invoker, + [ + "bundle_deps_filter", + "data_deps", + "deps", + "partial_info_plist", + "product_type", + "public_configs", + "public_deps", + "testonly", + "visibility", + "xcode_test_application_name", + ]) + + bundle_root_dir = "$_bundle_gen_dir/$_output_name$_bundle_extension" + if (target_environment == "simulator" || target_environment == "device") { + bundle_contents_dir = bundle_root_dir + bundle_resources_dir = bundle_contents_dir + bundle_executable_dir = bundle_contents_dir + } else if (target_environment == "catalyst") { + if (_bundle_extension != ".framework") { + bundle_contents_dir = "$bundle_root_dir/Contents" + bundle_resources_dir = "$bundle_contents_dir/Resources" + bundle_executable_dir = "$bundle_contents_dir/MacOS" + } else { + bundle_contents_dir = "$bundle_root_dir/Versions/A" + bundle_resources_dir = "$bundle_contents_dir/Resources" + bundle_executable_dir = bundle_contents_dir + } + } + + if (!defined(public_deps)) { + public_deps = [] + } + + if (ios_set_attributes_for_xcode_project_generation) { + _xcode_product_bundle_id = "" + if (defined(invoker.xcode_product_bundle_id)) { + _xcode_product_bundle_id = invoker.xcode_product_bundle_id + } + + if (_xcode_product_bundle_id != "") { + _ios_provisioning_profile_info = + exec_script("//build/config/ios/codesign.py", + [ + "find-provisioning-profile", + "-b=" + _xcode_product_bundle_id, + ], + "json") + } + + xcode_extra_attributes = { + IPHONEOS_DEPLOYMENT_TARGET = ios_deployment_target + if (_xcode_product_bundle_id != "") { + CODE_SIGN_IDENTITY = "iPhone Developer" + DEVELOPMENT_TEAM = _ios_provisioning_profile_info.team_identifier + PRODUCT_BUNDLE_IDENTIFIER = _xcode_product_bundle_id + PROVISIONING_PROFILE_SPECIFIER = _ios_provisioning_profile_info.name + } + + # If invoker has defined extra attributes, they override the defaults. + if (defined(invoker.xcode_extra_attributes)) { + forward_variables_from(invoker.xcode_extra_attributes, "*") + } + } + } + + if (defined(invoker.bundle_binary_target)) { + public_deps += [ invoker.bundle_binary_target ] + } + + if (defined(invoker.bundle_deps)) { + if (!defined(deps)) { + deps = [] + } + deps += invoker.bundle_deps + } + if (!defined(deps)) { + deps = [] + } + + code_signing_script = "//build/config/ios/codesign.py" + code_signing_sources = [ _bundle_binary_path ] + if (_enable_entitlements) { + if (defined(invoker.entitlements_target)) { + deps += [ invoker.entitlements_target ] + } + code_signing_sources += [ _entitlements_path ] + } + code_signing_outputs = [ "$bundle_executable_dir/$_output_name" ] + if (_enable_code_signing) { + code_signing_outputs += + [ "$bundle_contents_dir/_CodeSignature/CodeResources" ] + } + if (ios_code_signing_identity != "" && target_environment == "device" && + _enable_embedded_mobileprovision) { + code_signing_outputs += + [ "$bundle_contents_dir/embedded.mobileprovision" ] + } + if (_bundle_extension == ".framework") { + if (target_environment == "catalyst") { + code_signing_outputs += [ + "$bundle_root_dir/Versions/Current", + "$bundle_root_dir/$_output_name", + ] + + if (defined(invoker.has_public_headers) && invoker.has_public_headers) { + code_signing_outputs += [ + "$bundle_root_dir/Headers", + "$bundle_root_dir/Modules", + ] + } + } else { + not_needed(invoker, [ "has_public_headers" ]) + } + } + + if (defined(invoker.extra_system_frameworks)) { + foreach(_framework, invoker.extra_system_frameworks) { + code_signing_outputs += [ "$bundle_contents_dir/Frameworks/" + + get_path_info(_framework, "file") ] + } + } + + code_signing_args = [ + "code-sign-bundle", + "-t=" + ios_sdk_name, + "-i=" + ios_code_signing_identity, + "-b=" + rebase_path(_bundle_binary_path, root_build_dir), + ] + if (_enable_entitlements) { + code_signing_args += + [ "-e=" + rebase_path(_entitlements_path, root_build_dir) ] + } + if (!_enable_embedded_mobileprovision) { + code_signing_args += [ "--disable-embedded-mobileprovision" ] + } + code_signing_args += [ rebase_path(bundle_root_dir, root_build_dir) ] + if (!_enable_code_signing) { + code_signing_args += [ "--disable-code-signature" ] + } + if (defined(invoker.extra_system_frameworks)) { + # All framework in extra_system_frameworks are expected to be system + # framework and the path to be already system absolute so do not use + # rebase_path here unless using Goma RBE and system Xcode (as in that + # case the system framework are found via a symlink in root_build_dir). + foreach(_framework, invoker.extra_system_frameworks) { + if (use_system_xcode && use_goma) { + _framework_path = rebase_path(_framework, root_build_dir) + } else { + _framework_path = _framework + } + code_signing_args += [ "-F=$_framework_path" ] + } + } + if (defined(invoker.partial_info_plist)) { + _partial_info_plists = [ + invoker.primary_info_plist, + invoker.partial_info_plist, + ] + + _plist_compiler_path = "//build/apple/plist_util.py" + + code_signing_sources += _partial_info_plists + code_signing_sources += [ _plist_compiler_path ] + if (target_environment != "catalyst" || + _bundle_extension != ".framework") { + code_signing_outputs += [ "$bundle_contents_dir/Info.plist" ] + } else { + code_signing_outputs += [ "$bundle_resources_dir/Info.plist" ] + } + + code_signing_args += + [ "-P=" + rebase_path(_plist_compiler_path, root_build_dir) ] + foreach(_partial_info_plist, _partial_info_plists) { + code_signing_args += + [ "-p=" + rebase_path(_partial_info_plist, root_build_dir) ] + } + } + } +} + +# Generates Info.plist files for Mac apps and frameworks. +# +# Arguments +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# executable_name: +# string, name of the generated target used for the product +# and executable name as specified in the output Info.plist. +# +# extra_substitutions: +# (optional) string array, 'key=value' pairs for extra fields which are +# specified in a source Info.plist template. +template("ios_info_plist") { + assert(defined(invoker.info_plist) != defined(invoker.info_plist_target), + "Only one of info_plist or info_plist_target may be specified in " + + target_name) + + if (defined(invoker.info_plist)) { + _info_plist = invoker.info_plist + } else { + _info_plist_target_output = get_target_outputs(invoker.info_plist_target) + _info_plist = _info_plist_target_output[0] + } + + apple_info_plist(target_name) { + format = "binary1" + extra_substitutions = [] + if (defined(invoker.extra_substitutions)) { + extra_substitutions = invoker.extra_substitutions + } + extra_substitutions += [ + "IOS_BUNDLE_ID_PREFIX=$ios_app_bundle_id_prefix", + "IOS_PLATFORM_BUILD=$ios_platform_build", + "IOS_PLATFORM_NAME=$ios_sdk_name", + "IOS_PLATFORM_VERSION=$ios_sdk_version", + "IOS_SDK_BUILD=$ios_sdk_build", + "IOS_SDK_NAME=$ios_sdk_name$ios_sdk_version", + "IOS_SUPPORTED_PLATFORM=$ios_sdk_platform", + "BUILD_MACHINE_OS_BUILD=$machine_os_build", + "IOS_DEPLOYMENT_TARGET=$ios_deployment_target", + "XCODE_BUILD=$xcode_build", + "XCODE_VERSION=$xcode_version", + ] + plist_templates = [ + "//build/config/ios/BuildInfo.plist", + _info_plist, + ] + if (defined(invoker.info_plist_target)) { + deps = [ invoker.info_plist_target ] + } + forward_variables_from(invoker, + [ + "executable_name", + "output_name", + "visibility", + "testonly", + ]) + } +} + +# Template to build an application bundle for iOS. +# +# This should be used instead of "executable" built-in target type on iOS. +# As the template forward the generation of the application executable to +# an "executable" target, all arguments supported by "executable" targets +# are also supported by this template. +# +# Arguments +# +# output_name: +# (optional) string, name of the generated application, if omitted, +# defaults to the target_name. +# +# extra_substitutions: +# (optional) list of string in "key=value" format, each value will +# be used as an additional variable substitution rule when generating +# the application Info.plist +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# entitlements_path: +# (optional) path to the template to use to generate the application +# entitlements by performing variable substitutions, defaults to +# //build/config/ios/entitlements.plist. +# +# entitlements_target: +# (optional) label of the target generating the application +# entitlements (must generate a single file as output); cannot be +# defined if entitlements_path is set. +# +# product_type +# (optional) string, product type for the generated Xcode project, +# default to "com.apple.product-type.application". Should only be +# overriden when building application extension. +# +# enable_code_signing +# (optional) boolean, control whether code signing is enabled or not, +# default to ios_enable_code_signing if not defined. +# +# variants +# (optional) list of scopes, each scope needs to define the attributes +# "name" and "bundle_deps"; if defined and non-empty, then one bundle +# named $target_out_dir/$variant/$output_name will be created for each +# variant with the same binary but the correct bundle_deps, the bundle +# at $target_out_dir/$output_name will be a copy of the first variant. +# +# xcode_product_bundle_id: +# (optional) string, the bundle ID that will be added in the XCode +# attributes to enable some features when debugging (e.g. MetricKit). +# defaults to "$ios_app_bundle_id_prefix.$output_name". +# +# For more information, see "gn help executable". +template("ios_app_bundle") { + _output_name = target_name + _target_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _primary_toolchain = current_toolchain + if (is_fat_secondary_toolchain) { + _primary_toolchain = primary_fat_toolchain_name + } + + assert( + !defined(invoker.bundle_extension), + "bundle_extension must not be set for ios_app_bundle template for $target_name") + + _xcode_product_bundle_id = "$ios_app_bundle_id_prefix.$_output_name" + if (defined(invoker.xcode_product_bundle_id)) { + _xcode_product_bundle_id = invoker.xcode_product_bundle_id + _xcode_product_bundle_id = + "$ios_app_bundle_id_prefix.$_xcode_product_bundle_id" + } else if (defined(invoker.bundle_id)) { + _xcode_product_bundle_id = invoker.bundle_id + } + + # Bundle ID should respect rfc1034 and replace _ with -. + _xcode_product_bundle_id = + string_replace("$_xcode_product_bundle_id", "_", "-") + + _arch_executable_source = _target_name + "_arch_executable_sources" + _arch_executable_target = _target_name + "_arch_executable" + _lipo_executable_target = _target_name + "_executable" + + if (defined(invoker.variants) && invoker.variants != []) { + _variants = [] + + foreach(_variant, invoker.variants) { + assert(defined(_variant.name) && _variant.name != "", + "name must be defined for all $target_name variants") + + assert(defined(_variant.bundle_deps), + "bundle_deps must be defined for all $target_name variants") + + _variants += [ + { + name = _variant.name + bundle_deps = _variant.bundle_deps + target_name = "${_target_name}_variants_${_variant.name}" + bundle_gen_dir = "$root_out_dir/variants/${_variant.name}" + }, + ] + } + } else { + # If no variants are passed to the template, use a fake variant with + # no name to avoid duplicating code. As no variant can have an empty + # name except this fake variant, it is possible to know if a variant + # is fake or not. + _variants = [ + { + name = "" + bundle_deps = [] + target_name = _target_name + bundle_gen_dir = root_out_dir + }, + ] + } + + _default_variant = _variants[0] + + source_set(_arch_executable_source) { + forward_variables_from(invoker, + "*", + [ + "bundle_deps", + "bundle_deps_filter", + "bundle_extension", + "enable_code_signing", + "entitlements_path", + "entitlements_target", + "extra_substitutions", + "extra_system_frameworks", + "info_plist", + "info_plist_target", + "output_name", + "product_type", + "visibility", + "xcode_extra_attributes", + ]) + + visibility = [ ":$_arch_executable_target" ] + } + + if (!is_fat_secondary_toolchain || target_environment == "simulator") { + _generate_entitlements_target = _target_name + "_gen_entitlements" + _generate_entitlements_output = + get_label_info(":$_generate_entitlements_target($_primary_toolchain)", + "target_out_dir") + "/$_output_name.xcent" + } + + _product_type = _ios_xcode_app_bundle_id + if (defined(invoker.product_type)) { + _product_type = invoker.product_type + } + + if (_product_type == _ios_xcode_app_bundle_id) { + _bundle_extension = ".app" + } else if (_product_type == _ios_xcode_appex_bundle_id) { + _bundle_extension = ".appex" + } else { + assert(false, "unknown product_type \"$product_type\" for $_target_name") + } + + _is_app_bundle = _product_type == _ios_xcode_app_bundle_id + + executable(_arch_executable_target) { + forward_variables_from(invoker, + "*", + [ + "bundle_deps", + "bundle_deps_filter", + "bundle_extension", + "enable_code_signing", + "entitlements_path", + "entitlements_target", + "extra_substitutions", + "extra_system_frameworks", + "info_plist", + "info_plist_target", + "output_name", + "product_type", + "sources", + "visibility", + "xcode_extra_attributes", + ]) + + visibility = [ ":$_lipo_executable_target($_primary_toolchain)" ] + if (is_fat_secondary_toolchain) { + visibility += [ ":$_target_name" ] + } + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_arch_executable_source" ] + + if (!defined(frameworks)) { + frameworks = [] + } + frameworks += [ "UIKit.framework" ] + + if (target_environment == "simulator") { + deps += [ ":$_generate_entitlements_target($_primary_toolchain)" ] + + if (!defined(inputs)) { + inputs = [] + } + inputs += [ _generate_entitlements_output ] + + if (!defined(ldflags)) { + ldflags = [] + } + ldflags += [ "-Wl,-sectcreate,__TEXT,__entitlements," + + rebase_path(_generate_entitlements_output, root_build_dir) ] + } + + output_name = _output_name + output_prefix_override = true + output_dir = "$target_out_dir/$target_cpu" + } + + if (is_fat_secondary_toolchain) { + # For fat builds, only the default toolchain will generate an application + # bundle. For the other toolchains, the template is only used for building + # the arch-specific binary, thus the default target is just a group(). + + group(_target_name) { + forward_variables_from(invoker, + [ + "visibility", + "testonly", + ]) + public_deps = [ ":$_arch_executable_target" ] + } + } else { + lipo_binary(_lipo_executable_target) { + forward_variables_from(invoker, + [ + "configs", + "testonly", + ]) + + visibility = [] + foreach(_variant, _variants) { + visibility += [ ":${_variant.target_name}" ] + } + + output_name = _output_name + arch_binary_target = ":$_arch_executable_target" + arch_binary_output = _output_name + } + + _generate_info_plist = target_name + "_generate_info_plist" + ios_info_plist(_generate_info_plist) { + forward_variables_from(invoker, + [ + "extra_substitutions", + "info_plist", + "info_plist_target", + ]) + + executable_name = _output_name + } + + if (!is_fat_secondary_toolchain) { + if (!defined(invoker.entitlements_target)) { + _entitlements_path = "//build/config/ios/entitlements.plist" + if (defined(invoker.entitlements_path)) { + _entitlements_path = invoker.entitlements_path + } + } else { + assert(!defined(invoker.entitlements_path), + "Cannot define both entitlements_path and entitlements_target" + + "for $_target_name") + + _entitlements_target_outputs = + get_target_outputs(invoker.entitlements_target) + _entitlements_path = _entitlements_target_outputs[0] + } + + action(_generate_entitlements_target) { + _gen_info_plist_outputs = get_target_outputs(":$_generate_info_plist") + _info_plist_path = _gen_info_plist_outputs[0] + + script = "//build/config/ios/codesign.py" + deps = [ ":$_generate_info_plist" ] + if (defined(invoker.entitlements_target)) { + deps += [ invoker.entitlements_target ] + } + sources = [ + _entitlements_path, + _info_plist_path, + ] + outputs = [ _generate_entitlements_output ] + + args = [ + "generate-entitlements", + "-e=" + rebase_path(_entitlements_path, root_build_dir), + "-p=" + rebase_path(_info_plist_path, root_build_dir), + ] + rebase_path(outputs, root_build_dir) + } + } + + # Only write PkgInfo for real application, not application extension. + if (_is_app_bundle) { + _create_pkg_info = target_name + "_pkg_info" + action(_create_pkg_info) { + forward_variables_from(invoker, [ "testonly" ]) + script = "//build/apple/write_pkg_info.py" + inputs = [ "//build/apple/plist_util.py" ] + sources = get_target_outputs(":$_generate_info_plist") + outputs = [ + # Cannot name the output PkgInfo as the name will not be unique if + # multiple ios_app_bundle are defined in the same BUILD.gn file. The + # file is renamed in the bundle_data outputs to the correct name. + "$target_gen_dir/$target_name", + ] + args = [ "--plist" ] + rebase_path(sources, root_build_dir) + + [ "--output" ] + rebase_path(outputs, root_build_dir) + deps = [ ":$_generate_info_plist" ] + } + + _bundle_data_pkg_info = target_name + "_bundle_data_pkg_info" + bundle_data(_bundle_data_pkg_info) { + forward_variables_from(invoker, [ "testonly" ]) + sources = get_target_outputs(":$_create_pkg_info") + outputs = [ "{{bundle_resources_dir}}/PkgInfo" ] + public_deps = [ ":$_create_pkg_info" ] + } + } + + foreach(_variant, _variants) { + create_signed_bundle(_variant.target_name) { + forward_variables_from(invoker, + [ + "bundle_deps", + "bundle_deps_filter", + "data_deps", + "deps", + "enable_code_signing", + "entitlements_path", + "entitlements_target", + "extra_system_frameworks", + "public_configs", + "public_deps", + "testonly", + "visibility", + "xcode_extra_attributes", + ]) + + output_name = _output_name + bundle_gen_dir = _variant.bundle_gen_dir + bundle_binary_target = ":$_lipo_executable_target" + bundle_binary_output = _output_name + bundle_extension = _bundle_extension + product_type = _product_type + xcode_product_bundle_id = _xcode_product_bundle_id + + _generate_info_plist_outputs = + get_target_outputs(":$_generate_info_plist") + primary_info_plist = _generate_info_plist_outputs[0] + partial_info_plist = + "$target_gen_dir/${_variant.target_name}_partial_info.plist" + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_generate_info_plist" ] + + if (!defined(bundle_deps)) { + bundle_deps = [] + } + if (_is_app_bundle) { + bundle_deps += [ ":$_bundle_data_pkg_info" ] + } + bundle_deps += _variant.bundle_deps + + if (target_environment == "simulator") { + if (!defined(data_deps)) { + data_deps = [] + } + data_deps += [ "//testing/iossim" ] + } + } + } + + if (_default_variant.name != "") { + _bundle_short_name = "$_output_name$_bundle_extension" + action(_target_name) { + forward_variables_from(invoker, [ "testonly" ]) + + script = "//build/config/ios/hardlink.py" + public_deps = [] + foreach(_variant, _variants) { + public_deps += [ ":${_variant.target_name}" ] + } + + sources = [ "${_default_variant.bundle_gen_dir}/$_bundle_short_name" ] + outputs = [ "$root_out_dir/$_bundle_short_name" ] + + args = rebase_path(sources, root_build_dir) + + rebase_path(outputs, root_build_dir) + } + } + } + + if (is_fat_secondary_toolchain) { + not_needed("*") + } +} + +set_defaults("ios_app_bundle") { + configs = default_executable_configs +} + +# Template to build an application extension bundle for iOS. +# +# This should be used instead of "executable" built-in target type on iOS. +# As the template forward the generation of the application executable to +# an "executable" target, all arguments supported by "executable" targets +# are also supported by this template. +# +# Arguments +# +# output_name: +# (optional) string, name of the generated application, if omitted, +# defaults to the target_name. +# +# extra_substitutions: +# (optional) list of string in "key=value" format, each value will +# be used as an additional variable substitution rule when generating +# the application Info.plist +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# For more information, see "gn help executable". +template("ios_appex_bundle") { + ios_app_bundle(target_name) { + forward_variables_from(invoker, + "*", + [ + "bundle_extension", + "product_type", + ]) + product_type = _ios_xcode_appex_bundle_id + } +} + +set_defaults("ios_appex_bundle") { + configs = [ "//build/config/ios:ios_extension_executable_flags" ] +} + +# Template to compile .xib and .storyboard files. +# +# Arguments +# +# sources: +# list of string, sources to compile +# +# ibtool_flags: +# (optional) list of string, additional flags to pass to the ibtool +template("compile_ib_files") { + action_foreach(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + assert(defined(invoker.sources), + "sources must be specified for $target_name") + assert(defined(invoker.output_extension), + "output_extension must be specified for $target_name") + + ibtool_flags = [] + if (defined(invoker.ibtool_flags)) { + ibtool_flags = invoker.ibtool_flags + } + + _output_extension = invoker.output_extension + + script = "//build/config/ios/compile_ib_files.py" + sources = invoker.sources + outputs = [ + "$target_gen_dir/$target_name/{{source_name_part}}.$_output_extension", + ] + args = [ + "--input", + "{{source}}", + "--output", + rebase_path( + "$target_gen_dir/$target_name/{{source_name_part}}.$_output_extension", + root_build_dir), + ] + args += ibtool_flags + } +} + +# Compile a xib or storyboard file and add it to a bundle_data so that it is +# available at runtime in the bundle. +# +# Arguments +# +# source: +# string, path of the xib or storyboard to compile. +# +# Forwards all variables to the bundle_data target. +template("bundle_data_ib_file") { + assert(defined(invoker.source), "source needs to be defined for $target_name") + + _source_extension = get_path_info(invoker.source, "extension") + assert(_source_extension == "xib" || _source_extension == "storyboard", + "source must be a .xib or .storyboard for $target_name") + + _target_name = target_name + if (_source_extension == "xib") { + _compile_ib_file = target_name + "_compile_xib" + _output_extension = "nib" + } else { + _compile_ib_file = target_name + "_compile_storyboard" + _output_extension = "storyboardc" + } + + compile_ib_files(_compile_ib_file) { + sources = [ invoker.source ] + output_extension = _output_extension + visibility = [ ":$_target_name" ] + ibtool_flags = [ + "--minimum-deployment-target", + ios_deployment_target, + "--auto-activate-custom-fonts", + "--target-device", + "iphone", + "--target-device", + "ipad", + ] + } + + bundle_data(_target_name) { + forward_variables_from(invoker, "*", [ "source" ]) + + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ ":$_compile_ib_file" ] + + sources = get_target_outputs(":$_compile_ib_file") + + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + } +} + +# Compile a strings file and add it to a bundle_data so that it is available +# at runtime in the bundle. +# +# Arguments +# +# source: +# string, path of the strings file to compile. +# +# output: +# string, path of the compiled file in the final bundle. +# +# Forwards all variables to the bundle_data target. +template("bundle_data_strings") { + assert(defined(invoker.source), "source needs to be defined for $target_name") + assert(defined(invoker.output), "output needs to be defined for $target_name") + + _source_extension = get_path_info(invoker.source, "extension") + assert(_source_extension == "strings", + "source must be a .strings for $target_name") + + _target_name = target_name + _convert_target = target_name + "_compile_strings" + + convert_plist(_convert_target) { + visibility = [ ":$_target_name" ] + source = invoker.source + output = + "$target_gen_dir/$_target_name/" + get_path_info(invoker.source, "file") + format = "binary1" + } + + bundle_data(_target_name) { + forward_variables_from(invoker, + "*", + [ + "source", + "output", + ]) + + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ ":$_convert_target" ] + + sources = get_target_outputs(":$_convert_target") + + outputs = [ invoker.output ] + } +} + +# Template to package a shared library into an iOS framework bundle. +# +# By default, the bundle target this template generates does not link the +# resulting framework into anything that depends on it. If a dependency wants +# a link-time (as well as build-time) dependency on the framework bundle, +# depend against "$target_name+link". If only the build-time dependency is +# required (e.g., for copying into another bundle), then use "$target_name". +# +# Arguments +# +# output_name: +# (optional) string, name of the generated framework without the +# .framework suffix. If omitted, defaults to target_name. +# +# public_headers: +# (optional) list of paths to header file that needs to be copied +# into the framework bundle Headers subdirectory. If omitted or +# empty then the Headers subdirectory is not created. +# +# sources +# (optional) list of files. Needs to be defined and non-empty if +# public_headers is defined and non-empty. +# +# enable_code_signing +# (optional) boolean, control whether code signing is enabled or not, +# default to ios_enable_code_signing if not defined. +# +# This template provides two targets for the resulting framework bundle. The +# link-time behavior varies depending on which of the two targets below is +# added as a dependency: +# - $target_name only adds a build-time dependency. Targets that depend on +# it will not link against the framework. +# - $target_name+link adds a build-time and link-time dependency. Targets +# that depend on it will link against the framework. +# +# The build-time-only dependency is used for when a target needs to use the +# framework either only for resources, or because the target loads it at run- +# time, via dlopen() or NSBundle. The link-time dependency will cause the +# dependee to have the framework loaded by dyld at launch. +# +# Example of build-time only dependency: +# +# framework_bundle("CoreTeleportation") { +# sources = [ ... ] +# } +# +# bundle_data("core_teleportation_bundle_data") { +# deps = [ ":CoreTeleportation" ] +# sources = [ "$root_out_dir/CoreTeleportation.framework" ] +# outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ] +# } +# +# app_bundle("GoatTeleporter") { +# sources = [ ... ] +# deps = [ +# ":core_teleportation_bundle_data", +# ] +# } +# +# The GoatTeleporter.app will not directly link against +# CoreTeleportation.framework, but it will be included in the bundle's +# Frameworks directory. +# +# Example of link-time dependency: +# +# framework_bundle("CoreTeleportation") { +# sources = [ ... ] +# ldflags = [ +# "-install_name", +# "@executable_path/../Frameworks/$target_name.framework" +# ] +# } +# +# bundle_data("core_teleportation_bundle_data") { +# deps = [ ":CoreTeleportation+link" ] +# sources = [ "$root_out_dir/CoreTeleportation.framework" ] +# outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ] +# } +# +# app_bundle("GoatTeleporter") { +# sources = [ ... ] +# deps = [ +# ":core_teleportation_bundle_data", +# ] +# } +# +# Note that the framework is still copied to the app's bundle, but dyld will +# load this library when the app is launched because it uses the "+link" +# target as a dependency. This also requires that the framework set its +# install_name so that dyld can locate it. +# +# See "gn help shared_library" for more information on arguments supported +# by shared library target. +template("ios_framework_bundle") { + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _has_public_headers = + defined(invoker.public_headers) && invoker.public_headers != [] + + _primary_toolchain = current_toolchain + if (is_fat_secondary_toolchain) { + _primary_toolchain = primary_fat_toolchain_name + } + + # Public configs are not propagated across toolchain (see crbug.com/675224) + # so some configs have to be defined for both default_toolchain and all others + # toolchains when performing a fat build. Use "get_label_info" to construct + # the path since they need to be relative to the default_toolchain. + + _default_toolchain_root_out_dir = + get_label_info("$_target_name($_primary_toolchain)", "root_out_dir") + + _arch_shared_library_source = _target_name + "_arch_shared_library_sources" + _arch_shared_library_target = _target_name + "_arch_shared_library" + _lipo_shared_library_target = _target_name + "_shared_library" + _link_target_name = _target_name + "+link" + + if (_has_public_headers) { + _default_toolchain_target_gen_dir = + get_label_info("$_target_name($_primary_toolchain)", "target_gen_dir") + + _framework_headers_target = _target_name + "_framework_headers" + + _headers_map_config = _target_name + "_headers_map" + _header_map_filename = + "$_default_toolchain_target_gen_dir/$_output_name.headers.hmap" + config(_headers_map_config) { + visibility = [ + ":${_arch_shared_library_source}", + ":${_target_name}_signed_bundle", + ] + include_dirs = [ _header_map_filename ] + } + } + + _framework_headers_config = _target_name + "_framework_headers_config" + config(_framework_headers_config) { + framework_dirs = [ _default_toolchain_root_out_dir ] + } + + _framework_public_config = _target_name + "_public_config" + config(_framework_public_config) { + configs = [ ":$_framework_headers_config" ] + frameworks = [ "$_output_name.framework" ] + } + + source_set(_arch_shared_library_source) { + forward_variables_from(invoker, + "*", + [ + "bundle_deps", + "bundle_deps_filter", + "data_deps", + "enable_code_signing", + "extra_substitutions", + "info_plist", + "info_plist_target", + "output_name", + "public_configs", + "visibility", + ]) + + visibility = [ ":$_arch_shared_library_target" ] + + if (_has_public_headers) { + configs += [ ":$_headers_map_config" ] + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_framework_headers_target($_primary_toolchain)" ] + } + } + + shared_library(_arch_shared_library_target) { + forward_variables_from(invoker, + "*", + [ + "bundle_deps", + "bundle_deps_filter", + "data_deps", + "enable_code_signing", + "extra_substitutions", + "info_plist", + "info_plist_target", + "output_name", + "sources", + "public_configs", + "visibility", + ]) + + visibility = [ ":$_lipo_shared_library_target($_primary_toolchain)" ] + if (is_fat_secondary_toolchain) { + visibility += [ + ":${_target_name}", + ":${_target_name}_signed_bundle", + ] + } + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_arch_shared_library_source" ] + if (_has_public_headers) { + deps += [ ":$_framework_headers_target($_primary_toolchain)" ] + } + if (!defined(ldflags)) { + ldflags = [] + } + ldflags += + [ "-Wl,-install_name,@rpath/$_output_name.framework/$_output_name" ] + + output_extension = "" + output_name = _output_name + output_prefix_override = true + output_dir = "$target_out_dir/$target_cpu" + } + + if (is_fat_secondary_toolchain) { + # For fat builds, only the default toolchain will generate a framework + # bundle. For the other toolchains, the template is only used for building + # the arch-specific binary, thus the default target is just a group(). + + group(_target_name) { + forward_variables_from(invoker, + [ + "visibility", + "testonly", + ]) + public_deps = [ ":$_arch_shared_library_target" ] + } + + group(_link_target_name) { + forward_variables_from(invoker, + [ + "public_configs", + "visibility", + "testonly", + ]) + public_deps = [ ":$_link_target_name($_primary_toolchain)" ] + + if (_has_public_headers) { + if (!defined(public_configs)) { + public_configs = [] + } + public_configs += [ ":$_framework_headers_config" ] + } + if (!defined(all_dependent_configs)) { + all_dependent_configs = [] + } + all_dependent_configs += [ ":$_framework_public_config" ] + } + + group("$_target_name+bundle") { + forward_variables_from(invoker, [ "testonly" ]) + public_deps = [ ":$_target_name+bundle($_primary_toolchain)" ] + } + + not_needed(invoker, "*") + } else { + if (_has_public_headers) { + _public_headers = invoker.public_headers + + _framework_root_dir = "$root_out_dir/$_output_name.framework" + if (target_environment == "simulator" || target_environment == "device") { + _framework_contents_dir = _framework_root_dir + } else if (target_environment == "catalyst") { + _framework_contents_dir = "$_framework_root_dir/Versions/A" + } + + _compile_headers_map_target = _target_name + "_compile_headers_map" + action(_compile_headers_map_target) { + visibility = [ ":$_framework_headers_target" ] + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + ]) + script = "//build/config/ios/write_framework_hmap.py" + outputs = [ _header_map_filename ] + + # The header map generation only wants the list of headers, not all of + # sources, so filter any non-header source files from "sources". It is + # less error prone that having the developer duplicate the list of all + # headers in addition to "sources". + sources = [] + foreach(_source, invoker.sources) { + if (get_path_info(_source, "extension") == "h") { + sources += [ _source ] + } + } + + args = [ + rebase_path(_header_map_filename), + rebase_path(_framework_root_dir, root_build_dir), + ] + rebase_path(sources, root_build_dir) + } + + _create_module_map_target = _target_name + "_module_map" + action(_create_module_map_target) { + visibility = [ ":$_framework_headers_target" ] + script = "//build/config/ios/write_framework_modulemap.py" + outputs = [ "$_framework_contents_dir/Modules/module.modulemap" ] + args = [ + _output_name, + rebase_path("$_framework_contents_dir/Modules", root_build_dir), + ] + } + + _copy_public_headers_target = _target_name + "_copy_public_headers" + copy(_copy_public_headers_target) { + forward_variables_from(invoker, + [ + "testonly", + "deps", + ]) + visibility = [ ":$_framework_headers_target" ] + sources = _public_headers + outputs = [ "$_framework_contents_dir/Headers/{{source_file_part}}" ] + + # Do not use forward_variables_from for "public_deps" as + # we do not want to forward those dependencies. + if (defined(invoker.public_deps)) { + if (!defined(deps)) { + deps = [] + } + deps += invoker.public_deps + } + } + + group(_framework_headers_target) { + forward_variables_from(invoker, [ "testonly" ]) + deps = [ + ":$_compile_headers_map_target", + ":$_create_module_map_target", + ] + public_deps = [ ":$_copy_public_headers_target" ] + } + } + + lipo_binary(_lipo_shared_library_target) { + forward_variables_from(invoker, + [ + "configs", + "testonly", + ]) + + visibility = [ ":${_target_name}_signed_bundle" ] + output_name = _output_name + arch_binary_target = ":$_arch_shared_library_target" + arch_binary_output = _output_name + } + + _info_plist_target = _target_name + "_info_plist" + _info_plist_bundle = _target_name + "_info_plist_bundle" + ios_info_plist(_info_plist_target) { + visibility = [ ":$_info_plist_bundle" ] + executable_name = _output_name + forward_variables_from(invoker, + [ + "extra_substitutions", + "info_plist", + "info_plist_target", + ]) + } + + bundle_data(_info_plist_bundle) { + visibility = [ ":${_target_name}_signed_bundle" ] + forward_variables_from(invoker, [ "testonly" ]) + sources = get_target_outputs(":$_info_plist_target") + public_deps = [ ":$_info_plist_target" ] + + if (target_environment != "catalyst") { + outputs = [ "{{bundle_contents_dir}}/Info.plist" ] + } else { + outputs = [ "{{bundle_resources_dir}}/Info.plist" ] + } + } + + create_signed_bundle(_target_name + "_signed_bundle") { + forward_variables_from(invoker, + [ + "bundle_deps", + "bundle_deps_filter", + "data_deps", + "deps", + "enable_code_signing", + "public_configs", + "public_deps", + "testonly", + "visibility", + ]) + + product_type = "com.apple.product-type.framework" + bundle_extension = ".framework" + + output_name = _output_name + bundle_binary_target = ":$_lipo_shared_library_target" + bundle_binary_output = _output_name + + has_public_headers = _has_public_headers + + # Framework do not have entitlements nor mobileprovision because they use + # the one from the bundle using them (.app or .appex) as they are just + # dynamic library with shared code. + disable_entitlements = true + disable_embedded_mobileprovision = true + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_info_plist_bundle" ] + } + + group(_target_name) { + forward_variables_from(invoker, + [ + "public_configs", + "public_deps", + "testonly", + "visibility", + ]) + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ ":${_target_name}_signed_bundle" ] + + if (_has_public_headers) { + if (!defined(public_configs)) { + public_configs = [] + } + public_configs += [ ":$_framework_headers_config" ] + } + } + + group(_link_target_name) { + forward_variables_from(invoker, + [ + "public_configs", + "public_deps", + "testonly", + "visibility", + ]) + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ ":$_target_name" ] + + if (!defined(all_dependent_configs)) { + all_dependent_configs = [] + } + all_dependent_configs += [ ":$_framework_public_config" ] + } + + bundle_data(_target_name + "+bundle") { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + public_deps = [ ":$_target_name" ] + sources = [ "$root_out_dir/$_output_name.framework" ] + outputs = [ "{{bundle_contents_dir}}/Frameworks/$_output_name.framework" ] + } + } +} + +set_defaults("ios_framework_bundle") { + configs = default_shared_library_configs +} + +# Template to build a xctest bundle that contains a loadable module for iOS. +# +# Arguments +# +# deps: +# list of labels to depends on, these values are used to create the +# loadable module. +# +# product_type +# string, product type for the generated Xcode project, use +# "com.apple.product-type.bundle.unit-test" for unit test and +# "com.apple.product-type.bundle.ui-testing" for UI testing. +# +# host_target: +# string, name of the target that depends on the generated bundle, this +# value is used to restrict visibilities. +# +# xcode_test_application_name: +# string, name of the test application for Xcode unit or ui test target. +# +# output_name +# (optional) string, name of the generated application, if omitted, +# defaults to the target_name. +# +# This template defines two targets, one named "${target_name}" is the xctest +# bundle, and the other named "${target_name}_bundle" is a bundle_data that +# wraps the xctest bundle and that only the "${host_target}" can depend on. +# +template("ios_xctest_bundle") { + assert(defined(invoker.deps), "deps must be defined for $target_name") + assert(defined(invoker.product_type), + "product_type must be defined for $target_name") + assert(invoker.product_type == _ios_xcode_xctest_bundle_id || + invoker.product_type == _ios_xcode_xcuitest_bundle_id, + "product_type defined for $target_name is invalid.") + assert(defined(invoker.host_target), + "host_target must be defined for $target_name") + assert(defined(invoker.xcode_test_application_name), + "xcode_test_application_name must be defined for $target_name") + + # Silence "assignment had no effect" error for non-default toolchains as + # following variables are only used in the expansion of the template for the + # default toolchain. + if (is_fat_secondary_toolchain) { + not_needed(invoker, "*") + } + + _target_name = target_name + _output_name = target_name + + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _arch_loadable_module_source = _target_name + "_arch_loadable_module_source" + _arch_loadable_module_target = _target_name + "_arch_loadable_module" + _lipo_loadable_module_target = _target_name + "_loadable_module" + + _primary_toolchain = current_toolchain + if (is_fat_secondary_toolchain) { + _primary_toolchain = primary_fat_toolchain_name + } + + source_set(_arch_loadable_module_source) { + forward_variables_from(invoker, [ "deps" ]) + + testonly = true + visibility = [ ":$_arch_loadable_module_target" ] + } + + loadable_module(_arch_loadable_module_target) { + testonly = true + visibility = [ ":$_lipo_loadable_module_target($_primary_toolchain)" ] + if (is_fat_secondary_toolchain) { + visibility += [ ":$_target_name" ] + } + + deps = [ ":$_arch_loadable_module_source" ] + configs += [ "//build/config/ios:xctest_config" ] + + output_dir = "$target_out_dir/$target_cpu" + output_name = _output_name + output_prefix_override = true + output_extension = "" + } + + if (is_fat_secondary_toolchain) { + # For fat builds, only the default toolchain will generate a test bundle. + # For the other toolchains, the template is only used for building the + # arch-specific binary, thus the default target is just a group(). + group(_target_name) { + forward_variables_from(invoker, [ "visibility" ]) + testonly = true + + public_deps = [ ":$_arch_loadable_module_target" ] + } + + not_needed(invoker, "*") + } else { + _info_plist_target = _target_name + "_info_plist" + _info_plist_bundle = _target_name + "_info_plist_bundle" + + ios_info_plist(_info_plist_target) { + testonly = true + visibility = [ ":$_info_plist_bundle" ] + + info_plist = "//build/config/ios/Module-Info.plist" + executable_name = _output_name + + if (defined(invoker.xctest_bundle_principal_class)) { + _principal_class = invoker.xctest_bundle_principal_class + } else { + # Fall back to a reasonable default value. + _principal_class = "NSObject" + } + extra_substitutions = [ + "XCTEST_BUNDLE_PRINCIPAL_CLASS=${_principal_class}", + "MODULE_BUNDLE_ID=gtest.$_output_name", + ] + } + + bundle_data(_info_plist_bundle) { + testonly = true + visibility = [ ":$_target_name" ] + + public_deps = [ ":$_info_plist_target" ] + + sources = get_target_outputs(":$_info_plist_target") + outputs = [ "{{bundle_contents_dir}}/Info.plist" ] + } + + lipo_binary(_lipo_loadable_module_target) { + forward_variables_from(invoker, [ "configs" ]) + + testonly = true + visibility = [ ":$_target_name" ] + + output_name = _output_name + arch_binary_target = ":$_arch_loadable_module_target" + arch_binary_output = _output_name + } + + _xctest_bundle = _target_name + "_bundle" + create_signed_bundle(_target_name) { + forward_variables_from(invoker, + [ + "bundle_id", + "data_deps", + "enable_code_signing", + "product_type", + "xcode_test_application_name", + ]) + + testonly = true + visibility = [ ":$_xctest_bundle" ] + + bundle_extension = ".xctest" + + output_name = _output_name + bundle_binary_target = ":$_lipo_loadable_module_target" + bundle_binary_output = _output_name + + if (ios_set_attributes_for_xcode_project_generation) { + _xcode_product_bundle_id = + "$ios_app_bundle_id_prefix.gtest.$_output_name" + + _ios_provisioning_profile_info = + exec_script("//build/config/ios/codesign.py", + [ + "find-provisioning-profile", + "-b=" + _xcode_product_bundle_id, + ], + "json") + + xcode_extra_attributes = { + IPHONEOS_DEPLOYMENT_TARGET = ios_deployment_target + CODE_SIGN_IDENTITY = "iPhone Developer" + DEVELOPMENT_TEAM = _ios_provisioning_profile_info.team_identifier + PRODUCT_BUNDLE_IDENTIFIER = _xcode_product_bundle_id + PROVISIONING_PROFILE_SPECIFIER = _ios_provisioning_profile_info.name + + # For XCUITest, Xcode requires specifying the host application name + # via the TEST_TARGET_NAME attribute. + if (invoker.product_type == _ios_xcode_xcuitest_bundle_id) { + TEST_TARGET_NAME = invoker.xcode_test_application_name + } + + # For XCTest, Xcode requires specifying the host application path via + # both BUNDLE_LOADER and TEST_HOST attributes. + if (invoker.product_type == _ios_xcode_xctest_bundle_id) { + _xcode_app_name = invoker.xcode_test_application_name + if (defined(invoker.xcode_test_application_output_name)) { + _xcode_app_name = invoker.xcode_test_application_output_name + } + + BUNDLE_LOADER = "\$(TEST_HOST)" + TEST_HOST = "\$(BUILT_PRODUCTS_DIR)/" + + "${_xcode_app_name}.app/${_xcode_app_name}" + } + } + } else { + not_needed(invoker, + [ + "xcode_test_application_name", + "xcode_test_application_output_name", + ]) + } + + deps = [ ":$_info_plist_bundle" ] + } + + bundle_data(_xctest_bundle) { + forward_variables_from(invoker, [ "host_target" ]) + + testonly = true + visibility = [ ":$host_target" ] + + public_deps = [ ":$_target_name" ] + sources = [ "$root_out_dir/$_output_name.xctest" ] + outputs = [ "{{bundle_contents_dir}}/PlugIns/$_output_name.xctest" ] + } + } +} + +set_defaults("ios_xctest_bundle") { + configs = default_shared_library_configs +} + +# For Chrome on iOS we want to run XCTests for all our build configurations +# (Debug, Release, ...). In addition, the symbols visibility is configured to +# private by default. To simplify testing with those constraints, our tests are +# compiled in the TEST_HOST target instead of the .xctest bundle. +template("ios_xctest_test") { + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _xctest_target = _target_name + "_module" + _xctest_output = _output_name + "_module" + + _host_target = _target_name + _host_output = _output_name + + # Allow invokers to specify their own target for the xctest module, but + # fall back to a default (empty) module otherwise. + if (defined(invoker.xctest_module_target)) { + _xctest_module_target = invoker.xctest_module_target + } else { + _xctest_module_target_name = _xctest_target + "shell_source" + _xctest_module_target = ":$_xctest_module_target_name" + source_set(_xctest_module_target_name) { + sources = [ "//build/config/ios/xctest_shell.mm" ] + + configs += [ "//build/config/ios:xctest_config" ] + } + } + + ios_xctest_bundle(_xctest_target) { + forward_variables_from(invoker, [ "data_deps" ]) + output_name = _xctest_output + product_type = _ios_xcode_xctest_bundle_id + host_target = _host_target + + # TODO(crbug.com/1056328) The change in output name results in a mismatch + # between this value and the ios_app_bundle target name. To mitigate, this + # has been modified to _host_target. output_name is set to _host_output + # to mitigate the naming. + xcode_test_application_name = _host_target + xcode_test_application_output_name = _host_output + + deps = [ _xctest_module_target ] + } + + ios_app_bundle(_host_target) { + forward_variables_from(invoker, "*", [ "testonly" ]) + + testonly = true + output_name = _host_output + configs += [ "//build/config/ios:xctest_config" ] + + if (!defined(invoker.info_plist) && !defined(invoker.info_plist_target)) { + info_plist = "//build/config/ios/Host-Info.plist" + } + + # Xcode needs the following frameworks installed in the application (and + # signed) for the XCTest to run, so install them using + # extra_system_frameworks. + extra_system_frameworks = [ + "$ios_sdk_platform_path/Developer/Library/Frameworks/XCTest.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework", + "$ios_sdk_platform_path/Developer/usr/lib/libXCTestBundleInject.dylib", + ] + + # Xcode 13 now depends on XCTestCore. To keep things future proof, copy over + # everything that Xcode copies. + if (xcode_version_int >= 1300) { + extra_system_frameworks += [ + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestCore.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUIAutomation.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUnit.framework", + "$ios_sdk_platform_path/Developer/usr/lib/libXCTestSwiftSupport.dylib", + ] + } + + _xctest_bundle = _xctest_target + "_bundle" + if (!is_fat_secondary_toolchain) { + if (!defined(bundle_deps)) { + bundle_deps = [] + } + bundle_deps += [ ":$_xctest_bundle" ] + } + } +} + +set_defaults("ios_xctest_test") { + configs = default_executable_configs +} + +# Template to build a xcuitest test runner bundle. +# +# Xcode requires a test runner application with a copy of the XCTest dynamic +# library bundle in it for the XCUITest to run. The test runner bundle is created +# by copying the system bundle XCTRunner.app from Xcode SDK with the plist file +# being properly tweaked, and a xctest and it needs to be code signed in order +# to run on devices. +# +# Arguments +# +# xctest_bundle +# string, name of the dependent xctest bundle target. +# +# output_name +# (optional) string, name of the generated application, if omitted, +# defaults to the target_name. +# +template("ios_xcuitest_test_runner_bundle") { + assert(defined(invoker.xctest_bundle), + "xctest_bundle must be defined for $target_name") + + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _xctrunner_path = + "$ios_sdk_platform_path/Developer/Library/Xcode/Agents/XCTRunner.app" + + _info_plist_merge_plist = _target_name + "_info_plist_merge_plist" + _info_plist_target = _target_name + "_info_plist" + _info_plist_bundle = _target_name + "_info_plist_bundle" + + action(_info_plist_merge_plist) { + testonly = true + script = "//build/apple/plist_util.py" + + sources = [ + "$_xctrunner_path/Info.plist", + + # NOTE: The XCTRunnerAddition+Info.plist must come after the Info.plist + # because it overrides the values under "CFBundleIdentifier" and + # "CFBundleName". + "//build/config/ios/resources/XCTRunnerAddition+Info.plist", + ] + + _output_name = "$target_gen_dir/${_target_name}_merged.plist" + outputs = [ _output_name ] + args = [ + "merge", + "-f=xml1", + "-x=$xcode_version", + "-o=" + rebase_path(_output_name, root_build_dir), + ] + rebase_path(sources, root_build_dir) + + if (use_system_xcode && use_goma) { + deps = [ "//build/config/ios:copy_xctrunner_app" ] + } + } + + ios_info_plist(_info_plist_target) { + testonly = true + visibility = [ ":$_info_plist_bundle" ] + + executable_name = _output_name + info_plist_target = ":$_info_plist_merge_plist" + } + + bundle_data(_info_plist_bundle) { + testonly = true + visibility = [ ":$_target_name" ] + + public_deps = [ ":$_info_plist_target" ] + + sources = get_target_outputs(":$_info_plist_target") + outputs = [ "{{bundle_contents_dir}}/Info.plist" ] + } + + _pkginfo_bundle = _target_name + "_pkginfo_bundle" + bundle_data(_pkginfo_bundle) { + testonly = true + visibility = [ ":$_target_name" ] + + sources = [ "$_xctrunner_path/PkgInfo" ] + + outputs = [ "{{bundle_contents_dir}}/PkgInfo" ] + + if (use_system_xcode && use_goma) { + public_deps = [ "//build/config/ios:copy_xctrunner_app" ] + } + } + + _xctest_bundle = invoker.xctest_bundle + create_signed_bundle(_target_name) { + testonly = true + + bundle_binary_target = "//build/config/ios:xctest_runner_without_arm64e" + bundle_binary_output = "XCTRunner" + bundle_extension = ".app" + product_type = _ios_xcode_app_bundle_id + + output_name = _output_name + + # Xcode needs the following frameworks installed in the application + # (and signed) for the XCUITest to run, so install them using + # extra_system_frameworks. + extra_system_frameworks = [ + "$ios_sdk_platform_path/Developer/Library/Frameworks/XCTest.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework", + ] + + # Xcode 13 now depends on XCTestCore. To keep things future proof, copy over + # everything that Xcode copies. + if (xcode_version_int >= 1300) { + extra_system_frameworks += [ + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestCore.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUIAutomation.framework", + "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUnit.framework", + "$ios_sdk_platform_path/Developer/usr/lib/libXCTestSwiftSupport.dylib", + ] + } + + bundle_deps = [] + if (defined(invoker.bundle_deps)) { + bundle_deps += invoker.bundle_deps + } + bundle_deps += [ + ":$_info_plist_bundle", + ":$_pkginfo_bundle", + ":$_xctest_bundle", + ] + } +} + +# Template to build a XCUITest that consists of two parts: the test runner +# application bundle and the xctest dynamic library. +# +# Arguments +# +# deps: +# list of labels to depends on, these values are used to create the +# xctest dynamic library. +# +# xcode_test_application_name: +# string, name of the test application for the ui test target. +# +# This template defines two targets, one named "${target_name}_module" is the +# xctest dynamic library, and the other named "${target_name}_runner" is the +# test runner application bundle. +# +template("ios_xcuitest_test") { + assert(defined(invoker.deps), "deps must be defined for $target_name") + assert(defined(invoker.xcode_test_application_name), + "xcode_test_application_name must be defined for $target_name") + + _xcuitest_target = target_name + if (defined(invoker.output_name)) { + _xcuitest_target = invoker.output_name + } + + _xcuitest_runner_target = _xcuitest_target + "_runner" + _xcuitest_module_target = _xcuitest_target + "_module" + + group(target_name) { + testonly = true + + deps = [ ":$_xcuitest_runner_target" ] + } + + _xcuitest_module_output = _xcuitest_target + ios_xctest_bundle(_xcuitest_module_target) { + forward_variables_from(invoker, + [ + "xcode_test_application_name", + "xctest_bundle_principal_class", + "data_deps", + ]) + + product_type = _ios_xcode_xcuitest_bundle_id + host_target = _xcuitest_runner_target + output_name = _xcuitest_module_output + + deps = invoker.deps + } + + _xcuitest_runner_output = _xcuitest_target + "-Runner" + ios_xcuitest_test_runner_bundle(_xcuitest_runner_target) { + output_name = _xcuitest_runner_output + xctest_bundle = _xcuitest_module_target + "_bundle" + forward_variables_from(invoker, [ "bundle_deps" ]) + } +} + +set_defaults("ios_xcuitest_test") { + configs = default_executable_configs +} diff --git a/third_party/libwebrtc/build/config/ios/strip_arm64e.py b/third_party/libwebrtc/build/config/ios/strip_arm64e.py new file mode 100644 index 0000000000..f21baf423d --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/strip_arm64e.py @@ -0,0 +1,70 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Strip arm64e architecture from a binary if present.""" + +import argparse +import os +import shutil +import subprocess +import sys + + +def check_output(command): + """Returns the output from |command| or propagates error, quitting script.""" + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + outs, errs = process.communicate() + if process.returncode: + sys.stderr.write('error: command failed with retcode %d: %s\n\n' % + (process.returncode, ' '.join(map(repr, command)))) + sys.stderr.write(errs.decode('UTF-8', errors='ignore')) + sys.exit(process.returncode) + return outs.decode('UTF-8') + + +def check_call(command): + """Invokes |command| or propagates error.""" + check_output(command) + + +def parse_args(args): + """Parses the command-line.""" + parser = argparse.ArgumentParser() + parser.add_argument('--input', required=True, help='Path to input binary') + parser.add_argument('--output', required=True, help='Path to output binary') + parser.add_argument('--xcode-version', required=True, help='Version of Xcode') + return parser.parse_args(args) + + +def get_archs(path): + """Extracts the architectures present in binary at |path|.""" + outputs = check_output(["xcrun", "lipo", "-info", os.path.abspath(path)]) + return outputs.split(': ')[-1].split() + + +def main(args): + parsed = parse_args(args) + + outdir = os.path.dirname(parsed.output) + if not os.path.isdir(outdir): + os.makedirs(outdir) + + if os.path.exists(parsed.output): + os.unlink(parsed.output) + + # As "lipo" fails with an error if asked to remove an architecture that is + # not included, only use it if "arm64e" is present in the binary. Otherwise + # simply copy the file. + if 'arm64e' in get_archs(parsed.input): + check_output([ + "xcrun", "lipo", "-remove", "arm64e", "-output", + os.path.abspath(parsed.output), + os.path.abspath(parsed.input) + ]) + else: + shutil.copy(parsed.input, parsed.output) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/config/ios/write_framework_hmap.py b/third_party/libwebrtc/build/config/ios/write_framework_hmap.py new file mode 100644 index 0000000000..ac467ee92a --- /dev/null +++ b/third_party/libwebrtc/build/config/ios/write_framework_hmap.py @@ -0,0 +1,103 @@ +# 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. + +from __future__ import print_function + +import os +import struct +import sys + +def Main(args): + if len(args) < 4: + print( + "Usage: %s output.hmap Foo.framework header1.h..." % args[0], + file=sys.stderr) + return 1 + + (out, framework, all_headers) = args[1], args[2], args[3:] + + framework_name = os.path.basename(framework).split('.')[0] + all_headers = map(os.path.abspath, all_headers) + filelist = {} + for header in all_headers: + filename = os.path.basename(header) + filelist[filename] = header + filelist[os.path.join(framework_name, filename)] = header + WriteHmap(out, filelist) + return 0 + + +def NextGreaterPowerOf2(x): + return 2**(x).bit_length() + + +def WriteHmap(output_name, filelist): + """Generates a header map based on |filelist|. + + Per Mark Mentovai: + A header map is structured essentially as a hash table, keyed by names used + in #includes, and providing pathnames to the actual files. + + The implementation below and the comment above comes from inspecting: + http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt + while also looking at the implementation in clang in: + https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp + """ + magic = 1751998832 + version = 1 + _reserved = 0 + count = len(filelist) + capacity = NextGreaterPowerOf2(count) + strings_offset = 24 + (12 * capacity) + max_value_length = len(max(filelist.values(), key=lambda v: len(v))) + + out = open(output_name, 'wb') + out.write(struct.pack(' +#import + +// For Chrome on iOS we want to run EarlGrey tests (that are XCTests) for all +// our build configurations (Debug, Release, ...). In addition, the symbols +// visibility is configured to private by default. To simplify testing with +// those constraints, our tests are compiled in the TEST_HOST target instead +// of the .xctest bundle that all link against this single test (just there to +// ensure that the bundle is not empty). + +@interface XCTestShellEmptyClass : NSObject +@end + +@implementation XCTestShellEmptyClass +@end diff --git a/third_party/libwebrtc/build/config/linux/BUILD.gn b/third_party/libwebrtc/build/config/linux/BUILD.gn new file mode 100644 index 0000000000..c036e4ae94 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/BUILD.gn @@ -0,0 +1,79 @@ +# 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("../../../webrtc.gni") + +import("//build/config/c++/c++.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/linux/pkg_config.gni") +import("//build/config/ui.gni") + +group("linux") { + visibility = [ "//:optimize_gn_gen" ] +} + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic that is +# Linux-only. This is not applied to Android, but is applied to ChromeOS. +config("compiler") { + if (target_cpu == "arm64") { + import("//build/config/arm.gni") + cflags = [] + asmflags = [] + if (arm_control_flow_integrity == "standard") { + cflags += [ "-mbranch-protection=standard" ] + asmflags += [ "-mbranch-protection=standard" ] + } + } +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is Linux-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + # Set here because OS_CHROMEOS cannot be autodetected in build_config.h like + # OS_LINUX and the like. + if (is_chromeos) { + defines = [ "OS_CHROMEOS" ] + } + + if ((!(is_chromeos_ash || is_chromeos_lacros) || + default_toolchain != "//build/toolchain/cros:target") && + (!use_custom_libcxx || target_cpu == "mipsel") && !build_with_mozilla) { + libs = [ "atomic" ] + } +} + +config("libcap") { + libs = [ "cap" ] +} + +config("libresolv") { + libs = [ "resolv" ] +} + +if (false && use_glib) { + pkg_config("glib") { + packages = [ + "glib-2.0", + "gmodule-2.0", + "gobject-2.0", + "gthread-2.0", + ] + defines = [ + "GLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_40", + "GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40", + ] + } +} + +# Ensures all exported symbols are added to the dynamic symbol table. This is +# necessary to expose Chrome's custom operator new() and operator delete() (and +# other memory-related symbols) to libraries. Otherwise, they might +# (de)allocate memory on a different heap, which would spell trouble if pointers +# to heap-allocated memory are passed over shared library boundaries. +config("export_dynamic") { + ldflags = [ "-rdynamic" ] +} diff --git a/third_party/libwebrtc/build/config/linux/OWNERS b/third_party/libwebrtc/build/config/linux/OWNERS new file mode 100644 index 0000000000..280ba478dc --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/OWNERS @@ -0,0 +1 @@ +thomasanderson@chromium.org diff --git a/third_party/libwebrtc/build/config/linux/atk/BUILD.gn b/third_party/libwebrtc/build/config/linux/atk/BUILD.gn new file mode 100644 index 0000000000..92baff2a9b --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/atk/BUILD.gn @@ -0,0 +1,35 @@ +# 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. + +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/features.gni") +import("//build/config/linux/pkg_config.gni") +import("//build/config/ui.gni") + +# CrOS doesn't install GTK or any gnome packages. +assert(!is_chromeos) + +# These packages should _only_ be expected when building for a target. +assert(current_toolchain == default_toolchain) + +if (use_atk) { + assert(use_glib, "use_atk=true requires that use_glib=true") +} + +pkg_config("atk") { + packages = [ + "atk", + "atk-bridge-2.0", + ] + atk_lib_dir = exec_script(pkg_config_script, + pkg_config_args + [ + "--libdir", + "atk", + ], + "string") + defines = [ + "ATK_LIB_DIR=\"$atk_lib_dir\"", + "USE_ATK_BRIDGE", + ] +} diff --git a/third_party/libwebrtc/build/config/linux/atspi2/BUILD.gn b/third_party/libwebrtc/build/config/linux/atspi2/BUILD.gn new file mode 100644 index 0000000000..988a995681 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/atspi2/BUILD.gn @@ -0,0 +1,29 @@ +# Copyright 2018 The Chromium 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/ui.gni") + +# These packages should _only_ be expected when building for a target. +assert(current_toolchain == default_toolchain) + +if (use_atk) { + pkg_config("atspi2") { + packages = [ "atspi-2" ] + atspi_version = exec_script(pkg_config_script, + pkg_config_args + [ + "atspi-2", + "--version-as-components", + ], + "value") + atspi_major_version = atspi_version[0] + atspi_minor_version = atspi_version[1] + atspi_micro_version = atspi_version[2] + defines = [ + "ATSPI_MAJOR_VERSION=$atspi_major_version", + "ATSPI_MINOR_VERSION=$atspi_minor_version", + "ATSPI_MICRO_VERSION=$atspi_micro_version", + ] + } +} diff --git a/third_party/libwebrtc/build/config/linux/dbus/BUILD.gn b/third_party/libwebrtc/build/config/linux/dbus/BUILD.gn new file mode 100644 index 0000000000..f11cf7101c --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/dbus/BUILD.gn @@ -0,0 +1,14 @@ +# 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. + +import("//build/config/features.gni") +import("//build/config/linux/pkg_config.gni") + +assert(use_dbus) + +# Note: if your target also depends on //dbus, you don't need to add this +# config (it will get added automatically if you depend on //dbus). +pkg_config("dbus") { + packages = [ "dbus-1" ] +} diff --git a/third_party/libwebrtc/build/config/linux/dri/BUILD.gn b/third_party/libwebrtc/build/config/linux/dri/BUILD.gn new file mode 100644 index 0000000000..cad883b76d --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/dri/BUILD.gn @@ -0,0 +1,18 @@ +# Copyright 2017 The Chromium 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") + +pkg_config("dri") { + packages = [ "dri" ] + dri_driver_dir = exec_script(pkg_config_script, + pkg_config_args + [ + "--dridriverdir", + "dri", + ], + "string") + defines = [ "DRI_DRIVER_DIR=\"$dri_driver_dir\"" ] +} diff --git a/third_party/libwebrtc/build/config/linux/gtk/BUILD.gn b/third_party/libwebrtc/build/config/linux/gtk/BUILD.gn new file mode 100644 index 0000000000..6590023971 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/gtk/BUILD.gn @@ -0,0 +1,69 @@ +# 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. + +import("//build/config/linux/gtk/gtk.gni") +import("//build/config/linux/pkg_config.gni") + +assert(is_linux, "This file should only be referenced on Linux") + +declare_args() { + # The (major) version of GTK to build against. A different version may be + # loaded at runtime. + gtk_version = 3 +} + +# 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", + "gthread-2.0", + ] + if (gtk_version == 3) { + packages += [ "gtk+-3.0" ] + } else { + assert(gtk_version == 4) + packages += [ "gtk4" ] + } +} + +group("gtk") { + visibility = [ + # This is the only target that can depend on GTK. Do not add more targets + # to this list. + "//ui/gtk:gtk_stubs", + + # These are allow-listed for WebRTC builds. + "//examples:peerconnection_client", + "//remoting/host:common", + "//remoting/host:remoting_me2me_host_static", + "//remoting/host/file_transfer", + "//remoting/host/it2me:common", + "//remoting/host/it2me:remote_assistance_host", + "//remoting/host/linux", + "//remoting/host/remote_open_url:common", + "//remoting/test:it2me_standalone_host_main", + "//webrtc/examples:peerconnection_client", + ] + + public_configs = [ ":gtk_internal_config" ] +} + +# Depend on "gtkprint" to get this. +pkg_config("gtkprint_internal_config") { + if (gtk_version == 3) { + packages = [ "gtk+-unix-print-3.0" ] + } else { + assert(gtk_version == 4) + packages = [ "gtk4-unix-print" ] + } +} + +group("gtkprint") { + visibility = [ "//ui/gtk:*" ] + public_configs = [ ":gtkprint_internal_config" ] +} diff --git a/third_party/libwebrtc/build/config/linux/gtk/gtk.gni b/third_party/libwebrtc/build/config/linux/gtk/gtk.gni new file mode 100644 index 0000000000..70cb823154 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/gtk/gtk.gni @@ -0,0 +1,10 @@ +# Copyright 2018 The Chromium 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/ui.gni") + +declare_args() { + # Whether or not we should use libgtk. + use_gtk = (is_linux || is_bsd) && !is_chromecast +} diff --git a/third_party/libwebrtc/build/config/linux/libdrm/BUILD.gn b/third_party/libwebrtc/build/config/linux/libdrm/BUILD.gn new file mode 100644 index 0000000000..79695cbc60 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/libdrm/BUILD.gn @@ -0,0 +1,31 @@ +# Copyright 2018 The Chromium 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/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/linux/pkg_config.gni") + +assert(is_linux || is_chromeos) + +declare_args() { + # Controls whether the build should use the version of libdrm library shipped + # with the system. In release builds of desktop Linux and Chrome OS we use the + # system version. Some Chromecast devices use this as well. + use_system_libdrm = is_chromeos_device || ((is_linux || is_bsd) && !is_chromecast) +} + +if (use_system_libdrm) { + pkg_config("libdrm_config") { + packages = [ "libdrm" ] + } + group("libdrm") { + public_configs = [ ":libdrm_config" ] + } +} else { + group("libdrm") { + public_deps = [ "//third_party/libdrm" ] + } + config("libdrm_exynos_include_config") { + include_dirs = [ "//third_party/libdrm/src/exynos" ] + } +} diff --git a/third_party/libwebrtc/build/config/linux/libffi/BUILD.gn b/third_party/libwebrtc/build/config/linux/libffi/BUILD.gn new file mode 100644 index 0000000000..fb4fa18bf7 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/libffi/BUILD.gn @@ -0,0 +1,18 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") + +if (default_toolchain == "//build/toolchain/cros:target") { + pkg_config("libffi") { + packages = [ "libffi" ] + } +} else { + # On Linux, statically link libffi. This is necessary to prevent a situation + # where the runtime version of libffi is different from the build-time version + # from the sysroot. + config("libffi") { + libs = [ ":libffi_pic.a" ] + } +} diff --git a/third_party/libwebrtc/build/config/linux/libva/BUILD.gn b/third_party/libwebrtc/build/config/linux/libva/BUILD.gn new file mode 100644 index 0000000000..ada5d665fb --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/libva/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2018 The Chromium 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 || is_chromeos, "This file should only be referenced on Linux") + +pkg_config("libva") { + packages = [ "libva" ] + + # Do not use exec_script to check the version here. It is done with a + # static_assert instead. + + # vaapi decoders use dlopen pre-sandbox anyway to improve startup times. + ignore_libs = true +} diff --git a/third_party/libwebrtc/build/config/linux/nss/BUILD.gn b/third_party/libwebrtc/build/config/linux/nss/BUILD.gn new file mode 100644 index 0000000000..8c27938b76 --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/nss/BUILD.gn @@ -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. + +import("//build/config/linux/pkg_config.gni") + +if (is_linux || is_chromeos) { + # This is a dependency on NSS with no libssl. On Linux we use a built-in SSL + # library but the system NSS libraries. Non-Linux platforms using NSS use the + # hermetic one in //third_party/nss. + # + # Generally you should depend on //crypto:platform instead of using this + # config since that will properly pick up NSS or OpenSSL depending on + # platform and build config. + pkg_config("system_nss_no_ssl_config") { + packages = [ "nss" ] + extra_args = [ + "-v", + "-lssl3", + ] + } +} diff --git a/third_party/libwebrtc/build/config/linux/pangocairo/BUILD.gn b/third_party/libwebrtc/build/config/linux/pangocairo/BUILD.gn new file mode 100644 index 0000000000..ddcc754bbd --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/pangocairo/BUILD.gn @@ -0,0 +1,19 @@ +# 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. + +import("//build/config/linux/pangocairo/pangocairo.gni") +import("//build/config/linux/pkg_config.gni") + +if (use_pangocairo) { + pkg_config("pangocairo") { + packages = [ "pangocairo" ] + + # We don't want pkgconfig for pangocairo to explicitly request FreeType to get + # linked, because we control which FreeType to link to. + extra_args = [ + "-v", + "freetype", + ] + } +} diff --git a/third_party/libwebrtc/build/config/linux/pangocairo/pangocairo.gni b/third_party/libwebrtc/build/config/linux/pangocairo/pangocairo.gni new file mode 100644 index 0000000000..6bc75294cd --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/pangocairo/pangocairo.gni @@ -0,0 +1,10 @@ +# Copyright 2017 The Chromium 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/chromeos/ui_mode.gni") +import("//build/config/ui.gni") + +declare_args() { + use_pangocairo = is_linux && !is_chromecast +} diff --git a/third_party/libwebrtc/build/config/linux/pkg-config.py b/third_party/libwebrtc/build/config/linux/pkg-config.py new file mode 100755 index 0000000000..5adf70cc3b --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/pkg-config.py @@ -0,0 +1,248 @@ +#!/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. + +from __future__ import print_function + +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". +# +# CrOS systemroots place pkgconfig files at /usr/share/pkgconfig +# and one of /usr/lib/pkgconfig or /usr/lib64/pkgconfig +# depending on whether the systemroot is for a 32 or 64 bit architecture. They +# specify the 'lib' or 'lib64' of the pkgconfig path by defining the +# 'system_libdir' variable in the args.gn file. pkg_config.gni communicates this +# variable to this script with the "--system_libdir " flag. If no +# flag is provided, then pkgconfig files are assumed to come from +# /usr/lib/pkgconfig. +# +# 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. + + +def SetConfigPath(options): + """Set the PKG_CONFIG_LIBDIR environment variable. + + This takes into account any sysroot and architecture specification from the + options on the given command line. + """ + + sysroot = options.sysroot + assert 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) + + libdir = sysroot + '/usr/' + options.system_libdir + '/pkgconfig' + libdir += ':' + sysroot + '/usr/share/pkgconfig' + os.environ['PKG_CONFIG_LIBDIR'] = libdir + return libdir + + +def GetPkgConfigPrefixToStrip(options, 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([options.pkg_config, + "--variable=prefix"] + args, env=os.environ).decode('utf-8') + 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 + + +def main(): + # 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 "linux" not in sys.platform: + print("[[],[],[],[],[]]") + return 0 + + parser = OptionParser() + parser.add_option('-d', '--debug', action='store_true') + 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('--system_libdir', action='store', dest='system_libdir', + type='string', default='lib') + parser.add_option('--atleast-version', action='store', + dest='atleast_version', type='string') + parser.add_option('--libdir', action='store_true', dest='libdir') + parser.add_option('--dridriverdir', action='store_true', dest='dridriverdir') + parser.add_option('--version-as-components', action='store_true', + dest='version_as_components') + (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)) + + if options.sysroot: + libdir = SetConfigPath(options) + if options.debug: + sys.stderr.write('PKG_CONFIG_LIBDIR=%s\n' % libdir) + prefix = GetPkgConfigPrefixToStrip(options, 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): + print("true") + else: + print("false") + return 0 + + if options.version_as_components: + cmd = [options.pkg_config, "--modversion"] + args + try: + version_string = subprocess.check_output(cmd).decode('utf-8') + except: + sys.stderr.write('Error from pkg-config.\n') + return 1 + print(json.dumps(list(map(int, version_string.strip().split("."))))) + return 0 + + + if options.libdir: + cmd = [options.pkg_config, "--variable=libdir"] + args + if options.debug: + sys.stderr.write('Running: %s\n' % cmd) + try: + libdir = subprocess.check_output(cmd).decode('utf-8') + except: + print("Error from pkg-config.") + return 1 + sys.stdout.write(libdir.strip()) + return 0 + + if options.dridriverdir: + cmd = [options.pkg_config, "--variable=dridriverdir"] + args + if options.debug: + sys.stderr.write('Running: %s\n' % cmd) + try: + dridriverdir = subprocess.check_output(cmd).decode('utf-8') + except: + print("Error from pkg-config.") + return 1 + sys.stdout.write(dridriverdir.strip()) + return + + cmd = [options.pkg_config, "--cflags", "--libs"] + args + if options.debug: + sys.stderr.write('Running: %s\n' % ' '.join(cmd)) + + try: + flag_string = subprocess.check_output(cmd).decode('utf-8') + except: + sys.stderr.write('Could not run pkg-config.\n') + return 1 + + # 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(' ') + + + sysroot = options.sysroot + if not sysroot: + sysroot = '' + + includes = [] + cflags = [] + libs = [] + lib_dirs = [] + + 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': + # Don't allow libraries to control ld flags. These should be specified + # only in build files. + pass + 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])) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/config/linux/pkg_config.gni b/third_party/libwebrtc/build/config/linux/pkg_config.gni new file mode 100644 index 0000000000..f3b40755bd --- /dev/null +++ b/third_party/libwebrtc/build/config/linux/pkg_config.gni @@ -0,0 +1,128 @@ +# 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 = "" + + # A optional pkg-config wrapper to use for tools built on the host. + host_pkg_config = "" + + # CrOS systemroots place pkgconfig files at /usr/share/pkgconfig + # and one of /usr/lib/pkgconfig or /usr/lib64/pkgconfig + # depending on whether the systemroot is for a 32 or 64 bit architecture. + # + # When build under GYP, CrOS board builds specify the 'system_libdir' variable + # as part of the GYP_DEFINES provided by the CrOS emerge build or simple + # chrome build scheme. This variable permits controlling this for GN builds + # in similar fashion by setting the `system_libdir` variable in the build's + # args.gn file to 'lib' or 'lib64' as appropriate for the target architecture. + system_libdir = "lib" +} + +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. +pkg_config_args = [] + +if (sysroot != "") { + # Pass the sysroot if we're using one (it requires the CPU arch also). + pkg_config_args += [ + "-s", + rebase_path(sysroot), + "-a", + target_cpu, + ] +} + +if (pkg_config != "") { + pkg_config_args += [ + "-p", + pkg_config, + ] +} + +# Only use the custom libdir when building with the target sysroot. +if (target_sysroot != "" && sysroot == target_sysroot) { + pkg_config_args += [ + "--system_libdir", + system_libdir, + ] +} + +if (host_pkg_config != "") { + host_pkg_config_args = [ + "-p", + host_pkg_config, + ] +} else { + host_pkg_config_args = 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) { + if (host_toolchain == current_toolchain) { + args = host_pkg_config_args + invoker.packages + } else { + args = pkg_config_args + invoker.packages + } + if (defined(invoker.extra_args)) { + args += invoker.extra_args + } + + pkgresult = exec_script(pkg_config_script, args, "value") + cflags = pkgresult[1] + + foreach(include, pkgresult[0]) { + if (use_sysroot) { + # We want the system include paths to use -isystem instead of -I to + # suppress warnings in those headers. + include_relativized = rebase_path(include, root_build_dir) + cflags += [ "-isystem$include_relativized" ] + } else { + cflags += [ "-I$include" ] + } + } + + if (!defined(invoker.ignore_libs) || !invoker.ignore_libs) { + libs = pkgresult[2] + lib_dirs = pkgresult[3] + } + + forward_variables_from(invoker, + [ + "defines", + "visibility", + ]) + } +} diff --git a/third_party/libwebrtc/build/config/locales.gni b/third_party/libwebrtc/build/config/locales.gni new file mode 100644 index 0000000000..1d57b877dc --- /dev/null +++ b/third_party/libwebrtc/build/config/locales.gni @@ -0,0 +1,251 @@ +# 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/chromeos/ui_mode.gni") + +# This file creates the |locales| which is the set of current +# locales based on the current platform. Locales in this list are formated +# based on what .pak files expect. +# The |locales| variable *may* contain pseudolocales, depending on the +# |enable_pseudolocales| flag. +# If you specifically want to have the locales variable with or without +# pseudolocales, then use |locales_with_pseudolocales| or +# |locales_without_pseudolocales|. + +# The following additional platform specific lists are created: +# - |android_bundle_locales_as_resources| locales formatted for XML output names +# - |locales_as_mac_outputs| formated for mac output bundles +# - |ios_packed_locales| subset for iOS +# - |ios_packed_locales_as_mac_outputs| subset for iOS output + +# Chrome on iOS only ships with a subset of the locales supported by other +# version of Chrome as the corresponding locales are not supported by the +# operating system (but for simplicity, the corresponding .pak files are +# still generated). +ios_unsupported_locales = [ + "am", + "bn", + "et", + "fil", + "gu", + "kn", + "lv", + "ml", + "mr", + "sl", + "sw", + "ta", + "te", +] + +# These list are defined even when not building for Android or iOS for the +# sake of build/locale_tool.py. Ensure that GN doesn't complain about them +# being unused. +not_needed([ "ios_unsupported_locales" ]) + +# Superset of all locales used in Chrome with platform specific changes noted. +all_chrome_locales = [ + "af", + "am", + "ar", + "as", + "az", + "be", + "bg", + "bn", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "en-GB", + "en-US", + "es", + "es-419", # "es-MX" in iOS (Mexico vs Latin America) "es-US" on Android + "et", + "eu", + "fa", + "fi", + "fil", # "tl" in .xml but "fil" in TC and .pak + "fr", + "fr-CA", + "gl", + "gu", + "he", # "iw" in .xml and TC but "he" in .pak + "hi", + "hr", + "hu", + "hy", + "id", # "in" in .xml but "id" in TC and .pak + "is", + "it", + "ja", + "ka", + "kk", + "km", + "kn", + "ko", + "ky", + "lo", + "lt", + "lv", + "mk", + "ml", + "mn", + "mr", + "ms", + "my", + "nb", # "no" in TC but "nb" in .xml and .pak + "ne", + "nl", + "or", + "pa", + "pl", + "pt-BR", # just "pt" in iOS + "pt-PT", + "ro", + "ru", + "si", + "sk", + "sl", + "sq", + "sr", + "sr-Latn", # -b+sr+Latn in .xml + "sv", + "sw", + "ta", + "te", + "th", + "tr", + "uk", + "ur", + "uz", + "vi", + "zh-CN", + "zh-HK", + "zh-TW", + "zu", +] + +# New locales added to Chrome Android bundle builds. +android_bundle_only_locales = [ + "af", + "as", + "az", + "be", + "bs", + "eu", + "fr-CA", + "gl", + "hy", + "is", + "ka", + "kk", + "km", + "ky", + "lo", + "mk", + "mn", + "my", + "ne", + "or", + "pa", + "si", + "sq", + "sr-Latn", + "ur", + "uz", + "zh-HK", + "zu", +] + +# New locales added to ChromeOS builds. +chromeos_only_locales = [ "is" ] + +if (is_android) { + locales = all_chrome_locales + + # List for Android locale names in .xml exports. Note: needs to stay in sync + # with |ToAndroidLocaleName| in build/android/gyp/util/resource_utils.py. + # - add r: (e.g. zh-HK -> zh-rHK ) + android_bundle_locales_as_resources = [] + foreach(_locale, locales) { + android_bundle_locales_as_resources += + [ string_replace(_locale, "-", "-r") ] + } + + # - remove en-US + # - swap: (he, id, en-419, fil) -> (iw, in, es-rUS, tl) + # - sr-rLatn -> -b+sr+Latn + android_bundle_locales_as_resources -= [ + "en-rUS", + "es-r419", + "fil", + "he", + "id", + "sr-rLatn", + ] + android_bundle_locales_as_resources += [ + "b+sr+Latn", + "es-rUS", + "in", + "iw", + "tl", + ] +} else if (is_chromeos_ash || is_chromeos_lacros) { + # In ChromeOS we support a few more locales than standard Chrome. + locales = + all_chrome_locales - android_bundle_only_locales + chromeos_only_locales +} else { + # Change if other platforms support more locales. + locales = all_chrome_locales - android_bundle_only_locales +} + +# Chrome on iOS uses different names for "es-419" and "pt-BR" (called +# respectively "es-MX" and "pt" on iOS). +if (is_ios) { + locales -= [ + "es-419", + "pt-BR", + ] + locales += [ + "es-MX", + "pt", + ] +} + +pseudolocales = [ + "ar-XB", + "en-XA", +] +locales_without_pseudolocales = locales +locales_with_pseudolocales = locales + pseudolocales + +declare_args() { + # We want to give pseudolocales to everyone except end-users (devs & QA). + enable_pseudolocales = !is_official_build +} + +if (enable_pseudolocales) { + # Note that this only packages the locales in, and doesn't add the ui to enable them. + locales += pseudolocales +} + +# 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 = [] +foreach(locale, locales) { + if (locale == "en-US") { + locales_as_mac_outputs += [ "en" ] + } else { + locales_as_mac_outputs += [ string_replace(locale, "-", "_") ] + } +} + +if (is_ios) { + ios_packed_locales = locales - ios_unsupported_locales + ios_packed_locales_as_mac_outputs = + locales_as_mac_outputs - ios_unsupported_locales +} diff --git a/third_party/libwebrtc/build/config/logging.gni b/third_party/libwebrtc/build/config/logging.gni new file mode 100644 index 0000000000..c2f94e929e --- /dev/null +++ b/third_party/libwebrtc/build/config/logging.gni @@ -0,0 +1,13 @@ +# Copyright 2019 The Chromium 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/chromeos/ui_mode.gni") +import("//build/config/dcheck_always_on.gni") + +declare_args() { + # Use LogErrorNotReached() for NOTREACHED(). + enable_log_error_not_reached = + is_chromeos_ash && !(is_debug || dcheck_always_on) + enable_stack_trace_line_numbers = false +} diff --git a/third_party/libwebrtc/build/config/mac/BUILD.gn b/third_party/libwebrtc/build/config/mac/BUILD.gn new file mode 100644 index 0000000000..032c77ee34 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/BUILD.gn @@ -0,0 +1,136 @@ +# 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/apple/symbols.gni") +import("//build/config/c++/c++.gni") +import("//build/config/mac/mac_sdk.gni") +import("//build/config/sysroot.gni") + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic. +config("compiler") { + # These flags are shared between the C compiler and linker. + common_mac_flags = [] + + # CPU architecture. + if (target_cpu == "x64") { + clang_arch = "x86_64" + } else if (target_cpu == "x86") { + clang_arch = "i386" + } else if (target_cpu == "arm64") { + clang_arch = target_cpu + } else { + assert(false, "unknown target_cpu $target_cpu") + } + if (host_os == "mac") { + common_mac_flags += [ + "-arch", + clang_arch, + ] + } else { + common_mac_flags += [ "--target=$clang_arch-apple-macos" ] + } + + # This is here so that all files get recompiled after an Xcode update. + # (defines are passed via the command line, and build system rebuild things + # when their commandline changes). Nothing should ever read this define. + defines = [ "CR_XCODE_VERSION=$xcode_version" ] + + asmflags = common_mac_flags + 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" ] + + ldflags = common_mac_flags + + if (save_unstripped_output) { + ldflags += [ "-Wcrl,unstripped," + rebase_path(root_out_dir) ] + } + + if (export_libcxxabi_from_executables) { + ldflags += [ "-Wl,-undefined,dynamic_lookup" ] + } +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is Mac-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + common_flags = [ + "-isysroot", + rebase_path(sysroot, root_build_dir), + "-mmacosx-version-min=$mac_deployment_target", + ] + + asmflags = common_flags + cflags = common_flags + ldflags = common_flags + + # Prevent Mac OS X AssertMacros.h (included by system header) from defining + # macros that collide with common names, like 'check', 'require', and + # 'verify'. + # http://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/AssertMacros.h + defines = [ "__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=0" ] +} + +# On Mac, this is used for everything except static libraries. +config("mac_dynamic_flags") { + ldflags = [ "-Wl,-ObjC" ] # Always load Objective-C categories and classes. + + if (is_component_build) { + ldflags += [ + # 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/../../..", + ] + } +} + +# The ldflags referenced below are handled by +# //build/toolchain/apple/linker_driver.py. +# Remove this config if a target wishes to change the arguments passed to the +# strip command during linking. This config by default strips all symbols +# from a binary, but some targets may wish to specify an exports file to +# preserve specific symbols. +config("strip_all") { + if (enable_stripping) { + ldflags = [ "-Wcrl,strip,-x,-S" ] + } +} + +# When building with Goma, all inputs must be relative to the build directory. +# If using the system Xcode, which typically resides outside the build root, a +# symlink to the SDK is created in the build directory, and the path to that +# link is stored in $mac_sdk_path. If an action references a file in the SDK as +# an input, GN will complain that no target generates the file because it is +# below the $root_build_dir. The below action lists as outputs the files in the +# SDK that are referenced as inputs to actions, so that GN thinks a target has +# generated them. The list is centralized here, as multiple targets need to +# reference the same files, and an output can only be generated once. +# +# The symbolic link for $mac_sdk_path is set up by +# //build/config/apple/sdk_info.py in //build/config/mac/mac_sdk.gni. +if (use_system_xcode && use_goma && target_os == "mac" && + current_toolchain == default_toolchain) { + action("sdk_inputs") { + script = "//build/noop.py" + outputs = [ + "$mac_sdk_path/usr/include/mach/exc.defs", + "$mac_sdk_path/usr/include/mach/mach_exc.defs", + "$mac_sdk_path/usr/include/mach/notify.defs", + ] + } +} else { + group("sdk_inputs") { + if (current_toolchain != default_toolchain) { + public_deps = [ ":sdk_inputs($default_toolchain)" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/mac/BuildInfo.plist b/third_party/libwebrtc/build/config/mac/BuildInfo.plist new file mode 100644 index 0000000000..bfa3b8d573 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/BuildInfo.plist @@ -0,0 +1,16 @@ + + + + + DTCompiler + ${GCC_VERSION} + DTSDKBuild + ${MAC_SDK_BUILD} + DTSDKName + ${MAC_SDK_NAME} + DTXcode + ${XCODE_VERSION} + DTXcodeBuild + ${XCODE_BUILD} + + diff --git a/third_party/libwebrtc/build/config/mac/OWNERS b/third_party/libwebrtc/build/config/mac/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/config/mac/mac_sdk.gni b/third_party/libwebrtc/build/config/mac/mac_sdk.gni new file mode 100644 index 0000000000..6671893ae8 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/mac_sdk.gni @@ -0,0 +1,127 @@ +# 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") +import("//build/config/gclient_args.gni") +import("//build/config/mac/mac_sdk_overrides.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") + +assert(current_os == "mac" || current_toolchain == default_toolchain) + +declare_args() { + # The MACOSX_DEPLOYMENT_TARGET variable used when compiling. This partially + # controls the minimum supported version of macOS for Chromium by + # affecting the symbol availability rules. This may differ from + # mac_min_system_version when dropping support for older macOSes but where + # additional code changes are required to be compliant with the availability + # rules. + # Must be of the form x.x.x for Info.plist files. + mac_deployment_target = "10.11.0" + + # The value of the LSMinimmumSystemVersion in Info.plist files. This partially + # controls the minimum supported version of macOS for Chromium by + # affecting the Info.plist. This may differ from mac_deployment_target when + # dropping support for older macOSes. This should be greater than or equal to + # the mac_deployment_target version. + # Must be of the form x.x.x for Info.plist files. + mac_min_system_version = "10.11.0" + + # Path to a specific version of the Mac SDK, not including a slash at the end. + # If empty, the path to the lowest version greater than or equal to + # mac_sdk_min is used. + mac_sdk_path = "" + + # The SDK name as accepted by xcodebuild. + mac_sdk_name = "macosx" + + # The SDK version used when making official builds. This is a single exact + # version, not a minimum. If this version isn't available official builds + # will fail. + mac_sdk_official_version = "11.3" + + # Production builds should use hermetic Xcode. If you want to do production + # builds with system Xcode to test new SDKs, set this. + # Don't set this on any bots. + mac_allow_system_xcode_for_official_builds_for_testing = false +} + +# Check that the version of macOS SDK used is the one requested when building +# a version of Chrome shipped to the users. Disable the check if building for +# iOS as the version macOS SDK used is not relevant for the tool build for the +# host (they are not shipped) --- this is required as Chrome on iOS is usually +# build with the latest version of Xcode that may not ship with the version of +# the macOS SDK used to build Chrome on mac. +# TODO(crbug.com/635745): the check for target_os should be replaced by a +# check that current_toolchain is default_toolchain, and the file should +# assert that current_os is "mac" once this file is no longer included by +# iOS toolchains. +if (is_chrome_branded && is_official_build && target_os != "ios") { + assert(!use_system_xcode || + mac_allow_system_xcode_for_official_builds_for_testing, + "official branded builds should use hermetic xcode") +} + +# The path to the hermetic install of Xcode. Only relevant when +# use_system_xcode = false. +if (!use_system_xcode) { + _hermetic_xcode_path = "//build/mac_files/xcode_binaries" +} + +script_name = "//build/config/apple/sdk_info.py" +sdk_info_args = [] +if (!use_system_xcode) { + sdk_info_args += [ + "--developer_dir", + rebase_path(_hermetic_xcode_path, "", root_build_dir), + ] +} + +# Goma RBE requires paths relative to source directory. When using system +# Xcode, this is done by creating symbolic links in root_build_dir. +if (use_system_xcode && use_goma) { + sdk_info_args += [ + "--get_sdk_info", + "--create_symlink_at", + "sdk/xcode_links", + ] +} +sdk_info_args += [ mac_sdk_name ] + +#_mac_sdk_result = exec_script(script_name, sdk_info_args, "scope") +#xcode_version = _mac_sdk_result.xcode_version +#xcode_build = _mac_sdk_result.xcode_build +if (mac_sdk_path == "" && use_system_xcode && use_goma) { + mac_sdk_path = _mac_sdk_result.sdk_path +} + +if (use_system_xcode) { + # The tool will print the SDK path on the first line, and the version on the + # second line. + find_sdk_args = [ + "--print_sdk_path", + "--print_bin_path", + mac_sdk_min, + ] + find_sdk_lines = + exec_script("//build/mac/find_sdk.py", find_sdk_args, "list lines") + mac_sdk_version = find_sdk_lines[2] + if (mac_sdk_path == "") { + mac_sdk_path = find_sdk_lines[0] + mac_bin_path = find_sdk_lines[1] + } else { + mac_bin_path = find_sdk_lines[1] + } +} else { + mac_sdk_version = mac_sdk_official_version + _dev = _hermetic_xcode_path + "/Contents/Developer" + _sdk = "MacOSX${mac_sdk_version}.sdk" + mac_sdk_path = _dev + "/Platforms/MacOSX.platform/Developer/SDKs/$_sdk" + mac_bin_path = _dev + "/Toolchains/XcodeDefault.xctoolchain/usr/bin/" + + # If we're using hermetic Xcode, then we want the paths to be relative so that + # generated ninja files are independent of the directory location. + # TODO(thakis): Do this at the uses of this variable instead. + mac_bin_path = rebase_path(mac_bin_path, root_build_dir) +} diff --git a/third_party/libwebrtc/build/config/mac/mac_sdk_overrides.gni b/third_party/libwebrtc/build/config/mac/mac_sdk_overrides.gni new file mode 100644 index 0000000000..17eb3d9f62 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/mac_sdk_overrides.gni @@ -0,0 +1,16 @@ +# Copyright 2017 The Chromium 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 arguments that subprojects may choose to override. It +# asserts that those overrides are used, to prevent unused args warnings. + +_sdk_min_from_env = getenv("FORCE_MAC_SDK_MIN") +declare_args() { + # Minimum supported version of the Mac SDK. + if (_sdk_min_from_env == "") { + mac_sdk_min = "10.15" + } else { + mac_sdk_min = _sdk_min_from_env + } +} diff --git a/third_party/libwebrtc/build/config/mac/package_framework.py b/third_party/libwebrtc/build/config/mac/package_framework.py new file mode 100644 index 0000000000..0026f466b4 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/package_framework.py @@ -0,0 +1,60 @@ +# 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. + +import argparse +import errno +import os +import shutil +import sys + +def Main(): + parser = argparse.ArgumentParser(description='Create Mac Framework symlinks') + parser.add_argument('--framework', action='store', type=str, required=True) + parser.add_argument('--version', action='store', type=str) + parser.add_argument('--contents', action='store', type=str, nargs='+') + parser.add_argument('--stamp', action='store', type=str, required=True) + args = parser.parse_args() + + VERSIONS = 'Versions' + CURRENT = 'Current' + + # Ensure the Foo.framework/Versions/A/ directory exists and create the + # Foo.framework/Versions/Current symlink to it. + if args.version: + try: + os.makedirs(os.path.join(args.framework, VERSIONS, args.version), 0o755) + except OSError as e: + if e.errno != errno.EEXIST: + raise e + _Relink(os.path.join(args.version), + os.path.join(args.framework, VERSIONS, CURRENT)) + + # Establish the top-level symlinks in the framework bundle. The dest of + # the symlinks may not exist yet. + if args.contents: + for item in args.contents: + _Relink(os.path.join(VERSIONS, CURRENT, item), + os.path.join(args.framework, item)) + + # Write out a stamp file. + if args.stamp: + with open(args.stamp, 'w') as f: + f.write(str(args)) + + return 0 + + +def _Relink(dest, link): + """Creates a symlink to |dest| named |link|. If |link| already exists, + it is overwritten.""" + try: + os.remove(link) + except OSError as e: + if e.errno != errno.ENOENT: + shutil.rmtree(link) + os.symlink(dest, link) + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/config/mac/prepare_framework_version.py b/third_party/libwebrtc/build/config/mac/prepare_framework_version.py new file mode 100644 index 0000000000..db92150698 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/prepare_framework_version.py @@ -0,0 +1,42 @@ +# 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. + +import os +import shutil +import sys + +# Ensures that the current version matches the last-produced version, which is +# stored in the version_file. If it does not, then the framework_root_dir is +# obliterated. +# Usage: python prepare_framework_version.py out/obj/version_file \ +# out/Framework.framework \ +# 'A' + +def PrepareFrameworkVersion(version_file, framework_root_dir, version): + # Test what the current framework version is. Stop if it is up-to-date. + try: + with open(version_file, 'r') as f: + current_version = f.read() + if current_version == version: + return + except IOError: + pass + + # The framework version has changed, so clobber the framework. + if os.path.exists(framework_root_dir): + shutil.rmtree(framework_root_dir) + + # Write out the new framework version file, making sure its containing + # directory exists. + dirname = os.path.dirname(version_file) + if not os.path.isdir(dirname): + os.makedirs(dirname, 0o700) + + with open(version_file, 'w+') as f: + f.write(version) + + +if __name__ == '__main__': + PrepareFrameworkVersion(sys.argv[1], sys.argv[2], sys.argv[3]) + sys.exit(0) diff --git a/third_party/libwebrtc/build/config/mac/rules.gni b/third_party/libwebrtc/build/config/mac/rules.gni new file mode 100644 index 0000000000..7753a97fe7 --- /dev/null +++ b/third_party/libwebrtc/build/config/mac/rules.gni @@ -0,0 +1,628 @@ +# 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/apple/apple_info_plist.gni") +import("//build/config/apple/symbols.gni") +import("//build/config/mac/mac_sdk.gni") + +# Generates Info.plist files for Mac apps and frameworks. +# +# Arguments +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# executable_name: +# string, name of the generated target used for the product +# and executable name as specified in the output Info.plist. +# +# extra_substitutions: +# (optional) string array, 'key=value' pairs for extra fields which are +# specified in a source Info.plist template. +template("mac_info_plist") { + assert(defined(invoker.info_plist) != defined(invoker.info_plist_target), + "Only one of info_plist or info_plist_target may be specified in " + + target_name) + + if (defined(invoker.info_plist)) { + _info_plist = invoker.info_plist + } else { + _info_plist_target_output = get_target_outputs(invoker.info_plist_target) + _info_plist = _info_plist_target_output[0] + } + + apple_info_plist(target_name) { + format = "xml1" + extra_substitutions = [] + if (defined(invoker.extra_substitutions)) { + extra_substitutions = invoker.extra_substitutions + } + extra_substitutions += [ + "MAC_SDK_BUILD=$mac_sdk_version", + "MAC_SDK_NAME=$mac_sdk_name$mac_sdk_version", + "MACOSX_DEPLOYMENT_TARGET=$mac_deployment_target", + "CHROMIUM_MIN_SYSTEM_VERSION=$mac_min_system_version", + "XCODE_BUILD=$xcode_build", + "XCODE_VERSION=$xcode_version", + ] + plist_templates = [ + "//build/config/mac/BuildInfo.plist", + _info_plist, + ] + if (defined(invoker.info_plist_target)) { + deps = [ invoker.info_plist_target ] + } + forward_variables_from(invoker, + [ + "testonly", + "executable_name", + ]) + } +} + +# Template to package a shared library into a Mac framework bundle. +# +# By default, the bundle target this template generates does not link the +# resulting framework into anything that depends on it. If a dependency wants +# a link-time (as well as build-time) dependency on the framework bundle, +# depend against "$target_name+link". If only the build-time dependency is +# required (e.g., for copying into another bundle), then use "$target_name". +# +# Arguments +# +# framework_version: +# string, version of the framework. Typically this is a +# single letter, like "A". +# +# framework_contents: +# list of string, top-level items in the framework. This is +# the list of symlinks to create in the .framework directory that link +# into Versions/Current/. +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# output_name: +# (optional) string, name of the generated framework without the +# .framework suffix. If omitted, defaults to target_name. +# +# extra_substitutions: +# (optional) string array, 'key=value' pairs for extra fields which are +# specified in a source Info.plist template. +# +# This template provides three targets for the resulting framework bundle. The +# link-time behavior varies depending on which of the two targets below is +# added as a dependency: +# - $target_name only adds a build-time dependency. Targets that depend on +# it will not link against the framework. +# - $target_name+link adds a build-time and link-time dependency. Targets +# that depend on it will link against the framework. +# - $target_name+link_nested adds a build-time and link-time dependency, but +# only on the shared library and not the fully-assembled framework bundle. +# This should only be used for other nested binary components of the +# framework bundle (e.g. Helpers) that themselves depend on the main shared +# library of the framework bundle. +# +# The build-time-only dependency is used for when a target needs to use the +# framework either only for resources, or because the target loads it at run- +# time, via dlopen() or NSBundle. The link-time dependency will cause the +# dependee to have the framework loaded by dyld at launch. +# +# Example of build-time only dependency: +# +# mac_framework_bundle("CoreTeleportation") { +# sources = [ ... ] +# } +# +# bundle_data("core_teleportation_bundle_data") { +# deps = [ ":CoreTeleportation" ] +# sources = [ "$root_out_dir/CoreTeleportation.framework" ] +# outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ] +# } +# +# app_bundle("GoatTeleporter") { +# sources = [ ... ] +# deps = [ +# ":core_teleportation_bundle_data", +# ] +# } +# +# The GoatTeleporter.app will not directly link against +# CoreTeleportation.framework, but it will be included in the bundle's +# Frameworks directory. +# +# Example of link-time dependency: +# +# mac_framework_bundle("CoreTeleportation") { +# sources = [ ... ] +# ldflags = [ +# "-install_name", +# "@executable_path/../Frameworks/$target_name.framework" +# ] +# } +# +# bundle_data("core_teleportation_bundle_data") { +# deps = [ ":CoreTeleportation+link" ] +# sources = [ "$root_out_dir/CoreTeleportation.framework" ] +# outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ] +# } +# +# app_bundle("GoatTeleporter") { +# sources = [ ... ] +# deps = [ +# ":core_teleportation_bundle_data", +# ] +# } +# +# Note that the framework is still copied to the app's bundle, but dyld will +# load this library when the app is launched because it uses the "+link" +# target as a dependency. This also requires that the framework set its +# install_name so that dyld can locate it. +# +# See "gn help shared_library" for more information on arguments supported +# by shared library target. +template("mac_framework_bundle") { + assert(defined(invoker.deps) || defined(invoker.public_deps), + "Dependencies must be specified for $target_name") + assert(invoker.framework_version != "", "framework_version is required") + assert(defined(invoker.framework_contents), "framework_contents is required") + + _info_plist_target = target_name + "_info_plist" + + mac_info_plist(_info_plist_target) { + executable_name = target_name + if (defined(invoker.output_name)) { + executable_name = invoker.output_name + } + forward_variables_from(invoker, + [ + "extra_substitutions", + "info_plist", + "info_plist_target", + "testonly", + ]) + } + + _info_plist_bundle_data = _info_plist_target + "_bundle_data" + + bundle_data(_info_plist_bundle_data) { + forward_variables_from(invoker, [ "testonly" ]) + sources = get_target_outputs(":$_info_plist_target") + outputs = [ "{{bundle_resources_dir}}/Info.plist" ] + public_deps = [ ":$_info_plist_target" ] + } + + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + # Create a file to track the build dependency on the framework_version and + # framework_contents variables. + _framework_toc = [ + "Version=" + invoker.framework_version, + _output_name, + ] + invoker.framework_contents + _framework_contents = [ _output_name ] + invoker.framework_contents + _framework_toc_file = "$target_out_dir/${target_name}.toc" + write_file(_framework_toc_file, _framework_toc) + + # Create local variables for referencing different parts of the bundle. + _framework_target = _target_name + _framework_name = _output_name + ".framework" + _framework_base_dir = "$root_out_dir/$_framework_name" + _framework_root_dir = + _framework_base_dir + "/Versions/${invoker.framework_version}" + + # Clean the entire framework if the framework_version changes. + _version_file = "$target_out_dir/${target_name}_version" + exec_script("//build/config/mac/prepare_framework_version.py", + [ + rebase_path(_version_file), + rebase_path(_framework_base_dir), + invoker.framework_version, + ]) + + # Create the symlinks. + _framework_package_target = target_name + "_package" + action(_framework_package_target) { + script = "//build/config/mac/package_framework.py" + + # The TOC file never needs to be read, since its contents are the values + # of GN variables. It is only used to trigger this rule when the values + # change. + inputs = [ _framework_toc_file ] + + _stamp_file = "$target_out_dir/run_${_framework_package_target}.stamp" + outputs = [ _stamp_file ] + + visibility = [ ":$_framework_target" ] + + args = [ + "--framework", + rebase_path(_framework_base_dir, root_build_dir), + "--stamp", + rebase_path(_stamp_file, root_build_dir), + "--version", + invoker.framework_version, + "--contents", + ] + _framework_contents + + # It is not possible to list _framework_contents as outputs, since + # ninja does not properly stat symbolic links. + # https://github.com/ninja-build/ninja/issues/1186 + } + + _link_shared_library_target = target_name + "_shared_library" + _shared_library_bundle_data = target_name + "_shared_library_bundle_data" + + shared_library(_link_shared_library_target) { + forward_variables_from(invoker, + "*", + [ + "assert_no_deps", + "bundle_deps", + "code_signing_enabled", + "data_deps", + "info_plist", + "info_plist_target", + "output_name", + "visibility", + ]) + visibility = [ + ":$_shared_library_bundle_data", + ":${_framework_target}+link_nested", + ] + output_name = _output_name + output_prefix_override = true + output_extension = "" + output_dir = "$target_out_dir/$_link_shared_library_target" + } + + bundle_data(_shared_library_bundle_data) { + visibility = [ ":$_framework_target" ] + forward_variables_from(invoker, [ "testonly" ]) + sources = [ "$target_out_dir/$_link_shared_library_target/$_output_name" ] + outputs = [ "{{bundle_executable_dir}}/$_output_name" ] + public_deps = [ ":$_link_shared_library_target" ] + } + + _framework_public_config = _target_name + "_public_config" + config(_framework_public_config) { + visibility = [ ":$_framework_target+link" ] + framework_dirs = [ root_out_dir ] + frameworks = [ _framework_name ] + } + + create_bundle(_framework_target) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "public_deps", + "testonly", + ]) + + if (defined(invoker.visibility)) { + visibility = invoker.visibility + visibility += [ ":$_target_name+link" ] + } + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_info_plist_bundle_data" ] + + if (defined(invoker.bundle_deps)) { + deps += invoker.bundle_deps + } + + if (!defined(public_deps)) { + public_deps = [] + } + public_deps += [ + ":$_framework_package_target", + ":$_shared_library_bundle_data", + ] + + if (enable_dsyms) { + data = [ + "$root_out_dir/$_output_name.dSYM/Contents/Info.plist", + "$root_out_dir/$_output_name.dSYM/Contents/Resources/DWARF/$_output_name", + ] + } + + bundle_root_dir = _framework_base_dir + bundle_contents_dir = _framework_root_dir + bundle_resources_dir = "$bundle_contents_dir/Resources" + bundle_executable_dir = bundle_contents_dir + } + + group(_target_name + "+link") { + forward_variables_from(invoker, + [ + "public_configs", + "testonly", + "visibility", + ]) + public_deps = [ ":$_target_name" ] + if (!defined(public_configs)) { + public_configs = [] + } + public_configs += [ ":$_framework_public_config" ] + } + + group(_target_name + "+link_nested") { + forward_variables_from(invoker, + [ + "public_configs", + "testonly", + "visibility", + ]) + + # Depend only on the shared library. Nested code will be a dependency of + # the create_bundle target, which would be cyclic with depending on the + # framework itself. This is sufficient to link; for loading, a proper + # install_name should be set. + public_deps = [ ":$_link_shared_library_target" ] + } +} + +set_defaults("mac_framework_bundle") { + configs = default_shared_library_configs +} + +# Template to create a Mac executable application bundle. +# +# Arguments +# +# package_type: +# (optional) string, the product package type to create. Options are: +# "app" to create a .app bundle (default) +# "xpc" to create an .xpc service bundle +# +# info_plist: +# (optional) string, path to the Info.plist file that will be used for +# the bundle. +# +# info_plist_target: +# (optional) string, if the info_plist is generated from an action, +# rather than a regular source file, specify the target name in lieu +# of info_plist. The two arguments are mutually exclusive. +# +# output_name: +# (optional) string, name of the generated app without the +# .app suffix. If omitted, defaults to target_name. +# +# extra_configs: +# (optional) list of label, additional configs to apply to the +# executable target. +# +# remove_configs: +# (optional) list of label, default configs to remove from the target. +# +# extra_substitutions: +# (optional) string array, 'key=value' pairs for extra fields which are +# specified in a source Info.plist template. +template("mac_app_bundle") { + _target_name = target_name + _output_name = target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + _package_type = "app" + if (defined(invoker.package_type)) { + _package_type = invoker.package_type + } + + if (_package_type == "app") { + _output_extension = "app" + _product_type = "com.apple.product-type.application" + _write_pkg_info = true + } else if (_package_type == "xpc") { + _output_extension = "xpc" + _product_type = "com.apple.product-type.xpc-service" + _write_pkg_info = false + } else if (_package_type == "bundle") { + _output_extension = "bundle" + _product_type = "com.apple.product-type.bundle" + _write_pkg_info = false + } else { + assert(false, "Unsupported packge_type: " + packge_type) + } + + _executable_target = target_name + "_executable" + _executable_bundle_data = _executable_target + "_bundle_data" + + _info_plist_target = target_name + "_info_plist" + + mac_info_plist(_info_plist_target) { + executable_name = _output_name + forward_variables_from(invoker, + [ + "extra_substitutions", + "info_plist", + "info_plist_target", + "testonly", + ]) + } + + if (_write_pkg_info) { + _pkg_info_target = target_name + "_pkg_info" + + action(_pkg_info_target) { + forward_variables_from(invoker, [ "testonly" ]) + script = "//build/apple/write_pkg_info.py" + inputs = [ "//build/apple/plist_util.py" ] + sources = get_target_outputs(":$_info_plist_target") + outputs = [ "$target_gen_dir/$_pkg_info_target" ] + args = [ "--plist" ] + rebase_path(sources, root_build_dir) + + [ "--output" ] + rebase_path(outputs, root_build_dir) + deps = [ ":$_info_plist_target" ] + } + } + + executable(_executable_target) { + visibility = [ ":$_executable_bundle_data" ] + forward_variables_from(invoker, + "*", + [ + "assert_no_deps", + "data_deps", + "info_plist", + "output_name", + "visibility", + ]) + if (defined(extra_configs)) { + configs += extra_configs + } + if (defined(remove_configs)) { + configs -= remove_configs + } + output_name = _output_name + output_dir = "$target_out_dir/$_executable_target" + } + + bundle_data(_executable_bundle_data) { + visibility = [ ":$_target_name" ] + forward_variables_from(invoker, [ "testonly" ]) + sources = [ "$target_out_dir/$_executable_target/$_output_name" ] + outputs = [ "{{bundle_executable_dir}}/$_output_name" ] + public_deps = [ ":$_executable_target" ] + } + + _info_plist_bundle_data = _info_plist_target + "_bundle_data" + + bundle_data(_info_plist_bundle_data) { + forward_variables_from(invoker, [ "testonly" ]) + visibility = [ ":$_target_name" ] + sources = get_target_outputs(":$_info_plist_target") + outputs = [ "{{bundle_contents_dir}}/Info.plist" ] + public_deps = [ ":$_info_plist_target" ] + } + + if (_write_pkg_info) { + _pkg_info_bundle_data = _pkg_info_target + "_bundle_data" + + bundle_data(_pkg_info_bundle_data) { + forward_variables_from(invoker, [ "testonly" ]) + visibility = [ ":$_target_name" ] + sources = get_target_outputs(":$_pkg_info_target") + outputs = [ "{{bundle_contents_dir}}/PkgInfo" ] + public_deps = [ ":$_pkg_info_target" ] + } + } + + create_bundle(_target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "public_deps", + "testonly", + ]) + if (!defined(deps)) { + deps = [] + } + deps += [ + ":$_executable_bundle_data", + ":$_info_plist_bundle_data", + ] + if (_write_pkg_info) { + deps += [ ":$_pkg_info_bundle_data" ] + } + + if (enable_dsyms) { + data = [ + "$root_out_dir/$_output_name.dSYM/Contents/Info.plist", + "$root_out_dir/$_output_name.dSYM/Contents/Resources/DWARF/$_output_name", + ] + } + + product_type = _product_type + bundle_root_dir = "$root_out_dir/${_output_name}.${_output_extension}" + bundle_contents_dir = "$bundle_root_dir/Contents" + bundle_resources_dir = "$bundle_contents_dir/Resources" + bundle_executable_dir = "$bundle_contents_dir/MacOS" + } +} + +# Template to package a loadable_module into a .plugin bundle. +# +# This takes no extra arguments that differ from a loadable_module. +template("mac_plugin_bundle") { + assert(defined(invoker.deps), + "Dependencies must be specified for $target_name") + + _target_name = target_name + _loadable_module_target = _target_name + "_loadable_module" + _loadable_module_bundle_data = _loadable_module_target + "_bundle_data" + + _output_name = _target_name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + + loadable_module(_loadable_module_target) { + visibility = [ ":$_loadable_module_bundle_data" ] + forward_variables_from(invoker, + "*", + [ + "assert_no_deps", + "data_deps", + "output_name", + "visibility", + ]) + output_dir = "$target_out_dir" + output_name = _output_name + } + + bundle_data(_loadable_module_bundle_data) { + forward_variables_from(invoker, [ "testonly" ]) + visibility = [ ":$_target_name" ] + sources = [ "$target_out_dir/$_output_name.so" ] + outputs = [ "{{bundle_executable_dir}}/$_output_name" ] + public_deps = [ ":$_loadable_module_target" ] + } + + create_bundle(_target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "public_deps", + "testonly", + "visibility", + ]) + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_loadable_module_bundle_data" ] + + if (enable_dsyms) { + data = [ + "$root_out_dir/$_output_name.so.dSYM/Contents/Info.plist", + "$root_out_dir/$_output_name.so.dSYM/Contents/Resources/DWARF/$_output_name.so", + ] + } + + bundle_root_dir = "$root_out_dir/$_output_name.plugin" + bundle_contents_dir = "$bundle_root_dir/Contents" + bundle_executable_dir = "$bundle_contents_dir/MacOS" + } +} diff --git a/third_party/libwebrtc/build/config/mips.gni b/third_party/libwebrtc/build/config/mips.gni new file mode 100644 index 0000000000..8ad7e27911 --- /dev/null +++ b/third_party/libwebrtc/build/config/mips.gni @@ -0,0 +1,67 @@ +# 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/v8_target_cpu.gni") + +# These are primarily relevant in target_cpu == "mips*" contexts, where +# MIPS code is being compiled. But they can also be relevant in the +# other contexts when the code will change its behavior based on the +# cpu it wants to generate code for. +declare_args() { + # MIPS MultiMedia Instruction compilation flag. + mips_use_mmi = false +} + +if (target_cpu == "mipsel" || v8_target_cpu == "mipsel" || + target_cpu == "mips" || v8_target_cpu == "mips") { + declare_args() { + # MIPS arch variant. Possible values are: + # "r1" + # "r2" + # "r6" + # "loongson3" + mips_arch_variant = "r1" + + # MIPS DSP ASE revision. Possible values are: + # 0: unavailable + # 1: revision 1 + # 2: revision 2 + mips_dsp_rev = 0 + + # MIPS SIMD Arch compilation flag. + mips_use_msa = false + + # 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 (target_cpu == "mips64el" || v8_target_cpu == "mips64el" || + target_cpu == "mips64" || v8_target_cpu == "mips64") { + # MIPS arch variant. Possible values are: + # "r2" + # "r6" + # "loongson3" + if (current_os == "android" || target_os == "android") { + declare_args() { + mips_arch_variant = "r6" + + # MIPS SIMD Arch compilation flag. + mips_use_msa = true + } + } else { + declare_args() { + mips_arch_variant = "r2" + + # MIPS SIMD Arch compilation flag. + mips_use_msa = false + } + } +} diff --git a/third_party/libwebrtc/build/config/nacl/BUILD.gn b/third_party/libwebrtc/build/config/nacl/BUILD.gn new file mode 100644 index 0000000000..89fbf85aa5 --- /dev/null +++ b/third_party/libwebrtc/build/config/nacl/BUILD.gn @@ -0,0 +1,143 @@ +# Copyright (c) 2014 The Native Client 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/nacl/config.gni") + +# Native Client Definitions +config("nacl_defines") { + if (is_linux || is_chromeos || is_android || is_nacl) { + defines = [ + "_POSIX_C_SOURCE=199506", + "_XOPEN_SOURCE=600", + "_GNU_SOURCE=1", + "__STDC_LIMIT_MACROS=1", + ] + } else if (is_win) { + defines = [ "__STDC_LIMIT_MACROS=1" ] + } + + if (target_cpu == "pnacl" && !is_nacl_nonsfi) { + # TODO: Remove the following definition once NACL_BUILD_ARCH and + # NACL_BUILD_SUBARCH are defined by the PNaCl toolchain. + defines += [ "NACL_BUILD_ARCH=pnacl" ] + } +} + +config("nexe_defines") { + defines = [ + "DYNAMIC_ANNOTATIONS_ENABLED=1", + "DYNAMIC_ANNOTATIONS_PREFIX=NACL_", + ] +} + +config("nacl_warnings") { + if (is_win) { + # Some NaCl code uses forward declarations of static const variables, + # with initialized definitions later on. (The alternative would be + # many, many more forward declarations of everything used in that + # const variable's initializer before the definition.) The Windows + # compiler is too stupid to notice that there is an initializer later + # in the file, and warns about the forward declaration. + cflags = [ "/wd4132" ] + } +} + +# The base target that all targets in the NaCl build should depend on. +# This allows configs to be modified for everything in the NaCl build, even when +# the NaCl build is composed into the Chrome build. (GN has no functionality to +# add flags to everything in //native_client, having a base target works around +# that limitation.) +source_set("nacl_base") { + public_configs = [ + ":nacl_defines", + ":nacl_warnings", + ] + if (current_os == "nacl") { + public_configs += [ ":nexe_defines" ] + } +} + +config("compiler") { + configs = [] + cflags = [] + ldflags = [] + libs = [] + + if (is_clang && target_cpu != "pnacl") { + # -no-integrated-as is the default in nacl-clang for historical + # compatibility with inline assembly code and so forth. But there + # are no such cases in Chromium code, and -integrated-as is nicer in + # general. Moreover, the IRT must be built using LLVM's assembler + # on x86-64 to preserve sandbox base address hiding. Use it + # everywhere for consistency (and possibly quicker builds). + cflags += [ "-integrated-as" ] + } + if (is_nacl_nonsfi) { + cflags += [ "--pnacl-allow-translate" ] + ldflags += [ + "--pnacl-allow-translate", + "--pnacl-allow-native", + "-Wl,--noirt", + "-Wt,--noirt", + "-Wt,--noirtshim", + + # The clang driver automatically injects -lpthread when using libc++, but + # the toolchain doesn't have it yet. To get around this, use + # -nodefaultlibs and make each executable target depend on + # "//native_client/src/nonsfi/irt:nacl_sys_private". + "-nodefaultlibs", + ] + libs += [ + "c++", + "m", + "c", + "pnaclmm", + ] + include_dirs = [ "//native_client/src/public/linux_syscalls" ] + } + + asmflags = cflags +} + +config("compiler_codegen") { + cflags = [] + + if (is_nacl_irt) { + cflags += [ + # A debugger should be able to unwind IRT call frames. This is + # the default behavior on x86-64 and when compiling C++ with + # exceptions enabled; the change is for the benefit of x86-32 C. + # The frame pointer is unnecessary when unwind tables are used. + "-fasynchronous-unwind-tables", + "-fomit-frame-pointer", + ] + + if (target_cpu == "x86") { + # The x86-32 IRT needs to be callable with an under-aligned + # stack; so we disable SSE instructions, which can fault on + # misaligned addresses. See + # https://code.google.com/p/nativeclient/issues/detail?id=3935 + cflags += [ + "-mstackrealign", + "-mno-sse", + ] + } + } + + asmflags = cflags +} + +config("irt_optimize") { + cflags = [ + # Optimize for space, keep the IRT nexe small. + "-Os", + + # These are omitted from non-IRT libraries to keep the libraries + # themselves small. + "-ffunction-sections", + "-fdata-sections", + ] + + ldflags = [ "-Wl,--gc-sections" ] +} diff --git a/third_party/libwebrtc/build/config/nacl/config.gni b/third_party/libwebrtc/build/config/nacl/config.gni new file mode 100644 index 0000000000..9ebdb39c35 --- /dev/null +++ b/third_party/libwebrtc/build/config/nacl/config.gni @@ -0,0 +1,61 @@ +# 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() { + # Native Client supports multiple toolchains: + # - nacl_glibc, based on gcc and glibc. + # - pnacl_newlib, based on llvm 3.7 and newlib (default). + # - saigo_newlib, based on llvm 12+ and newlib. + + # True if nacl_glibc is used. + is_nacl_glibc = false + + # True if saigo_newlib is used. + is_nacl_saigo = false +} + +is_nacl_irt = false +is_nacl_nonsfi = false + +nacl_toolchain_dir = "//native_client/toolchain/${host_os}_x86" + +if (is_nacl_glibc) { + if (target_cpu == "x86" || target_cpu == "x64") { + nacl_toolchain_package = "nacl_x86_glibc" + } else if (target_cpu == "arm") { + nacl_toolchain_package = "nacl_arm_glibc" + } +} else { + nacl_toolchain_package = "pnacl_newlib" +} + +if (target_cpu == "pnacl") { + _nacl_tuple = "pnacl" +} else if (target_cpu == "x86" || target_cpu == "x64") { + _nacl_tuple = "x86_64-nacl" +} else if (target_cpu == "arm") { + _nacl_tuple = "arm-nacl" +} else if (target_cpu == "mipsel") { + _nacl_tuple = "mipsel-nacl" +} else { + # In order to allow this file to be included unconditionally + # from build files that can't depend on //components/nacl/features.gni + # we provide a dummy value that should be harmless if nacl isn't needed. + # If nacl *is* needed this will result in a real error, indicating that + # people need to set the toolchain path correctly. + _nacl_tuple = "unknown" +} + +nacl_toolchain_bindir = "${nacl_toolchain_dir}/${nacl_toolchain_package}/bin" +nacl_toolchain_tooldir = + "${nacl_toolchain_dir}/${nacl_toolchain_package}/${_nacl_tuple}" +nacl_toolprefix = "${nacl_toolchain_bindir}/${_nacl_tuple}-" + +nacl_irt_toolchain = "//build/toolchain/nacl:irt_" + target_cpu +is_nacl_irt = current_toolchain == nacl_irt_toolchain + +# Non-SFI mode is a lightweight sandbox used by Chrome OS for running ARC +# applications. +nacl_nonsfi_toolchain = "//build/toolchain/nacl:newlib_pnacl_nonsfi" +is_nacl_nonsfi = current_toolchain == nacl_nonsfi_toolchain diff --git a/third_party/libwebrtc/build/config/nacl/rules.gni b/third_party/libwebrtc/build/config/nacl/rules.gni new file mode 100644 index 0000000000..42bb248357 --- /dev/null +++ b/third_party/libwebrtc/build/config/nacl/rules.gni @@ -0,0 +1,180 @@ +# Copyright 2015 The Native Client 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/nacl/config.gni") + +# Generate a nmf file +# +# Native Client Manifest (nmf) is a JSON file that tells the browser where to +# download and load Native Client application files and libraries. +# +# Variables: +# executables: .nexe/.pexe/.bc executables to generate nmf for +# lib_prefix: path to prepend to shared libraries in the nmf +# nmf: the name and the path of the output file +# nmfflags: additional flags for the nmf generator +# stage_dependencies: directory for staging libraries +template("generate_nmf") { + assert(defined(invoker.executables), "Must define executables") + assert(defined(invoker.nmf), "Must define nmf") + + action(target_name) { + forward_variables_from(invoker, + [ + "deps", + "data_deps", + "executables", + "lib_prefix", + "nmf", + "nmfflags", + "public_deps", + "stage_dependencies", + "testonly", + "visibility", + ]) + if (!defined(nmfflags)) { + nmfflags = [] + } + + # TODO(phosek): Remove this conditional once + # https://bugs.chromium.org/p/nativeclient/issues/detail?id=4339 is + # resolved. + if (target_cpu == "pnacl") { + objdump = rebase_path("${nacl_toolchain_bindir}/x86_64-nacl-objdump") + } else { + objdump = rebase_path("${nacl_toolprefix}objdump") + } + if (host_os == "win") { + objdump += ".exe" + } + + script = "//native_client_sdk/src/tools/create_nmf.py" + inputs = [ objdump ] + sources = executables + outputs = [ nmf ] + if (is_nacl_glibc) { + if (defined(stage_dependencies)) { + nmfflags += [ "--stage-dependencies=" + + rebase_path(stage_dependencies, root_build_dir) ] + lib_path = stage_dependencies + } else { + lib_path = root_build_dir + } + if (defined(lib_prefix)) { + nmfflags += [ "--lib-prefix=" + lib_prefix ] + lib_path += "/${lib_prefix}" + } + + # Starts empty so the code below can use += everywhere. + data = [] + + nmfflags += + [ "--library-path=" + rebase_path(root_out_dir, root_build_dir) ] + + # NOTE: There is no explicit dependency for the lib directory + # (lib32 and lib64 for x86/x64) created in the product directory. + # They are created as a side-effect of nmf creation. + if (target_cpu != "x86" && target_cpu != "x64") { + nmfflags += + [ "--library-path=" + + rebase_path("${nacl_toolchain_tooldir}/lib", root_build_dir) ] + if (target_cpu == "arm") { + data += [ "${lib_path}/libarm/" ] + } else { + data += [ "${lib_path}/lib/" ] + } + } else { + # For x86-32, the lib/ directory is called lib32/ instead. + if (target_cpu == "x86") { + nmfflags += + [ "--library-path=" + + rebase_path("${nacl_toolchain_tooldir}/lib32", root_build_dir) ] + data += [ "${lib_path}/lib32/" ] + } + + # x86-32 Windows needs to build both x86-32 and x86-64 NaCl + # binaries into the same nmf covering both architectures. That + # gets handled at a higher level (see the nacl_test_data template), + # so a single generate_nmf invocation gets both x86-32 and x86-64 + # nexes listed in executables. + if (target_cpu == "x64" || target_os == "win") { + # For x86-64, the lib/ directory is called lib64/ instead + # when copied by create_nmf.py. + glibc_tc = "//build/toolchain/nacl:glibc" + assert(current_toolchain == "${glibc_tc}_${target_cpu}") + if (target_cpu == "x64") { + x64_out_dir = root_out_dir + } else { + x64_out_dir = get_label_info(":${target_name}(${glibc_tc}_x64)", + "root_out_dir") + } + nmfflags += [ + "--library-path=" + rebase_path(x64_out_dir, root_build_dir), + "--library-path=" + + rebase_path("${nacl_toolchain_tooldir}/lib", root_build_dir), + ] + data += [ "${lib_path}/lib64/" ] + } + } + } + args = [ + "--no-default-libpath", + "--objdump=" + rebase_path(objdump, root_build_dir), + "--output=" + rebase_path(nmf, root_build_dir), + ] + nmfflags + rebase_path(sources, root_build_dir) + if (is_nacl_glibc && target_cpu == "arm") { + deps += [ "//native_client/src/untrusted/elf_loader:elf_loader" ] + } + } +} + +# Generate a nmf file for Non-SFI tests +# +# Non-SFI tests use a different manifest format from regular Native Client and +# as such requires a different generator. +# +# Variables: +# executable: Non-SFI .nexe executable to generate nmf for +# nmf: the name and the path of the output file +# nmfflags: additional flags for the nmf generator +template("generate_nonsfi_test_nmf") { + assert(defined(invoker.executable), "Must define executable") + assert(defined(invoker.nmf), "Must define nmf") + + action(target_name) { + forward_variables_from(invoker, + [ + "deps", + "data_deps", + "executable", + "nmf", + "testonly", + "public_deps", + "visibility", + ]) + + script = "//ppapi/tests/create_nonsfi_test_nmf.py" + sources = [ executable ] + outputs = [ nmf ] + + # NOTE: We use target_cpu rather than target_cpu on purpose because + # target_cpu is always going to be pnacl for Non-SFI, but the Non-SFI + # .nexe executable is always translated to run on the target machine. + if (target_cpu == "x86") { + arch = "x86-32" + } else if (target_cpu == "x64") { + arch = "x86-64" + } else { + arch = target_cpu + } + args = [ + "--program=" + rebase_path(executable, root_build_dir), + "--arch=${arch}", + "--output=" + rebase_path(nmf, root_build_dir), + ] + if (defined(invoker.nmfflags)) { + args += invoker.nmfflags + } + } +} diff --git a/third_party/libwebrtc/build/config/ozone.gni b/third_party/libwebrtc/build/config/ozone.gni new file mode 100644 index 0000000000..bf6eb78272 --- /dev/null +++ b/third_party/libwebrtc/build/config/ozone.gni @@ -0,0 +1,152 @@ +# Copyright 2020 The Chromium 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/chromecast_build.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/toolchain/toolchain.gni") + +declare_args() { + # Indicates if Ozone is enabled. Ozone is a low-level library layer for Linux + # that does not require X11. + use_ozone = + is_chromeos || (is_chromecast && !is_android) || is_fuchsia || is_linux || is_bsd +} + +declare_args() { + # Ozone extra platforms file path. Can be overridden to build out of + # tree ozone platforms. + ozone_extra_path = "//build/config/ozone_extra.gni" + + # Select platforms automatically. Turn this off for manual control. + ozone_auto_platforms = use_ozone + + # TODO(petermcneeley): Backwards compatiblity support for VM images. + # Remove when deprecated. (https://crbug.com/1122009) + ozone_platform_gbm = -1 + + # Enable explicit apitrace (https://apitrace.github.io) loading. + # This requires apitrace library with additional bindings. + # See ChromeOS package for details: + # https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/2659419 + # Chrome will not start without an apitrace.so library. + # Trace will be saved to /tmp/gltrace.dat file by default. You can + # override it at run time with TRACE_FILE= environment variable. + enable_opengl_apitrace = false +} + +declare_args() { + # The platform that will used at runtime by default. This can be overridden + # with the command line flag --ozone-platform=. + ozone_platform = "" + + # Compile the 'cast' platform. + ozone_platform_cast = false + + # Compile the 'drm' platform. + ozone_platform_drm = false + + # Compile the 'headless' platform. + ozone_platform_headless = false + + # Compile the 'scenic' platform. + ozone_platform_scenic = false + + # Compile the 'flatland' platform. + ozone_platform_flatland = false + + # Compile the 'x11' platform. + ozone_platform_x11 = false + + # Compile the 'wayland' platform. + ozone_platform_wayland = false + + # Compile the 'windows' platform. + ozone_platform_windows = false + + if (ozone_auto_platforms) { + # Use headless as the default platform unless modified below. + ozone_platform = "headless" + ozone_platform_headless = true + + if (is_cast_audio_only) { + # Just use headless for audio-only Cast platforms. + } else if (is_chromecast && !is_fuchsia) { + # Enable the Cast ozone platform on all A/V Cast builds except Fuchsia. + ozone_platform_cast = true + + # For visual desktop Chromecast builds, override the default "headless" + # platform with --ozone-platform=x11. + # TODO(halliwell): Create a libcast_graphics implementation for desktop + # using X11, and disable this platform. + if (is_cast_desktop_build && !is_cast_audio_only) { + ozone_platform_x11 = true + } else { + ozone_platform = "cast" + } + } else if (is_chromeos_ash) { + ozone_platform = "x11" + ozone_platform_drm = true + ozone_platform_x11 = true + } else if (is_chromeos_lacros) { + ozone_platform = "wayland" + ozone_platform_wayland = true + } else if (is_linux || is_bsd) { + ozone_platform = "x11" + ozone_platform_wayland = true + ozone_platform_x11 = true + } else if (is_win) { + ozone_platform = "windows" + ozone_platform_windows = true + } else if (is_fuchsia) { + ozone_platform = "scenic" + ozone_platform_scenic = true + ozone_platform_flatland = true + } + } + + # TODO(petermcneeley): Backwards compatiblity support for VM images. + # Remove when deprecated. (https://crbug.com/1122009) + if (ozone_platform_gbm != -1) { + ozone_platform_drm = ozone_platform_gbm + } +} + +declare_args() { + # Deprecated. Ozone/X11 is default path on Linux now. However, there are still + # some components like remoting, angle, webrtc (desktop capture) that relies + # on this gn arg and on the USE_X11 define. This will be gradually removed + # once all the other places are fixed. + # TODO(1096425): remove use_x11. + use_x11 = + ozone_platform_x11 && (is_linux || is_bsd) && !is_chromecast && !is_chromeos_lacros +} + +import(ozone_extra_path) + +_ozone_extra_directory = get_path_info(ozone_extra_path, "dir") + +# Extra paths to add to targets visibility list. +ozone_external_platform_visibility = [ "$_ozone_extra_directory/*" ] + +if (is_a_target_toolchain) { + assert( + use_ozone || !(ozone_platform_cast || ozone_platform_drm || + ozone_platform_flatland || ozone_platform_headless || + ozone_platform_x11 || ozone_platform_wayland || + ozone_platform_windows || ozone_platform_scenic), + "Must set use_ozone to select ozone platforms") +} + +# TODO(petermcneeley): Backwards compatiblity support for VM images. +# Remove when deprecated. (https://crbug.com/1122009) + +assert(ozone_platform_gbm == -1 || ozone_platform_drm == ozone_platform_gbm) + +ozone_platform_gbm = ozone_platform_drm + +if (ozone_platform == "gbm") { + ozone_platform = "drm" +} + +assert(use_x11 == ozone_platform_x11 || !is_linux || !is_bsd || is_chromecast) diff --git a/third_party/libwebrtc/build/config/ozone_extra.gni b/third_party/libwebrtc/build/config/ozone_extra.gni new file mode 100644 index 0000000000..57fa791354 --- /dev/null +++ b/third_party/libwebrtc/build/config/ozone_extra.gni @@ -0,0 +1,24 @@ +# 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. + +# This list contains the name of external platforms that are passed to the +# --ozone-platform command line argument or used for the ozone_platform build +# config. For example ozone_external_platforms = [ "foo1", "foo2", ... ] +ozone_external_platforms = [] + +# This list contains dependencies for external platforms. Typically, the Ozone +# implementations are placed into ui/ozone/platform/ and so this will look +# something like: +# ozone_external_platform_deps = [ "platform/foo1", "platform/foo_2", ... ] +ozone_external_platform_deps = [] + +# If a platform has unit tests, the corresponding source_set can be listed here +# so that they get included into ozone_unittests. +# ozone_external_platform_test_deps = [ "platform/foo1:foo1_unitests", ... ] +ozone_external_platform_test_deps = [] + +# If a platform has test support files for ui, the corresponding source_set can +# be listed here so that they get included into ui_test_support. +# ozone_external_platform_ui_test_support_deps = [ "platform/foo1:ui_test_support", ... ] +ozone_external_platform_ui_test_support_deps = [] diff --git a/third_party/libwebrtc/build/config/pch.gni b/third_party/libwebrtc/build/config/pch.gni new file mode 100644 index 0000000000..efc8a3be36 --- /dev/null +++ b/third_party/libwebrtc/build/config/pch.gni @@ -0,0 +1,15 @@ +# 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. + +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") + +declare_args() { + # Precompiled header file support is by default available, + # but for distributed build system uses (like goma or rbe) or when + # doing official builds. + # On Linux it slows down the build, so don't enable it by default. + enable_precompiled_headers = + !is_official_build && !(use_goma || use_remoteexec) && !is_linux +} diff --git a/third_party/libwebrtc/build/config/posix/BUILD.gn b/third_party/libwebrtc/build/config/posix/BUILD.gn new file mode 100644 index 0000000000..a49254f019 --- /dev/null +++ b/third_party/libwebrtc/build/config/posix/BUILD.gn @@ -0,0 +1,59 @@ +# 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/sysroot.gni") + +# This build configuration is used by both Fuchsia and POSIX systems. +assert(is_posix || is_fuchsia) + +group("posix") { + visibility = [ "//:optimize_gn_gen" ] +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is Posix-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + asmflags = [] + cflags = [] + cflags_c = [] + cflags_cc = [] + cflags_objc = [] + cflags_objcc = [] + defines = [] + ldflags = [] + + if (!is_apple && sysroot != "") { + # Pass the sysroot to all C compiler variants, the assembler, and linker. + sysroot_flags = [ "--sysroot=" + rebase_path(sysroot, root_build_dir) ] + if (is_linux || is_chromeos) { + # This is here so that all files get recompiled after a sysroot roll and + # when turning the sysroot on or off. (defines are passed via the command + # line, and build system rebuilds things when their commandline + # changes). Nothing should ever read this define. + sysroot_hash = + exec_script("//build/linux/sysroot_scripts/install-sysroot.py", + [ "--print-hash=$target_cpu" ], + "trim string", + [ "//build/linux/sysroot_scripts/sysroots.json" ]) + defines += [ "CR_SYSROOT_HASH=$sysroot_hash" ] + } + asmflags += sysroot_flags + ldflags += sysroot_flags + + # When use_custom_libcxx=true, some -isystem flags get passed to + # cflags_cc to set up libc++ include paths. We want to make sure + # the sysroot includes take lower precendence than the libc++ + # ones, so they must appear later in the command line. However, + # the gn reference states "These variant-specific versions of + # cflags* will be appended on the compiler command line after + # 'cflags'." Because of this, we must set the sysroot flags for + # all cflags variants instead of using 'cflags' directly. + cflags_c += sysroot_flags + cflags_cc += sysroot_flags + cflags_objc += sysroot_flags + cflags_objcc += sysroot_flags + } +} diff --git a/third_party/libwebrtc/build/config/profiling/OWNERS b/third_party/libwebrtc/build/config/profiling/OWNERS new file mode 100644 index 0000000000..3d69566fd5 --- /dev/null +++ b/third_party/libwebrtc/build/config/profiling/OWNERS @@ -0,0 +1,2 @@ +liaoyuke@chromium.org +sajjadm@chromium.org diff --git a/third_party/libwebrtc/build/config/profiling/profiling.gni b/third_party/libwebrtc/build/config/profiling/profiling.gni new file mode 100644 index 0000000000..9ca3d4aa1b --- /dev/null +++ b/third_party/libwebrtc/build/config/profiling/profiling.gni @@ -0,0 +1,14 @@ +# Copyright 2020 The Chromium 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/compiler/pgo/pgo.gni") +import("//build/config/coverage/coverage.gni") + +declare_args() { + use_clang_profiling = + is_a_target_toolchain && (use_clang_coverage || chrome_pgo_phase == 1) +} + +assert(!use_clang_profiling || is_clang, + "Clang Source-based profiling requires clang.") diff --git a/third_party/libwebrtc/build/config/python.gni b/third_party/libwebrtc/build/config/python.gni new file mode 100644 index 0000000000..2dfb7e9270 --- /dev/null +++ b/third_party/libwebrtc/build/config/python.gni @@ -0,0 +1,161 @@ +# Copyright 2018 The Chromium 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 group() that lists Python sources as |data|. +# Having such targets serves two purposes: +# 1) Causes files to be included in runtime_deps, so that they are uploaded to +# swarming when running tests remotely. +# 2) Causes "gn analyze" to know about all Python inputs so that tests will be +# re-run when relevant Python files change. +# +# All non-trivial Python scripts should use a "pydeps" file to track their +# sources. To create a .pydep file for a target in //example: +# +# build/print_python_deps.py \ +# --root example \ +# --output example/$target_name.pydeps \ +# path/to/your/script.py +# +# Keep the .pydep file up-to-date by adding to //PRESUBMIT.py under one of: +# _ANDROID_SPECIFIC_PYDEPS_FILES, _GENERIC_PYDEPS_FILES +# +# Variables +# pydeps_file: Path to .pydeps file to read sources from (optional). +# data: Additional files to include in data. E.g. non-.py files needed by the +# library, or .py files that are conditionally / lazily imported. +# +# Example +# python_library("my_library_py") { +# pydeps_file = "my_library.pydeps" +# data = [ "foo.dat" ] +# } +template("python_library") { + group(target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "testonly", + "visibility", + ]) + + if (defined(invoker.pydeps_file)) { + # Read and filter out comments. + _pydeps_lines = read_file(invoker.pydeps_file, "list lines") + _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ]) + + # Dependencies are listed relative to the pydeps file directory, but data + # parameter expects paths that are relative to the current BUILD.gn + _script_dir = get_path_info(invoker.pydeps_file, "dir") + _rebased_pydeps_entries = rebase_path(_pydeps_entries, ".", _script_dir) + + # Even though the .pydep file is not used at runtime, it must be added + # so that "gn analyze" will mark the target as changed when .py files + # are removed but none are added or modified. + data = _rebased_pydeps_entries + [ invoker.pydeps_file ] + } else { + data = [] + } + if (defined(invoker.data)) { + data += invoker.data + } + } +} + +# A template used for actions that execute a Python script, which has an +# associated .pydeps file. In other words: +# +# - This is very similar to just an action(), except that |script| must point +# to a Python script (e.g. "//build/.../foo.py") that has a corresponding +# .pydeps file in the source tree (e.g. "//build/.../foo.pydeps"). +# +# - The .pydeps file contains a list of python dependencies (imports really) +# and is generated _manually_ by using a command like: +# +# build/print_python_deps.py --inplace build/android/gyp/foo.py +# +# Example +# action_with_pydeps("create_foo") { +# script = "myscript.py" +# args = [...] +# } +template("action_with_pydeps") { + action(target_name) { + # Ensure that testonly and visibility are forwarded + # explicitly, since this performs recursive scope lookups, which is + # required to ensure their definition from scopes above the caller are + # properly handled. All other variables are forwarded with "*", which + # doesn't perform recursive lookups at all. See https://crbug.com/862232 + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + forward_variables_from(invoker, + "*", + [ + "testonly", + "visibility", + ]) + + # Read and filter out comments. + # Happens every time the template is instantiated, but benchmarking shows no + # perceivable impact on overall 'gn gen' speed. + _pydeps_file = invoker.script + "deps" + + _pydeps_lines = + read_file(_pydeps_file, "list lines") # https://crbug.com/1102058 + _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ]) + + if (!defined(inputs)) { + inputs = [] + } + + # Dependencies are listed relative to the script directory, but inputs + # expects paths that are relative to the current BUILD.gn + _script_dir = get_path_info(_pydeps_file, "dir") + inputs += rebase_path(_pydeps_entries, ".", _script_dir) + } +} + +template("action_foreach_with_pydeps") { + action_foreach(target_name) { + # Ensure that testonly and visibility are forwarded + # explicitly, since this performs recursive scope lookups, which is + # required to ensure their definition from scopes above the caller are + # properly handled. All other variables are forwarded with "*", which + # doesn't perform recursive lookups at all. See https://crbug.com/862232 + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + forward_variables_from(invoker, + "*", + [ + "testonly", + "visibility", + ]) + + # Read and filter out comments. + # Happens every time the template is instantiated, but benchmarking shows no + # perceivable impact on overall 'gn gen' speed. + if (defined(invoker.deps_file)) { + _pydeps_file = invoker.deps_file + } else { + _pydeps_file = invoker.script + "deps" + } + _pydeps_lines = read_file(_pydeps_file, "list lines") + _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ]) + + if (!defined(inputs)) { + inputs = [] + } + + # Dependencies are listed relative to the script directory, but inputs + # expects paths that are relative to the current BUILD.gn + _script_dir = get_path_info(script, "dir") + inputs += rebase_path(_pydeps_entries, ".", _script_dir) + } +} diff --git a/third_party/libwebrtc/build/config/rts.gni b/third_party/libwebrtc/build/config/rts.gni new file mode 100644 index 0000000000..677b3fa2fe --- /dev/null +++ b/third_party/libwebrtc/build/config/rts.gni @@ -0,0 +1,5 @@ +declare_args() { + # For more info about RTS, please see + # //docs/testing/regression-test-selection.md + use_rts = false +} diff --git a/third_party/libwebrtc/build/config/rust.gni b/third_party/libwebrtc/build/config/rust.gni new file mode 100644 index 0000000000..5ff90e6d19 --- /dev/null +++ b/third_party/libwebrtc/build/config/rust.gni @@ -0,0 +1,138 @@ +# Copyright 2021 The Chromium Project. 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") +import("//build/config/compiler/compiler.gni") +import("//build/toolchain/toolchain.gni") + +if (is_android) { + import("//build/config/android/config.gni") +} + +declare_args() { + # Whether to allow Rust code to be part of the Chromium *build process*. + # This can be used to create Rust test binaries, even if the flag below + # is false. + enable_rust = false + + # Whether to allow Rust code to contribute to the main Chromium binaries. + enable_rust_in_chromium = false + + # Use unverified, untrusted, Rust toolchains from the internet + # (which support more platforms and options than those we trust for real + # builds). + use_unverified_rust_toolchain = false + + # If using an unverified Rust toolchain, use this prefix for where to find + # the binaries. + rust_bin_dir = "" + + # Use LTO when using rustc to link binaries. Experimental. Currently incompatible + # with the options we use in our C++ toolchain to split LTO units. + # This has no effect on the production of normal Chrome binaries, which are + # linked by clang/lld rather than rustc. + # https://crbug.com/1229419 + use_lto_in_rustc_linking = false + + # Use goma for Rust builds. Experimental. The only known problem is + # b/193072381, but then again, we don't expect a build speedup before much + # more work is done. + use_goma_rust = false +} + +# Rust code may end up being linked into a final executable by: +# * rustc (which calls lld) +# * our pre-existing C++ linker invocations +# At the moment, this first pipeline is incompatible with the ldflags we use +# for thin LTO, due to some problem in escaping gn rules. There's a further +# problem with -lunwind on Android. +# However, Rust code is still useful if it's contributing to our existing +# C++ linker invocations, so this doesn't disable Rust entirely. It does +# disable Rust unit test executables, so we do need to fix this. +# https://crbug.com/1229423 +rustc_can_link = !use_thin_lto && !is_android + +# Has a Rust toolchain available in the build by default. +toolchain_has_official_rust = + (!is_nacl && + (is_android && (target_cpu == "arm" || target_cpu == "arm64" || + target_cpu == "x64" || target_cpu == "x86"))) || + (is_linux && target_cpu == "x64") + +toolchain_has_rust = enable_rust && (toolchain_has_official_rust || + use_unverified_rust_toolchain) + +# We use the Rust linker for building test executables, so we only build them +# if we're able to use the Rust linker. We could use the C++ linker for this +# too, we've just not set up GN to do so at the moment. +build_rust_unit_tests = rustc_can_link + +if (use_unverified_rust_toolchain) { + assert(rust_bin_dir != "") + rust_prefix = "$rust_bin_dir/" +} else if (toolchain_has_official_rust) { + if (host_os != "linux") { +# assert(false, +# "Attempt to use standard Rust toolchain on an unsupported platform") + } + rust_prefix = + rebase_path("//third_party/android_rust_toolchain/toolchain/1.54.0/bin/") +} + +assert(!toolchain_has_rust || defined(rust_prefix)) + +# Figure out the Rust target triple (aka 'rust_abi_target') +# +# This is here rather than in the toolchain files because it's used +# also by //build/rust/std to find the Rust standard library. +# +# The list of architectures supported by Rust is here: +# https://doc.rust-lang.org/nightly/rustc/platform-support.html +# Although most of these are not yet supported by our limited +# official Rust toolchain (see 'toolchain_has_official_rust' above) +# it's useful to be able to experiment with our other platforms, +# so we try to be comprehensive here. +# +# It's OK if rust_abi_target is blank. That means we're building for the host +# and the host stdlib will be used. +rust_abi_target = "" +if (is_android) { + import("//build/config/android/abi.gni") + rust_abi_target = android_abi_target + if (rust_abi_target == "arm-linux-androideabi") { + # Android clang target specifications mostly match Rust, but this + # is an exception + rust_abi_target = "armv7-linux-androideabi" + } +} else if (is_fuchsia) { + if (target_cpu == "arm64") { + rust_abi_target = "aarch64-fuchsia" + } else if (target_cpu == "x64") { + rust_abi_target = "x86_64-fuchsia" + } else { + assert(false, "Architecture not supported") + } +} else if (is_ios) { + if (target_cpu == "arm64") { + rust_abi_target = "aarch64-apple-ios" + } else if (target_cpu == "arm") { + # There's also an armv7s-apple-ios, which targets a more recent ARMv7 + # generation CPU found in later iPhones. We'll go with the older one for + # maximal compatibility. As we come to support all the different platforms + # with Rust, we might want to be more precise here. + rust_abi_target = "armv7-apple-ios" + } else if (target_cpu == "x64") { + rust_abi_target = "x86_64-apple-ios" + } else if (target_cpu == "x86") { + rust_abi_target = "i386-apple-ios" + } else { + assert(false, "Architecture not supported") + } +} + +# Arguments for Rust invocation. +# This is common between gcc/clang, Mac and Windows toolchains so specify once, +# here. This is not the complete command-line: toolchains should add -o +# and probably --emit arguments too. +rustc_common_args = "--crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} {{rustdeps}} {{externs}}" diff --git a/third_party/libwebrtc/build/config/sanitizers/BUILD.gn b/third_party/libwebrtc/build/config/sanitizers/BUILD.gn new file mode 100644 index 0000000000..8c31a78085 --- /dev/null +++ b/third_party/libwebrtc/build/config/sanitizers/BUILD.gn @@ -0,0 +1,610 @@ +# 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") +import("//build/config/chromecast_build.gni") +import("//build/config/clang/clang.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +if (is_ios) { + import("//build/config/ios/ios_sdk.gni") +} + +# Contains the dependencies needed for sanitizers to link into executables and +# shared_libraries. +group("deps") { + if (using_sanitizer) { + public_configs = [ + ":sanitizer_options_link_helper", + + # Even when a target removes default_sanitizer_flags, it may be depending + # on a library that did not remove default_sanitizer_flags. Thus, we need + # to add the ldflags here as well as in default_sanitizer_flags. + ":default_sanitizer_ldflags", + ] + deps = [ ":options_sources" ] + if (is_win) { + exe = ".exe" + } else { + exe = "" + } + data = [ + "//tools/valgrind/asan/", + "$clang_base_path/bin/llvm-symbolizer${exe}", + ] + if (use_prebuilt_instrumented_libraries || + use_locally_built_instrumented_libraries) { + deps += [ "//third_party/instrumented_libraries:deps" ] + } + } + if (is_asan) { + # ASAN is supported on iOS but the runtime library depends on the compiler + # used (Chromium version of clang versus Xcode version of clang). Only copy + # the ASAN runtime on iOS if building with Chromium clang. + if (is_win || is_mac || (is_ios && !use_xcode_clang)) { + data_deps = [ ":copy_asan_runtime" ] + } + if (is_mac || (is_ios && !use_xcode_clang)) { + public_deps = [ ":asan_runtime_bundle_data" ] + } + } +} + +assert(!(is_win && is_asan && target_cpu == "x86"), + "ASan is only supported in 64-bit builds on Windows.") + +if ((is_mac || is_win || (is_ios && !use_xcode_clang)) && is_asan) { + if (is_mac) { + _clang_rt_dso_path = "darwin/libclang_rt.asan_osx_dynamic.dylib" + } else if (is_ios) { + _clang_rt_dso_path = "darwin/libclang_rt.asan_iossim_dynamic.dylib" + } else if (is_win && target_cpu == "x64") { + _clang_rt_dso_path = "windows/clang_rt.asan_dynamic-x86_64.dll" + } + + _clang_rt_dso_full_path = + "$clang_base_path/lib/clang/$clang_version/lib/$_clang_rt_dso_path" + + if (!is_ios) { + copy("copy_asan_runtime") { + sources = [ _clang_rt_dso_full_path ] + outputs = [ "$root_out_dir/{{source_file_part}}" ] + } + } else { + # On iOS, the runtime library need to be code signed (adhoc signature) + # starting with Xcode 8, so use an action instead of a copy on iOS. + action("copy_asan_runtime") { + script = "//build/config/ios/codesign.py" + sources = [ _clang_rt_dso_full_path ] + outputs = [ "$root_out_dir/" + get_path_info(sources[0], "file") ] + args = [ + "code-sign-file", + "--identity=" + ios_code_signing_identity, + "--output=" + rebase_path(outputs[0], root_build_dir), + rebase_path(sources[0], root_build_dir), + ] + } + } + + if (is_apple) { + bundle_data("asan_runtime_bundle_data") { + sources = get_target_outputs(":copy_asan_runtime") + outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ] + public_deps = [ ":copy_asan_runtime" ] + } + } +} + +config("sanitizer_options_link_helper") { + if (is_apple) { + ldflags = [ "-Wl,-U,_sanitizer_options_link_helper" ] + } else if (!is_win) { + ldflags = [ "-Wl,-u_sanitizer_options_link_helper" ] + } +} + +static_library("options_sources") { + # This is a static_library instead of a source_set, as it shouldn't be + # unconditionally linked into targets. + visibility = [ + ":deps", + "//:gn_visibility", + ] + sources = [ "//build/sanitizers/sanitizer_options.cc" ] + + # Don't compile this target with any sanitizer code. It can be called from + # the sanitizer runtimes, so instrumenting these functions could cause + # recursive calls into the runtime if there is an error. + configs -= [ "//build/config/sanitizers:default_sanitizer_flags" ] + + if (is_asan) { + if (!defined(asan_suppressions_file)) { + asan_suppressions_file = "//build/sanitizers/asan_suppressions.cc" + } + sources += [ asan_suppressions_file ] + } + + if (is_lsan) { + if (!defined(lsan_suppressions_file)) { + lsan_suppressions_file = "//build/sanitizers/lsan_suppressions.cc" + } + sources += [ lsan_suppressions_file ] + } + + if (is_tsan) { + if (!defined(tsan_suppressions_file)) { + tsan_suppressions_file = "//build/sanitizers/tsan_suppressions.cc" + } + sources += [ tsan_suppressions_file ] + } +} + +# Applies linker flags necessary when either :deps or :default_sanitizer_flags +# are used. +config("default_sanitizer_ldflags") { + visibility = [ + ":default_sanitizer_flags", + ":deps", + + # https://crbug.com/360158. + "//tools/ipc_fuzzer/fuzzer:ipc_fuzzer", + ] + + if (is_posix || is_fuchsia) { + ldflags = [] + if (is_asan) { + ldflags += [ "-fsanitize=address" ] + } + if (is_hwasan) { + ldflags += [ "-fsanitize=hwaddress" ] + } + if (is_lsan) { + ldflags += [ "-fsanitize=leak" ] + } + if (is_tsan) { + ldflags += [ "-fsanitize=thread" ] + } + if (is_msan) { + ldflags += [ "-fsanitize=memory" ] + } + if (is_ubsan || is_ubsan_security) { + ldflags += [ "-fsanitize=undefined" ] + } + if (is_ubsan_null) { + ldflags += [ "-fsanitize=null" ] + } + if (is_ubsan_vptr) { + ldflags += [ "-fsanitize=vptr" ] + } + + if (use_sanitizer_coverage) { + if (use_libfuzzer) { + ldflags += [ "-fsanitize=fuzzer-no-link" ] + if (is_mac) { + # TODO(crbug.com/926588): on macOS, dead code stripping does not work + # well with `pc-table` instrumentation enabled by `fuzzer-no-link`. + ldflags += [ "-fno-sanitize-coverage=pc-table" ] + } + } else { + ldflags += [ "-fsanitize-coverage=$sanitizer_coverage_flags" ] + } + } + + if (is_cfi && current_toolchain == default_toolchain) { + ldflags += [ "-fsanitize=cfi-vcall" ] + if (use_cfi_cast) { + ldflags += [ + "-fsanitize=cfi-derived-cast", + "-fsanitize=cfi-unrelated-cast", + ] + } + if (use_cfi_icall) { + ldflags += [ "-fsanitize=cfi-icall" ] + } + if (use_cfi_diag) { + ldflags += [ "-fno-sanitize-trap=cfi" ] + if (use_cfi_recover) { + ldflags += [ "-fsanitize-recover=cfi" ] + } + } + } + } else if (is_win) { + # Windows directly calls link.exe instead of the compiler driver when + # linking. Hence, pass the runtime libraries instead of -fsanitize=address + # or -fsanitize=fuzzer. + if (is_asan && is_component_build) { + # In the static-library build, ASan libraries are different for + # executables and dlls, see link_executable and link_shared_library below. + # This here handles only the component build. + if (target_cpu == "x64") { + # Windows 64-bit. + libs = [ + "clang_rt.asan_dynamic-x86_64.lib", + "clang_rt.asan_dynamic_runtime_thunk-x86_64.lib", + ] + } else { + assert(target_cpu == "x86", "WinASan unsupported architecture") + libs = [ + "clang_rt.asan_dynamic-i386.lib", + "clang_rt.asan_dynamic_runtime_thunk-i386.lib", + ] + } + } + if (use_libfuzzer) { + assert(target_cpu == "x64", "LibFuzzer unsupported architecture") + assert(!is_component_build, + "LibFuzzer only supports non-component builds on Windows") + + # Incremental linking causes padding that messes up SanitizerCoverage. + # Don't do it. + ldflags = [ "/INCREMENTAL:NO" ] + } + } +} + +config("common_sanitizer_flags") { + cflags = [] + + if (using_sanitizer) { + assert(is_clang, "sanitizers only supported with clang") + + # Allow non-default toolchains to enable sanitizers in toolchain_args even + # in official builds. + assert(current_toolchain != default_toolchain || !is_official_build, + "sanitizers not supported in official builds") + + cflags += [ + # Column info in debug data confuses Visual Studio's debugger, so don't + # use this by default. However, clusterfuzz needs it for good + # attribution of reports to CLs, so turn it on there. + "-gcolumn-info", + ] + + # Frame pointers are controlled in //build/config/compiler:default_stack_frames + } +} + +config("asan_flags") { + cflags = [] + if (is_asan) { + cflags += [ "-fsanitize=address" ] + if (is_win) { + if (!defined(asan_win_blocklist_path)) { + asan_win_blocklist_path = + rebase_path("//tools/memory/asan/blocklist_win.txt", root_build_dir) + } + cflags += [ "-fsanitize-ignorelist=$asan_win_blocklist_path" ] + } + } +} + +config("link_executable") { + if (is_asan && is_win && !is_component_build) { + if (target_cpu == "x64") { + ldflags = [ "-wholearchive:clang_rt.asan-x86_64.lib" ] + } else { + assert(target_cpu == "x86", "WinASan unsupported architecture") + ldflags = [ "-wholearchive:clang_rt.asan-i386.lib" ] + } + } +} + +config("link_shared_library") { + if (is_asan && is_win && !is_component_build) { + if (target_cpu == "x64") { + libs = [ "clang_rt.asan_dll_thunk-x86_64.lib" ] + } else { + assert(target_cpu == "x86", "WinASan unsupported architecture") + libs = [ "clang_rt.asan_dll_thunk-i386.lib" ] + } + } +} + +config("cfi_flags") { + cflags = [] + if (is_cfi && current_toolchain == default_toolchain) { + if (!defined(cfi_ignorelist_path)) { + cfi_ignorelist_path = + rebase_path("//tools/cfi/ignores.txt", root_build_dir) + } + cflags += [ + "-fsanitize=cfi-vcall", + "-fsanitize-ignorelist=$cfi_ignorelist_path", + ] + + if (use_cfi_cast) { + cflags += [ + "-fsanitize=cfi-derived-cast", + "-fsanitize=cfi-unrelated-cast", + ] + } + + if (use_cfi_icall) { + cflags += [ "-fsanitize=cfi-icall" ] + } + + if (use_cfi_diag) { + cflags += [ "-fno-sanitize-trap=cfi" ] + if (is_win) { + cflags += [ + "/Oy-", + "/Ob0", + ] + } else { + cflags += [ + "-fno-inline-functions", + "-fno-inline", + "-fno-omit-frame-pointer", + "-O1", + ] + } + if (use_cfi_recover) { + cflags += [ "-fsanitize-recover=cfi" ] + } + } + } +} + +# crbug.com/785442: Fix cfi-icall failures for code that casts pointer argument +# types in function pointer type signatures. +config("cfi_icall_generalize_pointers") { + if (is_clang && is_cfi && use_cfi_icall) { + cflags = [ "-fsanitize-cfi-icall-generalize-pointers" ] + } +} + +config("cfi_icall_disable") { + if (is_clang && is_cfi && use_cfi_icall) { + cflags = [ "-fno-sanitize=cfi-icall" ] + } +} + +config("coverage_flags") { + cflags = [] + if (use_sanitizer_coverage) { + # Used by sandboxing code to allow coverage dump to be written on the disk. + defines = [ "SANITIZER_COVERAGE" ] + + if (use_libfuzzer) { + cflags += [ "-fsanitize=fuzzer-no-link" ] + if (is_mac) { + # TODO(crbug.com/926588): on macOS, dead code stripping does not work + # well with `pc-table` instrumentation enabled by `fuzzer-no-link`. + cflags += [ "-fno-sanitize-coverage=pc-table" ] + } + } else { + cflags += [ + "-fsanitize-coverage=$sanitizer_coverage_flags", + "-mllvm", + "-sanitizer-coverage-prune-blocks=1", + ] + if (target_cpu == "arm") { + # http://crbug.com/517105 + cflags += [ + "-mllvm", + "-sanitizer-coverage-block-threshold=0", + ] + } + } + } +} + +config("hwasan_flags") { + if (is_hwasan) { + asmflags = [ "-fsanitize=hwaddress" ] + cflags = [ "-fsanitize=hwaddress" ] + } +} + +config("lsan_flags") { + if (is_lsan) { + cflags = [ "-fsanitize=leak" ] + } +} + +config("msan_flags") { + if (is_msan) { + assert(is_linux || is_chromeos, + "msan only supported on linux x86_64/ChromeOS") + if (!defined(msan_ignorelist_path)) { + msan_ignorelist_path = + rebase_path("//tools/msan/ignorelist.txt", root_build_dir) + } + cflags = [ + "-fsanitize=memory", + "-fsanitize-memory-track-origins=$msan_track_origins", + "-fsanitize-ignorelist=$msan_ignorelist_path", + ] + } +} + +config("tsan_flags") { + if (is_tsan) { + assert(is_linux || is_chromeos, "tsan only supported on linux x86_64") + if (!defined(tsan_ignorelist_path)) { + tsan_ignorelist_path = + rebase_path("//tools/memory/tsan_v2/ignores.txt", root_build_dir) + } + cflags = [ + "-fsanitize=thread", + "-fsanitize-ignorelist=$tsan_ignorelist_path", + ] + } +} + +config("ubsan_flags") { + cflags = [] + if (is_ubsan) { + if (!defined(ubsan_ignorelist_path)) { + ubsan_ignorelist_path = + rebase_path("//tools/ubsan/ignorelist.txt", root_build_dir) + } + cflags += [ + "-fsanitize=bool", + "-fsanitize=bounds", + "-fsanitize=builtin", + "-fsanitize=float-divide-by-zero", + "-fsanitize=integer-divide-by-zero", + "-fsanitize=null", + "-fsanitize=object-size", + "-fsanitize=return", + "-fsanitize=returns-nonnull-attribute", + "-fsanitize=shift-exponent", + "-fsanitize=signed-integer-overflow", + "-fsanitize=unreachable", + "-fsanitize=vla-bound", + "-fsanitize-ignorelist=$ubsan_ignorelist_path", + ] + + # Chromecast ubsan builds fail to compile with these + # experimental flags, so only add them to non-chromecast ubsan builds. + if (!is_chromecast) { + cflags += [ + # Employ the experimental PBQP register allocator to avoid slow + # compilation on files with too many basic blocks. + # See http://crbug.com/426271. + "-mllvm", + "-regalloc=pbqp", + + # Speculatively use coalescing to slightly improve the code generated + # by PBQP regallocator. May increase compile time. + "-mllvm", + "-pbqp-coalescing", + ] + } + } +} + +config("ubsan_no_recover") { + if (is_ubsan_no_recover) { + cflags = [ "-fno-sanitize-recover=undefined" ] + } +} + +config("ubsan_security_flags") { + if (is_ubsan_security) { + if (!defined(ubsan_security_ignorelist_path)) { + ubsan_security_ignorelist_path = + rebase_path("//tools/ubsan/security_ignorelist.txt", root_build_dir) + } + cflags = [ + "-fsanitize=function", + "-fsanitize=shift", + "-fsanitize=signed-integer-overflow", + "-fsanitize=vla-bound", + "-fsanitize-ignorelist=$ubsan_security_ignorelist_path", + ] + } +} + +config("ubsan_null_flags") { + if (is_ubsan_null) { + cflags = [ "-fsanitize=null" ] + } +} + +config("ubsan_vptr_flags") { + if (is_ubsan_vptr) { + if (!defined(ubsan_vptr_ignorelist_path)) { + ubsan_vptr_ignorelist_path = + rebase_path("//tools/ubsan/vptr_ignorelist.txt", root_build_dir) + } + cflags = [ + "-fsanitize=vptr", + "-fsanitize-ignorelist=$ubsan_vptr_ignorelist_path", + ] + } +} + +config("fuzzing_build_mode") { + if (use_fuzzing_engine && optimize_for_fuzzing) { + defines = [ "FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" ] + } +} + +all_sanitizer_configs = [ + ":common_sanitizer_flags", + ":coverage_flags", + ":default_sanitizer_ldflags", + ":asan_flags", + ":cfi_flags", + ":hwasan_flags", + ":lsan_flags", + ":msan_flags", + ":tsan_flags", + ":ubsan_flags", + ":ubsan_no_recover", + ":ubsan_null_flags", + ":ubsan_security_flags", + ":ubsan_vptr_flags", + ":fuzzing_build_mode", +] + +# This config is applied by default to all targets. It sets the compiler flags +# for sanitizer usage, or, if no sanitizer is set, does nothing. +# +# This needs to be in a separate config so that targets can opt out of +# sanitizers (by removing the config) if they desire. Even if a target +# removes this config, executables & shared libraries should still depend on +# :deps if any of their dependencies have not opted out of sanitizers. +# Keep this list in sync with default_sanitizer_flags_but_ubsan_vptr. +config("default_sanitizer_flags") { + configs = all_sanitizer_configs + + if (use_sanitizer_configs_without_instrumentation) { + configs = [] + } +} + +# This config is equivalent to default_sanitizer_flags, but excludes ubsan_vptr. +# This allows to selectively disable ubsan_vptr, when needed. In particular, +# if some third_party code is required to be compiled without rtti, which +# is a requirement for ubsan_vptr. +config("default_sanitizer_flags_but_ubsan_vptr") { + configs = all_sanitizer_configs - [ ":ubsan_vptr_flags" ] + + if (use_sanitizer_configs_without_instrumentation) { + configs = [] + } +} + +config("default_sanitizer_flags_but_coverage") { + configs = all_sanitizer_configs - [ ":coverage_flags" ] + + if (use_sanitizer_configs_without_instrumentation) { + configs = [] + } +} + +# This config is used by parts of code that aren't targeted in fuzzers and +# therefore don't need coverage instrumentation and possibly wont need +# sanitizer instrumentation either. The config also tells the compiler to +# perform additional optimizations on the configured code and ensures that +# linking it to the rest of the binary which is instrumented with sanitizers +# works. The config only does anything if the build is a fuzzing build. +config("not_fuzzed") { + if (use_fuzzing_engine) { + # Since we aren't instrumenting with coverage, code size is less of a + # concern, so use a more aggressive optimization level than + # optimize_for_fuzzing (-O1). When given multiple optimization flags, clang + # obeys the last one, so as long as this flag comes after -O1, it should work. + # Since this config will always be depended on after + # "//build/config/compiler:default_optimization" (which adds -O1 when + # optimize_for_fuzzing is true), -O2 should always be the second flag. Even + # though this sounds fragile, it isn't a big deal if it breaks, since proto + # fuzzers will still work, they will just be slightly slower. + cflags = [ "-O2" ] + + # We need to include this config when we remove default_sanitizer_flags or + # else there will be linking errors. We would remove default_sanitizer_flags + # here as well, but gn doesn't permit this. + if (!is_msan) { + # We don't actually remove sanitization when MSan is being used so there + # is no need to add default_sanitizer_ldflags in that case + configs = [ ":default_sanitizer_ldflags" ] + } + } +} diff --git a/third_party/libwebrtc/build/config/sanitizers/OWNERS b/third_party/libwebrtc/build/config/sanitizers/OWNERS new file mode 100644 index 0000000000..f6a122b518 --- /dev/null +++ b/third_party/libwebrtc/build/config/sanitizers/OWNERS @@ -0,0 +1,3 @@ +inferno@chromium.org +metzman@chromium.org +ochang@chromium.org diff --git a/third_party/libwebrtc/build/config/sanitizers/sanitizers.gni b/third_party/libwebrtc/build/config/sanitizers/sanitizers.gni new file mode 100644 index 0000000000..3bb41df05f --- /dev/null +++ b/third_party/libwebrtc/build/config/sanitizers/sanitizers.gni @@ -0,0 +1,298 @@ +# 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/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/profiling/profiling.gni") +import("//build/toolchain/toolchain.gni") + +declare_args() { + # Compile for Address Sanitizer to find memory bugs. + is_asan = false + + # Compile for Hardware-Assisted Address Sanitizer to find memory bugs + # (android/arm64 only). + # See http://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html + is_hwasan = false + + # Compile for Leak Sanitizer to find leaks. + is_lsan = false + + # Compile for Memory Sanitizer to find uninitialized reads. + is_msan = false + + # Compile for Thread Sanitizer to find threading bugs. + is_tsan = false + + # Compile for Undefined Behaviour Sanitizer to find various types of + # undefined behaviour (excludes vptr checks). + is_ubsan = false + + # Halt the program if a problem is detected. + is_ubsan_no_recover = false + + # Compile for Undefined Behaviour Sanitizer's null pointer checks. + is_ubsan_null = false + + # 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 build the libraries from source. + use_locally_built_instrumented_libraries = false + + # Compile with Control Flow Integrity to protect virtual calls and casts. + # See http://clang.llvm.org/docs/ControlFlowIntegrity.html + # + # TODO(pcc): Remove this flag if/when CFI is enabled in all official builds. + # + # Disable this on linux-chromeos to avoid using ThinLTO there; + # crbug.com/1033839. Similarly, don't use this on ARC builds. + # + # TODO(crbug.com/1159424): Reassess the validity of the next expression. + is_cfi = + is_official_build && + (((target_os == "linux" || is_chromeos_lacros) && target_cpu == "x64") || + ((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device)) + + # Enable checks for indirect function calls via a function pointer. + # TODO(pcc): remove this when we're ready to add these checks by default. + # https://crbug.com/701919 + # + # TODO(crbug.com/1159424): Reassess the validity of the next expression. + use_cfi_icall = (target_os == "linux" || is_chromeos_lacros) && + target_cpu == "x64" && is_official_build + + # Print detailed diagnostics when Control Flow Integrity detects a violation. + use_cfi_diag = false + + # Let Control Flow Integrity continue execution instead of crashing when + # printing diagnostics (use_cfi_diag = true). + use_cfi_recover = false + + # Compile for fuzzing with LLVM LibFuzzer. + # See http://www.chromium.org/developers/testing/libfuzzer + use_libfuzzer = false + + # Compile for fuzzing with AFL. + use_afl = false + + # Compile for fuzzing with an external engine (e.g., Grammarinator). + use_external_fuzzing_engine = false + + # Enables core ubsan security features. Will later be removed once it matches + # is_ubsan. + is_ubsan_security = false + + # Helper variable for testing builds with disabled libfuzzer. + # Not for client use. + disable_libfuzzer = false + + # Optimize for coverage guided fuzzing (balance between speed and number of + # branches). Can be also used to remove non-determinism and other issues. + optimize_for_fuzzing = false + + # Value for -fsanitize-coverage flag. Setting this causes + # use_sanitizer_coverage to be enabled. + # This flag is not used for libFuzzer (use_libfuzzer=true). Instead, we use: + # -fsanitize=fuzzer-no-link + # Default value when unset and use_fuzzing_engine=true: + # trace-pc-guard + # Default value when unset and use_sanitizer_coverage=true: + # trace-pc-guard,indirect-calls + sanitizer_coverage_flags = "" + + # When enabled, only relevant sanitizer defines are set, but compilation + # happens with no extra flags. This is useful when in component build + # enabling sanitizers only in some of the components. + use_sanitizer_configs_without_instrumentation = false + + # When true, seed corpora archives are built. + archive_seed_corpus = true +} + +declare_args() { + # Enable checks for bad casts: derived cast and unrelated cast. + # TODO(krasin): remove this, when we're ready to add these checks by default. + # https://crbug.com/626794 + use_cfi_cast = is_cfi && (is_chromeos_ash || is_chromeos_lacros) + + # Compile for Undefined Behaviour Sanitizer's vptr checks. + is_ubsan_vptr = is_ubsan_security +} + +assert(!is_hwasan || (target_os == "android" && target_cpu == "arm64"), + "HWASan only supported on Android ARM64 builds.") + +# Disable sanitizers for non-target toolchains. +if (!is_a_target_toolchain) { + is_asan = false + is_cfi = false + is_hwasan = false + is_lsan = false + is_msan = false + is_tsan = false + is_ubsan = false + is_ubsan_null = false + is_ubsan_no_recover = false + is_ubsan_security = false + is_ubsan_vptr = false + msan_track_origins = 0 + sanitizer_coverage_flags = "" + use_afl = false + use_cfi_diag = false + use_cfi_recover = false + use_libfuzzer = false + use_locally_built_instrumented_libraries = false + use_sanitizer_coverage = false +} else if (target_cpu != "arm64") { + is_hwasan = false +} + +# Use dynamic libraries instrumented by one of the sanitizers instead of the +# standard system libraries. We have instrumented system libraries for msan, +# which requires them to prevent false positives. +# TODO(thakis): Maybe remove this variable. +use_prebuilt_instrumented_libraries = is_msan + +# Whether we are doing a fuzzer build. Normally this should be checked instead +# of checking "use_libfuzzer || use_afl" because often developers forget to +# check for "use_afl". +use_fuzzing_engine = use_libfuzzer || use_afl || use_external_fuzzing_engine + +# Args that are in turn dependent on other args must be in a separate +# declare_args block. User overrides are only applied at the end of a +# declare_args block. +declare_args() { + # Generates an owners file for each fuzzer test. + # TODO(crbug.com/1194183): Remove this arg when finding OWNERS is faster. + generate_fuzzer_owners = use_fuzzing_engine + + use_sanitizer_coverage = + !use_clang_coverage && + (use_fuzzing_engine || sanitizer_coverage_flags != "") + + # https://crbug.com/1002058: Code coverage works inside the sandbox via the + # help of several helper IPCs. Unfortunately, the sandbox-only path does not + # work well for fuzzing builds. Since fuzzing builds already disable the + # sandbox when dumping coverage, limit the sandbox-only path to non-fuzzing + # builds. + # Everything is IPC on Fuchsia, so this workaround for code coverage inside + # the sandbox does not apply. + use_clang_profiling_inside_sandbox = + use_clang_profiling && !use_fuzzing_engine && !is_fuchsia +} + +if (use_fuzzing_engine && sanitizer_coverage_flags == "") { + sanitizer_coverage_flags = "trace-pc-guard" +} else if (use_sanitizer_coverage && sanitizer_coverage_flags == "") { + sanitizer_coverage_flags = "trace-pc-guard,indirect-calls" +} + +# Whether we are linking against a sanitizer runtime library. Among other +# things, this changes the default symbol level and other settings in order to +# prepare to create stack traces "live" using the sanitizer runtime. +using_sanitizer = is_asan || is_hwasan || is_lsan || is_tsan || is_msan || + is_ubsan || is_ubsan_null || is_ubsan_vptr || + is_ubsan_security || use_sanitizer_coverage || use_cfi_diag + +assert(!using_sanitizer || is_clang, + "Sanitizers (is_*san) require setting is_clang = true in 'gn args'") + +assert(!is_cfi || is_clang, + "is_cfi requires setting is_clang = true in 'gn args'") + +prebuilt_instrumented_libraries_available = + is_msan && (msan_track_origins == 0 || msan_track_origins == 2) + +if (use_libfuzzer && (is_linux || is_chromeos)) { + if (is_asan) { + # We do leak checking with libFuzzer on Linux. Set is_lsan for code that + # relies on LEAK_SANITIZER define to avoid false positives. + is_lsan = true + } +} + +# MSan only links Chrome properly in release builds (brettw -- 9/1/2015). The +# same is possibly true for the other non-ASan sanitizers. But regardless of +# whether it links, one would normally never run a sanitizer in debug mode. +# Running in debug mode probably indicates you forgot to set the "is_debug = +# false" flag in the build args. ASan seems to run fine in debug mode. +# +# If you find a use-case where you want to compile a sanitizer in debug mode +# and have verified it works, ask brettw and we can consider removing it from +# this condition. We may also be able to find another way to enable your case +# without having people accidentally get broken builds by compiling an +# unsupported or unadvisable configurations. +# +# For one-off testing, just comment this assertion out. +assert(!is_debug || !(is_msan || is_ubsan || is_ubsan_null || is_ubsan_vptr), + "Sanitizers should generally be used in release (set is_debug=false).") + +assert(!is_msan || ((is_linux || is_chromeos) && target_cpu == "x64"), + "MSan currently only works on 64-bit Linux and ChromeOS builds.") + +assert(!is_lsan || is_asan, "is_lsan = true requires is_asan = true also.") + +# ASAN build on Windows is not working in debug mode. Intercepting memory +# allocation functions is hard on Windows and not yet implemented in LLVM. +assert(!is_win || !is_debug || !is_asan, + "ASan on Windows doesn't work in debug (set is_debug=false).") + +# libFuzzer targets can fail to build or behave incorrectly when built without +# ASAN on Windows. +assert(!is_win || !use_libfuzzer || is_asan, + "use_libfuzzer on Windows requires setting is_asan = true") + +# Make sure that if we recover on detection (i.e. not crash), diagnostics are +# printed. +assert(!use_cfi_recover || use_cfi_diag, + "Only use CFI recovery together with diagnostics.") + +# TODO(crbug.com/753445): the use_sanitizer_coverage arg is currently +# not supported by the Chromium mac_clang_x64 toolchain on iOS distribution. +# The coverage works with iOS toolchain but it is broken when the mac +# toolchain is used as a secondary one on iOS distribution. E.g., it should be +# possible to build the "net" target for iOS with the sanitizer coverage +# enabled. +assert( + !(use_sanitizer_coverage && is_mac && target_os == "ios"), + "crbug.com/753445: use_sanitizer_coverage=true is not supported by the " + + "Chromium mac_clang_x64 toolchain on iOS distribution. Please set " + + "the argument value to false.") + +# Use these lists of configs to disable instrumenting code that is part of a +# fuzzer, but which isn't being targeted (such as libprotobuf-mutator, *.pb.cc +# and libprotobuf when they are built as part of a proto fuzzer). Adding or +# removing these lists does not have any effect if use_libfuzzer or use_afl are +# not passed as arguments to gn. +not_fuzzed_remove_configs = [] +not_fuzzed_remove_nonasan_configs = [] + +if (use_fuzzing_engine) { + # Removing coverage should always just work. + not_fuzzed_remove_configs += [ "//build/config/coverage:default_coverage" ] + not_fuzzed_remove_nonasan_configs += + [ "//build/config/coverage:default_coverage" ] + + if (!is_msan) { + # Allow sanitizer instrumentation to be removed if we are not using MSan + # since binaries cannot be partially instrumented with MSan. + not_fuzzed_remove_configs += + [ "//build/config/sanitizers:default_sanitizer_flags" ] + + # Certain parts of binaries must be instrumented with ASan if the rest of + # the binary is. For these, only remove non-ASan sanitizer instrumentation. + if (!is_asan) { + not_fuzzed_remove_nonasan_configs += + [ "//build/config/sanitizers:default_sanitizer_flags" ] + + assert(not_fuzzed_remove_nonasan_configs == not_fuzzed_remove_configs) + } + } +} diff --git a/third_party/libwebrtc/build/config/sysroot.gni b/third_party/libwebrtc/build/config/sysroot.gni new file mode 100644 index 0000000000..9d2c828c63 --- /dev/null +++ b/third_party/libwebrtc/build/config/sysroot.gni @@ -0,0 +1,81 @@ +# 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 path of the sysroot that is applied when compiling using the target + # toolchain. + target_sysroot = "" + + # The path to directory containing linux sysroot images. + target_sysroot_dir = "//build/linux" + + # The path of the sysroot for the current toolchain. If empty, default + # sysroot is used. + sysroot = "" + + # Controls default is_linux sysroot. If set to true, and sysroot + # is empty, default sysroot is calculated. + use_sysroot = target_cpu == "x86" || target_cpu == "x64" || + target_cpu == "arm" || target_cpu == "arm64" || + target_cpu == "mipsel" || target_cpu == "mips64el" +} + +if (sysroot == "") { + if (current_os == target_os && target_cpu == target_cpu && + target_sysroot != "") { + sysroot = target_sysroot + } else if (is_android) { + import("//build/config/android/config.gni") + + # Android uses unified headers, and thus a single compile time sysroot + sysroot = "$android_toolchain_root/sysroot" + } else if ((is_linux || is_chromeos) && use_sysroot) { + # By default build against a sysroot image downloaded from Cloud Storage + # during gclient runhooks. + if (target_cpu == "x64") { + sysroot = "$target_sysroot_dir/debian_sid_amd64-sysroot" + } else if (target_cpu == "x86") { + sysroot = "$target_sysroot_dir/debian_sid_i386-sysroot" + } else if (target_cpu == "mipsel") { + sysroot = "$target_sysroot_dir/debian_sid_mips-sysroot" + } else if (target_cpu == "mips64el") { + sysroot = "$target_sysroot_dir/debian_sid_mips64el-sysroot" + } else if (target_cpu == "arm") { + sysroot = "$target_sysroot_dir/debian_sid_arm-sysroot" + } else if (target_cpu == "arm64") { + sysroot = "$target_sysroot_dir/debian_sid_arm64-sysroot" + } else { + assert(false, "No linux sysroot for cpu: $target_cpu") + } + + if (sysroot != "") { + _script_arch = target_cpu + if (_script_arch == "x86") { + _script_arch = "i386" + } else if (_script_arch == "x64") { + _script_arch = "amd64" + } + #assert( + # exec_script("//build/dir_exists.py", + # [ rebase_path(sysroot) ], + # "string") == "True", + # "Missing sysroot ($sysroot). To fix, run: build/linux/sysroot_scripts/install-sysroot.py --arch=$_script_arch") + } + } 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 if (is_fuchsia) { + if (target_cpu == "arm64" || target_cpu == "x64") { + sysroot = "//third_party/fuchsia-sdk/sdk/arch/$target_cpu/sysroot" + } + } +} diff --git a/third_party/libwebrtc/build/config/ui.gni b/third_party/libwebrtc/build/config/ui.gni new file mode 100644 index 0000000000..1bcc33e192 --- /dev/null +++ b/third_party/libwebrtc/build/config/ui.gni @@ -0,0 +1,52 @@ +# 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. + +# ============================================= +# PLEASE DO NOT ADD MORE FLAGS TO THIS FILE +# ============================================= +# +# These flags are effectively global. Your feature flag should go near the +# code it controls. Most of these items are here now because they control +# legacy global #defines passed to the compiler (now replaced with generated +# buildflag headers -- see //build/buildflag_header.gni). +# +# These flags are ui-related so should eventually be moved to various places +# in //ui/*. +# +# There is more advice on where to put build flags in the "Build flag" section +# of //build/config/BUILDCONFIG.gn. + +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/ozone.gni") + +declare_args() { + # 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 || is_chromeos || is_fuchsia +} + +declare_args() { + # True means the UI is built using the "views" framework. + toolkit_views = is_mac || is_win || is_linux || is_chromeos || is_fuchsia + + use_glib = (is_linux || is_bsd) && !is_chromecast +} + +assert(!use_glib || ((is_linux || is_bsd) && !is_chromecast)) + +use_atk = (is_linux || is_bsd) && !is_chromecast && use_glib && + current_toolchain == default_toolchain + +# Whether using Xvfb to provide a display server for a test might be +# necessary. +use_xvfb_in_this_config = + is_linux || (is_chromeos_lacros && !is_chromeos_device) +# +# ============================================= +# PLEASE DO NOT ADD MORE FLAGS TO THIS FILE +# ============================================= +# +# See comment at the top. diff --git a/third_party/libwebrtc/build/config/v8_target_cpu.gni b/third_party/libwebrtc/build/config/v8_target_cpu.gni new file mode 100644 index 0000000000..305981f3fa --- /dev/null +++ b/third_party/libwebrtc/build/config/v8_target_cpu.gni @@ -0,0 +1,61 @@ +# 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. + +import("//build/config/sanitizers/sanitizers.gni") + +declare_args() { + # This arg is used when we want to tell the JIT-generating v8 code + # that we want to have it generate for an architecture that is different + # than the architecture that v8 will actually run on; we then run the + # code under an emulator. For example, we might run v8 on x86, but + # generate arm code and run that under emulation. + # + # This arg is defined here rather than in the v8 project because we want + # some of the common architecture-specific args (like arm_float_abi or + # mips_arch_variant) to be set to their defaults either if the current_cpu + # applies *or* if the v8_current_cpu applies. + # + # As described below, you can also specify the v8_target_cpu to use + # indirectly by specifying a `custom_toolchain` that contains v8_$cpu in the + # name after the normal toolchain. + # + # For example, `gn gen --args="custom_toolchain=...:clang_x64_v8_arm64"` + # is equivalent to setting --args=`v8_target_cpu="arm64"`. Setting + # `custom_toolchain` is more verbose but makes the toolchain that is + # (effectively) being used explicit. + # + # v8_target_cpu can only be used to target one architecture in a build, + # so if you wish to build multiple copies of v8 that are targeting + # different architectures, you will need to do something more + # complicated involving multiple toolchains along the lines of + # custom_toolchain, above. + v8_target_cpu = "" +} + +if (v8_target_cpu == "") { + if (current_toolchain == "//build/toolchain/linux:clang_x64_v8_arm64") { + v8_target_cpu = "arm64" + } else if (current_toolchain == "//build/toolchain/linux:clang_x86_v8_arm") { + v8_target_cpu = "arm" + } else if (current_toolchain == + "//build/toolchain/linux:clang_x86_v8_mips64el") { + v8_target_cpu = "mips64el" + } else if (current_toolchain == + "//build/toolchain/linux:clang_x86_v8_mipsel") { + v8_target_cpu = "mipsel" + } else if (is_msan) { + # If we're running under a sanitizer, if we configure v8 to generate + # code that will be run under a simulator, then the generated code + # also gets the benefits of the sanitizer. + v8_target_cpu = "arm64" + } else { + v8_target_cpu = target_cpu + } +} + +declare_args() { + # This argument is declared here so that it can be overridden in toolchains. + # It should never be explicitly set by the user. + v8_current_cpu = v8_target_cpu +} diff --git a/third_party/libwebrtc/build/config/win/BUILD.gn b/third_party/libwebrtc/build/config/win/BUILD.gn new file mode 100644 index 0000000000..afeea308df --- /dev/null +++ b/third_party/libwebrtc/build/config/win/BUILD.gn @@ -0,0 +1,610 @@ +# 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/c++/c++.gni") +import("//build/config/chrome_build.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/win/control_flow_guard.gni") +import("//build/config/win/visual_studio_version.gni") +import("//build/timestamp.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/toolchain.gni") + +assert(is_win) + +declare_args() { + # Turn this on to have the linker output extra timing information. + win_linker_timing = false + + # possible values for target_winuwp_version: + # "10" - Windows UWP 10 + # "8.1" - Windows RT 8.1 + # "8.0" - Windows RT 8.0 + target_winuwp_version = "10" + + # possible values: + # "app" - Windows Store Applications + # "phone" - Windows Phone Applications + # "system" - Windows Drivers and Tools + # "server" - Windows Server Applications + # "desktop" - Windows Desktop Applications + target_winuwp_family = "app" + + # Set this to use clang-style diagnostics format instead of MSVC-style, which + # is useful in e.g. Emacs compilation mode. + # E.g.: + # Without this, clang emits a diagnostic message like this: + # foo/bar.cc(12,34): error: something went wrong + # and with this switch, clang emits it like this: + # foo/bar.cc:12:34: error: something went wrong + use_clang_diagnostics_format = false +} + +# This is included by reference in the //build/config/compiler config that +# is applied to all targets. It is here to separate out the logic that is +# Windows-only. +config("compiler") { + if (target_cpu == "x86") { + asmflags = [ + # When /safeseh is specified, the linker will only produce an image if it + # can also produce a table of the image's safe exception handlers. This + # table specifies for the operating system which exception handlers are + # valid for the image. Note that /SAFESEH isn't accepted on the command + # line, only /safeseh. This is only accepted by ml.exe, not ml64.exe. + "/safeseh", + ] + } + + cflags = [ + "/Gy", # Enable function-level linking. + "/FS", # Preserve previous PDB behavior. + "/bigobj", # Some of our files are bigger than the regular limits. + "/utf-8", # Assume UTF-8 by default to avoid code page dependencies. + ] + + if (is_clang) { + cflags += [ "/Zc:twoPhase" ] + } + + # Force C/C++ mode for the given GN detected file type. This is necessary + # for precompiled headers where the same source file is compiled in both + # modes. + cflags_c = [ "/TC" ] + cflags_cc = [ "/TP" ] + + cflags += [ + # Work around crbug.com/526851, bug in VS 2015 RTM compiler. + "/Zc:sizedDealloc-", + ] + + if (is_clang) { + # Required to make the 19041 SDK compatible with clang-cl. + # See https://crbug.com/1089996 issue #2 for details. + cflags += [ "/D__WRL_ENABLE_FUNCTION_STATICS__" ] + + # Tell clang which version of MSVC to emulate. + cflags += [ "-fmsc-version=1916" ] + + if (is_component_build) { + cflags += [ + # Do not export inline member functions. This makes component builds + # faster. This is similar to -fvisibility-inlines-hidden. + "/Zc:dllexportInlines-", + ] + } + + if (target_cpu == "x86") { + cflags += [ "-m32" ] + } else if (target_cpu == "x64") { + cflags += [ "-m64" ] + } else if (target_cpu == "arm64") { + cflags += [ "--target=arm64-windows" ] + } else { + assert(false, "unknown target_cpu " + target_cpu) + } + + # Chrome currently requires SSE3. Clang supports targeting any Intel + # microarchitecture. MSVC only supports a subset of architectures, and the + # next step after SSE2 will be AVX. + if (target_cpu == "x86" || target_cpu == "x64") { + cflags += [ "-msse3" ] + } + + 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", + ] + } + + if (use_clang_diagnostics_format) { + cflags += [ "/clang:-fdiagnostics-format=clang" ] + } + } + + # Disabled with cc_wrapper because of https://github.com/mozilla/sccache/issues/264 + if (use_lld && !use_thin_lto && (is_clang || !use_goma) && cc_wrapper == "") { + # /Brepro lets the compiler not write the mtime field in the .obj output. + # link.exe /incremental relies on this field to work correctly, but lld + # never looks at this timestamp, so it's safe to pass this flag with + # lld and get more deterministic compiler output in return. + # In LTO builds, the compiler doesn't write .obj files containing mtimes, + # so /Brepro is ignored there. + cflags += [ "/Brepro" ] + } + + ldflags = [] + + if (use_lld) { + # lld defaults to writing the current time in the pe/coff header. + # For build reproducibility, pass an explicit timestamp. See + # build/compute_build_timestamp.py for how the timestamp is chosen. + # (link.exe also writes the current time, but it doesn't have a flag to + # override that behavior.) + ldflags += [ "/TIMESTAMP:" + build_timestamp ] + + # Don't look for libpaths in %LIB%, similar to /X in cflags above. + ldflags += [ "/lldignoreenv" ] + } + + if (!is_debug && !is_component_build) { + # Enable standard linker optimizations like GC (/OPT:REF) and ICF in static + # release builds. + # Release builds always want these optimizations, so enable them explicitly. + ldflags += [ + "/OPT:REF", + "/OPT:ICF", + "/INCREMENTAL:NO", + "/FIXED:NO", + ] + + if (use_lld) { + # String tail merging leads to smaller binaries, but they don't compress + # as well, leading to increased mini_installer size (crbug.com/838449). + ldflags += [ "/OPT:NOLLDTAILMERGE" ] + } + + # TODO(siggi): Is this of any use anymore? + # /PROFILE ensures that the PDB file contains FIXUP information (growing the + # PDB file by about 5%) but does not otherwise alter the output binary. It + # is enabled opportunistically for builds where it is not prohibited (not + # supported when incrementally linking, or using /debug:fastlink). + ldflags += [ "/PROFILE" ] + } + + # arflags apply only to static_libraries. The normal linker configs are only + # set for executable and shared library targets so arflags must be set + # elsewhere. Since this is relatively contained, we just apply them in this + # more general config and they will only have an effect on static libraries. + arflags = [ + # "No public symbols found; archive member will be inaccessible." This + # means that one or more object files in the library can never be + # pulled in to targets that link to this library. It's just a warning that + # the source file is a no-op. + "/ignore:4221", + ] +} + +# This is included by reference in the //build/config/compiler:runtime_library +# config that is applied to all targets. It is here to separate out the logic +# that is Windows-only. Please see that target for advice on what should go in +# :runtime_library vs. :compiler. +config("runtime_library") { + cflags = [] + cflags_cc = [] + + # Defines that set up the CRT. + defines = [ + "__STD_C", + "_CRT_RAND_S", + "_CRT_SECURE_NO_DEPRECATE", + "_SCL_SECURE_NO_DEPRECATE", + ] + + # Defines that set up the Windows SDK. + defines += [ + "_ATL_NO_OPENGL", + "_WINDOWS", + "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS", + "PSAPI_VERSION=2", + "WIN32", + "_SECURE_ATL", + ] + + if (current_os == "winuwp") { + # When targeting Windows Runtime, certain compiler/linker flags are + # necessary. + defines += [ + "WINUWP", + "__WRL_NO_DEFAULT_LIB__", + ] + if (target_winuwp_family == "app") { + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_PC_APP" ] + } else if (target_winuwp_family == "phone") { + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_PHONE_APP" ] + } else if (target_winuwp_family == "system") { + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_SYSTEM" ] + } else if (target_winuwp_family == "server") { + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_SERVER" ] + } else { + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP" ] + } + cflags_cc += [ "/EHsc" ] + + # This warning is given because the linker cannot tell the difference + # between consuming WinRT APIs versus authoring WinRT within static + # libraries as such this warning is always given by the linker. Since + # consuming WinRT APIs within a library is legitimate but authoring + # WinRT APis is not allowed, this warning is disabled to ignore the + # legitimate consumption of WinRT APIs within static library builds. + arflags = [ "/IGNORE:4264" ] + + if (target_winuwp_version == "10") { + defines += [ "WIN10=_WIN32_WINNT_WIN10" ] + } else if (target_winuwp_version == "8.1") { + defines += [ "WIN8_1=_WIN32_WINNT_WINBLUE" ] + } else if (target_winuwp_version == "8.0") { + defines += [ "WIN8=_WIN32_WINNT_WIN8" ] + } + } else { + # When not targeting Windows Runtime, make sure the WINAPI family is set + # to desktop. + defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP" ] + } +} + +# Chromium supports running on Windows 7, but if these constants are set to +# Windows 7, then newer APIs aren't made available by the Windows SDK. +# So we set this to Windows 10 and then are careful to check at runtime +# to only call newer APIs when they're available. +# Some third-party libraries assume that these defines set what version of +# Windows is available at runtime. Targets using these libraries need to +# manually override this config for their compiles. +config("winver") { + defines = [ + "NTDDI_VERSION=0x0A000000", + + # We can't say `=_WIN32_WINNT_WIN10` here because some files do + # `#if WINVER < 0x0600` without including windows.h before, + # and then _WIN32_WINNT_WIN10 isn't yet known to be 0x0A00. + "_WIN32_WINNT=0x0A00", + "WINVER=0x0A00", + ] +} + +# Linker flags for Windows SDK setup, this is applied only to EXEs and DLLs. +config("sdk_link") { + assert(target_cpu == "x64" || target_cpu == "x86" || target_cpu == "arm" || + target_cpu == "arm64", + "Only supports x64, x86, arm and arm64 CPUs") + if (target_cpu == "x64") { + ldflags = [ "/MACHINE:X64" ] + } else if (target_cpu == "x86") { + ldflags = [ + "/MACHINE:X86", + "/SAFESEH", # Not compatible with x64 so use only for x86. + "/largeaddressaware", + ] + } else if (target_cpu == "arm") { + ldflags = [ "/MACHINE:ARM" ] + } else if (target_cpu == "arm64") { + ldflags = [ "/MACHINE:ARM64" ] + } + + vcvars_toolchain_data = exec_script("../../toolchain/win/setup_toolchain.py", + [ + visual_studio_path, + windows_sdk_path, + visual_studio_runtime_dirs, + current_os, + target_cpu, + "none", + ], + "scope") + + vc_lib_path = vcvars_toolchain_data.vc_lib_path + if (defined(vcvars_toolchain_data.vc_lib_atlmfc_path)) { + vc_lib_atlmfc_path = vcvars_toolchain_data.vc_lib_atlmfc_path + } + vc_lib_um_path = vcvars_toolchain_data.vc_lib_um_path + + lib_dirs = [ + "$vc_lib_um_path", + "$vc_lib_path", + ] + if (defined(vc_lib_atlmfc_path)) { + lib_dirs += [ "$vc_lib_atlmfc_path" ] + } +} + +# 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", + "/DYNAMICBASE", + ] + + if (win_linker_timing) { + ldflags += [ + "/time", + "/verbose:incr", + ] + } +} + +config("default_cfg_compiler") { + # Emit table of address-taken functions for Control-Flow Guard (CFG). + # This is needed to allow functions to be called by code that is built + # with CFG enabled, such as system libraries. + # The CFG guards are only emitted if |win_enable_cfg_guards| is enabled. + if (is_clang) { + if (win_enable_cfg_guards) { + cflags = [ "/guard:cf" ] + } else { + cflags = [ "/guard:cf,nochecks" ] + } + } +} + +# To disable CFG guards for a target, remove the "default_cfg_compiler" +# config, and add "disable_guards_cfg_compiler" config. +config("disable_guards_cfg_compiler") { + # Emit table of address-taken functions for Control-Flow Guard (CFG). + # This is needed to allow functions to be called by code that is built + # with CFG enabled, such as system libraries. + if (is_clang) { + cflags = [ "/guard:cf,nochecks" ] + } +} + +config("cfi_linker") { + # Control Flow Guard (CFG) + # https://msdn.microsoft.com/en-us/library/windows/desktop/mt637065.aspx + # /DYNAMICBASE (ASLR) is turned off in debug builds, therefore CFG cannot be + # turned on either. + # ASan and CFG leads to slow process startup. Chromium's test runner uses + # lots of child processes, so this means things are really slow. Disable CFG + # for now. https://crbug.com/846966 + if (!is_debug && !is_asan) { + # Turn on CFG bitmap generation and CFG load config. + ldflags = [ "/guard:cf" ] + } +} + +# This is a superset of all the delayloads needed for chrome.exe, chrome.dll, +# chrome_child.dll, and chrome_elf.dll. The linker will automatically ignore +# anything which is not linked to the binary at all. +# Most of the dlls are simply not required at startup (or at all, depending +# on how the browser is used). The following dlls are interconnected and need to +# be delayloaded together to ensure user32 does not load too early or at all, +# depending on the process type: user32, gdi32, comctl32, comdlg32, cryptui, +# d3d9, dwmapi, imm32, msi, ole32, oleacc, rstrtmgr, shell32, shlwapi, and +# uxtheme. +# There are some exceptions to this list which need to be declared separately. +# Some dlls cannot be delayloaded by chrome_child.dll due to the sandbox +# restrictions that prevent them from being loaded properly. Those dlls are +# specified in the separate config below. +# This config should also be used for any test binary whose goal is to run +# tests with the full browser. +config("delayloads") { + ldflags = [ + "/DELAYLOAD:api-ms-win-core-winrt-error-l1-1-0.dll", + "/DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll", + "/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll", + "/DELAYLOAD:comctl32.dll", + "/DELAYLOAD:comdlg32.dll", + "/DELAYLOAD:credui.dll", + "/DELAYLOAD:cryptui.dll", + "/DELAYLOAD:d3d11.dll", + "/DELAYLOAD:d3d9.dll", + "/DELAYLOAD:dwmapi.dll", + "/DELAYLOAD:dxgi.dll", + "/DELAYLOAD:dxva2.dll", + "/DELAYLOAD:esent.dll", + "/DELAYLOAD:gdi32.dll", + "/DELAYLOAD:hid.dll", + "/DELAYLOAD:imagehlp.dll", + "/DELAYLOAD:imm32.dll", + "/DELAYLOAD:msi.dll", + "/DELAYLOAD:netapi32.dll", + "/DELAYLOAD:ncrypt.dll", + "/DELAYLOAD:ole32.dll", + "/DELAYLOAD:oleacc.dll", + "/DELAYLOAD:propsys.dll", + "/DELAYLOAD:psapi.dll", + "/DELAYLOAD:rpcrt4.dll", + "/DELAYLOAD:rstrtmgr.dll", + "/DELAYLOAD:setupapi.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:urlmon.dll", + "/DELAYLOAD:user32.dll", + "/DELAYLOAD:usp10.dll", + "/DELAYLOAD:uxtheme.dll", + "/DELAYLOAD:wer.dll", + "/DELAYLOAD:wevtapi.dll", + "/DELAYLOAD:wininet.dll", + "/DELAYLOAD:winusb.dll", + "/DELAYLOAD:wsock32.dll", + "/DELAYLOAD:wtsapi32.dll", + ] +} + +config("delayloads_not_for_child_dll") { + ldflags = [ + "/DELAYLOAD:advapi32.dll", + "/DELAYLOAD:crypt32.dll", + "/DELAYLOAD:dbghelp.dll", + "/DELAYLOAD:dhcpcsvc.dll", + "/DELAYLOAD:dwrite.dll", + "/DELAYLOAD:iphlpapi.dll", + "/DELAYLOAD:oleaut32.dll", + "/DELAYLOAD:secur32.dll", + "/DELAYLOAD:uiautomationcore.dll", + "/DELAYLOAD:userenv.dll", + "/DELAYLOAD:winhttp.dll", + "/DELAYLOAD:winmm.dll", + "/DELAYLOAD:winspool.drv", + "/DELAYLOAD:wintrust.dll", + "/DELAYLOAD:ws2_32.dll", + ] +} + +# CRT -------------------------------------------------------------------------- + +# Configures how the runtime library (CRT) is going to be used. +# See https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx for a reference of +# what each value does. +config("default_crt") { + if (is_component_build) { + # Component mode: dynamic CRT. Since the library is shared, it requires + # exceptions or will give errors about things not matching, so keep + # exceptions on. + configs = [ ":dynamic_crt" ] + } else { + if (current_os == "winuwp") { + # https://blogs.msdn.microsoft.com/vcblog/2014/06/10/the-great-c-runtime-crt-refactoring/ + # contains a details explanation of what is happening with the Windows + # CRT in Visual Studio releases related to Windows store applications. + configs = [ ":dynamic_crt" ] + } else { + # Desktop Windows: static CRT. + configs = [ ":static_crt" ] + } + } +} + +# Use this to force use of the release CRT when building perf-critical build +# tools that need to be fully optimized even in debug builds, for those times +# when the debug CRT is part of the bottleneck. This also avoids *implicitly* +# defining _DEBUG. +config("release_crt") { + if (is_component_build) { + cflags = [ "/MD" ] + + if (use_custom_libcxx) { + # On Windows, including libcpmt[d]/msvcprt[d] explicitly links the C++ + # standard library, which libc++ needs for exception_ptr internals. + ldflags = [ "/DEFAULTLIB:msvcprt.lib" ] + } + } else { + cflags = [ "/MT" ] + if (use_custom_libcxx) { + ldflags = [ "/DEFAULTLIB:libcpmt.lib" ] + } + } +} + +config("dynamic_crt") { + if (is_debug) { + # This pulls in the DLL debug CRT and defines _DEBUG + cflags = [ "/MDd" ] + if (use_custom_libcxx) { + ldflags = [ "/DEFAULTLIB:msvcprtd.lib" ] + } + } else { + cflags = [ "/MD" ] + if (use_custom_libcxx) { + ldflags = [ "/DEFAULTLIB:msvcprt.lib" ] + } + } +} + +config("static_crt") { + if (is_debug) { + # This pulls in the static debug CRT and defines _DEBUG + cflags = [ "/MTd" ] + if (use_custom_libcxx) { + ldflags = [ "/DEFAULTLIB:libcpmtd.lib" ] + } + } else { + cflags = [ "/MT" ] + if (use_custom_libcxx) { + ldflags = [ "/DEFAULTLIB:libcpmt.lib" ] + } + } +} + +# Subsystem -------------------------------------------------------------------- + +# This is appended to the subsystem to specify a minimum version. +if (target_cpu == "x64") { + # The number after the comma is the minimum required OS version. + # 5.02 = Windows Server 2003. + subsystem_version_suffix = ",5.02" +} else if (target_cpu == "arm64") { + # Windows ARM64 requires Windows 10. + subsystem_version_suffix = ",10.0" +} 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 ---------------------------------------------------------- + +# Applies incremental linking or not depending on the current configuration. +config("default_incremental_linking") { + # Enable incremental linking for debug builds and all component builds - any + # builds where performance is not job one. + # TODO(thakis): Always turn this on with lld, no reason not to. + if (is_debug || is_component_build) { + ldflags = [ "/INCREMENTAL" ] + if (use_lld) { + # lld doesn't use ilk files and doesn't really have an incremental link + # mode; the only effect of the flag is that the .lib file timestamp isn't + # updated if the .lib doesn't change. + # TODO(thakis): Why pass /OPT:NOREF for lld, but not otherwise? + # TODO(thakis): /INCREMENTAL is on by default in link.exe, but not in + # lld. + ldflags += [ "/OPT:NOREF" ] + } + } else { + ldflags = [ "/INCREMENTAL:NO" ] + } +} + +# 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 accommodating" 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/third_party/libwebrtc/build/config/win/console_app.gni b/third_party/libwebrtc/build/config/win/console_app.gni new file mode 100644 index 0000000000..cac2ef5d73 --- /dev/null +++ b/third_party/libwebrtc/build/config/win/console_app.gni @@ -0,0 +1,18 @@ +# 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. + +import("//build/config/sanitizers/sanitizers.gni") + +declare_args() { + # If true, builds as a console app (rather than a windowed app), which allows + # logging to be printed to the user. This will cause a terminal window to pop + # up when the executable is not run from the command line, so should only be + # used for development. Only has an effect on Windows builds. + win_console_app = false +} + +if (is_win && is_asan) { + # AddressSanitizer build should be a console app since it writes to stderr. + win_console_app = true +} diff --git a/third_party/libwebrtc/build/config/win/control_flow_guard.gni b/third_party/libwebrtc/build/config/win/control_flow_guard.gni new file mode 100644 index 0000000000..bf6a82af0c --- /dev/null +++ b/third_party/libwebrtc/build/config/win/control_flow_guard.gni @@ -0,0 +1,23 @@ +# Copyright 2020 The Chromium 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") + +declare_args() { + # Set this to true to enable generation of CFG indirect call dispatch + # guards. + win_enable_cfg_guards = false +} + +if (win_enable_cfg_guards) { + # Control Flow Guard (CFG) + # https://msdn.microsoft.com/en-us/library/windows/desktop/mt637065.aspx + # /DYNAMICBASE (ASLR) is turned off in debug builds, therefore CFG can't be + # turned on either. + # ASan and CFG leads to slow process startup. Chromium's test runner uses + # lots of child processes, so this means things are really slow. Disable CFG + # for now. https://crbug.com/846966 + assert(!is_debug && !is_asan, + "CFG does not work well in debug builds or with ASAN") +} diff --git a/third_party/libwebrtc/build/config/win/manifest.gni b/third_party/libwebrtc/build/config/win/manifest.gni new file mode 100644 index 0000000000..e2115083fe --- /dev/null +++ b/third_party/libwebrtc/build/config/win/manifest.gni @@ -0,0 +1,118 @@ +# 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. + +# HOW MANIFESTS WORK IN THE GN BUILD +# +# Use the windows_manifest template to declare a manifest generation step. +# This will combine all listed .manifest files. To link this manifest, just +# depend on the manifest target from your executable or shared library. +# +# This will define an empty placeholder target on non-Windows platforms so +# the manifest declarations and dependencies do not need to be inside of OS +# conditionals. +# +# A binary can depend on only one manifest target, but the manifest target +# can depend on many individual .manifest files which will be merged. As a +# result, only executables and shared libraries should depend on manifest +# targets. If you want to add a manifest to a component, put the dependency +# behind a "if (is_component_build)" conditional. +# +# Generally you will just want the defaults for the Chrome build. In this case +# the binary should just depend on one of the targets in //build/win/. There +# are also individual manifest files in that directory you can reference via +# the *_manifest variables defined below to pick and choose only some defaults. +# You might combine these with a custom manifest file to get specific behavior. + +# Reference this manifest as a source from windows_manifest targets to get +# the default Chrome OS compatibility list. +default_compatibility_manifest = "//build/win/compatibility.manifest" + +# Reference this manifest as a source from windows_manifest targets to get +# the default Chrome common constrols compatibility. +common_controls_manifest = "//build/win/common_controls.manifest" + +# Reference this manifest to request that Windows not perform any elevation +# when running your program. Otherwise, it might do some autodetection and +# request elevated privileges from the user. This is normally what you want. +as_invoker_manifest = "//build/win/as_invoker.manifest" + +# An alternative to as_invoker_manifest when you want the application to always +# elevate. +require_administrator_manifest = "//build/win/require_administrator.manifest" + +# Request the segment heap. See https://crbug.com/1014701 for details. +declare_args() { + enable_segment_heap = false +} +segment_heap_manifest = "//build/win/segment_heap.manifest" + +# Construct a target to combine the given manifest files into a .rc file. +# +# Variables for the windows_manifest template: +# +# sources: (required) +# List of source .manifest files to add. +# +# deps: (optional) +# visibility: (optional) +# Normal meaning. +# +# Example: +# +# windows_manifest("doom_melon_manifest") { +# sources = [ +# "doom_melon.manifest", # Custom values in here. +# default_compatibility_manifest, # Want the normal OS compat list. +# ] +# } +# +# executable("doom_melon") { +# deps = [ ":doom_melon_manifest" ] +# ... +# } + +if (is_win) { + template("windows_manifest") { + config_name = "${target_name}__config" + source_set_name = target_name + + config(config_name) { + visibility = [ ":$source_set_name" ] + assert(defined(invoker.sources), + "\"sources\" must be defined for a windows_manifest target") + manifests = [] + foreach(i, rebase_path(invoker.sources, root_build_dir)) { + manifests += [ "/manifestinput:" + i ] + } + ldflags = [ + "/manifest:embed", + + # We handle UAC by adding explicit .manifest files instead. + "/manifestuac:no", + ] + manifests + } + + # This source set only exists to add a dep on the invoker's deps and to + # add a public_config that sets ldflags on dependents. + source_set(source_set_name) { + forward_variables_from(invoker, [ "visibility" ]) + public_configs = [ ":$config_name" ] + + # Apply any dependencies from the invoker to this target, since those + # dependencies may have created the input manifest files. + forward_variables_from(invoker, [ "deps" ]) + } + } +} else { + # Make a no-op group on non-Windows platforms so windows_manifest + # instantiations don't need to be inside windows blocks. + template("windows_manifest") { + group(target_name) { + # Prevent unused variable warnings on non-Windows platforms. + assert(invoker.sources != "") + assert(!defined(invoker.deps) || invoker.deps != "") + assert(!defined(invoker.visibility) || invoker.visibility != "") + } + } +} diff --git a/third_party/libwebrtc/build/config/win/visual_studio_version.gni b/third_party/libwebrtc/build/config/win/visual_studio_version.gni new file mode 100644 index 0000000000..80eb0355c4 --- /dev/null +++ b/third_party/libwebrtc/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. + # Currently always "2015". + 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\10" +} + +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/third_party/libwebrtc/build/config/x64.gni b/third_party/libwebrtc/build/config/x64.gni new file mode 100644 index 0000000000..eccf417c07 --- /dev/null +++ b/third_party/libwebrtc/build/config/x64.gni @@ -0,0 +1,24 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# These are primarily relevant in target_cpu == "x64" contexts, where +# X64 code is being compiled. +if (target_cpu == "x64") { + declare_args() { + # The micro architecture of x64 cpu. This will be a string like "haswell" or + # "skylake". An empty string means to use the default architecture which is + # "x86-64". + # CPU options for "x86-64" in GCC can be found at + # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html + # CPU options for "x86-64" in llvm can be found at + # https://github.com/llvm/llvm-project/blob/master/llvm/include/llvm/Support/X86TargetParser.def + x64_arch = "" + } + + if ((is_posix && !is_apple) || is_fuchsia) { + if (x64_arch == "") { + x64_arch = "x86-64" + } + } +} diff --git a/third_party/libwebrtc/build/config/zip.gni b/third_party/libwebrtc/build/config/zip.gni new file mode 100644 index 0000000000..68bc49444c --- /dev/null +++ b/third_party/libwebrtc/build/config/zip.gni @@ -0,0 +1,48 @@ +# 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("python.gni") + +# Creates a zip archive of the inputs. +# +# output (required) +# Path to output zip. +# inputs (required) +# List of input files to zip. +# base_dir (optional) +# If provided, the archive paths will be relative to this directory. +# Applies only to |inputs|. +# +# deps, public_deps, data, data_deps, testonly, visibility +# Normal meaning. +template("zip") { + action_with_pydeps(target_name) { + forward_variables_from(invoker, + [ + "data", + "data_deps", + "deps", + "public_deps", + "testonly", + "visibility", + ]) + script = "//build/android/gyp/zip.py" + inputs = invoker.inputs + outputs = [ invoker.output ] + + args = [ + "--output", + rebase_path(invoker.output, root_build_dir), + ] + + _rebased_inputs = rebase_path(invoker.inputs, root_build_dir) + args += [ "--input-files=$_rebased_inputs" ] + if (defined(invoker.base_dir)) { + args += [ + "--input-files-base-dir", + rebase_path(invoker.base_dir, root_build_dir), + ] + } + } +} diff --git a/third_party/libwebrtc/build/copy_test_data_ios.py b/third_party/libwebrtc/build/copy_test_data_ios.py new file mode 100755 index 0000000000..cd23fb29bd --- /dev/null +++ b/third_party/libwebrtc/build/copy_test_data_ios.py @@ -0,0 +1,107 @@ +#!/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. + +"""Copies test data files or directories into a given output directory.""" + +from __future__ import print_function + +import optparse +import os +import shutil +import sys + +class WrongNumberOfArgumentsException(Exception): + pass + +def EscapePath(path): + """Returns a path with spaces escaped.""" + return path.replace(" ", "\\ ") + +def ListFilesForPath(path): + """Returns a list of all the files under a given path.""" + output = [] + # Ignore revision control metadata directories. + if (os.path.basename(path).startswith('.git') or + os.path.basename(path).startswith('.svn')): + return output + + # Files get returned without modification. + if not os.path.isdir(path): + output.append(path) + return output + + # Directories get recursively expanded. + contents = os.listdir(path) + for item in contents: + full_path = os.path.join(path, item) + output.extend(ListFilesForPath(full_path)) + return output + +def CalcInputs(inputs): + """Computes the full list of input files for a set of command-line arguments. + """ + # |inputs| is a list of paths, which may be directories. + output = [] + for input in inputs: + output.extend(ListFilesForPath(input)) + return output + +def CopyFiles(relative_filenames, output_basedir): + """Copies files to the given output directory.""" + for file in relative_filenames: + relative_dirname = os.path.dirname(file) + output_dir = os.path.join(output_basedir, relative_dirname) + output_filename = os.path.join(output_basedir, file) + + # In cases where a directory has turned into a file or vice versa, delete it + # before copying it below. + if os.path.exists(output_dir) and not os.path.isdir(output_dir): + os.remove(output_dir) + if os.path.exists(output_filename) and os.path.isdir(output_filename): + shutil.rmtree(output_filename) + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + shutil.copy(file, output_filename) + +def DoMain(argv): + parser = optparse.OptionParser() + usage = 'Usage: %prog -o [--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 as e: + print(e, file=sys.stderr) + return 1 + if result: + print(result) + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/third_party/libwebrtc/build/cp.py b/third_party/libwebrtc/build/cp.py new file mode 100755 index 0000000000..0f32536b62 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/detect_host_arch.py b/third_party/libwebrtc/build/detect_host_arch.py new file mode 100755 index 0000000000..cad0f4bf2a --- /dev/null +++ b/third_party/libwebrtc/build/detect_host_arch.py @@ -0,0 +1,55 @@ +#!/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.""" + +from __future__ import print_function + +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' + elif host_arch.startswith('aarch64'): + host_arch = 'arm64' + elif host_arch.startswith('mips64'): + host_arch = 'mips64' + elif host_arch.startswith('mips'): + host_arch = 'mips' + elif host_arch.startswith('ppc'): + host_arch = 'ppc' + elif host_arch.startswith('s390'): + host_arch = 's390' + + + # 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' + if host_arch == 'arm64' and platform.architecture()[0] == '32bit': + host_arch = 'arm' + + 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/third_party/libwebrtc/build/dir_exists.py b/third_party/libwebrtc/build/dir_exists.py new file mode 100755 index 0000000000..70d367ec26 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/docs/debugging_slow_builds.md b/third_party/libwebrtc/build/docs/debugging_slow_builds.md new file mode 100644 index 0000000000..315690c32f --- /dev/null +++ b/third_party/libwebrtc/build/docs/debugging_slow_builds.md @@ -0,0 +1,19 @@ +# Debugging slow builds + +Some tips for debugging slow build times: +* Use [ninjatracing](https://github.com/nico/ninjatracing) and chrome:tracing to + view a timeline of the most recent build. + * Many bots output a build trace (look for a `"ninja_log"` link). +* Use `gn gen --tracelog trace.json` to create a similar trace for `gn gen`. +* Depot Tool's `autoninja` has logic for summarizing slow steps. Enable it via: + * `NINJA_SUMMARIZE_BUILD=1 autoninja -C out/Debug my_target` +* Many Android templates make use of + [`md5_check.py`](https://cs.chromium.org/chromium/src/build/android/gyp/util/md5_check.py) + to optimize incremental builds. + * Set `PRINT_BUILD_EXPLANATIONS=1` to have these commands log which inputs + changed. +* If you suspect files are being rebuilt unnecessarily during incremental + builds: + * Use `ninja -n -d explain` to figure out why ninja thinks a target is dirty. + * Ensure actions are taking advantage of ninja's `restat=1` feature by not + updating timestamps on outputs when their content does not change. diff --git a/third_party/libwebrtc/build/docs/mac_hermetic_toolchain.md b/third_party/libwebrtc/build/docs/mac_hermetic_toolchain.md new file mode 100644 index 0000000000..d5c88deefb --- /dev/null +++ b/third_party/libwebrtc/build/docs/mac_hermetic_toolchain.md @@ -0,0 +1,34 @@ +# Mac and iOS hermetic toolchain instructions + +The following is a short explanation of why we use a the hermetic toolchain +and instructions on how to roll a new toolchain. This toolchain is only +available to Googlers and infra bots. + +## How to roll a new hermetic toolchain. + +1. Download a new version of Xcode, and confirm either mac or ios builds + properly with this new version. + +2. Create a new CIPD package by moving Xcode.app to the `build/` directory, then + follow the instructions in + [build/xcode_binaries.yaml](../xcode_binaries.yaml). + + The CIPD package creates a subset of the toolchain necessary for a build. + +2. Create a CL with the updated `MAC_BINARIES_TAG` in + [mac_toolchain.py](../mac_toolchain.py) with the version created by the + previous command. + +3. Run the CL through the trybots to confirm the roll works. + +## Why we use a hermetic toolchain. + +Building Chrome Mac currently requires many binaries that come bundled with +Xcode, as well the macOS and iphoneOS SDK (also bundled with Xcode). Note that +Chrome ships its own version of clang (compiler), but is dependent on Xcode +for these other binaries. Using a hermetic toolchain has two main benefits: + +1. Build Chrome with a well-defined toolchain (rather than whatever happens to + be installed on the machine). + +2. Easily roll/update the toolchain. diff --git a/third_party/libwebrtc/build/docs/rust_toolchain.md b/third_party/libwebrtc/build/docs/rust_toolchain.md new file mode 100644 index 0000000000..6217fd5af6 --- /dev/null +++ b/third_party/libwebrtc/build/docs/rust_toolchain.md @@ -0,0 +1,21 @@ +# Rust toolchain + +Chrome currently uses an experimental Rust toolchain built by the Android +team, which supports only Linux and Android builds. + +To build Rust code on other platforms for development/experimentation, add the +following to your `gn args`: + +``` +use_unverified_rust_toolchain=true +rust_bin_dir="/.cargo/bin" +``` + +## Using VSCode, rust-analyzer etc. + +Any IDE which supports rust-analyser should be able to ingest metadata from gn +about the structure of our Rust project. Do this: + +* `gn gen out/Debug/ --export-rust-project` +* `ln -s out/Debug/rust-project.json rust-project.json`, i.e. symlink the + `rust-project.json` file to the root of the Chromium src directory. diff --git a/third_party/libwebrtc/build/docs/writing_gn_templates.md b/third_party/libwebrtc/build/docs/writing_gn_templates.md new file mode 100644 index 0000000000..21a73bb65e --- /dev/null +++ b/third_party/libwebrtc/build/docs/writing_gn_templates.md @@ -0,0 +1,344 @@ +# Writing GN Templates +GN and Ninja are documented here: +* GN: https://gn.googlesource.com/gn/+/master/docs/ +* Ninja: https://ninja-build.org/manual.html + +[TOC] + +## Things to Consider When Writing Templates +### Inputs and Depfiles +List all files read (or executed) by an action as `inputs`. + * It is not enough to have inputs listed by dependent targets. They must be + listed directly by targets that use them, or added by a depfile. + * Non-system Python imports are inputs! For scripts that import such modules, + use [`action_with_pydeps`] to ensure all dependent Python files are captured + as inputs. + +[`action_with_pydeps`]: https://cs.chromium.org/chromium/src/build/config/python.gni?rcl=320ee4295eb7fabaa112f08d1aacc88efd1444e5&l=75 + +To understand *why* actions must list all inputs directly, you need to +understand ninja's "restat" directive, which is used for all GN `action()`s. + +From https://ninja-build.org/manual.html: + +> if present, causes Ninja to re-stat the command’s outputs after execution of +> the command. Each output whose modification time the command did not change +> will be treated as though it had never needed to be built. This may cause the +> output’s reverse dependencies to be removed from the list of pending build +> actions. + +So, if your action depends on target "X", and "X" does not change its outputs +when rebuilt, then ninja will not bother to rebuild your target. + +For action inputs that are not computable during "gn gen", actions can write +depfiles (.d files) to add additional input files as dependencies for +subsequent builds. They are relevant only for incremental builds since they +won't exist for the initial build. + * Depfiles should not list files that GN already lists as `inputs`. + * Besides being redundant, listing them also makes it harder to remove + inputs, since removing them from GN does not immediately remove them from + depfiles. + * Stale paths in depfiles can cause ninja to complain of circular + dependencies [in some cases](https://bugs.chromium.org/p/chromium/issues/detail?id=639042). + +### Ensuring "gn analyze" Knows About your Inputs +"gn analyze" is used by bots to run only affected tests and build only affected +targets. Try it out locally via: +```bash +echo "compute_inputs_for_analyze = true" >> out/Debug/args.gn +gn analyze //out/Debug <(echo '{ + "files": ["//BUILD.gn"], + "test_targets": ["//base"], + "additional_compile_targets":[]}') result.txt; cat result.txt +``` +* For analyze to work properly, GN must know about all inputs. +* Inputs added by depfiles are *not available* to "gn analyze". + * When paths listed in a target's depfile are listed as `inputs` to a + dependent target, analyze will be correct. + * Example: An `AndroidManifest.xml` file is an input to an + `android_library()` and is included in an `android_apk()`'s depfile. + `gn analyze` will know that a change to the file will require the APK + to be rebuilt, because the file is marked as an input to the library, and + the library is a dep of the APK. + * When paths listed in a target's depfile are *not* listed as `inputs` to a + dependent target, a few options exist: + * Rather than putting the inputs in a depfile, force users of your template + to list them, and then have your action re-compute them and assert that + they were correct. + * `jinja_template()` does this. + * Rather than putting the inputs in a depfile, compute them beforehand and + save them to a text file. Have your template Use `read_file()` to read + them in. + * `action_with_pydeps()` does this. + * Continue using a depfile, but use an `exec_script()` to compute them when + [`compute_inputs_for_analyze`](https://cs.chromium.org/chromium/src/build/config/compute_inputs_for_analyze.gni) + is set. + * `grit()` does this. + +### Outputs +#### What to List as Outputs +Do not list files as `outputs` unless they are important. Outputs are important +if they are: + * used as an input by another target, or + * are roots in the dependency graph (e.g. binaries, apks, etc). + +Example: +* An action runs a binary that creates an output as well as a log file. Do not + list the log file as an output. + +Rationale: +* Inputs and outputs are a node's public API on the build graph. Not listing + "implementation detail"-style outputs prevents other targets from depending on + them as inputs. +* Not listing them also helps to minimize the size of the build graph (although + this would be noticeable only for frequently used templates). + +#### Where to Place Outputs +**Option 1:** To make outputs visible in codesearch (e.g. generated sources): +* use `$target_gen_dir/$target_name.$EXTENSION`. + +**Option 2:** Otherwise (for binary files): +* use `$target_out_dir/$target_name.$EXTENSION`. + +**Option 3:** For outputs that are required at runtime +(e.g. [runtime_deps](https://gn.googlesource.com/gn/+/master/docs/reference.md#runtime_deps)), +options 1 & 2 do not work because they are not archived in builder/tester bot +configurations. In this case: +* use `$root_out_dir/gen.runtime` or `$root_out_dir/obj.runtime`. + +Example: +```python +# This .json file is used at runtime and thus cannot go in target_gen_dir. +_target_dir_name = rebase_path(get_label_info(":$target_name", "dir"), "//") +_output_path = "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.json" +``` + +**Option 4:** For outputs that map 1:1 with executables, and whose paths cannot +be derived at runtime: +* use `$root_build_dir/YOUR_NAME_HERE/$target_name`. + +Examples: +```python +# Wrapper scripts for apks: +_output_path = "$root_build_dir/bin/$target_name" +# Metadata for apks. Used by binary size tools. +_output_path = "$root_build_dir/size-info/${invoker.name}.apk.jar.info" +``` + +## Best Practices for Python Actions +Outputs should be atomic and take advantage of `restat=1`. +* Make outputs atomic by writing to temporary files and then moving them to + their final location. + * Rationale: An interrupted write can leave a file with an updated timestamp + and corrupt contents. Ninja looks only at timestamps. +* Do not overwrite an existing output with identical contents. + * Rationale: `restat=1` is a ninja feature enabled for all actions that + short-circuits a build when output timestamps do not change. This feature is + the reason that the total number of build steps sometimes decreases when + building.. +* Use [`build_utils.AtomicOutput()`](https://cs.chromium.org/chromium/src/build/android/gyp/util/build_utils.py?rcl=7d6ba28e92bec865a7b7876c35b4621d56fb37d8&l=128) + to perform both of these techniques. + +Actions should be deterministic in order to avoid hard-to-reproduce bugs. +Given identical inputs, they should produce byte-for-byte identical outputs. +* Some common mistakes: + * Depending on filesystem iteration order. + * Writing timestamps in files (or in zip entries). + * Writing absolute paths in outputs. + +## Style Guide +Chromium GN files follow +[GN's Style Guide](https://gn.googlesource.com/gn/+/master/docs/style_guide.md) +with a few additions. + +### Action Granularity + * Prefer writing new Python scripts that do what you want over + composing multiple separate actions within a template. + * Fewer targets makes for a simpler build graph. + * GN logic and build logic winds up much simpler. + +Bad: +```python +template("generate_zipped_sources") { + generate_files("${target_name}__gen") { + ... + outputs = [ "$target_gen_dir/$target_name.temp" ] + } + zip(target_name) { + deps = [ ":${target_name}__gen" ] + inputs = [ "$target_gen_dir/$target_name.temp" ] + outputs = [ invoker.output_zip ] + } +} +``` + +Good: +```python +template("generate_zipped_sources") { + action(target_name) { + script = "generate_and_zip.py" + ... + outputs = [ invoker.output_zip ] + } +} +``` + +### Naming for Intermediate Targets +Targets that are not relevant to users of your template should be named as: +`${target_name}__$something`. + +Example: +```python +template("my_template") { + action("${target_name}__helper") { + ... + } + action(target_name) { + deps = [ ":${target_name}__helper" ] + ... + } +} +``` + +This scheme ensures that subtargets defined in templates do not conflict with +top-level targets. + +### Visibility for Intermediate Targets + +You can restrict what targets can depend on one another using [visibility]. +When writing templates, with multiple intermediate targets, `visibility` should +only be applied to the final target (the one named `target_name`). Applying only +to the final target ensures that the invoker-provided visibility does not +prevent intermediate targets from depending on each other. + +[visibility]: https://gn.googlesource.com/gn/+/master/docs/reference.md#var_visibility + +Example: +```python +template("my_template") { + # Do not forward visibility here. + action("${target_name}__helper") { + # Do not forward visibility here. + ... + } + action(target_name) { + # Forward visibility here. + forward_variables_from(invoker, [ "visibility" ]) + deps = [ ":${target_name}__helper" ] + ... + } +} +``` + +### Variables +Prefix variables within templates and targets with an underscore. For example: + +```python +template("example") { + _outer_sources = invoker.extra_sources + + source_set(target_name) { + _inner_sources = invoker.sources + sources = _outer_sources + _inner_sources + } +} +``` + +This convention conveys that `sources` is relevant to `source_set`, while +`_outer_sources` and `_inner_sources` are not. + +### Passing Arguments to Targets +Pass arguments to targets by assigning them directly within target definitions. + +When a GN template goes to resolve `invoker.FOO`, GN will look in all enclosing +scopes of the target's definition. It is hard to figure out where `invoker.FOO` +is coming from when it is not assigned directly within the target definition. + +Bad: +```python +template("hello") { + script = "..." + action(target_name) { + # This action will see "script" from the enclosing scope. + } +} +``` + +Good: +```python +template("hello") { + action(target_name) { + script = "..." # This is equivalent, but much more clear. + } +} +``` + +**Exception:** `testonly` and `visibility` can be set in the outer scope so that +they are implicitly passed to all targets within a template. + +This is okay: +```python +template("hello") { + testonly = true # Applies to all nested targets. + action(target_name) { + script = "..." + } +} +``` + +### Using forward_variables_from() +Using [forward_variables_from()] is encouraged, but special care needs to be +taken when forwarding `"*"`. The variables `testonly` and `visibility` should +always be listed explicitly in case they are assigned in an enclosing +scope. +See [this bug] for more a full example. + +To make this easier, `//build/config/BUILDCONFIG.gn` defines: +```python +TESTONLY_AND_VISIBILITY = [ "testonly", "visibility" ] +``` + +Example usage: +```python +template("action_wrapper") { + action(target_name) { + forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + ... + } +} +``` + +If your template defines multiple targets, be careful to apply `testonly` to +both, but `visibility` only to the primary one (so that the primary one is not +prevented from depending on the other ones). + +Example: +```python +template("template_with_multiple_targets") { + action("${target_name}__helper) { + forward_variables_from(invoker, [ "testonly" ]) + ... + } + action(target_name) { + forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) + ... + } +} +``` + +An alternative would be to explicitly set `visibility` on all inner targets, +but doing so tends to be tedious and has little benefit. + +[this bug]: https://bugs.chromium.org/p/chromium/issues/detail?id=862232 +[forward_variables_from]: https://gn.googlesource.com/gn/+/master/docs/reference.md#func_forward_variables_from + +## Useful Ninja Flags +Useful ninja flags when developing build rules: +* `ninja -v` - log the full command-line of every target. +* `ninja -v -n` - log the full command-line of every target without having + to wait for a build. +* `ninja -w dupbuild=err` - fail if multiple targets have the same output. +* `ninja -d keeprsp` - prevent ninja from deleting response files. +* `ninja -n -d explain` - print why ninja thinks a target is dirty. +* `ninja -j1` - execute only one command at a time. diff --git a/third_party/libwebrtc/build/dotfile_settings.gni b/third_party/libwebrtc/build/dotfile_settings.gni new file mode 100644 index 0000000000..3ec6206c50 --- /dev/null +++ b/third_party/libwebrtc/build/dotfile_settings.gni @@ -0,0 +1,42 @@ +# Copyright (c) 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. + +# This file contains variables that can be imported into a repo's dotfile (.gn) +# to make it easier to roll new versions of //build in. + +build_dotfile_settings = { + exec_script_whitelist = [ + "//build/config/android/config.gni", + "//build/config/android/internal_rules.gni", + "//build/config/android/rules.gni", + "//build/config/chromeos/rules.gni", + "//build/config/compiler/BUILD.gn", + "//build/config/compiler/pgo/BUILD.gn", + "//build/config/gcc/gcc_version.gni", + "//build/config/host_byteorder.gni", + "//build/config/ios/ios_sdk.gni", + "//build/config/ios/rules.gni", + "//build/config/linux/atk/BUILD.gn", + "//build/config/linux/atspi2/BUILD.gn", + "//build/config/linux/BUILD.gn", + "//build/config/linux/dri/BUILD.gn", + "//build/config/linux/pkg_config.gni", + "//build/config/mac/mac_sdk.gni", + "//build/config/mac/rules.gni", + "//build/config/posix/BUILD.gn", + "//build/config/sysroot.gni", + "//build/config/win/BUILD.gn", + "//build/config/win/visual_studio_version.gni", + "//build/timestamp.gni", + "//build/toolchain/apple/toolchain.gni", + "//build/toolchain/BUILD.gn", + "//build/toolchain/concurrent_links.gni", + "//build/toolchain/goma.gni", + "//build/toolchain/nacl/BUILD.gn", + "//build/toolchain/toolchain.gni", + "//build/toolchain/win/BUILD.gn", + "//build/util/branding.gni", + "//build/util/version.gni", + ] +} diff --git a/third_party/libwebrtc/build/download_nacl_toolchains.py b/third_party/libwebrtc/build/download_nacl_toolchains.py new file mode 100755 index 0000000000..286a92a27e --- /dev/null +++ b/third_party/libwebrtc/build/download_nacl_toolchains.py @@ -0,0 +1,56 @@ +#!/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.""" + +from __future__ import print_function + +import os +import shutil +import sys + + +def Main(args): + 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 + + return package_version.main(args) + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/env_dump.py b/third_party/libwebrtc/build/env_dump.py new file mode 100755 index 0000000000..3f8217398c --- /dev/null +++ b/third_party/libwebrtc/build/env_dump.py @@ -0,0 +1,56 @@ +#!/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 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/third_party/libwebrtc/build/extract_from_cab.py b/third_party/libwebrtc/build/extract_from_cab.py new file mode 100755 index 0000000000..9695b96789 --- /dev/null +++ b/third_party/libwebrtc/build/extract_from_cab.py @@ -0,0 +1,65 @@ +#!/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.""" + +from __future__ import print_function + +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/third_party/libwebrtc/build/extract_partition.py b/third_party/libwebrtc/build/extract_partition.py new file mode 100755 index 0000000000..8b79a065d7 --- /dev/null +++ b/third_party/libwebrtc/build/extract_partition.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# Copyright 2019 The Chromium 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 an LLD partition from an ELF file.""" + +import argparse +import hashlib +import math +import os +import struct +import subprocess +import sys +import tempfile + + +def _ComputeNewBuildId(old_build_id, file_path): + """ + Computes the new build-id from old build-id and file_path. + + Args: + old_build_id: Original build-id in bytearray. + file_path: Path to output ELF file. + + Returns: + New build id with the same length as |old_build_id|. + """ + m = hashlib.sha256() + m.update(old_build_id) + m.update(os.path.basename(file_path).encode('utf-8')) + hash_bytes = m.digest() + # In case build_id is longer than hash computed, repeat the hash + # to the desired length first. + id_size = len(old_build_id) + hash_size = len(hash_bytes) + return (hash_bytes * (id_size // hash_size + 1))[:id_size] + + +def _ExtractPartition(objcopy, input_elf, output_elf, partition): + """ + Extracts a partition from an ELF file. + + For partitions other than main partition, we need to rewrite + the .note.gnu.build-id section so that the build-id remains + unique. + + Note: + - `objcopy` does not modify build-id when partitioning the + combined ELF file by default. + - The new build-id is calculated as hash of original build-id + and partitioned ELF file name. + + Args: + objcopy: Path to objcopy binary. + input_elf: Path to input ELF file. + output_elf: Path to output ELF file. + partition: Partition to extract from combined ELF file. None when + extracting main partition. + """ + if not partition: # main partition + # We do not overwrite build-id on main partition to allow the expected + # partition build ids to be synthesized given a libchrome.so binary, + # if necessary. + subprocess.check_call( + [objcopy, '--extract-main-partition', input_elf, output_elf]) + return + + # partitioned libs + build_id_section = '.note.gnu.build-id' + + with tempfile.TemporaryDirectory() as tempdir: + temp_elf = os.path.join(tempdir, 'obj_without_id.so') + old_build_id_file = os.path.join(tempdir, 'old_build_id') + new_build_id_file = os.path.join(tempdir, 'new_build_id') + + # Dump out build-id section and remove original build-id section from + # ELF file. + subprocess.check_call([ + objcopy, + '--extract-partition', + partition, + # Note: Not using '--update-section' here as it is not supported + # by llvm-objcopy. + '--remove-section', + build_id_section, + '--dump-section', + '{}={}'.format(build_id_section, old_build_id_file), + input_elf, + temp_elf, + ]) + + with open(old_build_id_file, 'rb') as f: + note_content = f.read() + + # .note section has following format according to + # typedef struct { + # unsigned char namesz[4]; /* Size of entry's owner string */ + # unsigned char descsz[4]; /* Size of the note descriptor */ + # unsigned char type[4]; /* Interpretation of the descriptor */ + # char name[1]; /* Start of the name+desc data */ + # } Elf_External_Note; + # `build-id` rewrite is only required on Android platform, + # where we have partitioned lib. + # Android platform uses little-endian. + # <: little-endian + # 4x: Skip 4 bytes + # L: unsigned long, 4 bytes + descsz, = struct.Struct('<4xL').unpack_from(note_content) + prefix = note_content[:-descsz] + build_id = note_content[-descsz:] + + with open(new_build_id_file, 'wb') as f: + f.write(prefix + _ComputeNewBuildId(build_id, output_elf)) + + # Write back the new build-id section. + subprocess.check_call([ + objcopy, + '--add-section', + '{}={}'.format(build_id_section, new_build_id_file), + # Add alloc section flag, or else the section will be removed by + # objcopy --strip-all when generating unstripped lib file. + '--set-section-flags', + '{}={}'.format(build_id_section, 'alloc'), + temp_elf, + output_elf, + ]) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--partition', + help='Name of partition if not the main partition', + metavar='PART') + parser.add_argument( + '--objcopy', + required=True, + help='Path to llvm-objcopy binary', + metavar='FILE') + parser.add_argument( + '--unstripped-output', + required=True, + help='Unstripped output file', + metavar='FILE') + parser.add_argument( + '--stripped-output', + required=True, + help='Stripped output file', + metavar='FILE') + parser.add_argument('--dwp', help='Path to dwp binary', metavar='FILE') + parser.add_argument('input', help='Input file') + args = parser.parse_args() + + _ExtractPartition(args.objcopy, args.input, args.unstripped_output, + args.partition) + subprocess.check_call([ + args.objcopy, + '--strip-all', + args.unstripped_output, + args.stripped_output, + ]) + + if args.dwp: + dwp_args = [ + args.dwp, '-e', args.unstripped_output, '-o', + args.unstripped_output + '.dwp' + ] + # Suppress output here because it doesn't seem to be useful. The most + # common error is a segfault, which will happen if files are missing. + subprocess.check_output(dwp_args, stderr=subprocess.STDOUT) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/find_depot_tools.py b/third_party/libwebrtc/build/find_depot_tools.py new file mode 100755 index 0000000000..49a9138ec3 --- /dev/null +++ b/third_party/libwebrtc/build/find_depot_tools.py @@ -0,0 +1,75 @@ +#!/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. +"""Small utility function to find depot_tools and add it to the python path. + +Will throw an ImportError exception if depot_tools can't be found since it +imports breakpad. + +This can also be used as a standalone script to print out the depot_tools +directory location. +""" + +from __future__ import print_function + +import os +import sys + + +# Path to //src +SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) + + +def IsRealDepotTools(path): + expanded_path = os.path.expanduser(path) + return os.path.isfile(os.path.join(expanded_path, 'gclient.py')) + + +def add_depot_tools_to_path(): + """Search for depot_tools and add it to sys.path.""" + # First, check if we have a DEPS'd in "depot_tools". + deps_depot_tools = os.path.join(SRC, 'third_party', 'depot_tools') + if IsRealDepotTools(deps_depot_tools): + # Put the pinned version at the start of the sys.path, in case there + # are other non-pinned versions already on the sys.path. + sys.path.insert(0, deps_depot_tools) + return deps_depot_tools + + # Then look if depot_tools is already in PYTHONPATH. + for i in sys.path: + if i.rstrip(os.sep).endswith('depot_tools') and IsRealDepotTools(i): + return i + # Then look if depot_tools is in PATH, common case. + for i in os.environ['PATH'].split(os.pathsep): + if IsRealDepotTools(i): + sys.path.append(i.rstrip(os.sep)) + return i + # Rare case, it's not even in PATH, look upward up to root. + root_dir = os.path.dirname(os.path.abspath(__file__)) + previous_dir = os.path.abspath(__file__) + while root_dir and root_dir != previous_dir: + i = os.path.join(root_dir, 'depot_tools') + if IsRealDepotTools(i): + sys.path.append(i) + return i + previous_dir = root_dir + root_dir = os.path.dirname(root_dir) + print('Failed to find depot_tools', file=sys.stderr) + return None + +DEPOT_TOOLS_PATH = add_depot_tools_to_path() + +# pylint: disable=W0611 +import breakpad + + +def main(): + if DEPOT_TOOLS_PATH is None: + return 1 + print(DEPOT_TOOLS_PATH) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fix_gn_headers.py b/third_party/libwebrtc/build/fix_gn_headers.py new file mode 100755 index 0000000000..71fb332bfd --- /dev/null +++ b/third_party/libwebrtc/build/fix_gn_headers.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Fix header files missing in GN. + +This script takes the missing header files from check_gn_headers.py, and +try to fix them by adding them to the GN files. +Manual cleaning up is likely required afterwards. +""" + +from __future__ import print_function + +import argparse +import os +import re +import subprocess +import sys + + +def GitGrep(pattern): + p = subprocess.Popen( + ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'], + stdout=subprocess.PIPE) + out, _ = p.communicate() + return out, p.returncode + + +def ValidMatches(basename, cc, grep_lines): + """Filter out 'git grep' matches with header files already.""" + matches = [] + for line in grep_lines: + gnfile, linenr, contents = line.split(':') + linenr = int(linenr) + new = re.sub(cc, basename, contents) + lines = open(gnfile).read().splitlines() + assert contents in lines[linenr - 1] + # Skip if it's already there. It could be before or after the match. + if lines[linenr] == new: + continue + if lines[linenr - 2] == new: + continue + print(' ', gnfile, linenr, new) + matches.append((gnfile, linenr, new)) + return matches + + +def AddHeadersNextToCC(headers, skip_ambiguous=True): + """Add header files next to the corresponding .cc files in GN files. + + When skip_ambiguous is True, skip if multiple .cc files are found. + Returns unhandled headers. + + Manual cleaning up is likely required, especially if not skip_ambiguous. + """ + edits = {} + unhandled = [] + for filename in headers: + filename = filename.strip() + if not (filename.endswith('.h') or filename.endswith('.hh')): + continue + basename = os.path.basename(filename) + print(filename) + cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b' + out, returncode = GitGrep('(/|")' + cc + '"') + if returncode != 0 or not out: + unhandled.append(filename) + continue + + matches = ValidMatches(basename, cc, out.splitlines()) + + if len(matches) == 0: + continue + if len(matches) > 1: + print('\n[WARNING] Ambiguous matching for', filename) + for i in enumerate(matches, 1): + print('%d: %s' % (i[0], i[1])) + print() + if skip_ambiguous: + continue + + picked = raw_input('Pick the matches ("2,3" for multiple): ') + try: + matches = [matches[int(i) - 1] for i in picked.split(',')] + except (ValueError, IndexError): + continue + + for match in matches: + gnfile, linenr, new = match + print(' ', gnfile, linenr, new) + edits.setdefault(gnfile, {})[linenr] = new + + for gnfile in edits: + lines = open(gnfile).read().splitlines() + for l in sorted(edits[gnfile].keys(), reverse=True): + lines.insert(l, edits[gnfile][l]) + open(gnfile, 'w').write('\n'.join(lines) + '\n') + + return unhandled + + +def AddHeadersToSources(headers, skip_ambiguous=True): + """Add header files to the sources list in the first GN file. + + The target GN file is the first one up the parent directories. + This usually does the wrong thing for _test files if the test and the main + target are in the same .gn file. + When skip_ambiguous is True, skip if multiple sources arrays are found. + + "git cl format" afterwards is required. Manually cleaning up duplicated items + is likely required. + """ + for filename in headers: + filename = filename.strip() + print(filename) + dirname = os.path.dirname(filename) + while not os.path.exists(os.path.join(dirname, 'BUILD.gn')): + dirname = os.path.dirname(dirname) + rel = filename[len(dirname) + 1:] + gnfile = os.path.join(dirname, 'BUILD.gn') + + lines = open(gnfile).read().splitlines() + matched = [i for i, l in enumerate(lines) if ' sources = [' in l] + if skip_ambiguous and len(matched) > 1: + print('[WARNING] Multiple sources in', gnfile) + continue + + if len(matched) < 1: + continue + print(' ', gnfile, rel) + index = matched[0] + lines.insert(index + 1, '"%s",' % rel) + open(gnfile, 'w').write('\n'.join(lines) + '\n') + + +def RemoveHeader(headers, skip_ambiguous=True): + """Remove non-existing headers in GN files. + + When skip_ambiguous is True, skip if multiple matches are found. + """ + edits = {} + unhandled = [] + for filename in headers: + filename = filename.strip() + if not (filename.endswith('.h') or filename.endswith('.hh')): + continue + basename = os.path.basename(filename) + print(filename) + out, returncode = GitGrep('(/|")' + basename + '"') + if returncode != 0 or not out: + unhandled.append(filename) + print(' Not found') + continue + + grep_lines = out.splitlines() + matches = [] + for line in grep_lines: + gnfile, linenr, contents = line.split(':') + print(' ', gnfile, linenr, contents) + linenr = int(linenr) + lines = open(gnfile).read().splitlines() + assert contents in lines[linenr - 1] + matches.append((gnfile, linenr, contents)) + + if len(matches) == 0: + continue + if len(matches) > 1: + print('\n[WARNING] Ambiguous matching for', filename) + for i in enumerate(matches, 1): + print('%d: %s' % (i[0], i[1])) + print() + if skip_ambiguous: + continue + + picked = raw_input('Pick the matches ("2,3" for multiple): ') + try: + matches = [matches[int(i) - 1] for i in picked.split(',')] + except (ValueError, IndexError): + continue + + for match in matches: + gnfile, linenr, contents = match + print(' ', gnfile, linenr, contents) + edits.setdefault(gnfile, set()).add(linenr) + + for gnfile in edits: + lines = open(gnfile).read().splitlines() + for l in sorted(edits[gnfile], reverse=True): + lines.pop(l - 1) + open(gnfile, 'w').write('\n'.join(lines) + '\n') + + return unhandled + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('input_file', help="missing or non-existing headers, " + "output of check_gn_headers.py") + parser.add_argument('--prefix', + help="only handle path name with this prefix") + parser.add_argument('--remove', action='store_true', + help="treat input_file as non-existing headers") + + args, _extras = parser.parse_known_args() + + headers = open(args.input_file).readlines() + + if args.prefix: + headers = [i for i in headers if i.startswith(args.prefix)] + + if args.remove: + RemoveHeader(headers, False) + else: + unhandled = AddHeadersNextToCC(headers) + AddHeadersToSources(unhandled) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fuchsia/DIR_METADATA b/third_party/libwebrtc/build/fuchsia/DIR_METADATA new file mode 100644 index 0000000000..fe8198aeaa --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/DIR_METADATA @@ -0,0 +1,5 @@ +monorail { + component: "Fuchsia" +} + +team_email: "cr-fuchsia@chromium.org" diff --git a/third_party/libwebrtc/build/fuchsia/OWNERS b/third_party/libwebrtc/build/fuchsia/OWNERS new file mode 100644 index 0000000000..cd7c5cf64d --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/OWNERS @@ -0,0 +1,9 @@ +ddorwin@chromium.org +fdegans@chromium.org +kmarshall@chromium.org +qsr@chromium.org +sergeyu@chromium.org +wez@chromium.org + +per-file linux.sdk.sha1=chromium-autoroll@skia-public.iam.gserviceaccount.com +per-file mac.sdk.sha1=chromium-autoroll@skia-public.iam.gserviceaccount.com diff --git a/third_party/libwebrtc/build/fuchsia/PRESUBMIT.py b/third_party/libwebrtc/build/fuchsia/PRESUBMIT.py new file mode 100644 index 0000000000..4cdd4d021a --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/PRESUBMIT.py @@ -0,0 +1,35 @@ +# Copyright 2021 The Chromium 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 Fuchsia. + +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): + build_fuchsia_dir = input_api.PresubmitLocalPath() + + def J(*dirs): + """Returns a path relative to presubmit directory.""" + return input_api.os_path.join(build_fuchsia_dir, *dirs) + + tests = [] + tests.extend( + input_api.canned_checks.GetUnitTests( + input_api, + output_api, + unit_tests=[J('boot_data_test.py'), + J('fvdl_target_test.py')], + run_on_python2=False, + run_on_python3=True)) + return input_api.RunTests(tests) + + +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/third_party/libwebrtc/build/fuchsia/__init__.py b/third_party/libwebrtc/build/fuchsia/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/fuchsia/aemu_target.py b/third_party/libwebrtc/build/fuchsia/aemu_target.py new file mode 100644 index 0000000000..6717005815 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/aemu_target.py @@ -0,0 +1,116 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Implements commands for running and interacting with Fuchsia on AEMU.""" + +import emu_target +import os +import platform +import qemu_target +import logging + +from common import GetEmuRootForPlatform + + +def GetTargetType(): + return AemuTarget + + +class AemuTarget(qemu_target.QemuTarget): + EMULATOR_NAME = 'aemu' + + def __init__(self, out_dir, target_cpu, system_log_file, cpu_cores, + require_kvm, ram_size_mb, enable_graphics, hardware_gpu): + super(AemuTarget, self).__init__(out_dir, target_cpu, system_log_file, + cpu_cores, require_kvm, ram_size_mb) + + self._enable_graphics = enable_graphics + self._hardware_gpu = hardware_gpu + + @staticmethod + def CreateFromArgs(args): + return AemuTarget(args.out_dir, args.target_cpu, args.system_log_file, + args.cpu_cores, args.require_kvm, args.ram_size_mb, + args.enable_graphics, args.hardware_gpu) + + @staticmethod + def RegisterArgs(arg_parser): + aemu_args = arg_parser.add_argument_group('aemu', 'AEMU arguments') + aemu_args.add_argument('--enable-graphics', + action='store_true', + default=False, + help='Start AEMU with graphics instead of '\ + 'headless.') + aemu_args.add_argument('--hardware-gpu', + action='store_true', + default=False, + help='Use local GPU hardware instead of '\ + 'Swiftshader.') + + def _EnsureEmulatorExists(self, path): + assert os.path.exists(path), \ + 'This checkout is missing %s.' % (self.EMULATOR_NAME) + + def _BuildCommand(self): + aemu_folder = GetEmuRootForPlatform(self.EMULATOR_NAME) + + self._EnsureEmulatorExists(aemu_folder) + aemu_path = os.path.join(aemu_folder, 'emulator') + + # `VirtioInput` is needed for touch input device support on Fuchsia. + # `RefCountPipe` is needed for proper cleanup of resources when a process + # that uses Vulkan dies inside the guest + aemu_features = 'VirtioInput,RefCountPipe' + + # Configure the CPU to emulate. + # On Linux, we can enable lightweight virtualization (KVM) if the host and + # guest architectures are the same. + if self._IsKvmEnabled(): + aemu_features += ',KVM,GLDirectMem,Vulkan' + else: + if self._target_cpu != 'arm64': + aemu_features += ',-GLDirectMem' + + # Use Swiftshader for Vulkan if requested + gpu_target = 'swiftshader_indirect' + if self._hardware_gpu: + gpu_target = 'host' + + aemu_command = [aemu_path] + if not self._enable_graphics: + aemu_command.append('-no-window') + # All args after -fuchsia flag gets passed to QEMU + aemu_command.extend([ + '-feature', aemu_features, '-window-size', '1024x600', '-gpu', + gpu_target, '-verbose', '-fuchsia' + ]) + + aemu_command.extend(self._BuildQemuConfig()) + + aemu_command.extend([ + '-vga', 'none', + '-device', 'virtio-keyboard-pci', + '-device', 'virtio_input_multi_touch_pci_1', + '-device', 'ich9-ahci,id=ahci']) + if platform.machine() == 'x86_64': + aemu_command.extend(['-device', 'isa-debug-exit,iobase=0xf4,iosize=0x04']) + + logging.info(' '.join(aemu_command)) + return aemu_command + + def _GetVulkanIcdFile(self): + return os.path.join(GetEmuRootForPlatform(self.EMULATOR_NAME), 'lib64', + 'vulkan', 'vk_swiftshader_icd.json') + + def _SetEnv(self): + env = os.environ.copy() + aemu_logging_env = { + "ANDROID_EMU_VK_NO_CLEANUP": "1", + "ANDROID_EMUGL_LOG_PRINT": "1", + "ANDROID_EMUGL_VERBOSE": "1", + "VK_ICD_FILENAMES": self._GetVulkanIcdFile(), + "VK_LOADER_DEBUG": "info,error", + } + env.update(aemu_logging_env) + return env diff --git a/third_party/libwebrtc/build/fuchsia/binary_sizes.py b/third_party/libwebrtc/build/fuchsia/binary_sizes.py new file mode 100755 index 0000000000..52d05999d0 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/binary_sizes.py @@ -0,0 +1,556 @@ +#!/usr/bin/env python2 +# +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +'''Implements Chrome-Fuchsia package binary size checks.''' + +from __future__ import division +from __future__ import print_function + +import argparse +import collections +import copy +import json +import logging +import math +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import traceback +import uuid + +from common import GetHostToolPathFromPlatform, GetHostArchFromPlatform +from common import SDK_ROOT, DIR_SOURCE_ROOT + +# Structure representing the compressed and uncompressed sizes for a Fuchsia +# package. +PackageSizes = collections.namedtuple('PackageSizes', + ['compressed', 'uncompressed']) + +# Structure representing a Fuchsia package blob and its compressed and +# uncompressed sizes. +Blob = collections.namedtuple( + 'Blob', ['name', 'hash', 'compressed', 'uncompressed', 'is_counted']) + + +def CreateSizesExternalDiagnostic(sizes_guid): + """Creates a histogram external sizes diagnostic.""" + + benchmark_diagnostic = { + 'type': 'GenericSet', + 'guid': str(sizes_guid), + 'values': ['sizes'], + } + + return benchmark_diagnostic + + +def CreateSizesHistogramItem(name, size, sizes_guid): + """Create a performance dashboard histogram from the histogram template and + binary size data.""" + + # Chromium performance dashboard histogram containing binary size data. + histogram = { + 'name': name, + 'unit': 'sizeInBytes_smallerIsBetter', + 'diagnostics': { + 'benchmarks': str(sizes_guid), + }, + 'sampleValues': [size], + 'running': [1, size, math.log(size), size, size, size, 0], + 'description': 'chrome-fuchsia package binary sizes', + 'summaryOptions': { + 'avg': True, + 'count': False, + 'max': False, + 'min': False, + 'std': False, + 'sum': False, + }, + } + + return histogram + + +def CreateSizesHistogram(package_sizes): + """Create a performance dashboard histogram from binary size data.""" + + sizes_guid = uuid.uuid1() + histogram = [CreateSizesExternalDiagnostic(sizes_guid)] + for name, size in package_sizes.items(): + histogram.append( + CreateSizesHistogramItem('%s_%s' % (name, 'compressed'), + size.compressed, sizes_guid)) + histogram.append( + CreateSizesHistogramItem('%s_%s' % (name, 'uncompressed'), + size.uncompressed, sizes_guid)) + return histogram + + +def CreateTestResults(test_status, timestamp): + """Create test results data to write to JSON test results file. + + The JSON data format is defined in + https://chromium.googlesource.com/chromium/src/+/main/docs/testing/json_test_results_format.md + """ + + results = { + 'tests': {}, + 'interrupted': False, + 'path_delimiter': '.', + 'version': 3, + 'seconds_since_epoch': timestamp, + } + + num_failures_by_type = {result: 0 for result in ['FAIL', 'PASS', 'CRASH']} + for metric in test_status: + actual_status = test_status[metric] + num_failures_by_type[actual_status] += 1 + results['tests'][metric] = { + 'expected': 'PASS', + 'actual': actual_status, + } + results['num_failures_by_type'] = num_failures_by_type + + return results + + +def GetTestStatus(package_sizes, sizes_config, test_completed): + """Checks package sizes against size limits. + + Returns a tuple of overall test pass/fail status and a dictionary mapping size + limit checks to PASS/FAIL/CRASH status.""" + + if not test_completed: + test_status = {'binary_sizes': 'CRASH'} + else: + test_status = {} + for metric, limit in sizes_config['size_limits'].items(): + # Strip the "_compressed" suffix from |metric| if it exists. + match = re.match(r'(?P\w+)_compressed', metric) + package_name = match.group('name') if match else metric + if package_name not in package_sizes: + raise Exception('package "%s" not in sizes "%s"' % + (package_name, str(package_sizes))) + if package_sizes[package_name].compressed <= limit: + test_status[metric] = 'PASS' + else: + test_status[metric] = 'FAIL' + + all_tests_passed = all(status == 'PASS' for status in test_status.values()) + + return all_tests_passed, test_status + + +def WriteSimpleTestResults(results_path, test_completed): + """Writes simplified test results file. + + Used when test status is not available. + """ + + simple_isolated_script_output = { + 'valid': test_completed, + 'failures': [], + 'version': 'simplified', + } + with open(results_path, 'w') as output_file: + json.dump(simple_isolated_script_output, output_file) + + +def WriteTestResults(results_path, test_completed, test_status, timestamp): + """Writes test results file containing test PASS/FAIL/CRASH statuses.""" + + if test_status: + test_results = CreateTestResults(test_status, timestamp) + with open(results_path, 'w') as results_file: + json.dump(test_results, results_file) + else: + WriteSimpleTestResults(results_path, test_completed) + + +def WriteGerritPluginSizeData(output_path, package_sizes): + """Writes a package size dictionary in json format for the Gerrit binary + sizes plugin.""" + + with open(output_path, 'w') as sizes_file: + sizes_data = {name: size.compressed for name, size in package_sizes.items()} + json.dump(sizes_data, sizes_file) + + +def WritePackageBlobsJson(json_path, package_blobs): + """Writes package blob information in human-readable JSON format. + + The json data is an array of objects containing these keys: + 'path': string giving blob location in the local file system + 'merkle': the blob's Merkle hash + 'bytes': the number of uncompressed bytes in the blod + 'size': the size of the compressed blob in bytes. A multiple of the blobfs + block size (8192) + 'is_counted: true if the blob counts towards the package budget, or false + if not (for ICU blobs or blobs distributed in the SDK)""" + + formatted_blob_stats_per_package = {} + for package in package_blobs: + blob_data = [] + for blob_name in package_blobs[package]: + blob = package_blobs[package][blob_name] + blob_data.append({ + 'path': blob.name, + 'merkle': blob.hash, + 'bytes': blob.uncompressed, + 'size': blob.compressed, + 'is_counted': blob.is_counted + }) + formatted_blob_stats_per_package[package] = blob_data + + with (open(json_path, 'w')) as json_file: + json.dump(formatted_blob_stats_per_package, json_file, indent=2) + + +def GetCompressedSize(file_path): + """Measures file size after blobfs compression.""" + + compressor_path = GetHostToolPathFromPlatform('blobfs-compression') + try: + temp_dir = tempfile.mkdtemp() + compressed_file_path = os.path.join(temp_dir, os.path.basename(file_path)) + compressor_cmd = [ + compressor_path, + '--source_file=%s' % file_path, + '--compressed_file=%s' % compressed_file_path + ] + proc = subprocess.Popen(compressor_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + proc.wait() + compressor_output = proc.stdout.read() + if proc.returncode != 0: + print(compressor_output, file=sys.stderr) + raise Exception('Error while running %s' % compressor_path) + finally: + shutil.rmtree(temp_dir) + + # Match a compressed bytes total from blobfs-compression output like + # Wrote 360830 bytes (40% compression) + blobfs_compressed_bytes_re = r'Wrote\s+(?P\d+)\s+bytes' + + match = re.search(blobfs_compressed_bytes_re, compressor_output) + if not match: + print(compressor_output, file=sys.stderr) + raise Exception('Could not get compressed bytes for %s' % file_path) + + # Round the compressed file size up to an integer number of blobfs blocks. + BLOBFS_BLOCK_SIZE = 8192 # Fuchsia's blobfs file system uses 8KiB blocks. + blob_bytes = int(match.group('bytes')) + return int(math.ceil(blob_bytes / BLOBFS_BLOCK_SIZE)) * BLOBFS_BLOCK_SIZE + + +def ExtractFarFile(file_path, extract_dir): + """Extracts contents of a Fuchsia archive file to the specified directory.""" + + far_tool = GetHostToolPathFromPlatform('far') + + if not os.path.isfile(far_tool): + raise Exception('Could not find FAR host tool "%s".' % far_tool) + if not os.path.isfile(file_path): + raise Exception('Could not find FAR file "%s".' % file_path) + + subprocess.check_call([ + far_tool, 'extract', + '--archive=%s' % file_path, + '--output=%s' % extract_dir + ]) + + +def GetBlobNameHashes(meta_dir): + """Returns mapping from Fuchsia pkgfs paths to blob hashes. The mapping is + read from the extracted meta.far archive contained in an extracted package + archive.""" + + blob_name_hashes = {} + contents_path = os.path.join(meta_dir, 'meta', 'contents') + with open(contents_path) as lines: + for line in lines: + (pkgfs_path, blob_hash) = line.strip().split('=') + blob_name_hashes[pkgfs_path] = blob_hash + return blob_name_hashes + + +# Compiled regular expression matching strings like *.so, *.so.1, *.so.2, ... +SO_FILENAME_REGEXP = re.compile(r'\.so(\.\d+)?$') + + +def GetSdkModules(): + """Finds shared objects (.so) under the Fuchsia SDK arch directory in dist or + lib subdirectories. + + Returns a set of shared objects' filenames. + """ + + # Fuchsia SDK arch directory path (contains all shared object files). + sdk_arch_dir = os.path.join(SDK_ROOT, 'arch') + # Leaf subdirectories containing shared object files. + sdk_so_leaf_dirs = ['dist', 'lib'] + # Match a shared object file name. + sdk_so_filename_re = r'\.so(\.\d+)?$' + + lib_names = set() + for dirpath, _, file_names in os.walk(sdk_arch_dir): + if os.path.basename(dirpath) in sdk_so_leaf_dirs: + for name in file_names: + if SO_FILENAME_REGEXP.search(name): + lib_names.add(name) + return lib_names + + +def FarBaseName(name): + _, name = os.path.split(name) + name = re.sub(r'\.far$', '', name) + return name + + +def GetPackageMerkleRoot(far_file_path): + """Returns a package's Merkle digest.""" + + # The digest is the first word on the first line of the merkle tool's output. + merkle_tool = GetHostToolPathFromPlatform('merkleroot') + output = subprocess.check_output([merkle_tool, far_file_path]) + return output.splitlines()[0].split()[0] + + +def GetBlobs(far_file, build_out_dir): + """Calculates compressed and uncompressed blob sizes for specified FAR file. + Marks ICU blobs and blobs from SDK libraries as not counted.""" + + base_name = FarBaseName(far_file) + + extract_dir = tempfile.mkdtemp() + + # Extract files and blobs from the specified Fuchsia archive. + far_file_path = os.path.join(build_out_dir, far_file) + far_extract_dir = os.path.join(extract_dir, base_name) + ExtractFarFile(far_file_path, far_extract_dir) + + # Extract the meta.far archive contained in the specified Fuchsia archive. + meta_far_file_path = os.path.join(far_extract_dir, 'meta.far') + meta_far_extract_dir = os.path.join(extract_dir, '%s_meta' % base_name) + ExtractFarFile(meta_far_file_path, meta_far_extract_dir) + + # Map Linux filesystem blob names to blob hashes. + blob_name_hashes = GetBlobNameHashes(meta_far_extract_dir) + + # "System" files whose sizes are not charged against component size budgets. + # Fuchsia SDK modules and the ICU icudtl.dat file sizes are not counted. + system_files = GetSdkModules() | set(['icudtl.dat']) + + # Add the meta.far file blob. + blobs = {} + meta_name = 'meta.far' + meta_hash = GetPackageMerkleRoot(meta_far_file_path) + compressed = GetCompressedSize(meta_far_file_path) + uncompressed = os.path.getsize(meta_far_file_path) + blobs[meta_name] = Blob(meta_name, meta_hash, compressed, uncompressed, True) + + # Add package blobs. + for blob_name, blob_hash in blob_name_hashes.items(): + extracted_blob_path = os.path.join(far_extract_dir, blob_hash) + compressed = GetCompressedSize(extracted_blob_path) + uncompressed = os.path.getsize(extracted_blob_path) + is_counted = os.path.basename(blob_name) not in system_files + blobs[blob_name] = Blob(blob_name, blob_hash, compressed, uncompressed, + is_counted) + + shutil.rmtree(extract_dir) + + return blobs + + +def GetPackageBlobs(far_files, build_out_dir): + """Returns dictionary mapping package names to blobs contained in the package. + + Prints package blob size statistics.""" + + package_blobs = {} + for far_file in far_files: + package_name = FarBaseName(far_file) + if package_name in package_blobs: + raise Exception('Duplicate FAR file base name "%s".' % package_name) + package_blobs[package_name] = GetBlobs(far_file, build_out_dir) + + # Print package blob sizes (does not count sharing). + for package_name in sorted(package_blobs.keys()): + print('Package blob sizes: %s' % package_name) + print('%-64s %12s %12s %s' % + ('blob hash', 'compressed', 'uncompressed', 'path')) + print('%s %s %s %s' % (64 * '-', 12 * '-', 12 * '-', 20 * '-')) + for blob_name in sorted(package_blobs[package_name].keys()): + blob = package_blobs[package_name][blob_name] + if blob.is_counted: + print('%64s %12d %12d %s' % + (blob.hash, blob.compressed, blob.uncompressed, blob.name)) + + return package_blobs + + +def GetPackageSizes(package_blobs): + """Calculates compressed and uncompressed package sizes from blob sizes.""" + + # TODO(crbug.com/1126177): Use partial sizes for blobs shared by + # non Chrome-Fuchsia packages. + + # Count number of packages sharing blobs (a count of 1 is not shared). + blob_counts = collections.defaultdict(int) + for package_name in package_blobs: + for blob_name in package_blobs[package_name]: + blob_counts[blob_name] += 1 + + # Package sizes are the sum of blob sizes divided by their share counts. + package_sizes = {} + for package_name in package_blobs: + compressed_total = 0 + uncompressed_total = 0 + for blob_name in package_blobs[package_name]: + blob = package_blobs[package_name][blob_name] + if blob.is_counted: + count = blob_counts[blob_name] + compressed_total += blob.compressed // count + uncompressed_total += blob.uncompressed // count + package_sizes[package_name] = PackageSizes(compressed_total, + uncompressed_total) + + return package_sizes + + +def GetBinarySizesAndBlobs(args, sizes_config): + """Get binary size data and contained blobs for packages specified in args. + + If "total_size_name" is set, then computes a synthetic package size which is + the aggregated sizes across all packages.""" + + # Calculate compressed and uncompressed package sizes. + package_blobs = GetPackageBlobs(sizes_config['far_files'], args.build_out_dir) + package_sizes = GetPackageSizes(package_blobs) + + # Optionally calculate total compressed and uncompressed package sizes. + if 'far_total_name' in sizes_config: + compressed = sum([a.compressed for a in package_sizes.values()]) + uncompressed = sum([a.uncompressed for a in package_sizes.values()]) + package_sizes[sizes_config['far_total_name']] = PackageSizes( + compressed, uncompressed) + + for name, size in package_sizes.items(): + print('%s: compressed size %d, uncompressed size %d' % + (name, size.compressed, size.uncompressed)) + + return package_sizes, package_blobs + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--build-out-dir', + '--output-directory', + type=os.path.realpath, + required=True, + help='Location of the build artifacts.', + ) + parser.add_argument( + '--isolated-script-test-output', + type=os.path.realpath, + help='File to which simplified JSON results will be written.') + parser.add_argument( + '--size-plugin-json-path', + help='Optional path for json size data for the Gerrit binary size plugin', + ) + parser.add_argument( + '--sizes-path', + default=os.path.join('fuchsia', 'release', 'size_tests', + 'fyi_sizes.json'), + help='path to package size limits json file. The path is relative to ' + 'the workspace src directory') + parser.add_argument('--verbose', + '-v', + action='store_true', + help='Enable verbose output') + # Accepted to conform to the isolated script interface, but ignored. + parser.add_argument('--isolated-script-test-filter', help=argparse.SUPPRESS) + parser.add_argument('--isolated-script-test-perf-output', + help=argparse.SUPPRESS) + args = parser.parse_args() + + if args.verbose: + print('Fuchsia binary sizes') + print('Working directory', os.getcwd()) + print('Args:') + for var in vars(args): + print(' {}: {}'.format(var, getattr(args, var) or '')) + + if not os.path.isdir(args.build_out_dir): + raise Exception('Could not find build output directory "%s".' % + args.build_out_dir) + + with open(os.path.join(DIR_SOURCE_ROOT, args.sizes_path)) as sizes_file: + sizes_config = json.load(sizes_file) + + if args.verbose: + print('Sizes Config:') + print(json.dumps(sizes_config)) + + for far_rel_path in sizes_config['far_files']: + far_abs_path = os.path.join(args.build_out_dir, far_rel_path) + if not os.path.isfile(far_abs_path): + raise Exception('Could not find FAR file "%s".' % far_abs_path) + + test_name = 'sizes' + timestamp = time.time() + test_completed = False + all_tests_passed = False + test_status = {} + package_sizes = {} + package_blobs = {} + sizes_histogram = [] + + results_directory = None + if args.isolated_script_test_output: + results_directory = os.path.join( + os.path.dirname(args.isolated_script_test_output), test_name) + if not os.path.exists(results_directory): + os.makedirs(results_directory) + + try: + package_sizes, package_blobs = GetBinarySizesAndBlobs(args, sizes_config) + sizes_histogram = CreateSizesHistogram(package_sizes) + test_completed = True + except: + _, value, trace = sys.exc_info() + traceback.print_tb(trace) + print(str(value)) + finally: + all_tests_passed, test_status = GetTestStatus(package_sizes, sizes_config, + test_completed) + + if results_directory: + WriteTestResults(os.path.join(results_directory, 'test_results.json'), + test_completed, test_status, timestamp) + with open(os.path.join(results_directory, 'perf_results.json'), 'w') as f: + json.dump(sizes_histogram, f) + WritePackageBlobsJson( + os.path.join(results_directory, 'package_blobs.json'), package_blobs) + + if args.isolated_script_test_output: + WriteTestResults(args.isolated_script_test_output, test_completed, + test_status, timestamp) + + if args.size_plugin_json_path: + WriteGerritPluginSizeData(args.size_plugin_json_path, package_sizes) + + return 0 if all_tests_passed else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fuchsia/binary_sizes_test.py b/third_party/libwebrtc/build/fuchsia/binary_sizes_test.py new file mode 100755 index 0000000000..962e4c9123 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/binary_sizes_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium 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 copy +import math +import os +import shutil +import subprocess +import tempfile +import time +import unittest + +import binary_sizes + +from common import DIR_SOURCE_ROOT + + +class TestBinarySizes(unittest.TestCase): + tmpdir = None + + @classmethod + def setUpClass(cls): + cls.tmpdir = tempfile.mkdtemp() + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls.tmpdir) + + # TODO(crbug.com/1145648): Add tests covering FAR file input and histogram + # output. + + def testCommitFromBuildProperty(self): + commit_position = binary_sizes.CommitPositionFromBuildProperty( + 'refs/heads/master@{#819458}') + self.assertEqual(commit_position, 819458) + + def testCompressedSize(self): + """Verifies that the compressed file size can be extracted from the + blobfs-compression output.""" + + uncompressed_file = tempfile.NamedTemporaryFile(delete=False) + for line in range(200): + uncompressed_file.write( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + 'Sed eleifend') + uncompressed_file.close() + compressed_path = uncompressed_file.name + '.compressed' + compressor_path = os.path.join(DIR_SOURCE_ROOT, 'third_party', + 'fuchsia-sdk', 'sdk', 'tools', 'x64', + 'blobfs-compression') + subprocess.call([compressor_path, uncompressed_file.name, compressed_path]) + self.assertEqual(binary_sizes.CompressedSize(uncompressed_file.name), + os.path.getsize(compressed_path)) + os.remove(uncompressed_file.name) + os.remove(compressed_path) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/fuchsia/boot_data.py b/third_party/libwebrtc/build/fuchsia/boot_data.py new file mode 100644 index 0000000000..f59d1974c8 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/boot_data.py @@ -0,0 +1,120 @@ +# Copyright 2018 The Chromium 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 used to provision Fuchsia boot images.""" + +import common +import logging +import os +import subprocess +import tempfile +import time +import uuid + +_SSH_CONFIG_TEMPLATE = """ +Host * + CheckHostIP no + StrictHostKeyChecking no + ForwardAgent no + ForwardX11 no + User fuchsia + IdentitiesOnly yes + IdentityFile {identity} + ServerAliveInterval 2 + ServerAliveCountMax 5 + ControlMaster auto + ControlPersist 1m + ControlPath /tmp/ssh-%r@%h:%p + ConnectTimeout 5 + """ + +# Specifies boot files intended for use by an emulator. +TARGET_TYPE_QEMU = 'qemu' + +# Specifies boot files intended for use by anything (incl. physical devices). +TARGET_TYPE_GENERIC = 'generic' + +# Defaults used by Fuchsia SDK +_SSH_DIR = os.path.expanduser('~/.ssh') +_SSH_CONFIG_DIR = os.path.expanduser('~/.fuchsia') + + +def _GetPubKeyPath(): + """Returns a path to the generated SSH public key.""" + + return os.path.join(_SSH_DIR, 'fuchsia_ed25519.pub') + + +def ProvisionSSH(): + """Generates a key pair and config file for SSH.""" + + fuchsia_authorized_keys_path = os.path.join(_SSH_DIR, + 'fuchsia_authorized_keys') + id_key_path = os.path.join(_SSH_DIR, 'fuchsia_ed25519') + _GetPubKeyPath() + + logging.debug('Generating SSH credentials.') + + if not os.path.isfile(id_key_path): + subprocess.check_output([ + 'ssh-keygen', '-P', '', '-t', 'ed25519', '-f', id_key_path, '-C', + 'generated by FEMU Start testing step' + ]) + + if not os.path.isfile(fuchsia_authorized_keys_path): + result = subprocess.check_output(['ssh-keygen', '-y', '-f', id_key_path]) + with open(fuchsia_authorized_keys_path, 'w') as out: + out.write(result.decode('utf-8')) + + if not os.path.exists(_SSH_CONFIG_DIR): + os.mkdir(_SSH_CONFIG_DIR) + elif not os.path.isdir(_SSH_CONFIG_DIR): + raise Exception(_SSH_CONFIG_DIR + ' is not a directory.') + ssh_config_path = os.path.join(_SSH_CONFIG_DIR, 'ssh_config') + with open(ssh_config_path, "w") as ssh_config: + ssh_config.write( + _SSH_CONFIG_TEMPLATE.format(identity=id_key_path)) + + +def GetTargetFile(filename, target_arch, target_type): + """Computes a path to |filename| in the Fuchsia boot image directory specific + to |target_type| and |target_arch|.""" + + assert target_type == TARGET_TYPE_QEMU or target_type == TARGET_TYPE_GENERIC + + return os.path.join(common.IMAGES_ROOT, target_arch, target_type, filename) + + +def GetSSHConfigPath(): + return os.path.join(_SSH_CONFIG_DIR, 'ssh_config') + + +def GetBootImage(output_dir, target_arch, target_type): + """"Gets a path to the Zircon boot image, with the SSH client public key + added.""" + ProvisionSSH() + pubkey_path = _GetPubKeyPath() + zbi_tool = common.GetHostToolPathFromPlatform('zbi') + image_source_path = GetTargetFile('zircon-a.zbi', target_arch, target_type) + image_dest_path = os.path.join(output_dir, 'gen', 'fuchsia-with-keys.zbi') + + cmd = [ zbi_tool, '-o', image_dest_path, image_source_path, + '-e', 'data/ssh/authorized_keys=' + pubkey_path ] + subprocess.check_call(cmd) + + return image_dest_path + + +def GetKernelArgs(output_dir): + return ['devmgr.epoch=%d' % time.time()] + + +def AssertBootImagesExist(arch, platform): + assert os.path.exists(GetTargetFile('zircon-a.zbi', arch, platform)), \ + 'This checkout is missing the files necessary for\n' \ + 'booting this configuration of Fuchsia.\n' \ + 'To check out the files, add this entry to the "custom_vars"\n' \ + 'section of your .gclient file:\n\n' \ + ' "checkout_fuchsia_boot_images": "%s.%s"\n\n' % \ + (platform, arch) diff --git a/third_party/libwebrtc/build/fuchsia/boot_data_test.py b/third_party/libwebrtc/build/fuchsia/boot_data_test.py new file mode 100755 index 0000000000..aa652eb198 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/boot_data_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 boot_data +import os +import unittest +from boot_data import _SSH_CONFIG_DIR, _SSH_DIR + + +class TestBootData(unittest.TestCase): + def testProvisionSSHGeneratesFiles(self): + fuchsia_authorized_keys_path = os.path.join(_SSH_DIR, + 'fuchsia_authorized_keys') + fuchsia_id_key_path = os.path.join(_SSH_DIR, 'fuchsia_ed25519') + pub_keys_path = os.path.join(_SSH_DIR, 'fuchsia_ed25519.pub') + ssh_config_path = os.path.join(_SSH_CONFIG_DIR, 'ssh_config') + # Check if the keys exists before generating. If they do, delete them + # afterwards before asserting if ProvisionSSH works. + authorized_key_before = os.path.exists(fuchsia_authorized_keys_path) + id_keys_before = os.path.exists(fuchsia_id_key_path) + pub_keys_before = os.path.exists(pub_keys_path) + ssh_config_before = os.path.exists(ssh_config_path) + ssh_dir_before = os.path.exists(_SSH_CONFIG_DIR) + boot_data.ProvisionSSH() + authorized_key_after = os.path.exists(fuchsia_authorized_keys_path) + id_keys_after = os.path.exists(fuchsia_id_key_path) + ssh_config_after = os.path.exists(ssh_config_path) + if not authorized_key_before: + os.remove(fuchsia_authorized_keys_path) + if not id_keys_before: + os.remove(fuchsia_id_key_path) + if not pub_keys_before: + os.remove(pub_keys_path) + if not ssh_config_before: + os.remove(ssh_config_path) + if not ssh_dir_before: + os.rmdir(_SSH_CONFIG_DIR) + self.assertTrue(os.path.exists(authorized_key_after)) + self.assertTrue(os.path.exists(id_keys_after)) + self.assertTrue(os.path.exists(ssh_config_after)) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/fuchsia/common.py b/third_party/libwebrtc/build/fuchsia/common.py new file mode 100644 index 0000000000..99ced81ee9 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/common.py @@ -0,0 +1,140 @@ +# Copyright 2018 The Chromium 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 platform +import signal +import socket +import subprocess +import sys +import time +import threading + +DIR_SOURCE_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +IMAGES_ROOT = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'fuchsia-sdk', 'images') +SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'fuchsia-sdk', 'sdk') + +def EnsurePathExists(path): + """Checks that the file |path| exists on the filesystem and returns the path + if it does, raising an exception otherwise.""" + + if not os.path.exists(path): + raise IOError('Missing file: ' + path) + + return path + +def GetHostOsFromPlatform(): + host_platform = sys.platform + if host_platform.startswith('linux'): + return 'linux' + elif host_platform.startswith('darwin'): + return 'mac' + raise Exception('Unsupported host platform: %s' % host_platform) + +def GetHostArchFromPlatform(): + host_arch = platform.machine() + if host_arch == 'x86_64': + return 'x64' + elif host_arch == 'aarch64': + return 'arm64' + raise Exception('Unsupported host architecture: %s' % host_arch) + +def GetHostToolPathFromPlatform(tool): + host_arch = platform.machine() + return os.path.join(SDK_ROOT, 'tools', GetHostArchFromPlatform(), tool) + + +def GetEmuRootForPlatform(emulator): + return os.path.join( + DIR_SOURCE_ROOT, 'third_party', '{0}-{1}-{2}'.format( + emulator, GetHostOsFromPlatform(), GetHostArchFromPlatform())) + + +def ConnectPortForwardingTask(target, local_port, remote_port = 0): + """Establishes a port forwarding SSH task to a localhost TCP endpoint hosted + at port |local_port|. Blocks until port forwarding is established. + + Returns the remote port number.""" + + forwarding_flags = ['-O', 'forward', # Send SSH mux control signal. + '-R', '%d:localhost:%d' % (remote_port, local_port), + '-v', # Get forwarded port info from stderr. + '-NT'] # Don't execute command; don't allocate terminal. + + if remote_port != 0: + # Forward to a known remote port. + task = target.RunCommand([], ssh_args=forwarding_flags) + if task.returncode != 0: + raise Exception('Could not establish a port forwarding connection.') + return + + task = target.RunCommandPiped([], + ssh_args=forwarding_flags, + stdout=subprocess.PIPE, + stderr=open('/dev/null')) + output = task.stdout.readlines() + task.wait() + if task.returncode != 0: + raise Exception('Got an error code when requesting port forwarding: %d' % + task.returncode) + + parsed_port = int(output[0].strip()) + logging.debug('Port forwarding established (local=%d, device=%d)' % + (local_port, parsed_port)) + return parsed_port + + +def GetAvailableTcpPort(): + """Finds a (probably) open port by opening and closing a listen socket.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(("", 0)) + port = sock.getsockname()[1] + sock.close() + return port + + +def SubprocessCallWithTimeout(command, silent=False, timeout_secs=None): + """Helper function for running a command. + + Args: + command: The command to run. + silent: If true, stdout and stderr of the command will not be printed. + timeout_secs: Maximum amount of time allowed for the command to finish. + + Returns: + A tuple of (return code, stdout, stderr) of the command. Raises + an exception if the subprocess times out. + """ + + if silent: + devnull = open(os.devnull, 'w') + process = subprocess.Popen(command, stdout=devnull, stderr=devnull) + else: + process = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + timeout_timer = None + if timeout_secs: + + def interrupt_process(): + process.send_signal(signal.SIGKILL) + + timeout_timer = threading.Timer(timeout_secs, interrupt_process) + + # Ensure that keyboard interrupts are handled properly (crbug/1198113). + timeout_timer.daemon = True + + timeout_timer.start() + + out, err = process.communicate() + if timeout_timer: + timeout_timer.cancel() + + if process.returncode == -9: + raise Exception('Timeout when executing \"%s\".' % ' '.join(command)) + + return process.returncode, out, err diff --git a/third_party/libwebrtc/build/fuchsia/common_args.py b/third_party/libwebrtc/build/fuchsia/common_args.py new file mode 100644 index 0000000000..691fad67e6 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/common_args.py @@ -0,0 +1,173 @@ +# Copyright 2018 The Chromium 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 importlib +import logging +import os +import sys + +from common import GetHostArchFromPlatform + +BUILTIN_TARGET_NAMES = ['aemu', 'qemu', 'device', 'fvdl'] + + +def _AddTargetSpecificationArgs(arg_parser): + """Returns a parser that handles the target type used for the test run.""" + + device_args = arg_parser.add_argument_group( + 'target', + 'Arguments specifying the Fuchsia target type. To see a list of ' + 'arguments available for a specific target type, specify the desired ' + 'target to use and add the --help flag.') + device_args.add_argument('--target-cpu', + default=GetHostArchFromPlatform(), + help='GN target_cpu setting for the build. Defaults ' + 'to the same architecture as host cpu.') + device_args.add_argument('--device', + default=None, + choices=BUILTIN_TARGET_NAMES + ['custom'], + help='Choose to run on aemu|qemu|device. ' + 'By default, Fuchsia will run on AEMU on x64 ' + 'hosts and QEMU on arm64 hosts. Alternatively, ' + 'setting to custom will require specifying the ' + 'subclass of Target class used via the ' + '--custom-device-target flag.') + device_args.add_argument('-d', + action='store_const', + dest='device', + const='device', + help='Run on device instead of emulator.') + device_args.add_argument('--custom-device-target', + default=None, + help='Specify path to file that contains the ' + 'subclass of Target that will be used. Only ' + 'needed if device specific operations such as ' + 'paving is required.') + + +def _GetPathToBuiltinTarget(target_name): + return '%s_target' % target_name + + +def _LoadTargetClass(target_path): + try: + loaded_target = importlib.import_module(target_path) + except ImportError: + logging.error( + 'Cannot import from %s. Make sure that --custom-device-target ' + 'is pointing to a file containing a target ' + 'module.' % target_path) + raise + return loaded_target.GetTargetType() + + +def AddCommonArgs(arg_parser): + """Adds command line arguments to |arg_parser| for options which are shared + across test and executable target types. + + Args: + arg_parser: an ArgumentParser object.""" + + common_args = arg_parser.add_argument_group('common', 'Common arguments') + common_args.add_argument('--runner-logs-dir', + help='Directory to write test runner logs to.') + common_args.add_argument('--exclude-system-logs', + action='store_false', + dest='include_system_logs', + help='Do not show system log data.') + common_args.add_argument('--verbose', + '-v', + default=False, + action='store_true', + help='Enable debug-level logging.') + common_args.add_argument( + '--out-dir', + type=os.path.realpath, + help=('Path to the directory in which build files are located. ' + 'Defaults to current directory.')) + common_args.add_argument('--system-log-file', + help='File to write system logs to. Specify ' + '\'-\' to log to stdout.') + common_args.add_argument('--fuchsia-out-dir', + help='Path to a Fuchsia build output directory. ' + 'Setting the GN arg ' + '"default_fuchsia_build_dir_for_installation" ' + 'will cause it to be passed here.') + + package_args = arg_parser.add_argument_group('package', 'Fuchsia Packages') + package_args.add_argument( + '--package', + action='append', + help='Paths of the packages to install, including ' + 'all dependencies.') + package_args.add_argument( + '--package-name', + help='Name of the package to execute, defined in ' + 'package metadata.') + + emu_args = arg_parser.add_argument_group('emu', 'General emulator arguments') + emu_args.add_argument('--cpu-cores', + type=int, + default=4, + help='Sets the number of CPU cores to provide.') + emu_args.add_argument('--ram-size-mb', + type=int, + default=8192, + help='Sets the emulated RAM size (MB).'), + emu_args.add_argument('--allow-no-kvm', + action='store_false', + dest='require_kvm', + default=True, + help='Do not require KVM acceleration for ' + 'emulators.') + + +# Register the arguments for all known target types and the optional custom +# target type (specified on the commandline). +def AddTargetSpecificArgs(arg_parser): + # Parse the minimal set of arguments to determine if custom targets need to + # be loaded so that their arguments can be registered. + target_spec_parser = argparse.ArgumentParser(add_help=False) + _AddTargetSpecificationArgs(target_spec_parser) + target_spec_args, _ = target_spec_parser.parse_known_args() + _AddTargetSpecificationArgs(arg_parser) + + for target in BUILTIN_TARGET_NAMES: + _LoadTargetClass(_GetPathToBuiltinTarget(target)).RegisterArgs(arg_parser) + if target_spec_args.custom_device_target: + _LoadTargetClass( + target_spec_args.custom_device_target).RegisterArgs(arg_parser) + + +def ConfigureLogging(args): + """Configures the logging level based on command line |args|.""" + + logging.basicConfig(level=(logging.DEBUG if args.verbose else logging.INFO), + format='%(asctime)s:%(levelname)s:%(name)s:%(message)s') + + # The test server spawner is too noisy with INFO level logging, so tweak + # its verbosity a bit by adjusting its logging level. + logging.getLogger('chrome_test_server_spawner').setLevel( + logging.DEBUG if args.verbose else logging.WARN) + + # Verbose SCP output can be useful at times but oftentimes is just too noisy. + # Only enable it if -vv is passed. + logging.getLogger('ssh').setLevel( + logging.DEBUG if args.verbose else logging.WARN) + + +def GetDeploymentTargetForArgs(args): + """Constructs a deployment target object using command line arguments. + If needed, an additional_args dict can be used to supplement the + command line arguments.""" + + if args.device == 'custom': + return _LoadTargetClass(args.custom_device_target).CreateFromArgs(args) + + if args.device: + device = args.device + else: + device = 'aemu' if args.target_cpu == 'x64' else 'qemu' + + return _LoadTargetClass(_GetPathToBuiltinTarget(device)).CreateFromArgs(args) diff --git a/third_party/libwebrtc/build/fuchsia/deploy_to_pkg_repo.py b/third_party/libwebrtc/build/fuchsia/deploy_to_pkg_repo.py new file mode 100755 index 0000000000..57635ee051 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/deploy_to_pkg_repo.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Deploys Fuchsia packages to a package repository in a Fuchsia +build output directory.""" + +import pkg_repo +import argparse +import os +import sys + + +# Populates the GDB-standard symbol directory structure |build_ids_path| with +# the files and build IDs specified in |ids_txt_path|. +def InstallSymbols(ids_txt_path, build_ids_path): + for entry in open(ids_txt_path, 'r'): + build_id, binary_relpath = entry.strip().split(' ') + binary_abspath = os.path.abspath( + os.path.join(os.path.dirname(ids_txt_path), binary_relpath)) + symbol_dir = os.path.join(build_ids_path, build_id[:2]) + symbol_file = os.path.join(symbol_dir, build_id[2:] + '.debug') + + if not os.path.exists(symbol_dir): + os.makedirs(symbol_dir) + + if os.path.islink(symbol_file) or os.path.exists(symbol_file): + # Clobber the existing entry to ensure that the symlink's target is + # up to date. + os.unlink(symbol_file) + + os.symlink(os.path.relpath(binary_abspath, symbol_dir), symbol_file) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--package', + action='append', + required=True, + help='Paths to packages to install.') + parser.add_argument('--fuchsia-out-dir', + required=True, + help='Path to a Fuchsia build output directory. ' + 'Setting the GN arg ' + '"default_fuchsia_build_dir_for_installation" ' + 'will cause it to be passed here.') + args = parser.parse_args() + assert args.package + + fuchsia_out_dir = os.path.expanduser(args.fuchsia_out_dir) + repo = pkg_repo.ExternalPkgRepo(os.path.join(fuchsia_out_dir, 'amber-files')) + print('Installing packages and symbols in package repo %s...' % + repo.GetPath()) + + for package in args.package: + repo.PublishPackage(package) + InstallSymbols(os.path.join(os.path.dirname(package), 'ids.txt'), + os.path.join(fuchsia_out_dir, '.build-id')) + + print('Installation success.') + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fuchsia/device_target.py b/third_party/libwebrtc/build/fuchsia/device_target.py new file mode 100644 index 0000000000..6905a623dd --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/device_target.py @@ -0,0 +1,266 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Implements commands for running and interacting with Fuchsia on devices.""" + +import boot_data +import logging +import os +import pkg_repo +import re +import subprocess +import target +import time + +from common import EnsurePathExists, GetHostToolPathFromPlatform + +# The maximum times to attempt mDNS resolution when connecting to a freshly +# booted Fuchsia instance before aborting. +BOOT_DISCOVERY_ATTEMPTS = 30 + +# Number of failed connection attempts before redirecting system logs to stdout. +CONNECT_RETRY_COUNT_BEFORE_LOGGING = 10 + +# Number of seconds between each device discovery. +BOOT_DISCOVERY_DELAY_SECS = 4 + +# Time between a reboot command is issued and when connection attempts from the +# host begin. +_REBOOT_SLEEP_PERIOD = 20 + + +def GetTargetType(): + return DeviceTarget + + +class DeviceTarget(target.Target): + """Prepares a device to be used as a deployment target. Depending on the + command line parameters, it automatically handling a number of preparatory + steps relating to address resolution. + + If |_node_name| is unset: + If there is one running device, use it for deployment and execution. + + If there are more than one running devices, then abort and instruct the + user to re-run the command with |_node_name| + + If |_node_name| is set: + If there is a running device with a matching nodename, then it is used + for deployment and execution. + + If |_host| is set: + Deploy to a device at the host IP address as-is.""" + + def __init__(self, + out_dir, + target_cpu, + host=None, + node_name=None, + port=None, + ssh_config=None, + fuchsia_out_dir=None, + os_check='update', + system_log_file=None): + """out_dir: The directory which will contain the files that are + generated to support the deployment. + target_cpu: The CPU architecture of the deployment target. Can be + "x64" or "arm64". + host: The address of the deployment target device. + node_name: The node name of the deployment target device. + port: The port of the SSH service on the deployment target device. + ssh_config: The path to SSH configuration data. + fuchsia_out_dir: The path to a Fuchsia build output directory, for + deployments to devices paved with local Fuchsia builds. + os_check: If 'check', the target's SDK version must match. + If 'update', the target will be repaved if the SDK versions + mismatch. + If 'ignore', the target's SDK version is ignored.""" + + super(DeviceTarget, self).__init__(out_dir, target_cpu) + + self._system_log_file = system_log_file + self._host = host + self._port = port + self._fuchsia_out_dir = None + self._node_name = node_name + self._os_check = os_check + self._pkg_repo = None + + if self._host and self._node_name: + raise Exception('Only one of "--host" or "--name" can be specified.') + + if fuchsia_out_dir: + if ssh_config: + raise Exception('Only one of "--fuchsia-out-dir" or "--ssh_config" can ' + 'be specified.') + + self._fuchsia_out_dir = os.path.expanduser(fuchsia_out_dir) + # Use SSH keys from the Fuchsia output directory. + self._ssh_config_path = os.path.join(self._fuchsia_out_dir, 'ssh-keys', + 'ssh_config') + self._os_check = 'ignore' + + elif ssh_config: + # Use the SSH config provided via the commandline. + self._ssh_config_path = os.path.expanduser(ssh_config) + + else: + # Default to using an automatically generated SSH config and keys. + boot_data.ProvisionSSH() + self._ssh_config_path = boot_data.GetSSHConfigPath() + + @staticmethod + def CreateFromArgs(args): + return DeviceTarget(args.out_dir, args.target_cpu, args.host, + args.node_name, args.port, args.ssh_config, + args.fuchsia_out_dir, args.os_check, + args.system_log_file) + + @staticmethod + def RegisterArgs(arg_parser): + device_args = arg_parser.add_argument_group( + 'device', 'External device deployment arguments') + device_args.add_argument('--host', + help='The IP of the target device. Optional.') + device_args.add_argument('--node-name', + help='The node-name of the device to boot or ' + 'deploy to. Optional, will use the first ' + 'discovered device if omitted.') + device_args.add_argument('--port', + '-p', + type=int, + default=None, + help='The port of the SSH service running on the ' + 'device. Optional.') + device_args.add_argument('--ssh-config', + '-F', + help='The path to the SSH configuration used for ' + 'connecting to the target device.') + device_args.add_argument( + '--os-check', + choices=['check', 'update', 'ignore'], + default='update', + help="Sets the OS version enforcement policy. If 'check', then the " + "deployment process will halt if the target\'s version doesn\'t " + "match. If 'update', then the target device will automatically " + "be repaved. If 'ignore', then the OS version won\'t be checked.") + + def _ProvisionDeviceIfNecessary(self): + if self._Discover(): + self._WaitUntilReady() + else: + raise Exception('Could not find device. If the device is connected ' + 'to the host remotely, make sure that --host flag is ' + 'set and that remote serving is set up.') + + def _Discover(self): + """Queries mDNS for the IP address of a booted Fuchsia instance whose name + matches |_node_name| on the local area network. If |_node_name| isn't + specified, and there is only one device on the network, then returns the + IP address of that advice. + + Sets |_host_name| and returns True if the device was found, + or waits up to |timeout| seconds and returns False if the device couldn't + be found.""" + + dev_finder_path = GetHostToolPathFromPlatform('device-finder') + + if self._node_name: + command = [ + dev_finder_path, + 'resolve', + '-device-limit', + '1', # Exit early as soon as a host is found. + self._node_name + ] + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=open(os.devnull, 'w')) + else: + proc = self.RunFFXCommand(['target', 'list', '-f', 'simple'], + stdout=subprocess.PIPE, + stderr=open(os.devnull, 'w')) + + output = set(proc.communicate()[0].strip().split('\n')) + if proc.returncode != 0: + return False + + if self._node_name: + # Handle the result of "device-finder resolve". + self._host = output.pop().strip() + else: + name_host_pairs = [x.strip().split(' ') for x in output] + + if len(name_host_pairs) > 1: + logging.info('More than one device was discovered on the network. ' + 'Use --node-name to specify the device to use.') + logging.info('List of devices:') + logging.info(output) + raise Exception('Ambiguous target device specification.') + assert len(name_host_pairs) == 1 + # Check if device has both address and name. + if len(name_host_pairs[0]) < 2: + return False + self._host, self._node_name = name_host_pairs[0] + + logging.info('Found device "%s" at address %s.' % (self._node_name, + self._host)) + + return True + + def Start(self): + if self._host: + self._WaitUntilReady() + else: + self._ProvisionDeviceIfNecessary() + + def GetPkgRepo(self): + if not self._pkg_repo: + if self._fuchsia_out_dir: + # Deploy to an already-booted device running a local Fuchsia build. + self._pkg_repo = pkg_repo.ExternalPkgRepo( + os.path.join(self._fuchsia_out_dir, 'amber-files')) + else: + # Create an ephemeral package repository, then start both "pm serve" as + # well as the bootserver. + self._pkg_repo = pkg_repo.ManagedPkgRepo(self) + + return self._pkg_repo + + def _ParseNodename(self, output): + # Parse the nodename from bootserver stdout. + m = re.search(r'.*Proceeding with nodename (?P.*)$', output, + re.MULTILINE) + if not m: + raise Exception('Couldn\'t parse nodename from bootserver output.') + self._node_name = m.groupdict()['nodename'] + logging.info('Booted device "%s".' % self._node_name) + + # Repeatedly search for a device for |BOOT_DISCOVERY_ATTEMPT| + # number of attempts. If a device isn't found, wait + # |BOOT_DISCOVERY_DELAY_SECS| before searching again. + logging.info('Waiting for device to join network.') + for _ in xrange(BOOT_DISCOVERY_ATTEMPTS): + if self._Discover(): + break + time.sleep(BOOT_DISCOVERY_DELAY_SECS) + + if not self._host: + raise Exception('Device %s couldn\'t be discovered via mDNS.' % + self._node_name) + + self._WaitUntilReady(); + + def _GetEndpoint(self): + return (self._host, self._port) + + def _GetSshConfigPath(self): + return self._ssh_config_path + + def Restart(self): + """Restart the device.""" + + self.RunCommandPiped('dm reboot') + time.sleep(_REBOOT_SLEEP_PERIOD) + self.Start() diff --git a/third_party/libwebrtc/build/fuchsia/device_target_test.py b/third_party/libwebrtc/build/fuchsia/device_target_test.py new file mode 100755 index 0000000000..52ead22495 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/device_target_test.py @@ -0,0 +1,103 @@ +#!/usr/bin/env vpython3 +# Copyright 2021 The Chromium 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 scenarios with number of devices and invalid devices""" +import subprocess +import unittest +import unittest.mock as mock +from argparse import Namespace +from device_target import DeviceTarget +from target import Target + + +class TestDiscoverDeviceTarget(unittest.TestCase): + def setUp(self): + self.args = Namespace(out_dir='out/fuchsia', + target_cpu='x64', + host=None, + node_name=None, + port=None, + ssh_config=None, + fuchsia_out_dir=None, + os_check='update', + system_log_file=None) + + def testNoNodeNameOneDeviceReturnNoneCheckNameAndAddress(self): + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch.object(DeviceTarget, 'RunFFXCommand') as mock_ffx: + mock_spec_popen = mock.create_autospec(subprocess.Popen, instance=True) + mock_spec_popen.communicate.return_value = ('address device_name', '') + mock_spec_popen.returncode = 0 + mock_ffx.return_value = mock_spec_popen + with mock.patch.object(Target, + '_WaitUntilReady') as mock_waituntilready: + mock_waituntilready.return_value = True + self.assertIsNone(device_target_instance.Start()) + self.assertEqual(device_target_instance._node_name, 'device_name') + self.assertEqual(device_target_instance._host, 'address') + + def testNoNodeNameTwoDevicesRaiseExceptionAmbiguousTarget(self): + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch.object(DeviceTarget, 'RunFFXCommand') as mock_ffx: + mock_spec_popen = mock.create_autospec(subprocess.Popen, instance=True) + mock_spec_popen.communicate.return_value = ('address1 device_name1\n' + 'address2 device_name2', '') + mock_spec_popen.returncode = 0 + mock_spec_popen.stdout = '' + mock_ffx.return_value = mock_spec_popen + with self.assertRaisesRegex(Exception, + 'Ambiguous target device specification.'): + device_target_instance.Start() + self.assertIsNone(device_target_instance._node_name) + self.assertIsNone(device_target_instance._host) + + def testNoNodeNameDeviceDoesntHaveNameRaiseExceptionCouldNotFind(self): + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch.object(DeviceTarget, 'RunFFXCommand') as mock_ffx: + mock_spec_popen = mock.create_autospec(subprocess.Popen, instance=True) + mock_spec_popen.communicate.return_value = ('address', '') + mock_spec_popen.returncode = 0 + mock_ffx.return_value = mock_spec_popen + with self.assertRaisesRegex(Exception, 'Could not find device'): + device_target_instance.Start() + self.assertIsNone(device_target_instance._node_name) + self.assertIsNone(device_target_instance._host) + + def testNodeNameDefinedDeviceFoundReturnNoneCheckNameAndHost(self): + self.args.node_name = 'device_name' + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch('subprocess.Popen') as mock_popen: + mock_popen.returncode = ('address', 'device_name') + with mock.patch.object(Target, + '_WaitUntilReady') as mock_waituntilready: + mock_waituntilready.return_value = True + self.assertIsNone(device_target_instance.Start()) + self.assertEqual(device_target_instance._node_name, 'device_name') + self.assertEqual(device_target_instance._host, 'address') + + def testNodeNameDefinedDeviceNotFoundRaiseExceptionCouldNotFind(self): + self.args.node_name = 'wrong_device_name' + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch('subprocess.Popen') as mock_popen: + mock_popen.returncode = ('', '') + with self.assertRaisesRegex(Exception, 'Could not find device'): + device_target_instance.Start() + self.assertIsNone(device_target_instance._node_name) + self.assertIsNone(device_target_instance._host) + + def testNoDevicesFoundRaiseExceptionCouldNotFind(self): + with (DeviceTarget.CreateFromArgs(self.args)) as device_target_instance: + with mock.patch.object(DeviceTarget, 'RunFFXCommand') as mock_ffx: + mock_spec_popen = mock.create_autospec(subprocess.Popen, instance=True) + mock_spec_popen.communicate.return_value = ('', '') + mock_spec_popen.returncode = 0 + mock_ffx.return_value = mock_spec_popen + with self.assertRaisesRegex(Exception, 'Could not find device'): + device_target_instance.Start() + self.assertIsNone(device_target_instance._node_name) + self.assertIsNone(device_target_instance._host) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/fuchsia/emu_target.py b/third_party/libwebrtc/build/fuchsia/emu_target.py new file mode 100644 index 0000000000..335f5418eb --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/emu_target.py @@ -0,0 +1,145 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Implements commands for running/interacting with Fuchsia on an emulator.""" + +import pkg_repo +import boot_data +import logging +import os +import runner_logs +import subprocess +import sys +import target +import tempfile + + +class EmuTarget(target.Target): + def __init__(self, out_dir, target_cpu, system_log_file): + """out_dir: The directory which will contain the files that are + generated to support the emulator deployment. + target_cpu: The emulated target CPU architecture. + Can be 'x64' or 'arm64'.""" + + super(EmuTarget, self).__init__(out_dir, target_cpu) + self._emu_process = None + self._system_log_file = system_log_file + self._pkg_repo = None + + def __enter__(self): + return self + + def _BuildCommand(self): + """Build the command that will be run to start Fuchsia in the emulator.""" + pass + + def _SetEnv(self): + return os.environ.copy() + + # Used by the context manager to ensure that the emulator is killed when + # the Python process exits. + def __exit__(self, exc_type, exc_val, exc_tb): + self.Shutdown(); + + def Start(self): + emu_command = self._BuildCommand() + + # We pass a separate stdin stream. Sharing stdin across processes + # leads to flakiness due to the OS prematurely killing the stream and the + # Python script panicking and aborting. + # The precise root cause is still nebulous, but this fix works. + # See crbug.com/741194. + logging.debug('Launching %s.' % (self.EMULATOR_NAME)) + logging.debug(' '.join(emu_command)) + + # Zircon sends debug logs to serial port (see kernel.serial=legacy flag + # above). Serial port is redirected to a file through emulator stdout. + # If runner_logs are not enabled, we output the kernel serial log + # to a temporary file, and print that out if we are unable to connect to + # the emulator guest, to make it easier to diagnose connectivity issues. + temporary_log_file = None + if runner_logs.IsEnabled(): + stdout = runner_logs.FileStreamFor('serial_log') + else: + temporary_log_file = tempfile.NamedTemporaryFile('w') + stdout = temporary_log_file + + LogProcessStatistics('proc_stat_start_log') + LogSystemStatistics('system_statistics_start_log') + + self._emu_process = subprocess.Popen(emu_command, + stdin=open(os.devnull), + stdout=stdout, + stderr=subprocess.STDOUT, + env=self._SetEnv()) + + try: + self._WaitUntilReady() + LogProcessStatistics('proc_stat_ready_log') + except target.FuchsiaTargetException: + if temporary_log_file: + logging.info('Kernel logs:\n' + + open(temporary_log_file.name, 'r').read()) + raise + + def GetPkgRepo(self): + if not self._pkg_repo: + self._pkg_repo = pkg_repo.ManagedPkgRepo(self) + + return self._pkg_repo + + def Shutdown(self): + if not self._emu_process: + logging.error('%s did not start' % (self.EMULATOR_NAME)) + return + returncode = self._emu_process.poll() + if returncode == None: + logging.info('Shutting down %s' % (self.EMULATOR_NAME)) + self._emu_process.kill() + elif returncode == 0: + logging.info('%s quit unexpectedly without errors' % self.EMULATOR_NAME) + elif returncode < 0: + logging.error('%s was terminated by signal %d' % + (self.EMULATOR_NAME, -returncode)) + else: + logging.error('%s quit unexpectedly with exit code %d' % + (self.EMULATOR_NAME, returncode)) + + LogProcessStatistics('proc_stat_end_log') + LogSystemStatistics('system_statistics_end_log') + + + def _IsEmuStillRunning(self): + if not self._emu_process: + return False + return os.waitpid(self._emu_process.pid, os.WNOHANG)[0] == 0 + + def _GetEndpoint(self): + if not self._IsEmuStillRunning(): + raise Exception('%s quit unexpectedly.' % (self.EMULATOR_NAME)) + return ('localhost', self._host_ssh_port) + + def _GetSshConfigPath(self): + return boot_data.GetSSHConfigPath() + + +def LogSystemStatistics(log_file_name): + statistics_log = runner_logs.FileStreamFor(log_file_name) + # Log the cpu load and process information. + subprocess.call(['top', '-b', '-n', '1'], + stdin=open(os.devnull), + stdout=statistics_log, + stderr=subprocess.STDOUT) + subprocess.call(['ps', '-ax'], + stdin=open(os.devnull), + stdout=statistics_log, + stderr=subprocess.STDOUT) + + +def LogProcessStatistics(log_file_name): + statistics_log = runner_logs.FileStreamFor(log_file_name) + subprocess.call(['cat', '/proc/stat'], + stdin=open(os.devnull), + stdout=statistics_log, + stderr=subprocess.STDOUT) diff --git a/third_party/libwebrtc/build/fuchsia/fvdl_target.py b/third_party/libwebrtc/build/fuchsia/fvdl_target.py new file mode 100644 index 0000000000..fa4fe4f004 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/fvdl_target.py @@ -0,0 +1,204 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Implements commands for running and interacting with Fuchsia on FVDL.""" + +import boot_data +import common +import emu_target +import logging +import os +import re +import subprocess +import tempfile + +_SSH_KEY_DIR = os.path.expanduser('~/.ssh') +_DEFAULT_SSH_PORT = 22 +_DEVICE_PROTO_TEMPLATE = """ +device_spec: {{ + horizontal_resolution: 1024 + vertical_resolution: 600 + vm_heap: 192 + ram: {ramsize} + cache: 32 + screen_density: 240 +}} +""" + + +def GetTargetType(): + return FvdlTarget + + +class EmulatorNetworkNotFoundError(Exception): + """Raised when emulator's address cannot be found""" + pass + + +class FvdlTarget(emu_target.EmuTarget): + EMULATOR_NAME = 'aemu' + _FVDL_PATH = os.path.join(common.SDK_ROOT, 'tools', 'x64', 'fvdl') + + def __init__(self, out_dir, target_cpu, system_log_file, require_kvm, + enable_graphics, hardware_gpu, with_network, ram_size_mb): + super(FvdlTarget, self).__init__(out_dir, target_cpu, system_log_file) + self._require_kvm = require_kvm + self._enable_graphics = enable_graphics + self._hardware_gpu = hardware_gpu + self._with_network = with_network + self._ram_size_mb = ram_size_mb + + self._host = None + self._pid = None + + # Use a temp file for vdl output. + self._vdl_output_file = tempfile.NamedTemporaryFile() + + # Use a temp file for the device proto and write the ram size. + self._device_proto_file = tempfile.NamedTemporaryFile() + with open(self._device_proto_file.name, 'w') as file: + file.write(_DEVICE_PROTO_TEMPLATE.format(ramsize=self._ram_size_mb)) + + @staticmethod + def CreateFromArgs(args): + return FvdlTarget(args.out_dir, args.target_cpu, args.system_log_file, + args.require_kvm, args.enable_graphics, args.hardware_gpu, + args.with_network, args.ram_size_mb) + + @staticmethod + def RegisterArgs(arg_parser): + fvdl_args = arg_parser.add_argument_group('fvdl', 'FVDL arguments') + fvdl_args.add_argument('--with-network', + action='store_true', + default=False, + help='Run emulator with emulated nic via tun/tap.') + + def _BuildCommand(self): + boot_data.ProvisionSSH() + self._host_ssh_port = common.GetAvailableTcpPort() + kernel_image = common.EnsurePathExists( + boot_data.GetTargetFile('qemu-kernel.kernel', self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_QEMU)) + zbi_image = common.EnsurePathExists( + boot_data.GetTargetFile('zircon-a.zbi', self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_QEMU)) + fvm_image = common.EnsurePathExists( + boot_data.GetTargetFile('storage-full.blk', self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_QEMU)) + aemu_path = common.EnsurePathExists( + os.path.join(common.GetEmuRootForPlatform(self.EMULATOR_NAME), + 'emulator')) + + emu_command = [ + self._FVDL_PATH, + '--sdk', + 'start', + '--nopackageserver', + '--nointeractive', + + # Host port mapping for user-networking mode. + '--port-map', + 'hostfwd=tcp::{}-:22'.format(self._host_ssh_port), + + # no-interactive requires a --vdl-output flag to shutdown the emulator. + '--vdl-output', + self._vdl_output_file.name, + + # Use existing images instead of downloading new ones. + '--kernel-image', + kernel_image, + '--zbi-image', + zbi_image, + '--fvm-image', + fvm_image, + '--image-architecture', + self._target_cpu, + + # Use an existing emulator checked out by Chromium. + '--aemu-path', + aemu_path, + + # Use this flag and temp file to define ram size. + '--device-proto', + self._device_proto_file.name + ] + + if not self._require_kvm: + emu_command.append('--noacceleration') + if not self._enable_graphics: + emu_command.append('--headless') + if self._hardware_gpu: + emu_command.append('--host-gpu') + if self._with_network: + emu_command.append('-N') + + logging.info('FVDL command: ' + ' '.join(emu_command)) + + return emu_command + + def _WaitUntilReady(self): + # Indicates the FVDL command finished running. + self._emu_process.communicate() + super(FvdlTarget, self)._WaitUntilReady() + + def _IsEmuStillRunning(self): + if not self._pid: + try: + with open(self._vdl_output_file.name) as vdl_file: + for line in vdl_file: + if 'pid' in line: + match = re.match(r'.*pid:\s*(\d*).*', line) + if match: + self._pid = match.group(1) + except IOError: + logging.error('vdl_output file no longer found. ' + 'Cannot get emulator pid.') + return False + if subprocess.check_output(['ps', '-p', self._pid, 'o', 'comm=']): + return True + logging.error('Emulator pid no longer found. Emulator must be down.') + return False + + def _GetEndpoint(self): + if self._with_network: + return self._GetNetworkAddress() + return ('localhost', self._host_ssh_port) + + def _GetNetworkAddress(self): + if self._host: + return (self._host, _DEFAULT_SSH_PORT) + try: + with open(self._vdl_output_file.name) as vdl_file: + for line in vdl_file: + if 'network_address' in line: + address = re.match(r'.*network_address:\s*"\[(.*)\]".*', line) + if address: + self._host = address.group(1) + return (self._host, _DEFAULT_SSH_PORT) + logging.error('Network address not found.') + raise EmulatorNetworkNotFoundError() + except IOError as e: + logging.error('vdl_output file not found. Cannot get network address.') + raise + + def Shutdown(self): + if not self._emu_process: + logging.error('%s did not start' % (self.EMULATOR_NAME)) + return + femu_command = [ + self._FVDL_PATH, '--sdk', 'kill', '--launched-proto', + self._vdl_output_file.name + ] + femu_process = subprocess.Popen(femu_command) + returncode = femu_process.wait() + if returncode == 0: + logging.info('FVDL shutdown successfully') + else: + logging.info('FVDL kill returned error status {}'.format(returncode)) + emu_target.LogProcessStatistics('proc_stat_end_log') + emu_target.LogSystemStatistics('system_statistics_end_log') + self._vdl_output_file.close() + self._device_proto_file.close() + + def _GetSshConfigPath(self): + return boot_data.GetSSHConfigPath() diff --git a/third_party/libwebrtc/build/fuchsia/fvdl_target_test.py b/third_party/libwebrtc/build/fuchsia/fvdl_target_test.py new file mode 100755 index 0000000000..de5e52f0a9 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/fvdl_target_test.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 different flags to see if they are being used correctly""" + +import boot_data +import common +import os +import unittest +import unittest.mock as mock + +from argparse import Namespace +from fvdl_target import FvdlTarget, _SSH_KEY_DIR + + +class TestBuildCommandFvdlTarget(unittest.TestCase): + def setUp(self): + self.args = Namespace(out_dir='outdir', + system_log_file=None, + target_cpu='x64', + require_kvm=True, + enable_graphics=False, + hardware_gpu=False, + with_network=False, + ram_size_mb=8192) + + def testBasicEmuCommand(self): + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + build_command = target._BuildCommand() + self.assertIn(target._FVDL_PATH, build_command) + self.assertIn('--sdk', build_command) + self.assertIn('start', build_command) + self.assertNotIn('--noacceleration', target._BuildCommand()) + self.assertIn('--headless', target._BuildCommand()) + self.assertNotIn('--host-gpu', target._BuildCommand()) + self.assertNotIn('-N', target._BuildCommand()) + self.assertIn('--device-proto', target._BuildCommand()) + self.assertTrue(os.path.exists(target._device_proto_file.name)) + correct_ram_amount = False + with open(target._device_proto_file.name) as file: + for line in file: + if line.strip() == 'ram: 8192': + correct_ram_amount = True + break + self.assertTrue(correct_ram_amount) + + def testBuildCommandCheckIfNotRequireKVMSetNoAcceleration(self): + self.args.require_kvm = False + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + self.assertIn('--noacceleration', target._BuildCommand()) + + def testBuildCommandCheckIfNotEnableGraphicsSetHeadless(self): + self.args.enable_graphics = True + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + self.assertNotIn('--headless', target._BuildCommand()) + + def testBuildCommandCheckIfHardwareGpuSetHostGPU(self): + self.args.hardware_gpu = True + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + self.assertIn('--host-gpu', target._BuildCommand()) + + def testBuildCommandCheckIfWithNetworkSetTunTap(self): + self.args.with_network = True + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + self.assertIn('-N', target._BuildCommand()) + + def testBuildCommandCheckRamSizeNot8192SetRamSize(self): + self.args.ram_size_mb = 4096 + with FvdlTarget.CreateFromArgs(self.args) as target: + target.Shutdown = mock.MagicMock() + common.EnsurePathExists = mock.MagicMock(return_value='image') + with mock.patch.object(boot_data, 'ProvisionSSH') as provision_mock: + self.assertIn('--device-proto', target._BuildCommand()) + self.assertTrue(os.path.exists(target._device_proto_file.name)) + correct_ram_amount = False + with open(target._device_proto_file.name) as file: + for line in file: + if line.strip() == 'ram: 4096': + correct_ram_amount = True + break + self.assertTrue(correct_ram_amount) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/fuchsia/generic_x64_target.py b/third_party/libwebrtc/build/fuchsia/generic_x64_target.py new file mode 100644 index 0000000000..5fece127d9 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/generic_x64_target.py @@ -0,0 +1,99 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Implements commands for running and interacting with Fuchsia generic +build on devices.""" + +import boot_data +import device_target +import logging +import os + +from common import SDK_ROOT, EnsurePathExists, \ + GetHostToolPathFromPlatform, SubprocessCallWithTimeout + + +def GetTargetType(): + return GenericX64PavedDeviceTarget + + +class GenericX64PavedDeviceTarget(device_target.DeviceTarget): + """In addition to the functionality provided by DeviceTarget, this class + automatically handles paving of x64 devices that use generic Fuchsia build. + + If there are no running devices, then search for a device running Zedboot + and pave it. + + If there's only one running device, or |_node_name| is set, then the + device's SDK version is checked unless --os-check=ignore is set. + If --os-check=update is set, then the target device is repaved if the SDK + version doesn't match.""" + + TARGET_HASH_FILE_PATH = '/data/.hash' + + def _SDKHashMatches(self): + """Checks if /data/.hash on the device matches SDK_ROOT/.hash. + + Returns True if the files are identical, or False otherwise. + """ + + with tempfile.NamedTemporaryFile() as tmp: + # TODO: Avoid using an exception for when file is unretrievable. + try: + self.GetFile(TARGET_HASH_FILE_PATH, tmp.name) + except subprocess.CalledProcessError: + # If the file is unretrievable for whatever reason, assume mismatch. + return False + + return filecmp.cmp(tmp.name, os.path.join(SDK_ROOT, '.hash'), False) + + def _ProvisionDeviceIfNecessary(self): + should_provision = False + + if self._Discover(): + self._WaitUntilReady() + + if self._os_check != 'ignore': + if self._SDKHashMatches(): + if self._os_check == 'update': + logging.info('SDK hash does not match; rebooting and repaving.') + self.RunCommand(['dm', 'reboot']) + should_provision = True + elif self._os_check == 'check': + raise Exception('Target device SDK version does not match.') + else: + should_provision = True + + if should_provision: + self._ProvisionDevice() + + def _ProvisionDevice(self): + """Pave a device with a generic image of Fuchsia.""" + + bootserver_path = GetHostToolPathFromPlatform('bootserver') + bootserver_command = [ + bootserver_path, '-1', '--fvm', + EnsurePathExists( + boot_data.GetTargetFile('storage-sparse.blk', + self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_GENERIC)), + EnsurePathExists( + boot_data.GetBootImage(self._out_dir, self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_GENERIC)) + ] + + if self._node_name: + bootserver_command += ['-n', self._node_name] + + bootserver_command += ['--'] + bootserver_command += boot_data.GetKernelArgs(self._out_dir) + + logging.debug(' '.join(bootserver_command)) + _, stdout = SubprocessCallWithTimeout(bootserver_command, + silent=False, + timeout_secs=300) + + self._ParseNodename(stdout) + + # Update the target's hash to match the current tree's. + self.PutFile(os.path.join(SDK_ROOT, '.hash'), TARGET_HASH_FILE_PATH) diff --git a/third_party/libwebrtc/build/fuchsia/linux.sdk.sha1 b/third_party/libwebrtc/build/fuchsia/linux.sdk.sha1 new file mode 100644 index 0000000000..c7cd76a58d --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/linux.sdk.sha1 @@ -0,0 +1 @@ +6.20210918.3.1 diff --git a/third_party/libwebrtc/build/fuchsia/mac.sdk.sha1 b/third_party/libwebrtc/build/fuchsia/mac.sdk.sha1 new file mode 100644 index 0000000000..c7cd76a58d --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/mac.sdk.sha1 @@ -0,0 +1 @@ +6.20210918.3.1 diff --git a/third_party/libwebrtc/build/fuchsia/net_test_server.py b/third_party/libwebrtc/build/fuchsia/net_test_server.py new file mode 100644 index 0000000000..56005cf12c --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/net_test_server.py @@ -0,0 +1,90 @@ +# Copyright 2018 The Chromium 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 common +import json +import logging +import os +import re +import socket +import sys +import subprocess +import tempfile + +DIR_SOURCE_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +sys.path.append(os.path.join(DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common')) +import chrome_test_server_spawner + + +# Implementation of chrome_test_server_spawner.PortForwarder that uses SSH's +# remote port forwarding feature to forward ports. +class SSHPortForwarder(chrome_test_server_spawner.PortForwarder): + def __init__(self, target): + self._target = target + + # Maps the host (server) port to the device port number. + self._port_mapping = {} + + def Map(self, port_pairs): + for p in port_pairs: + _, host_port = p + self._port_mapping[host_port] = \ + common.ConnectPortForwardingTask(self._target, host_port) + + def GetDevicePortForHostPort(self, host_port): + return self._port_mapping[host_port] + + def Unmap(self, device_port): + for host_port, entry in self._port_mapping.iteritems(): + if entry == device_port: + forwarding_args = [ + '-NT', '-O', 'cancel', '-R', '0:localhost:%d' % host_port] + task = self._target.RunCommandPiped([], + ssh_args=forwarding_args, + stdout=open(os.devnull, 'w'), + stderr=subprocess.PIPE) + task.wait() + if task.returncode != 0: + raise Exception( + 'Error %d when unmapping port %d' % (task.returncode, + device_port)) + del self._port_mapping[host_port] + return + + raise Exception('Unmap called for unknown port: %d' % device_port) + + +def SetupTestServer(target, test_concurrency, for_package, for_realms=[]): + """Provisions a forwarding test server and configures |target| to use it. + + Returns a Popen object for the test server process.""" + + logging.debug('Starting test server.') + # The TestLauncher can launch more jobs than the limit specified with + # --test-launcher-jobs so the max number of spawned test servers is set to + # twice that limit here. See https://crbug.com/913156#c19. + spawning_server = chrome_test_server_spawner.SpawningServer( + 0, SSHPortForwarder(target), test_concurrency * 2) + forwarded_port = common.ConnectPortForwardingTask( + target, spawning_server.server_port) + spawning_server.Start() + + logging.debug('Test server listening for connections (port=%d)' % + spawning_server.server_port) + logging.debug('Forwarded port is %d' % forwarded_port) + + config_file = tempfile.NamedTemporaryFile(delete=True) + + config_file.write(json.dumps({ + 'spawner_url_base': 'http://localhost:%d' % forwarded_port + })) + + config_file.flush() + target.PutFile(config_file.name, + '/tmp/net-test-server-config', + for_package=for_package, + for_realms=for_realms) + + return spawning_server diff --git a/third_party/libwebrtc/build/fuchsia/pkg_repo.py b/third_party/libwebrtc/build/fuchsia/pkg_repo.py new file mode 100644 index 0000000000..3e635e9ec0 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/pkg_repo.py @@ -0,0 +1,209 @@ +# Copyright 2019 The Chromium 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 common +import json +import logging +import os +import shutil +import subprocess +import tempfile +import time + +from six.moves import urllib + +# Maximum amount of time to block while waiting for "pm serve" to come up. +_PM_SERVE_LIVENESS_TIMEOUT_SECS = 10 + +_MANAGED_REPO_NAME = 'chrome-runner' + + +class PkgRepo(object): + """Abstract interface for a repository used to serve packages to devices.""" + + def __init__(self, target): + self._target = target + + def PublishPackage(self, package_path): + pm_tool = common.GetHostToolPathFromPlatform('pm') + # Flags for `pm publish`: + # https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/sys/pkg/bin/pm/cmd/pm/publish/publish.go + # https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/sys/pkg/bin/pm/repo/config.go + # -a: Publish archived package + # -f : Path to packages + # -r : Path to repository + # -vt: Repo versioning based on time rather than monotonic version number + # increase + # -v: Verbose output + subprocess.check_call([ + pm_tool, 'publish', '-a', '-f', package_path, '-r', + self.GetPath(), '-vt', '-v' + ], + stderr=subprocess.STDOUT) + + def GetPath(self): + pass + + +class ManagedPkgRepo(PkgRepo): + """Creates and serves packages from an ephemeral repository.""" + + def __init__(self, target): + PkgRepo.__init__(self, target) + self._with_count = 0 + + self._pkg_root = tempfile.mkdtemp() + pm_tool = common.GetHostToolPathFromPlatform('pm') + subprocess.check_call([pm_tool, 'newrepo', '-repo', self._pkg_root]) + logging.info('Creating and serving temporary package root: {}.'.format( + self._pkg_root)) + + serve_port = common.GetAvailableTcpPort() + # Flags for `pm serve`: + # https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/sys/pkg/bin/pm/cmd/pm/serve/serve.go + # -l : Port to listen on + # -c 2: Use config.json format v2, the default for pkgctl + # -q: Don't print out information about requests + self._pm_serve_task = subprocess.Popen([ + pm_tool, 'serve', '-d', + os.path.join(self._pkg_root, 'repository'), '-l', + ':%d' % serve_port, '-c', '2', '-q' + ]) + + # Block until "pm serve" starts serving HTTP traffic at |serve_port|. + timeout = time.time() + _PM_SERVE_LIVENESS_TIMEOUT_SECS + while True: + try: + urllib.request.urlopen('http://localhost:%d' % serve_port, + timeout=1).read() + break + except urllib.error.URLError: + logging.info('Waiting until \'pm serve\' is up...') + + if time.time() >= timeout: + raise Exception('Timed out while waiting for \'pm serve\'.') + + time.sleep(1) + + remote_port = common.ConnectPortForwardingTask(target, serve_port, 0) + self._RegisterPkgRepository(self._pkg_root, remote_port) + + def __enter__(self): + self._with_count += 1 + return self + + def __exit__(self, type, value, tb): + # Allows the repository to delete itself when it leaves the scope of a 'with' block. + self._with_count -= 1 + if self._with_count > 0: + return + + self._UnregisterPkgRepository() + self._pm_serve_task.kill() + self._pm_serve_task = None + + logging.info('Cleaning up package root: ' + self._pkg_root) + shutil.rmtree(self._pkg_root) + self._pkg_root = None + + def GetPath(self): + return self._pkg_root + + def _RegisterPkgRepository(self, tuf_repo, remote_port): + """Configures a device to use a local TUF repository as an installation + source for packages. + |tuf_repo|: The host filesystem path to the TUF repository. + |remote_port|: The reverse-forwarded port used to connect to instance of + `pm serve` that is serving the contents of |tuf_repo|.""" + + # Extract the public signing key for inclusion in the config file. + root_keys = [] + root_json_path = os.path.join(tuf_repo, 'repository', 'root.json') + root_json = json.load(open(root_json_path, 'r')) + for root_key_id in root_json['signed']['roles']['root']['keyids']: + root_keys.append({ + 'type': + root_json['signed']['keys'][root_key_id]['keytype'], + 'value': + root_json['signed']['keys'][root_key_id]['keyval']['public'] + }) + + # "pm serve" can automatically generate a "config.json" file at query time, + # but the file is unusable because it specifies URLs with port + # numbers that are unreachable from across the port forwarding boundary. + # So instead, we generate our own config file with the forwarded port + # numbers instead. + config_file = open(os.path.join(tuf_repo, 'repository', 'repo_config.json'), + 'w') + json.dump( + { + 'repo_url': + "fuchsia-pkg://%s" % _MANAGED_REPO_NAME, + 'root_keys': + root_keys, + 'mirrors': [{ + "mirror_url": "http://127.0.0.1:%d" % remote_port, + "subscribe": True + }], + 'root_threshold': + 1, + 'root_version': + 1 + }, config_file) + config_file.close() + + # Register the repo. + return_code = self._target.RunCommand([ + ('pkgctl repo rm fuchsia-pkg://%s; ' + + 'pkgctl repo add url http://127.0.0.1:%d/repo_config.json; ') % + (_MANAGED_REPO_NAME, remote_port) + ]) + if return_code != 0: + raise Exception('Error code %d when running pkgctl repo add.' % + return_code) + + rule_template = """'{"version":"1","content":[{"host_match":"fuchsia.com","host_replacement":"%s","path_prefix_match":"/","path_prefix_replacement":"/"}]}'""" + return_code = self._target.RunCommand([ + ('pkgctl rule replace json %s') % (rule_template % (_MANAGED_REPO_NAME)) + ]) + if return_code != 0: + raise Exception('Error code %d when running pkgctl rule replace.' % + return_code) + + def _UnregisterPkgRepository(self): + """Unregisters the package repository.""" + + logging.debug('Unregistering package repository.') + self._target.RunCommand( + ['pkgctl', 'repo', 'rm', + 'fuchsia-pkg://%s' % (_MANAGED_REPO_NAME)]) + + # Re-enable 'devhost' repo if it's present. This is useful for devices that + # were booted with 'fx serve'. + self._target.RunCommand([ + 'pkgctl', 'rule', 'replace', 'json', + """'{"version":"1","content":[{"host_match":"fuchsia.com","host_replacement":"devhost","path_prefix_match":"/","path_prefix_replacement":"/"}]}'""" + ], + silent=True) + + +class ExternalPkgRepo(PkgRepo): + """Publishes packages to a package repository located and served externally + (ie. located under a Fuchsia build directory and served by "fx serve".""" + + def __init__(self, pkg_root): + self._pkg_root = pkg_root + logging.info('Using existing package root: {}'.format(pkg_root)) + logging.info( + 'ATTENTION: This will not start a package server. Please run "fx serve" manually.' + ) + + def GetPath(self): + return self._pkg_root + + def __enter__(self): + return self + + def __exit__(self, type, value, tb): + pass diff --git a/third_party/libwebrtc/build/fuchsia/qemu_image.py b/third_party/libwebrtc/build/fuchsia/qemu_image.py new file mode 100644 index 0000000000..ab5e040acb --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/qemu_image.py @@ -0,0 +1,75 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Workaround for qemu-img bug on arm64 platforms with multiple cores. + +Runs qemu-img command with timeout and retries the command if it hangs. + +See: +crbug.com/1046861 QEMU is out of date; current version of qemu-img +is unstable + +https://bugs.launchpad.net/qemu/+bug/1805256 qemu-img hangs on +rcu_call_ready_event logic in Aarch64 when converting images + +TODO(crbug.com/1046861): Remove this workaround when the bug is fixed. +""" + +import logging +import subprocess +import tempfile +import time + + +# qemu-img p99 run time on Cavium ThunderX2 servers is 26 seconds. +# Using 2x the p99 time as the timeout. +QEMU_IMG_TIMEOUT_SEC = 52 + + +def _ExecQemuImgWithTimeout(command): + """Execute qemu-img command in subprocess with timeout. + + Returns: None if command timed out or return code if command completed. + """ + + logging.info('qemu-img starting') + command_output_file = tempfile.NamedTemporaryFile('w') + p = subprocess.Popen(command, stdout=command_output_file, + stderr=subprocess.STDOUT) + start_sec = time.time() + while p.poll() is None and time.time() - start_sec < QEMU_IMG_TIMEOUT_SEC: + time.sleep(1) + stop_sec = time.time() + logging.info('qemu-img duration: %f' % float(stop_sec - start_sec)) + + if p.poll() is None: + returncode = None + p.kill() + p.wait() + else: + returncode = p.returncode + + log_level = logging.WARN if returncode else logging.DEBUG + for line in open(command_output_file.name, 'r'): + logging.log(log_level, 'qemu-img stdout: ' + line.strip()) + + return returncode + + +def ExecQemuImgWithRetry(command): + """ Execute qemu-img command in subprocess with 2 retries. + + Raises CalledProcessError if command does not complete successfully. + """ + + tries = 0 + status = None + while status is None and tries <= 2: + tries += 1 + status = _ExecQemuImgWithTimeout(command) + + if status is None: + raise subprocess.CalledProcessError(-1, command) + if status: + raise subprocess.CalledProcessError(status, command) diff --git a/third_party/libwebrtc/build/fuchsia/qemu_target.py b/third_party/libwebrtc/build/fuchsia/qemu_target.py new file mode 100644 index 0000000000..529b1cc443 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/qemu_target.py @@ -0,0 +1,243 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Implements commands for running and interacting with Fuchsia on QEMU.""" + +import boot_data +import common +import emu_target +import hashlib +import logging +import os +import platform +import qemu_image +import shutil +import subprocess +import sys +import tempfile + +from common import GetHostArchFromPlatform, GetEmuRootForPlatform +from common import EnsurePathExists +from qemu_image import ExecQemuImgWithRetry +from target import FuchsiaTargetException + + +# Virtual networking configuration data for QEMU. +HOST_IP_ADDRESS = '10.0.2.2' +GUEST_MAC_ADDRESS = '52:54:00:63:5e:7b' + +# Capacity of the system's blobstore volume. +EXTENDED_BLOBSTORE_SIZE = 1073741824 # 1GB + + +def GetTargetType(): + return QemuTarget + + +class QemuTarget(emu_target.EmuTarget): + EMULATOR_NAME = 'qemu' + + def __init__(self, out_dir, target_cpu, system_log_file, cpu_cores, + require_kvm, ram_size_mb): + super(QemuTarget, self).__init__(out_dir, target_cpu, system_log_file) + self._cpu_cores=cpu_cores + self._require_kvm=require_kvm + self._ram_size_mb=ram_size_mb + + @staticmethod + def CreateFromArgs(args): + return QemuTarget(args.out_dir, args.target_cpu, args.system_log_file, + args.cpu_cores, args.require_kvm, args.ram_size_mb) + + def _IsKvmEnabled(self): + kvm_supported = sys.platform.startswith('linux') and \ + os.access('/dev/kvm', os.R_OK | os.W_OK) + same_arch = \ + (self._target_cpu == 'arm64' and platform.machine() == 'aarch64') or \ + (self._target_cpu == 'x64' and platform.machine() == 'x86_64') + if kvm_supported and same_arch: + return True + elif self._require_kvm: + if same_arch: + if not os.path.exists('/dev/kvm'): + kvm_error = 'File /dev/kvm does not exist. Please install KVM first.' + else: + kvm_error = 'To use KVM acceleration, add user to the kvm group '\ + 'with "sudo usermod -a -G kvm $USER". Log out and back '\ + 'in for the change to take effect.' + raise FuchsiaTargetException(kvm_error) + else: + raise FuchsiaTargetException('KVM unavailable when CPU architecture '\ + 'of host is different from that of'\ + ' target. See --allow-no-kvm.') + else: + return False + + def _BuildQemuConfig(self): + boot_data.AssertBootImagesExist(self._GetTargetSdkArch(), 'qemu') + + emu_command = [ + '-kernel', + EnsurePathExists( + boot_data.GetTargetFile('qemu-kernel.kernel', + self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_QEMU)), + '-initrd', + EnsurePathExists( + boot_data.GetBootImage(self._out_dir, self._GetTargetSdkArch(), + boot_data.TARGET_TYPE_QEMU)), + '-m', + str(self._ram_size_mb), + '-smp', + str(self._cpu_cores), + + # Attach the blobstore and data volumes. Use snapshot mode to discard + # any changes. + '-snapshot', + '-drive', + 'file=%s,format=qcow2,if=none,id=blobstore,snapshot=on' % + _EnsureBlobstoreQcowAndReturnPath(self._out_dir, + self._GetTargetSdkArch()), + '-device', + 'virtio-blk-pci,drive=blobstore', + + # Use stdio for the guest OS only; don't attach the QEMU interactive + # monitor. + '-serial', + 'stdio', + '-monitor', + 'none', + ] + + # Configure the machine to emulate, based on the target architecture. + if self._target_cpu == 'arm64': + emu_command.extend([ + '-machine','virt,gic_version=3', + ]) + else: + emu_command.extend([ + '-machine', 'q35', + ]) + + # Configure virtual network. + netdev_type = 'virtio-net-pci' + netdev_config = 'type=user,id=net0,restrict=off' + + self._host_ssh_port = common.GetAvailableTcpPort() + netdev_config += ",hostfwd=tcp::%s-:22" % self._host_ssh_port + emu_command.extend([ + '-netdev', netdev_config, + '-device', '%s,netdev=net0,mac=%s' % (netdev_type, GUEST_MAC_ADDRESS), + ]) + + # Configure the CPU to emulate. + # On Linux, we can enable lightweight virtualization (KVM) if the host and + # guest architectures are the same. + if self._IsKvmEnabled(): + kvm_command = ['-enable-kvm', '-cpu'] + if self._target_cpu == 'arm64': + kvm_command.append('host') + else: + kvm_command.append('host,migratable=no,+invtsc') + else: + logging.warning('Unable to launch %s with KVM acceleration. ' + 'The guest VM will be slow.' % (self.EMULATOR_NAME)) + if self._target_cpu == 'arm64': + kvm_command = ['-cpu', 'cortex-a53'] + else: + kvm_command = ['-cpu', 'Haswell,+smap,-check,-fsgsbase'] + + emu_command.extend(kvm_command) + + kernel_args = boot_data.GetKernelArgs(self._out_dir) + + # TERM=dumb tells the guest OS to not emit ANSI commands that trigger + # noisy ANSI spew from the user's terminal emulator. + kernel_args.append('TERM=dumb') + + # Construct kernel cmd line + kernel_args.append('kernel.serial=legacy') + + # Don't 'reboot' the emulator if the kernel crashes + kernel_args.append('kernel.halt-on-panic=true') + + emu_command.extend(['-append', ' '.join(kernel_args)]) + + return emu_command + + def _BuildCommand(self): + if self._target_cpu == 'arm64': + qemu_exec = 'qemu-system-' + 'aarch64' + elif self._target_cpu == 'x64': + qemu_exec = 'qemu-system-' + 'x86_64' + else: + raise Exception('Unknown target_cpu %s:' % self._target_cpu) + + qemu_command = [ + os.path.join(GetEmuRootForPlatform(self.EMULATOR_NAME), 'bin', + qemu_exec) + ] + qemu_command.extend(self._BuildQemuConfig()) + qemu_command.append('-nographic') + return qemu_command + +def _ComputeFileHash(filename): + hasher = hashlib.md5() + with open(filename, 'rb') as f: + buf = f.read(4096) + while buf: + hasher.update(buf) + buf = f.read(4096) + + return hasher.hexdigest() + + +def _EnsureBlobstoreQcowAndReturnPath(out_dir, target_arch): + """Returns a file containing the Fuchsia blobstore in a QCOW format, + with extra buffer space added for growth.""" + + qimg_tool = os.path.join(common.GetEmuRootForPlatform('qemu'), + 'bin', 'qemu-img') + fvm_tool = common.GetHostToolPathFromPlatform('fvm') + blobstore_path = boot_data.GetTargetFile('storage-full.blk', target_arch, + 'qemu') + qcow_path = os.path.join(out_dir, 'gen', 'blobstore.qcow') + + # Check a hash of the blobstore to determine if we can re-use an existing + # extended version of it. + blobstore_hash_path = os.path.join(out_dir, 'gen', 'blobstore.hash') + current_blobstore_hash = _ComputeFileHash(blobstore_path) + + if os.path.exists(blobstore_hash_path) and os.path.exists(qcow_path): + if current_blobstore_hash == open(blobstore_hash_path, 'r').read(): + return qcow_path + + # Add some extra room for growth to the Blobstore volume. + # Fuchsia is unable to automatically extend FVM volumes at runtime so the + # volume enlargement must be performed prior to QEMU startup. + + # The 'fvm' tool only supports extending volumes in-place, so make a + # temporary copy of 'blobstore.bin' before it's mutated. + extended_blobstore = tempfile.NamedTemporaryFile() + shutil.copyfile(blobstore_path, extended_blobstore.name) + subprocess.check_call([fvm_tool, extended_blobstore.name, 'extend', + '--length', str(EXTENDED_BLOBSTORE_SIZE), + blobstore_path]) + + # Construct a QCOW image from the extended, temporary FVM volume. + # The result will be retained in the build output directory for re-use. + qemu_img_cmd = [qimg_tool, 'convert', '-f', 'raw', '-O', 'qcow2', + '-c', extended_blobstore.name, qcow_path] + # TODO(crbug.com/1046861): Remove arm64 call with retries when bug is fixed. + if common.GetHostArchFromPlatform() == 'arm64': + qemu_image.ExecQemuImgWithRetry(qemu_img_cmd) + else: + subprocess.check_call(qemu_img_cmd) + + # Write out a hash of the original blobstore file, so that subsequent runs + # can trivially check if a cached extended FVM volume is available for reuse. + with open(blobstore_hash_path, 'w') as blobstore_hash_file: + blobstore_hash_file.write(current_blobstore_hash) + + return qcow_path diff --git a/third_party/libwebrtc/build/fuchsia/qemu_target_test.py b/third_party/libwebrtc/build/fuchsia/qemu_target_test.py new file mode 100755 index 0000000000..44b3802909 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/qemu_target_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/python2 +# Copyright 2018 The Chromium 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 qemu_target +import shutil +import subprocess +import tempfile +import time +import unittest + +TEST_PAYLOAD = "Let's get this payload across the finish line!" + +tmpdir = tempfile.mkdtemp() + +# Register the target with the context manager so that it always gets +# torn down on process exit. Otherwise there might be lingering QEMU instances +# if Python crashes or is interrupted. +with qemu_target.QemuTarget(tmpdir, 'x64') as target: + class TestQemuTarget(unittest.TestCase): + @classmethod + def setUpClass(cls): + target.Start() + + @classmethod + def tearDownClass(cls): + target.Shutdown() + shutil.rmtree(tmpdir) + + def testCopyBidirectional(self): + tmp_path = tmpdir + "/payload" + with open(tmp_path, "w") as tmpfile: + tmpfile.write(TEST_PAYLOAD) + target.PutFile(tmp_path, '/tmp/payload') + + tmp_path_roundtrip = tmp_path + ".roundtrip" + target.GetFile('/tmp/payload', tmp_path_roundtrip) + with open(tmp_path_roundtrip) as roundtrip: + self.assertEqual(TEST_PAYLOAD, roundtrip.read()) + + def testRunCommand(self): + self.assertEqual(0, target.RunCommand(['true'])) + self.assertEqual(1, target.RunCommand(['false'])) + + def testRunCommandPiped(self): + proc = target.RunCommandPiped(['cat'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + proc.stdin.write(TEST_PAYLOAD) + proc.stdin.flush() + proc.stdin.close() + self.assertEqual(TEST_PAYLOAD, proc.stdout.readline()) + proc.kill() + + + if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/fuchsia/remote_cmd.py b/third_party/libwebrtc/build/fuchsia/remote_cmd.py new file mode 100644 index 0000000000..56aa8b1721 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/remote_cmd.py @@ -0,0 +1,131 @@ +# Copyright 2018 The Chromium 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 subprocess +import threading + +from common import SubprocessCallWithTimeout + +_SSH = ['ssh'] +_SCP = ['scp', '-C'] # Use gzip compression. +_SSH_LOGGER = logging.getLogger('ssh') + +COPY_TO_TARGET = 0 +COPY_FROM_TARGET = 1 + + +def _IsLinkLocalIPv6(hostname): + return hostname.startswith('fe80::') + +def _EscapeIfIPv6Address(address): + if ':' in address: + return '[' + address + ']' + else: + return address + +class CommandRunner(object): + """Helper class used to execute commands on a remote host over SSH.""" + + def __init__(self, config_path, host, port): + """Creates a CommandRunner that connects to the specified |host| and |port| + using the ssh config at the specified |config_path|. + + config_path: Full path to SSH configuration. + host: The hostname or IP address of the remote host. + port: The port to connect to.""" + + self._config_path = config_path + self._host = host + self._port = port + + def _GetSshCommandLinePrefix(self): + cmd_prefix = _SSH + ['-F', self._config_path, self._host] + if self._port: + cmd_prefix += ['-p', str(self._port)] + return cmd_prefix + + def RunCommand(self, command, silent, timeout_secs=None): + """Executes an SSH command on the remote host and blocks until completion. + + command: A list of strings containing the command and its arguments. + silent: If true, suppresses all output from 'ssh'. + timeout_secs: If set, limits the amount of time that |command| may run. + Commands which exceed the timeout are killed. + + Returns the exit code from the remote command.""" + + ssh_command = self._GetSshCommandLinePrefix() + command + logging.warning(ssh_command) + _SSH_LOGGER.debug('ssh exec: ' + ' '.join(ssh_command)) + retval, _, _ = SubprocessCallWithTimeout(ssh_command, silent, timeout_secs) + return retval + + + def RunCommandPiped(self, command, stdout, stderr, ssh_args = None, **kwargs): + """Executes an SSH command on the remote host and returns a process object + with access to the command's stdio streams. Does not block. + + command: A list of strings containing the command and its arguments. + stdout: subprocess stdout. Must not be None. + stderr: subprocess stderr. Must not be None. + ssh_args: Arguments that will be passed to SSH. + kwargs: A dictionary of parameters to be passed to subprocess.Popen(). + The parameters can be used to override stdin and stdout, for + example. + + Returns a Popen object for the command.""" + + if not stdout or not stderr: + raise Exception('Stdout/stderr must be specified explicitly') + + if not ssh_args: + ssh_args = [] + + ssh_command = self._GetSshCommandLinePrefix() + ssh_args + ['--'] + command + logging.warning(ssh_command) + _SSH_LOGGER.debug(' '.join(ssh_command)) + return subprocess.Popen(ssh_command, stdout=stdout, stderr=stderr, **kwargs) + + + def RunScp(self, sources, dest, direction, recursive=False): + """Copies a file to or from a remote host using SCP and blocks until + completion. + + sources: Paths of the files to be copied. + dest: The path that |source| will be copied to. + direction: Indicates whether the file should be copied to + or from the remote side. + Valid values are COPY_TO_TARGET or COPY_FROM_TARGET. + recursive: If true, performs a recursive copy. + + Function will raise an assertion if a failure occurred.""" + + scp_command = _SCP[:] + if _SSH_LOGGER.getEffectiveLevel() == logging.DEBUG: + scp_command.append('-v') + if recursive: + scp_command.append('-r') + + host = _EscapeIfIPv6Address(self._host) + + if direction == COPY_TO_TARGET: + dest = "%s:%s" % (host, dest) + else: + sources = ["%s:%s" % (host, source) for source in sources] + + scp_command += ['-F', self._config_path] + if self._port: + scp_command += ['-P', str(self._port)] + scp_command += sources + scp_command += [dest] + + _SSH_LOGGER.debug(' '.join(scp_command)) + try: + scp_output = subprocess.check_output(scp_command, + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as error: + _SSH_LOGGER.info(error.output) + raise diff --git a/third_party/libwebrtc/build/fuchsia/run_test_package.py b/third_party/libwebrtc/build/fuchsia/run_test_package.py new file mode 100644 index 0000000000..7e93461027 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/run_test_package.py @@ -0,0 +1,278 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Contains a helper function for deploying and executing a packaged +executable on a Target.""" + +from __future__ import print_function + +import common +import hashlib +import logging +import multiprocessing +import os +import re +import select +import subprocess +import sys +import threading +import uuid + +from symbolizer import BuildIdsPaths, RunSymbolizer, SymbolizerFilter + +FAR = common.GetHostToolPathFromPlatform('far') + +# Amount of time to wait for the termination of the system log output thread. +_JOIN_TIMEOUT_SECS = 5 + + +def _AttachKernelLogReader(target): + """Attaches a kernel log reader as a long-running SSH task.""" + + logging.info('Attaching kernel logger.') + return target.RunCommandPiped(['dlog', '-f'], + stdin=open(os.devnull, 'r'), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + +class SystemLogReader(object): + """Collects and symbolizes Fuchsia system log to a file.""" + + def __init__(self): + self._listener_proc = None + self._symbolizer_proc = None + self._system_log = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Stops the system logging processes and closes the output file.""" + if self._symbolizer_proc: + self._symbolizer_proc.kill() + if self._listener_proc: + self._listener_proc.kill() + if self._system_log: + self._system_log.close() + + def Start(self, target, package_paths, system_log_file): + """Start a system log reader as a long-running SSH task.""" + logging.debug('Writing fuchsia system log to %s' % system_log_file) + + self._listener_proc = target.RunCommandPiped(['log_listener'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + self._system_log = open(system_log_file, 'w', buffering=1) + self._symbolizer_proc = RunSymbolizer(self._listener_proc.stdout, + self._system_log, + BuildIdsPaths(package_paths)) + + +class MergedInputStream(object): + """Merges a number of input streams into a UNIX pipe on a dedicated thread. + Terminates when the file descriptor of the primary stream (the first in + the sequence) is closed.""" + + def __init__(self, streams): + assert len(streams) > 0 + self._streams = streams + self._output_stream = None + self._thread = None + + def Start(self): + """Returns a pipe to the merged output stream.""" + + read_pipe, write_pipe = os.pipe() + + self._output_stream = os.fdopen(write_pipe, 'wb', 1) + self._thread = threading.Thread(target=self._Run) + self._thread.start() + + return os.fdopen(read_pipe, 'r') + + def _Run(self): + streams_by_fd = {} + primary_fd = self._streams[0].fileno() + for s in self._streams: + streams_by_fd[s.fileno()] = s + + # Set when the primary FD is closed. Input from other FDs will continue to + # be processed until select() runs dry. + flush = False + + # The lifetime of the MergedInputStream is bound to the lifetime of + # |primary_fd|. + while primary_fd: + # When not flushing: block until data is read or an exception occurs. + rlist, _, xlist = select.select(streams_by_fd, [], streams_by_fd) + + if len(rlist) == 0 and flush: + break + + for fileno in xlist: + del streams_by_fd[fileno] + if fileno == primary_fd: + primary_fd = None + + for fileno in rlist: + line = streams_by_fd[fileno].readline() + if line: + self._output_stream.write(line) + else: + del streams_by_fd[fileno] + if fileno == primary_fd: + primary_fd = None + + # Flush the streams by executing nonblocking reads from the input file + # descriptors until no more data is available, or all the streams are + # closed. + while streams_by_fd: + rlist, _, _ = select.select(streams_by_fd, [], [], 0) + + if not rlist: + break + + for fileno in rlist: + line = streams_by_fd[fileno].readline() + if line: + self._output_stream.write(line) + else: + del streams_by_fd[fileno] + + +def _GetComponentUri(package_name): + return 'fuchsia-pkg://fuchsia.com/%s#meta/%s.cmx' % (package_name, + package_name) + + +class RunTestPackageArgs: + """RunTestPackage() configuration arguments structure. + + code_coverage: If set, the test package will be run via 'runtests', and the + output will be saved to /tmp folder on the device. + system_logging: If set, connects a system log reader to the target. + test_realm_label: Specifies the realm name that run-test-component should use. + This must be specified if a filter file is to be set, or a results summary + file fetched after the test suite has run. + use_run_test_component: If True then the test package will be run hermetically + via 'run-test-component', rather than using 'run'. + """ + + def __init__(self): + self.code_coverage = False + self.system_logging = False + self.test_realm_label = None + self.use_run_test_component = False + + @staticmethod + def FromCommonArgs(args): + run_test_package_args = RunTestPackageArgs() + run_test_package_args.code_coverage = args.code_coverage + run_test_package_args.system_logging = args.include_system_logs + return run_test_package_args + + +def _DrainStreamToStdout(stream, quit_event): + """Outputs the contents of |stream| until |quit_event| is set.""" + + while not quit_event.is_set(): + rlist, _, _ = select.select([stream], [], [], 0.1) + if rlist: + line = rlist[0].readline() + if not line: + return + print(line.rstrip()) + + +def RunTestPackage(output_dir, target, package_paths, package_name, + package_args, args): + """Installs the Fuchsia package at |package_path| on the target, + executes it with |package_args|, and symbolizes its output. + + output_dir: The path containing the build output files. + target: The deployment Target object that will run the package. + package_paths: The paths to the .far packages to be installed. + package_name: The name of the primary package to run. + package_args: The arguments which will be passed to the Fuchsia process. + args: RunTestPackageArgs instance configuring how the package will be run. + + Returns the exit code of the remote package process.""" + + system_logger = (_AttachKernelLogReader(target) + if args.system_logging else None) + try: + if system_logger: + # Spin up a thread to asynchronously dump the system log to stdout + # for easier diagnoses of early, pre-execution failures. + log_output_quit_event = multiprocessing.Event() + log_output_thread = threading.Thread(target=lambda: _DrainStreamToStdout( + system_logger.stdout, log_output_quit_event)) + log_output_thread.daemon = True + log_output_thread.start() + + with target.GetPkgRepo(): + target.InstallPackage(package_paths) + + if system_logger: + log_output_quit_event.set() + log_output_thread.join(timeout=_JOIN_TIMEOUT_SECS) + + logging.info('Running application.') + + # TODO(crbug.com/1156768): Deprecate runtests. + if args.code_coverage: + # runtests requires specifying an output directory and a double dash + # before the argument list. + command = ['runtests', '-o', '/tmp', _GetComponentUri(package_name)] + if args.test_realm_label: + command += ['--realm-label', args.test_realm_label] + command += ['--'] + elif args.use_run_test_component: + command = ['run-test-component'] + if args.test_realm_label: + command += ['--realm-label=%s' % args.test_realm_label] + command.append(_GetComponentUri(package_name)) + else: + command = ['run', _GetComponentUri(package_name)] + + command.extend(package_args) + + process = target.RunCommandPiped(command, + stdin=open(os.devnull, 'r'), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + if system_logger: + output_stream = MergedInputStream( + [process.stdout, system_logger.stdout]).Start() + else: + output_stream = process.stdout + + # Run the log data through the symbolizer process. + output_stream = SymbolizerFilter(output_stream, + BuildIdsPaths(package_paths)) + + for next_line in output_stream: + # TODO(crbug/1198733): Switch to having stream encode to utf-8 directly + # once we drop Python 2 support. + print(next_line.encode('utf-8').rstrip()) + + process.wait() + if process.returncode == 0: + logging.info('Process exited normally with status code 0.') + else: + # The test runner returns an error status code if *any* tests fail, + # so we should proceed anyway. + logging.warning('Process exited with status code %d.' % + process.returncode) + + finally: + if system_logger: + logging.info('Terminating kernel log reader.') + log_output_quit_event.set() + log_output_thread.join() + system_logger.kill() + + return process.returncode diff --git a/third_party/libwebrtc/build/fuchsia/runner_exceptions.py b/third_party/libwebrtc/build/fuchsia/runner_exceptions.py new file mode 100644 index 0000000000..03f872e453 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/runner_exceptions.py @@ -0,0 +1,78 @@ +# Copyright 2020 The Chromium 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 exceptions to return codes and prints error messages. + +This makes it easier to query build tables for particular error types as +exit codes are visible to queries while exception stack traces are not.""" + +import errno +import fcntl +import logging +import os +import subprocess +import sys +import traceback + +from target import FuchsiaTargetException + +def _PrintException(value, trace): + """Prints stack trace and error message for the current exception.""" + + traceback.print_tb(trace) + print(str(value)) + + +def IsStdoutBlocking(): + """Returns True if sys.stdout is blocking or False if non-blocking. + + sys.stdout should always be blocking. Non-blocking is associated with + intermittent IOErrors (crbug.com/1080858). + """ + + nonblocking = fcntl.fcntl(sys.stdout, fcntl.F_GETFL) & os.O_NONBLOCK + return not nonblocking + + +def HandleExceptionAndReturnExitCode(): + """Maps the current exception to a return code and prints error messages. + + Mapped exception types are assigned blocks of 8 return codes starting at 64. + The choice of 64 as the starting code is based on the Advanced Bash-Scripting + Guide (http://tldp.org/LDP/abs/html/exitcodes.html). + + A generic exception is mapped to the start of the block. More specific + exceptions are mapped to numbers inside the block. For example, a + FuchsiaTargetException is mapped to return code 64, unless it involves SSH + in which case it is mapped to return code 65. + + Exceptions not specifically mapped go to return code 1. + + Returns the mapped return code.""" + + (type, value, trace) = sys.exc_info() + _PrintException(value, trace) + + if type is FuchsiaTargetException: + if 'ssh' in str(value).lower(): + print('Error: FuchsiaTargetException: SSH to Fuchsia target failed.') + return 65 + return 64 + elif type is IOError: + if value.errno == errno.EAGAIN: + logging.info('Python print to sys.stdout probably failed') + if not IsStdoutBlocking(): + logging.warn('sys.stdout is non-blocking') + return 73 + return 72 + elif type is subprocess.CalledProcessError: + if os.path.basename(value.cmd[0]) == 'scp': + print('Error: scp operation failed - %s' % str(value)) + return 81 + if os.path.basename(value.cmd[0]) == 'qemu-img': + print('Error: qemu-img fuchsia image generation failed.') + return 82 + return 80 + else: + return 1 diff --git a/third_party/libwebrtc/build/fuchsia/runner_logs.py b/third_party/libwebrtc/build/fuchsia/runner_logs.py new file mode 100644 index 0000000000..20ab6b227d --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/runner_logs.py @@ -0,0 +1,96 @@ +# Copyright 2020 The Chromium 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 and manages test runner log file objects. + +Provides a context manager object for use in a with statement +and a module level FileStreamFor function for use by clients. +""" + +import collections +import multiprocessing +import os + +from symbolizer import RunSymbolizer + +SYMBOLIZED_SUFFIX = '.symbolized' + +_RunnerLogEntry = collections.namedtuple( + '_RunnerLogEntry', ['name', 'log_file', 'path', 'symbolize']) + +# Module singleton variable. +_instance = None + + +class RunnerLogManager(object): + """ Runner logs object for use in a with statement.""" + + def __init__(self, log_dir, build_ids_files): + global _instance + if _instance: + raise Exception('Only one RunnerLogManager can be instantiated') + + self._log_dir = log_dir + self._build_ids_files = build_ids_files + self._runner_logs = [] + + if self._log_dir and not os.path.isdir(self._log_dir): + os.makedirs(self._log_dir) + + _instance = self + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pool = multiprocessing.Pool(4) + for log_entry in self._runner_logs: + pool.apply_async(_FinalizeLog, (log_entry, self._build_ids_files)) + pool.close() + pool.join() + _instance = None + + + def _FileStreamFor(self, name, symbolize): + if any(elem.name == name for elem in self._runner_logs): + raise Exception('RunnerLogManager can only open "%s" once' % name) + + path = os.path.join(self._log_dir, name) if self._log_dir else os.devnull + log_file = open(path, 'w') + + self._runner_logs.append(_RunnerLogEntry(name, log_file, path, symbolize)) + + return log_file + + +def _FinalizeLog(log_entry, build_ids_files): + log_entry.log_file.close() + + if log_entry.symbolize: + input_file = open(log_entry.path, 'r') + output_file = open(log_entry.path + SYMBOLIZED_SUFFIX, 'w') + proc = RunSymbolizer(input_file, output_file, build_ids_files) + proc.wait() + output_file.close() + input_file.close() + + +def IsEnabled(): + """Returns True if the RunnerLogManager has been created, or False if not.""" + + return _instance is not None and _instance._log_dir is not None + + +def FileStreamFor(name, symbolize=False): + """Opens a test runner file stream in the test runner log directory. + + If no test runner log directory is specified, output is discarded. + + name: log file name + symbolize: if True, make a symbolized copy of the log after closing it. + + Returns an opened log file object.""" + + return _instance._FileStreamFor(name, symbolize) if IsEnabled() else open( + os.devnull, 'w') diff --git a/third_party/libwebrtc/build/fuchsia/sdk-bucket.txt b/third_party/libwebrtc/build/fuchsia/sdk-bucket.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/fuchsia/sdk-hash-files.list b/third_party/libwebrtc/build/fuchsia/sdk-hash-files.list new file mode 100644 index 0000000000..6f37bcd9f7 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/sdk-hash-files.list @@ -0,0 +1 @@ +{platform}.sdk.sha1 diff --git a/third_party/libwebrtc/build/fuchsia/start_emulator.py b/third_party/libwebrtc/build/fuchsia/start_emulator.py new file mode 100755 index 0000000000..c7edead3fe --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/start_emulator.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Starts up a long running emulator for unit testing and developer use.""" + +import argparse +import common +import common_args +import logging +import os +import time +import subprocess + +from fvdl_target import FvdlTarget + + +def main(): + parser = argparse.ArgumentParser( + description='Launches a long-running emulator that can ' + 'be re-used for multiple test runs.') + AddLongRunningArgs(parser) + args = parser.parse_args() + args.out_dir = None + args.device = 'fvdl' + common_args.ConfigureLogging(args) + with common_args.GetDeploymentTargetForArgs(args) as fvdl_target: + if fvdl_target._with_network: + logging.info('If you haven\'t set up tuntap, you may be prompted ' + 'for your sudo password to set up tuntap.') + fvdl_target.Start() + logging.info('Emulator successfully started up! If you are running ' + 'multiple fuchsia devices, specify the port the ip address ' + 'via the --host flag.') + if fvdl_target._with_network: + logging.info('You can now use the "-d" flag when running ' + 'Chrome Fuchsia tests to target this emulator.') + while fvdl_target._IsEmuStillRunning(): + time.sleep(10) + pass + + +def AddLongRunningArgs(arg_parser): + arg_parser.add_argument('-v', + '--verbose', + default=False, + action='store_true', + help='Enable debug-level logging.') + fvdl_args = arg_parser.add_argument_group('FVDL arguments') + fvdl_args.add_argument('--target-cpu', + default=common_args.GetHostArchFromPlatform(), + help='Set target_cpu for the emulator. Defaults ' + 'to the same architecture as host cpu.') + fvdl_args.add_argument('--system-log-file', + help='File to write system logs to. Specify ' + '\'-\' to log to stdout.') + fvdl_args.add_argument('--allow-no-kvm', + action='store_false', + dest='require_kvm', + default=True, + help='Disables KVM acceleration for the emulator.') + fvdl_args.add_argument('--enable-graphics', + action='store_true', + default=False, + help='Start FVDL with graphics instead of '\ + 'headless.') + fvdl_args.add_argument('--hardware-gpu', + action='store_true', + default=False, + help='Use local GPU hardware instead of '\ + 'Swiftshader.') + fvdl_args.add_argument('--without-network', + action='store_false', + dest='with_network', + default=True, + help='Run emulator without emulated nic via tun/tap.') + fvdl_args.add_argument('--ram-size-mb', + type=int, + default=8192, + help='Set the ram size amount for the emulator.') + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/fuchsia/symbolizer.py b/third_party/libwebrtc/build/fuchsia/symbolizer.py new file mode 100644 index 0000000000..8469d11046 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/symbolizer.py @@ -0,0 +1,70 @@ +# Copyright 2018 The Chromium 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 subprocess + +from common import SDK_ROOT +from common import GetHostArchFromPlatform +from common import GetHostToolPathFromPlatform + + +def BuildIdsPaths(package_paths): + """Generates build ids paths for symbolizer processes.""" + + build_ids_paths = map( + lambda package_path: os.path.join( + os.path.dirname(package_path), 'ids.txt'), + package_paths) + return build_ids_paths + + +def RunSymbolizer(input_file, output_file, build_ids_files): + """Starts a symbolizer process. + + input_file: Input file to be symbolized. + output_file: Output file for symbolizer stdout and stderr. + build_ids_file: Path to the ids.txt file which maps build IDs to + unstripped binaries on the filesystem. + Returns a Popen object for the started process.""" + + symbolizer = GetHostToolPathFromPlatform('symbolizer') + symbolizer_cmd = [ + symbolizer, '--build-id-dir', + os.path.join(SDK_ROOT, '.build-id') + ] + for build_ids_file in build_ids_files: + symbolizer_cmd.extend(['--ids-txt', build_ids_file]) + + logging.info('Running "%s".' % ' '.join(symbolizer_cmd)) + return subprocess.Popen(symbolizer_cmd, stdin=input_file, stdout=output_file, + stderr=subprocess.STDOUT, close_fds=True) + + +def SymbolizerFilter(input_file, build_ids_files): + """Symbolizes an output stream from a process. + + input_file: Input file to be symbolized. + build_ids_file: Path to the ids.txt file which maps build IDs to + unstripped binaries on the filesystem. + Returns a generator that yields symbolized process output.""" + + symbolizer_proc = RunSymbolizer(input_file, subprocess.PIPE, build_ids_files) + + while True: + # TODO(chonggu): Switch to encoding='utf-8' once we drop Python 2 + # support. + line = symbolizer_proc.stdout.readline().decode('utf-8') + if not line: + break + + # Skip spam emitted by the symbolizer that obscures the symbolized output. + # TODO(https://crbug.com/1069446): Fix the symbolizer and remove this. + if '[[[ELF ' in line: + continue + + yield line + + symbolizer_proc.wait() diff --git a/third_party/libwebrtc/build/fuchsia/target.py b/third_party/libwebrtc/build/fuchsia/target.py new file mode 100644 index 0000000000..7a8628266a --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/target.py @@ -0,0 +1,336 @@ +# Copyright 2018 The Chromium 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 +import os +import subprocess +import time + +import common +import remote_cmd +import runner_logs + + +_SHUTDOWN_CMD = ['dm', 'poweroff'] +_ATTACH_RETRY_INTERVAL = 1 +_ATTACH_RETRY_SECONDS = 120 + +# Amount of time to wait for a complete package installation, as a +# mitigation against hangs due to pkg/network-related failures. +_INSTALL_TIMEOUT_SECS = 10 * 60 + + +def _GetPackageUri(package_name): + """Returns the URI for the specified package name.""" + return 'fuchsia-pkg://fuchsia.com/%s' % (package_name) + + +def _GetPackageInfo(package_path): + """Returns a tuple with the name and version of a package.""" + + # Query the metadata file which resides next to the package file. + package_info = json.load( + open(os.path.join(os.path.dirname(package_path), 'package'))) + return package_info['name'], package_info['version'], + + +class _MapIsolatedPathsForPackage: + """Callable object which remaps /data and /tmp paths to their component- + specific locations, based on the package name and test realm path.""" + + def __init__(self, package_name, package_version, realms): + realms_path_fragment = '/r/'.join(['r/sys'] + realms) + package_sub_path = '{2}/fuchsia.com:{0}:{1}#meta:{0}.cmx/'.format( + package_name, package_version, realms_path_fragment) + self.isolated_format = '{0}' + package_sub_path + '{1}' + + def __call__(self, path): + for isolated_directory in ['/data/' , '/tmp/']: + if (path+'/').startswith(isolated_directory): + return self.isolated_format.format(isolated_directory, + path[len(isolated_directory):]) + return path + + +class FuchsiaTargetException(Exception): + def __init__(self, message): + super(FuchsiaTargetException, self).__init__(message) + + +class Target(object): + """Base class representing a Fuchsia deployment target.""" + + def __init__(self, out_dir, target_cpu): + self._out_dir = out_dir + self._started = False + self._dry_run = False + self._target_cpu = target_cpu + self._command_runner = None + self._ffx_path = os.path.join(common.SDK_ROOT, 'tools', + common.GetHostArchFromPlatform(), 'ffx') + + @staticmethod + def CreateFromArgs(args): + raise NotImplementedError() + + @staticmethod + def RegisterArgs(arg_parser): + pass + + # Functions used by the Python context manager for teardown. + def __enter__(self): + return self + def __exit__(self, exc_type, exc_val, exc_tb): + return + + def Start(self): + """Handles the instantiation and connection process for the Fuchsia + target instance.""" + + def IsStarted(self): + """Returns True if the Fuchsia target instance is ready to accept + commands.""" + return self._started + + def IsNewInstance(self): + """Returns True if the connected target instance is newly provisioned.""" + return True + + def GetCommandRunner(self): + """Returns CommandRunner that can be used to execute commands on the + target. Most clients should prefer RunCommandPiped() and RunCommand().""" + self._AssertIsStarted() + + if self._command_runner is None: + host, port = self._GetEndpoint() + self._command_runner = \ + remote_cmd.CommandRunner(self._GetSshConfigPath(), host, port) + + return self._command_runner + + def RunCommandPiped(self, command, **kwargs): + """Starts a remote command and immediately returns a Popen object for the + command. The caller may interact with the streams, inspect the status code, + wait on command termination, etc. + + command: A list of strings representing the command and arguments. + kwargs: A dictionary of parameters to be passed to subprocess.Popen(). + The parameters can be used to override stdin and stdout, for + example. + + Returns: a Popen object. + + Note: method does not block. + """ + logging.debug('running (non-blocking) \'%s\'.', ' '.join(command)) + return self.GetCommandRunner().RunCommandPiped(command, **kwargs) + + def RunCommand(self, command, silent=False, timeout_secs=None): + """Executes a remote command and waits for it to finish executing. + + Returns the exit code of the command. + """ + logging.debug('running \'%s\'.', ' '.join(command)) + return self.GetCommandRunner().RunCommand(command, silent, + timeout_secs=timeout_secs) + + def EnsureIsolatedPathsExist(self, for_package, for_realms): + """Ensures that the package's isolated /data and /tmp exist.""" + for isolated_directory in ['/data', '/tmp']: + self.RunCommand([ + 'mkdir', '-p', + _MapIsolatedPathsForPackage(for_package, 0, + for_realms)(isolated_directory) + ]) + + def PutFile(self, + source, + dest, + recursive=False, + for_package=None, + for_realms=()): + """Copies a file from the local filesystem to the target filesystem. + + source: The path of the file being copied. + dest: The path on the remote filesystem which will be copied to. + recursive: If true, performs a recursive copy. + for_package: If specified, isolated paths in the |dest| are mapped to their + obsolute paths for the package, on the target. This currently + affects the /data and /tmp directories. + for_realms: If specified, identifies the sub-realm of 'sys' under which + isolated paths (see |for_package|) are stored. + """ + assert type(source) is str + self.PutFiles([source], dest, recursive, for_package, for_realms) + + def PutFiles(self, + sources, + dest, + recursive=False, + for_package=None, + for_realms=()): + """Copies files from the local filesystem to the target filesystem. + + sources: List of local file paths to copy from, or a single path. + dest: The path on the remote filesystem which will be copied to. + recursive: If true, performs a recursive copy. + for_package: If specified, /data in the |dest| is mapped to the package's + isolated /data location. + for_realms: If specified, identifies the sub-realm of 'sys' under which + isolated paths (see |for_package|) are stored. + """ + assert type(sources) is tuple or type(sources) is list + if for_package: + self.EnsureIsolatedPathsExist(for_package, for_realms) + dest = _MapIsolatedPathsForPackage(for_package, 0, for_realms)(dest) + logging.debug('copy local:%s => remote:%s', sources, dest) + self.GetCommandRunner().RunScp(sources, dest, remote_cmd.COPY_TO_TARGET, + recursive) + + def GetFile(self, + source, + dest, + for_package=None, + for_realms=(), + recursive=False): + """Copies a file from the target filesystem to the local filesystem. + + source: The path of the file being copied. + dest: The path on the local filesystem which will be copied to. + for_package: If specified, /data in paths in |sources| is mapped to the + package's isolated /data location. + for_realms: If specified, identifies the sub-realm of 'sys' under which + isolated paths (see |for_package|) are stored. + recursive: If true, performs a recursive copy. + """ + assert type(source) is str + self.GetFiles([source], dest, for_package, for_realms, recursive) + + def GetFiles(self, + sources, + dest, + for_package=None, + for_realms=(), + recursive=False): + """Copies files from the target filesystem to the local filesystem. + + sources: List of remote file paths to copy. + dest: The path on the local filesystem which will be copied to. + for_package: If specified, /data in paths in |sources| is mapped to the + package's isolated /data location. + for_realms: If specified, identifies the sub-realm of 'sys' under which + isolated paths (see |for_package|) are stored. + recursive: If true, performs a recursive copy. + """ + assert type(sources) is tuple or type(sources) is list + self._AssertIsStarted() + if for_package: + sources = map(_MapIsolatedPathsForPackage(for_package, 0, for_realms), + sources) + logging.debug('copy remote:%s => local:%s', sources, dest) + return self.GetCommandRunner().RunScp(sources, dest, + remote_cmd.COPY_FROM_TARGET, + recursive) + + def _GetEndpoint(self): + """Returns a (host, port) tuple for the SSH connection to the target.""" + raise NotImplementedError() + + def _GetTargetSdkArch(self): + """Returns the Fuchsia SDK architecture name for the target CPU.""" + if self._target_cpu == 'arm64' or self._target_cpu == 'x64': + return self._target_cpu + raise FuchsiaTargetException('Unknown target_cpu:' + self._target_cpu) + + def _AssertIsStarted(self): + assert self.IsStarted() + + def _WaitUntilReady(self): + logging.info('Connecting to Fuchsia using SSH.') + + host, port = self._GetEndpoint() + end_time = time.time() + _ATTACH_RETRY_SECONDS + ssh_diagnostic_log = runner_logs.FileStreamFor('ssh_diagnostic_log') + while time.time() < end_time: + runner = remote_cmd.CommandRunner(self._GetSshConfigPath(), host, port) + ssh_proc = runner.RunCommandPiped(['true'], + ssh_args=['-v'], + stdout=ssh_diagnostic_log, + stderr=subprocess.STDOUT) + if ssh_proc.wait() == 0: + logging.info('Connected!') + self._started = True + return True + time.sleep(_ATTACH_RETRY_INTERVAL) + + logging.error('Timeout limit reached.') + + raise FuchsiaTargetException('Couldn\'t connect using SSH.') + + def _GetSshConfigPath(self, path): + raise NotImplementedError() + + def GetPkgRepo(self): + """Returns an PkgRepo instance which serves packages for this Target. + Callers should typically call GetPkgRepo() in a |with| statement, and + install and execute commands inside the |with| block, so that the returned + PkgRepo can teardown correctly, if necessary. + """ + raise NotImplementedError() + + def InstallPackage(self, package_paths): + """Installs a package and it's dependencies on the device. If the package is + already installed then it will be updated to the new version. + + package_paths: Paths to the .far files to install. + """ + with self.GetPkgRepo() as pkg_repo: + # Publish all packages to the serving TUF repository under |tuf_root|. + for package_path in package_paths: + pkg_repo.PublishPackage(package_path) + + # Resolve all packages, to have them pulled into the device/VM cache. + for package_path in package_paths: + package_name, package_version = _GetPackageInfo(package_path) + logging.info('Resolving %s into cache.', package_name) + return_code = self.RunCommand( + ['pkgctl', 'resolve', + _GetPackageUri(package_name), '>/dev/null'], + timeout_secs=_INSTALL_TIMEOUT_SECS) + if return_code != 0: + raise Exception( + 'Error {} while resolving {}.'.format(return_code, package_name)) + + # Verify that the newly resolved versions of packages are reported. + for package_path in package_paths: + # Use pkgctl get-hash to determine which version will be resolved. + package_name, package_version = _GetPackageInfo(package_path) + pkgctl = self.RunCommandPiped( + ['pkgctl', 'get-hash', + _GetPackageUri(package_name)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + pkgctl_out, pkgctl_err = pkgctl.communicate() + + # Read the expected version from the meta.far Merkel hash file alongside + # the package's FAR. + meta_far_path = os.path.join(os.path.dirname(package_path), 'meta.far') + meta_far_merkel = subprocess.check_output( + [common.GetHostToolPathFromPlatform('merkleroot'), + meta_far_path]).split()[0] + if pkgctl_out != meta_far_merkel: + raise Exception('Hash mismatch for %s after resolve (%s vs %s).' % + (package_name, pkgctl_out, meta_far_merkel)) + + def RunFFXCommand(self, ffx_args, **kwargs): + """Automatically gets the FFX path and runs FFX based on the + arguments provided. Extra args can be added to be used with Popen. + + ffx_args: The arguments for a ffx command. + kwargs: A dictionary of parameters to be passed to subprocess.Popen(). + + Returns a Popen object for the command.""" + command = [self._ffx_path] + ffx_args + return subprocess.Popen(command, **kwargs) diff --git a/third_party/libwebrtc/build/fuchsia/test_runner.py b/third_party/libwebrtc/build/fuchsia/test_runner.py new file mode 100755 index 0000000000..2ccbec9593 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/test_runner.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Deploys and runs a test package on a Fuchsia target.""" + +import argparse +import os +import runner_logs +import sys +import tempfile + +from common_args import AddCommonArgs, AddTargetSpecificArgs, \ + ConfigureLogging, GetDeploymentTargetForArgs +from net_test_server import SetupTestServer +from run_test_package import RunTestPackage, RunTestPackageArgs, SystemLogReader +from runner_exceptions import HandleExceptionAndReturnExitCode +from runner_logs import RunnerLogManager +from symbolizer import BuildIdsPaths + +DEFAULT_TEST_SERVER_CONCURRENCY = 4 + +TEST_DATA_DIR = '/tmp' +TEST_FILTER_PATH = TEST_DATA_DIR + '/test_filter.txt' +TEST_LLVM_PROFILE_PATH = TEST_DATA_DIR + '/llvm-profile' +TEST_PERF_RESULT_PATH = TEST_DATA_DIR + '/test_perf_summary.json' +TEST_RESULT_PATH = TEST_DATA_DIR + '/test_summary.json' + +TEST_REALM_NAME = 'chromium_tests' + + +def AddTestExecutionArgs(arg_parser): + test_args = arg_parser.add_argument_group('testing', + 'Test execution arguments') + test_args.add_argument('--gtest_filter', + help='GTest filter to use in place of any default.') + test_args.add_argument( + '--gtest_repeat', + help='GTest repeat value to use. This also disables the ' + 'test launcher timeout.') + test_args.add_argument( + '--test-launcher-retry-limit', + help='Number of times that test suite will retry failing ' + 'tests. This is multiplicative with --gtest_repeat.') + test_args.add_argument('--test-launcher-shard-index', + type=int, + default=os.environ.get('GTEST_SHARD_INDEX'), + help='Index of this instance amongst swarming shards.') + test_args.add_argument('--test-launcher-total-shards', + type=int, + default=os.environ.get('GTEST_TOTAL_SHARDS'), + help='Total number of swarming shards of this suite.') + test_args.add_argument('--gtest_break_on_failure', + action='store_true', + default=False, + help='Should GTest break on failure; useful with ' + '--gtest_repeat.') + test_args.add_argument('--single-process-tests', + action='store_true', + default=False, + help='Runs the tests and the launcher in the same ' + 'process. Useful for debugging.') + test_args.add_argument('--test-launcher-batch-limit', + type=int, + help='Sets the limit of test batch to run in a single ' + 'process.') + # --test-launcher-filter-file is specified relative to --out-dir, + # so specifying type=os.path.* will break it. + test_args.add_argument( + '--test-launcher-filter-file', + default=None, + help='Filter file(s) passed to target test process. Use ";" to separate ' + 'multiple filter files ') + test_args.add_argument('--test-launcher-jobs', + type=int, + help='Sets the number of parallel test jobs.') + test_args.add_argument('--test-launcher-summary-output', + help='Where the test launcher will output its json.') + test_args.add_argument('--enable-test-server', + action='store_true', + default=False, + help='Enable Chrome test server spawner.') + test_args.add_argument( + '--test-launcher-bot-mode', + action='store_true', + default=False, + help='Informs the TestLauncher to that it should enable ' + 'special allowances for running on a test bot.') + test_args.add_argument('--isolated-script-test-output', + help='If present, store test results on this path.') + test_args.add_argument( + '--isolated-script-test-perf-output', + help='If present, store chartjson results on this path.') + test_args.add_argument('--use-run-test-component', + default=False, + action='store_true', + help='Run the test package hermetically using ' + 'run-test-component, rather than run.') + test_args.add_argument( + '--code-coverage', + default=False, + action='store_true', + help='Gather code coverage information and place it in ' + 'the output directory.') + test_args.add_argument('--code-coverage-dir', + default=os.getcwd(), + help='Directory to place code coverage information. ' + 'Only relevant when --code-coverage set to true. ' + 'Defaults to current directory.') + test_args.add_argument('--child-arg', + action='append', + help='Arguments for the test process.') + test_args.add_argument('child_args', + nargs='*', + help='Arguments for the test process.') + + +def main(): + parser = argparse.ArgumentParser() + AddTestExecutionArgs(parser) + AddCommonArgs(parser) + AddTargetSpecificArgs(parser) + args = parser.parse_args() + + # Flag out_dir is required for tests launched with this script. + if not args.out_dir: + raise ValueError("out-dir must be specified.") + + # Code coverage uses runtests, which calls run_test_component. + if args.code_coverage: + args.use_run_test_component = True + + ConfigureLogging(args) + + child_args = [] + if args.test_launcher_shard_index != None: + child_args.append( + '--test-launcher-shard-index=%d' % args.test_launcher_shard_index) + if args.test_launcher_total_shards != None: + child_args.append( + '--test-launcher-total-shards=%d' % args.test_launcher_total_shards) + if args.single_process_tests: + child_args.append('--single-process-tests') + if args.test_launcher_bot_mode: + child_args.append('--test-launcher-bot-mode') + if args.test_launcher_batch_limit: + child_args.append('--test-launcher-batch-limit=%d' % + args.test_launcher_batch_limit) + + # Only set --test-launcher-jobs if the caller specifies it, in general. + # If the caller enables the test-server then we need to launch the right + # number of instances to match the maximum number of parallel test jobs, so + # in that case we set --test-launcher-jobs based on the number of CPU cores + # specified for the emulator to use. + test_concurrency = None + if args.test_launcher_jobs: + test_concurrency = args.test_launcher_jobs + elif args.enable_test_server: + if args.device == 'device': + test_concurrency = DEFAULT_TEST_SERVER_CONCURRENCY + else: + test_concurrency = args.cpu_cores + if test_concurrency: + child_args.append('--test-launcher-jobs=%d' % test_concurrency) + + if args.gtest_filter: + child_args.append('--gtest_filter=' + args.gtest_filter) + if args.gtest_repeat: + child_args.append('--gtest_repeat=' + args.gtest_repeat) + child_args.append('--test-launcher-timeout=-1') + if args.test_launcher_retry_limit: + child_args.append( + '--test-launcher-retry-limit=' + args.test_launcher_retry_limit) + if args.gtest_break_on_failure: + child_args.append('--gtest_break_on_failure') + if args.test_launcher_summary_output: + child_args.append('--test-launcher-summary-output=' + TEST_RESULT_PATH) + if args.isolated_script_test_output: + child_args.append('--isolated-script-test-output=' + TEST_RESULT_PATH) + if args.isolated_script_test_perf_output: + child_args.append('--isolated-script-test-perf-output=' + + TEST_PERF_RESULT_PATH) + + if args.child_arg: + child_args.extend(args.child_arg) + if args.child_args: + child_args.extend(args.child_args) + + test_realms = [] + if args.use_run_test_component: + test_realms = [TEST_REALM_NAME] + + try: + with GetDeploymentTargetForArgs(args) as target, \ + SystemLogReader() as system_logger, \ + RunnerLogManager(args.runner_logs_dir, BuildIdsPaths(args.package)): + target.Start() + + if args.system_log_file and args.system_log_file != '-': + system_logger.Start(target, args.package, args.system_log_file) + + if args.test_launcher_filter_file: + test_launcher_filter_files = args.test_launcher_filter_file.split(';') + with tempfile.NamedTemporaryFile('a+b') as combined_filter_file: + for filter_file in test_launcher_filter_files: + with open(filter_file, 'r') as f: + combined_filter_file.write(f.read()) + combined_filter_file.seek(0) + target.PutFile(combined_filter_file.name, + TEST_FILTER_PATH, + for_package=args.package_name, + for_realms=test_realms) + child_args.append('--test-launcher-filter-file=' + TEST_FILTER_PATH) + + test_server = None + if args.enable_test_server: + assert test_concurrency + test_server = SetupTestServer(target, test_concurrency, + args.package_name, test_realms) + + run_package_args = RunTestPackageArgs.FromCommonArgs(args) + if args.use_run_test_component: + run_package_args.test_realm_label = TEST_REALM_NAME + run_package_args.use_run_test_component = True + returncode = RunTestPackage(args.out_dir, target, args.package, + args.package_name, child_args, + run_package_args) + + if test_server: + test_server.Stop() + + if args.code_coverage: + # Copy all the files in the profile directory. /* is used instead + # of recursively copying due to permission issues for the latter. + target.GetFile(TEST_LLVM_PROFILE_PATH + '/*', args.code_coverage_dir) + + if args.test_launcher_summary_output: + target.GetFile(TEST_RESULT_PATH, + args.test_launcher_summary_output, + for_package=args.package_name, + for_realms=test_realms) + + if args.isolated_script_test_output: + target.GetFile(TEST_RESULT_PATH, + args.isolated_script_test_output, + for_package=args.package_name, + for_realms=test_realms) + + if args.isolated_script_test_perf_output: + target.GetFile(TEST_PERF_RESULT_PATH, + args.isolated_script_test_perf_output, + for_package=args.package_name, + for_realms=test_realms) + + return returncode + + except: + return HandleExceptionAndReturnExitCode() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fuchsia/update_images.py b/third_party/libwebrtc/build/fuchsia/update_images.py new file mode 100755 index 0000000000..79b8e49d86 --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/update_images.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Updates the Fuchsia SDK to the given revision. Should be used in a 'hooks_os' +entry so that it only runs when .gclient's target_os includes 'fuchsia'.""" + +import argparse +import itertools +import logging +import os +import re +import shutil +import subprocess +import sys +import tarfile + +from common import GetHostOsFromPlatform, GetHostArchFromPlatform, \ + DIR_SOURCE_ROOT, IMAGES_ROOT +from update_sdk import DownloadAndUnpackFromCloudStorage, \ + GetOverrideCloudStorageBucket, GetSdkHash, \ + MakeCleanDirectory, SDK_SIGNATURE_FILE + + +def GetSdkSignature(sdk_hash, boot_images): + return 'gn:{sdk_hash}:{boot_images}:'.format(sdk_hash=sdk_hash, + boot_images=boot_images) + + +def GetAllImages(boot_image_names): + if not boot_image_names: + return + + all_device_types = ['generic', 'qemu'] + all_archs = ['x64', 'arm64'] + + images_to_download = set() + + for boot_image in boot_image_names.split(','): + components = boot_image.split('.') + if len(components) != 2: + continue + + device_type, arch = components + device_images = all_device_types if device_type == '*' else [device_type] + arch_images = all_archs if arch == '*' else [arch] + images_to_download.update(itertools.product(device_images, arch_images)) + return images_to_download + + +def DownloadSdkBootImages(bucket, sdk_hash, boot_image_names, image_root_dir): + images_to_download = GetAllImages(boot_image_names) + for image_to_download in images_to_download: + device_type = image_to_download[0] + arch = image_to_download[1] + image_output_dir = os.path.join(image_root_dir, arch, device_type) + if os.path.exists(image_output_dir): + continue + + logging.info('Downloading Fuchsia boot images for %s.%s...' % + (device_type, arch)) + if bucket == 'fuchsia-sdk': + images_tarball_url = 'gs://{bucket}/development/{sdk_hash}/images/'\ + '{device_type}.{arch}.tgz'.format( + bucket=bucket, sdk_hash=sdk_hash, + device_type=device_type, arch=arch) + else: + images_tarball_url = 'gs://{bucket}/development/{sdk_hash}/images/'\ + '{device_type}-{arch}.tgz'.format( + bucket=bucket, sdk_hash=sdk_hash, + device_type=device_type, arch=arch) + DownloadAndUnpackFromCloudStorage(images_tarball_url, image_output_dir) + + +def GetNewSignature(sdk_hash, boot_images): + return GetSdkSignature(sdk_hash, boot_images) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', + '-v', + action='store_true', + help='Enable debug-level logging.') + parser.add_argument( + '--boot-images', + type=str, + required=True, + help='List of boot images to download, represented as a comma separated ' + 'list. Wildcards are allowed. ') + parser.add_argument( + '--default-bucket', + type=str, + default='fuchsia', + help='The Google Cloud Storage bucket in which the Fuchsia images are ' + 'stored. Entry in sdk-bucket.txt will override this flag.') + parser.add_argument( + '--image-root-dir', + default=IMAGES_ROOT, + help='Specify the root directory of the downloaded images. Optional') + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + + # If no boot images need to be downloaded, exit. + if not args.boot_images: + return 0 + + # Check whether there's SDK support for this platform. + GetHostOsFromPlatform() + + # Use the bucket in sdk-bucket.txt if an entry exists. + # Otherwise use the default bucket. + bucket = GetOverrideCloudStorageBucket() or args.default_bucket + + sdk_hash = GetSdkHash(bucket) + if not sdk_hash: + return 1 + + signature_filename = os.path.join(args.image_root_dir, SDK_SIGNATURE_FILE) + current_signature = (open(signature_filename, 'r').read().strip() + if os.path.exists(signature_filename) else '') + new_signature = GetNewSignature(sdk_hash, args.boot_images) + if current_signature != new_signature: + logging.info('Downloading Fuchsia images %s...' % sdk_hash) + MakeCleanDirectory(args.image_root_dir) + + try: + DownloadSdkBootImages(bucket, sdk_hash, args.boot_images, + args.image_root_dir) + with open(signature_filename, 'w') as f: + f.write(new_signature) + + except subprocess.CalledProcessError as e: + logging.error(("command '%s' failed with status %d.%s"), " ".join(e.cmd), + e.returncode, " Details: " + e.output if e.output else "") + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/fuchsia/update_sdk.py b/third_party/libwebrtc/build/fuchsia/update_sdk.py new file mode 100755 index 0000000000..a1c9621fac --- /dev/null +++ b/third_party/libwebrtc/build/fuchsia/update_sdk.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Updates the Fuchsia SDK to the given revision. Should be used in a 'hooks_os' +entry so that it only runs when .gclient's target_os includes 'fuchsia'.""" + +import argparse +import logging +import os +import re +import shutil +import subprocess +import sys +import tarfile + +from common import GetHostOsFromPlatform, GetHostArchFromPlatform, \ + DIR_SOURCE_ROOT, SDK_ROOT + +sys.path.append(os.path.join(DIR_SOURCE_ROOT, 'build')) +import find_depot_tools + +SDK_SIGNATURE_FILE = '.hash' +SDK_TARBALL_PATH_TEMPLATE = ( + 'gs://{bucket}/development/{sdk_hash}/sdk/{platform}-amd64/gn.tar.gz') + + +def ReadFile(filename): + with open(os.path.join(os.path.dirname(__file__), filename), 'r') as f: + return f.read() + + +# TODO(crbug.com/1138433): Investigate whether we can deprecate +# use of sdk_bucket.txt. +def GetOverrideCloudStorageBucket(): + """Read bucket entry from sdk_bucket.txt""" + return ReadFile('sdk-bucket.txt').strip() + + +def GetSdkHash(bucket): + hashes = GetSdkHashList() + return (max(hashes, key=lambda sdk: GetSdkGeneration(bucket, sdk)) + if hashes else None) + + +def GetSdkHashList(): + """Read filename entries from sdk-hash-files.list (one per line), substitute + {platform} in each entry if present, and read from each filename.""" + platform = GetHostOsFromPlatform() + filenames = [ + line.strip() for line in ReadFile('sdk-hash-files.list').replace( + '{platform}', platform).splitlines() + ] + sdk_hashes = [ReadFile(filename).strip() for filename in filenames] + return sdk_hashes + + +def GetSdkGeneration(bucket, hash): + if not hash: + return None + + sdk_path = GetSdkTarballPath(bucket, hash) + cmd = [ + os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'), 'ls', '-L', + sdk_path + ] + logging.debug("Running '%s'", " ".join(cmd)) + sdk_details = subprocess.check_output(cmd).decode('utf-8') + m = re.search('Generation:\s*(\d*)', sdk_details) + if not m: + raise RuntimeError('Could not find SDK generation for {sdk_path}'.format( + sdk_path=sdk_path)) + return int(m.group(1)) + + +def GetSdkTarballPath(bucket, sdk_hash): + return SDK_TARBALL_PATH_TEMPLATE.format( + bucket=bucket, sdk_hash=sdk_hash, platform=GetHostOsFromPlatform()) + + +# Updates the modification timestamps of |path| and its contents to the +# current time. +def UpdateTimestampsRecursive(): + for root, dirs, files in os.walk(SDK_ROOT): + for f in files: + os.utime(os.path.join(root, f), None) + for d in dirs: + os.utime(os.path.join(root, d), None) + + +# Fetches a tarball from GCS and uncompresses it to |output_dir|. +def DownloadAndUnpackFromCloudStorage(url, output_dir): + # Pass the compressed stream directly to 'tarfile'; don't bother writing it + # to disk first. + cmd = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'), + 'cp', url, '-'] + logging.debug('Running "%s"', ' '.join(cmd)) + task = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + try: + tarfile.open(mode='r|gz', fileobj=task.stdout).extractall(path=output_dir) + except tarfile.ReadError: + task.wait() + stderr = task.stderr.read() + raise subprocess.CalledProcessError(task.returncode, cmd, + "Failed to read a tarfile from gsutil.py.{}".format( + stderr if stderr else "")) + task.wait() + if task.returncode: + raise subprocess.CalledProcessError(task.returncode, cmd, + task.stderr.read()) + + +def MakeCleanDirectory(directory_name): + if (os.path.exists(directory_name)): + shutil.rmtree(directory_name) + os.mkdir(directory_name) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', + action='store_true', + help='Enable debug-level logging.') + parser.add_argument( + '--default-bucket', + type=str, + default='fuchsia', + help='The Google Cloud Storage bucket in which the Fuchsia SDK is ' + 'stored. Entry in sdk-bucket.txt will override this flag.') + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + + # Quietly exit if there's no SDK support for this platform. + try: + GetHostOsFromPlatform() + except: + return 0 + + # Use the bucket in sdk-bucket.txt if an entry exists. + # Otherwise use the default bucket. + bucket = GetOverrideCloudStorageBucket() or args.default_bucket + + sdk_hash = GetSdkHash(bucket) + if not sdk_hash: + return 1 + + signature_filename = os.path.join(SDK_ROOT, SDK_SIGNATURE_FILE) + current_signature = (open(signature_filename, 'r').read().strip() + if os.path.exists(signature_filename) else '') + if current_signature != sdk_hash: + logging.info('Downloading GN SDK %s...' % sdk_hash) + + MakeCleanDirectory(SDK_ROOT) + DownloadAndUnpackFromCloudStorage(GetSdkTarballPath(bucket, sdk_hash), + SDK_ROOT) + + with open(signature_filename, 'w') as f: + f.write(sdk_hash) + + UpdateTimestampsRecursive() + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/gdb-add-index b/third_party/libwebrtc/build/gdb-add-index new file mode 100755 index 0000000000..73367c8350 --- /dev/null +++ b/third_party/libwebrtc/build/gdb-add-index @@ -0,0 +1,184 @@ +#!/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. + +function usage_exit { + echo "Usage: $0 [-f] [-r] [-n] ..." + echo " -f forces replacement of an existing index." + echo " -r removes the index section." + echo " -n don't extract the dependencies of each binary with lld." + echo " e.g., $0 -n out/Debug/lib.unstripped/lib*" + echo + echo " Set TOOLCHAIN_PREFIX to use a non-default set of binutils." + exit 1 +} + +# 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 [ -d "$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_this_file="${should_index}" + + local readelf_out=$(${TOOLCHAIN_PREFIX}readelf -S "$file") + if [[ $readelf_out =~ "gdb_index" ]]; then + if $remove_index; then + ${TOOLCHAIN_PREFIX}objcopy --remove-section .gdb_index "$file" + echo "Removed index from $basename." + else + echo "Skipped $basename -- already contains index." + should_index_this_file=false + fi + fi + + if $should_index_this_file; 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=false +should_index=true +should_index_deps=true +files_to_index=() +while (($# > 0)); do + case "$1" in + -h) + usage_exit + ;; + -f) + remove_index=true + ;; + -r) + remove_index=true + should_index=false + ;; + -n) + should_index_deps=false + ;; + -*) + echo "Invalid option: $1" >&2 + usage_exit + ;; + *) + if [[ ! -f "$1" ]]; then + echo "Path $1 does not exist." + exit 1 + fi + files_to_index+=("$1") + ;; + esac + shift +done + +if ((${#files_to_index[@]} == 0)); then + usage_exit +fi + +dependencies=() +if $should_index_deps; then + for file in "${files_to_index[@]}"; do + # Append the shared library dependencies of this file that + # have the same dirname. The dirname is a signal that these + # shared libraries were part of the same build as the binary. + dependencies+=( \ + $(ldd "$file" 2>/dev/null \ + | grep $(dirname "$file") \ + | sed "s/.*[ \t]\(.*\) (.*/\1/") \ + ) + done +fi +files_to_index+=("${dependencies[@]}") + +# Ensure we cleanup on on exit. +trap on_exit EXIT INT + +# We're good to go! Create temp directory for index files. +directory=$(mktemp -d) +echo "Made temp directory $directory." + +# 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/third_party/libwebrtc/build/get_landmines.py b/third_party/libwebrtc/build/get_landmines.py new file mode 100755 index 0000000000..a32ab9937c --- /dev/null +++ b/third_party/libwebrtc/build/get_landmines.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. + +""" +This file emits the list of reasons why a particular build needs to be clobbered +(or a list of 'landmines'). +""" + +from __future__ import print_function + +import sys + +import landmine_utils + + +host_os = landmine_utils.host_os + + +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. + # + # Before adding or changing a landmine consider the consequences of doing so. + # Doing so will wipe out every output directory on every Chrome developer's + # machine. This can be particularly problematic on Windows where the directory + # deletion may well fail (locked files, command prompt in the directory, + # etc.), and generated .sln and .vcxproj files will be deleted. + # + # This output directory deletion will be repeated when going back and forth + # across the change that added the landmine, adding to the cost. There are + # usually less troublesome alternatives. + + if host_os() == 'win': + print('Compile on cc_unittests fails due to symbols removed in r185063.') + if host_os() == 'linux': + print('Builders switching from make to ninja will clobber on this.') + if host_os() == 'mac': + print('Switching from bundle to unbundled dylib (issue 14743002).') + if host_os() in ('win', 'mac'): + print('Improper dependency for create_nmf.py broke in r240802, ' + 'fixed in r240860.') + if host_os() == 'win': + print('Switch to VS2015 Update 3, 14393 SDK') + print('Need to clobber everything due to an IDL change in r154579 (blink)') + print('Need to clobber everything due to gen file moves in r175513 (Blink)') + print('Clobber to get rid of obselete test plugin after r248358') + print('Clobber to rebuild GN files for V8') + print('Clobber to get rid of stale generated mojom.h files') + print('Need to clobber everything due to build_nexe change in nacl r13424') + print( + '[chromium-dev] PSA: clobber build needed for IDR_INSPECTOR_* compil...') + print('blink_resources.grd changed: crbug.com/400860') + print('ninja dependency cycle: crbug.com/408192') + print('Clobber to fix missing NaCl gyp dependencies (crbug.com/427427).') + print('Another clobber for missing NaCl gyp deps (crbug.com/427427).') + print( + 'Clobber to fix GN not picking up increased ID range (crbug.com/444902)') + print('Remove NaCl toolchains from the output dir (crbug.com/456902)') + if host_os() == 'win': + print('Clobber to delete stale generated files (crbug.com/510086)') + if host_os() == 'mac': + print('Clobber to get rid of evil libsqlite3.dylib (crbug.com/526208)') + if host_os() == 'mac': + print('Clobber to remove libsystem.dylib. See crbug.com/620075') + if host_os() == 'mac': + print('Clobber to get past mojo gen build error (crbug.com/679607)') + if host_os() == 'win': + print('Clobber Windows to fix strange PCH-not-rebuilt errors.') + print('CLobber all to fix GN breakage (crbug.com/736215)') + print('The Great Blink mv for source files (crbug.com/768828)') + if host_os() == 'linux': + print('Clobber to workaround buggy .ninja_deps cycle (crbug.com/934404)') + + +def main(): + print_landmines() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/get_symlink_targets.py b/third_party/libwebrtc/build/get_symlink_targets.py new file mode 100755 index 0000000000..3285ff1d93 --- /dev/null +++ b/third_party/libwebrtc/build/get_symlink_targets.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# Copyright (c) 2019 The Chromium 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 target paths of the given symlinks. + +Prints out each target in the order that the links were passed in. +""" + +import os +import sys + + +def main(): + for link_name in sys.argv[1:]: + if not os.path.islink(link_name): + sys.stderr.write("%s is not a link" % link_name) + return 1 + target = os.readlink(link_name) + if not os.path.isabs(target): + target = os.path.join(os.path.dirname(link_name), target) + print(os.path.realpath(target)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/gn_helpers.py b/third_party/libwebrtc/build/gn_helpers.py new file mode 100644 index 0000000000..c6e4129b21 --- /dev/null +++ b/third_party/libwebrtc/build/gn_helpers.py @@ -0,0 +1,542 @@ +# 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 integrate with GN. + +The main functions are ToGNString() and FromGNString(), to convert between +serialized GN veriables and Python variables. + +To use in an arbitrary Python file in the build: + + import os + import sys + + sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, 'build')) + import gn_helpers + +Where the sequence of parameters to join is the relative path from your source +file to the build directory. +""" + +import json +import os +import re +import sys + + +_CHROMIUM_ROOT = os.path.join(os.path.dirname(__file__), os.pardir) + +BUILD_VARS_FILENAME = 'build_vars.json' +IMPORT_RE = re.compile(r'^import\("//(\S+)"\)') + + +class GNError(Exception): + pass + + +# Computes ASCII code of an element of encoded Python 2 str / Python 3 bytes. +_Ord = ord if sys.version_info.major < 3 else lambda c: c + + +def _TranslateToGnChars(s): + for decoded_ch in s.encode('utf-8'): # str in Python 2, bytes in Python 3. + code = _Ord(decoded_ch) # int + if code in (34, 36, 92): # For '"', '$', or '\\'. + yield '\\' + chr(code) + elif 32 <= code < 127: + yield chr(code) + else: + yield '$0x%02X' % code + + +def ToGNString(value, pretty=False): + """Returns a stringified GN equivalent of a Python value. + + Args: + value: The Python value to convert. + pretty: Whether to pretty print. If true, then non-empty lists are rendered + recursively with one item per line, with indents. Otherwise lists are + rendered without new line. + Returns: + The stringified GN equivalent to |value|. + + Raises: + GNError: |value| cannot be printed to GN. + """ + + if sys.version_info.major < 3: + basestring_compat = basestring + else: + basestring_compat = str + + # Emits all output tokens without intervening whitespaces. + def GenerateTokens(v, level): + if isinstance(v, basestring_compat): + yield '"' + ''.join(_TranslateToGnChars(v)) + '"' + + elif isinstance(v, bool): + yield 'true' if v else 'false' + + elif isinstance(v, int): + yield str(v) + + elif isinstance(v, list): + yield '[' + for i, item in enumerate(v): + if i > 0: + yield ',' + for tok in GenerateTokens(item, level + 1): + yield tok + yield ']' + + elif isinstance(v, dict): + if level > 0: + yield '{' + for key in sorted(v): + if not isinstance(key, basestring_compat): + raise GNError('Dictionary key is not a string.') + if not key or key[0].isdigit() or not key.replace('_', '').isalnum(): + raise GNError('Dictionary key is not a valid GN identifier.') + yield key # No quotations. + yield '=' + for tok in GenerateTokens(v[key], level + 1): + yield tok + if level > 0: + yield '}' + + else: # Not supporting float: Add only when needed. + raise GNError('Unsupported type when printing to GN.') + + can_start = lambda tok: tok and tok not in ',}]=' + can_end = lambda tok: tok and tok not in ',{[=' + + # Adds whitespaces, trying to keep everything (except dicts) in 1 line. + def PlainGlue(gen): + prev_tok = None + for i, tok in enumerate(gen): + if i > 0: + if can_end(prev_tok) and can_start(tok): + yield '\n' # New dict item. + elif prev_tok == '[' and tok == ']': + yield ' ' # Special case for []. + elif tok != ',': + yield ' ' + yield tok + prev_tok = tok + + # Adds whitespaces so non-empty lists can span multiple lines, with indent. + def PrettyGlue(gen): + prev_tok = None + level = 0 + for i, tok in enumerate(gen): + if i > 0: + if can_end(prev_tok) and can_start(tok): + yield '\n' + ' ' * level # New dict item. + elif tok == '=' or prev_tok in '=': + yield ' ' # Separator before and after '=', on same line. + if tok in ']}': + level -= 1 + # Exclude '[]' and '{}' cases. + if int(prev_tok == '[') + int(tok == ']') == 1 or \ + int(prev_tok == '{') + int(tok == '}') == 1: + yield '\n' + ' ' * level + yield tok + if tok in '[{': + level += 1 + if tok == ',': + yield '\n' + ' ' * level + prev_tok = tok + + token_gen = GenerateTokens(value, 0) + ret = ''.join((PrettyGlue if pretty else PlainGlue)(token_gen)) + # Add terminating '\n' for dict |value| or multi-line output. + if isinstance(value, dict) or '\n' in ret: + return ret + '\n' + return ret + + +def FromGNString(input_string): + """Converts the input string from a GN serialized value to Python values. + + For details on supported types see GNValueParser.Parse() below. + + If your GN script did: + something = [ "file1", "file2" ] + args = [ "--values=$something" ] + The command line would look something like: + --values="[ \"file1\", \"file2\" ]" + Which when interpreted as a command line gives the value: + [ "file1", "file2" ] + + You can parse this into a Python list using GN rules with: + input_values = FromGNValues(options.values) + Although the Python 'ast' module will parse many forms of such input, it + will not handle GN escaping properly, nor GN booleans. You should use this + function instead. + + + A NOTE ON STRING HANDLING: + + If you just pass a string on the command line to your Python script, or use + string interpolation on a string variable, the strings will not be quoted: + str = "asdf" + args = [ str, "--value=$str" ] + Will yield the command line: + asdf --value=asdf + The unquoted asdf string will not be valid input to this function, which + accepts only quoted strings like GN scripts. In such cases, you can just use + the Python string literal directly. + + The main use cases for this is for other types, in particular lists. When + using string interpolation on a list (as in the top example) the embedded + strings will be quoted and escaped according to GN rules so the list can be + re-parsed to get the same result. + """ + parser = GNValueParser(input_string) + return parser.Parse() + + +def FromGNArgs(input_string): + """Converts a string with a bunch of gn arg assignments into a Python dict. + + Given a whitespace-separated list of + + = (integer | string | boolean | ) + + gn assignments, this returns a Python dict, i.e.: + + FromGNArgs('foo=true\nbar=1\n') -> { 'foo': True, 'bar': 1 }. + + Only simple types and lists supported; variables, structs, calls + and other, more complicated things are not. + + This routine is meant to handle only the simple sorts of values that + arise in parsing --args. + """ + parser = GNValueParser(input_string) + return parser.ParseArgs() + + +def UnescapeGNString(value): + """Given a string with GN escaping, returns the unescaped string. + + Be careful not to feed with input from a Python parsing function like + 'ast' because it will do Python unescaping, which will be incorrect when + fed into the GN unescaper. + + Args: + value: Input string to unescape. + """ + result = '' + i = 0 + while i < len(value): + if value[i] == '\\': + if i < len(value) - 1: + next_char = value[i + 1] + if next_char in ('$', '"', '\\'): + # These are the escaped characters GN supports. + result += next_char + i += 1 + else: + # Any other backslash is a literal. + result += '\\' + else: + result += value[i] + i += 1 + return result + + +def _IsDigitOrMinus(char): + return char in '-0123456789' + + +class GNValueParser(object): + """Duplicates GN parsing of values and converts to Python types. + + Normally you would use the wrapper function FromGNValue() below. + + If you expect input as a specific type, you can also call one of the Parse* + functions directly. All functions throw GNError on invalid input. + """ + + def __init__(self, string, checkout_root=_CHROMIUM_ROOT): + self.input = string + self.cur = 0 + self.checkout_root = checkout_root + + def IsDone(self): + return self.cur == len(self.input) + + def ReplaceImports(self): + """Replaces import(...) lines with the contents of the imports. + + Recurses on itself until there are no imports remaining, in the case of + nested imports. + """ + lines = self.input.splitlines() + if not any(line.startswith('import(') for line in lines): + return + for line in lines: + if not line.startswith('import('): + continue + regex_match = IMPORT_RE.match(line) + if not regex_match: + raise GNError('Not a valid import string: %s' % line) + import_path = os.path.join(self.checkout_root, regex_match.group(1)) + with open(import_path) as f: + imported_args = f.read() + self.input = self.input.replace(line, imported_args) + # Call ourselves again if we've just replaced an import() with additional + # imports. + self.ReplaceImports() + + + def _ConsumeWhitespace(self): + while not self.IsDone() and self.input[self.cur] in ' \t\n': + self.cur += 1 + + def ConsumeCommentAndWhitespace(self): + self._ConsumeWhitespace() + + # Consume each comment, line by line. + while not self.IsDone() and self.input[self.cur] == '#': + # Consume the rest of the comment, up until the end of the line. + while not self.IsDone() and self.input[self.cur] != '\n': + self.cur += 1 + # Move the cursor to the next line (if there is one). + if not self.IsDone(): + self.cur += 1 + + self._ConsumeWhitespace() + + def Parse(self): + """Converts a string representing a printed GN value to the Python type. + + See additional usage notes on FromGNString() above. + + * GN booleans ('true', 'false') will be converted to Python booleans. + + * GN numbers ('123') will be converted to Python numbers. + + * GN strings (double-quoted as in '"asdf"') will be converted to Python + strings with GN escaping rules. GN string interpolation (embedded + variables preceded by $) are not supported and will be returned as + literals. + + * GN lists ('[1, "asdf", 3]') will be converted to Python lists. + + * GN scopes ('{ ... }') are not supported. + + Raises: + GNError: Parse fails. + """ + result = self._ParseAllowTrailing() + self.ConsumeCommentAndWhitespace() + if not self.IsDone(): + raise GNError("Trailing input after parsing:\n " + self.input[self.cur:]) + return result + + def ParseArgs(self): + """Converts a whitespace-separated list of ident=literals to a dict. + + See additional usage notes on FromGNArgs(), above. + + Raises: + GNError: Parse fails. + """ + d = {} + + self.ReplaceImports() + self.ConsumeCommentAndWhitespace() + + while not self.IsDone(): + ident = self._ParseIdent() + self.ConsumeCommentAndWhitespace() + if self.input[self.cur] != '=': + raise GNError("Unexpected token: " + self.input[self.cur:]) + self.cur += 1 + self.ConsumeCommentAndWhitespace() + val = self._ParseAllowTrailing() + self.ConsumeCommentAndWhitespace() + d[ident] = val + + return d + + def _ParseAllowTrailing(self): + """Internal version of Parse() that doesn't check for trailing stuff.""" + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError("Expected input to parse.") + + next_char = self.input[self.cur] + if next_char == '[': + return self.ParseList() + elif next_char == '{': + return self.ParseScope() + elif _IsDigitOrMinus(next_char): + return self.ParseNumber() + elif next_char == '"': + return self.ParseString() + elif self._ConstantFollows('true'): + return True + elif self._ConstantFollows('false'): + return False + else: + raise GNError("Unexpected token: " + self.input[self.cur:]) + + def _ParseIdent(self): + ident = '' + + next_char = self.input[self.cur] + if not next_char.isalpha() and not next_char=='_': + raise GNError("Expected an identifier: " + self.input[self.cur:]) + + ident += next_char + self.cur += 1 + + next_char = self.input[self.cur] + while next_char.isalpha() or next_char.isdigit() or next_char=='_': + ident += next_char + self.cur += 1 + next_char = self.input[self.cur] + + return ident + + def ParseNumber(self): + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Expected number but got nothing.') + + begin = self.cur + + # The first character can include a negative sign. + if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]): + self.cur += 1 + while not self.IsDone() and self.input[self.cur].isdigit(): + self.cur += 1 + + number_string = self.input[begin:self.cur] + if not len(number_string) or number_string == '-': + raise GNError('Not a valid number.') + return int(number_string) + + def ParseString(self): + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Expected string but got nothing.') + + if self.input[self.cur] != '"': + raise GNError('Expected string beginning in a " but got:\n ' + + self.input[self.cur:]) + self.cur += 1 # Skip over quote. + + begin = self.cur + while not self.IsDone() and self.input[self.cur] != '"': + if self.input[self.cur] == '\\': + self.cur += 1 # Skip over the backslash. + if self.IsDone(): + raise GNError('String ends in a backslash in:\n ' + self.input) + self.cur += 1 + + if self.IsDone(): + raise GNError('Unterminated string:\n ' + self.input[begin:]) + + end = self.cur + self.cur += 1 # Consume trailing ". + + return UnescapeGNString(self.input[begin:end]) + + def ParseList(self): + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Expected list but got nothing.') + + # Skip over opening '['. + if self.input[self.cur] != '[': + raise GNError('Expected [ for list but got:\n ' + self.input[self.cur:]) + self.cur += 1 + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Unterminated list:\n ' + self.input) + + list_result = [] + previous_had_trailing_comma = True + while not self.IsDone(): + if self.input[self.cur] == ']': + self.cur += 1 # Skip over ']'. + return list_result + + if not previous_had_trailing_comma: + raise GNError('List items not separated by comma.') + + list_result += [ self._ParseAllowTrailing() ] + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + break + + # Consume comma if there is one. + previous_had_trailing_comma = self.input[self.cur] == ',' + if previous_had_trailing_comma: + # Consume comma. + self.cur += 1 + self.ConsumeCommentAndWhitespace() + + raise GNError('Unterminated list:\n ' + self.input) + + def ParseScope(self): + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Expected scope but got nothing.') + + # Skip over opening '{'. + if self.input[self.cur] != '{': + raise GNError('Expected { for scope but got:\n ' + self.input[self.cur:]) + self.cur += 1 + self.ConsumeCommentAndWhitespace() + if self.IsDone(): + raise GNError('Unterminated scope:\n ' + self.input) + + scope_result = {} + while not self.IsDone(): + if self.input[self.cur] == '}': + self.cur += 1 + return scope_result + + ident = self._ParseIdent() + self.ConsumeCommentAndWhitespace() + if self.input[self.cur] != '=': + raise GNError("Unexpected token: " + self.input[self.cur:]) + self.cur += 1 + self.ConsumeCommentAndWhitespace() + val = self._ParseAllowTrailing() + self.ConsumeCommentAndWhitespace() + scope_result[ident] = val + + raise GNError('Unterminated scope:\n ' + self.input) + + def _ConstantFollows(self, constant): + """Checks and maybe consumes a string constant at current input location. + + Param: + constant: The string constant to check. + + Returns: + True if |constant| follows immediately at the current location in the + input. In this case, the string is consumed as a side effect. Otherwise, + returns False and the current position is unchanged. + """ + end = self.cur + len(constant) + if end > len(self.input): + return False # Not enough room. + if self.input[self.cur:end] == constant: + self.cur = end + return True + return False + + +def ReadBuildVars(output_directory): + """Parses $output_directory/build_vars.json into a dict.""" + with open(os.path.join(output_directory, BUILD_VARS_FILENAME)) as f: + return json.load(f) diff --git a/third_party/libwebrtc/build/gn_helpers_unittest.py b/third_party/libwebrtc/build/gn_helpers_unittest.py new file mode 100644 index 0000000000..f4b756cd17 --- /dev/null +++ b/third_party/libwebrtc/build/gn_helpers_unittest.py @@ -0,0 +1,315 @@ +# 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. + +import mock +import sys +import textwrap +import unittest + +import gn_helpers + + +class UnitTest(unittest.TestCase): + def test_ToGNString(self): + test_cases = [ + (42, '42', '42'), ('foo', '"foo"', '"foo"'), (True, 'true', 'true'), + (False, 'false', 'false'), ('', '""', '""'), + ('\\$"$\\', '"\\\\\\$\\"\\$\\\\"', '"\\\\\\$\\"\\$\\\\"'), + (' \t\r\n', '" $0x09$0x0D$0x0A"', '" $0x09$0x0D$0x0A"'), + (u'\u2713', '"$0xE2$0x9C$0x93"', '"$0xE2$0x9C$0x93"'), + ([], '[ ]', '[]'), ([1], '[ 1 ]', '[\n 1\n]\n'), + ([3, 1, 4, 1], '[ 3, 1, 4, 1 ]', '[\n 3,\n 1,\n 4,\n 1\n]\n'), + (['a', True, 2], '[ "a", true, 2 ]', '[\n "a",\n true,\n 2\n]\n'), + ({ + 'single': 'item' + }, 'single = "item"\n', 'single = "item"\n'), + ({ + 'kEy': 137, + '_42A_Zaz_': [False, True] + }, '_42A_Zaz_ = [ false, true ]\nkEy = 137\n', + '_42A_Zaz_ = [\n false,\n true\n]\nkEy = 137\n'), + ([1, 'two', + ['"thr,.$\\', True, False, [], + u'(\u2713)']], '[ 1, "two", [ "\\"thr,.\\$\\\\", true, false, ' + + '[ ], "($0xE2$0x9C$0x93)" ] ]', '''[ + 1, + "two", + [ + "\\"thr,.\\$\\\\", + true, + false, + [], + "($0xE2$0x9C$0x93)" + ] +] +'''), + ({ + 's': 'foo', + 'n': 42, + 'b': True, + 'a': [3, 'x'] + }, 'a = [ 3, "x" ]\nb = true\nn = 42\ns = "foo"\n', + 'a = [\n 3,\n "x"\n]\nb = true\nn = 42\ns = "foo"\n'), + ( + [[[], [[]]], []], + '[ [ [ ], [ [ ] ] ], [ ] ]', + '[\n [\n [],\n [\n []\n ]\n ],\n []\n]\n', + ), + ( + [{ + 'a': 1, + 'c': { + 'z': 8 + }, + 'b': [] + }], + '[ { a = 1\nb = [ ]\nc = { z = 8 } } ]\n', + '[\n {\n a = 1\n b = []\n c = {\n' + + ' z = 8\n }\n }\n]\n', + ) + ] + for obj, exp_ugly, exp_pretty in test_cases: + out_ugly = gn_helpers.ToGNString(obj) + self.assertEqual(exp_ugly, out_ugly) + out_pretty = gn_helpers.ToGNString(obj, pretty=True) + self.assertEqual(exp_pretty, out_pretty) + + def test_UnescapeGNString(self): + # Backslash followed by a \, $, or " means the folling character without + # the special meaning. Backslash followed by everything else is a literal. + self.assertEqual( + gn_helpers.UnescapeGNString('\\as\\$\\\\asd\\"'), + '\\as$\\asd"') + + def test_FromGNString(self): + self.assertEqual( + gn_helpers.FromGNString('[1, -20, true, false,["as\\"", []]]'), + [ 1, -20, True, False, [ 'as"', [] ] ]) + + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('123 456') + parser.Parse() + + def test_ParseBool(self): + parser = gn_helpers.GNValueParser('true') + self.assertEqual(parser.Parse(), True) + + parser = gn_helpers.GNValueParser('false') + self.assertEqual(parser.Parse(), False) + + def test_ParseNumber(self): + parser = gn_helpers.GNValueParser('123') + self.assertEqual(parser.ParseNumber(), 123) + + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('') + parser.ParseNumber() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('a123') + parser.ParseNumber() + + def test_ParseString(self): + parser = gn_helpers.GNValueParser('"asdf"') + self.assertEqual(parser.ParseString(), 'asdf') + + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('') # Empty. + parser.ParseString() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('asdf') # Unquoted. + parser.ParseString() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('"trailing') # Unterminated. + parser.ParseString() + + def test_ParseList(self): + parser = gn_helpers.GNValueParser('[1,]') # Optional end comma OK. + self.assertEqual(parser.ParseList(), [ 1 ]) + + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('') # Empty. + parser.ParseList() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('asdf') # No []. + parser.ParseList() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('[1, 2') # Unterminated + parser.ParseList() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('[1 2]') # No separating comma. + parser.ParseList() + + def test_ParseScope(self): + parser = gn_helpers.GNValueParser('{a = 1}') + self.assertEqual(parser.ParseScope(), {'a': 1}) + + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('') # Empty. + parser.ParseScope() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('asdf') # No {}. + parser.ParseScope() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('{a = 1') # Unterminated. + parser.ParseScope() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('{"a" = 1}') # Not identifier. + parser.ParseScope() + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser('{a = }') # No value. + parser.ParseScope() + + def test_FromGNArgs(self): + # Booleans and numbers should work; whitespace is allowed works. + self.assertEqual(gn_helpers.FromGNArgs('foo = true\nbar = 1\n'), + {'foo': True, 'bar': 1}) + + # Whitespace is not required; strings should also work. + self.assertEqual(gn_helpers.FromGNArgs('foo="bar baz"'), + {'foo': 'bar baz'}) + + # Comments should work (and be ignored). + gn_args_lines = [ + '# Top-level comment.', + 'foo = true', + 'bar = 1 # In-line comment followed by whitespace.', + ' ', + 'baz = false', + ] + self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), { + 'foo': True, + 'bar': 1, + 'baz': False + }) + + # Lists should work. + self.assertEqual(gn_helpers.FromGNArgs('foo=[1, 2, 3]'), + {'foo': [1, 2, 3]}) + + # Empty strings should return an empty dict. + self.assertEqual(gn_helpers.FromGNArgs(''), {}) + self.assertEqual(gn_helpers.FromGNArgs(' \n '), {}) + + # Comments should work everywhere (and be ignored). + gn_args_lines = [ + '# Top-level comment.', + '', + '# Variable comment.', + 'foo = true', + 'bar = [', + ' # Value comment in list.', + ' 1,', + ' 2,', + ']', + '', + 'baz # Comment anywhere, really', + ' = # also here', + ' 4', + ] + self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), { + 'foo': True, + 'bar': [1, 2], + 'baz': 4 + }) + + # Scope should be parsed, even empty ones. + gn_args_lines = [ + 'foo = {', + ' a = 1', + ' b = [', + ' { },', + ' {', + ' c = 1', + ' },', + ' ]', + '}', + ] + self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), + {'foo': { + 'a': 1, + 'b': [ + {}, + { + 'c': 1, + }, + ] + }}) + + # Non-identifiers should raise an exception. + with self.assertRaises(gn_helpers.GNError): + gn_helpers.FromGNArgs('123 = true') + + # References to other variables should raise an exception. + with self.assertRaises(gn_helpers.GNError): + gn_helpers.FromGNArgs('foo = bar') + + # References to functions should raise an exception. + with self.assertRaises(gn_helpers.GNError): + gn_helpers.FromGNArgs('foo = exec_script("//build/baz.py")') + + # Underscores in identifiers should work. + self.assertEqual(gn_helpers.FromGNArgs('_foo = true'), + {'_foo': True}) + self.assertEqual(gn_helpers.FromGNArgs('foo_bar = true'), + {'foo_bar': True}) + self.assertEqual(gn_helpers.FromGNArgs('foo_=true'), + {'foo_': True}) + + def test_ReplaceImports(self): + # Should be a no-op on args inputs without any imports. + parser = gn_helpers.GNValueParser( + textwrap.dedent(""" + some_arg1 = "val1" + some_arg2 = "val2" + """)) + parser.ReplaceImports() + self.assertEqual( + parser.input, + textwrap.dedent(""" + some_arg1 = "val1" + some_arg2 = "val2" + """)) + + # A single "import(...)" line should be replaced with the contents of the + # file being imported. + parser = gn_helpers.GNValueParser( + textwrap.dedent(""" + some_arg1 = "val1" + import("//some/args/file.gni") + some_arg2 = "val2" + """)) + fake_import = 'some_imported_arg = "imported_val"' + builtin_var = '__builtin__' if sys.version_info.major < 3 else 'builtins' + open_fun = '{}.open'.format(builtin_var) + with mock.patch(open_fun, mock.mock_open(read_data=fake_import)): + parser.ReplaceImports() + self.assertEqual( + parser.input, + textwrap.dedent(""" + some_arg1 = "val1" + some_imported_arg = "imported_val" + some_arg2 = "val2" + """)) + + # No trailing parenthesis should raise an exception. + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser( + textwrap.dedent('import("//some/args/file.gni"')) + parser.ReplaceImports() + + # No double quotes should raise an exception. + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser( + textwrap.dedent('import(//some/args/file.gni)')) + parser.ReplaceImports() + + # A path that's not source absolute should raise an exception. + with self.assertRaises(gn_helpers.GNError): + parser = gn_helpers.GNValueParser( + textwrap.dedent('import("some/relative/args/file.gni")')) + parser.ReplaceImports() + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/gn_logs.gni b/third_party/libwebrtc/build/gn_logs.gni new file mode 100644 index 0000000000..79a92b9d4f --- /dev/null +++ b/third_party/libwebrtc/build/gn_logs.gni @@ -0,0 +1,8 @@ +# Copyright 2020 The Chromium 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/toolchain/concurrent_links.gni") + +# Log lines for gn_logs.txt that originate from within //build. +build_gn_logs = [ "#### get_concurrent_links.py ####" ] + concurrent_links_logs diff --git a/third_party/libwebrtc/build/gn_run_binary.py b/third_party/libwebrtc/build/gn_run_binary.py new file mode 100644 index 0000000000..d7e2926fa4 --- /dev/null +++ b/third_party/libwebrtc/build/gn_run_binary.py @@ -0,0 +1,36 @@ +# 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 ...] +""" + +from __future__ import print_function + +import os +import subprocess +import sys + +# This script is designed to run binaries produced by the current build. We +# may prefix it with "./" to avoid picking up system versions that might +# also be on the path. +path = sys.argv[1] +if not os.path.isabs(path): + path = './' + path + +# The rest of the arguments are passed directly to the executable. +args = [path] + sys.argv[2:] + +ret = subprocess.call(args) +if ret != 0: + if ret <= -100: + # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier to + # recognize and differentiate in hex. In order to print them as unsigned + # hex we need to add 4 Gig to them. + print('%s failed with exit code 0x%08X' % (sys.argv[1], ret + (1 << 32))) + else: + print('%s failed with exit code %d' % (sys.argv[1], ret)) +sys.exit(ret) diff --git a/third_party/libwebrtc/build/install-build-deps-android.sh b/third_party/libwebrtc/build/install-build-deps-android.sh new file mode 100755 index 0000000000..686b5f6a3c --- /dev/null +++ b/third_party/libwebrtc/build/install-build-deps-android.sh @@ -0,0 +1,37 @@ +#!/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. + +# Script to install everything needed to build chromium on android, including +# items requiring sudo privileges. +# See https://www.chromium.org/developers/how-tos/android-build-instructions + +args="$@" + +if ! uname -m | egrep -q "i686|x86_64"; then + echo "Only x86 architectures are currently supported" >&2 + exit +fi + +# Exit if any commands fail. +set -e + +lsb_release=$(lsb_release --codename --short) + +# 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}" + +# Fix deps +sudo apt-get -f install + +# common +sudo apt-get -y install lib32z1 lighttpd 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 +sudo apt-get -y install libncurses5:i386 libstdc++6:i386 zlib1g:i386 + +echo "install-build-deps-android.sh complete." diff --git a/third_party/libwebrtc/build/install-build-deps.sh b/third_party/libwebrtc/build/install-build-deps.sh new file mode 100755 index 0000000000..5e89b73e23 --- /dev/null +++ b/third_party/libwebrtc/build/install-build-deps.sh @@ -0,0 +1,763 @@ +#!/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 https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md + +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-]backwards-compatible: enable or disable installation of packages + that are no longer currently needed and have been removed from this + script. Useful for bisection." + 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 +} + +# Build list of apt packages in dpkg --get-selections format. +build_apt_package_list() { + echo "Building apt package list." >&2 + apt-cache dumpavail | \ + python3 -c '\ + import re,sys; \ + o = sys.stdin.read(); \ + p = {"i386": ":i386"}; \ + f = re.M | re.S; \ + r = re.compile(r"^Package: (.+?)$.+?^Architecture: (.+?)$", f); \ + m = ["%s%s" % (x, p.get(y, "")) for x, y in re.findall(r, o)]; \ + print("\n".join(m))' +} + +# Checks whether a particular package is available in the repos. +# Uses pre-formatted ${apt_package_list}. +# USAGE: $ package_exists +package_exists() { + if [ -z "${apt_package_list}" ]; then + echo "Call build_apt_package_list() prior to calling package_exists()" >&2 + apt_package_list=$(build_apt_package_list) + fi + # `grep` takes a regex string, so the +'s in package names, e.g. "libstdc++", + # need to be escaped. + local escaped="$(echo $1 | sed 's/[\~\+\.\:-]/\\&/g')" + [ ! -z "$(grep "^${escaped}$" <<< "${apt_package_list}")" ] +} + +do_inst_arm=0 +do_inst_nacl=0 + +while [ "$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;; + --backwards-compatible) do_inst_backwards_compatible=1;; + --no-backwards-compatible) do_inst_backwards_compatible=0;; + --add-cross-tool-repo) add_cross_tool_repo=1;; + --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 [ "$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 + echo "try: sudo apt-get install lsb-release" >&2 + exit 1; +fi + +distro_codename=$(lsb_release --codename --short) +distro_id=$(lsb_release --id --short) +# TODO(crbug.com/1199405): Remove 14.04 (trusty) and 16.04 (xenial). +supported_codenames="(trusty|xenial|bionic|disco|eoan|focal|groovy)" +supported_ids="(Debian)" +if [ 0 -eq "${do_unsupported-0}" ] && [ 0 -eq "${do_quick_check-0}" ] ; then + if [[ ! $distro_codename =~ $supported_codenames && + ! $distro_id =~ $supported_ids ]]; then + echo -e "ERROR: The only supported distros are\n" \ + "\tUbuntu 14.04 LTS (trusty with EoL April 2022)\n" \ + "\tUbuntu 16.04 LTS (xenial with EoL April 2024)\n" \ + "\tUbuntu 18.04 LTS (bionic with EoL April 2028)\n" \ + "\tUbuntu 20.04 LTS (focal with Eol April 2030)\n" \ + "\tUbuntu 20.10 (groovy)\n" \ + "\tDebian 10 (buster) or later" >&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 + +if [ 0 -eq "${do_quick_check-0}" ] ; then + if [ "$do_inst_lib32" = "1" ] || [ "$do_inst_nacl" = "1" ]; then + sudo dpkg --add-architecture i386 + fi + sudo apt-get update +fi + +# Populate ${apt_package_list} for package_exists() parsing. +apt_package_list=$(build_apt_package_list) + +# Packages needed for chromeos only +chromeos_dev_list="libbluetooth-dev libxkbcommon-dev mesa-common-dev" + +if package_exists realpath; then + chromeos_dev_list="${chromeos_dev_list} realpath" +fi + +# Packages needed for development +dev_list="\ + binutils + bison + bzip2 + cdbs + curl + dbus-x11 + dpkg-dev + elfutils + devscripts + fakeroot + flex + git-core + gperf + libappindicator3-dev + libasound2-dev + libatspi2.0-dev + libbrlapi-dev + libbz2-dev + libcairo2-dev + libcap-dev + libc6-dev + libcups2-dev + libcurl4-gnutls-dev + libdrm-dev + libelf-dev + libevdev-dev + libffi-dev + libgbm-dev + libglib2.0-dev + libglu1-mesa-dev + libgtk-3-dev + libkrb5-dev + libnspr4-dev + libnss3-dev + libpam0g-dev + libpci-dev + libpulse-dev + libsctp-dev + libspeechd-dev + libsqlite3-dev + libssl-dev + libudev-dev + libva-dev + libwww-perl + libxshmfence-dev + libxslt1-dev + libxss-dev + libxt-dev + libxtst-dev + locales + openbox + p7zip + patch + perl + pkg-config + python-setuptools + rpm + ruby + subversion + uuid-dev + wdiff + x11-utils + xcompmgr + xz-utils + zip + $chromeos_dev_list +" + +if package_exists python-is-python2; then + dev_list="${dev_list} python-is-python2 python2-dev" +else + dev_list="${dev_list} python python-dev" +fi + +# 64-bit systems need a minimum set of 32-bit compat packages for the pre-built +# NaCl binaries. +if file -L /sbin/init | grep -q 'ELF 64-bit'; then + dev_list="${dev_list} libc6-i386 lib32stdc++6" + + # lib32gcc-s1 used to be called lib32gcc1 in older distros. + if package_exists lib32gcc-s1; then + dev_list="${dev_list} lib32gcc-s1" + elif package_exists lib32gcc1; then + dev_list="${dev_list} lib32gcc1" + fi +fi + +# Run-time libraries required by chromeos only +chromeos_lib_list="libpulse0 libbz2-1.0" + +# List of required run-time libraries +common_lib_list="\ + libappindicator3-1 + libasound2 + libatk1.0-0 + libatspi2.0-0 + libc6 + libcairo2 + libcap2 + libcups2 + libdrm2 + libevdev2 + libexpat1 + libfontconfig1 + libfreetype6 + libgbm1 + libglib2.0-0 + libgtk-3-0 + libpam0g + libpango-1.0-0 + libpci3 + libpcre3 + libpixman-1-0 + libspeechd2 + libstdc++6 + libsqlite3-0 + libuuid1 + libwayland-egl1-mesa + libx11-6 + libx11-xcb1 + libxau6 + libxcb1 + libxcomposite1 + libxcursor1 + libxdamage1 + libxdmcp6 + libxext6 + libxfixes3 + libxi6 + libxinerama1 + libxrandr2 + libxrender1 + libxtst6 + zlib1g +" + +if package_exists libffi7; then + common_lib_list="${common_lib_list} libffi7" +elif package_exists libffi6; then + common_lib_list="${common_lib_list} libffi6" +fi + +# Full list of required run-time libraries +lib_list="\ + $common_lib_list + $chromeos_lib_list +" + +# 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf +lib32_list="linux-libc-dev:i386 libpci3:i386" + +# 32-bit libraries needed for a 32-bit build +lib32_list="$lib32_list libx11-xcb1:i386" + +# Packages that have been removed from this script. Regardless of configuration +# or options passed to this script, whenever a package is removed, it should be +# added here. +backwards_compatible_list="\ + 7za + fonts-indic + fonts-ipafont + fonts-stix + fonts-thai-tlwg + fonts-tlwg-garuda + g++ + git-svn + language-pack-da + language-pack-fr + language-pack-he + language-pack-zh-hant + libappindicator-dev + libappindicator1 + libdconf-dev + libdconf1 + libdconf1:i386 + libexif-dev + libexif12 + libexif12:i386 + libgbm-dev + libgconf-2-4:i386 + libgconf2-dev + libgl1-mesa-dev + libgl1-mesa-glx:i386 + libgles2-mesa-dev + libgtk-3-0:i386 + libgtk2.0-0 + libgtk2.0-0:i386 + libgtk2.0-dev + mesa-common-dev + msttcorefonts + ttf-dejavu-core + ttf-indic-fonts + ttf-kochi-gothic + ttf-kochi-mincho + ttf-mscorefonts-installer + xfonts-mathml +" +if package_exists python-is-python2; then + backwards_compatible_list="${backwards_compatible_list} python-dev" +fi + +case $distro_codename in + trusty) + backwards_compatible_list+=" \ + libgbm-dev-lts-trusty + libgl1-mesa-dev-lts-trusty + libgl1-mesa-glx-lts-trusty:i386 + libgles2-mesa-dev-lts-trusty + mesa-common-dev-lts-trusty" + ;; + xenial) + backwards_compatible_list+=" \ + libgbm-dev-lts-xenial + libgl1-mesa-dev-lts-xenial + libgl1-mesa-glx-lts-xenial:i386 + libgles2-mesa-dev-lts-xenial + mesa-common-dev-lts-xenial" + ;; +esac + +# arm cross toolchain packages needed to build chrome on armhf +arm_list="libc6-dev-armhf-cross + linux-libc-dev-armhf-cross + g++-arm-linux-gnueabihf" + +# Work around for dependency issue Ubuntu/Trusty: http://crbug.com/435056 +case $distro_codename in + trusty) + arm_list+=" g++-4.8-multilib-arm-linux-gnueabihf + gcc-4.8-multilib-arm-linux-gnueabihf" + ;; + xenial|bionic) + arm_list+=" g++-5-multilib-arm-linux-gnueabihf + gcc-5-multilib-arm-linux-gnueabihf + gcc-arm-linux-gnueabihf" + ;; + disco|eoan) + arm_list+=" g++-9-multilib-arm-linux-gnueabihf + gcc-9-multilib-arm-linux-gnueabihf + gcc-arm-linux-gnueabihf" + ;; + focal) + arm_list+=" g++-10-multilib-arm-linux-gnueabihf + gcc-10-multilib-arm-linux-gnueabihf + gcc-arm-linux-gnueabihf" + ;; + groovy) + arm_list+=" g++-10-multilib-arm-linux-gnueabihf + gcc-10-multilib-arm-linux-gnueabihf + gcc-arm-linux-gnueabihf + g++-10-arm-linux-gnueabihf + gcc-10-arm-linux-gnueabihf" + ;; +esac + +# 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 + libfontconfig1:i386 + libglib2.0-0:i386 + libgpm2:i386 + libncurses5:i386 + lib32ncurses5-dev + libnss3:i386 + libpango-1.0-0:i386 + libssl-dev:i386 + libtinfo-dev + libtinfo-dev:i386 + libtool + libuuid1:i386 + libxcomposite1:i386 + libxcursor1:i386 + libxdamage1:i386 + libxi6:i386 + libxrandr2:i386 + libxss1:i386 + libxtst6:i386 + texinfo + xvfb + ${naclports_list} +" + +# Some package names have changed over time +if package_exists libssl1.1; then + nacl_list="${nacl_list} libssl1.1:i386" +elif package_exists libssl1.0.2; then + nacl_list="${nacl_list} libssl1.0.2:i386" +else + nacl_list="${nacl_list} libssl1.0.0:i386" +fi +if package_exists libtinfo5; then + nacl_list="${nacl_list} libtinfo5" +fi +if package_exists libpng16-16; then + lib_list="${lib_list} libpng16-16" +else + lib_list="${lib_list} libpng12-0" +fi +if package_exists libnspr4; then + lib_list="${lib_list} libnspr4 libnss3" +else + 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.8; then + dev_list="${dev_list} libbrlapi0.8" +elif package_exists libbrlapi0.7; then + dev_list="${dev_list} libbrlapi0.7" +elif package_exists libbrlapi0.6; then + dev_list="${dev_list} libbrlapi0.6" +else + dev_list="${dev_list} libbrlapi0.5" +fi +if package_exists apache2.2-bin; then + dev_list="${dev_list} apache2.2-bin" +else + dev_list="${dev_list} apache2-bin" +fi +if package_exists libav-tools; then + dev_list="${dev_list} libav-tools" +fi +if package_exists php7.4-cgi; then + dev_list="${dev_list} php7.4-cgi libapache2-mod-php7.4" +elif package_exists php7.3-cgi; then + dev_list="${dev_list} php7.3-cgi libapache2-mod-php7.3" +elif package_exists php7.2-cgi; then + dev_list="${dev_list} php7.2-cgi libapache2-mod-php7.2" +elif package_exists php7.1-cgi; then + dev_list="${dev_list} php7.1-cgi libapache2-mod-php7.1" +elif package_exists php7.0-cgi; then + dev_list="${dev_list} php7.0-cgi libapache2-mod-php7.0" +else + dev_list="${dev_list} php5-cgi libapache2-mod-php5" +fi + +# Most python 2 packages are removed in Ubuntu 20.10, but the build doesn't seem +# to need them, so only install them if they're available. +if package_exists python-crypto; then + dev_list="${dev_list} python-crypto" +fi +if package_exists python-numpy; then + dev_list="${dev_list} python-numpy" +fi +if package_exists python-openssl; then + dev_list="${dev_list} python-openssl" +fi +if package_exists python-psutil; then + dev_list="${dev_list} python-psutil" +fi +if package_exists python-yaml; then + dev_list="${dev_list} python-yaml" +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 +if package_exists libgnome-keyring0; then + lib_list="${lib_list} libgnome-keyring0" +fi +if package_exists libgnome-keyring-dev; then + lib_list="${lib_list} libgnome-keyring-dev" +fi +if package_exists libvulkan-dev; then + dev_list="${dev_list} libvulkan-dev" +fi +if package_exists libvulkan1; then + lib_list="${lib_list} libvulkan1" +fi +if package_exists libinput10; then + lib_list="${lib_list} libinput10" +fi +if package_exists libinput-dev; then + dev_list="${dev_list} libinput-dev" +fi +if package_exists snapcraft; then + dev_list="${dev_list} snapcraft" +fi + +# Cross-toolchain strip is needed for building the sysroots. +if package_exists binutils-arm-linux-gnueabihf; then + dev_list="${dev_list} binutils-arm-linux-gnueabihf" +fi +if package_exists binutils-aarch64-linux-gnu; then + dev_list="${dev_list} binutils-aarch64-linux-gnu" +fi +if package_exists binutils-mipsel-linux-gnu; then + dev_list="${dev_list} binutils-mipsel-linux-gnu" +fi +if package_exists binutils-mips64el-linux-gnuabi64; then + dev_list="${dev_list} binutils-mips64el-linux-gnuabi64" +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 -L /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 + +if [ "$do_inst_syms" = "1" ]; then + echo "Including debugging symbols." + + # Debian is in the process of transitioning to automatic debug packages, which + # have the -dbgsym suffix (https://wiki.debian.org/AutomaticDebugPackages). + # Untransitioned packages have the -dbg suffix. And on some systems, neither + # will be available, so exclude the ones that are missing. + dbg_package_name() { + if package_exists "$1-dbgsym"; then + echo "$1-dbgsym" + elif package_exists "$1-dbg"; then + echo "$1-dbg" + fi + } + + for package in "${common_lib_list}"; do + dbg_list="$dbg_list $(dbg_package_name ${package})" + done + + # Debugging symbols packages not following common naming scheme + if [ "$(dbg_package_name libstdc++6)" == "" ]; then + if package_exists libstdc++6-8-dbg; then + dbg_list="${dbg_list} libstdc++6-8-dbg" + elif package_exists libstdc++6-7-dbg; then + dbg_list="${dbg_list} libstdc++6-7-dbg" + elif package_exists libstdc++6-6-dbg; then + dbg_list="${dbg_list} libstdc++6-6-dbg" + elif package_exists libstdc++6-5-dbg; then + dbg_list="${dbg_list} libstdc++6-5-dbg" + elif package_exists libstdc++6-4.9-dbg; then + dbg_list="${dbg_list} libstdc++6-4.9-dbg" + elif package_exists libstdc++6-4.8-dbg; then + dbg_list="${dbg_list} libstdc++6-4.8-dbg" + elif package_exists libstdc++6-4.7-dbg; then + dbg_list="${dbg_list} libstdc++6-4.7-dbg" + elif package_exists libstdc++6-4.6-dbg; then + dbg_list="${dbg_list} libstdc++6-4.6-dbg" + fi + fi + if [ "$(dbg_package_name libatk1.0-0)" == "" ]; then + dbg_list="$dbg_list $(dbg_package_name libatk1.0)" + fi + if [ "$(dbg_package_name libpango-1.0-0)" == "" ]; then + dbg_list="$dbg_list $(dbg_package_name libpango1.0-dev)" + fi +else + echo "Skipping debugging symbols." + dbg_list= +fi + +if [ "$do_inst_lib32" = "1" ]; then + echo "Including 32-bit libraries." +else + echo "Skipping 32-bit libraries." + lib32_list= +fi + +if [ "$do_inst_arm" = "1" ]; then + echo "Including ARM cross toolchain." +else + echo "Skipping ARM cross toolchain." + arm_list= +fi + +if [ "$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 + +filtered_backwards_compatible_list= +if [ "$do_inst_backwards_compatible" = "1" ]; then + echo "Including backwards compatible packages." + for package in ${backwards_compatible_list}; do + if package_exists ${package}; then + filtered_backwards_compatible_list+=" ${package}" + fi + done +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}" ${filtered_backwards_compatible_list} | tr " " "\n" | \ + sort -u | sort -r -s -t: -k2 | tr "\n" " " +)" + +if [ 1 -eq "${do_quick_check-0}" ] ; then + if ! missing_packages="$(dpkg-query -W -f ' ' ${packages} 2>&1)"; 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). + missing_packages="$(echo "${missing_packages}" | awk '{print $NF}')" + not_installed="" + unknown="" + for p in ${missing_packages}; do + if apt-cache show ${p} > /dev/null 2>&1; then + not_installed="${p}\n${not_installed}" + else + unknown="${p}\n${unknown}" + fi + done + if [ -n "${not_installed}" ]; then + echo "WARNING: The following packages are not installed:" + echo -e "${not_installed}" | sed -e "s/^/ /" + fi + 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 + exit 1 + fi + exit 0 +fi + +echo "Finding missing packages..." +# Intentionally leaving $packages unquoted so it's more readable. +echo "Packages required: " $packages +echo +query_cmd="apt-get --just-print install $(echo $packages)" +if cmd_output="$(LANGUAGE=en LANG=C $query_cmd)"; then + new_list=$(echo "$cmd_output" | + sed -e '1,/The following NEW packages will be installed:/d;s/^ //;t;d' | + sed 's/ *$//') + upgrade_list=$(echo "$cmd_output" | + sed -e '1,/The following packages will be upgraded:/d;s/^ //;t;d' | + sed 's/ *$//') + if [ -z "$new_list" ] && [ -z "$upgrade_list" ]; then + echo "No missing packages, and the packages are up to date." + else + echo "Installing and upgrading packages: $new_list $upgrade_list." + sudo apt-get install ${do_quietly-} ${new_list} ${upgrade_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 query_cmd, + # as this makes it easier to cut and paste the output + echo "The following command failed: " ${query_cmd} + echo + echo "It produced the following output:" + echo "$cmd_output" + 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 [ "$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 + +echo "Installing locales." +CHROMIUM_LOCALES="da_DK.UTF-8 fr_FR.UTF-8 he_IL.UTF-8 zh_TW.UTF-8" +LOCALE_GEN=/etc/locale.gen +if [ -e ${LOCALE_GEN} ]; then + OLD_LOCALE_GEN="$(cat /etc/locale.gen)" + for CHROMIUM_LOCALE in ${CHROMIUM_LOCALES}; do + sudo sed -i "s/^# ${CHROMIUM_LOCALE}/${CHROMIUM_LOCALE}/" ${LOCALE_GEN} + done + # Regenerating locales can take a while, so only do it if we need to. + if (echo "${OLD_LOCALE_GEN}" | cmp -s ${LOCALE_GEN}); then + echo "Locales already up-to-date." + else + sudo locale-gen + fi +else + for CHROMIUM_LOCALE in ${CHROMIUM_LOCALES}; do + sudo locale-gen ${CHROMIUM_LOCALE} + done +fi diff --git a/third_party/libwebrtc/build/install-chroot.sh b/third_party/libwebrtc/build/install-chroot.sh new file mode 100755 index 0000000000..d76d53563b --- /dev/null +++ b/third_party/libwebrtc/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 subtraction 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 <= 1140, + "intent_definition requires Xcode 11.4 or higher") + + _compile_intent_target = "${target_name}_compile_intent" + _compile_intent_output = + "$target_gen_dir/" + get_path_info(invoker.intent_file, "file") + + action(_compile_intent_target) { + script = "//build/apple/xcrun.py" + inputs = [ invoker.intent_file ] + outputs = [ _compile_intent_output ] + args = [ + "intentbuilderc", + "compile", + "-input", + rebase_path(invoker.intent_file, root_build_dir), + "-output", + rebase_path(target_gen_dir, root_build_dir), + ] + + if (defined(invoker.developer_dir)) { + args += [ + "--developer-dir", + rebase_path(invoker.developer_dir, root_build_dir), + ] + } + } + + _compile_intent_bundle = "${target_name}_compile_intent_bundle" + bundle_data(_compile_intent_bundle) { + sources = [ _compile_intent_output ] + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + public_deps = [ ":$_compile_intent_target" ] + } + + _generate_source_target = "${target_name}_generate_source" + _generate_source_output = [] + foreach(_intent, invoker.intent_names) { + _generate_source_output += [ + "$target_gen_dir/$_intent.h", + "$target_gen_dir/$_intent.m", + ] + } + + action(_generate_source_target) { + script = "//build/apple/xcrun.py" + inputs = [ invoker.intent_file ] + outputs = _generate_source_output + args = [ + "intentbuilderc", + "generate", + "-input", + rebase_path(invoker.intent_file, root_build_dir), + "-output", + rebase_path(target_gen_dir, root_build_dir), + "-language", + "Objective-C", + ] + + if (defined(invoker.developer_dir)) { + args += [ + "--developer-dir", + rebase_path(invoker.developer_dir, root_build_dir), + ] + } + } + + source_set(target_name) { + forward_variables_from(invoker, + "*", + [ + "developer_dir", + "intent_file", + "intent_names", + "sources", + ]) + + if (!defined(deps)) { + deps = [] + } + if (!defined(frameworks)) { + frameworks = [] + } + + sources = _generate_source_output + configs += [ "//build/config/compiler:enable_arc" ] + deps += [ + ":$_compile_intent_bundle", + ":$_generate_source_target", + ] + frameworks += [ "Intents.framework" ] + } +} diff --git a/third_party/libwebrtc/build/lacros/BUILD.gn b/third_party/libwebrtc/build/lacros/BUILD.gn new file mode 100644 index 0000000000..216010a42e --- /dev/null +++ b/third_party/libwebrtc/build/lacros/BUILD.gn @@ -0,0 +1,20 @@ +# Copyright 2020 The Chromium 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/python.gni") +import("//build/util/process_version.gni") + +python_library("lacros_resource_sizes_py") { + pydeps_file = "lacros_resource_sizes.pydeps" + data = [ "//buildtools/third_party/eu-strip/bin/eu-strip" ] + data_deps = [ "//third_party/catapult/tracing:convert_chart_json" ] +} + +process_version("lacros_version_metadata") { + sources = [ "//chrome/VERSION" ] + + template_file = "metadata.json.in" + output = "$root_out_dir/metadata.json" + process_only = true +} diff --git a/third_party/libwebrtc/build/lacros/OWNERS b/third_party/libwebrtc/build/lacros/OWNERS new file mode 100644 index 0000000000..aae4f73726 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/OWNERS @@ -0,0 +1,3 @@ +svenzheng@chromium.org +liaoyuke@chromium.org +erikchen@chromium.org diff --git a/third_party/libwebrtc/build/lacros/PRESUBMIT.py b/third_party/libwebrtc/build/lacros/PRESUBMIT.py new file mode 100644 index 0000000000..1667f2ae39 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/PRESUBMIT.py @@ -0,0 +1,25 @@ +# Copyright (c) 2020 The Chromium 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 changes affecting //build/lacros""" + +USE_PYTHON3 = True + + +def _CommonChecks(input_api, output_api): + tests = input_api.canned_checks.GetUnitTestsInDirectory( + input_api, + output_api, + '.', [r'^.+_test\.py$'], + run_on_python2=False, + run_on_python3=True, + skip_shebang_check=True) + return input_api.RunTests(tests) + + +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/third_party/libwebrtc/build/lacros/lacros_resource_sizes.gni b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.gni new file mode 100644 index 0000000000..0c50e0fe34 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.gni @@ -0,0 +1,21 @@ +# Copyright 2020 The Chromium 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/util/generate_wrapper.gni") + +# Generates a script in the bin directory that runs +# //build/lacros/lacros_resource_sizes.py for the provided configuration. +template("lacros_resource_sizes_test") { + generate_wrapper(target_name) { + forward_variables_from(invoker, [ "data_deps" ]) + executable = "//build/lacros/lacros_resource_sizes.py" + wrapper_script = "$root_out_dir/bin/run_${target_name}" + + deps = [ "//build/lacros:lacros_resource_sizes_py" ] + executable_args = [ + "--chromium-output-directory", + "@WrappedPath(.)", + ] + } +} diff --git a/third_party/libwebrtc/build/lacros/lacros_resource_sizes.py b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.py new file mode 100755 index 0000000000..f761662861 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Reports binary size metrics for LaCrOS build artifacts. + +More information at //docs/speed/binary_size/metrics.md. +""" + +import argparse +import collections +import contextlib +import json +import logging +import os +import subprocess +import sys +import tempfile + + +@contextlib.contextmanager +def _SysPath(path): + """Library import context that temporarily appends |path| to |sys.path|.""" + if path and path not in sys.path: + sys.path.insert(0, path) + else: + path = None # Indicates that |sys.path| is not modified. + try: + yield + finally: + if path: + sys.path.pop(0) + + +DIR_SOURCE_ROOT = os.environ.get( + 'CHECKOUT_SOURCE_ROOT', + os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))) + +BUILD_COMMON_PATH = os.path.join(DIR_SOURCE_ROOT, 'build', 'util', 'lib', + 'common') + +TRACING_PATH = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'catapult', + 'tracing') + +EU_STRIP_PATH = os.path.join(DIR_SOURCE_ROOT, 'buildtools', 'third_party', + 'eu-strip', 'bin', 'eu-strip') + +with _SysPath(BUILD_COMMON_PATH): + import perf_tests_results_helper # pylint: disable=import-error + +with _SysPath(TRACING_PATH): + from tracing.value import convert_chart_json # pylint: disable=import-error + +_BASE_CHART = { + 'format_version': '0.1', + 'benchmark_name': 'resource_sizes', + 'benchmark_description': 'LaCrOS resource size information.', + 'trace_rerun_options': [], + 'charts': {} +} + +_KEY_RAW = 'raw' +_KEY_GZIPPED = 'gzipped' +_KEY_STRIPPED = 'stripped' +_KEY_STRIPPED_GZIPPED = 'stripped_then_gzipped' + + +class _Group: + """A group of build artifacts whose file sizes are summed and tracked. + + Build artifacts for size tracking fall under these categories: + * File: A single file. + * Group: A collection of files. + * Dir: All files under a directory. + + Attributes: + paths: A list of files or directories to be tracked together. + title: The display name of the group. + track_stripped: Whether to also track summed stripped ELF sizes. + track_compressed: Whether to also track summed compressed sizes. + """ + + def __init__(self, paths, title, track_stripped=False, + track_compressed=False): + self.paths = paths + self.title = title + self.track_stripped = track_stripped + self.track_compressed = track_compressed + + +# List of disjoint build artifact groups for size tracking. This list should be +# synched with lacros-amd64-generic-binary-size-rel builder contents (specified +# in # //infra/config/subprojects/chromium/ci.star) and +# chromeos-amd64-generic-lacros-internal builder (specified in src-internal). +_TRACKED_GROUPS = [ + _Group(paths=['chrome'], + title='File: chrome', + track_stripped=True, + track_compressed=True), + _Group(paths=['chrome_crashpad_handler'], + title='File: chrome_crashpad_handler'), + _Group(paths=['icudtl.dat'], title='File: icudtl.dat'), + _Group(paths=['nacl_helper'], title='File: nacl_helper'), + _Group(paths=['nacl_irt_x86_64.nexe'], title='File: nacl_irt_x86_64.nexe'), + _Group(paths=['resources.pak'], title='File: resources.pak'), + _Group(paths=[ + 'chrome_100_percent.pak', 'chrome_200_percent.pak', 'headless_lib.pak' + ], + title='Group: Other PAKs'), + _Group(paths=['snapshot_blob.bin'], title='Group: Misc'), + _Group(paths=['locales/'], title='Dir: locales'), + _Group(paths=['swiftshader/'], title='Dir: swiftshader'), + _Group(paths=['WidevineCdm/'], title='Dir: WidevineCdm'), +] + + +def _visit_paths(base_dir, paths): + """Itemizes files specified by a list of paths. + + Args: + base_dir: Base directory for all elements in |paths|. + paths: A list of filenames or directory names to specify files whose sizes + to be counted. Directories are recursed. There's no de-duping effort. + Non-existing files or directories are ignored (with warning message). + """ + for path in paths: + full_path = os.path.join(base_dir, path) + if os.path.exists(full_path): + if os.path.isdir(full_path): + for dirpath, _, filenames in os.walk(full_path): + for filename in filenames: + yield os.path.join(dirpath, filename) + else: # Assume is file. + yield full_path + else: + logging.critical('Not found: %s', path) + + +def _is_probably_elf(filename): + """Heuristically decides whether |filename| is ELF via magic signature.""" + with open(filename, 'rb') as fh: + return fh.read(4) == '\x7FELF' + + +def _is_unstrippable_elf(filename): + """Identifies known-unstrippable ELF files to denoise the system.""" + return filename.endswith('.nexe') or filename.endswith('libwidevinecdm.so') + + +def _get_filesize(filename): + """Returns the size of a file, or 0 if file is not found.""" + try: + return os.path.getsize(filename) + except OSError: + logging.critical('Failed to get size: %s', filename) + return 0 + + +def _get_gzipped_filesize(filename): + """Returns the gzipped size of a file, or 0 if file is not found.""" + BUFFER_SIZE = 65536 + if not os.path.isfile(filename): + return 0 + try: + # Call gzip externally instead of using gzip package since it's > 2x faster. + cmd = ['gzip', '-c', filename] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + # Manually counting bytes instead of using len(p.communicate()[0]) to avoid + # buffering the entire compressed data (can be ~100 MB). + ret = 0 + while True: + chunk = len(p.stdout.read(BUFFER_SIZE)) + if chunk == 0: + break + ret += chunk + return ret + except OSError: + logging.critical('Failed to get gzipped size: %s', filename) + return 0 + + +def _get_catagorized_filesizes(filename): + """Measures |filename| sizes under various transforms. + + Returns: A Counter (keyed by _Key_* constants) that stores measured sizes. + """ + sizes = collections.Counter() + sizes[_KEY_RAW] = _get_filesize(filename) + sizes[_KEY_GZIPPED] = _get_gzipped_filesize(filename) + + # Pre-assign values for non-ELF, or in case of failure for ELF. + sizes[_KEY_STRIPPED] = sizes[_KEY_RAW] + sizes[_KEY_STRIPPED_GZIPPED] = sizes[_KEY_GZIPPED] + + if _is_probably_elf(filename) and not _is_unstrippable_elf(filename): + try: + fd, temp_file = tempfile.mkstemp() + os.close(fd) + cmd = [EU_STRIP_PATH, filename, '-o', temp_file] + subprocess.check_output(cmd) + sizes[_KEY_STRIPPED] = _get_filesize(temp_file) + sizes[_KEY_STRIPPED_GZIPPED] = _get_gzipped_filesize(temp_file) + if sizes[_KEY_STRIPPED] > sizes[_KEY_RAW]: + # This weird case has been observed for libwidevinecdm.so. + logging.critical('Stripping made things worse for %s' % filename) + except subprocess.CalledProcessError: + logging.critical('Failed to strip file: %s' % filename) + finally: + os.unlink(temp_file) + return sizes + + +def _dump_chart_json(output_dir, chartjson): + """Writes chart histogram to JSON files. + + Output files: + results-chart.json contains the chart JSON. + perf_results.json contains histogram JSON for Catapult. + + Args: + output_dir: Directory to place the JSON files. + chartjson: Source JSON data for output files. + """ + results_path = os.path.join(output_dir, 'results-chart.json') + logging.critical('Dumping chartjson to %s', results_path) + with open(results_path, 'w') as json_file: + json.dump(chartjson, json_file, indent=2) + + # We would ideally generate a histogram set directly instead of generating + # chartjson then converting. However, perf_tests_results_helper is in + # //build, which doesn't seem to have any precedent for depending on + # anything in Catapult. This can probably be fixed, but since this doesn't + # need to be super fast or anything, converting is a good enough solution + # for the time being. + histogram_result = convert_chart_json.ConvertChartJson(results_path) + if histogram_result.returncode != 0: + raise Exception('chartjson conversion failed with error: ' + + histogram_result.stdout) + + histogram_path = os.path.join(output_dir, 'perf_results.json') + logging.critical('Dumping histograms to %s', histogram_path) + with open(histogram_path, 'w') as json_file: + json_file.write(histogram_result.stdout) + + +def _run_resource_sizes(args): + """Main flow to extract and output size data.""" + chartjson = _BASE_CHART.copy() + report_func = perf_tests_results_helper.ReportPerfResult + total_sizes = collections.Counter() + + def report_sizes(sizes, title, track_stripped, track_compressed): + report_func(chart_data=chartjson, + graph_title=title, + trace_title='size', + value=sizes[_KEY_RAW], + units='bytes') + + if track_stripped: + report_func(chart_data=chartjson, + graph_title=title + ' (Stripped)', + trace_title='size', + value=sizes[_KEY_STRIPPED], + units='bytes') + + if track_compressed: + report_func(chart_data=chartjson, + graph_title=title + ' (Gzipped)', + trace_title='size', + value=sizes[_KEY_GZIPPED], + units='bytes') + + if track_stripped and track_compressed: + report_func(chart_data=chartjson, + graph_title=title + ' (Stripped, Gzipped)', + trace_title='size', + value=sizes[_KEY_STRIPPED_GZIPPED], + units='bytes') + + for g in _TRACKED_GROUPS: + sizes = sum( + map(_get_catagorized_filesizes, _visit_paths(args.out_dir, g.paths)), + collections.Counter()) + report_sizes(sizes, g.title, g.track_stripped, g.track_compressed) + + # Total compressed size is summed over individual compressed sizes, instead + # of concatanating first, then compress everything. This is done for + # simplicity. It also gives a conservative size estimate (assuming file + # metadata and overheads are negligible). + total_sizes += sizes + + report_sizes(total_sizes, 'Total', True, True) + + _dump_chart_json(args.output_dir, chartjson) + + +def main(): + """Parses arguments and runs high level flows.""" + argparser = argparse.ArgumentParser(description='Writes LaCrOS size metrics.') + + argparser.add_argument('--chromium-output-directory', + dest='out_dir', + required=True, + type=os.path.realpath, + help='Location of the build artifacts.') + + output_group = argparser.add_mutually_exclusive_group() + + output_group.add_argument('--output-dir', + default='.', + help='Directory to save chartjson to.') + + # Accepted to conform to the isolated script interface, but ignored. + argparser.add_argument('--isolated-script-test-filter', + help=argparse.SUPPRESS) + argparser.add_argument('--isolated-script-test-perf-output', + type=os.path.realpath, + help=argparse.SUPPRESS) + + output_group.add_argument( + '--isolated-script-test-output', + type=os.path.realpath, + help='File to which results will be written in the simplified JSON ' + 'output format.') + + args = argparser.parse_args() + + isolated_script_output = {'valid': False, 'failures': []} + if args.isolated_script_test_output: + test_name = 'lacros_resource_sizes' + args.output_dir = os.path.join( + os.path.dirname(args.isolated_script_test_output), test_name) + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + + try: + _run_resource_sizes(args) + isolated_script_output = {'valid': True, 'failures': []} + finally: + if args.isolated_script_test_output: + results_path = os.path.join(args.output_dir, 'test_results.json') + with open(results_path, 'w') as output_file: + json.dump(isolated_script_output, output_file) + with open(args.isolated_script_test_output, 'w') as output_file: + json.dump(isolated_script_output, output_file) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/lacros/lacros_resource_sizes.pydeps b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.pydeps new file mode 100644 index 0000000000..9c0afd3570 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/lacros_resource_sizes.pydeps @@ -0,0 +1,12 @@ +# Generated by running: +# build/print_python_deps.py --root build/lacros --output build/lacros/lacros_resource_sizes.pydeps build/lacros/lacros_resource_sizes.py +../../third_party/catapult/third_party/six/six.py +../../third_party/catapult/third_party/vinn/vinn/__init__.py +../../third_party/catapult/third_party/vinn/vinn/_vinn.py +../../third_party/catapult/tracing/tracing/__init__.py +../../third_party/catapult/tracing/tracing/value/__init__.py +../../third_party/catapult/tracing/tracing/value/convert_chart_json.py +../../third_party/catapult/tracing/tracing_project.py +../util/lib/common/perf_result_data_type.py +../util/lib/common/perf_tests_results_helper.py +lacros_resource_sizes.py diff --git a/third_party/libwebrtc/build/lacros/metadata.json.in b/third_party/libwebrtc/build/lacros/metadata.json.in new file mode 100644 index 0000000000..3fceff256c --- /dev/null +++ b/third_party/libwebrtc/build/lacros/metadata.json.in @@ -0,0 +1,6 @@ +{ + "content": { + "version": "@MAJOR@.@MINOR@.@BUILD@.@PATCH@" + }, + "metadata_version": 1 +} diff --git a/third_party/libwebrtc/build/lacros/mojo_connection_lacros_launcher.py b/third_party/libwebrtc/build/lacros/mojo_connection_lacros_launcher.py new file mode 100755 index 0000000000..c537183448 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/mojo_connection_lacros_launcher.py @@ -0,0 +1,160 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Helps launch lacros-chrome with mojo connection established on Linux + or Chrome OS. Use on Chrome OS is for dev purposes. + + The main use case is to be able to launch lacros-chrome in a debugger. + + Please first launch an ash-chrome in the background as usual except without + the '--lacros-chrome-path' argument and with an additional + '--lacros-mojo-socket-for-testing' argument pointing to a socket path: + + XDG_RUNTIME_DIR=/tmp/ash_chrome_xdg_runtime ./out/ash/chrome \\ + --user-data-dir=/tmp/ash-chrome --enable-wayland-server \\ + --no-startup-window --enable-features=LacrosSupport \\ + --lacros-mojo-socket-for-testing=/tmp/lacros.sock + + Then, run this script with '-s' pointing to the same socket path used to + launch ash-chrome, followed by a command one would use to launch lacros-chrome + inside a debugger: + + EGL_PLATFORM=surfaceless XDG_RUNTIME_DIR=/tmp/ash_chrome_xdg_runtime \\ + ./build/lacros/mojo_connection_lacros_launcher.py -s /tmp/lacros.sock + gdb --args ./out/lacros-release/chrome --user-data-dir=/tmp/lacros-chrome +""" + +import argparse +import array +import contextlib +import os +import pathlib +import socket +import sys +import subprocess + + +_NUM_FDS_MAX = 3 + + +# contextlib.nullcontext is introduced in 3.7, while Python version on +# CrOS is still 3.6. This is for backward compatibility. +class NullContext: + def __init__(self, enter_ret=None): + self.enter_ret = enter_ret + + def __enter__(self): + return self.enter_ret + + def __exit__(self, exc_type, exc_value, trace): + pass + + +def _ReceiveFDs(sock): + """Receives FDs from ash-chrome that will be used to launch lacros-chrome. + + Args: + sock: A connected unix domain socket. + + Returns: + File objects for the mojo connection and maybe startup data file. + """ + # This function is borrowed from with modifications: + # https://docs.python.org/3/library/socket.html#socket.socket.recvmsg + fds = array.array("i") # Array of ints + # Along with the file descriptor, ash-chrome also sends the version in the + # regular data. + version, ancdata, _, _ = sock.recvmsg( + 1, socket.CMSG_LEN(fds.itemsize * _NUM_FDS_MAX)) + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS: + # There are three versions currently this script supports. + # The oldest one: ash-chrome returns one FD, the mojo connection of + # old bootstrap procedure (i.e., it will be BrowserService). + # The middle one: ash-chrome returns two FDs, the mojo connection of + # old bootstrap procedure, and the second for the start up data FD. + # The newest one: ash-chrome returns three FDs, the mojo connection of + # old bootstrap procedure, the second for the start up data FD, and + # the third for another mojo connection of new bootstrap procedure. + # TODO(crbug.com/1156033): Clean up the code to drop the support of + # oldest one after M91. + # TODO(crbug.com/1180712): Clean up the mojo procedure support of the + # the middle one after M92. + cmsg_len_candidates = [(i + 1) * fds.itemsize + for i in range(_NUM_FDS_MAX)] + assert len(cmsg_data) in cmsg_len_candidates, ( + 'CMSG_LEN is unexpected: %d' % (len(cmsg_data), )) + fds.frombytes(cmsg_data[:]) + + if version == b'\x00': + assert len(fds) in (1, 2, 3), 'Expecting exactly 1, 2, or 3 FDs' + legacy_mojo_fd = os.fdopen(fds[0]) + startup_fd = None if len(fds) < 2 else os.fdopen(fds[1]) + mojo_fd = None if len(fds) < 3 else os.fdopen(fds[2]) + elif version == b'\x01': + assert len(fds) == 2, 'Expecting exactly 2 FDs' + legacy_mojo_fd = None + startup_fd = os.fdopen(fds[0]) + mojo_fd = os.fdopen(fds[1]) + else: + raise AssertionError('Unknown version: \\x%s' % version.encode('hex')) + return legacy_mojo_fd, startup_fd, mojo_fd + + +def _MaybeClosing(fileobj): + """Returns closing context manager, if given fileobj is not None. + + If the given fileobj is none, return nullcontext. + """ + return (contextlib.closing if fileobj else NullContext)(fileobj) + + +def Main(): + arg_parser = argparse.ArgumentParser() + arg_parser.usage = __doc__ + arg_parser.add_argument( + '-s', + '--socket-path', + type=pathlib.Path, + required=True, + help='Absolute path to the socket that were used to start ash-chrome, ' + 'for example: "/tmp/lacros.socket"') + flags, args = arg_parser.parse_known_args() + + assert 'XDG_RUNTIME_DIR' in os.environ + assert os.environ.get('EGL_PLATFORM') == 'surfaceless' + + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + sock.connect(flags.socket_path.as_posix()) + legacy_mojo_connection, startup_connection, mojo_connection = ( + _ReceiveFDs(sock)) + + with _MaybeClosing(legacy_mojo_connection), \ + _MaybeClosing(startup_connection), \ + _MaybeClosing(mojo_connection): + cmd = args[:] + pass_fds = [] + if legacy_mojo_connection: + cmd.append('--mojo-platform-channel-handle=%d' % + legacy_mojo_connection.fileno()) + pass_fds.append(legacy_mojo_connection.fileno()) + else: + # TODO(crbug.com/1188020): This is for backward compatibility. + # We should remove this after M93 lacros is spread enough. + cmd.append('--mojo-platform-channel-handle=-1') + if startup_connection: + cmd.append('--cros-startup-data-fd=%d' % startup_connection.fileno()) + pass_fds.append(startup_connection.fileno()) + if mojo_connection: + cmd.append('--crosapi-mojo-platform-channel-handle=%d' % + mojo_connection.fileno()) + pass_fds.append(mojo_connection.fileno()) + proc = subprocess.Popen(cmd, pass_fds=pass_fds) + + return proc.wait() + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/lacros/test_runner.py b/third_party/libwebrtc/build/lacros/test_runner.py new file mode 100755 index 0000000000..397076a287 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/test_runner.py @@ -0,0 +1,581 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2020 The Chromium 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 facilitates running tests for lacros on Linux. + + In order to run lacros tests on Linux, please first follow bit.ly/3juQVNJ + to setup build directory with the lacros-chrome-on-linux build configuration, + and corresponding test targets are built successfully. + + * Example usages: + + ./build/lacros/test_runner.py test out/lacros/url_unittests + ./build/lacros/test_runner.py test out/lacros/browser_tests + + The commands above run url_unittests and browser_tests respecitively, and more + specifically, url_unitests is executed directly while browser_tests is + executed with the latest version of prebuilt ash-chrome, and the behavior is + controlled by |_TARGETS_REQUIRE_ASH_CHROME|, and it's worth noting that the + list is maintained manually, so if you see something is wrong, please upload a + CL to fix it. + + ./build/lacros/test_runner.py test out/lacros/browser_tests \\ + --gtest_filter=BrowserTest.Title + + The above command only runs 'BrowserTest.Title', and any argument accepted by + the underlying test binary can be specified in the command. + + ./build/lacros/test_runner.py test out/lacros/browser_tests \\ + --ash-chrome-version=793554 + + The above command runs tests with a given version of ash-chrome, which is + useful to reproduce test failures, the version corresponds to the commit + position of commits on the master branch, and a list of prebuilt versions can + be found at: gs://ash-chromium-on-linux-prebuilts/x86_64. + + ./testing/xvfb.py ./build/lacros/test_runner.py test out/lacros/browser_tests + + The above command starts ash-chrome with xvfb instead of an X11 window, and + it's useful when running tests without a display attached, such as sshing. + + For version skew testing when passing --ash-chrome-path-override, the runner + will try to find the ash major version and Lacros major version. If ash is + newer(major version larger), the runner will not run any tests and just + returns success. +""" + +import argparse +import json +import os +import logging +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import time +import zipfile + +_SRC_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) +sys.path.append(os.path.join(_SRC_ROOT, 'third_party', 'depot_tools')) + +# Base GS URL to store prebuilt ash-chrome. +_GS_URL_BASE = 'gs://ash-chromium-on-linux-prebuilts/x86_64' + +# Latest file version. +_GS_URL_LATEST_FILE = _GS_URL_BASE + '/latest/ash-chromium.txt' + +# GS path to the zipped ash-chrome build with any given version. +_GS_ASH_CHROME_PATH = 'ash-chromium.zip' + +# Directory to cache downloaded ash-chrome versions to avoid re-downloading. +_PREBUILT_ASH_CHROME_DIR = os.path.join(os.path.dirname(__file__), + 'prebuilt_ash_chrome') + +# Number of seconds to wait for ash-chrome to start. +ASH_CHROME_TIMEOUT_SECONDS = ( + 300 if os.environ.get('ASH_WRAPPER', None) else 10) + +# List of targets that require ash-chrome as a Wayland server in order to run. +_TARGETS_REQUIRE_ASH_CHROME = [ + 'app_shell_unittests', + 'aura_unittests', + 'browser_tests', + 'components_unittests', + 'compositor_unittests', + 'content_unittests', + 'dbus_unittests', + 'extensions_unittests', + 'media_unittests', + 'message_center_unittests', + 'snapshot_unittests', + 'sync_integration_tests', + 'unit_tests', + 'views_unittests', + 'wm_unittests', + + # regex patterns. + '.*_browsertests', + '.*interactive_ui_tests' +] + +# List of targets that require ash-chrome to support crosapi mojo APIs. +_TARGETS_REQUIRE_MOJO_CROSAPI = [ + # TODO(jamescook): Add 'browser_tests' after multiple crosapi connections + # are allowed. For now we only enable crosapi in targets that run tests + # serially. + 'interactive_ui_tests', + 'lacros_chrome_browsertests', + 'lacros_chrome_browsertests_run_in_series' +] + + +def _GetAshChromeDirPath(version): + """Returns a path to the dir storing the downloaded version of ash-chrome.""" + return os.path.join(_PREBUILT_ASH_CHROME_DIR, version) + + +def _remove_unused_ash_chrome_versions(version_to_skip): + """Removes unused ash-chrome versions to save disk space. + + Currently, when an ash-chrome zip is downloaded and unpacked, the atime/mtime + of the dir and the files are NOW instead of the time when they were built, but + there is no garanteen it will always be the behavior in the future, so avoid + removing the current version just in case. + + Args: + version_to_skip (str): the version to skip removing regardless of its age. + """ + days = 7 + expiration_duration = 60 * 60 * 24 * days + + for f in os.listdir(_PREBUILT_ASH_CHROME_DIR): + if f == version_to_skip: + continue + + p = os.path.join(_PREBUILT_ASH_CHROME_DIR, f) + if os.path.isfile(p): + # The prebuilt ash-chrome dir is NOT supposed to contain any files, remove + # them to keep the directory clean. + os.remove(p) + continue + chrome_path = os.path.join(p, 'test_ash_chrome') + if not os.path.exists(chrome_path): + chrome_path = p + age = time.time() - os.path.getatime(chrome_path) + if age > expiration_duration: + logging.info( + 'Removing ash-chrome: "%s" as it hasn\'t been used in the ' + 'past %d days', p, days) + shutil.rmtree(p) + +def _GsutilCopyWithRetry(gs_path, local_name, retry_times=3): + """Gsutil copy with retry. + + Args: + gs_path: The gs path for remote location. + local_name: The local file name. + retry_times: The total try times if the gsutil call fails. + + Raises: + RuntimeError: If failed to download the specified version, for example, + if the version is not present on gcs. + """ + import download_from_google_storage + gsutil = download_from_google_storage.Gsutil( + download_from_google_storage.GSUTIL_DEFAULT_PATH) + exit_code = 1 + retry = 0 + while exit_code and retry < retry_times: + retry += 1 + exit_code = gsutil.call('cp', gs_path, local_name) + if exit_code: + raise RuntimeError('Failed to download: "%s"' % gs_path) + + +def _DownloadAshChromeIfNecessary(version): + """Download a given version of ash-chrome if not already exists. + + Args: + version: A string representing the version, such as "793554". + + Raises: + RuntimeError: If failed to download the specified version, for example, + if the version is not present on gcs. + """ + + def IsAshChromeDirValid(ash_chrome_dir): + # This function assumes that once 'chrome' is present, other dependencies + # will be present as well, it's not always true, for example, if the test + # runner process gets killed in the middle of unzipping (~2 seconds), but + # it's unlikely for the assumption to break in practice. + return os.path.isdir(ash_chrome_dir) and os.path.isfile( + os.path.join(ash_chrome_dir, 'test_ash_chrome')) + + ash_chrome_dir = _GetAshChromeDirPath(version) + if IsAshChromeDirValid(ash_chrome_dir): + return + + shutil.rmtree(ash_chrome_dir, ignore_errors=True) + os.makedirs(ash_chrome_dir) + with tempfile.NamedTemporaryFile() as tmp: + logging.info('Ash-chrome version: %s', version) + gs_path = _GS_URL_BASE + '/' + version + '/' + _GS_ASH_CHROME_PATH + _GsutilCopyWithRetry(gs_path, tmp.name) + + # https://bugs.python.org/issue15795. ZipFile doesn't preserve permissions. + # And in order to workaround the issue, this function is created and used + # instead of ZipFile.extractall(). + # The solution is copied from: + # https://stackoverflow.com/questions/42326428/zipfile-in-python-file-permission + def ExtractFile(zf, info, extract_dir): + zf.extract(info.filename, path=extract_dir) + perm = info.external_attr >> 16 + os.chmod(os.path.join(extract_dir, info.filename), perm) + + with zipfile.ZipFile(tmp.name, 'r') as zf: + # Extra all files instead of just 'chrome' binary because 'chrome' needs + # other resources and libraries to run. + for info in zf.infolist(): + ExtractFile(zf, info, ash_chrome_dir) + + _remove_unused_ash_chrome_versions(version) + + +def _GetLatestVersionOfAshChrome(): + """Returns the latest version of uploaded ash-chrome.""" + with tempfile.NamedTemporaryFile() as tmp: + _GsutilCopyWithRetry(_GS_URL_LATEST_FILE, tmp.name) + with open(tmp.name, 'r') as f: + return f.read().strip() + + +def _WaitForAshChromeToStart(tmp_xdg_dir, lacros_mojo_socket_file, + enable_mojo_crosapi): + """Waits for Ash-Chrome to be up and running and returns a boolean indicator. + + Determine whether ash-chrome is up and running by checking whether two files + (lock file + socket) have been created in the |XDG_RUNTIME_DIR| and the lacros + mojo socket file has been created if enabling the mojo "crosapi" interface. + TODO(crbug.com/1107966): Figure out a more reliable hook to determine the + status of ash-chrome, likely through mojo connection. + + Args: + tmp_xdg_dir (str): Path to the XDG_RUNTIME_DIR. + lacros_mojo_socket_file (str): Path to the lacros mojo socket file. + enable_mojo_crosapi (bool): Whether to bootstrap the crosapi mojo interface + between ash and the lacros test binary. + + Returns: + A boolean indicating whether Ash-chrome is up and running. + """ + + def IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file, + enable_mojo_crosapi): + return (len(os.listdir(tmp_xdg_dir)) >= 2 + and (not enable_mojo_crosapi + or os.path.exists(lacros_mojo_socket_file))) + + time_counter = 0 + while not IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file, + enable_mojo_crosapi): + time.sleep(0.5) + time_counter += 0.5 + if time_counter > ASH_CHROME_TIMEOUT_SECONDS: + break + + return IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file, + enable_mojo_crosapi) + + +def _ExtractAshMajorVersion(file_path): + """Extract major version from file_path. + + File path like this: + ../../lacros_version_skew_tests_v94.0.4588.0/test_ash_chrome + + Returns: + int representing the major version. Or 0 if it can't extract + major version. + """ + m = re.search( + 'lacros_version_skew_tests_v(?P[0-9]+).[0-9]+.[0-9]+.[0-9]+/', + file_path) + if (m and 'version' in m.groupdict().keys()): + return int(m.group('version')) + logging.warning('Can not find the ash version in %s.' % file_path) + # Returns ash major version as 0, so we can still run tests. + # This is likely happen because user is running in local environments. + return 0 + + +def _FindLacrosMajorVersionFromMetadata(): + # This handles the logic on bots. When running on bots, + # we don't copy source files to test machines. So we build a + # metadata.json file which contains version information. + if not os.path.exists('metadata.json'): + logging.error('Can not determine current version.') + # Returns 0 so it can't run any tests. + return 0 + version = '' + with open('metadata.json', 'r') as file: + content = json.load(file) + version = content['content']['version'] + return int(version[:version.find('.')]) + + +def _FindLacrosMajorVersion(): + """Returns the major version in the current checkout. + + It would try to read src/chrome/VERSION. If it's not available, + then try to read metadata.json. + + Returns: + int representing the major version. Or 0 if it fails to + determine the version. + """ + version_file = os.path.abspath( + os.path.join(os.path.abspath(os.path.dirname(__file__)), + '../../chrome/VERSION')) + # This is mostly happens for local development where + # src/chrome/VERSION exists. + if os.path.exists(version_file): + lines = open(version_file, 'r').readlines() + return int(lines[0][lines[0].find('=') + 1:-1]) + return _FindLacrosMajorVersionFromMetadata() + + +def _ParseSummaryOutput(forward_args): + """Find the summary output file path. + + Args: + forward_args (list): Args to be forwarded to the test command. + + Returns: + None if not found, or str representing the output file path. + """ + logging.warning(forward_args) + for arg in forward_args: + if arg.startswith('--test-launcher-summary-output='): + return arg[len('--test-launcher-summary-output='):] + return None + + +def _RunTestWithAshChrome(args, forward_args): + """Runs tests with ash-chrome. + + Args: + args (dict): Args for this script. + forward_args (list): Args to be forwarded to the test command. + """ + if args.ash_chrome_path_override: + ash_chrome_file = args.ash_chrome_path_override + ash_major_version = _ExtractAshMajorVersion(ash_chrome_file) + lacros_major_version = _FindLacrosMajorVersion() + if ash_major_version > lacros_major_version: + logging.warning('''Not running any tests, because we do not \ +support version skew testing for Lacros M%s against ash M%s''' % + (lacros_major_version, ash_major_version)) + # Create an empty output.json file so result adapter can read + # the file. Or else result adapter will report no file found + # and result infra failure. + output_json = _ParseSummaryOutput(forward_args) + if output_json: + with open(output_json, 'w') as f: + f.write("""{"all_tests":[],"disabled_tests":[],"global_tags":[], +"per_iteration_data":[],"test_locations":{}}""") + # Although we don't run any tests, this is considered as success. + return 0 + if not os.path.exists(ash_chrome_file): + logging.error("""Can not find ash chrome at %s. Did you download \ +the ash from CIPD? If you don't plan to build your own ash, you need \ +to download first. Example commandlines: + $ cipd auth-login + $ echo "chromium/testing/linux-ash-chromium/x86_64/ash.zip \ +version:92.0.4515.130" > /tmp/ensure-file.txt + $ cipd ensure -ensure-file /tmp/ensure-file.txt \ +-root lacros_version_skew_tests_v92.0.4515.130 + Then you can use --ash-chrome-path-override=\ +lacros_version_skew_tests_v92.0.4515.130/test_ash_chrome +""" % ash_chrome_file) + return 1 + elif args.ash_chrome_path: + ash_chrome_file = args.ash_chrome_path + else: + ash_chrome_version = (args.ash_chrome_version + or _GetLatestVersionOfAshChrome()) + _DownloadAshChromeIfNecessary(ash_chrome_version) + logging.info('Ash-chrome version: %s', ash_chrome_version) + + ash_chrome_file = os.path.join(_GetAshChromeDirPath(ash_chrome_version), + 'test_ash_chrome') + try: + # Starts Ash-Chrome. + tmp_xdg_dir_name = tempfile.mkdtemp() + tmp_ash_data_dir_name = tempfile.mkdtemp() + + # Please refer to below file for how mojo connection is set up in testing. + # //chrome/browser/ash/crosapi/test_mojo_connection_manager.h + lacros_mojo_socket_file = '%s/lacros.sock' % tmp_ash_data_dir_name + lacros_mojo_socket_arg = ('--lacros-mojo-socket-for-testing=%s' % + lacros_mojo_socket_file) + enable_mojo_crosapi = any(t == os.path.basename(args.command) + for t in _TARGETS_REQUIRE_MOJO_CROSAPI) + + ash_process = None + ash_env = os.environ.copy() + ash_env['XDG_RUNTIME_DIR'] = tmp_xdg_dir_name + ash_cmd = [ + ash_chrome_file, + '--user-data-dir=%s' % tmp_ash_data_dir_name, + '--enable-wayland-server', + '--no-startup-window', + ] + if enable_mojo_crosapi: + ash_cmd.append(lacros_mojo_socket_arg) + + # Users can specify a wrapper for the ash binary to do things like + # attaching debuggers. For example, this will open a new terminal window + # and run GDB. + # $ export ASH_WRAPPER="gnome-terminal -- gdb --ex=r --args" + ash_wrapper = os.environ.get('ASH_WRAPPER', None) + if ash_wrapper: + logging.info('Running ash with "ASH_WRAPPER": %s', ash_wrapper) + ash_cmd = list(ash_wrapper.split()) + ash_cmd + + ash_process_has_started = False + total_tries = 3 + num_tries = 0 + while not ash_process_has_started and num_tries < total_tries: + num_tries += 1 + ash_process = subprocess.Popen(ash_cmd, env=ash_env) + ash_process_has_started = _WaitForAshChromeToStart( + tmp_xdg_dir_name, lacros_mojo_socket_file, enable_mojo_crosapi) + if ash_process_has_started: + break + + logging.warning('Starting ash-chrome timed out after %ds', + ASH_CHROME_TIMEOUT_SECONDS) + logging.warning('Printing the output of "ps aux" for debugging:') + subprocess.call(['ps', 'aux']) + if ash_process and ash_process.poll() is None: + ash_process.kill() + + if not ash_process_has_started: + raise RuntimeError('Timed out waiting for ash-chrome to start') + + # Starts tests. + if enable_mojo_crosapi: + forward_args.append(lacros_mojo_socket_arg) + + test_env = os.environ.copy() + test_env['EGL_PLATFORM'] = 'surfaceless' + test_env['XDG_RUNTIME_DIR'] = tmp_xdg_dir_name + test_process = subprocess.Popen([args.command] + forward_args, env=test_env) + return test_process.wait() + + finally: + if ash_process and ash_process.poll() is None: + ash_process.terminate() + # Allow process to do cleanup and exit gracefully before killing. + time.sleep(0.5) + ash_process.kill() + + shutil.rmtree(tmp_xdg_dir_name, ignore_errors=True) + shutil.rmtree(tmp_ash_data_dir_name, ignore_errors=True) + + +def _RunTestDirectly(args, forward_args): + """Runs tests by invoking the test command directly. + + args (dict): Args for this script. + forward_args (list): Args to be forwarded to the test command. + """ + try: + p = None + p = subprocess.Popen([args.command] + forward_args) + return p.wait() + finally: + if p and p.poll() is None: + p.terminate() + time.sleep(0.5) + p.kill() + + +def _HandleSignal(sig, _): + """Handles received signals to make sure spawned test process are killed. + + sig (int): An integer representing the received signal, for example SIGTERM. + """ + logging.warning('Received signal: %d, killing spawned processes', sig) + + # Don't do any cleanup here, instead, leave it to the finally blocks. + # Assumption is based on https://docs.python.org/3/library/sys.html#sys.exit: + # cleanup actions specified by finally clauses of try statements are honored. + + # https://tldp.org/LDP/abs/html/exitcodes.html: + # Exit code 128+n -> Fatal error signal "n". + sys.exit(128 + sig) + + +def _RunTest(args, forward_args): + """Runs tests with given args. + + args (dict): Args for this script. + forward_args (list): Args to be forwarded to the test command. + + Raises: + RuntimeError: If the given test binary doesn't exist or the test runner + doesn't know how to run it. + """ + + if not os.path.isfile(args.command): + raise RuntimeError('Specified test command: "%s" doesn\'t exist' % + args.command) + + # |_TARGETS_REQUIRE_ASH_CHROME| may not always be accurate as it is updated + # with a best effort only, therefore, allow the invoker to override the + # behavior with a specified ash-chrome version, which makes sure that + # automated CI/CQ builders would always work correctly. + requires_ash_chrome = any( + re.match(t, os.path.basename(args.command)) + for t in _TARGETS_REQUIRE_ASH_CHROME) + if not requires_ash_chrome and not args.ash_chrome_version: + return _RunTestDirectly(args, forward_args) + + return _RunTestWithAshChrome(args, forward_args) + + +def Main(): + for sig in (signal.SIGTERM, signal.SIGINT): + signal.signal(sig, _HandleSignal) + + logging.basicConfig(level=logging.INFO) + arg_parser = argparse.ArgumentParser() + arg_parser.usage = __doc__ + + subparsers = arg_parser.add_subparsers() + + test_parser = subparsers.add_parser('test', help='Run tests') + test_parser.set_defaults(func=_RunTest) + + test_parser.add_argument( + 'command', + help='A single command to invoke the tests, for example: ' + '"./url_unittests". Any argument unknown to this test runner script will ' + 'be forwarded to the command, for example: "--gtest_filter=Suite.Test"') + + version_group = test_parser.add_mutually_exclusive_group() + version_group.add_argument( + '--ash-chrome-version', + type=str, + help='Version of an prebuilt ash-chrome to use for testing, for example: ' + '"793554", and the version corresponds to the commit position of commits ' + 'on the main branch. If not specified, will use the latest version ' + 'available') + version_group.add_argument( + '--ash-chrome-path', + type=str, + help='Path to an locally built ash-chrome to use for testing. ' + 'In general you should build //chrome/test:test_ash_chrome.') + + # This is for version skew testing. The current CI/CQ builder builds + # an ash chrome and pass it using --ash-chrome-path. In order to use the same + # builder for version skew testing, we use a new argument to override + # the ash chrome. + test_parser.add_argument( + '--ash-chrome-path-override', + type=str, + help='The same as --ash-chrome-path. But this will override ' + '--ash-chrome-path or --ash-chrome-version if any of these ' + 'arguments exist.') + args = arg_parser.parse_known_args() + return args[0].func(args[0], args[1]) + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/lacros/test_runner_test.py b/third_party/libwebrtc/build/lacros/test_runner_test.py new file mode 100755 index 0000000000..9713dfc387 --- /dev/null +++ b/third_party/libwebrtc/build/lacros/test_runner_test.py @@ -0,0 +1,267 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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 subprocess +import sys +import tempfile +import time +import unittest + +import mock +from parameterized import parameterized + +import test_runner + + +class TestRunnerTest(unittest.TestCase): + def setUp(self): + logging.disable(logging.CRITICAL) + time.sleep = mock.Mock() + + def tearDown(self): + logging.disable(logging.NOTSET) + + @parameterized.expand([ + 'url_unittests', + './url_unittests', + 'out/release/url_unittests', + './out/release/url_unittests', + ]) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that the test runner doesn't attempt to download ash-chrome if not + # required. + def test_do_not_require_ash_chrome(self, command, mock_popen, mock_download, + _): + args = ['script_name', 'test', command] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + self.assertEqual(1, mock_popen.call_count) + mock_popen.assert_called_with([command]) + self.assertFalse(mock_download.called) + + @parameterized.expand([ + 'browser_tests', + 'components_browsertests', + 'content_browsertests', + 'lacros_chrome_browsertests', + ]) + @mock.patch.object(os, + 'listdir', + return_value=['wayland-0', 'wayland-0.lock']) + @mock.patch.object(tempfile, + 'mkdtemp', + side_effect=['/tmp/xdg', '/tmp/ash-data']) + @mock.patch.object(os.environ, 'copy', side_effect=[{}, {}]) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, + '_GetLatestVersionOfAshChrome', + return_value='793554') + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that the test runner downloads and spawns ash-chrome if ash-chrome is + # required. + def test_require_ash_chrome(self, command, mock_popen, mock_download, *_): + args = ['script_name', 'test', command] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + mock_download.assert_called_with('793554') + self.assertEqual(2, mock_popen.call_count) + + ash_chrome_args = mock_popen.call_args_list[0][0][0] + self.assertTrue(ash_chrome_args[0].endswith( + 'build/lacros/prebuilt_ash_chrome/793554/test_ash_chrome')) + expected_ash_chrome_args = [ + '--user-data-dir=/tmp/ash-data', + '--enable-wayland-server', + '--no-startup-window', + ] + if command == 'lacros_chrome_browsertests': + expected_ash_chrome_args.append( + '--lacros-mojo-socket-for-testing=/tmp/ash-data/lacros.sock') + self.assertListEqual(expected_ash_chrome_args, ash_chrome_args[1:]) + ash_chrome_env = mock_popen.call_args_list[0][1].get('env', {}) + self.assertDictEqual({'XDG_RUNTIME_DIR': '/tmp/xdg'}, ash_chrome_env) + + test_args = mock_popen.call_args_list[1][0][0] + if command == 'lacros_chrome_browsertests': + self.assertListEqual([ + command, + '--lacros-mojo-socket-for-testing=/tmp/ash-data/lacros.sock' + ], test_args) + else: + self.assertListEqual([command], test_args) + + test_env = mock_popen.call_args_list[1][1].get('env', {}) + self.assertDictEqual( + { + 'XDG_RUNTIME_DIR': '/tmp/xdg', + 'EGL_PLATFORM': 'surfaceless' + }, test_env) + + + @mock.patch.object(os, + 'listdir', + return_value=['wayland-0', 'wayland-0.lock']) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, + '_GetLatestVersionOfAshChrome', + return_value='793554') + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that when a ash-chrome version is specified, that version is used + # instead of the latest one. + def test_specify_ash_chrome_version(self, mock_popen, mock_download, *_): + args = [ + 'script_name', 'test', 'browser_tests', '--ash-chrome-version', '781122' + ] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + mock_download.assert_called_with('781122') + + @mock.patch.object(os, + 'listdir', + return_value=['wayland-0', 'wayland-0.lock']) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that if a ash-chrome version is specified, uses ash-chrome to run + # tests anyway even if |_TARGETS_REQUIRE_ASH_CHROME| indicates an ash-chrome + # is not required. + def test_overrides_do_not_require_ash_chrome(self, mock_popen, mock_download, + *_): + args = [ + 'script_name', 'test', './url_unittests', '--ash-chrome-version', + '793554' + ] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + mock_download.assert_called_with('793554') + self.assertEqual(2, mock_popen.call_count) + + @mock.patch.object(os, + 'listdir', + return_value=['wayland-0', 'wayland-0.lock']) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, '_GetLatestVersionOfAshChrome') + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that when an ash-chrome path is specified, the test runner doesn't try + # to download prebuilt ash-chrome. + def test_specify_ash_chrome_path(self, mock_popen, mock_download, + mock_get_latest_version, *_): + args = [ + 'script_name', + 'test', + 'browser_tests', + '--ash-chrome-path', + '/ash/test_ash_chrome', + ] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + self.assertFalse(mock_get_latest_version.called) + self.assertFalse(mock_download.called) + + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that arguments not known to the test runner are forwarded to the + # command that invokes tests. + def test_command_arguments(self, mock_popen, mock_download, _): + args = [ + 'script_name', 'test', './url_unittests', '--gtest_filter=Suite.Test' + ] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + mock_popen.assert_called_with( + ['./url_unittests', '--gtest_filter=Suite.Test']) + self.assertFalse(mock_download.called) + + @mock.patch.dict(os.environ, {'ASH_WRAPPER': 'gdb --args'}, clear=False) + @mock.patch.object(os, + 'listdir', + return_value=['wayland-0', 'wayland-0.lock']) + @mock.patch.object(tempfile, + 'mkdtemp', + side_effect=['/tmp/xdg', '/tmp/ash-data']) + @mock.patch.object(os.environ, 'copy', side_effect=[{}, {}]) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, + '_GetLatestVersionOfAshChrome', + return_value='793554') + @mock.patch.object(test_runner, '_DownloadAshChromeIfNecessary') + @mock.patch.object(subprocess, 'Popen', return_value=mock.Mock()) + # Tests that, when the ASH_WRAPPER environment variable is set, it forwards + # the commands to the invocation of ash. + def test_ash_wrapper(self, mock_popen, *_): + args = [ + 'script_name', 'test', './browser_tests', '--gtest_filter=Suite.Test' + ] + with mock.patch.object(sys, 'argv', args): + test_runner.Main() + ash_args = mock_popen.call_args_list[0][0][0] + self.assertTrue(ash_args[2].endswith('test_ash_chrome')) + self.assertEqual(['gdb', '--args'], ash_args[:2]) + + + # Test when ash is newer, test runner skips running tests and returns 0. + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(test_runner, '_FindLacrosMajorVersion', return_value=91) + def test_version_skew_ash_newer(self, *_): + args = [ + 'script_name', 'test', './browser_tests', '--gtest_filter=Suite.Test', + '--ash-chrome-path-override=\ +lacros_version_skew_tests_v92.0.100.0/test_ash_chrome' + ] + with mock.patch.object(sys, 'argv', args): + self.assertEqual(test_runner.Main(), 0) + + @mock.patch.object(os.path, 'exists', return_value=True) + def test_lacros_version_from_chrome_version(self, *_): + version_data = '''\ +MAJOR=95 +MINOR=0 +BUILD=4615 +PATCH=0\ +''' + open_lib = '__builtin__.open' + if sys.version_info[0] >= 3: + open_lib = 'builtins.open' + with mock.patch(open_lib, + mock.mock_open(read_data=version_data)) as mock_file: + version = test_runner._FindLacrosMajorVersion() + self.assertEqual(95, version) + + @mock.patch.object(os.path, 'exists', return_value=True) + def test_lacros_version_from_metadata(self, *_): + metadata_json = ''' +{ + "content": { + "version": "92.1.4389.2" + }, + "metadata_version": 1 +} + ''' + open_lib = '__builtin__.open' + if sys.version_info[0] >= 3: + open_lib = 'builtins.open' + with mock.patch(open_lib, + mock.mock_open(read_data=metadata_json)) as mock_file: + version = test_runner._FindLacrosMajorVersionFromMetadata() + self.assertEqual(92, version) + mock_file.assert_called_with('metadata.json', 'r') + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/landmine_utils.py b/third_party/libwebrtc/build/landmine_utils.py new file mode 100644 index 0000000000..a3f21ff1b8 --- /dev/null +++ b/third_party/libwebrtc/build/landmine_utils.py @@ -0,0 +1,33 @@ +# 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 sys + + +def IsWindows(): + return sys.platform in ['win32', 'cygwin'] + + +def IsLinux(): + return sys.platform.startswith(('linux', 'freebsd', 'netbsd', 'openbsd')) + + +def IsMac(): + return sys.platform == 'darwin' + + +def host_os(): + """ + Returns a string representing the host_os of the current system. + Possible values: 'win', 'mac', 'linux', 'unknown'. + """ + if IsWindows(): + return 'win' + elif IsLinux(): + return 'linux' + elif IsMac(): + return 'mac' + else: + return 'unknown' diff --git a/third_party/libwebrtc/build/landmines.py b/third_party/libwebrtc/build/landmines.py new file mode 100755 index 0000000000..f5adf80f92 --- /dev/null +++ b/third_party/libwebrtc/build/landmines.py @@ -0,0 +1,151 @@ +#!/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 gclient runhooks 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. + +Before adding or changing a landmine consider the consequences of doing so. +Doing so will wipe out every output directory on every Chrome developer's +machine. This can be particularly problematic on Windows where the directory +deletion may well fail (locked files, command prompt in the directory, etc.), +and generated .sln and .vcxproj files will be deleted. + +This output directory deletion will be repeated when going back and forth across +the change that added the landmine, adding to the cost. There are usually less +troublesome alternatives. +""" + +import difflib +import errno +import logging +import optparse +import os +import sys +import subprocess +import time + +import clobber +import landmine_utils + + +def get_build_dir(src_dir): + r""" + Returns the absolute path to the directory containing the build directories. + Examples: + 'C:\src\out' + '/b/s/w/ir/cache/builder/src/out' + """ + 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 = 'out' + return os.path.abspath(os.path.join(src_dir, output_dir)) + + +def clobber_if_necessary(new_landmines, src_dir, landmines_path): + """Does the work of setting, planting, and triggering landmines.""" + out_dir = get_build_dir(src_dir) + 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) + sys.stdout.flush() + + 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 an options object containing the configuration for this script.""" + parser = optparse.OptionParser() + parser.add_option( + '-s', '--landmine-scripts', action='append', + 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('-d', '--src-dir', + help='Path of the source root dir. Overrides the default location of the ' + 'source root dir when calculating the build directory.') + parser.add_option( + '-l', + '--landmines-path', + help='Path to the landmines file to use (defaults to .landmines)') + 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) + + if options.src_dir: + if not os.path.isdir(options.src_dir): + parser.error('Cannot find source root dir at %s' % options.src_dir) + logging.debug('Overriding source root dir. Using: %s', options.src_dir) + else: + options.src_dir = \ + os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + if not options.landmine_scripts: + options.landmine_scripts = [os.path.join(options.src_dir, 'build', + 'get_landmines.py')] + + extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT') + if extra_script: + options.landmine_scripts += [extra_script] + + return options + + +def main(): + options = process_options() + + landmines = [] + for s in options.landmine_scripts: + proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE, + universal_newlines=True) + output, _ = proc.communicate() + landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()]) + if options.landmines_path: + landmines_path = options.landmines_path + else: + landmines_path = os.path.join(options.src_dir, '.landmines') + clobber_if_necessary(landmines, options.src_dir, + os.path.normpath(landmines_path)) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/linux/BUILD.gn b/third_party/libwebrtc/build/linux/BUILD.gn new file mode 100644 index 0000000000..54314c7687 --- /dev/null +++ b/third_party/libwebrtc/build/linux/BUILD.gn @@ -0,0 +1,31 @@ +# 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("//build/config/features.gni") +import("//build/config/freetype/freetype.gni") +import("//build/config/linux/pkg_config.gni") + +if (use_gio) { + pkg_config("gio_config") { + packages = [ "gio-2.0" ] + + defines = [ "USE_GIO" ] + } +} + +# Looking for libspeechd? Use //third_party/speech-dispatcher + +if (use_system_freetype) { + assert(!is_chromecast) + + # Only provided for distributions which prefer to keep linking to FreeType on + # the system, use with caution,for details see build/config/freetype/BUILD.gn. + pkg_config("freetype_from_pkgconfig") { + visibility = [ + "//third_party:freetype_harfbuzz", + "//third_party/harfbuzz-ng:harfbuzz_source", + ] + packages = [ "freetype2" ] + } +} diff --git a/third_party/libwebrtc/build/linux/OWNERS b/third_party/libwebrtc/build/linux/OWNERS new file mode 100644 index 0000000000..8e1cb55729 --- /dev/null +++ b/third_party/libwebrtc/build/linux/OWNERS @@ -0,0 +1,3 @@ +mmoss@chromium.org +thestig@chromium.org +thomasanderson@chromium.org diff --git a/third_party/libwebrtc/build/linux/chrome.map b/third_party/libwebrtc/build/linux/chrome.map new file mode 100644 index 0000000000..718796f8c4 --- /dev/null +++ b/third_party/libwebrtc/build/linux/chrome.map @@ -0,0 +1,89 @@ +{ +global: + __bss_start; + __data_start; + data_start; + _edata; + _end; + _IO_stdin_used; + + # Initialization and finalization functions for static global + # variables. + _fini; + _init; + __libc_csu_fini; + __libc_csu_init; + + # Chrome's main function. Exported for historical purposes. + ChromeMain; + + # Program entry point. + _start; + + # Memory allocation symbols. We want chrome and any libraries to + # share the same heap, so it is correct to export these symbols. + calloc; + cfree; + free; + __free_hook; + __libc_calloc; + __libc_cfree; + __libc_free; + __libc_malloc; + __libc_memalign; + __libc_pvalloc; + __libc_realloc; + __libc_valloc; + mallinfo; + malloc; + __malloc_hook; + malloc_size; + malloc_stats; + malloc_usable_size; + mallopt; + memalign; + __memalign_hook; + __posix_memalign; + posix_memalign; + pvalloc; + realloc; + __realloc_hook; + valloc; + + # Various flavors of operator new and operator delete. + _ZdaPv; + _ZdaPvm; + _ZdaPvmSt11align_val_t; + _ZdaPvRKSt9nothrow_t; + _ZdaPvSt11align_val_t; + _ZdaPvSt11align_val_tRKSt9nothrow_t; + _ZdlPv; + _ZdlPvm; + _ZdlPvmSt11align_val_t; + _ZdlPvRKSt9nothrow_t; + _ZdlPvSt11align_val_t; + _ZdlPvSt11align_val_tRKSt9nothrow_t; + _Znam; + _ZnamRKSt9nothrow_t; + _ZnamSt11align_val_t; + _ZnamSt11align_val_tRKSt9nothrow_t; + _Znwm; + _ZnwmRKSt9nothrow_t; + _ZnwmSt11align_val_t; + _ZnwmSt11align_val_tRKSt9nothrow_t; + + # Various flavors of localtime(). These are exported by the chrome + # sandbox to intercept calls to localtime(), which would otherwise + # fail in untrusted processes that don't have permission to read + # /etc/localtime. These overrides forward the request to the browser + # process, which uses dlsym(localtime) to make the real calls. + localtime; + localtime64; + localtime64_r; + localtime_r; + + v8dbg_*; + +local: + *; +}; diff --git a/third_party/libwebrtc/build/linux/dump_app_syms.py b/third_party/libwebrtc/build/linux/dump_app_syms.py new file mode 100644 index 0000000000..f156baf3b9 --- /dev/null +++ b/third_party/libwebrtc/build/linux/dump_app_syms.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. + +# Helper script to run dump_syms on Chrome Linux executables and strip +# them if needed. + +from __future__ import print_function + +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, infile], stdout=outfileobj) + +if strip_binary != '0': + subprocess.check_call(['strip', infile]) diff --git a/third_party/libwebrtc/build/linux/extract_symbols.gni b/third_party/libwebrtc/build/linux/extract_symbols.gni new file mode 100644 index 0000000000..c92aaa0a19 --- /dev/null +++ b/third_party/libwebrtc/build/linux/extract_symbols.gni @@ -0,0 +1,41 @@ +# Copyright 2018 The Chromium 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/toolchain/toolchain.gni") + +# Extracts symbols from a binary into a symbol file using dump_app_syms.py. +# +# Args: +# binary: Path to the binary containing symbols to extract, e.g.: +# "$root_out_dir/chrome" +# symbol_file: Desired output file for symbols, e.g.: +# "$root_out_dir/chrome.breakpad.$target_cpu" +template("extract_symbols") { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + action("${target_name}") { + dump_syms_label = "//third_party/breakpad:dump_syms($host_toolchain)" + dump_syms_binary = + get_label_info(dump_syms_label, "root_out_dir") + "/" + "dump_syms" + + pool = "//build/toolchain:link_pool($default_toolchain)" + script = "//build/linux/dump_app_syms.py" + inputs = [ + invoker.binary, + dump_syms_binary, + ] + outputs = [ invoker.symbol_file ] + args = [ + "./" + rebase_path(dump_syms_binary, root_build_dir), + "0", # strip_binary = false + rebase_path(invoker.binary, root_build_dir), + rebase_path(invoker.symbol_file, root_build_dir), + ] + + deps += [ dump_syms_label ] + } +} diff --git a/third_party/libwebrtc/build/linux/install-chromeos-fonts.py b/third_party/libwebrtc/build/linux/install-chromeos-fonts.py new file mode 100755 index 0000000000..da8fb40f48 --- /dev/null +++ b/third_party/libwebrtc/build/linux/install-chromeos-fonts.py @@ -0,0 +1,120 @@ +#!/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. + +from __future__ import print_function + +import os +import shutil +import subprocess +import sys + +URL_TEMPLATE = ('https://commondatastorage.googleapis.com/chromeos-localmirror/' + 'distfiles/%(name)s-%(version)s.tar.bz2') + +# Taken from the media-fonts/ ebuilds in chromiumos-overlay. +# noto-cjk used to be here, but is removed because fc-cache takes too long +# regenerating the fontconfig cache (See crbug.com/697954.) +# TODO(jshin): Add it back when the above issue can be avoided. +SOURCES = [ + { + 'name': 'notofonts', + 'version': '20161129' + }, { + 'name': 'robotofonts', + 'version': '2.132' + } +] + +URLS = sorted([URL_TEMPLATE % d for d in SOURCES]) +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() == '\n'.join(URLS): + 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, 0o755) + + print("Installing Chrome OS fonts to %s." % dest_dir) + for url in URLS: + 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('\n'.join(URLS)) + + for base, dirs, files in os.walk(dest_dir): + for dir in dirs: + os.chmod(os.path.join(base, dir), 0o755) + for file in files: + os.chmod(os.path.join(base, file), 0o644) + + print("""\ + +Chrome OS font rendering settings are specified using Fontconfig. If your +system's configuration doesn't match Chrome OS's (which vary for different +devices), fonts may be rendered with different subpixel rendering, subpixel +positioning, or hinting settings. This may affect font metrics. + +Chrome OS's settings are stored in the media-libs/fontconfig package, which is +at src/third_party/chromiumos-overlay/media-libs/fontconfig in a Chrome OS +checkout. You can configure your system to match Chrome OS's defaults by +creating or editing a ~/.fonts.conf file: + + + + + + true + true + true + hintslight + rgb + + + +To load additional per-font configs (and assuming you have Chrome OS checked +out), add the following immediately before the "" line: + + /path/to/src/third_party/chromiumos-overlay/media-libs/fontconfig/files/local.conf +""") + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/linux/libncursesw/DIR_METADATA b/third_party/libwebrtc/build/linux/libncursesw/DIR_METADATA new file mode 100644 index 0000000000..6bbf490f43 --- /dev/null +++ b/third_party/libwebrtc/build/linux/libncursesw/DIR_METADATA @@ -0,0 +1,5 @@ +monorail { + component: "Internals>Accessibility" +} + +team_email: "chromium-accessibility@chromium.org" diff --git a/third_party/libwebrtc/build/linux/libncursesw/OWNERS b/third_party/libwebrtc/build/linux/libncursesw/OWNERS new file mode 100644 index 0000000000..976b9550c6 --- /dev/null +++ b/third_party/libwebrtc/build/linux/libncursesw/OWNERS @@ -0,0 +1 @@ +file://ui/accessibility/OWNERS diff --git a/third_party/libwebrtc/build/linux/libpci/BUILD.gn b/third_party/libwebrtc/build/linux/libpci/BUILD.gn new file mode 100644 index 0000000000..2d1e267e12 --- /dev/null +++ b/third_party/libwebrtc/build/linux/libpci/BUILD.gn @@ -0,0 +1,22 @@ +# 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. + +import("//tools/generate_library_loader/generate_library_loader.gni") + +# This generates a target named "libpci". +generate_library_loader("libpci") { + name = "LibPciLoader" + output_h = "libpci.h" + output_cc = "libpci_loader.cc" + header = "" + + functions = [ + "pci_alloc", + "pci_init", + "pci_cleanup", + "pci_scan_bus", + "pci_fill_info", + "pci_lookup_name", + ] +} diff --git a/third_party/libwebrtc/build/linux/libudev/BUILD.gn b/third_party/libwebrtc/build/linux/libudev/BUILD.gn new file mode 100644 index 0000000000..dcd9f234db --- /dev/null +++ b/third_party/libwebrtc/build/linux/libudev/BUILD.gn @@ -0,0 +1,66 @@ +# 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. + +import("//tools/generate_library_loader/generate_library_loader.gni") + +libudev_functions = [ + "udev_device_get_action", + "udev_device_get_devnode", + "udev_device_get_devtype", + "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", +] + +# This generates a target named "udev0_loader". +generate_library_loader("udev0_loader") { + name = "LibUdev0Loader" + output_h = "libudev0.h" + output_cc = "libudev0_loader.cc" + header = "\"third_party/libudev/libudev0.h\"" + + functions = libudev_functions +} + +# This generates a target named "udev1_loader". +generate_library_loader("udev1_loader") { + name = "LibUdev1Loader" + output_h = "libudev1.h" + output_cc = "libudev1_loader.cc" + header = "\"third_party/libudev/libudev1.h\"" + + functions = libudev_functions +} + +group("libudev") { + public_deps = [ + ":udev0_loader", + ":udev1_loader", + ] +} diff --git a/third_party/libwebrtc/build/linux/rewrite_dirs.py b/third_party/libwebrtc/build/linux/rewrite_dirs.py new file mode 100755 index 0000000000..17659c3d3c --- /dev/null +++ b/third_party/libwebrtc/build/linux/rewrite_dirs.py @@ -0,0 +1,73 @@ +#!/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.""" + +from __future__ import print_function + +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/third_party/libwebrtc/build/linux/strip_binary.gni b/third_party/libwebrtc/build/linux/strip_binary.gni new file mode 100644 index 0000000000..ddc42cc4e0 --- /dev/null +++ b/third_party/libwebrtc/build/linux/strip_binary.gni @@ -0,0 +1,44 @@ +# Copyright 2021 The Chromium 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/toolchain/toolchain.gni") + +# Extracts symbols from a binary into a symbol file. +# +# Args: +# binary_input: Path to the binary containing symbols to extract, e.g.: +# "$root_out_dir/chrome" +# symbol_output: Desired output file for symbols, e.g.: +# "$root_out_dir/chrome.debug" +# stripped_binary_output: Desired output file for stripped file, e.g.: +# "$root_out_dir/chrome.stripped" +template("strip_binary") { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + action("${target_name}") { + eu_strip_binary = "//buildtools/third_party/eu-strip/bin/eu-strip" + script = "//build/linux/strip_binary.py" + inputs = [ + invoker.binary_input, + eu_strip_binary, + ] + outputs = [ + invoker.symbol_output, + invoker.stripped_binary_output, + ] + args = [ + "--eu-strip-binary-path", + rebase_path(eu_strip_binary, root_build_dir), + "--symbol-output", + rebase_path(invoker.symbol_output, root_build_dir), + "--stripped-binary-output", + rebase_path(invoker.stripped_binary_output, root_build_dir), + "--binary-input", + rebase_path(invoker.binary_input, root_build_dir), + ] + } +} diff --git a/third_party/libwebrtc/build/linux/strip_binary.py b/third_party/libwebrtc/build/linux/strip_binary.py new file mode 100755 index 0000000000..00b4089e76 --- /dev/null +++ b/third_party/libwebrtc/build/linux/strip_binary.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# Copyright 2021 The Chromium 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 subprocess +import sys + + +def main(): + argparser = argparse.ArgumentParser(description='eu-strip binary.') + + argparser.add_argument('--eu-strip-binary-path', help='eu-strip path.') + argparser.add_argument('--binary-input', help='exe file path.') + argparser.add_argument('--symbol-output', help='debug file path.') + argparser.add_argument('--stripped-binary-output', help='stripped file path.') + args = argparser.parse_args() + + cmd_line = [ + args.eu_strip_binary_path, '-o', args.stripped_binary_output, '-f', + args.symbol_output, args.binary_input + ] + + process = subprocess.Popen(cmd_line) + + return process.returncode + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/build_and_upload.py b/third_party/libwebrtc/build/linux/sysroot_scripts/build_and_upload.py new file mode 100755 index 0000000000..1a24da2906 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/build_and_upload.py @@ -0,0 +1,101 @@ +#!/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. + +"""Automates running BuildPackageLists, BuildSysroot, and +UploadSysroot for each supported arch of each sysroot creator. +""" + +from __future__ import print_function + +import glob +import hashlib +import json +import multiprocessing +import os +import re +import string +import subprocess +import sys + + +def run_script(args): + fnull = open(os.devnull, 'w') + subprocess.check_call(args, stdout=fnull, stderr=fnull) + + +def sha1sumfile(filename): + sha1 = hashlib.sha1() + with open(filename, 'rb') as f: + while True: + data = f.read(65536) + if not data: + break + sha1.update(data) + return sha1.hexdigest() + + +def get_proc_output(args): + return subprocess.check_output(args).strip() + + +def build_and_upload(script_path, distro, release, arch, lock): + script_dir = os.path.dirname(os.path.realpath(__file__)) + + run_script([script_path, 'BuildSysroot' + arch]) + run_script([script_path, 'UploadSysroot' + arch]) + + tarball = '%s_%s_%s_sysroot.tar.xz' % (distro, release, arch.lower()) + tarxz_path = os.path.join(script_dir, "..", "..", "..", "out", + "sysroot-build", release, tarball) + sha1sum = sha1sumfile(tarxz_path) + sysroot_dir = '%s_%s_%s-sysroot' % (distro, release, arch.lower()) + + sysroot_metadata = { + 'Tarball': tarball, + 'Sha1Sum': sha1sum, + 'SysrootDir': sysroot_dir, + } + with lock: + with open(os.path.join(script_dir, 'sysroots.json'), 'rw+') as f: + sysroots = json.load(f) + sysroots["%s_%s" % (release, arch.lower())] = sysroot_metadata + f.seek(0) + f.truncate() + f.write( + json.dumps( + sysroots, sort_keys=True, indent=4, separators=(',', ': '))) + f.write('\n') + + +def main(): + script_dir = os.path.dirname(os.path.realpath(__file__)) + procs = [] + lock = multiprocessing.Lock() + for filename in glob.glob(os.path.join(script_dir, 'sysroot-creator-*.sh')): + script_path = os.path.join(script_dir, filename) + distro = get_proc_output([script_path, 'PrintDistro']) + release = get_proc_output([script_path, 'PrintRelease']) + architectures = get_proc_output([script_path, 'PrintArchitectures']) + for arch in architectures.split('\n'): + proc = multiprocessing.Process( + target=build_and_upload, + args=(script_path, distro, release, arch, lock)) + procs.append(("%s %s (%s)" % (distro, release, arch), proc)) + proc.start() + for _, proc in procs: + proc.join() + + print("SYSROOT CREATION SUMMARY") + failures = 0 + for name, proc in procs: + if proc.exitcode: + failures += 1 + status = "FAILURE" if proc.exitcode else "SUCCESS" + print("%s sysroot creation\t%s" % (name, status)) + return failures + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/debian_archive_unstable.gpg b/third_party/libwebrtc/build/linux/sysroot_scripts/debian_archive_unstable.gpg new file mode 100644 index 0000000000..e86a7007a8 Binary files /dev/null and b/third_party/libwebrtc/build/linux/sysroot_scripts/debian_archive_unstable.gpg differ diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols.py b/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols.py new file mode 100755 index 0000000000..1e0b5f614a --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium 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 incompatible symbols in glibc and output a list of replacements. +""" + +from __future__ import print_function + +import re +import sys + +# This constant comes from https://crbug.com/580892 +MAX_ALLOWED_GLIBC_VERSION = [2, 17] + + +def get_replacements(nm_file, max_allowed_glibc_version): + version_format = re.compile('GLIBC_[0-9\.]+') + symbols = {} + for line in nm_file: + # Some versions of nm have a bug where the version gets printed twice. + # Since the symbol may either be formatted like "name@@VERSION" or + # "name@@VERSION@@VERSION", handle both cases. + line = line.replace('@@', '@') + symver = line.split('@') + symbol = symver[0].split(' ')[-1] + version = symver[-1] + if not re.match(version_format, version): + continue + if symbol in symbols: + symbols[symbol].add(version) + else: + symbols[symbol] = set([version]) + + replacements = [] + for symbol, versions in symbols.items(): + if len(versions) <= 1: + continue + versions_parsed = [[ + int(part) for part in version.lstrip('GLIBC_').split('.') + ] for version in versions] + if (max(versions_parsed) > max_allowed_glibc_version and + min(versions_parsed) <= max_allowed_glibc_version): + # Use the newest allowed version of the symbol. + replacement_version_parsed = max([ + version for version in versions_parsed + if version <= max_allowed_glibc_version + ]) + replacement_version = 'GLIBC_' + '.'.join( + [str(part) for part in replacement_version_parsed]) + replacements.append('__asm__(".symver %s, %s@%s");' % + (symbol, symbol, replacement_version)) + return sorted(replacements) + + +if __name__ == '__main__': + replacements = get_replacements(sys.stdin, MAX_ALLOWED_GLIBC_VERSION) + if replacements: + print('// Chromium-specific hack.') + print('// See explanation in sysroot-creator.sh.') + for replacement in replacements: + print(replacement) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols_unittest.py b/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols_unittest.py new file mode 100755 index 0000000000..7c66555068 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/find_incompatible_glibc_symbols_unittest.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium 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 find_incompatible_glibc_symbols +import sys + +if sys.version_info.major == 2: + from cStringIO import StringIO +else: + from io import StringIO + +NM_DATA = """\ +0000000000000001 W expf@GLIBC_2.2.5 +0000000000000002 W expf@@GLIBC_2.27 +0000000000000003 W foo@@GLIBC_2.2.5 +0000000000000004 W bar@GLIBC_2.2.5 +0000000000000005 W baz@GLIBC_2.2.5 +0000000000000006 T foo2@GLIBC_2.2 +0000000000000007 T foo2@GLIBC_2.3 +0000000000000008 T foo2@GLIBC_2.30 +0000000000000009 T foo2@@GLIBC_2.31 +000000000000000a T bar2@GLIBC_2.30 +000000000000000b T bar2@@GLIBC_2.31 +000000000000000c T baz2@GLIBC_2.2 +000000000000000d T baz2@@GLIBC_2.3 +""" + +EXPECTED_REPLACEMENTS = [ + '__asm__(".symver expf, expf@GLIBC_2.2.5");', + '__asm__(".symver foo2, foo2@GLIBC_2.3");', +] + +nm_file = StringIO() +nm_file.write(NM_DATA) +nm_file.seek(0) + +assert ( + EXPECTED_REPLACEMENTS == find_incompatible_glibc_symbols.get_replacements( + nm_file, [2, 17])) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generate_debian_archive_unstable_gpg.sh b/third_party/libwebrtc/build/linux/sysroot_scripts/generate_debian_archive_unstable_gpg.sh new file mode 100755 index 0000000000..ecbdd64312 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generate_debian_archive_unstable_gpg.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -o nounset +set -o errexit + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +KEYS=( + # Debian Stable Release Key (10/buster) + "DCC9EFBF77E11517" + # Debian Archive Automatic Signing Key (10/buster) + "DC30D7C23CBBABEE" + # Debian Security Archive Automatic Signing Key (10/buster) + "4DFAB270CAA96DFA" + # Debian Archive Automatic Signing Key (10/buster) + "DC30D7C23CBBABEE" + # Jessie Stable Release Key + "CBF8D6FD518E17E1" + # Debian Archive Automatic Signing Key (7.0/wheezy) + "8B48AD6246925553" + # Debian Archive Automatic Signing Key (8/jessie) + "7638D0442B90D010" + # Debian Security Archive Automatic Signing Key (8/jessie) + "9D6D8F6BC857C906" + # Debian Archive Automatic Signing Key (9/stretch) + "E0B11894F66AEC98" + # Debian Security Archive Automatic Signing Key (9/stretch) + "EDA0D2388AE22BA9" + # Debian Stable Release Key (9/stretch) + "EF0F382A1A7B6500" +) + +gpg --recv-keys ${KEYS[@]} +gpg --output "${SCRIPT_DIR}/debian_archive_unstable.gpg" --export ${KEYS[@]} diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.amd64 b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.amd64 new file mode 100644 index 0000000000..29ea13c4dc --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.amd64 @@ -0,0 +1,372 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libasan6_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libitm1_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/liblsan0_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libquadmath0_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libtsan0_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libubsan1_10.2.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libcilkrts5_6.3.0-18+deb9u1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libmpx2_6.3.0-18+deb9u1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-intel1_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_amd64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_amd64.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm new file mode 100644 index 0000000000..b91ebc48a8 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm @@ -0,0 +1,368 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libasan6_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libubsan1_10.2.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-etnaviv1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-exynos1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-freedreno1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-omap1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-tegra0_2.4.104-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_armhf.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_armhf.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm64 b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm64 new file mode 100644 index 0000000000..4db2b8a994 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.arm64 @@ -0,0 +1,371 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libasan6_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libitm1_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/liblsan0_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libtsan0_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libubsan1_10.2.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-etnaviv1_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-freedreno1_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-tegra0_2.4.104-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_arm64.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_arm64.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.armel b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.armel new file mode 100644 index 0000000000..f8f54f343d --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.armel @@ -0,0 +1,367 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libasan6_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libubsan1_10.2.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-exynos1_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-freedreno1_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-omap1_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-tegra0_2.4.104-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_armel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_armel.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.i386 b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.i386 new file mode 100644 index 0000000000..cdeaf9a2b7 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.i386 @@ -0,0 +1,368 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libasan6_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libitm1_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libquadmath0_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libubsan1_10.2.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libcilkrts5_6.3.0-18+deb9u1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libmpx2_6.3.0-18+deb9u1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-1_4.0.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk4/libgtk-4-dev_4.0.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-intel1_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_i386.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_i386.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mips64el b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mips64el new file mode 100644 index 0000000000..a3b6559aa2 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mips64el @@ -0,0 +1,359 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_mips64el.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_mips64el.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mipsel b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mipsel new file mode 100644 index 0000000000..0aa38490bd --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/generated_package_lists/sid.mipsel @@ -0,0 +1,359 @@ +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-0_2.36.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/atk1.0/libatk1.0-dev_2.36.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-dev_2.38.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-0_2.38.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/at-spi2-core/libatspi2.0-dev_2.38.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/attr/libattr1_2.4.48-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/audit/libaudit1_3.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-client3_0.8-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/a/avahi/libavahi-common3_0.8-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth3_5.55-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/bluez/libbluetooth-dev_5.55-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli1_1.0.9-2+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/b/brotli/libbrotli-dev_1.0.9-2+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2_1.16.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-gobject2_1.16.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cairo/libcairo-script-interpreter2_1.16.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/colord/libcolord2_1.4.5-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2_2.3.3op2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcups2-dev_2.3.3op2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2_2.3.3op2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cups/libcupsimage2-dev_2.3.3op2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl3-gnutls_7.74.0-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/curl/libcurl4-gnutls-dev_7.74.0-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-3_1.12.20-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/d/dbus/libdbus-1-dev_1.12.20-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/comerr-dev_2.1-1.46.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/e2fsprogs/libcom-err2_1.46.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf1_0.183-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/elfutils/libelf-dev_0.183-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1_2.2.10-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/e/expat/libexpat1-dev_2.2.10-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac8_1.3.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/flac/libflac-dev_1.3.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig1_2.13.1-4.2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fontconfig/libfontconfig-dev_2.13.1-4.2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype6_2.10.4+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/freetype/libfreetype-dev_2.10.4+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi0_1.0.8-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/f/fribidi/libfribidi-dev_1.0.8-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libatomic1_10.2.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libgomp1_10.2.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++-10-dev_10.2.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-10/libstdc++6_10.2.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gcc-6/libgcc1_6.3.0-18+deb9u1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-0_2.66.7-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.7-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6_2.31-9_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/glibc/libc6-dev_2.31-9_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gmp/libgmp10_6.2.1+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls28-dev_3.7.0-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls30_3.7.0-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-dane0_3.7.0-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutls-openssl27_3.7.0-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gnutls28/libgnutlsxx28_3.7.0-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-0_1.10.4+dfsg1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphene/libgraphene-1.0-dev_1.10.4+dfsg1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-3_1.3.14-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/graphite2/libgraphite2-dev_1.3.14-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+2.0/libgtk2.0-0_2.24.33-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-0_3.24.24-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/g/gtk+3.0/libgtk-3-dev_3.24.24-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-dev_2.7.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-gobject0_2.7.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/i/icu/libicu67_67.1-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/keyutils/libkeyutils1_1.6.1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/krb5-multidev_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssapi-krb5-2_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libgssrpc4_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libk5crypto3_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5clnt-mit12_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkadm5srv-mit12_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkdb5-10_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-3_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5-dev_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/k/krb5/libkrb5support0_1.18.3-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator1_0.4.92-8_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-1_0.4.92-8_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator3-dev_0.4.92-8_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libappindicator/libappindicator-dev_0.4.92-8_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/liba/libasyncns/libasyncns0_0.8-6+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libb/libbsd/libbsd0_0.11.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap2_2.44-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap2/libcap-dev_2.44-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libc/libcap-ng/libcap-ng0_0.7.9-2.2+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie1_0.2.13-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdatrie/libdatrie-dev_0.2.13-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdeflate/libdeflate0_1.7-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm2_2.4.104-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-dev_2.4.104-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-nouveau2_2.4.104-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libd/libdrm/libdrm-radeon1_2.4.104-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy0_1.5.5-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libepoxy/libepoxy-dev_1.5.5-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev2_1.11.0+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevdev/libevdev-dev_1.11.0+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libe/libevent/libevent-2.1-7_2.1.12-stable-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi6_3.2.1-9_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi7_3.3-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libf/libffi/libffi-dev_3.3-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20_1.8.7-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgcrypt20/libgcrypt20-dev_1.8.7-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl1_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libegl-dev_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl1_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgl-dev_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles1_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles2_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libgles-dev_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd0_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglvnd-dev_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx0_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libglx-dev_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libglvnd/libopengl0_1.3.2-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring0_3.12.0-1+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgnome-keyring/libgnome-keyring-dev_3.12.0-1+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error0_1.38-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgpg-error/libgpg-error-dev_1.38-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libg/libgudev/libgudev-1.0-0_234-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libice/libice6_1.0.10-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libidn/libidn11_1.33-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator3-7_0.5.0-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libindicator/libindicator7_0.5.0-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput10_1.16.4-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libi/libinput/libinput-dev_1.16.4-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.0.6-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjpeg-turbo/libjpeg62-turbo-dev_2.0.6-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp24_1.9.4-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libj/libjsoncpp/libjsoncpp-dev_1.9.4-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libm/libmd/libmd0_1.0.3-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpsl/libpsl5_0.21.0-1.2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1_3.1-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1_3.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsepol/libsepol1-dev_3.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsm/libsm6_1.2.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsndfile/libsndfile1_1.0.31-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup2.4-1_2.72.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libsoup2.4/libsoup-gnome2.4-1_2.72.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libs/libssh2/libssh2-1_1.9.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtasn1-6/libtasn1-6_4.16.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai0_0.1.28-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libthai/libthai-dev_0.1.28-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libt/libtool/libltdl7_2.4.6-15_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva2_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-dev_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-drm2_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-glx2_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-wayland2_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libva/libva-x11-2_2.10.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbis0a_1.3.7-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwacom/libwacom2_1.8-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp6_0.6.1-2+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebp-dev_0.6.1-2+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libw/libwebp/libwebpmux3_0.6.1-2+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-6_1.7.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-dev_1.7.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb1_1.7.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libx11/libx11-xcb-dev_1.7.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau6_1.0.9-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxau/libxau-dev_1.0.9-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb1-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri2-0-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-dri3-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-glx0-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-present-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-render0-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-shm0-dev_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-sync1_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcb/libxcb-xfixes0_1.14-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite1_0.4.5-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcomposite/libxcomposite-dev_0.4.5-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor1_1.2.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage1_1.1.5-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdamage/libxdamage-dev_1.1.5-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp6_1.1.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxdmcp/libxdmcp-dev_1.1.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext6_1.3.3-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxext/libxext-dev_1.3.3-1.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes3_5.0.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi6_1.7.10-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxi/libxi-dev_1.7.10-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama1_1.1.4-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxinerama/libxinerama-dev_1.1.4-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2_2.9.10+dfsg-6.3+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxml2/libxml2-dev_2.9.10+dfsg-6.3+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr2_1.5.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender1_0.9.10-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxrender/libxrender-dev_0.9.10-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence1_1.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxshmfence/libxshmfence-dev_1.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1.1_1.1.34-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxslt/libxslt1-dev_1.1.34-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss1_1.2.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxss/libxss-dev_1.2.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt6_1.2.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxt/libxt-dev_1.2.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst6_1.2.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxtst/libxtst-dev_1.2.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libx/libxxf86vm/libxxf86vm-dev_1.1.4-1+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/libz/libzstd/libzstd1_1.4.8+dfsg-2.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lcms2/liblcms2-2_2.12~rc1-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/linux/linux-libc-dev_5.10.19-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lz4/liblz4-1_1.9.3-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/l/lzo2/liblzo2-2_2.10-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm1_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgbm-dev_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libglapi-mesa_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mesa/mesa-common-dev_20.3.4-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip1_1.1-8+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/m/mtdev/libmtdev1_1.1.6-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses6_6.2+20201114-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncurses-dev_6.2+20201114-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libncursesw6_6.2+20201114-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/ncurses/libtinfo6_6.2+20201114-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libhogweed6_3.7-2.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nettle/libnettle8_3.7-2.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nghttp2/libnghttp2-14_1.43.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4_4.29-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nspr/libnspr4-dev_4.29-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3_3.61-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/n/nss/libnss3-dev_3.61-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openldap/libldap-2.4-2_2.4.57+dfsg-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl1.1_1.1.1j-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/openssl/libssl-dev_1.1.1j-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus0_1.3.1-0.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/o/opus/libopus-dev_1.3.1-0.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/p11-kit/libp11-kit0_0.23.22-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g_1.4.0-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pam/libpam0g-dev_1.4.0-6_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango-1.0-0_1.46.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpango1.0-dev_1.46.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangocairo-1.0-0_1.46.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci3_3.7.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-16-0_10.36-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-32-0_10.36-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-8-0_10.36-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-dev_10.36-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix0_10.22-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre2/libpcre2-posix2_10.36-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre16-3_8.39-13_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre32-3_8.39-13_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3_8.39-13_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcre3-dev_8.39-13_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pcre3/libpcrecpp0v5_8.39-13_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-0_0.3.19-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libpipewire-0.3-dev_0.3.19-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pipewire/libspa-0.2-dev_0.3.19-4_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-0_0.40.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pixman/libpixman-1-dev_0.40.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse0_14.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-dev_14.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/p/pulseaudio/libpulse-mainloop-glib0_14.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-9_20210201+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/re2/libre2-dev_20210201+dfsg-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/shared-mime-info/shared-mime-info_2.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy1v5_1.1.8-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/snappy/libsnappy-dev_1.1.8-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd2_0.10.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/libspeechd-dev_0.10.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/speech-dispatcher/speech-dispatcher_0.10.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libsystemd0_247.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev1_247.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/s/systemd/libudev-dev_247.3-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tcp-wrappers/libwrap0_7.6.q-31_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/t/tiff/libtiff5_4.2.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/unbound/libunbound8_1.13.1-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid1_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libblkid-dev_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount1_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libmount-dev_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/libuuid1_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/u/util-linux/uuid-dev_2.36.1-7_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan1_1.2.162.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/v/vulkan-loader/libvulkan-dev_1.2.162.0-1_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-client0_1.19.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-cursor0_1.19.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-dev_1.19.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-egl1_1.19.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland/libwayland-server0_1.19.0-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/w/wayland-protocols/wayland-protocols_1.20-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-composite/x11proto-composite-dev_0.4.2-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-damage/x11proto-damage-dev_1.2.1-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/x11proto-fixes/x11proto-fixes-dev_5.0-2_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft2_2.3.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xft/libxft-dev_2.3.2-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-input-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-kb-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-randr-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-record-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-render-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-scrnsaver-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xext-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xorgproto/x11proto-xinerama-dev_2020.1-1_all.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/x/xz-utils/liblzma5_5.2.5-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g_1.2.11.dfsg-2_mipsel.deb +https://snapshot.debian.org/archive/debian/20210309T203820Z/pool/main/z/zlib/zlib1g-dev_1.2.11.dfsg-2_mipsel.deb diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/install-sysroot.py b/third_party/libwebrtc/build/linux/sysroot_scripts/install-sysroot.py new file mode 100755 index 0000000000..ada6208644 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/install-sysroot.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# 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. + +"""Install Debian sysroots for building chromium. +""" + +# The sysroot is needed to ensure that binaries that get built will run on +# the oldest stable version of Debian that we currently support. +# This script can be run manually but is more often run as part of gclient +# hooks. When run from hooks this script is a no-op on non-linux platforms. + +# The sysroot image could be constructed from scratch based on the current state +# of the Debian archive but for consistency we use a pre-built root image (we +# don't want upstream changes to Debian to effect the chromium build until we +# choose to pull them in). The images will normally need to be rebuilt every +# time chrome's build dependencies are changed but should also be updated +# periodically to include upstream security fixes from Debian. + +# This script looks at sysroots.json next to it to find the name of a .tar.xz +# to download and the location to extract it to. The extracted sysroot could for +# example be in build/linux/debian_sid_amd64-sysroot/. + +from __future__ import print_function + +import hashlib +import json +import platform +import optparse +import os +import re +import shutil +import subprocess +import sys +try: + # For Python 3.0 and later + from urllib.request import urlopen +except ImportError: + # Fall back to Python 2's urllib2 + from urllib2 import urlopen + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +URL_PREFIX = 'https://commondatastorage.googleapis.com' +URL_PATH = 'chrome-linux-sysroot/toolchain' + +VALID_ARCHS = ('arm', 'arm64', 'i386', 'amd64', 'mips', 'mips64el') + +ARCH_TRANSLATIONS = { + 'x64': 'amd64', + 'x86': 'i386', + 'mipsel': 'mips', + 'mips64': 'mips64el', +} + +DEFAULT_TARGET_PLATFORM = 'sid' + +class Error(Exception): + pass + + +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 main(args): + parser = optparse.OptionParser('usage: %prog [OPTIONS]', description=__doc__) + parser.add_option('--arch', + help='Sysroot architecture: %s' % ', '.join(VALID_ARCHS)) + parser.add_option('--all', action='store_true', + help='Install all sysroot images (useful when updating the' + ' images)') + parser.add_option('--print-hash', + help='Print the hash of the sysroot for the given arch.') + options, _ = parser.parse_args(args) + + if options.print_hash: + arch = options.print_hash + print(GetSysrootDict(DEFAULT_TARGET_PLATFORM, + ARCH_TRANSLATIONS.get(arch, arch))['Sha1Sum']) + return 0 + if options.arch: + InstallSysroot(DEFAULT_TARGET_PLATFORM, + ARCH_TRANSLATIONS.get(options.arch, options.arch)) + elif options.all: + for arch in VALID_ARCHS: + InstallSysroot(DEFAULT_TARGET_PLATFORM, arch) + else: + print('You much specify one of the options.') + return 1 + + return 0 + + +def GetSysrootDict(target_platform, target_arch): + if target_arch not in VALID_ARCHS: + raise Error('Unknown architecture: %s' % target_arch) + + sysroots_file = os.path.join(SCRIPT_DIR, 'sysroots.json') + sysroots = json.load(open(sysroots_file)) + sysroot_key = '%s_%s' % (target_platform, target_arch) + if sysroot_key not in sysroots: + raise Error('No sysroot for: %s %s' % (target_platform, target_arch)) + return sysroots[sysroot_key] + + +def InstallSysroot(target_platform, target_arch): + sysroot_dict = GetSysrootDict(target_platform, target_arch) + tarball_filename = sysroot_dict['Tarball'] + tarball_sha1sum = sysroot_dict['Sha1Sum'] + # TODO(thestig) Consider putting this elsewhere to avoid having to recreate + # it on every build. + linux_dir = os.path.dirname(SCRIPT_DIR) + sysroot = os.path.join(linux_dir, sysroot_dict['SysrootDir']) + + url = '%s/%s/%s/%s' % (URL_PREFIX, URL_PATH, tarball_sha1sum, + tarball_filename) + + stamp = os.path.join(sysroot, '.stamp') + if os.path.exists(stamp): + with open(stamp) as s: + if s.read() == url: + return + + print('Installing Debian %s %s root image: %s' % \ + (target_platform, 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() + for _ in range(3): + try: + response = urlopen(url) + with open(tarball, "wb") as f: + f.write(response.read()) + break + except Exception: # Ignore exceptions. + pass + else: + raise Error('Failed to download %s' % url) + sha1sum = GetSha1(tarball) + if sha1sum != tarball_sha1sum: + raise Error('Tarball sha1sum is wrong.' + 'Expected %s, actual: %s' % (tarball_sha1sum, sha1sum)) + subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot]) + os.remove(tarball) + + with open(stamp, 'w') as s: + s.write(url) + + +if __name__ == '__main__': + try: + sys.exit(main(sys.argv[1:])) + except Error as e: + sys.stderr.write(str(e) + '\n') + sys.exit(1) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/libdbus-1-3-symbols b/third_party/libwebrtc/build/linux/sysroot_scripts/libdbus-1-3-symbols new file mode 100644 index 0000000000..28050aa570 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/libdbus-1-3-symbols @@ -0,0 +1,235 @@ +libdbus-1.so.3 libdbus-1-3 #MINVER# + dbus_address_entries_free@Base 1.0.2 + dbus_address_entry_get_method@Base 1.0.2 + dbus_address_entry_get_value@Base 1.0.2 + dbus_address_escape_value@Base 1.0.2 + dbus_address_unescape_value@Base 1.0.2 + dbus_bus_add_match@Base 1.0.2 + dbus_bus_get@Base 1.0.2 + dbus_bus_get_id@Base 1.1.1 + dbus_bus_get_private@Base 1.0.2 + dbus_bus_get_unique_name@Base 1.0.2 + dbus_bus_get_unix_user@Base 1.0.2 + dbus_bus_name_has_owner@Base 1.0.2 + dbus_bus_register@Base 1.0.2 + dbus_bus_release_name@Base 1.0.2 + dbus_bus_remove_match@Base 1.0.2 + dbus_bus_request_name@Base 1.0.2 + dbus_bus_set_unique_name@Base 1.0.2 + dbus_bus_start_service_by_name@Base 1.0.2 + dbus_connection_add_filter@Base 1.0.2 + dbus_connection_allocate_data_slot@Base 1.0.2 + dbus_connection_borrow_message@Base 1.0.2 + dbus_connection_can_send_type@Base 1.3.1 + dbus_connection_close@Base 1.0.2 + dbus_connection_dispatch@Base 1.0.2 + dbus_connection_flush@Base 1.0.2 + dbus_connection_free_data_slot@Base 1.0.2 + dbus_connection_free_preallocated_send@Base 1.0.2 + dbus_connection_get_adt_audit_session_data@Base 1.2.4 + dbus_connection_get_data@Base 1.0.2 + dbus_connection_get_dispatch_status@Base 1.0.2 + dbus_connection_get_is_anonymous@Base 1.1.1 + dbus_connection_get_is_authenticated@Base 1.0.2 + dbus_connection_get_is_connected@Base 1.0.2 + dbus_connection_get_max_message_size@Base 1.0.2 + dbus_connection_get_max_message_unix_fds@Base 1.3.1 + dbus_connection_get_max_received_size@Base 1.0.2 + dbus_connection_get_max_received_unix_fds@Base 1.3.1 + dbus_connection_get_object_path_data@Base 1.0.2 + dbus_connection_get_outgoing_size@Base 1.0.2 + dbus_connection_get_outgoing_unix_fds@Base 1.3.1 + dbus_connection_get_server_id@Base 1.1.1 + dbus_connection_get_socket@Base 1.0.2 + dbus_connection_get_unix_fd@Base 1.0.2 + dbus_connection_get_unix_process_id@Base 1.0.2 + dbus_connection_get_unix_user@Base 1.0.2 + dbus_connection_get_windows_user@Base 1.1.1 + dbus_connection_has_messages_to_send@Base 1.0.2 + dbus_connection_list_registered@Base 1.0.2 + dbus_connection_open@Base 1.0.2 + dbus_connection_open_private@Base 1.0.2 + dbus_connection_pop_message@Base 1.0.2 + dbus_connection_preallocate_send@Base 1.0.2 + dbus_connection_read_write@Base 1.0.2 + dbus_connection_read_write_dispatch@Base 1.0.2 + dbus_connection_ref@Base 1.0.2 + dbus_connection_register_fallback@Base 1.0.2 + dbus_connection_register_object_path@Base 1.0.2 + dbus_connection_remove_filter@Base 1.0.2 + dbus_connection_return_message@Base 1.0.2 + dbus_connection_send@Base 1.0.2 + dbus_connection_send_preallocated@Base 1.0.2 + dbus_connection_send_with_reply@Base 1.0.2 + dbus_connection_send_with_reply_and_block@Base 1.0.2 + dbus_connection_set_allow_anonymous@Base 1.1.1 + dbus_connection_set_change_sigpipe@Base 1.0.2 + dbus_connection_set_data@Base 1.0.2 + dbus_connection_set_dispatch_status_function@Base 1.0.2 + dbus_connection_set_exit_on_disconnect@Base 1.0.2 + dbus_connection_set_max_message_size@Base 1.0.2 + dbus_connection_set_max_message_unix_fds@Base 1.3.1 + dbus_connection_set_max_received_size@Base 1.0.2 + dbus_connection_set_max_received_unix_fds@Base 1.3.1 + dbus_connection_set_route_peer_messages@Base 1.0.2 + dbus_connection_set_timeout_functions@Base 1.0.2 + dbus_connection_set_unix_user_function@Base 1.0.2 + dbus_connection_set_wakeup_main_function@Base 1.0.2 + dbus_connection_set_watch_functions@Base 1.0.2 + dbus_connection_set_windows_user_function@Base 1.1.1 + dbus_connection_steal_borrowed_message@Base 1.0.2 + dbus_connection_try_register_fallback@Base 1.1.4 + dbus_connection_try_register_object_path@Base 1.1.4 + dbus_connection_unref@Base 1.0.2 + dbus_connection_unregister_object_path@Base 1.0.2 + dbus_error_free@Base 1.0.2 + dbus_error_has_name@Base 1.0.2 + dbus_error_init@Base 1.0.2 + dbus_error_is_set@Base 1.0.2 + dbus_free@Base 1.0.2 + dbus_free_string_array@Base 1.0.2 + dbus_get_local_machine_id@Base 1.0.2 + dbus_get_version@Base 1.1.4 + dbus_internal_do_not_use_create_uuid@Base 1.0.2 + dbus_internal_do_not_use_get_uuid@Base 1.0.2 + dbus_malloc0@Base 1.0.2 + dbus_malloc@Base 1.0.2 + dbus_message_allocate_data_slot@Base 1.0.2 + dbus_message_append_args@Base 1.0.2 + dbus_message_append_args_valist@Base 1.0.2 + dbus_message_contains_unix_fds@Base 1.3.1 + dbus_message_copy@Base 1.0.2 + dbus_message_demarshal@Base 1.1.1 + dbus_message_demarshal_bytes_needed@Base 1.2.14 + dbus_message_free_data_slot@Base 1.0.2 + dbus_message_get_args@Base 1.0.2 + dbus_message_get_args_valist@Base 1.0.2 + dbus_message_get_auto_start@Base 1.0.2 + dbus_message_get_data@Base 1.0.2 + dbus_message_get_destination@Base 1.0.2 + dbus_message_get_error_name@Base 1.0.2 + dbus_message_get_interface@Base 1.0.2 + dbus_message_get_member@Base 1.0.2 + dbus_message_get_no_reply@Base 1.0.2 + dbus_message_get_path@Base 1.0.2 + dbus_message_get_path_decomposed@Base 1.0.2 + dbus_message_get_reply_serial@Base 1.0.2 + dbus_message_get_sender@Base 1.0.2 + dbus_message_get_serial@Base 1.0.2 + dbus_message_get_signature@Base 1.0.2 + dbus_message_get_type@Base 1.0.2 + dbus_message_has_destination@Base 1.0.2 + dbus_message_has_interface@Base 1.0.2 + dbus_message_has_member@Base 1.0.2 + dbus_message_has_path@Base 1.0.2 + dbus_message_has_sender@Base 1.0.2 + dbus_message_has_signature@Base 1.0.2 + dbus_message_is_error@Base 1.0.2 + dbus_message_is_method_call@Base 1.0.2 + dbus_message_is_signal@Base 1.0.2 + dbus_message_iter_abandon_container@Base 1.2.16 + dbus_message_iter_append_basic@Base 1.0.2 + dbus_message_iter_append_fixed_array@Base 1.0.2 + dbus_message_iter_close_container@Base 1.0.2 + dbus_message_iter_get_arg_type@Base 1.0.2 + dbus_message_iter_get_array_len@Base 1.0.2 + dbus_message_iter_get_basic@Base 1.0.2 + dbus_message_iter_get_element_type@Base 1.0.2 + dbus_message_iter_get_fixed_array@Base 1.0.2 + dbus_message_iter_get_signature@Base 1.0.2 + dbus_message_iter_has_next@Base 1.0.2 + dbus_message_iter_init@Base 1.0.2 + dbus_message_iter_init_append@Base 1.0.2 + dbus_message_iter_next@Base 1.0.2 + dbus_message_iter_open_container@Base 1.0.2 + dbus_message_iter_recurse@Base 1.0.2 + dbus_message_lock@Base 1.2.14 + dbus_message_marshal@Base 1.1.1 + dbus_message_new@Base 1.0.2 + dbus_message_new_error@Base 1.0.2 + dbus_message_new_error_printf@Base 1.0.2 + dbus_message_new_method_call@Base 1.0.2 + dbus_message_new_method_return@Base 1.0.2 + dbus_message_new_signal@Base 1.0.2 + dbus_message_ref@Base 1.0.2 + dbus_message_set_auto_start@Base 1.0.2 + dbus_message_set_data@Base 1.0.2 + dbus_message_set_destination@Base 1.0.2 + dbus_message_set_error_name@Base 1.0.2 + dbus_message_set_interface@Base 1.0.2 + dbus_message_set_member@Base 1.0.2 + dbus_message_set_no_reply@Base 1.0.2 + dbus_message_set_path@Base 1.0.2 + dbus_message_set_reply_serial@Base 1.0.2 + dbus_message_set_sender@Base 1.0.2 + dbus_message_set_serial@Base 1.2.14 + dbus_message_type_from_string@Base 1.0.2 + dbus_message_type_to_string@Base 1.0.2 + dbus_message_unref@Base 1.0.2 + dbus_move_error@Base 1.0.2 + dbus_parse_address@Base 1.0.2 + dbus_pending_call_allocate_data_slot@Base 1.0.2 + dbus_pending_call_block@Base 1.0.2 + dbus_pending_call_cancel@Base 1.0.2 + dbus_pending_call_free_data_slot@Base 1.0.2 + dbus_pending_call_get_completed@Base 1.0.2 + dbus_pending_call_get_data@Base 1.0.2 + dbus_pending_call_ref@Base 1.0.2 + dbus_pending_call_set_data@Base 1.0.2 + dbus_pending_call_set_notify@Base 1.0.2 + dbus_pending_call_steal_reply@Base 1.0.2 + dbus_pending_call_unref@Base 1.0.2 + dbus_realloc@Base 1.0.2 + dbus_server_allocate_data_slot@Base 1.0.2 + dbus_server_disconnect@Base 1.0.2 + dbus_server_free_data_slot@Base 1.0.2 + dbus_server_get_address@Base 1.0.2 + dbus_server_get_data@Base 1.0.2 + dbus_server_get_id@Base 1.1.1 + dbus_server_get_is_connected@Base 1.0.2 + dbus_server_listen@Base 1.0.2 + dbus_server_ref@Base 1.0.2 + dbus_server_set_auth_mechanisms@Base 1.0.2 + dbus_server_set_data@Base 1.0.2 + dbus_server_set_new_connection_function@Base 1.0.2 + dbus_server_set_timeout_functions@Base 1.0.2 + dbus_server_set_watch_functions@Base 1.0.2 + dbus_server_unref@Base 1.0.2 + dbus_set_error@Base 1.0.2 + dbus_set_error_const@Base 1.0.2 + dbus_set_error_from_message@Base 1.0.2 + dbus_setenv@Base 1.7.6 + dbus_shutdown@Base 1.0.2 + dbus_signature_iter_get_current_type@Base 1.0.2 + dbus_signature_iter_get_element_type@Base 1.0.2 + dbus_signature_iter_get_signature@Base 1.0.2 + dbus_signature_iter_init@Base 1.0.2 + dbus_signature_iter_next@Base 1.0.2 + dbus_signature_iter_recurse@Base 1.0.2 + dbus_signature_validate@Base 1.0.2 + dbus_signature_validate_single@Base 1.0.2 + dbus_threads_init@Base 1.0.2 + dbus_threads_init_default@Base 1.0.2 + dbus_timeout_get_data@Base 1.0.2 + dbus_timeout_get_enabled@Base 1.0.2 + dbus_timeout_get_interval@Base 1.0.2 + dbus_timeout_handle@Base 1.0.2 + dbus_timeout_set_data@Base 1.0.2 + dbus_type_is_basic@Base 1.0.2 + dbus_type_is_container@Base 1.0.2 + dbus_type_is_fixed@Base 1.0.2 + dbus_type_is_valid@Base 1.5.0 + dbus_validate_bus_name@Base 1.5.12 + dbus_validate_error_name@Base 1.5.12 + dbus_validate_interface@Base 1.5.12 + dbus_validate_member@Base 1.5.12 + dbus_validate_path@Base 1.5.12 + dbus_validate_utf8@Base 1.5.12 + dbus_watch_get_data@Base 1.0.2 + dbus_watch_get_enabled@Base 1.0.2 + dbus_watch_get_fd@Base 1.0.2 + dbus_watch_get_flags@Base 1.0.2 + dbus_watch_get_socket@Base 1.1.1 + dbus_watch_get_unix_fd@Base 1.1.1 + dbus_watch_handle@Base 1.0.2 + dbus_watch_set_data@Base 1.0.2 diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/libxcomposite1-symbols b/third_party/libwebrtc/build/linux/sysroot_scripts/libxcomposite1-symbols new file mode 100644 index 0000000000..aba31fa2a8 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/libxcomposite1-symbols @@ -0,0 +1,15 @@ +libXcomposite.so.1 libxcomposite1 #MINVER# + XCompositeCreateRegionFromBorderClip@Base 1:0.4.4-1 + XCompositeExtensionInfo@Base 1:0.4.4-1 + XCompositeExtensionName@Base 1:0.4.4-1 + XCompositeFindDisplay@Base 1:0.4.4-1 + XCompositeGetOverlayWindow@Base 1:0.4.4-1 + XCompositeNameWindowPixmap@Base 1:0.4.4-1 + XCompositeQueryExtension@Base 1:0.4.4-1 + XCompositeQueryVersion@Base 1:0.4.4-1 + XCompositeRedirectSubwindows@Base 1:0.4.4-1 + XCompositeRedirectWindow@Base 1:0.4.4-1 + XCompositeReleaseOverlayWindow@Base 1:0.4.4-1 + XCompositeUnredirectSubwindows@Base 1:0.4.4-1 + XCompositeUnredirectWindow@Base 1:0.4.4-1 + XCompositeVersion@Base 1:0.4.4-1 diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/libxkbcommon0-symbols b/third_party/libwebrtc/build/linux/sysroot_scripts/libxkbcommon0-symbols new file mode 100644 index 0000000000..5750fc0a59 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/libxkbcommon0-symbols @@ -0,0 +1,93 @@ +libxkbcommon.so.0 libxkbcommon0 #MINVER# + xkb_context_get_log_level@Base 0.4.1 + xkb_context_get_log_verbosity@Base 0.4.1 + xkb_context_get_user_data@Base 0.4.1 + xkb_context_include_path_append@Base 0.4.1 + xkb_context_include_path_append_default@Base 0.4.1 + xkb_context_include_path_clear@Base 0.4.1 + xkb_context_include_path_get@Base 0.4.1 + xkb_context_include_path_reset_defaults@Base 0.4.1 + xkb_context_new@Base 0.4.1 + xkb_context_num_include_paths@Base 0.4.1 + xkb_context_ref@Base 0.4.1 + xkb_context_set_log_fn@Base 0.4.1 + xkb_context_set_log_level@Base 0.4.1 + xkb_context_set_log_verbosity@Base 0.4.1 + xkb_context_set_user_data@Base 0.4.1 + xkb_context_unref@Base 0.4.1 + xkb_key_get_syms@Base 0.4.1 + xkb_key_mod_index_is_consumed@Base 0.4.1 + xkb_key_mod_mask_remove_consumed@Base 0.4.1 + xkb_key_num_groups@Base 0.4.1 + xkb_key_repeats@Base 0.4.1 + xkb_keymap_get_as_string@Base 0.4.1 + xkb_keymap_key_for_each@Base 0.4.1 + xkb_keymap_key_get_syms_by_level@Base 0.4.1 + xkb_keymap_key_repeats@Base 0.4.1 + xkb_keymap_layout_get_index@Base 0.4.1 + xkb_keymap_layout_get_name@Base 0.4.1 + xkb_keymap_led_get_index@Base 0.4.1 + xkb_keymap_led_get_name@Base 0.4.1 + xkb_keymap_max_keycode@Base 0.4.1 + xkb_keymap_min_keycode@Base 0.4.1 + xkb_keymap_mod_get_index@Base 0.4.1 + xkb_keymap_mod_get_name@Base 0.4.1 + xkb_keymap_new_from_buffer@Base 0.4.1 + xkb_keymap_new_from_file@Base 0.4.1 + xkb_keymap_new_from_names@Base 0.4.1 + xkb_keymap_new_from_string@Base 0.4.1 + xkb_keymap_num_layouts@Base 0.4.1 + xkb_keymap_num_layouts_for_key@Base 0.4.1 + xkb_keymap_num_leds@Base 0.4.1 + xkb_keymap_num_levels_for_key@Base 0.4.1 + xkb_keymap_num_mods@Base 0.4.1 + xkb_keymap_ref@Base 0.4.1 + xkb_keymap_unref@Base 0.4.1 + xkb_keysym_from_name@Base 0.4.1 + xkb_keysym_get_name@Base 0.4.1 + xkb_keysym_to_utf32@Base 0.4.1 + xkb_keysym_to_utf8@Base 0.4.1 + xkb_map_get_as_string@Base 0.4.1 + xkb_map_group_get_index@Base 0.4.1 + xkb_map_group_get_name@Base 0.4.1 + xkb_map_led_get_index@Base 0.4.1 + xkb_map_led_get_name@Base 0.4.1 + xkb_map_mod_get_index@Base 0.4.1 + xkb_map_mod_get_name@Base 0.4.1 + xkb_map_new_from_file@Base 0.4.1 + xkb_map_new_from_names@Base 0.4.1 + xkb_map_new_from_string@Base 0.4.1 + xkb_map_num_groups@Base 0.4.1 + xkb_map_num_leds@Base 0.4.1 + xkb_map_num_mods@Base 0.4.1 + xkb_map_ref@Base 0.4.1 + xkb_map_unref@Base 0.4.1 + xkb_state_get_keymap@Base 0.4.1 + xkb_state_get_map@Base 0.4.1 + xkb_state_group_index_is_active@Base 0.4.1 + xkb_state_group_name_is_active@Base 0.4.1 + xkb_state_key_get_consumed_mods@Base 0.4.1 + xkb_state_key_get_layout@Base 0.4.1 + xkb_state_key_get_level@Base 0.4.1 + xkb_state_key_get_one_sym@Base 0.4.1 + xkb_state_key_get_syms@Base 0.4.1 + xkb_state_key_get_utf32@Base 0.4.1 + xkb_state_key_get_utf8@Base 0.4.1 + xkb_state_layout_index_is_active@Base 0.4.1 + xkb_state_layout_name_is_active@Base 0.4.1 + xkb_state_led_index_is_active@Base 0.4.1 + xkb_state_led_name_is_active@Base 0.4.1 + xkb_state_mod_index_is_active@Base 0.4.1 + xkb_state_mod_index_is_consumed@Base 0.4.1 + xkb_state_mod_indices_are_active@Base 0.4.1 + xkb_state_mod_mask_remove_consumed@Base 0.4.1 + xkb_state_mod_name_is_active@Base 0.4.1 + xkb_state_mod_names_are_active@Base 0.4.1 + xkb_state_new@Base 0.4.1 + xkb_state_ref@Base 0.4.1 + xkb_state_serialize_group@Base 0.4.1 + xkb_state_serialize_layout@Base 0.4.1 + xkb_state_serialize_mods@Base 0.4.1 + xkb_state_unref@Base 0.4.1 + xkb_state_update_key@Base 0.4.1 + xkb_state_update_mask@Base 0.4.1 diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/merge-package-lists.py b/third_party/libwebrtc/build/linux/sysroot_scripts/merge-package-lists.py new file mode 100755 index 0000000000..70b3d05777 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/merge-package-lists.py @@ -0,0 +1,34 @@ +#!/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. + +"""Merge package entries from different package lists. +""" + +# This is used for replacing packages in eg. sid with those in experimental. +# The updated packages are ABI compatible, but include security patches, so we +# should use those instead in our sysroots. + +import sys + +if len(sys.argv) != 2: + exit(1) + +packages = {} + +def AddPackagesFromFile(file): + global packages + lines = file.readlines() + if len(lines) % 3 != 0: + exit(1) + for i in range(0, len(lines), 3): + packages[lines[i]] = (lines[i + 1], lines[i + 2]) + +AddPackagesFromFile(open(sys.argv[1], 'r')) +AddPackagesFromFile(sys.stdin) + +output_file = open(sys.argv[1], 'w') + +for (package, (filename, sha256)) in packages.items(): + output_file.write(package + filename + sha256) diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator-sid.sh b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator-sid.sh new file mode 100755 index 0000000000..86c311cb62 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator-sid.sh @@ -0,0 +1,463 @@ +#!/bin/bash +# Copyright 2017 The Chromium 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="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +DISTRO=debian +DIST=sid + +# Keep the "experimental" repo before the "sid" repo. There are some packages +# that are currently only available in experimental like libgtk-4-1, but if it +# were to be placed first, experimental (unreleased) versions of other packages +# like libc6 would take precedence over the sid (released) versions. While this +# may be useful for certain kinds of development, the standard sysroots should +# continue to be shipped only with released packages. +# Also keep "stretch" before "sid". For now, it's needed to bring back +# libgnome-keyring-dev which has since been deprecated and removed from sid. +# It will be needed until gnome keyring is removed (http://crbug.com/466975 and +# http://crbug.com/355223). +ARCHIVE_URL="https://snapshot.debian.org/archive/debian" +ARCHIVE_TIMESTAMP=20210309T203820Z +APT_SOURCES_LIST="${ARCHIVE_URL}/${ARCHIVE_TIMESTAMP}/ stretch main +${ARCHIVE_URL}/${ARCHIVE_TIMESTAMP}/ experimental main +${ARCHIVE_URL}/${ARCHIVE_TIMESTAMP}/ sid main" + +# gpg keyring file generated using generate_debian_archive_unstable.sh +KEYRING_FILE="${SCRIPT_DIR}/debian_archive_unstable.gpg" + +HAS_ARCH_AMD64=1 +HAS_ARCH_I386=1 +HAS_ARCH_ARM=1 +HAS_ARCH_ARM64=1 +HAS_ARCH_ARMEL=1 +HAS_ARCH_MIPS=1 +HAS_ARCH_MIPS64EL=1 + +# 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 + krb5-multidev + libappindicator-dev + libappindicator1 + libappindicator3-1 + libappindicator3-dev + libasound2 + libasound2-dev + libasyncns0 + libatk-bridge2.0-0 + libatk-bridge2.0-dev + libatk1.0-0 + libatk1.0-dev + libatomic1 + libatspi2.0-0 + libatspi2.0-dev + libattr1 + libaudit1 + libavahi-client3 + libavahi-common3 + libblkid-dev + libblkid1 + libbluetooth-dev + libbluetooth3 + libbrotli-dev + libbrotli1 + libbsd0 + libc6 + libc6-dev + libcairo-gobject2 + libcairo-script-interpreter2 + libcairo2 + libcairo2-dev + libcap-dev + libcap-ng0 + libcap2 + libcolord2 + libcom-err2 + libcups2 + libcups2-dev + libcupsimage2 + libcupsimage2-dev + libdatrie-dev + libcurl3-gnutls + libcurl4-gnutls-dev + libdatrie1 + libdb5.3 + libdbus-1-3 + libdbus-1-dev + libdbus-glib-1-2 + libdbusmenu-glib-dev + libdbusmenu-glib4 + libdbusmenu-gtk3-4 + libdbusmenu-gtk4 + libdeflate0 + libdrm-amdgpu1 + libdrm-dev + libdrm-nouveau2 + libdrm-radeon1 + libdrm2 + libegl-dev + libegl1 + libegl1-mesa + libegl1-mesa-dev + libelf-dev + libelf1 + libepoxy-dev + libepoxy0 + libevdev-dev + libevdev2 + libevent-2.1-7 + libexpat1 + libexpat1-dev + libffi-dev + libffi6 + libffi7 + libflac-dev + libflac8 + libfontconfig-dev + libfontconfig1 + libfreetype-dev + libfreetype6 + libfribidi-dev + libfribidi0 + libgbm-dev + libgbm1 + libgcc-10-dev + libgcc1 + libgcrypt20 + libgcrypt20-dev + libgdk-pixbuf-2.0-0 + libgdk-pixbuf-2.0-dev + libgl-dev + libgl1 + libgl1-mesa-dev + libgl1-mesa-glx + libglapi-mesa + libgles-dev + libgles1 + libgles2 + libglib2.0-0 + libglib2.0-dev + libglvnd-dev + libglvnd0 + libglx-dev + libglx0 + libgmp10 + libgnome-keyring-dev + libgnome-keyring0 + libgnutls-dane0 + libgnutls-openssl27 + libgnutls28-dev + libgnutls30 + libgnutlsxx28 + libgomp1 + libgpg-error-dev + libgpg-error0 + libgraphene-1.0-0 + libgraphene-1.0-dev + libgraphite2-3 + libgraphite2-dev + libgssapi-krb5-2 + libgssrpc4 + libgtk-3-0 + libgtk-3-dev + libgtk2.0-0 + libgudev-1.0-0 + libharfbuzz-dev + libharfbuzz-gobject0 + libharfbuzz-icu0 + libharfbuzz0b + libhogweed6 + libice6 + libicu-le-hb0 + libicu67 + libidl-2-0 + libidn11 + libidn2-0 + libindicator3-7 + libindicator7 + libinput-dev + libinput10 + libjbig0 + libjpeg62-turbo + libjpeg62-turbo-dev + libjson-glib-1.0-0 + libjsoncpp-dev + libjsoncpp24 + libk5crypto3 + libkadm5clnt-mit12 + libkadm5srv-mit12 + libkdb5-10 + libkeyutils1 + libkrb5-3 + libkrb5-dev + libkrb5support0 + liblcms2-2 + libldap-2.4-2 + libltdl7 + liblz4-1 + liblzma5 + liblzo2-2 + libmd0 + libminizip-dev + libminizip1 + libmount-dev + libmount1 + libmtdev1 + libncurses-dev + libncurses6 + libncursesw6 + libnettle8 + libnghttp2-14 + libnsl2 + libnspr4 + libnspr4-dev + libnss-db + libnss3 + libnss3-dev + libogg-dev + libogg0 + libopengl0 + libopus-dev + libopus0 + libp11-kit0 + libpam0g + libpam0g-dev + libpango-1.0-0 + libpango1.0-dev + libpangocairo-1.0-0 + libpangoft2-1.0-0 + libpangox-1.0-0 + libpangoxft-1.0-0 + libpci-dev + libpci3 + libpciaccess0 + libpcre16-3 + libpcre2-16-0 + libpcre2-32-0 + libpcre2-8-0 + libpcre2-dev + libpcre2-posix0 + libpcre2-posix2 + libpcre3 + libpcre3-dev + libpcre32-3 + libpcrecpp0v5 + libpipewire-0.3-0 + libpipewire-0.3-dev + libpixman-1-0 + libpixman-1-dev + libpng-dev + libpng16-16 + libpsl5 + libpthread-stubs0-dev + libpulse-dev + libpulse-mainloop-glib0 + libpulse0 + libre2-9 + libre2-dev + librest-0.7-0 + librtmp1 + libsasl2-2 + libselinux1 + libselinux1-dev + libsepol1 + libsepol1-dev + libsm6 + libsnappy-dev + libsnappy1v5 + libsndfile1 + libsoup-gnome2.4-1 + libsoup2.4-1 + libspa-0.2-dev + libspeechd-dev + libspeechd2 + libsqlite3-0 + libssh2-1 + libssl-dev + libssl1.1 + libstdc++-10-dev + libstdc++6 + libsystemd0 + libtasn1-6 + libthai-dev + libthai0 + libtiff5 + libtinfo6 + libtirpc3 + libudev-dev + libudev1 + libunbound8 + libunistring2 + libuuid1 + libva-dev + libva-drm2 + libva-glx2 + libva-wayland2 + libva-x11-2 + libva2 + libvorbis0a + libvorbisenc2 + libvpx-dev + libvpx6 + libvulkan-dev + libvulkan1 + libwacom2 + libwayland-client0 + libwayland-cursor0 + libwayland-dev + libwayland-egl1 + libwayland-egl1-mesa + libwayland-server0 + libwebp-dev + libwebp6 + libwebpdemux2 + libwebpmux3 + libwrap0 + libx11-6 + libx11-dev + libx11-xcb-dev + libx11-xcb1 + libxau-dev + libxau6 + libxcb-dri2-0 + libxcb-dri2-0-dev + libxcb-dri3-0 + libxcb-dri3-dev + libxcb-glx0 + libxcb-glx0-dev + libxcb-present-dev + libxcb-present0 + libxcb-render0 + libxcb-render0-dev + libxcb-shm0 + libxcb-shm0-dev + libxcb-sync1 + libxcb-xfixes0 + libxcb1 + libxcb1-dev + libxcomposite-dev + libxcomposite1 + libxcursor-dev + libxcursor1 + libxdamage-dev + libxdamage1 + libxdmcp-dev + libxdmcp6 + libxext-dev + libxext6 + libxfixes-dev + libxfixes3 + libxft-dev + libxft2 + libxi-dev + libxi6 + libxinerama-dev + libxinerama1 + libxkbcommon-dev + libxkbcommon0 + libxml2 + libxml2-dev + libxrandr-dev + libxrandr2 + libxrender-dev + libxrender1 + libxshmfence-dev + libxshmfence1 + libxslt1-dev + libxslt1.1 + libxss-dev + libxss1 + libxt-dev + libxt6 + libxtst-dev + libxtst6 + libxxf86vm-dev + libxxf86vm1 + libzstd1 + linux-libc-dev + mesa-common-dev + shared-mime-info + speech-dispatcher + uuid-dev + wayland-protocols + x11proto-composite-dev + x11proto-damage-dev + x11proto-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 + x11proto-xinerama-dev + zlib1g + zlib1g-dev +" + +DEBIAN_PACKAGES_AMD64=" + libgtk-4-1 + libgtk-4-dev + liblsan0 + libtsan0 +" + +DEBIAN_PACKAGES_X86=" + libasan6 + libcilkrts5 + libdrm-intel1 + libgtk-4-1 + libgtk-4-dev + libitm1 + libmpx2 + libquadmath0 + libubsan1 +" + +DEBIAN_PACKAGES_ARM=" + libasan6 + libdrm-etnaviv1 + libdrm-exynos1 + libdrm-freedreno1 + libdrm-omap1 + libdrm-tegra0 + libgtk-4-1 + libgtk-4-dev + libubsan1 +" + +DEBIAN_PACKAGES_ARM64=" + libasan6 + libdrm-etnaviv1 + libdrm-freedreno1 + libdrm-tegra0 + libgmp10 + libgtk-4-1 + libgtk-4-dev + libitm1 + liblsan0 + libthai0 + libtsan0 + libubsan1 +" + +DEBIAN_PACKAGES_ARMEL=" + libasan6 + libdrm-exynos1 + libdrm-freedreno1 + libdrm-omap1 + libdrm-tegra0 + libgtk-4-1 + libgtk-4-dev + libubsan1 +" + +DEBIAN_PACKAGES_MIPS64EL=" +" + +. "${SCRIPT_DIR}/sysroot-creator.sh" diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator.sh b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator.sh new file mode 100644 index 0000000000..fda3de4e57 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroot-creator.sh @@ -0,0 +1,968 @@ +# 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-sid.sh). Its up to the parent scripts +# to define certain environment variables: e.g. +# DISTRO=debian +# DIST=sid +# # Similar in syntax to /etc/apt/sources.list +# APT_SOURCES_LIST="http://ftp.us.debian.org/debian/ sid main" +# KEYRING_FILE=debian-archive-sid-stable.gpg +# DEBIAN_PACKAGES="gcc libz libssl" + +#@ This script builds Debian/Ubuntu 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 "${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 HAS_ARCH_AMD64=${HAS_ARCH_AMD64:=0} +readonly HAS_ARCH_I386=${HAS_ARCH_I386:=0} +readonly HAS_ARCH_ARM=${HAS_ARCH_ARM:=0} +readonly HAS_ARCH_ARM64=${HAS_ARCH_ARM64:=0} +readonly HAS_ARCH_ARMEL=${HAS_ARCH_ARMEL:=0} +readonly HAS_ARCH_MIPS=${HAS_ARCH_MIPS:=0} +readonly HAS_ARCH_MIPS64EL=${HAS_ARCH_MIPS64EL:=0} + +readonly REQUIRED_TOOLS="curl xzcat" + +###################################################################### +# Package Config +###################################################################### + +readonly PACKAGES_EXT=xz +readonly RELEASE_FILE="Release" +readonly RELEASE_FILE_GPG="Release.gpg" + +readonly DEBIAN_DEP_LIST_AMD64="generated_package_lists/${DIST}.amd64" +readonly DEBIAN_DEP_LIST_I386="generated_package_lists/${DIST}.i386" +readonly DEBIAN_DEP_LIST_ARM="generated_package_lists/${DIST}.arm" +readonly DEBIAN_DEP_LIST_ARM64="generated_package_lists/${DIST}.arm64" +readonly DEBIAN_DEP_LIST_ARMEL="generated_package_lists/${DIST}.armel" +readonly DEBIAN_DEP_LIST_MIPS="generated_package_lists/${DIST}.mipsel" +readonly DEBIAN_DEP_LIST_MIPS64EL="generated_package_lists/${DIST}.mips64el" + + +###################################################################### +# Helper +###################################################################### + +Banner() { + echo "######################################################################" + echo $* + echo "######################################################################" +} + + +SubBanner() { + echo "----------------------------------------------------------------------" + echo $* + echo "----------------------------------------------------------------------" +} + + +Usage() { + egrep "^#@" "${BASH_SOURCE[0]}" | cut --bytes=3- +} + + +DownloadOrCopyNonUniqueFilename() { + # Use this function instead of DownloadOrCopy when the url uniquely + # identifies the file, but the filename (excluding the directory) + # does not. + local url="$1" + local dest="$2" + + local hash="$(echo "$url" | sha256sum | cut -d' ' -f1)" + + DownloadOrCopy "${url}" "${dest}.${hash}" + # cp the file to prevent having to redownload it, but mv it to the + # final location so that it's atomic. + cp "${dest}.${hash}" "${dest}.$$" + mv "${dest}.$$" "${dest}" +} + +DownloadOrCopy() { + if [ -f "$2" ] ; then + echo "$2 already in place" + return + fi + + HTTP=0 + echo "$1" | grep -Eqs '^https?://' && HTTP=1 + if [ "$HTTP" = "1" ]; then + SubBanner "downloading from $1 -> $2" + # Appending the "$$" shell pid is necessary here to prevent concurrent + # instances of sysroot-creator.sh from trying to write to the same file. + local temp_file="${2}.partial.$$" + # curl --retry doesn't retry when the page gives a 4XX error, so we need to + # manually rerun. + for i in {1..10}; do + # --create-dirs is added in case there are slashes in the filename, as can + # happen with the "debian/security" release class. + local http_code=$(curl -L "$1" --create-dirs -o "${temp_file}" \ + -w "%{http_code}") + if [ ${http_code} -eq 200 ]; then + break + fi + echo "Bad HTTP code ${http_code} when downloading $1" + rm -f "${temp_file}" + sleep $i + done + if [ ! -f "${temp_file}" ]; then + exit 1 + fi + mv "${temp_file}" $2 + else + SubBanner "copying from $1" + cp "$1" "$2" + fi +} + + +SetEnvironmentVariables() { + case $1 in + *Amd64) + ARCH=AMD64 + ;; + *I386) + ARCH=I386 + ;; + *Mips64el) + ARCH=MIPS64EL + ;; + *Mips) + ARCH=MIPS + ;; + *ARM) + ARCH=ARM + ;; + *ARM64) + ARCH=ARM64 + ;; + *ARMEL) + ARCH=ARMEL + ;; + *) + echo "ERROR: Unable to determine architecture based on: $1" + exit 1 + ;; + esac + 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.tar.xz" + + 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 -I "xz -9 -T0" -cf ${TARBALL} -C ${INSTALL_ROOT} . +} + +ExtractPackageXz() { + local src_file="$1" + local dst_file="$2" + local repo="$3" + xzcat "${src_file}" | egrep '^(Package:|Filename:|SHA256:) ' | + sed "s|Filename: |Filename: ${repo}|" > "${dst_file}" +} + +GeneratePackageListDist() { + local arch="$1" + set -- $2 + local repo="$1" + local dist="$2" + local repo_name="$3" + + TMP_PACKAGE_LIST="${BUILD_DIR}/Packages.${dist}_${repo_name}_${arch}" + local repo_basedir="${repo}/dists/${dist}" + local package_list="${BUILD_DIR}/Packages.${dist}_${repo_name}_${arch}.${PACKAGES_EXT}" + local package_file_arch="${repo_name}/binary-${arch}/Packages.${PACKAGES_EXT}" + local package_list_arch="${repo_basedir}/${package_file_arch}" + + DownloadOrCopyNonUniqueFilename "${package_list_arch}" "${package_list}" + VerifyPackageListing "${package_file_arch}" "${package_list}" ${repo} ${dist} + ExtractPackageXz "${package_list}" "${TMP_PACKAGE_LIST}" ${repo} +} + +GeneratePackageListCommon() { + local output_file="$1" + local arch="$2" + local packages="$3" + + local dists="${DIST} ${DIST_UPDATES:-}" + + local list_base="${BUILD_DIR}/Packages.${DIST}_${arch}" + > "${list_base}" # Create (or truncate) a zero-length file. + echo "${APT_SOURCES_LIST}" | while read source; do + GeneratePackageListDist "${arch}" "${source}" + cat "${TMP_PACKAGE_LIST}" | ./merge-package-lists.py "${list_base}" + done + + GeneratePackageList "${list_base}" "${output_file}" "${packages}" +} + +GeneratePackageListAmd64() { + GeneratePackageListCommon "$1" amd64 "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_X86:=} ${DEBIAN_PACKAGES_AMD64:=}" +} + +GeneratePackageListI386() { + GeneratePackageListCommon "$1" i386 "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_X86:=}" +} + +GeneratePackageListARM() { + GeneratePackageListCommon "$1" armhf "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_ARM:=}" +} + +GeneratePackageListARM64() { + GeneratePackageListCommon "$1" arm64 "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_ARM64:=}" +} + +GeneratePackageListARMEL() { + GeneratePackageListCommon "$1" armel "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_ARMEL:=}" +} + +GeneratePackageListMips() { + GeneratePackageListCommon "$1" mipsel "${DEBIAN_PACKAGES}" +} + +GeneratePackageListMips64el() { + GeneratePackageListCommon "$1" mips64el "${DEBIAN_PACKAGES} + ${DEBIAN_PACKAGES_MIPS64EL:=}" +} + +StripChecksumsFromPackageList() { + local package_file="$1" + sed -i 's/ [a-f0-9]\{64\}$//' "$package_file" +} + +###################################################################### +# +###################################################################### + +HacksAndPatchesCommon() { + local arch=$1 + local os=$2 + local strip=$3 + Banner "Misc Hacks & Patches" + # these are linker scripts with absolute pathnames in them + # which we rewrite here + lscripts="${INSTALL_ROOT}/usr/lib/${arch}-${os}/libpthread.so \ + ${INSTALL_ROOT}/usr/lib/${arch}-${os}/libc.so" + + # Rewrite linker scripts + sed -i -e 's|/usr/lib/${arch}-${os}/||g' ${lscripts} + sed -i -e 's|/lib/${arch}-${os}/||g' ${lscripts} + + # Unversion libdbus and libxkbcommon symbols. This is required because + # libdbus-1-3 and libxkbcommon0 switched from unversioned symbols to versioned + # ones, and we must still support distros using the unversioned library. This + # hack can be removed once support for Ubuntu Trusty and Debian Jessie are + # dropped. + ${strip} -R .gnu.version_d -R .gnu.version \ + "${INSTALL_ROOT}/lib/${arch}-${os}/libdbus-1.so.3" + cp "${SCRIPT_DIR}/libdbus-1-3-symbols" \ + "${INSTALL_ROOT}/debian/libdbus-1-3/DEBIAN/symbols" + + ${strip} -R .gnu.version_d -R .gnu.version \ + "${INSTALL_ROOT}/usr/lib/${arch}-${os}/libxkbcommon.so.0.0.0" + cp "${SCRIPT_DIR}/libxkbcommon0-symbols" \ + "${INSTALL_ROOT}/debian/libxkbcommon0/DEBIAN/symbols" + + # libxcomposite1 is missing a symbols file. + cp "${SCRIPT_DIR}/libxcomposite1-symbols" \ + "${INSTALL_ROOT}/debian/libxcomposite1/DEBIAN/symbols" + + # Shared objects depending on libdbus-1.so.3 have unsatisfied undefined + # versioned symbols. To avoid LLD --no-allow-shlib-undefined errors, rewrite + # DT_NEEDED entries from libdbus-1.so.3 to a different string. LLD will + # suppress --no-allow-shlib-undefined diagnostics for such shared objects. + set +e + for f in "${INSTALL_ROOT}/lib/${arch}-${os}"/*.so \ + "${INSTALL_ROOT}/usr/lib/${arch}-${os}"/*.so; do + echo "$f" | grep -q 'libdbus-1.so$' && continue + # In a dependent shared object, the only occurrence of "libdbus-1.so.3" is + # the string referenced by the DT_NEEDED entry. + offset=$(LANG=C grep -abo libdbus-1.so.3 "$f") + [ -n "$offset" ] || continue + echo -n 'libdbus-1.so.0' | dd of="$f" conv=notrunc bs=1 \ + seek="$(echo -n "$offset" | cut -d : -f 1)" status=none + done + set -e + + # Glibc 2.27 introduced some new optimizations to several math functions, but + # it will be a while before it makes it into all supported distros. Luckily, + # glibc maintains ABI compatibility with previous versions, so the old symbols + # are still there. + # TODO(thomasanderson): Remove this once glibc 2.27 is available on all + # supported distros. + local math_h="${INSTALL_ROOT}/usr/include/math.h" + local libm_so="${INSTALL_ROOT}/lib/${arch}-${os}/libm.so.6" + nm -D --defined-only --with-symbol-versions "${libm_so}" | \ + "${SCRIPT_DIR}/find_incompatible_glibc_symbols.py" >> "${math_h}" + + # glob64() was also optimized in glibc 2.27. Make sure to choose the older + # version. + local glob_h="${INSTALL_ROOT}/usr/include/glob.h" + local libc_so="${INSTALL_ROOT}/lib/${arch}-${os}/libc.so.6" + nm -D --defined-only --with-symbol-versions "${libc_so}" | \ + "${SCRIPT_DIR}/find_incompatible_glibc_symbols.py" >> "${glob_h}" + + # fcntl64() was introduced in glibc 2.28. Make sure to use fcntl() instead. + local fcntl_h="${INSTALL_ROOT}/usr/include/fcntl.h" + sed -i '{N; s/#ifndef __USE_FILE_OFFSET64\(\nextern int fcntl\)/#if 1\1/}' \ + "${fcntl_h}" + # On i386, fcntl() was updated in glibc 2.28. + nm -D --defined-only --with-symbol-versions "${libc_so}" | \ + "${SCRIPT_DIR}/find_incompatible_glibc_symbols.py" >> "${fcntl_h}" + + # __GLIBC_MINOR__ is used as a feature test macro. Replace it with the + # earliest supported version of glibc (2.17, https://crbug.com/376567). + local features_h="${INSTALL_ROOT}/usr/include/features.h" + sed -i 's|\(#define\s\+__GLIBC_MINOR__\)|\1 17 //|' "${features_h}" + + # This is for chrome's ./build/linux/pkg-config-wrapper + # which overwrites PKG_CONFIG_LIBDIR internally + SubBanner "Move pkgconfig scripts" + mkdir -p ${INSTALL_ROOT}/usr/lib/pkgconfig + mv ${INSTALL_ROOT}/usr/lib/${arch}-${os}/pkgconfig/* \ + ${INSTALL_ROOT}/usr/lib/pkgconfig +} + + +HacksAndPatchesAmd64() { + HacksAndPatchesCommon x86_64 linux-gnu strip +} + + +HacksAndPatchesI386() { + HacksAndPatchesCommon i386 linux-gnu strip +} + + +HacksAndPatchesARM() { + HacksAndPatchesCommon arm linux-gnueabihf arm-linux-gnueabihf-strip +} + +HacksAndPatchesARM64() { + # Use the unstripped libdbus for arm64 to prevent linker errors. + # https://bugs.chromium.org/p/webrtc/issues/detail?id=8535 + HacksAndPatchesCommon aarch64 linux-gnu true +} + +HacksAndPatchesARMEL() { + HacksAndPatchesCommon arm linux-gnueabi arm-linux-gnueabi-strip +} + +HacksAndPatchesMips() { + HacksAndPatchesCommon mipsel linux-gnu mipsel-linux-gnu-strip +} + + +HacksAndPatchesMips64el() { + HacksAndPatchesCommon mips64el linux-gnuabi64 mips64el-linux-gnuabi64-strip +} + + +InstallIntoSysroot() { + Banner "Install Libs And Headers Into Jail" + + mkdir -p ${BUILD_DIR}/debian-packages + # The /debian directory is an implementation detail that's used to cd into + # when running dpkg-shlibdeps. + mkdir -p ${INSTALL_ROOT}/debian + # An empty control file is necessary to run dpkg-shlibdeps. + touch ${INSTALL_ROOT}/debian/control + 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 $(basename ${file})" + DownloadOrCopy ${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-deb -x ${package} ${INSTALL_ROOT} + + base_package=$(dpkg-deb --field ${package} Package) + mkdir -p ${INSTALL_ROOT}/debian/${base_package}/DEBIAN + dpkg-deb -e ${package} ${INSTALL_ROOT}/debian/${base_package}/DEBIAN + done + + # Prune /usr/share, leaving only pkgconfig. + ls -d ${INSTALL_ROOT}/usr/share/* | grep -v "/pkgconfig$" | xargs rm -r +} + + +CleanupJailSymlinks() { + Banner "Jail symlink cleanup" + + SAVEDPWD=$(pwd) + cd ${INSTALL_ROOT} + local libdirs="lib usr/lib" + if [ "${ARCH}" != "MIPS" ]; then + libdirs="${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}" + # Relativize the symlink. + prefix=$(echo "${link}" | sed -e 's/[^/]//g' | sed -e 's|/|../|g') + ln -snfv "${prefix}${target}" "${link}" + 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" +} + + +VerifyLibraryDepsCommon() { + local arch=$1 + local os=$2 + local find_dirs=( + "${INSTALL_ROOT}/lib/" + "${INSTALL_ROOT}/lib/${arch}-${os}/" + "${INSTALL_ROOT}/usr/lib/${arch}-${os}/" + ) + local needed_libs="$( + find ${find_dirs[*]} -name "*\.so*" -type f -exec file {} \; | \ + grep ': ELF' | sed 's/^\(.*\): .*$/\1/' | xargs readelf -d | \ + grep NEEDED | sort | uniq | sed 's/^.*Shared library: \[\(.*\)\]$/\1/g')" + local all_libs="$(find ${find_dirs[*]} -printf '%f\n')" + # Ignore missing libdbus-1.so.0 + all_libs+="$(echo -e '\nlibdbus-1.so.0')" + local missing_libs="$(grep -vFxf <(echo "${all_libs}") \ + <(echo "${needed_libs}"))" + if [ ! -z "${missing_libs}" ]; then + echo "Missing libraries:" + echo "${missing_libs}" + exit 1 + fi +} + + +VerifyLibraryDepsAmd64() { + VerifyLibraryDepsCommon x86_64 linux-gnu +} + + +VerifyLibraryDepsI386() { + VerifyLibraryDepsCommon i386 linux-gnu +} + + +VerifyLibraryDepsARM() { + VerifyLibraryDepsCommon arm linux-gnueabihf +} + + +VerifyLibraryDepsARM64() { + VerifyLibraryDepsCommon aarch64 linux-gnu +} + +VerifyLibraryDepsARMEL() { + VerifyLibraryDepsCommon arm linux-gnueabi +} + +VerifyLibraryDepsMips() { + VerifyLibraryDepsCommon mipsel linux-gnu +} + + +VerifyLibraryDepsMips64el() { + VerifyLibraryDepsCommon mips64el linux-gnuabi64 +} + + +#@ +#@ BuildSysrootAmd64 +#@ +#@ Build everything and package it +BuildSysrootAmd64() { + if [ "$HAS_ARCH_AMD64" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_AMD64}" + GeneratePackageListAmd64 "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesAmd64 + VerifyLibraryDepsAmd64 + CreateTarBall +} + +#@ +#@ BuildSysrootI386 +#@ +#@ Build everything and package it +BuildSysrootI386() { + if [ "$HAS_ARCH_I386" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_I386}" + GeneratePackageListI386 "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesI386 + VerifyLibraryDepsI386 + CreateTarBall +} + +#@ +#@ BuildSysrootARM +#@ +#@ Build everything and package it +BuildSysrootARM() { + if [ "$HAS_ARCH_ARM" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_ARM}" + GeneratePackageListARM "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesARM + VerifyLibraryDepsARM + CreateTarBall +} + +#@ +#@ BuildSysrootARM64 +#@ +#@ Build everything and package it +BuildSysrootARM64() { + if [ "$HAS_ARCH_ARM64" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_ARM64}" + GeneratePackageListARM64 "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesARM64 + VerifyLibraryDepsARM64 + CreateTarBall +} + +#@ +#@ BuildSysrootARMEL +#@ +#@ Build everything and package it +BuildSysrootARMEL() { + if [ "$HAS_ARCH_ARMEL" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_ARMEL}" + GeneratePackageListARMEL "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesARMEL + VerifyLibraryDepsARMEL + CreateTarBall +} + +#@ +#@ BuildSysrootMips +#@ +#@ Build everything and package it +BuildSysrootMips() { + if [ "$HAS_ARCH_MIPS" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_MIPS}" + GeneratePackageListMips "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesMips + VerifyLibraryDepsMips + CreateTarBall +} + +#@ +#@ BuildSysrootMips64el +#@ +#@ Build everything and package it +BuildSysrootMips64el() { + if [ "$HAS_ARCH_MIPS64EL" = "0" ]; then + return + fi + ClearInstallDir + local package_file="${DEBIAN_DEP_LIST_MIPS64EL}" + GeneratePackageListMips64el "$package_file" + local files_and_sha256sums="$(cat ${package_file})" + StripChecksumsFromPackageList "$package_file" + InstallIntoSysroot ${files_and_sha256sums} + CleanupJailSymlinks + HacksAndPatchesMips64el + VerifyLibraryDepsMips64el + CreateTarBall +} + +#@ +#@ BuildSysrootAll +#@ +#@ Build sysroot images for all architectures +BuildSysrootAll() { + RunCommand BuildSysrootAmd64 + RunCommand BuildSysrootI386 + RunCommand BuildSysrootARM + RunCommand BuildSysrootARM64 + RunCommand BuildSysrootARMEL + RunCommand BuildSysrootMips + RunCommand BuildSysrootMips64el +} + +UploadSysroot() { + local sha=$(sha1sum "${TARBALL}" | awk '{print $1;}') + set -x + gsutil.py cp -a public-read "${TARBALL}" \ + "gs://chrome-linux-sysroot/toolchain/$sha/" + set +x +} + +#@ +#@ UploadSysrootAmd64 +#@ +UploadSysrootAmd64() { + if [ "$HAS_ARCH_AMD64" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootI386 +#@ +UploadSysrootI386() { + if [ "$HAS_ARCH_I386" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootARM +#@ +UploadSysrootARM() { + if [ "$HAS_ARCH_ARM" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootARM64 +#@ +UploadSysrootARM64() { + if [ "$HAS_ARCH_ARM64" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootARMEL +#@ +UploadSysrootARMEL() { + if [ "$HAS_ARCH_ARMEL" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootMips +#@ +UploadSysrootMips() { + if [ "$HAS_ARCH_MIPS" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootMips64el +#@ +UploadSysrootMips64el() { + if [ "$HAS_ARCH_MIPS64EL" = "0" ]; then + return + fi + UploadSysroot "$@" +} + +#@ +#@ UploadSysrootAll +#@ +#@ Upload sysroot image for all architectures +UploadSysrootAll() { + RunCommand UploadSysrootAmd64 "$@" + RunCommand UploadSysrootI386 "$@" + RunCommand UploadSysrootARM "$@" + RunCommand UploadSysrootARM64 "$@" + RunCommand UploadSysrootARMEL "$@" + RunCommand UploadSysrootMips "$@" + RunCommand UploadSysrootMips64el "$@" + +} + +# +# CheckForDebianGPGKeyring +# +# Make sure the Debian GPG keys exist. Otherwise print a helpful message. +# +CheckForDebianGPGKeyring() { + if [ ! -e "$KEYRING_FILE" ]; then + echo "KEYRING_FILE not found: ${KEYRING_FILE}" + echo "Debian GPG keys missing. Install the debian-archive-keyring package." + exit 1 + fi +} + +# +# VerifyPackageListing +# +# Verifies the downloaded Packages.xz file has the right checksums. +# +VerifyPackageListing() { + local file_path="$1" + local output_file="$2" + local repo="$3" + local dist="$4" + + local repo_basedir="${repo}/dists/${dist}" + local release_list="${repo_basedir}/${RELEASE_FILE}" + local release_list_gpg="${repo_basedir}/${RELEASE_FILE_GPG}" + + local release_file="${BUILD_DIR}/${dist}-${RELEASE_FILE}" + local release_file_gpg="${BUILD_DIR}/${dist}-${RELEASE_FILE_GPG}" + + CheckForDebianGPGKeyring + + DownloadOrCopyNonUniqueFilename ${release_list} ${release_file} + DownloadOrCopyNonUniqueFilename ${release_list_gpg} ${release_file_gpg} + echo "Verifying: ${release_file} with ${release_file_gpg}" + set -x + gpgv --keyring "${KEYRING_FILE}" "${release_file_gpg}" "${release_file}" + set +x + + echo "Verifying: ${output_file}" + local sha256sum=$(grep -E "${file_path}\$|:\$" "${release_file}" | \ + grep "SHA256:" -A 1 | xargs echo | awk '{print $2;}') + + 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 "pool/.*" | sed 's/.*Filename: //') + if [ -z "${pkg_full}" ]; then + echo "ERROR: missing package: $pkg" + exit 1 + fi + 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_full $sha256sum >> "$output_file" + done + # sort -o does an in-place sort of this file + sort "$output_file" -o "$output_file" +} + +#@ +#@ PrintArchitectures +#@ +#@ Prints supported architectures. +PrintArchitectures() { + if [ "$HAS_ARCH_AMD64" = "1" ]; then + echo Amd64 + fi + if [ "$HAS_ARCH_I386" = "1" ]; then + echo I386 + fi + if [ "$HAS_ARCH_ARM" = "1" ]; then + echo ARM + fi + if [ "$HAS_ARCH_ARM64" = "1" ]; then + echo ARM64 + fi + if [ "$HAS_ARCH_ARMEL" = "1" ]; then + echo ARMEL + fi + if [ "$HAS_ARCH_MIPS" = "1" ]; then + echo Mips + fi + if [ "$HAS_ARCH_MIPS64EL" = "1" ]; then + echo Mips64el + fi +} + +#@ +#@ PrintDistro +#@ +#@ Prints distro. eg: ubuntu +PrintDistro() { + echo ${DISTRO} +} + +#@ +#@ DumpRelease +#@ +#@ Prints disto release. eg: jessie +PrintRelease() { + echo ${DIST} +} + +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 --regexp='\(^Print\)\|\(All$\)'; then + "$@" + else + RunCommand "$@" + fi +fi diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/sysroots.json b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroots.json new file mode 100644 index 0000000000..6248db7d9a --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/sysroots.json @@ -0,0 +1,37 @@ +{ + "sid_amd64": { + "Sha1Sum": "43a87bbebccad99325fdcf34166295b121ee15c7", + "SysrootDir": "debian_sid_amd64-sysroot", + "Tarball": "debian_sid_amd64_sysroot.tar.xz" + }, + "sid_arm": { + "Sha1Sum": "11d6f690ca49e8ba01a1d8c5346cedad2cf308fd", + "SysrootDir": "debian_sid_arm-sysroot", + "Tarball": "debian_sid_arm_sysroot.tar.xz" + }, + "sid_arm64": { + "Sha1Sum": "2befe8ce3e88be6080e4fb7e6d412278ea6a7625", + "SysrootDir": "debian_sid_arm64-sysroot", + "Tarball": "debian_sid_arm64_sysroot.tar.xz" + }, + "sid_armel": { + "Sha1Sum": "a0e2a51aaa7d779fc45415ac30c835b67caa6663", + "SysrootDir": "debian_sid_armel-sysroot", + "Tarball": "debian_sid_armel_sysroot.tar.xz" + }, + "sid_i386": { + "Sha1Sum": "d53a049af5961f2f121ee4e149918097c193f8ed", + "SysrootDir": "debian_sid_i386-sysroot", + "Tarball": "debian_sid_i386_sysroot.tar.xz" + }, + "sid_mips": { + "Sha1Sum": "eb577cef43088b7e0540950c74f994267631d4cd", + "SysrootDir": "debian_sid_mips-sysroot", + "Tarball": "debian_sid_mips_sysroot.tar.xz" + }, + "sid_mips64el": { + "Sha1Sum": "6cb76f27035d1460fe164f7e6c5318c047aac153", + "SysrootDir": "debian_sid_mips64el-sysroot", + "Tarball": "debian_sid_mips64el_sysroot.tar.xz" + } +} diff --git a/third_party/libwebrtc/build/linux/sysroot_scripts/update-archive-timestamp.sh b/third_party/libwebrtc/build/linux/sysroot_scripts/update-archive-timestamp.sh new file mode 100755 index 0000000000..a61dd99571 --- /dev/null +++ b/third_party/libwebrtc/build/linux/sysroot_scripts/update-archive-timestamp.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Copyright 2018 The Chromium 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 updates sysroot-creator-*.sh with the timestamp of the latest +# snapshot from snapshot.debian.org. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ARCHIVE_URL="http://snapshot.debian.org/archive/debian" + +# Use 9999-01-01 as the date so that we get a redirect to the page with the +# latest timestamp. +TIMESTAMP=$(curl -s "${ARCHIVE_URL}/99990101T000000Z/pool/" | \ + sed -n "s|.*${ARCHIVE_URL}/\([[:digit:]TZ]\+\)/pool/.*|\1|p" | head -n 1) + +sed -i "s/ARCHIVE_TIMESTAMP=.*$/ARCHIVE_TIMESTAMP=${TIMESTAMP}/" \ + "${SCRIPT_DIR}"/sysroot-creator-*.sh diff --git a/third_party/libwebrtc/build/linux/unbundle/README b/third_party/libwebrtc/build/linux/unbundle/README new file mode 100644 index 0000000000..b6b6321b38 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/README @@ -0,0 +1,57 @@ +This directory contains files that make it possible for Linux +distributions to build Chromium using system libraries and exclude the +source code for Chromium's bundled copies of system libraries in a +consistent manner. Nothing here is used in normal developer builds. + + +For more info on the Linux distros' philosophy on bundling system +libraries and why this exists, please read the following: + + - https://fedoraproject.org/wiki/Packaging:No_Bundled_Libraries + - https://wiki.gentoo.org/wiki/Why_not_bundle_dependencies + - http://www.debian.org/doc/debian-policy/ch-source.html#s-embeddedfiles + +For more Chromium-specific context please read +http://spot.livejournal.com/312320.html . + +Additional resources which might provide even more context: + + - http://events.linuxfoundation.org/sites/events/files/slides/LinuxCon%202014%20Slides_0.pdf + - https://lwn.net/Articles/619158/ + + +This directory is provided in the source tree so one can follow the +above guidelines without having to download additional tools and worry +about having the right version of the tool. It is a compromise solution +which takes into account Chromium developers who want to avoid the +perceived burden of more conditional code in build files, and +expectations of Open Source community, where using system-provided +libraries is the norm. + +Usage: + +1. remove_bundled_libraries.py + + For example: remove_bundled_libraries.py third_party/zlib + + The script scans sources looking for third_party directories. + Everything that is not explicitly preserved is removed (except for + GYP/GN build 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 source code extracted from a tarball, + not on a git repository. + + NOTE: by default this will not remove anything (for safety). Pass + the --do-remove flag to actually remove files. + +2. replace_gn_files.py --system-libraries lib... + + This swaps out a normal library GN build file that is intended for + use with a bundled library for a build file that is set up to use + the system library. While some build files have use_system_libfoo + build flags, using unbundled build files has the advantage that Linux + distros can build Chromium without having to specify many additional + build flags. + + For example: replace_gn_files.py --system-libraries libxml diff --git a/third_party/libwebrtc/build/linux/unbundle/ffmpeg.gn b/third_party/libwebrtc/build/linux/unbundle/ffmpeg.gn new file mode 100644 index 0000000000..16e2074470 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/ffmpeg.gn @@ -0,0 +1,38 @@ +# 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. + +import("//build/buildflag_header.gni") +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +pkg_config("system_ffmpeg") { + packages = [ + "libavcodec", + "libavformat", + "libavutil", + ] +} + +buildflag_header("ffmpeg_features") { + header = "ffmpeg_features.h" + flags = [ "USE_SYSTEM_FFMPEG=true" ] +} + +shim_headers("ffmpeg_shim") { + root_path = "." + headers = [ + "libavcodec/avcodec.h", + "libavcodec/packet.h", + "libavformat/avformat.h", + "libavutil/imgutils.h", + ] +} + +source_set("ffmpeg") { + deps = [ + ":ffmpeg_features", + ":ffmpeg_shim", + ] + public_configs = [ ":system_ffmpeg" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/flac.gn b/third_party/libwebrtc/build/linux/unbundle/flac.gn new file mode 100644 index 0000000000..ced81d6550 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/flac.gn @@ -0,0 +1,30 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +pkg_config("system_flac") { + packages = [ "flac" ] +} + +shim_headers("flac_shim") { + root_path = "include" + headers = [ + "FLAC/all.h", + "FLAC/assert.h", + "FLAC/callback.h", + "FLAC/export.h", + "FLAC/format.h", + "FLAC/metadata.h", + "FLAC/ordinals.h", + "FLAC/stream_decoder.h", + "FLAC/stream_encoder.h", + ] +} + +source_set("flac") { + deps = [ ":flac_shim" ] + public_configs = [ ":system_flac" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/fontconfig.gn b/third_party/libwebrtc/build/linux/unbundle/fontconfig.gn new file mode 100644 index 0000000000..49236c9c8a --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/fontconfig.gn @@ -0,0 +1,13 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +assert(is_linux || is_chromeos) + +config("fontconfig_config") { + libs = [ "fontconfig" ] +} + +group("fontconfig") { + public_configs = [ ":fontconfig_config" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/freetype.gn b/third_party/libwebrtc/build/linux/unbundle/freetype.gn new file mode 100644 index 0000000000..cafa9db6b7 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/freetype.gn @@ -0,0 +1,14 @@ +# Copyright 2017 The Chromium 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() { + # Blink needs a recent and properly build-configured FreeType version to + # support OpenType variations, color emoji and avoid security bugs. By default + # we ship and link such a version as part of Chrome. For distributions that + # prefer to keep linking to the version the system, FreeType must be newer + # than version 2.7.1 and have color bitmap support compiled in. WARNING: + # System FreeType configurations other than as described WILL INTRODUCE TEXT + # RENDERING AND SECURITY REGRESSIONS. + use_system_freetype = true +} diff --git a/third_party/libwebrtc/build/linux/unbundle/harfbuzz-ng.gn b/third_party/libwebrtc/build/linux/unbundle/harfbuzz-ng.gn new file mode 100644 index 0000000000..b4ba17a9b8 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/harfbuzz-ng.gn @@ -0,0 +1,13 @@ +# Copyright 2017 The Chromium 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() { + # Blink uses a cutting-edge version of Harfbuzz (version listed in + # third_party/harfbuzz-ng/README.chromium); most Linux distros do not contain + # a new enough version of the code to work correctly. However, ChromeOS + # chroots (i.e. real ChromeOS builds for devices) do contain a new enough + # version of the library, and so this variable exists so that ChromeOS can + # build against the system lib and keep binary sizes smaller. + use_system_harfbuzz = true +} diff --git a/third_party/libwebrtc/build/linux/unbundle/icu.gn b/third_party/libwebrtc/build/linux/unbundle/icu.gn new file mode 100644 index 0000000000..6f3f8438bd --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/icu.gn @@ -0,0 +1,254 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +group("icu") { + public_deps = [ + ":icui18n", + ":icuuc", + ] +} + +config("icu_config") { + defines = [ + "USING_SYSTEM_ICU=1", + "ICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_STATIC", + + # U_EXPORT (defined in unicode/platform.h) is used to set public visibility + # on classes through the U_COMMON_API and U_I18N_API macros (among others). + # When linking against the system ICU library, we want its symbols to have + # public LTO visibility. This disables CFI checks for the ICU classes and + # allows whole-program optimization to be applied to the rest of Chromium. + # + # Both U_COMMON_API and U_I18N_API macros would be defined to U_EXPORT only + # when U_COMBINED_IMPLEMENTATION is defined (see unicode/utypes.h). Because + # we override the default system UCHAR_TYPE (char16_t), it is not possible + # to use U_COMBINED_IMPLEMENTATION at this moment, meaning the U_COMMON_API + # and U_I18N_API macros are set to U_IMPORT which is an empty definition. + # + # Until building with UCHAR_TYPE=char16_t is supported, one way to apply + # public visibility (and thus public LTO visibility) to all ICU classes is + # to define U_IMPORT to have the same value as U_EXPORT. For more details, + # please see: https://crbug.com/822820 + "U_IMPORT=U_EXPORT", + ] +} + +pkg_config("system_icui18n") { + packages = [ "icu-i18n" ] +} + +pkg_config("system_icuuc") { + packages = [ "icu-uc" ] +} + +source_set("icui18n") { + deps = [ ":icui18n_shim" ] + public_configs = [ + ":icu_config", + ":system_icui18n", + ] +} + +source_set("icuuc") { + deps = [ ":icuuc_shim" ] + public_configs = [ + ":icu_config", + ":system_icuuc", + ] +} + +shim_headers("icui18n_shim") { + root_path = "source/i18n" + headers = [ + # This list can easily be updated using the commands below: + # cd third_party/icu/source/i18n + # find unicode -iname '*.h' -printf ' "%p",\n' | LC_ALL=C sort -u + "unicode/alphaindex.h", + "unicode/basictz.h", + "unicode/calendar.h", + "unicode/choicfmt.h", + "unicode/coleitr.h", + "unicode/coll.h", + "unicode/compactdecimalformat.h", + "unicode/curramt.h", + "unicode/currpinf.h", + "unicode/currunit.h", + "unicode/datefmt.h", + "unicode/dcfmtsym.h", + "unicode/decimfmt.h", + "unicode/dtfmtsym.h", + "unicode/dtitvfmt.h", + "unicode/dtitvinf.h", + "unicode/dtptngen.h", + "unicode/dtrule.h", + "unicode/fieldpos.h", + "unicode/fmtable.h", + "unicode/format.h", + "unicode/fpositer.h", + "unicode/gender.h", + "unicode/gregocal.h", + "unicode/listformatter.h", + "unicode/measfmt.h", + "unicode/measunit.h", + "unicode/measure.h", + "unicode/msgfmt.h", + "unicode/numfmt.h", + "unicode/numsys.h", + "unicode/plurfmt.h", + "unicode/plurrule.h", + "unicode/rbnf.h", + "unicode/rbtz.h", + "unicode/regex.h", + "unicode/region.h", + "unicode/reldatefmt.h", + "unicode/scientificnumberformatter.h", + "unicode/search.h", + "unicode/selfmt.h", + "unicode/simpletz.h", + "unicode/smpdtfmt.h", + "unicode/sortkey.h", + "unicode/stsearch.h", + "unicode/tblcoll.h", + "unicode/timezone.h", + "unicode/tmunit.h", + "unicode/tmutamt.h", + "unicode/tmutfmt.h", + "unicode/translit.h", + "unicode/tzfmt.h", + "unicode/tznames.h", + "unicode/tzrule.h", + "unicode/tztrans.h", + "unicode/ucal.h", + "unicode/ucol.h", + "unicode/ucoleitr.h", + "unicode/ucsdet.h", + "unicode/udat.h", + "unicode/udateintervalformat.h", + "unicode/udatpg.h", + "unicode/ufieldpositer.h", + "unicode/uformattable.h", + "unicode/ugender.h", + "unicode/ulocdata.h", + "unicode/umsg.h", + "unicode/unirepl.h", + "unicode/unum.h", + "unicode/unumsys.h", + "unicode/upluralrules.h", + "unicode/uregex.h", + "unicode/uregion.h", + "unicode/ureldatefmt.h", + "unicode/usearch.h", + "unicode/uspoof.h", + "unicode/utmscale.h", + "unicode/utrans.h", + "unicode/vtzone.h", + ] +} + +shim_headers("icuuc_shim") { + root_path = "source/common" + headers = [ + # This list can easily be updated using the commands below: + # cd third_party/icu/source/common + # find unicode -iname '*.h' -printf ' "%p",\n' | LC_ALL=C sort -u + "unicode/appendable.h", + "unicode/brkiter.h", + "unicode/bytestream.h", + "unicode/bytestrie.h", + "unicode/bytestriebuilder.h", + "unicode/caniter.h", + "unicode/casemap.h", + "unicode/char16ptr.h", + "unicode/chariter.h", + "unicode/dbbi.h", + "unicode/docmain.h", + "unicode/dtintrv.h", + "unicode/edits.h", + "unicode/enumset.h", + "unicode/errorcode.h", + "unicode/filteredbrk.h", + "unicode/icudataver.h", + "unicode/icuplug.h", + "unicode/idna.h", + "unicode/localematcher.h", + "unicode/localpointer.h", + "unicode/locdspnm.h", + "unicode/locid.h", + "unicode/messagepattern.h", + "unicode/normalizer2.h", + "unicode/normlzr.h", + "unicode/parseerr.h", + "unicode/parsepos.h", + "unicode/platform.h", + "unicode/ptypes.h", + "unicode/putil.h", + "unicode/rbbi.h", + "unicode/rep.h", + "unicode/resbund.h", + "unicode/schriter.h", + "unicode/simpleformatter.h", + "unicode/std_string.h", + "unicode/strenum.h", + "unicode/stringpiece.h", + "unicode/stringtriebuilder.h", + "unicode/symtable.h", + "unicode/ubidi.h", + "unicode/ubiditransform.h", + "unicode/ubrk.h", + "unicode/ucasemap.h", + "unicode/ucat.h", + "unicode/uchar.h", + "unicode/ucharstrie.h", + "unicode/ucharstriebuilder.h", + "unicode/uchriter.h", + "unicode/uclean.h", + "unicode/ucnv.h", + "unicode/ucnv_cb.h", + "unicode/ucnv_err.h", + "unicode/ucnvsel.h", + "unicode/uconfig.h", + "unicode/ucurr.h", + "unicode/udata.h", + "unicode/udisplaycontext.h", + "unicode/uenum.h", + "unicode/uidna.h", + "unicode/uiter.h", + "unicode/uldnames.h", + "unicode/ulistformatter.h", + "unicode/uloc.h", + "unicode/umachine.h", + "unicode/umisc.h", + "unicode/unifilt.h", + "unicode/unifunct.h", + "unicode/unimatch.h", + "unicode/uniset.h", + "unicode/unistr.h", + "unicode/unorm.h", + "unicode/unorm2.h", + "unicode/uobject.h", + "unicode/urename.h", + "unicode/urep.h", + "unicode/ures.h", + "unicode/uscript.h", + "unicode/uset.h", + "unicode/usetiter.h", + "unicode/ushape.h", + "unicode/usprep.h", + "unicode/ustring.h", + "unicode/ustringtrie.h", + "unicode/utext.h", + "unicode/utf.h", + "unicode/utf16.h", + "unicode/utf32.h", + "unicode/utf8.h", + "unicode/utf_old.h", + "unicode/utrace.h", + "unicode/utypes.h", + "unicode/uvernum.h", + "unicode/uversion.h", + ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libdrm.gn b/third_party/libwebrtc/build/linux/unbundle/libdrm.gn new file mode 100644 index 0000000000..30cdcef3ef --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libdrm.gn @@ -0,0 +1,20 @@ +# Copyright 2017 The Chromium 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/shim_headers.gni") + +pkg_config("system_libdrm") { + packages = [ "libdrm" ] +} + +shim_headers("libdrm_shim") { + root_path = "src/include" + headers = [ "drm.h" ] +} + +source_set("libdrm") { + deps = [ ":libdrm_shim" ] + public_configs = [ ":system_libdrm" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libevent.gn b/third_party/libwebrtc/build/linux/unbundle/libevent.gn new file mode 100644 index 0000000000..7e1c34d337 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libevent.gn @@ -0,0 +1,15 @@ +# 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. + +import("//build/shim_headers.gni") + +shim_headers("libevent_shim") { + root_path = "." + headers = [ "event.h" ] +} + +source_set("libevent") { + deps = [ ":libevent_shim" ] + libs = [ "event" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libjpeg.gn b/third_party/libwebrtc/build/linux/unbundle/libjpeg.gn new file mode 100644 index 0000000000..17398ea60b --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libjpeg.gn @@ -0,0 +1,12 @@ +# Copyright 2018 The Chromium 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() { + # Uses system libjpeg. If true, overrides use_libjpeg_turbo. + use_system_libjpeg = true + + # Uses libjpeg_turbo as the jpeg implementation. Has no effect if + # use_system_libjpeg is set. + use_libjpeg_turbo = true +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libpng.gn b/third_party/libwebrtc/build/linux/unbundle/libpng.gn new file mode 100644 index 0000000000..1d6590dcae --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libpng.gn @@ -0,0 +1,23 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +pkg_config("libpng_config") { + packages = [ "libpng" ] +} + +shim_headers("libpng_shim") { + root_path = "." + headers = [ + "png.h", + "pngconf.h", + ] +} + +source_set("libpng") { + deps = [ ":libpng_shim" ] + public_configs = [ ":libpng_config" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libvpx.gn b/third_party/libwebrtc/build/linux/unbundle/libvpx.gn new file mode 100644 index 0000000000..87be30d170 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libvpx.gn @@ -0,0 +1,32 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +pkg_config("system_libvpx") { + packages = [ "vpx" ] +} + +shim_headers("libvpx_shim") { + root_path = "source/libvpx" + headers = [ + "vpx/vp8.h", + "vpx/vp8cx.h", + "vpx/vp8dx.h", + "vpx/vpx_codec.h", + "vpx/vpx_codec_impl_bottom.h", + "vpx/vpx_codec_impl_top.h", + "vpx/vpx_decoder.h", + "vpx/vpx_encoder.h", + "vpx/vpx_frame_buffer.h", + "vpx/vpx_image.h", + "vpx/vpx_integer.h", + ] +} + +source_set("libvpx") { + deps = [ ":libvpx_shim" ] + public_configs = [ ":system_libvpx" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libwebp.gn b/third_party/libwebrtc/build/linux/unbundle/libwebp.gn new file mode 100644 index 0000000000..de0c230318 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libwebp.gn @@ -0,0 +1,35 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") +import("//build/shim_headers.gni") + +pkg_config("system_libwebp") { + packages = [ + "libwebp", + "libwebpdemux", + "libwebpmux", + ] +} + +shim_headers("libwebp_shim") { + root_path = "src" + headers = [ + "webp/decode.h", + "webp/demux.h", + "webp/encode.h", + "webp/mux.h", + "webp/mux_types.h", + "webp/types.h", + ] +} + +source_set("libwebp_webp") { + deps = [ ":libwebp_shim" ] + public_configs = [ ":system_libwebp" ] +} + +group("libwebp") { + deps = [ ":libwebp_webp" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libxml.gn b/third_party/libwebrtc/build/linux/unbundle/libxml.gn new file mode 100644 index 0000000000..3587881eea --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libxml.gn @@ -0,0 +1,55 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") + +pkg_config("system_libxml") { + packages = [ "libxml-2.0" ] +} + +source_set("libxml") { + public_configs = [ ":system_libxml" ] +} + +static_library("libxml_utils") { + # Do not expand this visibility list without first consulting with the + # Security Team. + visibility = [ + ":xml_reader", + ":xml_writer", + "//base/test:test_support", + "//services/data_decoder:xml_parser_fuzzer", + ] + sources = [ + "chromium/libxml_utils.cc", + "chromium/libxml_utils.h", + ] + public_configs = [ ":system_libxml" ] +} + +static_library("xml_reader") { + # Do not expand this visibility list without first consulting with the + # Security Team. + visibility = [ + "//base/test:test_support", + "//components/policy/core/common:unit_tests", + "//services/data_decoder:*", + "//tools/traffic_annotation/auditor:auditor_sources", + ] + sources = [ + "chromium/xml_reader.cc", + "chromium/xml_reader.h", + ] + deps = [ ":libxml_utils" ] +} + +static_library("xml_writer") { + # The XmlWriter is considered safe to use from any target. + visibility = [ "*" ] + sources = [ + "chromium/xml_writer.cc", + "chromium/xml_writer.h", + ] + deps = [ ":libxml_utils" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/libxslt.gn b/third_party/libwebrtc/build/linux/unbundle/libxslt.gn new file mode 100644 index 0000000000..885574ef89 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/libxslt.gn @@ -0,0 +1,13 @@ +# 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. + +import("//build/config/linux/pkg_config.gni") + +pkg_config("system_libxslt") { + packages = [ "libxslt" ] +} + +source_set("libxslt") { + public_configs = [ ":system_libxslt" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/openh264.gn b/third_party/libwebrtc/build/linux/unbundle/openh264.gn new file mode 100644 index 0000000000..882e0a8223 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/openh264.gn @@ -0,0 +1,36 @@ +# Copyright 2017 The Chromium 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/shim_headers.gni") + +pkg_config("config") { + packages = [ "openh264" ] +} + +shim_headers("openh264_shim") { + prefix = "wels/" + root_path = "src/codec/api/svc" + headers = [ + "codec_api.h", + "codec_app_def.h", + "codec_def.h", + "codec_ver.h", + ] +} + +source_set("common") { + deps = [ ":openh264_shim" ] + public_configs = [ ":config" ] +} + +source_set("processing") { + deps = [ ":openh264_shim" ] + public_configs = [ ":config" ] +} + +source_set("encoder") { + deps = [ ":openh264_shim" ] + public_configs = [ ":config" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/opus.gn b/third_party/libwebrtc/build/linux/unbundle/opus.gn new file mode 100644 index 0000000000..504d7d27f0 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/opus.gn @@ -0,0 +1,43 @@ +# Copyright 2017 The Chromium 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/shim_headers.gni") + +pkg_config("opus_config") { + packages = [ "opus" ] +} + +shim_headers("opus_shim") { + root_path = "src/include" + headers = [ + "opus.h", + "opus_defines.h", + "opus_multistream.h", + "opus_types.h", + ] +} + +source_set("opus") { + deps = [ ":opus_shim" ] + public_configs = [ ":opus_config" ] +} + +source_set("opus_compare") { +} + +source_set("opus_demo") { +} + +source_set("test_opus_api") { +} + +source_set("test_opus_decode") { +} + +source_set("test_opus_encode") { +} + +source_set("test_opus_padding") { +} diff --git a/third_party/libwebrtc/build/linux/unbundle/re2.gn b/third_party/libwebrtc/build/linux/unbundle/re2.gn new file mode 100644 index 0000000000..824d778813 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/re2.gn @@ -0,0 +1,27 @@ +# 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. + +import("//build/config/c++/c++.gni") +import("//build/shim_headers.gni") + +assert(!use_custom_libcxx, + "Usage of the system libre2.so is not supported with " + + "use_custom_libcxx=true because the library's interface relies on " + + "libstdc++'s std::string and std::vector.") + +shim_headers("re2_shim") { + root_path = "src" + headers = [ + "re2/filtered_re2.h", + "re2/re2.h", + "re2/set.h", + "re2/stringpiece.h", + "re2/variadic_function.h", + ] +} + +source_set("re2") { + deps = [ ":re2_shim" ] + libs = [ "re2" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/remove_bundled_libraries.py b/third_party/libwebrtc/build/linux/unbundle/remove_bundled_libraries.py new file mode 100755 index 0000000000..899877a165 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/remove_bundled_libraries.py @@ -0,0 +1,107 @@ +#!/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. + +""" +Removes bundled libraries to make sure they are not used. + +See README for more details. +""" + +from __future__ import print_function + +import optparse +import os.path +import sys + + +def DoMain(argv): + my_dirname = os.path.abspath(os.path.dirname(__file__)) + source_tree_root = os.path.abspath( + os.path.join(my_dirname, '..', '..', '..')) + + if os.path.join(source_tree_root, 'build', 'linux', 'unbundle') != my_dirname: + print('Sanity check failed: please run this script from ' + 'build/linux/unbundle directory.') + return 1 + + parser = optparse.OptionParser() + parser.add_option('--do-remove', action='store_true') + + options, args = parser.parse_args(argv) + + exclusion_used = {} + for exclusion in args: + exclusion_used[exclusion] = False + + for root, dirs, files in os.walk(source_tree_root, topdown=False): + # Only look at paths which contain a "third_party" component + # (note that e.g. third_party.png doesn't count). + root_relpath = os.path.relpath(root, source_tree_root) + if 'third_party' not in root_relpath.split(os.sep): + continue + + for f in files: + path = os.path.join(root, f) + relpath = os.path.relpath(path, source_tree_root) + + excluded = False + for exclusion in args: + # Require precise exclusions. Find the right-most third_party + # in the relative path, and if there is more than one ignore + # the exclusion if it's completely contained within the part + # before right-most third_party path component. + split = relpath.rsplit(os.sep + 'third_party' + os.sep, 1) + if len(split) > 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 + + # Same about GN files. + if f.endswith('.gn') or f.endswith('.gni'): + 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.items(): + 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/third_party/libwebrtc/build/linux/unbundle/replace_gn_files.py b/third_party/libwebrtc/build/linux/unbundle/replace_gn_files.py new file mode 100755 index 0000000000..eba4bd1fb3 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/replace_gn_files.py @@ -0,0 +1,83 @@ +#!/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. + +""" +Replaces GN files in tree with files from here that +make the build use system libraries. +""" + +from __future__ import print_function + +import argparse +import os +import shutil +import sys + + +REPLACEMENTS = { + 'ffmpeg': 'third_party/ffmpeg/BUILD.gn', + 'flac': 'third_party/flac/BUILD.gn', + 'fontconfig': 'third_party/fontconfig/BUILD.gn', + 'freetype': 'build/config/freetype/freetype.gni', + 'harfbuzz-ng': 'third_party/harfbuzz-ng/harfbuzz.gni', + 'icu': 'third_party/icu/BUILD.gn', + 'libdrm': 'third_party/libdrm/BUILD.gn', + 'libevent': 'base/third_party/libevent/BUILD.gn', + 'libjpeg': 'third_party/libjpeg.gni', + 'libpng': 'third_party/libpng/BUILD.gn', + 'libvpx': 'third_party/libvpx/BUILD.gn', + 'libwebp': 'third_party/libwebp/BUILD.gn', + 'libxml': 'third_party/libxml/BUILD.gn', + 'libxslt': 'third_party/libxslt/BUILD.gn', + 'openh264': 'third_party/openh264/BUILD.gn', + 'opus': 'third_party/opus/BUILD.gn', + 're2': 'third_party/re2/BUILD.gn', + 'snappy': 'third_party/snappy/BUILD.gn', + 'zlib': 'third_party/zlib/BUILD.gn', +} + + +def DoMain(argv): + my_dirname = os.path.dirname(__file__) + source_tree_root = os.path.abspath( + os.path.join(my_dirname, '..', '..', '..')) + + parser = argparse.ArgumentParser() + parser.add_argument('--system-libraries', nargs='*', default=[]) + parser.add_argument('--undo', action='store_true') + + args = parser.parse_args(argv) + + handled_libraries = set() + for lib, path in REPLACEMENTS.items(): + if lib not in args.system_libraries: + continue + handled_libraries.add(lib) + + if args.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 GN file from directory of this script to target path. + shutil.copyfile(os.path.join(my_dirname, '%s.gn' % lib), + os.path.join(source_tree_root, path)) + + unhandled_libraries = set(args.system_libraries) - handled_libraries + if unhandled_libraries: + print('Unrecognized system libraries requested: %s' % ', '.join( + sorted(unhandled_libraries)), file=sys.stderr) + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(DoMain(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/linux/unbundle/snappy.gn b/third_party/libwebrtc/build/linux/unbundle/snappy.gn new file mode 100644 index 0000000000..966666f800 --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/snappy.gn @@ -0,0 +1,20 @@ +# 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. + +import("//build/shim_headers.gni") + +shim_headers("snappy_shim") { + root_path = "src" + headers = [ + "snappy-c.h", + "snappy-sinksource.h", + "snappy-stubs-public.h", + "snappy.h", + ] +} + +source_set("snappy") { + deps = [ ":snappy_shim" ] + libs = [ "snappy" ] +} diff --git a/third_party/libwebrtc/build/linux/unbundle/zlib.gn b/third_party/libwebrtc/build/linux/unbundle/zlib.gn new file mode 100644 index 0000000000..97d6aebe6d --- /dev/null +++ b/third_party/libwebrtc/build/linux/unbundle/zlib.gn @@ -0,0 +1,64 @@ +# 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. + +import("//build/shim_headers.gni") + +shim_headers("zlib_shim") { + root_path = "." + headers = [ "zlib.h" ] +} + +config("system_zlib") { + defines = [ "USE_SYSTEM_ZLIB=1" ] +} + +config("zlib_config") { + configs = [ ":system_zlib" ] +} + +source_set("zlib") { + deps = [ ":zlib_shim" ] + libs = [ "z" ] + public_configs = [ ":system_zlib" ] +} + +shim_headers("minizip_shim") { + root_path = "contrib" + headers = [ + "minizip/crypt.h", + "minizip/ioapi.h", + "minizip/iowin32.h", + "minizip/mztools.h", + "minizip/unzip.h", + "minizip/zip.h", + ] +} + +source_set("minizip") { + deps = [ ":minizip_shim" ] + libs = [ "minizip" ] +} + +static_library("zip") { + sources = [ + "google/zip.cc", + "google/zip.h", + "google/zip_internal.cc", + "google/zip_internal.h", + "google/zip_reader.cc", + "google/zip_reader.h", + ] + deps = [ + ":minizip", + "//base", + ] +} + +static_library("compression_utils") { + sources = [ + "google/compression_utils.cc", + "google/compression_utils.h", + ] + deps = [ ":zlib" ] +} diff --git a/third_party/libwebrtc/build/locale_tool.py b/third_party/libwebrtc/build/locale_tool.py new file mode 100755 index 0000000000..b5729d8af0 --- /dev/null +++ b/third_party/libwebrtc/build/locale_tool.py @@ -0,0 +1,1512 @@ +#!/usr/bin/env vpython +# Copyright 2019 The Chromium 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 used to manage locale-related files in Chromium. + +This script is used to check, and potentially fix, many locale-related files +in your Chromium workspace, such as: + + - GRIT input files (.grd) and the corresponding translations (.xtb). + + - BUILD.gn files listing Android localized resource string resource .xml + generated by GRIT for all supported Chrome locales. These correspond to + elements that use the type="android" attribute. + +The --scan-dir option can be used to check for all files under a specific +directory, and the --fix-inplace option can be used to try fixing any file +that doesn't pass the check. + +This can be very handy to avoid tedious and repetitive work when adding new +translations / locales to the Chrome code base, since this script can update +said input files for you. + +Important note: checks and fix may fail on some input files. For example +remoting/resources/remoting_strings.grd contains an in-line comment element +inside its section that breaks the script. The check will fail, and +trying to fix it too, but at least the file will not be modified. +""" + +from __future__ import print_function + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import unittest + +# Assume this script is under build/ +_SCRIPT_DIR = os.path.dirname(__file__) +_SCRIPT_NAME = os.path.join(_SCRIPT_DIR, os.path.basename(__file__)) +_TOP_SRC_DIR = os.path.join(_SCRIPT_DIR, '..') + +# Need to import android/gyp/util/resource_utils.py here. +sys.path.insert(0, os.path.join(_SCRIPT_DIR, 'android/gyp')) + +from util import build_utils +from util import resource_utils + + +# This locale is the default and doesn't have translations. +_DEFAULT_LOCALE = 'en-US' + +# Misc terminal codes to provide human friendly progress output. +_CONSOLE_CODE_MOVE_CURSOR_TO_COLUMN_0 = '\x1b[0G' +_CONSOLE_CODE_ERASE_LINE = '\x1b[K' +_CONSOLE_START_LINE = ( + _CONSOLE_CODE_MOVE_CURSOR_TO_COLUMN_0 + _CONSOLE_CODE_ERASE_LINE) + +########################################################################## +########################################################################## +##### +##### G E N E R I C H E L P E R F U N C T I O N S +##### +########################################################################## +########################################################################## + +def _FixChromiumLangAttribute(lang): + """Map XML "lang" attribute values to Chromium locale names.""" + _CHROMIUM_LANG_FIXES = { + 'en': 'en-US', # For now, Chromium doesn't have an 'en' locale. + 'iw': 'he', # 'iw' is the obsolete form of ISO 639-1 for Hebrew + 'no': 'nb', # 'no' is used by the Translation Console for Norwegian (nb). + } + return _CHROMIUM_LANG_FIXES.get(lang, lang) + + +def _FixTranslationConsoleLocaleName(locale): + _FIXES = { + 'nb': 'no', # Norwegian. + 'he': 'iw', # Hebrew + } + return _FIXES.get(locale, locale) + + +def _CompareLocaleLists(list_a, list_expected, list_name): + """Compare two lists of locale names. Print errors if they differ. + + Args: + list_a: First list of locales. + list_expected: Second list of locales, as expected. + list_name: Name of list printed in error messages. + Returns: + On success, return False. On error, print error messages and return True. + """ + errors = [] + missing_locales = sorted(set(list_a) - set(list_expected)) + if missing_locales: + errors.append('Missing locales: %s' % missing_locales) + + extra_locales = sorted(set(list_expected) - set(list_a)) + if extra_locales: + errors.append('Unexpected locales: %s' % extra_locales) + + if errors: + print('Errors in %s definition:' % list_name) + for error in errors: + print(' %s\n' % error) + return True + + return False + + +def _BuildIntervalList(input_list, predicate): + """Find ranges of contiguous list items that pass a given predicate. + + Args: + input_list: An input list of items of any type. + predicate: A function that takes a list item and return True if it + passes a given test. + Returns: + A list of (start_pos, end_pos) tuples, where all items in + [start_pos, end_pos) pass the predicate. + """ + result = [] + size = len(input_list) + start = 0 + while True: + # Find first item in list that passes the predicate. + while start < size and not predicate(input_list[start]): + start += 1 + + if start >= size: + return result + + # Find first item in the rest of the list that does not pass the + # predicate. + end = start + 1 + while end < size and predicate(input_list[end]): + end += 1 + + result.append((start, end)) + start = end + 1 + + +def _SortListSubRange(input_list, start, end, key_func): + """Sort an input list's sub-range according to a specific key function. + + Args: + input_list: An input list. + start: Sub-range starting position in list. + end: Sub-range limit position in list. + key_func: A function that extracts a sort key from a line. + Returns: + A copy of |input_list|, with all items in [|start|, |end|) sorted + according to |key_func|. + """ + result = input_list[:start] + inputs = [] + for pos in xrange(start, end): + line = input_list[pos] + key = key_func(line) + inputs.append((key, line)) + + for _, line in sorted(inputs): + result.append(line) + + result += input_list[end:] + return result + + +def _SortElementsRanges(lines, element_predicate, element_key): + """Sort all elements of a given type in a list of lines by a given key. + + Args: + lines: input lines. + element_predicate: predicate function to select elements to sort. + element_key: lambda returning a comparison key for each element that + passes the predicate. + Returns: + A new list of input lines, with lines [start..end) sorted. + """ + intervals = _BuildIntervalList(lines, element_predicate) + for start, end in intervals: + lines = _SortListSubRange(lines, start, end, element_key) + + return lines + + +def _ProcessFile(input_file, locales, check_func, fix_func): + """Process a given input file, potentially fixing it. + + Args: + input_file: Input file path. + locales: List of Chrome locales to consider / expect. + check_func: A lambda called to check the input file lines with + (input_lines, locales) argument. It must return an list of error + messages, or None on success. + fix_func: None, or a lambda called to fix the input file lines with + (input_lines, locales). It must return the new list of lines for + the input file, and may raise an Exception in case of error. + Returns: + True at the moment. + """ + print('%sProcessing %s...' % (_CONSOLE_START_LINE, input_file), end=' ') + sys.stdout.flush() + with open(input_file) as f: + input_lines = f.readlines() + errors = check_func(input_file, input_lines, locales) + if errors: + print('\n%s%s' % (_CONSOLE_START_LINE, '\n'.join(errors))) + if fix_func: + try: + input_lines = fix_func(input_file, input_lines, locales) + output = ''.join(input_lines) + with open(input_file, 'wt') as f: + f.write(output) + print('Fixed %s.' % input_file) + except Exception as e: # pylint: disable=broad-except + print('Skipped %s: %s' % (input_file, e)) + + return True + + +def _ScanDirectoriesForFiles(scan_dirs, file_predicate): + """Scan a directory for files that match a given predicate. + + Args: + scan_dir: A list of top-level directories to start scan in. + file_predicate: lambda function which is passed the file's base name + and returns True if its full path, relative to |scan_dir|, should be + passed in the result. + Returns: + A list of file full paths. + """ + result = [] + for src_dir in scan_dirs: + for root, _, files in os.walk(src_dir): + result.extend(os.path.join(root, f) for f in files if file_predicate(f)) + return result + + +def _WriteFile(file_path, file_data): + """Write |file_data| to |file_path|.""" + with open(file_path, 'w') as f: + f.write(file_data) + + +def _FindGnExecutable(): + """Locate the real GN executable used by this Chromium checkout. + + This is needed because the depot_tools 'gn' wrapper script will look + for .gclient and other things we really don't need here. + + Returns: + Path of real host GN executable from current Chromium src/ checkout. + """ + # Simply scan buildtools/*/gn and return the first one found so we don't + # have to guess the platform-specific sub-directory name (e.g. 'linux64' + # for 64-bit Linux machines). + buildtools_dir = os.path.join(_TOP_SRC_DIR, 'buildtools') + for subdir in os.listdir(buildtools_dir): + subdir_path = os.path.join(buildtools_dir, subdir) + if not os.path.isdir(subdir_path): + continue + gn_path = os.path.join(subdir_path, 'gn') + if os.path.exists(gn_path): + return gn_path + return None + + +def _PrettyPrintListAsLines(input_list, available_width, trailing_comma=False): + result = [] + input_str = ', '.join(input_list) + while len(input_str) > available_width: + pos = input_str.rfind(',', 0, available_width) + result.append(input_str[:pos + 1]) + input_str = input_str[pos + 1:].lstrip() + if trailing_comma and input_str: + input_str += ',' + result.append(input_str) + return result + + +class _PrettyPrintListAsLinesTest(unittest.TestCase): + + def test_empty_list(self): + self.assertListEqual([''], _PrettyPrintListAsLines([], 10)) + + def test_wrapping(self): + input_list = ['foo', 'bar', 'zoo', 'tool'] + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 8), + ['foo,', 'bar,', 'zoo,', 'tool']) + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 12), ['foo, bar,', 'zoo, tool']) + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 79), ['foo, bar, zoo, tool']) + + def test_trailing_comma(self): + input_list = ['foo', 'bar', 'zoo', 'tool'] + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 8, trailing_comma=True), + ['foo,', 'bar,', 'zoo,', 'tool,']) + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 12, trailing_comma=True), + ['foo, bar,', 'zoo, tool,']) + self.assertListEqual( + _PrettyPrintListAsLines(input_list, 79, trailing_comma=True), + ['foo, bar, zoo, tool,']) + + +########################################################################## +########################################################################## +##### +##### L O C A L E S L I S T S +##### +########################################################################## +########################################################################## + +# Various list of locales that will be extracted from build/config/locales.gni +# Do not use these directly, use ChromeLocales(), and IosUnsupportedLocales() +# instead to access these lists. +_INTERNAL_CHROME_LOCALES = [] +_INTERNAL_IOS_UNSUPPORTED_LOCALES = [] + + +def ChromeLocales(): + """Return the list of all locales supported by Chrome.""" + if not _INTERNAL_CHROME_LOCALES: + _ExtractAllChromeLocalesLists() + return _INTERNAL_CHROME_LOCALES + + +def IosUnsupportedLocales(): + """Return the list of locales that are unsupported on iOS.""" + if not _INTERNAL_IOS_UNSUPPORTED_LOCALES: + _ExtractAllChromeLocalesLists() + return _INTERNAL_IOS_UNSUPPORTED_LOCALES + + +def _PrepareTinyGnWorkspace(work_dir, out_subdir_name='out'): + """Populate an empty directory with a tiny set of working GN config files. + + This allows us to run 'gn gen --root ' as fast as possible + to generate files containing the locales list. This takes about 300ms on + a decent machine, instead of more than 5 seconds when running the equivalent + commands from a real Chromium workspace, which requires regenerating more + than 23k targets. + + Args: + work_dir: target working directory. + out_subdir_name: Name of output sub-directory. + Returns: + Full path of output directory created inside |work_dir|. + """ + # Create top-level .gn file that must point to the BUILDCONFIG.gn. + _WriteFile(os.path.join(work_dir, '.gn'), + 'buildconfig = "//BUILDCONFIG.gn"\n') + # Create BUILDCONFIG.gn which must set a default toolchain. Also add + # all variables that may be used in locales.gni in a declare_args() block. + _WriteFile( + os.path.join(work_dir, 'BUILDCONFIG.gn'), + r'''set_default_toolchain("toolchain") +declare_args () { + is_ios = false + is_android = true +} +''') + + # Create fake toolchain required by BUILDCONFIG.gn. + os.mkdir(os.path.join(work_dir, 'toolchain')) + _WriteFile(os.path.join(work_dir, 'toolchain', 'BUILD.gn'), + r'''toolchain("toolchain") { + tool("stamp") { + command = "touch {{output}}" # Required by action() + } +} +''') + + # Create top-level BUILD.gn, GN requires at least one target to build so do + # that with a fake action which will never be invoked. Also write the locales + # to misc files in the output directory. + _WriteFile( + os.path.join(work_dir, 'BUILD.gn'), r'''import("//locales.gni") + +action("create_foo") { # fake action to avoid GN complaints. + script = "//build/create_foo.py" + inputs = [] + outputs = [ "$target_out_dir/$target_name" ] +} + +# Write the locales lists to files in the output directory. +_filename = root_build_dir + "/foo" +write_file(_filename + ".locales", locales, "json") +write_file(_filename + ".ios_unsupported_locales", + ios_unsupported_locales, + "json") +''') + + # Copy build/config/locales.gni to the workspace, as required by BUILD.gn. + shutil.copyfile(os.path.join(_TOP_SRC_DIR, 'build', 'config', 'locales.gni'), + os.path.join(work_dir, 'locales.gni')) + + # Create output directory. + out_path = os.path.join(work_dir, out_subdir_name) + os.mkdir(out_path) + + # And ... we're good. + return out_path + + +# Set this global variable to the path of a given temporary directory +# before calling _ExtractAllChromeLocalesLists() if you want to debug +# the locales list extraction process. +_DEBUG_LOCALES_WORK_DIR = None + + +def _ReadJsonList(file_path): + """Read a JSON file that must contain a list, and return it.""" + with open(file_path) as f: + data = json.load(f) + assert isinstance(data, list), "JSON file %s is not a list!" % file_path + return [item.encode('utf8') for item in data] + + +def _ExtractAllChromeLocalesLists(): + with build_utils.TempDir() as tmp_path: + if _DEBUG_LOCALES_WORK_DIR: + tmp_path = _DEBUG_LOCALES_WORK_DIR + build_utils.DeleteDirectory(tmp_path) + build_utils.MakeDirectory(tmp_path) + + out_path = _PrepareTinyGnWorkspace(tmp_path, 'out') + + # NOTE: The file suffixes used here should be kept in sync with + # build/config/locales.gni + gn_executable = _FindGnExecutable() + try: + subprocess.check_output( + [gn_executable, 'gen', out_path, '--root=' + tmp_path]) + except subprocess.CalledProcessError as e: + print(e.output) + raise e + + global _INTERNAL_CHROME_LOCALES + _INTERNAL_CHROME_LOCALES = _ReadJsonList( + os.path.join(out_path, 'foo.locales')) + + global _INTERNAL_IOS_UNSUPPORTED_LOCALES + _INTERNAL_IOS_UNSUPPORTED_LOCALES = _ReadJsonList( + os.path.join(out_path, 'foo.ios_unsupported_locales')) + + +########################################################################## +########################################################################## +##### +##### G R D H E L P E R F U N C T I O N S +##### +########################################################################## +########################################################################## + +# Technical note: +# +# Even though .grd files are XML, an xml parser library is not used in order +# to preserve the original file's structure after modification. ElementTree +# tends to re-order attributes in each element when re-writing an XML +# document tree, which is undesirable here. +# +# Thus simple line-based regular expression matching is used instead. +# + +# Misc regular expressions used to match elements and their attributes. +_RE_OUTPUT_ELEMENT = re.compile(r'') +_RE_TRANSLATION_ELEMENT = re.compile(r'') +_RE_FILENAME_ATTRIBUTE = re.compile(r'filename="([^"]*)"') +_RE_LANG_ATTRIBUTE = re.compile(r'lang="([^"]*)"') +_RE_PATH_ATTRIBUTE = re.compile(r'path="([^"]*)"') +_RE_TYPE_ANDROID_ATTRIBUTE = re.compile(r'type="android"') + + + +def _IsGritInputFile(input_file): + """Returns True iff this is a GRIT input file.""" + return input_file.endswith('.grd') + + +def _GetXmlLangAttribute(xml_line): + """Extract the lang attribute value from an XML input line.""" + m = _RE_LANG_ATTRIBUTE.search(xml_line) + if not m: + return None + return m.group(1) + + +class _GetXmlLangAttributeTest(unittest.TestCase): + TEST_DATA = { + '': None, + 'foo': None, + 'lang=foo': None, + 'lang="foo"': 'foo', + '': 'foo bar', + '': 'fr-CA', + } + + def test_GetXmlLangAttribute(self): + for test_line, expected in self.TEST_DATA.iteritems(): + self.assertEquals(_GetXmlLangAttribute(test_line), expected) + + +def _SortGrdElementsRanges(grd_lines, element_predicate): + """Sort all .grd elements of a given type by their lang attribute.""" + return _SortElementsRanges(grd_lines, element_predicate, _GetXmlLangAttribute) + + +def _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales): + """Check the element 'lang' attributes in specific .grd lines range. + + This really checks the following: + - Each item has a correct 'lang' attribute. + - There are no duplicated lines for the same 'lang' attribute. + - That there are no extra locales that Chromium doesn't want. + - That no wanted locale is missing. + + Args: + grd_lines: Input .grd lines. + start: Sub-range start position in input line list. + end: Sub-range limit position in input line list. + wanted_locales: Set of wanted Chromium locale names. + Returns: + List of error message strings for this input. Empty on success. + """ + errors = [] + locales = set() + for pos in xrange(start, end): + line = grd_lines[pos] + lang = _GetXmlLangAttribute(line) + if not lang: + errors.append('%d: Missing "lang" attribute in element' % pos + + 1) + continue + cr_locale = _FixChromiumLangAttribute(lang) + if cr_locale in locales: + errors.append( + '%d: Redefinition of for "%s" locale' % (pos + 1, lang)) + locales.add(cr_locale) + + extra_locales = locales.difference(wanted_locales) + if extra_locales: + errors.append('%d-%d: Extra locales found: %s' % (start + 1, end + 1, + sorted(extra_locales))) + + missing_locales = wanted_locales.difference(locales) + if missing_locales: + errors.append('%d-%d: Missing locales: %s' % (start + 1, end + 1, + sorted(missing_locales))) + + return errors + + +########################################################################## +########################################################################## +##### +##### G R D A N D R O I D O U T P U T S +##### +########################################################################## +########################################################################## + +def _IsGrdAndroidOutputLine(line): + """Returns True iff this is an Android-specific line.""" + m = _RE_OUTPUT_ELEMENT.search(line) + if m: + return 'type="android"' in m.group(1) + return False + +assert _IsGrdAndroidOutputLine(' ') + +# Many of the functions below have unused arguments due to genericity. +# pylint: disable=unused-argument + +def _CheckGrdElementRangeAndroidOutputFilename(grd_lines, start, end, + wanted_locales): + """Check all elements in specific input .grd lines range. + + This really checks the following: + - Filenames exist for each listed locale. + - Filenames are well-formed. + + Args: + grd_lines: Input .grd lines. + start: Sub-range start position in input line list. + end: Sub-range limit position in input line list. + wanted_locales: Set of wanted Chromium locale names. + Returns: + List of error message strings for this input. Empty on success. + """ + errors = [] + for pos in xrange(start, end): + line = grd_lines[pos] + lang = _GetXmlLangAttribute(line) + if not lang: + continue + cr_locale = _FixChromiumLangAttribute(lang) + + m = _RE_FILENAME_ATTRIBUTE.search(line) + if not m: + errors.append('%d: Missing filename attribute in element' % pos + + 1) + else: + filename = m.group(1) + if not filename.endswith('.xml'): + errors.append( + '%d: Filename should end with ".xml": %s' % (pos + 1, filename)) + + dirname = os.path.basename(os.path.dirname(filename)) + prefix = ('values-%s' % resource_utils.ToAndroidLocaleName(cr_locale) + if cr_locale != _DEFAULT_LOCALE else 'values') + if dirname != prefix: + errors.append( + '%s: Directory name should be %s: %s' % (pos + 1, prefix, filename)) + + return errors + + +def _CheckGrdAndroidOutputElements(grd_file, grd_lines, wanted_locales): + """Check all elements related to Android. + + Args: + grd_file: Input .grd file path. + grd_lines: List of input .grd lines. + wanted_locales: set of wanted Chromium locale names. + Returns: + List of error message strings. Empty on success. + """ + intervals = _BuildIntervalList(grd_lines, _IsGrdAndroidOutputLine) + errors = [] + for start, end in intervals: + errors += _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales) + errors += _CheckGrdElementRangeAndroidOutputFilename(grd_lines, start, end, + wanted_locales) + return errors + + +def _AddMissingLocalesInGrdAndroidOutputs(grd_file, grd_lines, wanted_locales): + """Fix an input .grd line by adding missing Android outputs. + + Args: + grd_file: Input .grd file path. + grd_lines: Input .grd line list. + wanted_locales: set of Chromium locale names. + Returns: + A new list of .grd lines, containing new elements when needed + for locales from |wanted_locales| that were not part of the input. + """ + intervals = _BuildIntervalList(grd_lines, _IsGrdAndroidOutputLine) + for start, end in reversed(intervals): + locales = set() + for pos in xrange(start, end): + lang = _GetXmlLangAttribute(grd_lines[pos]) + locale = _FixChromiumLangAttribute(lang) + locales.add(locale) + + missing_locales = wanted_locales.difference(locales) + if not missing_locales: + continue + + src_locale = 'bg' + src_lang_attribute = 'lang="%s"' % src_locale + src_line = None + for pos in xrange(start, end): + if src_lang_attribute in grd_lines[pos]: + src_line = grd_lines[pos] + break + + if not src_line: + raise Exception( + 'Cannot find element with "%s" lang attribute' % src_locale) + + line_count = end - 1 + for locale in missing_locales: + android_locale = resource_utils.ToAndroidLocaleName(locale) + dst_line = src_line.replace( + 'lang="%s"' % src_locale, 'lang="%s"' % locale).replace( + 'values-%s/' % src_locale, 'values-%s/' % android_locale) + grd_lines.insert(line_count, dst_line) + line_count += 1 + + # Sort the new elements. + return _SortGrdElementsRanges(grd_lines, _IsGrdAndroidOutputLine) + + +########################################################################## +########################################################################## +##### +##### G R D T R A N S L A T I O N S +##### +########################################################################## +########################################################################## + + +def _IsTranslationGrdOutputLine(line): + """Returns True iff this is an output .xtb element.""" + m = _RE_TRANSLATION_ELEMENT.search(line) + return m is not None + + +class _IsTranslationGrdOutputLineTest(unittest.TestCase): + + def test_GrdTranslationOutputLines(self): + _VALID_INPUT_LINES = [ + '', + '', + '', + '', + ' ', + ] + _INVALID_INPUT_LINES = [''] + + for line in _VALID_INPUT_LINES: + self.assertTrue( + _IsTranslationGrdOutputLine(line), + '_IsTranslationGrdOutputLine() returned False for [%s]' % line) + + for line in _INVALID_INPUT_LINES: + self.assertFalse( + _IsTranslationGrdOutputLine(line), + '_IsTranslationGrdOutputLine() returned True for [%s]' % line) + + +def _CheckGrdTranslationElementRange(grd_lines, start, end, + wanted_locales): + """Check all sub-elements in specific input .grd lines range. + + This really checks the following: + - Each item has a 'path' attribute. + - Each such path value ends up with '.xtb'. + + Args: + grd_lines: Input .grd lines. + start: Sub-range start position in input line list. + end: Sub-range limit position in input line list. + wanted_locales: Set of wanted Chromium locale names. + Returns: + List of error message strings for this input. Empty on success. + """ + errors = [] + for pos in xrange(start, end): + line = grd_lines[pos] + lang = _GetXmlLangAttribute(line) + if not lang: + continue + m = _RE_PATH_ATTRIBUTE.search(line) + if not m: + errors.append('%d: Missing path attribute in element' % pos + + 1) + else: + filename = m.group(1) + if not filename.endswith('.xtb'): + errors.append( + '%d: Path should end with ".xtb": %s' % (pos + 1, filename)) + + return errors + + +def _CheckGrdTranslations(grd_file, grd_lines, wanted_locales): + """Check all elements that correspond to an .xtb output file. + + Args: + grd_file: Input .grd file path. + grd_lines: List of input .grd lines. + wanted_locales: set of wanted Chromium locale names. + Returns: + List of error message strings. Empty on success. + """ + wanted_locales = wanted_locales - set([_DEFAULT_LOCALE]) + intervals = _BuildIntervalList(grd_lines, _IsTranslationGrdOutputLine) + errors = [] + for start, end in intervals: + errors += _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales) + errors += _CheckGrdTranslationElementRange(grd_lines, start, end, + wanted_locales) + return errors + + +# Regular expression used to replace the lang attribute inside .xtb files. +_RE_TRANSLATIONBUNDLE = re.compile('') + + +def _CreateFakeXtbFileFrom(src_xtb_path, dst_xtb_path, dst_locale): + """Create a fake .xtb file. + + Args: + src_xtb_path: Path to source .xtb file to copy from. + dst_xtb_path: Path to destination .xtb file to write to. + dst_locale: Destination locale, the lang attribute in the source file + will be substituted with this value before its lines are written + to the destination file. + """ + with open(src_xtb_path) as f: + src_xtb_lines = f.readlines() + + def replace_xtb_lang_attribute(line): + m = _RE_TRANSLATIONBUNDLE.search(line) + if not m: + return line + return line[:m.start(1)] + dst_locale + line[m.end(1):] + + dst_xtb_lines = [replace_xtb_lang_attribute(line) for line in src_xtb_lines] + with build_utils.AtomicOutput(dst_xtb_path) as tmp: + tmp.writelines(dst_xtb_lines) + + +def _AddMissingLocalesInGrdTranslations(grd_file, grd_lines, wanted_locales): + """Fix an input .grd line by adding missing Android outputs. + + This also creates fake .xtb files from the one provided for 'en-GB'. + + Args: + grd_file: Input .grd file path. + grd_lines: Input .grd line list. + wanted_locales: set of Chromium locale names. + Returns: + A new list of .grd lines, containing new elements when needed + for locales from |wanted_locales| that were not part of the input. + """ + wanted_locales = wanted_locales - set([_DEFAULT_LOCALE]) + intervals = _BuildIntervalList(grd_lines, _IsTranslationGrdOutputLine) + for start, end in reversed(intervals): + locales = set() + for pos in xrange(start, end): + lang = _GetXmlLangAttribute(grd_lines[pos]) + locale = _FixChromiumLangAttribute(lang) + locales.add(locale) + + missing_locales = wanted_locales.difference(locales) + if not missing_locales: + continue + + src_locale = 'en-GB' + src_lang_attribute = 'lang="%s"' % src_locale + src_line = None + for pos in xrange(start, end): + if src_lang_attribute in grd_lines[pos]: + src_line = grd_lines[pos] + break + + if not src_line: + raise Exception( + 'Cannot find element with "%s" lang attribute' % src_locale) + + src_path = os.path.join( + os.path.dirname(grd_file), + _RE_PATH_ATTRIBUTE.search(src_line).group(1)) + + line_count = end - 1 + for locale in missing_locales: + dst_line = src_line.replace( + 'lang="%s"' % src_locale, 'lang="%s"' % locale).replace( + '_%s.xtb' % src_locale, '_%s.xtb' % locale) + grd_lines.insert(line_count, dst_line) + line_count += 1 + + dst_path = src_path.replace('_%s.xtb' % src_locale, '_%s.xtb' % locale) + _CreateFakeXtbFileFrom(src_path, dst_path, locale) + + + # Sort the new elements. + return _SortGrdElementsRanges(grd_lines, _IsTranslationGrdOutputLine) + + +########################################################################## +########################################################################## +##### +##### G N A N D R O I D O U T P U T S +##### +########################################################################## +########################################################################## + +_RE_GN_VALUES_LIST_LINE = re.compile( + r'^\s*".*values(\-([A-Za-z0-9-]+))?/.*\.xml",\s*$') + +def _IsBuildGnInputFile(input_file): + """Returns True iff this is a BUILD.gn file.""" + return os.path.basename(input_file) == 'BUILD.gn' + + +def _GetAndroidGnOutputLocale(line): + """Check a GN list, and return its Android locale if it is an output .xml""" + m = _RE_GN_VALUES_LIST_LINE.match(line) + if not m: + return None + + if m.group(1): # First group is optional and contains group 2. + return m.group(2) + + return resource_utils.ToAndroidLocaleName(_DEFAULT_LOCALE) + + +def _IsAndroidGnOutputLine(line): + """Returns True iff this is an Android-specific localized .xml output.""" + return _GetAndroidGnOutputLocale(line) != None + + +def _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end): + """Check that a range of GN lines corresponds to localized strings. + + Special case: Some BUILD.gn files list several non-localized .xml files + that should be ignored by this function, e.g. in + components/cronet/android/BUILD.gn, the following appears: + + inputs = [ + ... + "sample/res/layout/activity_main.xml", + "sample/res/layout/dialog_url.xml", + "sample/res/values/dimens.xml", + "sample/res/values/strings.xml", + ... + ] + + These are non-localized strings, and should be ignored. This function is + used to detect them quickly. + """ + for pos in xrange(start, end): + if not 'values/' in gn_lines[pos]: + return True + return False + + +def _CheckGnOutputsRange(gn_lines, start, end, wanted_locales): + if not _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end): + return [] + + errors = [] + locales = set() + for pos in xrange(start, end): + line = gn_lines[pos] + android_locale = _GetAndroidGnOutputLocale(line) + assert android_locale != None + cr_locale = resource_utils.ToChromiumLocaleName(android_locale) + if cr_locale in locales: + errors.append('%s: Redefinition of output for "%s" locale' % + (pos + 1, android_locale)) + locales.add(cr_locale) + + extra_locales = locales.difference(wanted_locales) + if extra_locales: + errors.append('%d-%d: Extra locales: %s' % (start + 1, end + 1, + sorted(extra_locales))) + + missing_locales = wanted_locales.difference(locales) + if missing_locales: + errors.append('%d-%d: Missing locales: %s' % (start + 1, end + 1, + sorted(missing_locales))) + + return errors + + +def _CheckGnAndroidOutputs(gn_file, gn_lines, wanted_locales): + intervals = _BuildIntervalList(gn_lines, _IsAndroidGnOutputLine) + errors = [] + for start, end in intervals: + errors += _CheckGnOutputsRange(gn_lines, start, end, wanted_locales) + return errors + + +def _AddMissingLocalesInGnAndroidOutputs(gn_file, gn_lines, wanted_locales): + intervals = _BuildIntervalList(gn_lines, _IsAndroidGnOutputLine) + # NOTE: Since this may insert new lines to each interval, process the + # list in reverse order to maintain valid (start,end) positions during + # the iteration. + for start, end in reversed(intervals): + if not _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end): + continue + + locales = set() + for pos in xrange(start, end): + lang = _GetAndroidGnOutputLocale(gn_lines[pos]) + locale = resource_utils.ToChromiumLocaleName(lang) + locales.add(locale) + + missing_locales = wanted_locales.difference(locales) + if not missing_locales: + continue + + src_locale = 'bg' + src_values = 'values-%s/' % resource_utils.ToAndroidLocaleName(src_locale) + src_line = None + for pos in xrange(start, end): + if src_values in gn_lines[pos]: + src_line = gn_lines[pos] + break + + if not src_line: + raise Exception( + 'Cannot find output list item with "%s" locale' % src_locale) + + line_count = end - 1 + for locale in missing_locales: + if locale == _DEFAULT_LOCALE: + dst_line = src_line.replace('values-%s/' % src_locale, 'values/') + else: + dst_line = src_line.replace( + 'values-%s/' % src_locale, + 'values-%s/' % resource_utils.ToAndroidLocaleName(locale)) + gn_lines.insert(line_count, dst_line) + line_count += 1 + + gn_lines = _SortListSubRange( + gn_lines, start, line_count, + lambda line: _RE_GN_VALUES_LIST_LINE.match(line).group(1)) + + return gn_lines + + +########################################################################## +########################################################################## +##### +##### T R A N S L A T I O N E X P E C T A T I O N S +##### +########################################################################## +########################################################################## + +_EXPECTATIONS_FILENAME = 'translation_expectations.pyl' + +# Technical note: the format of translation_expectations.pyl +# is a 'Python literal', which defines a python dictionary, so should +# be easy to parse. However, when modifying it, care should be taken +# to respect the line comments and the order of keys within the text +# file. + + +def _ReadPythonLiteralFile(pyl_path): + """Read a .pyl file into a Python data structure.""" + with open(pyl_path) as f: + pyl_content = f.read() + # Evaluate as a Python data structure, use an empty global + # and local dictionary. + return eval(pyl_content, dict(), dict()) + + +def _UpdateLocalesInExpectationLines(pyl_lines, + wanted_locales, + available_width=79): + """Update the locales list(s) found in an expectations file. + + Args: + pyl_lines: Iterable of input lines from the file. + wanted_locales: Set or list of new locale names. + available_width: Optional, number of character colums used + to word-wrap the new list items. + Returns: + New list of updated lines. + """ + locales_list = ['"%s"' % loc for loc in sorted(wanted_locales)] + result = [] + line_count = len(pyl_lines) + line_num = 0 + DICT_START = '"languages": [' + while line_num < line_count: + line = pyl_lines[line_num] + line_num += 1 + result.append(line) + # Look for start of "languages" dictionary. + pos = line.find(DICT_START) + if pos < 0: + continue + + start_margin = pos + start_line = line_num + # Skip over all lines from the list. + while (line_num < line_count and + not pyl_lines[line_num].rstrip().endswith('],')): + line_num += 1 + continue + + if line_num == line_count: + raise Exception('%d: Missing list termination!' % start_line) + + # Format the new list according to the new margin. + locale_width = available_width - (start_margin + 2) + locale_lines = _PrettyPrintListAsLines( + locales_list, locale_width, trailing_comma=True) + for locale_line in locale_lines: + result.append(' ' * (start_margin + 2) + locale_line) + result.append(' ' * start_margin + '],') + line_num += 1 + + return result + + +class _UpdateLocalesInExpectationLinesTest(unittest.TestCase): + + def test_simple(self): + self.maxDiff = 1000 + input_text = r''' +# This comment should be preserved +# 23456789012345678901234567890123456789 +{ + "android_grd": { + "languages": [ + "aa", "bb", "cc", "dd", "ee", + "ff", "gg", "hh", "ii", "jj", + "kk"], + }, + # Example with bad indentation in input. + "another_grd": { + "languages": [ + "aa", "bb", "cc", "dd", "ee", "ff", "gg", "hh", "ii", "jj", "kk", + ], + }, +} +''' + expected_text = r''' +# This comment should be preserved +# 23456789012345678901234567890123456789 +{ + "android_grd": { + "languages": [ + "A2", "AA", "BB", "CC", "DD", + "E2", "EE", "FF", "GG", "HH", + "I2", "II", "JJ", "KK", + ], + }, + # Example with bad indentation in input. + "another_grd": { + "languages": [ + "A2", "AA", "BB", "CC", "DD", + "E2", "EE", "FF", "GG", "HH", + "I2", "II", "JJ", "KK", + ], + }, +} +''' + input_lines = input_text.splitlines() + test_locales = ([ + 'AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG', 'HH', 'II', 'JJ', 'KK', 'A2', + 'E2', 'I2' + ]) + expected_lines = expected_text.splitlines() + self.assertListEqual( + _UpdateLocalesInExpectationLines(input_lines, test_locales, 40), + expected_lines) + + def test_missing_list_termination(self): + input_lines = r''' + "languages": [' + "aa", "bb", "cc", "dd" +'''.splitlines() + with self.assertRaises(Exception) as cm: + _UpdateLocalesInExpectationLines(input_lines, ['a', 'b'], 40) + + self.assertEqual(str(cm.exception), '2: Missing list termination!') + + +def _UpdateLocalesInExpectationFile(pyl_path, wanted_locales): + """Update all locales listed in a given expectations file. + + Args: + pyl_path: Path to .pyl file to update. + wanted_locales: List of locales that need to be written to + the file. + """ + tc_locales = { + _FixTranslationConsoleLocaleName(locale) + for locale in set(wanted_locales) - set([_DEFAULT_LOCALE]) + } + + with open(pyl_path) as f: + input_lines = [l.rstrip() for l in f.readlines()] + + updated_lines = _UpdateLocalesInExpectationLines(input_lines, tc_locales) + with build_utils.AtomicOutput(pyl_path) as f: + f.writelines('\n'.join(updated_lines) + '\n') + + +########################################################################## +########################################################################## +##### +##### C H E C K E V E R Y T H I N G +##### +########################################################################## +########################################################################## + +# pylint: enable=unused-argument + + +def _IsAllInputFile(input_file): + return _IsGritInputFile(input_file) or _IsBuildGnInputFile(input_file) + + +def _CheckAllFiles(input_file, input_lines, wanted_locales): + errors = [] + if _IsGritInputFile(input_file): + errors += _CheckGrdTranslations(input_file, input_lines, wanted_locales) + errors += _CheckGrdAndroidOutputElements( + input_file, input_lines, wanted_locales) + elif _IsBuildGnInputFile(input_file): + errors += _CheckGnAndroidOutputs(input_file, input_lines, wanted_locales) + return errors + + +def _AddMissingLocalesInAllFiles(input_file, input_lines, wanted_locales): + if _IsGritInputFile(input_file): + lines = _AddMissingLocalesInGrdTranslations( + input_file, input_lines, wanted_locales) + lines = _AddMissingLocalesInGrdAndroidOutputs( + input_file, lines, wanted_locales) + elif _IsBuildGnInputFile(input_file): + lines = _AddMissingLocalesInGnAndroidOutputs( + input_file, input_lines, wanted_locales) + return lines + + +########################################################################## +########################################################################## +##### +##### C O M M A N D H A N D L I N G +##### +########################################################################## +########################################################################## + +class _Command(object): + """A base class for all commands recognized by this script. + + Usage is the following: + 1) Derived classes must re-define the following class-based fields: + - name: Command name (e.g. 'list-locales') + - description: Command short description. + - long_description: Optional. Command long description. + NOTE: As a convenience, if the first character is a newline, + it will be omitted in the help output. + + 2) Derived classes for commands that take arguments should override + RegisterExtraArgs(), which receives a corresponding argparse + sub-parser as argument. + + 3) Derived classes should implement a Run() command, which can read + the current arguments from self.args. + """ + name = None + description = None + long_description = None + + def __init__(self): + self._parser = None + self.args = None + + def RegisterExtraArgs(self, subparser): + pass + + def RegisterArgs(self, parser): + subp = parser.add_parser( + self.name, help=self.description, + description=self.long_description or self.description, + formatter_class=argparse.RawDescriptionHelpFormatter) + self._parser = subp + subp.set_defaults(command=self) + group = subp.add_argument_group('%s arguments' % self.name) + self.RegisterExtraArgs(group) + + def ProcessArgs(self, args): + self.args = args + + +class _ListLocalesCommand(_Command): + """Implement the 'list-locales' command to list locale lists of interest.""" + name = 'list-locales' + description = 'List supported Chrome locales' + long_description = r''' +List locales of interest, by default this prints all locales supported by +Chrome, but `--type=ios_unsupported` can be used for the list of locales +unsupported on iOS. + +These values are extracted directly from build/config/locales.gni. + +Additionally, use the --as-json argument to print the list as a JSON list, +instead of the default format (which is a space-separated list of locale names). +''' + + # Maps type argument to a function returning the corresponding locales list. + TYPE_MAP = { + 'all': ChromeLocales, + 'ios_unsupported': IosUnsupportedLocales, + } + + def RegisterExtraArgs(self, group): + group.add_argument( + '--as-json', + action='store_true', + help='Output as JSON list.') + group.add_argument( + '--type', + choices=tuple(self.TYPE_MAP.viewkeys()), + default='all', + help='Select type of locale list to print.') + + def Run(self): + locale_list = self.TYPE_MAP[self.args.type]() + if self.args.as_json: + print('[%s]' % ", ".join("'%s'" % loc for loc in locale_list)) + else: + print(' '.join(locale_list)) + + +class _CheckInputFileBaseCommand(_Command): + """Used as a base for other _Command subclasses that check input files. + + Subclasses should also define the following class-level variables: + + - select_file_func: + A predicate that receives a file name (not path) and return True if it + should be selected for inspection. Used when scanning directories with + '--scan-dir '. + + - check_func: + - fix_func: + Two functions passed as parameters to _ProcessFile(), see relevant + documentation in this function's definition. + """ + select_file_func = None + check_func = None + fix_func = None + + def RegisterExtraArgs(self, group): + group.add_argument( + '--scan-dir', + action='append', + help='Optional directory to scan for input files recursively.') + group.add_argument( + 'input', + nargs='*', + help='Input file(s) to check.') + group.add_argument( + '--fix-inplace', + action='store_true', + help='Try to fix the files in-place too.') + group.add_argument( + '--add-locales', + help='Space-separated list of additional locales to use') + + def Run(self): + args = self.args + input_files = [] + if args.input: + input_files = args.input + if args.scan_dir: + input_files.extend(_ScanDirectoriesForFiles( + args.scan_dir, self.select_file_func.__func__)) + locales = ChromeLocales() + if args.add_locales: + locales.extend(args.add_locales.split(' ')) + + locales = set(locales) + + for input_file in input_files: + _ProcessFile(input_file, + locales, + self.check_func.__func__, + self.fix_func.__func__ if args.fix_inplace else None) + print('%sDone.' % (_CONSOLE_START_LINE)) + + +class _CheckGrdAndroidOutputsCommand(_CheckInputFileBaseCommand): + name = 'check-grd-android-outputs' + description = ( + 'Check the Android resource (.xml) files outputs in GRIT input files.') + long_description = r''' +Check the Android .xml files outputs in one or more input GRIT (.grd) files +for the following conditions: + + - Each item has a correct 'lang' attribute. + - There are no duplicated lines for the same 'lang' attribute. + - That there are no extra locales that Chromium doesn't want. + - That no wanted locale is missing. + - Filenames exist for each listed locale. + - Filenames are well-formed. +''' + select_file_func = _IsGritInputFile + check_func = _CheckGrdAndroidOutputElements + fix_func = _AddMissingLocalesInGrdAndroidOutputs + + +class _CheckGrdTranslationsCommand(_CheckInputFileBaseCommand): + name = 'check-grd-translations' + description = ( + 'Check the translation (.xtb) files outputted by .grd input files.') + long_description = r''' +Check the translation (.xtb) file outputs in one or more input GRIT (.grd) files +for the following conditions: + + - Each item has a correct 'lang' attribute. + - There are no duplicated lines for the same 'lang' attribute. + - That there are no extra locales that Chromium doesn't want. + - That no wanted locale is missing. + - Each item has a 'path' attribute. + - Each such path value ends up with '.xtb'. +''' + select_file_func = _IsGritInputFile + check_func = _CheckGrdTranslations + fix_func = _AddMissingLocalesInGrdTranslations + + +class _CheckGnAndroidOutputsCommand(_CheckInputFileBaseCommand): + name = 'check-gn-android-outputs' + description = 'Check the Android .xml file lists in GN build files.' + long_description = r''' +Check one or more BUILD.gn file, looking for lists of Android resource .xml +files, and checking that: + + - There are no duplicated output files in the list. + - Each output file belongs to a wanted Chromium locale. + - There are no output files for unwanted Chromium locales. +''' + select_file_func = _IsBuildGnInputFile + check_func = _CheckGnAndroidOutputs + fix_func = _AddMissingLocalesInGnAndroidOutputs + + +class _CheckAllCommand(_CheckInputFileBaseCommand): + name = 'check-all' + description = 'Check everything.' + long_description = 'Equivalent to calling all other check-xxx commands.' + select_file_func = _IsAllInputFile + check_func = _CheckAllFiles + fix_func = _AddMissingLocalesInAllFiles + + +class _UpdateExpectationsCommand(_Command): + name = 'update-expectations' + description = 'Update translation expectations file.' + long_description = r''' +Update %s files to match the current list of locales supported by Chromium. +This is especially useful to add new locales before updating any GRIT or GN +input file with the --add-locales option. +''' % _EXPECTATIONS_FILENAME + + def RegisterExtraArgs(self, group): + group.add_argument( + '--add-locales', + help='Space-separated list of additional locales to use.') + + def Run(self): + locales = ChromeLocales() + add_locales = self.args.add_locales + if add_locales: + locales.extend(add_locales.split(' ')) + + expectation_paths = [ + 'tools/gritsettings/translation_expectations.pyl', + 'clank/tools/translation_expectations.pyl', + ] + missing_expectation_files = [] + for path in enumerate(expectation_paths): + file_path = os.path.join(_TOP_SRC_DIR, path) + if not os.path.exists(file_path): + missing_expectation_files.append(file_path) + continue + _UpdateLocalesInExpectationFile(file_path, locales) + + if missing_expectation_files: + sys.stderr.write('WARNING: Missing file(s): %s\n' % + (', '.join(missing_expectation_files))) + + +class _UnitTestsCommand(_Command): + name = 'unit-tests' + description = 'Run internal unit-tests for this script' + + def RegisterExtraArgs(self, group): + group.add_argument( + '-v', '--verbose', action='count', help='Increase test verbosity.') + group.add_argument('args', nargs=argparse.REMAINDER) + + def Run(self): + argv = [_SCRIPT_NAME] + self.args.args + unittest.main(argv=argv, verbosity=self.args.verbose) + + +# List of all commands supported by this script. +_COMMANDS = [ + _ListLocalesCommand, + _CheckGrdAndroidOutputsCommand, + _CheckGrdTranslationsCommand, + _CheckGnAndroidOutputsCommand, + _CheckAllCommand, + _UpdateExpectationsCommand, + _UnitTestsCommand, +] + + +def main(argv): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + + subparsers = parser.add_subparsers() + commands = [clazz() for clazz in _COMMANDS] + for command in commands: + command.RegisterArgs(subparsers) + + if not argv: + argv = ['--help'] + + args = parser.parse_args(argv) + args.command.ProcessArgs(args) + args.command.Run() + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/third_party/libwebrtc/build/mac/OWNERS b/third_party/libwebrtc/build/mac/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/mac/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/mac/find_sdk.py b/third_party/libwebrtc/build/mac/find_sdk.py new file mode 100755 index 0000000000..d86f310935 --- /dev/null +++ b/third_party/libwebrtc/build/mac/find_sdk.py @@ -0,0 +1,99 @@ +#!/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. +r"""Prints the lowest locally available SDK version greater than or equal to a +given minimum sdk version to standard output. + +If --print_sdk_path is passed, then the script will also print the SDK path. +If --print_bin_path is passed, then the script will also print the path to the +toolchain bin dir. + +Usage: + python find_sdk.py \ + [--print_sdk_path] \ + [--print_bin_path] \ + 10.6 # Ignores SDKs < 10.6 + +Sample Output: +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ +10.14 +""" + +from __future__ import print_function + +import os +import re +import subprocess +import sys + +from optparse import OptionParser + + +class SdkError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +def parse_version(version_str): + """'10.6' => [10, 6]""" + return [int(s) for s in re.findall(r'(\d+)', version_str)] + + +def main(): + parser = OptionParser() + parser.add_option("--print_sdk_path", + action="store_true", dest="print_sdk_path", default=False, + help="Additionally print the path the SDK (appears first).") + parser.add_option("--print_bin_path", + action="store_true", dest="print_bin_path", default=False, + help="Additionally print the path the toolchain bin dir.") + options, args = parser.parse_args() + if len(args) != 1: + parser.error('Please specify a minimum SDK version') + 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(out, file=sys.stderr) + print(err, file=sys.stderr) + raise Exception('Error %d running xcode-select' % job.returncode) + dev_dir = out.decode('UTF-8').rstrip() + sdk_dir = os.path.join( + dev_dir, 'Platforms/MacOSX.platform/Developer/SDKs') + + if not os.path.isdir(sdk_dir): + raise SdkError('Install Xcode, launch it, accept the license ' + + 'agreement, and run `sudo xcode-select -s /path/to/Xcode.app` ' + + 'to continue.') + sdks = [re.findall('^MacOSX(\d+\.\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.print_sdk_path: + sdk_name = 'MacOSX' + best_sdk + '.sdk' + print(os.path.join(sdk_dir, sdk_name)) + + if options.print_bin_path: + bin_path = 'Toolchains/XcodeDefault.xctoolchain/usr/bin/' + print(os.path.join(dev_dir, bin_path)) + + return best_sdk + + +if __name__ == '__main__': + if sys.platform != 'darwin': + raise Exception("This script only runs on Mac") + print(main()) + sys.exit(0) diff --git a/third_party/libwebrtc/build/mac/should_use_hermetic_xcode.py b/third_party/libwebrtc/build/mac/should_use_hermetic_xcode.py new file mode 100755 index 0000000000..63d44af85c --- /dev/null +++ b/third_party/libwebrtc/build/mac/should_use_hermetic_xcode.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# 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. + +""" +Prints "1" if Chrome targets should be built with hermetic Xcode. +Prints "2" if Chrome targets should be built with hermetic Xcode, but the OS +version does not meet the minimum requirements of the hermetic version of Xcode. +Prints "3" if FORCE_MAC_TOOLCHAIN is set for an iOS target_os +Otherwise prints "0". + +Usage: + python should_use_hermetic_xcode.py +""" + +from __future__ import print_function + +import argparse +import os +import sys + +_THIS_DIR_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +_BUILD_PATH = os.path.join(_THIS_DIR_PATH, os.pardir) +sys.path.insert(0, _BUILD_PATH) + +import mac_toolchain + + +def _IsCorpMachine(): + if sys.platform == 'darwin': + return os.path.isdir('/Library/GoogleCorpSupport/') + if sys.platform.startswith('linux'): + import subprocess + try: + return subprocess.check_output(['lsb_release', + '-sc']).rstrip() == b'rodete' + except: + return False + return False + + +def main(): + parser = argparse.ArgumentParser(description='Download hermetic Xcode.') + parser.add_argument('platform') + args = parser.parse_args() + + force_toolchain = os.environ.get('FORCE_MAC_TOOLCHAIN') + if force_toolchain and args.platform == 'ios': + return "3" + allow_corp = args.platform == 'mac' and _IsCorpMachine() + if force_toolchain or allow_corp: + if not mac_toolchain.PlatformMeetsHermeticXcodeRequirements(): + return "2" + return "1" + else: + return "0" + + +if __name__ == '__main__': + print(main()) + sys.exit(0) diff --git a/third_party/libwebrtc/build/mac_toolchain.py b/third_party/libwebrtc/build/mac_toolchain.py new file mode 100755 index 0000000000..2dfdb43eee --- /dev/null +++ b/third_party/libwebrtc/build/mac_toolchain.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +# Copyright 2018 The Chromium 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 should_use_hermetic_xcode.py emits "1", and the current toolchain is out of +date: + * Downloads the hermetic mac toolchain + * Requires CIPD authentication. Run `cipd auth-login`, use Google account. + * Accepts the license. + * If xcode-select and xcodebuild are not passwordless in sudoers, requires + user interaction. + * Downloads standalone binaries from [a possibly different version of Xcode]. + +The toolchain version can be overridden by setting MAC_TOOLCHAIN_REVISION with +the full revision, e.g. 9A235. +""" + +from __future__ import print_function + +import argparse +import os +import pkg_resources +import platform +import plistlib +import shutil +import subprocess +import sys + + +def LoadPList(path): + """Loads Plist at |path| and returns it as a dictionary.""" + if sys.version_info.major == 2: + return plistlib.readPlist(path) + with open(path, 'rb') as f: + return plistlib.load(f) + + +# This contains binaries from Xcode 12.5 12E262, along with the macOS 11 SDK. +# To build these packages, see comments in build/xcode_binaries.yaml +MAC_BINARIES_LABEL = 'infra_internal/ios/xcode/xcode_binaries/mac-amd64' +MAC_BINARIES_TAG = 'pBipKbKSkYGXpuOBm4-8zuvfIGeFtpGbQ4IHM9YW0xMC' + +# The toolchain will not be downloaded if the minimum OS version is not met. 19 +# is the major version number for macOS 10.15. 12B5044c (Xcode 12.2rc) only runs +# on 10.15.4 and newer. +MAC_MINIMUM_OS_VERSION = [19, 4] + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +TOOLCHAIN_ROOT = os.path.join(BASE_DIR, 'mac_files') +TOOLCHAIN_BUILD_DIR = os.path.join(TOOLCHAIN_ROOT, 'Xcode.app') + +# Always integrity-check the entire SDK. Mac SDK packages are complex and often +# hit edge cases in cipd (eg https://crbug.com/1033987, +# https://crbug.com/915278), and generally when this happens it requires manual +# intervention to fix. +# Note the trailing \n! +PARANOID_MODE = '$ParanoidMode CheckIntegrity\n' + + +def PlatformMeetsHermeticXcodeRequirements(): + if sys.platform != 'darwin': + return True + needed = MAC_MINIMUM_OS_VERSION + major_version = [int(v) for v in platform.release().split('.')[:len(needed)]] + return major_version >= needed + + +def _UseHermeticToolchain(): + current_dir = os.path.dirname(os.path.realpath(__file__)) + script_path = os.path.join(current_dir, 'mac/should_use_hermetic_xcode.py') + proc = subprocess.Popen([script_path, 'mac'], stdout=subprocess.PIPE) + return '1' in proc.stdout.readline().decode() + + +def RequestCipdAuthentication(): + """Requests that the user authenticate to access Xcode CIPD packages.""" + + print('Access to Xcode CIPD package requires authentication.') + print('-----------------------------------------------------------------') + print() + print('You appear to be a Googler.') + print() + print('I\'m sorry for the hassle, but you may need to do a one-time manual') + print('authentication. Please run:') + print() + print(' cipd auth-login') + print() + print('and follow the instructions.') + print() + print('NOTE: Use your google.com credentials, not chromium.org.') + print() + print('-----------------------------------------------------------------') + print() + sys.stdout.flush() + + +def PrintError(message): + # Flush buffers to ensure correct output ordering. + sys.stdout.flush() + sys.stderr.write(message + '\n') + sys.stderr.flush() + + +def InstallXcodeBinaries(): + """Installs the Xcode binaries needed to build Chrome and accepts the license. + + This is the replacement for InstallXcode that installs a trimmed down version + of Xcode that is OS-version agnostic. + """ + # First make sure the directory exists. It will serve as the cipd root. This + # also ensures that there will be no conflicts of cipd root. + binaries_root = os.path.join(TOOLCHAIN_ROOT, 'xcode_binaries') + if not os.path.exists(binaries_root): + os.makedirs(binaries_root) + + # 'cipd ensure' is idempotent. + args = ['cipd', 'ensure', '-root', binaries_root, '-ensure-file', '-'] + + p = subprocess.Popen(args, + universal_newlines=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate(input=PARANOID_MODE + MAC_BINARIES_LABEL + + ' ' + MAC_BINARIES_TAG) + if p.returncode != 0: + print(stdout) + print(stderr) + RequestCipdAuthentication() + return 1 + + if sys.platform != 'darwin': + return 0 + + # Accept the license for this version of Xcode if it's newer than the + # currently accepted version. + cipd_xcode_version_plist_path = os.path.join(binaries_root, + 'Contents/version.plist') + cipd_xcode_version_plist = LoadPList(cipd_xcode_version_plist_path) + cipd_xcode_version = cipd_xcode_version_plist['CFBundleShortVersionString'] + + cipd_license_path = os.path.join(binaries_root, + 'Contents/Resources/LicenseInfo.plist') + cipd_license_plist = LoadPList(cipd_license_path) + cipd_license_version = cipd_license_plist['licenseID'] + + should_overwrite_license = True + current_license_path = '/Library/Preferences/com.apple.dt.Xcode.plist' + if os.path.exists(current_license_path): + current_license_plist = LoadPList(current_license_path) + xcode_version = current_license_plist.get( + 'IDEXcodeVersionForAgreedToGMLicense') + if (xcode_version is not None and pkg_resources.parse_version(xcode_version) + >= pkg_resources.parse_version(cipd_xcode_version)): + should_overwrite_license = False + + if not should_overwrite_license: + return 0 + + # Use puppet's sudoers script to accept the license if its available. + license_accept_script = '/usr/local/bin/xcode_accept_license.py' + if os.path.exists(license_accept_script): + args = [ + 'sudo', license_accept_script, '--xcode-version', cipd_xcode_version, + '--license-version', cipd_license_version + ] + subprocess.check_call(args) + return 0 + + # Otherwise manually accept the license. This will prompt for sudo. + print('Accepting new Xcode license. Requires sudo.') + sys.stdout.flush() + args = [ + 'sudo', 'defaults', 'write', current_license_path, + 'IDEXcodeVersionForAgreedToGMLicense', cipd_xcode_version + ] + subprocess.check_call(args) + args = [ + 'sudo', 'defaults', 'write', current_license_path, + 'IDELastGMLicenseAgreedTo', cipd_license_version + ] + subprocess.check_call(args) + args = ['sudo', 'plutil', '-convert', 'xml1', current_license_path] + subprocess.check_call(args) + + return 0 + + +def main(): + if not _UseHermeticToolchain(): + print('Skipping Mac toolchain installation for mac') + return 0 + + parser = argparse.ArgumentParser(description='Download hermetic Xcode.') + args = parser.parse_args() + + if not PlatformMeetsHermeticXcodeRequirements(): + print('OS version does not support toolchain.') + return 0 + + return InstallXcodeBinaries() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/nocompile.gni b/third_party/libwebrtc/build/nocompile.gni new file mode 100644 index 0000000000..5b4aeb8f4a --- /dev/null +++ b/third_party/libwebrtc/build/nocompile.gni @@ -0,0 +1,137 @@ +# 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: +# +# import("//build/nocompile.gni") +# nocompile_test("my_module_nc_unittests") { +# sources = [ +# 'nc_testset_1.nc', +# 'nc_testset_2.nc', +# ] +# +# # optional extra include dirs: +# include_dirs = [ ... ] +# } +# +# 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. + +import("//build/config/clang/clang.gni") +import("//build/config/python.gni") +import("//build/toolchain/toolchain.gni") +import("//testing/test.gni") + +declare_args() { + # TODO(crbug.com/105388): make sure no-compile test is not flaky. + enable_nocompile_tests = (is_linux || is_chromeos || is_apple) && is_clang && + host_cpu == target_cpu +} + +if (enable_nocompile_tests) { + import("//build/config/c++/c++.gni") + import("//build/config/sysroot.gni") + template("nocompile_test") { + nocompile_target = target_name + "_run_nocompile" + + action_foreach(nocompile_target) { + testonly = true + script = "//tools/nocompile_driver.py" + sources = invoker.sources + deps = invoker.deps + if (defined(invoker.public_deps)) { + public_deps = invoker.public_deps + } + + result_path = "$target_gen_dir/{{source_name_part}}_nc.cc" + depfile = "${result_path}.d" + outputs = [ result_path ] + args = [ + rebase_path("$clang_base_path/bin/clang++", root_build_dir), + "4", # number of compilers to invoke in parallel. + "{{source}}", + rebase_path(result_path, root_build_dir), + "--", + "-nostdinc++", + "-isystem" + rebase_path("$libcxx_prefix/include", root_build_dir), + "-isystem" + rebase_path("$libcxxabi_prefix/include", root_build_dir), + "-std=c++14", + "-Wall", + "-Werror", + "-Wfatal-errors", + "-Wthread-safety", + "-I" + rebase_path("//", root_build_dir), + "-I" + rebase_path("//third_party/abseil-cpp/", root_build_dir), + "-I" + rebase_path("//buildtools/third_party/libc++/", root_build_dir), + "-I" + rebase_path(root_gen_dir, root_build_dir), + + # TODO(https://crbug.com/989932): Track build/config/compiler/BUILD.gn + "-Wno-implicit-int-float-conversion", + ] + + if (is_apple && host_os != "mac") { + args += [ "--target=x86_64-apple-macos" ] + } + + # Iterate over any extra include dirs and append them to the command line. + if (defined(invoker.include_dirs)) { + foreach(include_dir, invoker.include_dirs) { + args += [ "-I" + rebase_path(include_dir, root_build_dir) ] + } + } + + if (sysroot != "") { + args += [ + "--sysroot", + rebase_path(sysroot, root_build_dir), + ] + } + } + + test(target_name) { + deps = invoker.deps + [ ":$nocompile_target" ] + sources = get_target_outputs(":$nocompile_target") + } + } +} diff --git a/third_party/libwebrtc/build/noop.py b/third_party/libwebrtc/build/noop.py new file mode 100644 index 0000000000..cf4d3ae305 --- /dev/null +++ b/third_party/libwebrtc/build/noop.py @@ -0,0 +1,4 @@ +# Copyright 2020 The Chromium 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 that does nothing successfully.""" diff --git a/third_party/libwebrtc/build/partitioned_shared_library.gni b/third_party/libwebrtc/build/partitioned_shared_library.gni new file mode 100644 index 0000000000..2ea32ce728 --- /dev/null +++ b/third_party/libwebrtc/build/partitioned_shared_library.gni @@ -0,0 +1,143 @@ +# Copyright 2019 The Chromium 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/clang/clang.gni") +import("//build/config/compiler/compiler.gni") + +# This template creates a set of shared libraries, by linking a single +# "partitioned" shared library, then splitting it into multiple pieces. +# The intention is to facilitate code-splitting between a base library and +# additional feature-specific libraries that may be obtained and loaded at a +# later time. +# +# The combined library is an intermediate product made by leveraging the LLVM +# toolchain. Code modules may be labeled via compiler flag as belonging to a +# particular partition. At link time, any symbols reachable by only a single +# partition's entrypoints will be located in a partition-specific library +# segment. After linking, the segments are split apart using objcopy into +# separate libraries. The main library is then packaged with the application +# as usual, while feature libraries may be packaged, delivered and loaded +# separately (via an Android Dynamic Feature Module). +# +# When loading a feature library, the intended address of the library must be +# supplied to the loader, so that it can be mapped to the memory location. The +# address offsets of the feature libraries are stored in the base library and +# accessed through special symbols named according to the partitions. +# +# The template instantiates targets for the base library, as well as each +# specified partition, based on the root target name. Example: +# +# - libmonochrome (base library) +# - libmonochrome_foo (partition library for feature 'foo') +# - libmonochrome_bar (partition library for feature 'bar') +# +# Note that the feature library filenames are chosen based on the main +# library's name (eg. libmonochrome_foo.so), but the soname of the feature +# library is based on the feature name (eg. "foo"). This should generally be +# okay, with the caveat that loading the library multiple times *might* cause +# problems in Android. +# +# This template uses shared_library's default configurations. +# +# Variables: +# partitions: A list of library partition names to extract, in addition to +# the base library. + +template("partitioned_shared_library") { + assert(is_clang) + forward_variables_from(invoker, [ "testonly" ]) + + _combined_library_target = "${target_name}__combined" + + # Strip "lib" from target names; it will be re-added to output libraries. + _output_name = string_replace(target_name, "lib", "") + + shared_library(_combined_library_target) { + forward_variables_from(invoker, "*", [ "partitions" ]) + if (!defined(ldflags)) { + ldflags = [] + } + ldflags += [ + "-Wl,-soname,lib${_output_name}.so", + "--link-only", + ] + + # This shared library is an intermediate artifact that should not packaged + # into the final build. Therefore, reset metadata. + metadata = { + } + } + + template("partition_action") { + action(target_name) { + deps = [ ":$_combined_library_target" ] + script = "//build/extract_partition.py" + sources = + [ "$root_out_dir/lib.unstripped/lib${_output_name}__combined.so" ] + outputs = [ + invoker.unstripped_output, + invoker.stripped_output, + ] + data = [ invoker.unstripped_output ] + metadata = { + shared_libraries = [ invoker.stripped_output ] + } + args = [ + "--objcopy", + rebase_path("$clang_base_path/bin/llvm-objcopy", root_build_dir), + "--unstripped-output", + rebase_path(invoker.unstripped_output, root_build_dir), + "--stripped-output", + rebase_path(invoker.stripped_output, root_build_dir), + ] + if (defined(invoker.partition) && invoker.partition != "") { + args += [ + "--partition", + "${invoker.partition}", + ] + } + + if (use_debug_fission) { + _dwp = rebase_path("${android_tool_prefix}dwp", root_build_dir) + args += [ "--dwp=${_dwp}" ] + outputs += [ invoker.unstripped_output + ".dwp" ] + } + args += [ rebase_path(sources[0], root_build_dir) ] + } + } + + partition_action(target_name) { + stripped_output = "$root_out_dir/lib${_output_name}.so" + unstripped_output = "$root_out_dir/lib.unstripped/lib${_output_name}.so" + } + + # Note that as of now, non-base partition libraries are placed in a + # subdirectory of the root output directory. This is because partition + # sonames are not sensitive to the filename of the base library, and as such, + # their corresponding file names may be generated multiple times by different + # base libraries. To avoid collisions, each base library target has a + # corresponding subdir for its extra partitions. + # + # If this proves problematic to various pieces of infrastructure, a proposed + # alternative is allowing the linker to rename partitions. For example, + # feature "foo" may be a partition. If two different base libraries both + # define "foo" partitions, the linker may be made to accept an extra command + # to rename the partition's soname to "foo1" or "foo2". Other build config + # can name the libraries foo1.so and foo2.so, allowing them to reside in the + # same directory. + foreach(_partition, invoker.partitions) { + partition_action("${target_name}_${_partition}") { + partition = "${_partition}_partition" + stripped_output = "$root_out_dir/lib${_output_name}_${partition}.so" + unstripped_output = + "$root_out_dir/lib.unstripped/lib${_output_name}_${partition}.so" + } + } +} + +set_defaults("partitioned_shared_library") { + configs = default_shared_library_configs +} diff --git a/third_party/libwebrtc/build/precompile.cc b/third_party/libwebrtc/build/precompile.cc new file mode 100644 index 0000000000..db1ef6dfe5 --- /dev/null +++ b/third_party/libwebrtc/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/third_party/libwebrtc/build/precompile.h b/third_party/libwebrtc/build/precompile.h new file mode 100644 index 0000000000..c699562c0c --- /dev/null +++ b/third_party/libwebrtc/build/precompile.h @@ -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 used as a precompiled header for both C and C++ files. So +// any C++ headers must go in the __cplusplus block below. + +#if defined(BUILD_PRECOMPILE_H_) +#error You shouldn't include the precompiled header file more than once. +#endif + +#define BUILD_PRECOMPILE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cplusplus) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // __cplusplus diff --git a/third_party/libwebrtc/build/print_python_deps.py b/third_party/libwebrtc/build/print_python_deps.py new file mode 100755 index 0000000000..e56783477a --- /dev/null +++ b/third_party/libwebrtc/build/print_python_deps.py @@ -0,0 +1,223 @@ +#!/usr/bin/python2.7 +# 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. + +"""Prints all non-system dependencies for the given module. + +The primary use-case for this script is to generate the list of python modules +required for .isolate files. + +This script should be compatible with Python 2 and Python 3. +""" + +import argparse +import fnmatch +import os +import pipes +import sys + +# Don't use any helper modules, or else they will end up in the results. + + +_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) + + +def ComputePythonDependencies(): + """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.values() + if m and hasattr(m, '__file__') and m.__file__) + + src_paths = set() + for path in module_paths: + if path == __file__: + continue + path = os.path.abspath(path) + if not path.startswith(_SRC_ROOT): + continue + + if (path.endswith('.pyc') + or (path.endswith('c') and not os.path.splitext(path)[1])): + path = path[:-1] + src_paths.add(path) + + return src_paths + + +def _NormalizeCommandLine(options): + """Returns a string that when run from SRC_ROOT replicates the command.""" + args = ['build/print_python_deps.py'] + root = os.path.relpath(options.root, _SRC_ROOT) + if root != '.': + args.extend(('--root', root)) + if options.output: + args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT))) + if options.gn_paths: + args.extend(('--gn-paths',)) + for allowlist in sorted(options.allowlists): + args.extend(('--allowlist', os.path.relpath(allowlist, _SRC_ROOT))) + args.append(os.path.relpath(options.module, _SRC_ROOT)) + return ' '.join(pipes.quote(x) for x in args) + + +def _FindPythonInDirectory(directory, allow_test): + """Returns an iterable of all non-test python files in the given directory.""" + files = [] + for root, _dirnames, filenames in os.walk(directory): + for filename in filenames: + if filename.endswith('.py') and (allow_test + or not filename.endswith('_test.py')): + yield os.path.join(root, filename) + + +def _GetTargetPythonVersion(module): + """Heuristically determines the target module's Python version.""" + with open(module) as f: + shebang = f.readline().strip() + default_version = 2 + if shebang.startswith('#!'): + # Examples: + # '#!/usr/bin/python' + # '#!/usr/bin/python2.7' + # '#!/usr/bin/python3' + # '#!/usr/bin/env python3' + # '#!/usr/bin/env vpython' + # '#!/usr/bin/env vpython3' + exec_name = os.path.basename(shebang[2:].split(' ')[-1]) + for python_prefix in ['python', 'vpython']: + if exec_name.startswith(python_prefix): + version_string = exec_name[len(python_prefix):] + break + else: + raise ValueError('Invalid shebang: ' + shebang) + if version_string: + return int(float(version_string)) + return default_version + + +def _ImportModuleByPath(module_path): + """Imports a module by its source file.""" + # Replace the path entry for print_python_deps.py with the one for the given + # module. + sys.path[0] = os.path.dirname(module_path) + if sys.version_info[0] == 2: + import imp # Python 2 only, since it's deprecated in Python 3. + imp.load_source('NAME', module_path) + else: + # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly + module_name = os.path.splitext(os.path.basename(module_path))[0] + import importlib.util # Python 3 only, since it's unavailable in Python 2. + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + + +def main(): + parser = argparse.ArgumentParser( + description='Prints all non-system dependencies for the given module.') + parser.add_argument('module', + help='The python module to analyze.') + parser.add_argument('--root', default='.', + help='Directory to make paths relative to.') + parser.add_argument('--output', + help='Write output to a file rather than stdout.') + parser.add_argument('--inplace', action='store_true', + help='Write output to a file with the same path as the ' + 'module, but with a .pydeps extension. Also sets the ' + 'root to the module\'s directory.') + parser.add_argument('--no-header', action='store_true', + help='Do not write the "# Generated by" header.') + parser.add_argument('--gn-paths', action='store_true', + help='Write paths as //foo/bar/baz.py') + parser.add_argument('--did-relaunch', action='store_true', + help=argparse.SUPPRESS) + parser.add_argument('--allowlist', + default=[], + action='append', + dest='allowlists', + help='Recursively include all non-test python files ' + 'within this directory. May be specified multiple times.') + options = parser.parse_args() + + if options.inplace: + if options.output: + parser.error('Cannot use --inplace and --output at the same time!') + if not options.module.endswith('.py'): + parser.error('Input module path should end with .py suffix!') + options.output = options.module + 'deps' + options.root = os.path.dirname(options.module) + + modules = [options.module] + if os.path.isdir(options.module): + modules = list(_FindPythonInDirectory(options.module, allow_test=True)) + if not modules: + parser.error('Input directory does not contain any python files!') + + target_versions = [_GetTargetPythonVersion(m) for m in modules] + target_version = target_versions[0] + assert target_version in [2, 3] + assert all(v == target_version for v in target_versions) + + current_version = sys.version_info[0] + + # Trybots run with vpython as default Python, but with a different config + # from //.vpython. To make the is_vpython test work, and to match the behavior + # of dev machines, the shebang line must be run with python2.7. + # + # E.g. $HOME/.vpython-root/dd50d3/bin/python + # E.g. /b/s/w/ir/cache/vpython/ab5c79/bin/python + is_vpython = 'vpython' in sys.executable + if not is_vpython or target_version != current_version: + # Prevent infinite relaunch if something goes awry. + assert not options.did_relaunch + # Re-launch using vpython will cause us to pick up modules specified in + # //.vpython, but does not cause it to pick up modules defined inline via + # [VPYTHON:BEGIN] ... [VPYTHON:END] comments. + # TODO(agrieve): Add support for this if the need ever arises. + vpython_to_use = {2: 'vpython', 3: 'vpython3'}[target_version] + os.execvp(vpython_to_use, [vpython_to_use] + sys.argv + ['--did-relaunch']) + + if current_version == 3: + # Work-around for protobuf library not being loadable via importlib + # This is needed due to compile_resources.py. + import importlib._bootstrap_external + importlib._bootstrap_external._NamespacePath.sort = lambda self, **_: 0 + + paths_set = set() + try: + for module in modules: + _ImportModuleByPath(module) + paths_set.update(ComputePythonDependencies()) + except Exception: + # Output extra diagnostics when loading the script fails. + sys.stderr.write('Error running print_python_deps.py.\n') + sys.stderr.write('is_vpython={}\n'.format(is_vpython)) + sys.stderr.write('did_relanuch={}\n'.format(options.did_relaunch)) + sys.stderr.write('python={}\n'.format(sys.executable)) + raise + + for path in options.allowlists: + paths_set.update( + os.path.abspath(p) + for p in _FindPythonInDirectory(path, allow_test=False)) + + paths = [os.path.relpath(p, options.root) for p in paths_set] + + normalized_cmdline = _NormalizeCommandLine(options) + out = open(options.output, 'w') if options.output else sys.stdout + with out: + if not options.no_header: + out.write('# Generated by running:\n') + out.write('# %s\n' % normalized_cmdline) + prefix = '//' if options.gn_paths else '' + for path in sorted(paths): + out.write(prefix + path + '\n') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/protoc_java.py b/third_party/libwebrtc/build/protoc_java.py new file mode 100755 index 0000000000..fe602a9fc9 --- /dev/null +++ b/third_party/libwebrtc/build/protoc_java.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. + +"""Generate java source files from protobuf files. + +This is the action script for the proto_java_library template. + +It performs the following steps: +1. Deletes all old sources (ensures deleted classes are not part of new jars). +2. Creates source directory. +3. Generates Java files using protoc (output into either --java-out-dir or + --srcjar). +4. Creates a new stamp file. +""" + +from __future__ import print_function + +import argparse +import os +import shutil +import subprocess +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), 'android', 'gyp')) +from util import build_utils + + +def _HasJavaPackage(proto_lines): + return any(line.strip().startswith('option java_package') + for line in proto_lines) + + +def _EnforceJavaPackage(proto_srcs): + for proto_path in proto_srcs: + with open(proto_path) as in_proto: + if not _HasJavaPackage(in_proto.readlines()): + raise Exception('Proto files for java must contain a "java_package" ' + 'line: {}'.format(proto_path)) + + +def main(argv): + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--protoc', required=True, help='Path to protoc binary.') + parser.add_argument('--proto-path', + required=True, + help='Path to proto directory.') + parser.add_argument('--java-out-dir', + help='Path to output directory for java files.') + parser.add_argument('--srcjar', help='Path to output srcjar.') + parser.add_argument('--stamp', help='File to touch on success.') + parser.add_argument( + '--import-dir', + action='append', + default=[], + help='Extra import directory for protos, can be repeated.') + parser.add_argument('protos', nargs='+', help='proto source files') + options = parser.parse_args(argv) + + if not options.java_out_dir and not options.srcjar: + raise Exception('One of --java-out-dir or --srcjar must be specified.') + + _EnforceJavaPackage(options.protos) + + with build_utils.TempDir() as temp_dir: + out_arg = '--java_out=lite:' + temp_dir + + proto_path_args = ['--proto_path', options.proto_path] + for path in options.import_dir: + proto_path_args += ["--proto_path", path] + + # Generate Java files using protoc. + build_utils.CheckOutput( + [options.protoc] + proto_path_args + [out_arg] + options.protos, + # protoc generates superfluous warnings about LITE_RUNTIME deprecation + # even though we are using the new non-deprecated method. + stderr_filter=lambda output: build_utils.FilterLines( + output, '|'.join([r'optimize_for = LITE_RUNTIME', r'java/lite\.md']) + )) + + if options.java_out_dir: + build_utils.DeleteDirectory(options.java_out_dir) + shutil.copytree(temp_dir, options.java_out_dir) + else: + build_utils.ZipDir(options.srcjar, temp_dir) + + if options.depfile: + assert options.srcjar + deps = options.protos + [options.protoc] + build_utils.WriteDepfile(options.depfile, options.srcjar, deps) + + if options.stamp: + build_utils.Touch(options.stamp) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/protoc_java.pydeps b/third_party/libwebrtc/build/protoc_java.pydeps new file mode 100644 index 0000000000..c3ed2be81a --- /dev/null +++ b/third_party/libwebrtc/build/protoc_java.pydeps @@ -0,0 +1,6 @@ +# Generated by running: +# build/print_python_deps.py --root build --output build/protoc_java.pydeps build/protoc_java.py +android/gyp/util/__init__.py +android/gyp/util/build_utils.py +gn_helpers.py +protoc_java.py diff --git a/third_party/libwebrtc/build/redirect_stdout.py b/third_party/libwebrtc/build/redirect_stdout.py new file mode 100644 index 0000000000..166293cb38 --- /dev/null +++ b/third_party/libwebrtc/build/redirect_stdout.py @@ -0,0 +1,21 @@ +# 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. + +from __future__ import print_function + +import subprocess +import sys + +# This script executes a command and redirects the stdout to a file. This is +# equivalent to |command... > output_file|. +# +# Usage: python redirect_stdout.py output_file command... + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: %s output_file command..." % sys.argv[0], file=sys.stderr) + sys.exit(1) + + with open(sys.argv[1], 'w') as fp: + sys.exit(subprocess.check_call(sys.argv[2:], stdout=fp)) diff --git a/third_party/libwebrtc/build/rm.py b/third_party/libwebrtc/build/rm.py new file mode 100755 index 0000000000..43a663d6df --- /dev/null +++ b/third_party/libwebrtc/build/rm.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# Copyright (c) 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. + +"""Delete a file. + +This module works much like the rm posix command. +""" + +from __future__ import print_function + +import argparse +import os +import sys + + +def Main(): + parser = argparse.ArgumentParser() + parser.add_argument('files', nargs='+') + parser.add_argument('-f', '--force', action='store_true', + help="don't err on missing") + parser.add_argument('--stamp', required=True, help='touch this file') + args = parser.parse_args() + for f in args.files: + try: + os.remove(f) + except OSError: + if not args.force: + print("'%s' does not exist" % f, file=sys.stderr) + return 1 + + with open(args.stamp, 'w'): + os.utime(args.stamp, None) + + return 0 + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/rust/OWNERS b/third_party/libwebrtc/build/rust/OWNERS new file mode 100644 index 0000000000..e15cf8c77f --- /dev/null +++ b/third_party/libwebrtc/build/rust/OWNERS @@ -0,0 +1,8 @@ +adetaylor@chromium.org +ajgo@chromium.org +danakj@chromium.org +jclinton@chromium.org +lukasza@chromium.org +palmer@chromium.org +rsesek@chromium.org +thakis@chromium.org diff --git a/third_party/libwebrtc/build/rust/std/BUILD.gn b/third_party/libwebrtc/build/rust/std/BUILD.gn new file mode 100644 index 0000000000..7532439364 --- /dev/null +++ b/third_party/libwebrtc/build/rust/std/BUILD.gn @@ -0,0 +1,153 @@ +# Copyright 2021 The Chromium 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 provides the ability for our C++ toolchain to successfully +# link binaries containing arbitrary Rust code. +# +# By "arbitrary Rust code" I mean .rlib archives full of Rust code, which +# is actually a static archive. +# +# Those static libraries don't link as-is into a final executable because +# they're designed for downstream processing by further invocations of rustc +# which link into a final binary. That final invocation of rustc knows how +# to do two things: +# * Find the Rust standard library. +# * Remap some generic allocator symbols to the specific allocator symbols +# in use. +# This file does both those things. Any C++ target containing Rust .rlibs +# should simply depend on :std within this file and it will be taken care of. +# In practice, this will in future be taken care of by a standard template +# used for each Rust source set, so that a typical user of Rust need not +# think about it. +# +# This is obviously a bit fragile - rustc might do other magic in future. +# But, linking with a final C++ toolchain is something often needed, and +# https://github.com/rust-lang/rust/issues/64191 aims to make this +# officially possible. + +import("//build/config/compiler/compiler.gni") +import("//build/config/rust.gni") + +stdlib_files = [ + "std", # List first because it makes depfiles more debuggable (see below) + "addr2line", + "adler", + "alloc", + "cfg_if", + "compiler_builtins", + "core", + "getopts", + "gimli", + "hashbrown", + "libc", + "miniz_oxide", + "object", + "panic_abort", + "panic_unwind", + "proc_macro", + "rustc_demangle", + "std_detect", + "term", + "test", + "unicode_width", + "unwind", +] + +if (!use_unverified_rust_toolchain) { + # rlib files which are distributed alongside Rust's prebuilt stdlib, but we + # don't need to pass to the C++ linker because they're used for specialized + # purposes. + skip_stdlib_files = [ + "profiler_builtins", + "rustc_std_workspace_alloc", + "rustc_std_workspace_core", + "rustc_std_workspace_std", + ] +} + +if (toolchain_has_rust) { + action("find_stdlib") { + # Specifics of what we're doing here. + # + # We are using prebuilt Rust rlibs supplied along with the toolchain. + # The Rust standard library consists of rlibs with roughly all the names + # above. + # + # However, their filenames are not predictable, and therefore we can't + # have ninja rules which depend upon them. (gn offers a facility to + # build rules dynamically, but it's frowned upon because a script needs + # to run each time). + # + # Instead therefore we copy these unpredictable .rlib paths to apredictable + # location. That's what this script does. Furthermore, it generates a + # .d file in order to teach Ninja that it only needs to do this copying + # once, unless the source .rlibs change. + # + # The script accepts the list of known libraries and will raise an + # exception if the list on disk differs. (Either 'Found stdlib rlib + # that wasn't expected' or 'We failed to find all expected stdlib + # rlibs'). + script = "find_std_rlibs.py" + depfile = "$target_out_dir/stdlib.d" + out_libdir = rebase_path(target_out_dir, root_build_dir) + out_depfile = rebase_path(depfile, root_build_dir) + args = [ + "--rust-bin-dir", + rust_prefix, + "--output", + out_libdir, + "--depfile", + out_depfile, + + # Due to limitations in Ninja's handling of .d files, we have to pick + # *the first* of our outputs. To make diagnostics more obviously + # related to the Rust standard library, we ensure libstd.rlib is first. + "--depfile-target", + stdlib_files[0], + ] + if (!use_unverified_rust_toolchain) { + args += [ + "--stdlibs", + string_join(",", stdlib_files), + "--skip", + string_join(",", skip_stdlib_files), + ] + } + if (rust_abi_target != "") { + args += [ + "--target", + rust_abi_target, + ] + } + + outputs = [] + foreach(lib, stdlib_files) { + outputs += [ "$target_out_dir/lib$lib.rlib" ] + } + } + + config("rust_stdlib_config") { + ldflags = [] + out_libdir = rebase_path(target_out_dir, root_build_dir) + foreach(lib, stdlib_files) { + this_file = "$out_libdir/lib$lib.rlib" + ldflags += [ this_file ] + } + } + + source_set("remap_alloc") { + sources = [ + "immediate_crash.h", + "remap_alloc.c", + ] + } + + group("std") { + all_dependent_configs = [ ":rust_stdlib_config" ] + deps = [ + ":find_stdlib", + ":remap_alloc", + ] + } +} diff --git a/third_party/libwebrtc/build/rust/std/find_std_rlibs.py b/third_party/libwebrtc/build/rust/std/find_std_rlibs.py new file mode 100755 index 0000000000..b8f775e438 --- /dev/null +++ b/third_party/libwebrtc/build/rust/std/find_std_rlibs.py @@ -0,0 +1,97 @@ +#!/usr/bin/env/python3 + +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# See BUILD.gn in this directory for an explanation of what this script is for. + +import argparse +import os +import sys +import shutil +import subprocess +import re + +REMOVE_METADATA_SUFFIX_RE = re.compile(b"-[0-9a-f]*", re.I) + + +def expand_name(concise_name): + return "lib%s.rlib" % concise_name + + +def main(): + parser = argparse.ArgumentParser("find_std_rlibs.py") + parser.add_argument("--rust-bin-dir", + help="Path to Rust binaries", + required=True), + parser.add_argument("--target", help="Rust target triple", required=False), + parser.add_argument("--output", + help="Path to rlibs without suffixes", + required=True) + parser.add_argument("--depfile", help="Path to write depfile", required=True) + parser.add_argument("--depfile-target", + help="Target to key depfile around", + required=True) + parser.add_argument("--stdlibs", + help="Expected list of standard library libraries") + parser.add_argument("--skip-stdlibs", + help="Standard library files to skip", + default="") + args = parser.parse_args() + # Ensure we handle each rlib in the expected list exactly once. + if args.stdlibs: + rlibs_expected = [expand_name(x) for x in args.stdlibs.split(',')] + else: + rlibs_expected = None + rlibs_to_skip = [expand_name(x) for x in args.skip_stdlibs.split(',')] + # Ask rustc where to find the stdlib for this target. + rustc = os.path.join(args.rust_bin_dir, "rustc") + rustc_args = [rustc, "--print", "target-libdir"] + if args.target: + rustc_args.extend(["--target", args.target]) + rustlib_dir = subprocess.check_output(rustc_args).rstrip() + # Copy the rlibs to a predictable location. Whilst we're doing so, + # also write a .d file so that ninja knows it doesn't need to do this + # again unless the source rlibs change. + # Format: + # /lib: + with open(args.depfile, 'w') as depfile: + # Ninja isn't versatile at understanding depfiles. We have to say that a + # single output depends on all the inputs. We choose any one of the + # output rlibs for that purpose. If any of the input rlibs change, ninja + # will run this script again and we'll copy them all afresh. + depfile.write("%s:" % + (os.path.join(args.output, expand_name(args.depfile_target)))) + for f in os.listdir(rustlib_dir): + if f.endswith(b'.rlib'): + # As standard Rust includes a hash on the end of each filename + # representing certain metadata, to ensure that clients will link + # against the correct version. As gn will be manually passing + # the correct file path to our linker invocations, we don't need + # that, and it would prevent us having the predictable filenames + # which we need for statically computable gn dependency rules. + (concise_name, count) = REMOVE_METADATA_SUFFIX_RE.subn(b"", f) + if count == 0: + raise Exception("Unable to remove suffix from %s" % f) + if concise_name.decode() in rlibs_to_skip: + continue + if rlibs_expected is not None: + if concise_name.decode() not in rlibs_expected: + raise Exception("Found stdlib rlib that wasn't expected: %s" % + concise_name) + rlibs_expected.remove(concise_name.decode()) + infile = os.path.join(rustlib_dir, f) + outfile = os.path.join(str.encode(args.output), concise_name) + depfile.write(" %s" % (infile.decode())) + if (not os.path.exists(outfile) + or os.stat(infile).st_mtime > os.stat(outfile).st_mtime): + shutil.copy(infile, outfile) + depfile.write("\n") + if rlibs_expected: + raise Exception("We failed to find all expected stdlib rlibs: %s" % + ','.join(rlibs_expected)) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/rust/std/immediate_crash.h b/third_party/libwebrtc/build/rust/std/immediate_crash.h new file mode 100644 index 0000000000..9874bdcd48 --- /dev/null +++ b/third_party/libwebrtc/build/rust/std/immediate_crash.h @@ -0,0 +1,170 @@ +// Copyright 2021 The Chromium 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 copied from //base/immediate_crash.h. + +#ifndef BUILD_RUST_STD_IMMEDIATE_CRASH_H_ +#define BUILD_RUST_STD_IMMEDIATE_CRASH_H_ + +#include "build/build_config.h" + +// Crashes in the fastest possible way with no attempt at logging. +// There are several constraints; see http://crbug.com/664209 for more context. +// +// - TRAP_SEQUENCE_() must be fatal. It should not be possible to ignore the +// resulting exception or simply hit 'continue' to skip over it in a debugger. +// - Different instances of TRAP_SEQUENCE_() must not be folded together, to +// ensure crash reports are debuggable. Unlike __builtin_trap(), asm volatile +// blocks will not be folded together. +// Note: TRAP_SEQUENCE_() previously required an instruction with a unique +// nonce since unlike clang, GCC folds together identical asm volatile +// blocks. +// - TRAP_SEQUENCE_() must produce a signal that is distinct from an invalid +// memory access. +// - TRAP_SEQUENCE_() must be treated as a set of noreturn instructions. +// __builtin_unreachable() is used to provide that hint here. clang also uses +// this as a heuristic to pack the instructions in the function epilogue to +// improve code density. +// +// Additional properties that are nice to have: +// - TRAP_SEQUENCE_() should be as compact as possible. +// - The first instruction of TRAP_SEQUENCE_() should not change, to avoid +// shifting crash reporting clusters. As a consequence of this, explicit +// assembly is preferred over intrinsics. +// Note: this last bullet point may no longer be true, and may be removed in +// the future. + +// Note: TRAP_SEQUENCE Is currently split into two macro helpers due to the fact +// that clang emits an actual instruction for __builtin_unreachable() on certain +// platforms (see https://crbug.com/958675). In addition, the int3/bkpt/brk will +// be removed in followups, so splitting it up like this now makes it easy to +// land the followups. + +#if defined(COMPILER_GCC) + +#if defined(OS_NACL) + +// Crash report accuracy is not guaranteed on NaCl. +#define TRAP_SEQUENCE1_() __builtin_trap() +#define TRAP_SEQUENCE2_() asm volatile("") + +#elif defined(ARCH_CPU_X86_FAMILY) + +// TODO(https://crbug.com/958675): In theory, it should be possible to use just +// int3. However, there are a number of crashes with SIGILL as the exception +// code, so it seems likely that there's a signal handler that allows execution +// to continue after SIGTRAP. +#define TRAP_SEQUENCE1_() asm volatile("int3") + +#if defined(OS_APPLE) +// Intentionally empty: __builtin_unreachable() is always part of the sequence +// (see IMMEDIATE_CRASH below) and already emits a ud2 on Mac. +#define TRAP_SEQUENCE2_() asm volatile("") +#else +#define TRAP_SEQUENCE2_() asm volatile("ud2") +#endif // defined(OS_APPLE) + +#elif defined(ARCH_CPU_ARMEL) + +// bkpt will generate a SIGBUS when running on armv7 and a SIGTRAP when running +// as a 32 bit userspace app on arm64. There doesn't seem to be any way to +// cause a SIGTRAP from userspace without using a syscall (which would be a +// problem for sandboxing). +// TODO(https://crbug.com/958675): Remove bkpt from this sequence. +#define TRAP_SEQUENCE1_() asm volatile("bkpt #0") +#define TRAP_SEQUENCE2_() asm volatile("udf #0") + +#elif defined(ARCH_CPU_ARM64) + +// This will always generate a SIGTRAP on arm64. +// TODO(https://crbug.com/958675): Remove brk from this sequence. +#define TRAP_SEQUENCE1_() asm volatile("brk #0") +#define TRAP_SEQUENCE2_() asm volatile("hlt #0") + +#else + +// Crash report accuracy will not be guaranteed on other architectures, but at +// least this will crash as expected. +#define TRAP_SEQUENCE1_() __builtin_trap() +#define TRAP_SEQUENCE2_() asm volatile("") + +#endif // ARCH_CPU_* + +#elif defined(COMPILER_MSVC) + +#if !defined(__clang__) + +// MSVC x64 doesn't support inline asm, so use the MSVC intrinsic. +#define TRAP_SEQUENCE1_() __debugbreak() +#define TRAP_SEQUENCE2_() + +#elif defined(ARCH_CPU_ARM64) + +// Windows ARM64 uses "BRK #F000" as its breakpoint instruction, and +// __debugbreak() generates that in both VC++ and clang. +#define TRAP_SEQUENCE1_() __debugbreak() +// Intentionally empty: __builtin_unreachable() is always part of the sequence +// (see IMMEDIATE_CRASH below) and already emits a ud2 on Win64, +// https://crbug.com/958373 +#define TRAP_SEQUENCE2_() __asm volatile("") + +#else + +#define TRAP_SEQUENCE1_() asm volatile("int3") +#define TRAP_SEQUENCE2_() asm volatile("ud2") + +#endif // __clang__ + +#else + +#error No supported trap sequence! + +#endif // COMPILER_GCC + +#define TRAP_SEQUENCE_() \ + do { \ + TRAP_SEQUENCE1_(); \ + TRAP_SEQUENCE2_(); \ + } while (false) + +// CHECK() and the trap sequence can be invoked from a constexpr function. +// This could make compilation fail on GCC, as it forbids directly using inline +// asm inside a constexpr function. However, it allows calling a lambda +// expression including the same asm. +// The side effect is that the top of the stacktrace will not point to the +// calling function, but to this anonymous lambda. This is still useful as the +// full name of the lambda will typically include the name of the function that +// calls CHECK() and the debugger will still break at the right line of code. +#if !defined(COMPILER_GCC) + +#define WRAPPED_TRAP_SEQUENCE_() TRAP_SEQUENCE_() + +#else + +#define WRAPPED_TRAP_SEQUENCE_() \ + do { \ + [] { TRAP_SEQUENCE_(); }(); \ + } while (false) + +#endif // !defined(COMPILER_GCC) + +#if defined(__clang__) || defined(COMPILER_GCC) + +// __builtin_unreachable() hints to the compiler that this is noreturn and can +// be packed in the function epilogue. +#define IMMEDIATE_CRASH() \ + ({ \ + WRAPPED_TRAP_SEQUENCE_(); \ + __builtin_unreachable(); \ + }) + +#else + +// This is supporting non-chromium user of logging.h to build with MSVC, like +// pdfium. On MSVC there is no __builtin_unreachable(). +#define IMMEDIATE_CRASH() WRAPPED_TRAP_SEQUENCE_() + +#endif // defined(__clang__) || defined(COMPILER_GCC) + +#endif // BUILD_RUST_STD_IMMEDIATE_CRASH_H_ diff --git a/third_party/libwebrtc/build/rust/std/remap_alloc.c b/third_party/libwebrtc/build/rust/std/remap_alloc.c new file mode 100644 index 0000000000..ba3b2b0671 --- /dev/null +++ b/third_party/libwebrtc/build/rust/std/remap_alloc.c @@ -0,0 +1,70 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "build/rust/std/immediate_crash.h" + +// When linking a final binary, rustc has to pick between either: +// * The default Rust allocator +// * Any #[global_allocator] defined in *any rlib in its dependency tree* +// (https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html) +// +// In this latter case, this fact will be recorded in some of the metadata +// within the .rlib file. (An .rlib file is just a .a file, but does have +// additional metadata for use by rustc. This is, as far as I know, the only +// such metadata we would ideally care about.) +// +// In all the linked rlibs, +// * If 0 crates define a #[global_allocator], rustc uses its default allocator +// * If 1 crate defines a #[global_allocator], rustc uses that +// * If >1 crates define a #[global_allocator], rustc bombs out. +// +// Because rustc does these checks, it doesn't just have the __rust_alloc +// symbols defined anywhere (neither in the stdlib nor in any of these +// crates which have a #[global_allocator] defined.) +// +// Instead: +// Rust's final linking stage invokes dynamic LLVM codegen to create symbols +// for the basic heap allocation operations. It literally creates a +// __rust_alloc symbol at link time. Unless any crate has specified a +// #[global_allocator], it simply calls from __rust_alloc into +// __rdl_alloc, which is the default Rust allocator. The same applies to a +// few other symbols. +// +// We're not (always) using rustc for final linking. For cases where we're not +// Rustc as the final linker, we'll define those symbols here instead. +// +// In future, we may wish to do something different from using the Rust +// default allocator (e.g. explicitly redirect to PartitionAlloc). We could +// do that here, or we could build a crate with a #[global_allocator] and +// redirect these symbols to that crate instead. The advantage of the latter +// is that it would work equally well for those cases where rustc is doing +// the final linking. + +void* __rdl_alloc(size_t, size_t); +void __rdl_dealloc(void*); +void* __rdl_realloc(void*, size_t, size_t, size_t); +void* __rdl_alloc_zeroed(size_t, size_t); + +void* __rust_alloc(size_t a, size_t b) { + return __rdl_alloc(a, b); +} + +void __rust_dealloc(void* a) { + __rdl_dealloc(a); +} + +void* __rust_realloc(void* a, size_t b, size_t c, size_t d) { + return __rdl_realloc(a, b, c, d); +} + +void* __rust_alloc_zeroed(size_t a, size_t b) { + return __rdl_alloc_zeroed(a, b); +} + +void __rust_alloc_error_handler(size_t a, size_t b) { + IMMEDIATE_CRASH(); +} diff --git a/third_party/libwebrtc/build/rust/tests/BUILD.gn b/third_party/libwebrtc/build/rust/tests/BUILD.gn new file mode 100644 index 0000000000..87a299f21f --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 2021 The Chromium 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/rust.gni") + +group("tests") { + # Build some minimal binaries to exercise the Rust toolchain + # only if that toolchain is enabled in gn args. + if (toolchain_has_rust) { + deps = [ + "test_cpp_exe_including_rust", + "test_rust_source_set", + ] + if (build_rust_unit_tests) { + deps += [ "test_rust_source_set:test_rust_source_set_unittests" ] + } + if (rustc_can_link) { + deps += [ "test_rust_exe" ] + } + } +} diff --git a/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/BUILD.gn b/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/BUILD.gn new file mode 100644 index 0000000000..a5bdb3dc6c --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/BUILD.gn @@ -0,0 +1,8 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +executable("test_cpp_exe_including_rust") { + sources = [ "main.cc" ] + deps = [ "//build/rust/tests/test_rust_source_set:test_rust_source_set_lib" ] +} diff --git a/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/main.cc b/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/main.cc new file mode 100644 index 0000000000..c1de55af1c --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_cpp_exe_including_rust/main.cc @@ -0,0 +1,11 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defined in Rust. +extern "C" void say_hello_from_cpp(); + +int main(int argc, char* argv[]) { + say_hello_from_cpp(); + return 0; +} diff --git a/third_party/libwebrtc/build/rust/tests/test_rust_exe/BUILD.gn b/third_party/libwebrtc/build/rust/tests/test_rust_exe/BUILD.gn new file mode 100644 index 0000000000..0ce2e4a507 --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_rust_exe/BUILD.gn @@ -0,0 +1,12 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +executable("test_rust_exe") { + crate_root = "main.rs" + deps = [ "//build/rust/tests/test_rust_source_set" ] + rustflags = [ + "--edition", + "2018", + ] +} diff --git a/third_party/libwebrtc/build/rust/tests/test_rust_exe/main.rs b/third_party/libwebrtc/build/rust/tests/test_rust_exe/main.rs new file mode 100644 index 0000000000..c1072054b8 --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_rust_exe/main.rs @@ -0,0 +1,9 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use test_rust_source_set::say_hello; + +fn main() { + say_hello(); +} diff --git a/third_party/libwebrtc/build/rust/tests/test_rust_source_set/BUILD.gn b/third_party/libwebrtc/build/rust/tests/test_rust_source_set/BUILD.gn new file mode 100644 index 0000000000..0dc9c540e1 --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_rust_source_set/BUILD.gn @@ -0,0 +1,42 @@ +# Copyright 2021 The Chromium 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/compiler/compiler.gni") +import("//build/config/rust.gni") + +# A future CL will provide a gn template to generate all the following +# automatically. We anticipate calling that template "rust_source_set" +# which is why this test component is named the way it is. + +# Dependent Rust targets should depend on this. +rust_library("test_rust_source_set") { + crate_name = "test_rust_source_set" + crate_root = "main.rs" + rustflags = [ + "--edition", + "2018", + ] +} + +# Dependent C++ targets should depend on this, +# rather than the rlib above +group("test_rust_source_set_lib") { + deps = [ + ":test_rust_source_set", + "//build/rust/std", + ] +} + +if (build_rust_unit_tests) { + executable("test_rust_source_set_unittests") { + crate_root = "main.rs" + rustflags = [ + "--cfg", + "feature=\"test\"", + "--test", + "--edition", + "2018", + ] + } +} diff --git a/third_party/libwebrtc/build/rust/tests/test_rust_source_set/main.rs b/third_party/libwebrtc/build/rust/tests/test_rust_source_set/main.rs new file mode 100644 index 0000000000..f0db1a3065 --- /dev/null +++ b/third_party/libwebrtc/build/rust/tests/test_rust_source_set/main.rs @@ -0,0 +1,21 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#[no_mangle] +pub extern "C" fn say_hello_from_cpp() { + say_hello(); +} + +pub fn say_hello() { + println!("Hello, world - from a Rust library. Calculations suggest that 3+4={}", do_maths()); +} + +fn do_maths() -> i32 { + 3 + 4 +} + +#[test] +fn test_hello() { + assert_eq!(7, do_maths()); +} diff --git a/third_party/libwebrtc/build/sample_arg_file.gn b/third_party/libwebrtc/build/sample_arg_file.gn new file mode 100644 index 0000000000..91e90456e8 --- /dev/null +++ b/third_party/libwebrtc/build/sample_arg_file.gn @@ -0,0 +1,6 @@ +# Build arguments go here. Here are some of the most commonly set ones. +# Run `gn args --list` for the full list. +# is_component_build = true +# is_debug = true +# symbol_level = 2 +# use_goma = false diff --git a/third_party/libwebrtc/build/sanitize-mac-build-log.sed b/third_party/libwebrtc/build/sanitize-mac-build-log.sed new file mode 100644 index 0000000000..b4111c7b82 --- /dev/null +++ b/third_party/libwebrtc/build/sanitize-mac-build-log.sed @@ -0,0 +1,33 @@ +# 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. + +# Use this sed script to reduce a Mac build log into something readable. + +# Drop uninformative lines. +/^distcc/d +/^Check dependencies/d +/^ setenv /d +/^ cd /d +/^make: Nothing to be done/d +/^$/d + +# Xcode prints a short "compiling foobar.o" line followed by the lengthy +# full command line. These deletions drop the command line. +\|^ /Developer/usr/bin/|d +\|^ /Developer/Library/PrivateFrameworks/DevToolsCore\.framework/|d +\|^ /Developer/Library/Xcode/Plug-ins/CoreBuildTasks\.xcplugin/|d + +# Drop any goma command lines as well. +\|^ .*/gomacc |d + +# And, if you've overridden something from your own bin directory, remove those +# full command lines, too. +\|^ /Users/[^/]*/bin/|d + +# There's already a nice note for bindings, don't need the command line. +\|^python scripts/rule_binding\.py|d + +# Shorten the "compiling foobar.o" line. +s|^Distributed-CompileC (.*) normal i386 c\+\+ com\.apple\.compilers\.gcc\.4_2| CC \1| +s|^CompileC (.*) normal i386 c\+\+ com\.apple\.compilers\.gcc\.4_2| CC \1| diff --git a/third_party/libwebrtc/build/sanitize-mac-build-log.sh b/third_party/libwebrtc/build/sanitize-mac-build-log.sh new file mode 100755 index 0000000000..df5a7af29e --- /dev/null +++ b/third_party/libwebrtc/build/sanitize-mac-build-log.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +sed -r -f `dirname "${0}"`/`basename "${0}" sh`sed diff --git a/third_party/libwebrtc/build/sanitize-win-build-log.sed b/third_party/libwebrtc/build/sanitize-win-build-log.sed new file mode 100644 index 0000000000..c18e664c83 --- /dev/null +++ b/third_party/libwebrtc/build/sanitize-win-build-log.sed @@ -0,0 +1,15 @@ +# 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. + +# Use this sed script to reduce a Windows build log into something +# machine-parsable. + +# Drop uninformative lines. +/The operation completed successfully\./d + +# Drop parallelization indicators on lines. +s/^[0-9]+>// + +# Shorten bindings generation lines +s/^.*"python".*idl_compiler\.py".*("[^"]+\.idl").*$/ idl_compiler \1/ diff --git a/third_party/libwebrtc/build/sanitize-win-build-log.sh b/third_party/libwebrtc/build/sanitize-win-build-log.sh new file mode 100755 index 0000000000..df5a7af29e --- /dev/null +++ b/third_party/libwebrtc/build/sanitize-win-build-log.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +sed -r -f `dirname "${0}"`/`basename "${0}" sh`sed diff --git a/third_party/libwebrtc/build/sanitizers/OWNERS b/third_party/libwebrtc/build/sanitizers/OWNERS new file mode 100644 index 0000000000..b893bc80e9 --- /dev/null +++ b/third_party/libwebrtc/build/sanitizers/OWNERS @@ -0,0 +1,8 @@ +ochang@chromium.org +eugenis@chromium.org +glider@chromium.org +inferno@chromium.org +metzman@chromium.org +rnk@chromium.org +per-file tsan_suppressions.cc=* +per-file lsan_suppressions.cc=* diff --git a/third_party/libwebrtc/build/sanitizers/asan_suppressions.cc b/third_party/libwebrtc/build/sanitizers/asan_suppressions.cc new file mode 100644 index 0000000000..f3b9459ded --- /dev/null +++ b/third_party/libwebrtc/build/sanitizers/asan_suppressions.cc @@ -0,0 +1,23 @@ +// 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 contains the default suppressions for AddressSanitizer. +// It should only be used under very limited circumstances such as suppressing +// a report caused by an interceptor call in a system-installed library. + +#if defined(ADDRESS_SANITIZER) + +// Please make sure the code below declares a single string variable +// kASanDefaultSuppressions which contains ASan suppressions delimited by +// newlines. Entries should look, for example, like: +// +// // http://crbug.com/178677 +// "interceptor_via_lib:libsqlite3.so\n" +char kASanDefaultSuppressions[] = + + // End of suppressions. + // PLEASE READ ABOVE BEFORE ADDING NEW SUPPRESSIONS. + ""; // Please keep this semicolon. + +#endif // ADDRESS_SANITIZER diff --git a/third_party/libwebrtc/build/sanitizers/lsan_suppressions.cc b/third_party/libwebrtc/build/sanitizers/lsan_suppressions.cc new file mode 100644 index 0000000000..8b35550061 --- /dev/null +++ b/third_party/libwebrtc/build/sanitizers/lsan_suppressions.cc @@ -0,0 +1,78 @@ +// 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 contains the default suppressions for LeakSanitizer. +// You can also pass additional suppressions via LSAN_OPTIONS: +// LSAN_OPTIONS=suppressions=/path/to/suppressions. Please refer to +// http://dev.chromium.org/developers/testing/leaksanitizer for more info. + +#if defined(LEAK_SANITIZER) + +// Please make sure the code below declares a single string variable +// kLSanDefaultSuppressions which contains LSan suppressions delimited by +// newlines. See http://dev.chromium.org/developers/testing/leaksanitizer +// for the instructions on writing suppressions. +char kLSanDefaultSuppressions[] = + // Intentional leak used as sanity test for Valgrind/memcheck. + "leak:base::ToolsSanityTest_MemoryLeak_Test::TestBody\n" + + // ================ Leaks in third-party code ================ + + // False positives in libfontconfig. http://crbug.com/39050 + "leak:libfontconfig\n" + // eglibc-2.19/string/strdup.c creates false positive leak errors because of + // the same reason as crbug.com/39050. The leak error stack trace, when + // unwind on malloc, includes a call to libfontconfig. But the default stack + // trace is too short in leak sanitizer bot to make the libfontconfig + // suppression works. http://crbug.com/605286 + "leak:__strdup\n" + + // Leaks in Nvidia's libGL. + "leak:libGL.so\n" + "leak:libGLX_nvidia.so\n" + "leak:libnvidia-glcore.so\n" + + // XRandR has several one time leaks. + "leak:libxrandr\n" + + // xrandr leak. http://crbug.com/119677 + "leak:XRRFindDisplay\n" + + // http://crbug.com/431213, http://crbug.com/416665 + "leak:gin/object_template_builder.h\n" + "leak:gin/function_template.h\n" + + // Leaks in swrast_dri.so. http://crbug.com/540042 + "leak:swrast_dri.so\n" + + // Leak in glibc's gconv caused by fopen(..., "r,ccs=UNICODE") + "leak:__gconv_lookup_cache\n" + + // ================ Leaks in Chromium code ================ + // PLEASE DO NOT ADD SUPPRESSIONS FOR NEW LEAKS. + // Instead, commits that introduce memory leaks should be reverted. + // Suppressing the leak is acceptable in some cases when reverting is + // impossible, i.e. when enabling leak detection for the first time for a + // test target with pre-existing leaks. + + // https://crbug.com/755670 + "leak:third_party/yasm/\n" + + // v8 leaks caused by weak ref not call + "leak:blink::DOMWrapperWorld::Create\n" + "leak:blink::ScriptState::Create\n" + + // Crash keys are intentionally leaked. + "leak:crash_reporter::(anonymous " + "namespace)::CrashKeyBaseSupport::Allocate\n" + + // Suppress leaks in CreateCdmInstance. https://crbug.com/961062 + "leak:media::CdmAdapter::CreateCdmInstance\n" + + // PLEASE READ ABOVE BEFORE ADDING NEW SUPPRESSIONS. + + // End of suppressions. + ; // Please keep this semicolon. + +#endif // LEAK_SANITIZER diff --git a/third_party/libwebrtc/build/sanitizers/sanitizer_options.cc b/third_party/libwebrtc/build/sanitizers/sanitizer_options.cc new file mode 100644 index 0000000000..d3ab861773 --- /dev/null +++ b/third_party/libwebrtc/build/sanitizers/sanitizer_options.cc @@ -0,0 +1,205 @@ +// 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 the default options for various compiler-based dynamic +// tools. + +#include "build/build_config.h" + +#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \ + defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ + defined(UNDEFINED_SANITIZER) +// Functions returning default options are declared weak in the tools' runtime +// libraries. To make the linker pick the strong replacements for those +// functions from this module, we explicitly force its inclusion by passing +// -Wl,-u_sanitizer_options_link_helper +extern "C" +void _sanitizer_options_link_helper() { } + +// The callbacks we define here will be called from the sanitizer runtime, but +// aren't referenced from the Chrome executable. We must ensure that those +// callbacks are not sanitizer-instrumented, and that they aren't stripped by +// the linker. +#define SANITIZER_HOOK_ATTRIBUTE \ + extern "C" \ + __attribute__((no_sanitize("address", "memory", "thread", "undefined"))) \ + __attribute__((visibility("default"))) \ + __attribute__((used)) +#endif + +#if defined(ADDRESS_SANITIZER) +// Default options for AddressSanitizer in various configurations: +// check_printf=1 - check the memory accesses to printf (and other formatted +// output routines) arguments. +// use_sigaltstack=1 - handle signals on an alternate signal stack. Useful +// for stack overflow detection. +// strip_path_prefix=/../../ - prefixes up to and including this +// substring will be stripped from source file paths in symbolized reports +// fast_unwind_on_fatal=1 - use the fast (frame-pointer-based) stack unwinder +// to print error reports. V8 doesn't generate debug info for the JIT code, +// so the slow unwinder may not work properly. +// detect_stack_use_after_return=1 - use fake stack to delay the reuse of +// stack allocations and detect stack-use-after-return errors. +// symbolize=1 - enable in-process symbolization. +// external_symbolizer_path=... - provides the path to llvm-symbolizer +// relative to the main executable +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +const char kAsanDefaultOptions[] = + "check_printf=1 use_sigaltstack=1 strip_path_prefix=/../../ " + "fast_unwind_on_fatal=1 detect_stack_use_after_return=1 " + "symbolize=1 detect_leaks=0 allow_user_segv_handler=1 " + "external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/" + "bin/llvm-symbolizer"; + +#elif defined(OS_APPLE) +const char* kAsanDefaultOptions = + "check_printf=1 use_sigaltstack=1 strip_path_prefix=/../../ " + "fast_unwind_on_fatal=1 detect_stack_use_after_return=1 "; + +#elif defined(OS_WIN) +const char* kAsanDefaultOptions = + "check_printf=1 use_sigaltstack=1 strip_path_prefix=\\..\\..\\ " + "fast_unwind_on_fatal=1 detect_stack_use_after_return=1 " + "symbolize=1 external_symbolizer_path=%d/../../third_party/" + "llvm-build/Release+Asserts/bin/llvm-symbolizer.exe"; +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_APPLE) || \ + defined(OS_WIN) +// Allow NaCl to override the default asan options. +extern const char* kAsanDefaultOptionsNaCl; +__attribute__((weak)) const char* kAsanDefaultOptionsNaCl = nullptr; + +SANITIZER_HOOK_ATTRIBUTE const char *__asan_default_options() { + if (kAsanDefaultOptionsNaCl) + return kAsanDefaultOptionsNaCl; + return kAsanDefaultOptions; +} + +extern char kASanDefaultSuppressions[]; + +SANITIZER_HOOK_ATTRIBUTE const char *__asan_default_suppressions() { + return kASanDefaultSuppressions; +} +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_APPLE) || + // defined(OS_WIN) +#endif // ADDRESS_SANITIZER + +#if defined(THREAD_SANITIZER) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) +// Default options for ThreadSanitizer in various configurations: +// detect_deadlocks=1 - enable deadlock (lock inversion) detection. +// second_deadlock_stack=1 - more verbose deadlock reports. +// report_signal_unsafe=0 - do not report async-signal-unsafe functions +// called from signal handlers. +// report_thread_leaks=0 - do not report unjoined threads at the end of +// the program execution. +// print_suppressions=1 - print the list of matched suppressions. +// history_size=7 - make the history buffer proportional to 2^7 (the maximum +// value) to keep more stack traces. +// strip_path_prefix=/../../ - prefixes up to and including this +// substring will be stripped from source file paths in symbolized reports. +// external_symbolizer_path=... - provides the path to llvm-symbolizer +// relative to the main executable +const char kTsanDefaultOptions[] = + "detect_deadlocks=1 second_deadlock_stack=1 report_signal_unsafe=0 " + "report_thread_leaks=0 print_suppressions=1 history_size=7 " + "strip_path_prefix=/../../ external_symbolizer_path=%d/../../third_party/" + "llvm-build/Release+Asserts/bin/llvm-symbolizer"; + +SANITIZER_HOOK_ATTRIBUTE const char *__tsan_default_options() { + return kTsanDefaultOptions; +} + +extern char kTSanDefaultSuppressions[]; + +SANITIZER_HOOK_ATTRIBUTE const char *__tsan_default_suppressions() { + return kTSanDefaultSuppressions; +} + +#endif // defined(THREAD_SANITIZER) && (defined(OS_LINUX) || + // defined(OS_CHROMEOS)) + +#if defined(MEMORY_SANITIZER) +// Default options for MemorySanitizer: +// strip_path_prefix=/../../ - prefixes up to and including this +// substring will be stripped from source file paths in symbolized reports. +// external_symbolizer_path=... - provides the path to llvm-symbolizer +// relative to the main executable +const char kMsanDefaultOptions[] = + "strip_path_prefix=/../../ " + +#if !defined(OS_APPLE) + "external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/" + "bin/llvm-symbolizer" +#endif +; + +SANITIZER_HOOK_ATTRIBUTE const char *__msan_default_options() { + return kMsanDefaultOptions; +} + +#endif // MEMORY_SANITIZER + +#if defined(LEAK_SANITIZER) +// Default options for LeakSanitizer: +// print_suppressions=1 - print the list of matched suppressions. +// strip_path_prefix=/../../ - prefixes up to and including this +// substring will be stripped from source file paths in symbolized reports. +// external_symbolizer_path=... - provides the path to llvm-symbolizer +// relative to the main executable +// use_poisoned=1 - Scan poisoned memory. This is useful for Oilpan (C++ +// garbage collection) which wants to exclude its managed memory from being +// reported as leaks (through root regions) and also temporarily poisons +// memory regions before calling destructors of objects to avoid destructors +// cross-referencing memory in other objects. Main thread termination in +// Blink is not graceful and leak checks may be emitted at any time, which +// means that the garbage collector may be in a state with poisoned memory, +// leading to false-positive reports. +const char kLsanDefaultOptions[] = + "print_suppressions=1 strip_path_prefix=/../../ " + "use_poisoned=1 " + +#if !defined(OS_APPLE) + "external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/" + "bin/llvm-symbolizer " +#endif + +#if defined(ARCH_CPU_64_BITS) + // When pointer compression in V8 is enabled the external pointers in the + // heap are guaranteed to be only 4 bytes aligned. So we need this option + // in order to ensure that LSAN will find all the external pointers. + // TODO(crbug.com/328552): see updates from 2019. + "use_unaligned=1 " +#endif // ARCH_CPU_64_BITS + ; + +SANITIZER_HOOK_ATTRIBUTE const char *__lsan_default_options() { + return kLsanDefaultOptions; +} + +extern char kLSanDefaultSuppressions[]; + +SANITIZER_HOOK_ATTRIBUTE const char *__lsan_default_suppressions() { + return kLSanDefaultSuppressions; +} + +#endif // LEAK_SANITIZER + +#if defined(UNDEFINED_SANITIZER) +// Default options for UndefinedBehaviorSanitizer: +// print_stacktrace=1 - print the stacktrace when UBSan reports an error. +const char kUbsanDefaultOptions[] = + "print_stacktrace=1 strip_path_prefix=/../../ " + +#if !defined(OS_APPLE) + "external_symbolizer_path=%d/../../third_party/llvm-build/Release+Asserts/" + "bin/llvm-symbolizer" +#endif + ; + +SANITIZER_HOOK_ATTRIBUTE const char* __ubsan_default_options() { + return kUbsanDefaultOptions; +} + +#endif // UNDEFINED_SANITIZER diff --git a/third_party/libwebrtc/build/sanitizers/tsan_suppressions.cc b/third_party/libwebrtc/build/sanitizers/tsan_suppressions.cc new file mode 100644 index 0000000000..06d32f3cfc --- /dev/null +++ b/third_party/libwebrtc/build/sanitizers/tsan_suppressions.cc @@ -0,0 +1,138 @@ +// 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 the default suppressions for ThreadSanitizer. +// You can also pass additional suppressions via TSAN_OPTIONS: +// TSAN_OPTIONS=suppressions=/path/to/suppressions. Please refer to +// http://dev.chromium.org/developers/testing/threadsanitizer-tsan-v2 +// for more info. + +#if defined(THREAD_SANITIZER) + +// Please make sure the code below declares a single string variable +// kTSanDefaultSuppressions contains TSan suppressions delimited by newlines. +// See http://dev.chromium.org/developers/testing/threadsanitizer-tsan-v2 +// for the instructions on writing suppressions. +char kTSanDefaultSuppressions[] = + // False positives in libdbus.so, libdconfsettings.so, libflashplayer.so, + // libgio.so, libglib.so and libgobject.so. + // Since we don't instrument them, we cannot reason about the + // synchronization in them. + "race:libdbus*.so\n" + "race:libdconfsettings*.so\n" + "race:libflashplayer.so\n" + "race:libgio*.so\n" + "race:libglib*.so\n" + "race:libgobject*.so\n" + + // Intentional race in ToolsSanityTest.DataRace in base_unittests. + "race:base/tools_sanity_unittest.cc\n" + + // Data race on WatchdogCounter [test-only]. + "race:base/threading/watchdog_unittest.cc\n" + + // Data race caused by swapping out the network change notifier with a mock + // [test-only]. http://crbug.com/927330. + "race:content/browser/net_info_browsertest.cc\n" + + // http://crbug.com/120808 + "race:base/threading/watchdog.cc\n" + + // http://crbug.com/157586 + "race:third_party/libvpx/source/libvpx/vp8/decoder/threading.c\n" + + // http://crbug.com/244856 + "race:libpulsecommon*.so\n" + + // http://crbug.com/258479 + "race:g_trace_state\n" + + // http://crbug.com/268924 + "race:base::g_power_monitor\n" + "race:base::PowerMonitor::PowerMonitor\n" + "race:base::PowerMonitor::AddObserver\n" + "race:base::PowerMonitor::RemoveObserver\n" + "race:base::PowerMonitor::IsOnBatteryPower\n" + + // http://crbug.com/272095 + "race:base::g_top_manager\n" + + // http://crbug.com/308590 + "race:CustomThreadWatcher::~CustomThreadWatcher\n" + + // http://crbug.com/476529 + "deadlock:cc::VideoLayerImpl::WillDraw\n" + + // http://crbug.com/328826 + "race:skia::(anonymous namespace)::g_pixel_geometry\n" + + // http://crbug.com/328868 + "race:PR_Lock\n" + + // http://crbug.com/348984 + "race:sctp_express_handle_sack\n" + "race:system_base_info\n" + + // False positive in libc's tzset_internal, http://crbug.com/379738. + "race:tzset_internal\n" + + // http://crbug.com/380554 + "deadlock:g_type_add_interface_static\n" + + // http:://crbug.com/386385 + "race:content::AppCacheStorageImpl::DatabaseTask::CallRunCompleted\n" + + // http://crbug.com/397022 + "deadlock:" + "base::trace_event::TraceEventTestFixture_ThreadOnceBlocking_Test::" + "TestBody\n" + + // http://crbug.com/415472 + "deadlock:base::trace_event::TraceLog::GetCategoryGroupEnabled\n" + + // Lock inversion in third party code, won't fix. + // https://crbug.com/455638 + "deadlock:dbus::Bus::ShutdownAndBlock\n" + + // https://crbug.com/459429 + "race:randomnessPid\n" + + // http://crbug.com/582274 + "race:usrsctp_close\n" + + // http://crbug.com/633145 + "race:third_party/libjpeg_turbo/simd/jsimd_x86_64.c\n" + + // http://crbug.com/691029 + "deadlock:libGLX.so*\n" + + // http://crbug.com/973947 + "deadlock:libnvidia-glsi.so*\n" + + // http://crbug.com/695929 + "race:base::i18n::IsRTL\n" + "race:base::i18n::SetICUDefaultLocale\n" + + // https://crbug.com/794920 + "race:base::debug::SetCrashKeyString\n" + "race:crash_reporter::internal::CrashKeyStringImpl::Set\n" + + // http://crbug.com/927330 + "race:net::(anonymous namespace)::g_network_change_notifier\n" + + // https://crbug.com/977085 + "race:vp3_update_thread_context\n" + + // Benign data race in libjpeg-turbo, won't fix + // (https://github.com/libjpeg-turbo/libjpeg-turbo/issues/87). + // https://crbug.com/1056011 + "race:third_party/libjpeg_turbo/simd/x86_64/jsimd.c\n" + + // https://crbug.com/1158622 + "race:absl::synchronization_internal::Waiter::Post\n" + + // End of suppressions. + ; // Please keep this semicolon. + +#endif // THREAD_SANITIZER diff --git a/third_party/libwebrtc/build/shim_headers.gni b/third_party/libwebrtc/build/shim_headers.gni new file mode 100644 index 0000000000..0900cba335 --- /dev/null +++ b/third_party/libwebrtc/build/shim_headers.gni @@ -0,0 +1,39 @@ +# 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. + +template("shim_headers") { + action_name = "gen_${target_name}" + config_name = "${target_name}_config" + shim_headers_path = "${root_gen_dir}/shim_headers/${target_name}" + + config(config_name) { + include_dirs = [ shim_headers_path ] + } + + action(action_name) { + script = "//tools/generate_shim_headers/generate_shim_headers.py" + args = [ + "--generate", + "--headers-root", + rebase_path(invoker.root_path), + "--output-directory", + rebase_path(shim_headers_path), + ] + if (defined(invoker.prefix)) { + args += [ + "--prefix", + invoker.prefix, + ] + } + args += invoker.headers + + outputs = process_file_template(invoker.headers, + "${shim_headers_path}/{{source_file_part}}") + } + + group(target_name) { + deps = [ ":${action_name}" ] + all_dependent_configs = [ ":${config_name}" ] + } +} diff --git a/third_party/libwebrtc/build/skia_gold_common/.style.yapf b/third_party/libwebrtc/build/skia_gold_common/.style.yapf new file mode 100644 index 0000000000..239e0a247f --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/.style.yapf @@ -0,0 +1,6 @@ +[style] +based_on_style = pep8 + +column_limit = 80 +indent_width = 2 + diff --git a/third_party/libwebrtc/build/skia_gold_common/OWNERS b/third_party/libwebrtc/build/skia_gold_common/OWNERS new file mode 100644 index 0000000000..428f610282 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/OWNERS @@ -0,0 +1 @@ +bsheedy@chromium.org diff --git a/third_party/libwebrtc/build/skia_gold_common/PRESUBMIT.py b/third_party/libwebrtc/build/skia_gold_common/PRESUBMIT.py new file mode 100644 index 0000000000..f4aeda79cf --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/PRESUBMIT.py @@ -0,0 +1,36 @@ +# Copyright 2020 The Chromium 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 //build/skia_gold_common/. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details on the presubmit API built into depot_tools. +""" + +USE_PYTHON3 = True + + +def CommonChecks(input_api, output_api): + output = [] + build_path = input_api.os_path.join(input_api.PresubmitLocalPath(), '..') + skia_gold_env = dict(input_api.environ) + skia_gold_env.update({ + 'PYTHONPATH': build_path, + 'PYTHONDONTWRITEBYTECODE': '1', + }) + output.extend( + input_api.canned_checks.RunUnitTestsInDirectory( + input_api, + output_api, + input_api.PresubmitLocalPath(), [r'^.+_unittest\.py$'], + env=skia_gold_env)) + output.extend(input_api.canned_checks.RunPylint(input_api, output_api)) + 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/third_party/libwebrtc/build/skia_gold_common/README.md b/third_party/libwebrtc/build/skia_gold_common/README.md new file mode 100644 index 0000000000..ec72111748 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/README.md @@ -0,0 +1,6 @@ +This directory contains Python code used for interacting with the Skia Gold +image diff service. It is used by multiple test harnesses, e.g. +`//build/android/test_runner.py` and +`//content/test/gpu/run_gpu_integration_test.py`. A place such as +`//testing/` would likely be a better location, but causes issues with +V8 since it imports `//build/` but not all of Chromium src. diff --git a/third_party/libwebrtc/build/skia_gold_common/__init__.py b/third_party/libwebrtc/build/skia_gold_common/__init__.py new file mode 100644 index 0000000000..ae1922e1cc --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2020 The Chromium 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/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session.py b/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session.py new file mode 100644 index 0000000000..b22487f749 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session.py @@ -0,0 +1,68 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Implementation of skia_gold_session.py without output managers. + +Diff output is instead stored in a directory and pointed to with file:// URLs. +""" + +import os +import subprocess +import time + +from skia_gold_common import skia_gold_session + + +class OutputManagerlessSkiaGoldSession(skia_gold_session.SkiaGoldSession): + def RunComparison( # pylint: disable=too-many-arguments + self, + name, + png_file, + output_manager=True, + inexact_matching_args=None, + use_luci=True, + optional_keys=None, + force_dryrun=False): + # Passing True for the output manager is a bit of a hack, as we don't + # actually need an output manager and just need to get past the truthy + # check. + return super(OutputManagerlessSkiaGoldSession, self).RunComparison( + name=name, + png_file=png_file, + output_manager=output_manager, + inexact_matching_args=inexact_matching_args, + use_luci=use_luci, + optional_keys=optional_keys, + force_dryrun=force_dryrun) + + def _CreateDiffOutputDir(self, name): + # Do this instead of just making a temporary directory so that it's easier + # for users to look through multiple results. We intentionally do not clean + # this directory up since the user might need to look at it later. + timestamp = int(time.time()) + name = '%s_%d' % (name, timestamp) + filepath = os.path.join(self._local_png_directory, name) + os.makedirs(filepath) + return filepath + + def _StoreDiffLinks(self, image_name, _, output_dir): + results = self._comparison_results.setdefault(image_name, + self.ComparisonResults()) + # The directory should contain "input-.png", "closest-.png", + # and "diff.png". + for f in os.listdir(output_dir): + file_url = 'file://%s' % os.path.join(output_dir, f) + if f.startswith('input-'): + results.local_diff_given_image = file_url + elif f.startswith('closest-'): + results.local_diff_closest_image = file_url + elif f == 'diff.png': + results.local_diff_diff_image = file_url + + @staticmethod + def _RunCmdForRcAndOutput(cmd): + try: + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + return 0, output + except subprocess.CalledProcessError as e: + return e.returncode, e.output diff --git a/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session_unittest.py b/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session_unittest.py new file mode 100755 index 0000000000..80937bb8e8 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/output_managerless_skia_gold_session_unittest.py @@ -0,0 +1,137 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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=protected-access + +import os +import re +import sys +import tempfile +import unittest + +import six + +if six.PY2: + import mock +else: + import unittest.mock as mock + +from pyfakefs import fake_filesystem_unittest + +from skia_gold_common import output_managerless_skia_gold_session as omsgs +from skia_gold_common import skia_gold_properties +from skia_gold_common import unittest_utils + +createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs + + +def assertArgWith(test, arg_list, arg, value): + i = arg_list.index(arg) + test.assertEqual(arg_list[i + 1], value) + + +class GpuSkiaGoldSessionDiffTest(fake_filesystem_unittest.TestCase): + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + @mock.patch.object(omsgs.OutputManagerlessSkiaGoldSession, + '_RunCmdForRcAndOutput') + def test_commandCommonArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = omsgs.OutputManagerlessSkiaGoldSession(self._working_dir, + sgp, + self._json_keys, + 'corpus', + instance='instance') + session.Diff('name', 'png_file', None) + call_args = cmd_mock.call_args[0][0] + self.assertIn('diff', call_args) + assertArgWith(self, call_args, '--corpus', 'corpus') + # TODO(skbug.com/10610): Remove the -public once we go back to using the + # non-public instance, or add a second test for testing that the correct + # instance is chosen if we decide to support both depending on what the + # user is authenticated for. + assertArgWith(self, call_args, '--instance', 'instance-public') + assertArgWith(self, call_args, '--input', 'png_file') + assertArgWith(self, call_args, '--test', 'name') + # TODO(skbug.com/10611): Re-add this assert and remove the check for the + # absence of the directory once we switch back to using the proper working + # directory. + # assertArgWith(self, call_args, '--work-dir', self._working_dir) + self.assertNotIn(self._working_dir, call_args) + i = call_args.index('--out-dir') + # The output directory should not be a subdirectory of the working + # directory. + self.assertNotIn(self._working_dir, call_args[i + 1]) + + @mock.patch.object(omsgs.OutputManagerlessSkiaGoldSession, '_StoreDiffLinks') + @mock.patch.object(omsgs.OutputManagerlessSkiaGoldSession, + '_RunCmdForRcAndOutput') + def test_explicitLocalPngDirectory(self, cmd_mock, _): + cmd_mock.return_value = (0, '') + if sys.platform == 'win32': + local_png_dir = 'c:\\tmp\\foo' + else: + local_png_dir = '/tmp/foo' + args = createSkiaGoldArgs(git_revision='a', + skia_gold_local_png_write_directory=local_png_dir) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = omsgs.OutputManagerlessSkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, + None) + _, _ = session.Diff('name', None, None) + self.assertEqual(cmd_mock.call_count, 1) + if six.PY3: + call_args = cmd_mock.call_args.args[0] + else: + call_args = cmd_mock.call_args[0][0] + self.assertIn('--out-dir', call_args) + output_dir = call_args[call_args.index('--out-dir') + 1] + # Directory should be a subdirectory of the directory we gave and be made + # up of the image name and a timestamp. + parent_dir, sub_dir = output_dir.rsplit(os.sep, 1) + self.assertEqual(parent_dir, local_png_dir) + sub_dir = os.path.normpath(sub_dir) + self.assertIsNotNone(re.match(r'^name_\d+$', sub_dir)) + + +class OutputManagerlessSkiaGoldSessionStoreDiffLinksTest( + fake_filesystem_unittest.TestCase): + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + def test_outputManagerNotNeeded(self): + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = omsgs.OutputManagerlessSkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, + None) + input_filepath = os.path.join(self._working_dir, 'input-inputhash.png') + with open(input_filepath, 'w') as f: + f.write('') + closest_filepath = os.path.join(self._working_dir, + 'closest-closesthash.png') + with open(closest_filepath, 'w') as f: + f.write('') + diff_filepath = os.path.join(self._working_dir, 'diff.png') + with open(diff_filepath, 'w') as f: + f.write('') + + session._StoreDiffLinks('foo', None, self._working_dir) + self.assertEqual(session.GetGivenImageLink('foo'), + 'file://' + input_filepath) + self.assertEqual(session.GetClosestImageLink('foo'), + 'file://' + closest_filepath) + self.assertEqual(session.GetDiffImageLink('foo'), 'file://' + diff_filepath) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties.py new file mode 100644 index 0000000000..c34146c699 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties.py @@ -0,0 +1,148 @@ +# Copyright 2020 The Chromium 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 storing Skia Gold comparison properties. + +Examples: +* git revision being tested +* Whether the test is being run locally or on a bot +* What the continuous integration system is +""" + +import logging +import os + + +class SkiaGoldProperties(object): + def __init__(self, args): + """Abstract class to validate and store properties related to Skia Gold. + + Args: + args: The parsed arguments from an argparse.ArgumentParser. + """ + self._git_revision = None + self._issue = None + self._patchset = None + self._job_id = None + self._local_pixel_tests = None + self._no_luci_auth = None + self._bypass_skia_gold_functionality = None + self._code_review_system = None + self._continuous_integration_system = None + self._local_png_directory = None + + self._InitializeProperties(args) + + def IsTryjobRun(self): + return self.issue is not None + + @property + def continuous_integration_system(self): + return self._continuous_integration_system or 'buildbucket' + + @property + def code_review_system(self): + return self._code_review_system or 'gerrit' + + @property + def git_revision(self): + return self._GetGitRevision() + + @property + def issue(self): + return self._issue + + @property + def job_id(self): + return self._job_id + + @property + def local_pixel_tests(self): + return self._IsLocalRun() + + @property + def local_png_directory(self): + return self._local_png_directory + + @property + def no_luci_auth(self): + return self._no_luci_auth + + @property + def patchset(self): + return self._patchset + + @property + def bypass_skia_gold_functionality(self): + return self._bypass_skia_gold_functionality + + @staticmethod + def _GetGitOriginMasterHeadSha1(): + raise NotImplementedError() + + def _GetGitRevision(self): + if not self._git_revision: + # Automated tests should always pass the revision, so assume we're on + # a workstation and try to get the local origin/master HEAD. + if not self._IsLocalRun(): + raise RuntimeError( + '--git-revision was not passed when running on a bot') + revision = self._GetGitOriginMasterHeadSha1() + if not revision or len(revision) != 40: + raise RuntimeError( + '--git-revision not passed and unable to determine from git') + self._git_revision = revision + return self._git_revision + + def _IsLocalRun(self): + if self._local_pixel_tests is None: + # Look for the presence of the SWARMING_SERVER environment variable as a + # heuristic to determine whether we're running on a workstation or a bot. + # This should always be set on swarming, but would be strange to be set on + # a workstation. + self._local_pixel_tests = 'SWARMING_SERVER' not in os.environ + if self._local_pixel_tests: + logging.warning( + 'Automatically determined that test is running on a workstation') + else: + logging.warning( + 'Automatically determined that test is running on a bot') + return self._local_pixel_tests + + def _InitializeProperties(self, args): + if hasattr(args, 'local_pixel_tests'): + # If not set, will be automatically determined later if needed. + self._local_pixel_tests = args.local_pixel_tests + + if hasattr(args, 'skia_gold_local_png_write_directory'): + self._local_png_directory = args.skia_gold_local_png_write_directory + + if hasattr(args, 'no_luci_auth'): + self._no_luci_auth = args.no_luci_auth + + if hasattr(args, 'bypass_skia_gold_functionality'): + self._bypass_skia_gold_functionality = args.bypass_skia_gold_functionality + + if hasattr(args, 'code_review_system'): + self._code_review_system = args.code_review_system + + if hasattr(args, 'continuous_integration_system'): + self._continuous_integration_system = args.continuous_integration_system + + # Will be automatically determined later if needed. + if not hasattr(args, 'git_revision') or not args.git_revision: + return + self._git_revision = args.git_revision + + # Only expected on tryjob runs. + if not hasattr(args, 'gerrit_issue') or not args.gerrit_issue: + return + self._issue = args.gerrit_issue + if not hasattr(args, 'gerrit_patchset') or not args.gerrit_patchset: + raise RuntimeError( + '--gerrit-issue passed, but --gerrit-patchset not passed.') + self._patchset = args.gerrit_patchset + if not hasattr(args, 'buildbucket_id') or not args.buildbucket_id: + raise RuntimeError( + '--gerrit-issue passed, but --buildbucket-id not passed.') + self._job_id = args.buildbucket_id diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties_unittest.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties_unittest.py new file mode 100755 index 0000000000..ddbac3277d --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_properties_unittest.py @@ -0,0 +1,187 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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=protected-access + +import os +import sys +import unittest + +if sys.version_info[0] == 2: + import mock +else: + import unittest.mock as mock + +from skia_gold_common import skia_gold_properties +from skia_gold_common import unittest_utils + +createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs + + +class SkiaGoldPropertiesInitializationTest(unittest.TestCase): + """Tests that SkiaGoldProperties initializes (or doesn't) when expected.""" + + def verifySkiaGoldProperties(self, instance, expected): + self.assertEqual(instance._local_pixel_tests, + expected.get('local_pixel_tests')) + self.assertEqual(instance._no_luci_auth, expected.get('no_luci_auth')) + self.assertEqual(instance._code_review_system, + expected.get('code_review_system')) + self.assertEqual(instance._continuous_integration_system, + expected.get('continuous_integration_system')) + self.assertEqual(instance._git_revision, expected.get('git_revision')) + self.assertEqual(instance._issue, expected.get('gerrit_issue')) + self.assertEqual(instance._patchset, expected.get('gerrit_patchset')) + self.assertEqual(instance._job_id, expected.get('buildbucket_id')) + self.assertEqual(instance._bypass_skia_gold_functionality, + expected.get('bypass_skia_gold_functionality')) + + def test_initializeSkiaGoldAttributes_unsetLocal(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {}) + + def test_initializeSkiaGoldAttributes_explicitLocal(self): + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'local_pixel_tests': True}) + + def test_initializeSkiaGoldAttributes_explicitNonLocal(self): + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'local_pixel_tests': False}) + + def test_initializeSkiaGoldAttributes_explicitNoLuciAuth(self): + args = createSkiaGoldArgs(no_luci_auth=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'no_luci_auth': True}) + + def test_initializeSkiaGoldAttributes_explicitCrs(self): + args = createSkiaGoldArgs(code_review_system='foo') + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'code_review_system': 'foo'}) + + def test_initializeSkiaGoldAttributes_explicitCis(self): + args = createSkiaGoldArgs(continuous_integration_system='foo') + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'continuous_integration_system': 'foo'}) + + def test_initializeSkiaGoldAttributes_bypassExplicitTrue(self): + args = createSkiaGoldArgs(bypass_skia_gold_functionality=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'bypass_skia_gold_functionality': True}) + + def test_initializeSkiaGoldAttributes_explicitGitRevision(self): + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {'git_revision': 'a'}) + + def test_initializeSkiaGoldAttributes_tryjobArgsIgnoredWithoutRevision(self): + args = createSkiaGoldArgs(gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties(sgp, {}) + + def test_initializeSkiaGoldAttributes_tryjobArgs(self): + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.verifySkiaGoldProperties( + sgp, { + 'git_revision': 'a', + 'gerrit_issue': 1, + 'gerrit_patchset': 2, + 'buildbucket_id': 3 + }) + + def test_initializeSkiaGoldAttributes_tryjobMissingPatchset(self): + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + buildbucket_id=3) + with self.assertRaises(RuntimeError): + skia_gold_properties.SkiaGoldProperties(args) + + def test_initializeSkiaGoldAttributes_tryjobMissingBuildbucket(self): + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + gerrit_patchset=2) + with self.assertRaises(RuntimeError): + skia_gold_properties.SkiaGoldProperties(args) + + +class SkiaGoldPropertiesCalculationTest(unittest.TestCase): + """Tests that SkiaGoldProperties properly calculates certain properties.""" + + def testLocalPixelTests_determineTrue(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + with mock.patch.dict(os.environ, {}, clear=True): + self.assertTrue(sgp.local_pixel_tests) + + def testLocalPixelTests_determineFalse(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + with mock.patch.dict(os.environ, {'SWARMING_SERVER': ''}, clear=True): + self.assertFalse(sgp.local_pixel_tests) + + def testIsTryjobRun_noIssue(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.assertFalse(sgp.IsTryjobRun()) + + def testIsTryjobRun_issue(self): + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.assertTrue(sgp.IsTryjobRun()) + + def testGetGitRevision_revisionSet(self): + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + self.assertEqual(sgp.git_revision, 'a') + + def testGetGitRevision_findValidRevision(self): + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + with mock.patch.object(skia_gold_properties.SkiaGoldProperties, + '_GetGitOriginMasterHeadSha1') as patched_head: + expected = 'a' * 40 + patched_head.return_value = expected + self.assertEqual(sgp.git_revision, expected) + # Should be cached. + self.assertEqual(sgp._git_revision, expected) + + def testGetGitRevision_noExplicitOnBot(self): + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + with self.assertRaises(RuntimeError): + _ = sgp.git_revision + + def testGetGitRevision_findEmptyRevision(self): + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + with mock.patch.object(skia_gold_properties.SkiaGoldProperties, + '_GetGitOriginMasterHeadSha1') as patched_head: + patched_head.return_value = '' + with self.assertRaises(RuntimeError): + _ = sgp.git_revision + + def testGetGitRevision_findMalformedRevision(self): + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + with mock.patch.object(skia_gold_properties.SkiaGoldProperties, + '_GetGitOriginMasterHeadSha1') as patched_head: + patched_head.return_value = 'a' * 39 + with self.assertRaises(RuntimeError): + _ = sgp.git_revision + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_session.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session.py new file mode 100644 index 0000000000..7e69a238cd --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session.py @@ -0,0 +1,552 @@ +# Copyright 2020 The Chromium 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 interacting with the Skia Gold image diffing service.""" + +import logging +import os +import shutil +import sys +import tempfile +import time + +CHROMIUM_SRC = os.path.realpath( + os.path.join(os.path.dirname(__file__), '..', '..')) + +GOLDCTL_BINARY = os.path.join(CHROMIUM_SRC, 'tools', 'skia_goldctl') +if sys.platform == 'win32': + GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'win', 'goldctl') + '.exe' +elif sys.platform == 'darwin': + GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'mac', 'goldctl') +else: + GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'linux', 'goldctl') + + +class SkiaGoldSession(object): + class StatusCodes(object): + """Status codes for RunComparison.""" + SUCCESS = 0 + AUTH_FAILURE = 1 + INIT_FAILURE = 2 + COMPARISON_FAILURE_REMOTE = 3 + COMPARISON_FAILURE_LOCAL = 4 + LOCAL_DIFF_FAILURE = 5 + NO_OUTPUT_MANAGER = 6 + + class ComparisonResults(object): + """Struct-like object for storing results of an image comparison.""" + + def __init__(self): + self.public_triage_link = None + self.internal_triage_link = None + self.triage_link_omission_reason = None + self.local_diff_given_image = None + self.local_diff_closest_image = None + self.local_diff_diff_image = None + + def __init__(self, + working_dir, + gold_properties, + keys_file, + corpus, + instance, + bucket=None): + """Abstract class to handle all aspects of image comparison via Skia Gold. + + A single SkiaGoldSession is valid for a single instance/corpus/keys_file + combination. + + Args: + working_dir: The directory to store config files, etc. + gold_properties: A skia_gold_properties.SkiaGoldProperties instance for + the current test run. + keys_file: A path to a JSON file containing various comparison config data + such as corpus and debug information like the hardware/software + configuration the images will be produced on. + corpus: The corpus that images that will be compared belong to. + instance: The name of the Skia Gold instance to interact with. + bucket: Overrides the formulaic Google Storage bucket name generated by + goldctl + """ + self._working_dir = working_dir + self._gold_properties = gold_properties + self._corpus = corpus + self._instance = instance + self._bucket = bucket + self._local_png_directory = (self._gold_properties.local_png_directory + or tempfile.mkdtemp()) + self._triage_link_file = tempfile.NamedTemporaryFile(suffix='.txt', + dir=working_dir, + delete=False).name + # A map of image name (string) to ComparisonResults for that image. + self._comparison_results = {} + self._authenticated = False + self._initialized = False + + # Copy the given keys file to the working directory in case it ends up + # getting deleted before we try to use it. + self._keys_file = os.path.join(working_dir, 'gold_keys.json') + shutil.copy(keys_file, self._keys_file) + + def RunComparison(self, + name, + png_file, + output_manager, + inexact_matching_args=None, + use_luci=True, + optional_keys=None, + force_dryrun=False): + """Helper method to run all steps to compare a produced image. + + Handles authentication, itnitialization, comparison, and, if necessary, + local diffing. + + Args: + name: The name of the image being compared. + png_file: A path to a PNG file containing the image to be compared. + output_manager: An output manager to use to store diff links. The + argument's type depends on what type a subclasses' _StoreDiffLinks + implementation expects. Can be None even if _StoreDiffLinks expects + a valid input, but will fail if it ever actually needs to be used. + inexact_matching_args: A list of strings containing extra command line + arguments to pass to Gold for inexact matching. Can be omitted to use + exact matching. + use_luci: If true, authentication will use the service account provided by + the LUCI context. If false, will attempt to use whatever is set up in + gsutil, which is only supported for local runs. + optional_keys: A dict containing optional key/value pairs to pass to Gold + for this comparison. Optional keys are keys unrelated to the + configuration the image was produced on, e.g. a comment or whether + Gold should treat the image as ignored. + force_dryrun: A boolean denoting whether dryrun should be forced on + regardless of whether this is a local comparison or not. + + Returns: + A tuple (status, error). |status| is a value from + SkiaGoldSession.StatusCodes signifying the result of the comparison. + |error| is an error message describing the status if not successful. + """ + auth_rc, auth_stdout = self.Authenticate(use_luci=use_luci) + if auth_rc: + return self.StatusCodes.AUTH_FAILURE, auth_stdout + + init_rc, init_stdout = self.Initialize() + if init_rc: + return self.StatusCodes.INIT_FAILURE, init_stdout + + compare_rc, compare_stdout = self.Compare( + name=name, + png_file=png_file, + inexact_matching_args=inexact_matching_args, + optional_keys=optional_keys, + force_dryrun=force_dryrun) + if not compare_rc: + return self.StatusCodes.SUCCESS, None + + logging.error('Gold comparison failed: %s', compare_stdout) + if not self._gold_properties.local_pixel_tests: + return self.StatusCodes.COMPARISON_FAILURE_REMOTE, compare_stdout + + if not output_manager: + return (self.StatusCodes.NO_OUTPUT_MANAGER, + 'No output manager for local diff images') + + diff_rc, diff_stdout = self.Diff(name=name, + png_file=png_file, + output_manager=output_manager) + if diff_rc: + return self.StatusCodes.LOCAL_DIFF_FAILURE, diff_stdout + return self.StatusCodes.COMPARISON_FAILURE_LOCAL, compare_stdout + + def Authenticate(self, use_luci=True): + """Authenticates with Skia Gold for this session. + + Args: + use_luci: If true, authentication will use the service account provided + by the LUCI context. If false, will attempt to use whatever is set up + in gsutil, which is only supported for local runs. + + Returns: + A tuple (return_code, output). |return_code| is the return code of the + authentication process. |output| is the stdout + stderr of the + authentication process. + """ + if self._authenticated: + return 0, None + if self._gold_properties.bypass_skia_gold_functionality: + logging.warning('Not actually authenticating with Gold due to ' + '--bypass-skia-gold-functionality being present.') + return 0, None + + auth_cmd = [GOLDCTL_BINARY, 'auth', '--work-dir', self._working_dir] + if use_luci: + auth_cmd.append('--luci') + elif not self._gold_properties.local_pixel_tests: + raise RuntimeError( + 'Cannot authenticate to Skia Gold with use_luci=False unless running ' + 'local pixel tests') + + rc, stdout = self._RunCmdForRcAndOutput(auth_cmd) + if rc == 0: + self._authenticated = True + return rc, stdout + + def Initialize(self): + """Initializes the working directory if necessary. + + This can technically be skipped if the same information is passed to the + command used for image comparison, but that is less efficient under the + hood. Doing it that way effectively requires an initialization for every + comparison (~250 ms) instead of once at the beginning. + + Returns: + A tuple (return_code, output). |return_code| is the return code of the + initialization process. |output| is the stdout + stderr of the + initialization process. + """ + if self._initialized: + return 0, None + if self._gold_properties.bypass_skia_gold_functionality: + logging.warning('Not actually initializing Gold due to ' + '--bypass-skia-gold-functionality being present.') + return 0, None + + init_cmd = [ + GOLDCTL_BINARY, + 'imgtest', + 'init', + '--passfail', + '--instance', + self._instance, + '--corpus', + self._corpus, + '--keys-file', + self._keys_file, + '--work-dir', + self._working_dir, + '--failure-file', + self._triage_link_file, + '--commit', + self._gold_properties.git_revision, + ] + if self._bucket: + init_cmd.extend(['--bucket', self._bucket]) + if self._gold_properties.IsTryjobRun(): + init_cmd.extend([ + '--issue', + str(self._gold_properties.issue), + '--patchset', + str(self._gold_properties.patchset), + '--jobid', + str(self._gold_properties.job_id), + '--crs', + str(self._gold_properties.code_review_system), + '--cis', + str(self._gold_properties.continuous_integration_system), + ]) + + rc, stdout = self._RunCmdForRcAndOutput(init_cmd) + if rc == 0: + self._initialized = True + return rc, stdout + + def Compare(self, + name, + png_file, + inexact_matching_args=None, + optional_keys=None, + force_dryrun=False): + """Compares the given image to images known to Gold. + + Triage links can later be retrieved using GetTriageLinks(). + + Args: + name: The name of the image being compared. + png_file: A path to a PNG file containing the image to be compared. + inexact_matching_args: A list of strings containing extra command line + arguments to pass to Gold for inexact matching. Can be omitted to use + exact matching. + optional_keys: A dict containing optional key/value pairs to pass to Gold + for this comparison. Optional keys are keys unrelated to the + configuration the image was produced on, e.g. a comment or whether + Gold should treat the image as ignored. + force_dryrun: A boolean denoting whether dryrun should be forced on + regardless of whether this is a local comparison or not. + + Returns: + A tuple (return_code, output). |return_code| is the return code of the + comparison process. |output| is the stdout + stderr of the comparison + process. + """ + if self._gold_properties.bypass_skia_gold_functionality: + logging.warning('Not actually comparing with Gold due to ' + '--bypass-skia-gold-functionality being present.') + return 0, None + + compare_cmd = [ + GOLDCTL_BINARY, + 'imgtest', + 'add', + '--test-name', + name, + '--png-file', + png_file, + '--work-dir', + self._working_dir, + ] + if self._gold_properties.local_pixel_tests or force_dryrun: + compare_cmd.append('--dryrun') + if inexact_matching_args: + logging.info('Using inexact matching arguments for image %s: %s', name, + inexact_matching_args) + compare_cmd.extend(inexact_matching_args) + + optional_keys = optional_keys or {} + for k, v in optional_keys.items(): + compare_cmd.extend([ + '--add-test-optional-key', + '%s:%s' % (k, v), + ]) + + self._ClearTriageLinkFile() + rc, stdout = self._RunCmdForRcAndOutput(compare_cmd) + + self._comparison_results[name] = self.ComparisonResults() + if rc == 0: + self._comparison_results[name].triage_link_omission_reason = ( + 'Comparison succeeded, no triage link') + elif self._gold_properties.IsTryjobRun(): + cl_triage_link = ('https://{instance}-gold.skia.org/cl/{crs}/{issue}') + cl_triage_link = cl_triage_link.format( + instance=self._instance, + crs=self._gold_properties.code_review_system, + issue=self._gold_properties.issue) + self._comparison_results[name].internal_triage_link = cl_triage_link + self._comparison_results[name].public_triage_link =\ + self._GeneratePublicTriageLink(cl_triage_link) + else: + try: + with open(self._triage_link_file) as tlf: + triage_link = tlf.read().strip() + if not triage_link: + self._comparison_results[name].triage_link_omission_reason = ( + 'Gold did not provide a triage link. This is likely a bug on ' + "Gold's end.") + self._comparison_results[name].internal_triage_link = None + self._comparison_results[name].public_triage_link = None + else: + self._comparison_results[name].internal_triage_link = triage_link + self._comparison_results[name].public_triage_link =\ + self._GeneratePublicTriageLink(triage_link) + except IOError: + self._comparison_results[name].triage_link_omission_reason = ( + 'Failed to read triage link from file') + return rc, stdout + + def Diff(self, name, png_file, output_manager): + """Performs a local image diff against the closest known positive in Gold. + + This is used for running tests on a workstation, where uploading data to + Gold for ingestion is not allowed, and thus the web UI is not available. + + Image links can later be retrieved using Get*ImageLink(). + + Args: + name: The name of the image being compared. + png_file: The path to a PNG file containing the image to be diffed. + output_manager: An output manager to use to store diff links. The + argument's type depends on what type a subclasses' _StoreDiffLinks + implementation expects. + + Returns: + A tuple (return_code, output). |return_code| is the return code of the + diff process. |output| is the stdout + stderr of the diff process. + """ + # Instead of returning that everything is okay and putting in dummy links, + # just fail since this should only be called when running locally and + # --bypass-skia-gold-functionality is only meant for use on the bots. + if self._gold_properties.bypass_skia_gold_functionality: + raise RuntimeError( + '--bypass-skia-gold-functionality is not supported when running ' + 'tests locally.') + + output_dir = self._CreateDiffOutputDir(name) + # TODO(skbug.com/10611): Remove this temporary work dir and instead just use + # self._working_dir once `goldctl diff` stops clobbering the auth files in + # the provided work directory. + temp_work_dir = tempfile.mkdtemp() + # shutil.copytree() fails if the destination already exists, so use a + # subdirectory of the temporary directory. + temp_work_dir = os.path.join(temp_work_dir, 'diff_work_dir') + try: + shutil.copytree(self._working_dir, temp_work_dir) + diff_cmd = [ + GOLDCTL_BINARY, + 'diff', + '--corpus', + self._corpus, + '--instance', + self._GetDiffGoldInstance(), + '--input', + png_file, + '--test', + name, + '--work-dir', + temp_work_dir, + '--out-dir', + output_dir, + ] + rc, stdout = self._RunCmdForRcAndOutput(diff_cmd) + self._StoreDiffLinks(name, output_manager, output_dir) + return rc, stdout + finally: + shutil.rmtree(os.path.realpath(os.path.join(temp_work_dir, '..'))) + + def GetTriageLinks(self, name): + """Gets the triage links for the given image. + + Args: + name: The name of the image to retrieve the triage link for. + + Returns: + A tuple (public, internal). |public| is a string containing the triage + link for the public Gold instance if it is available, or None if it is not + available for some reason. |internal| is the same as |public|, but + containing a link to the internal Gold instance. The reason for links not + being available can be retrieved using GetTriageLinkOmissionReason. + """ + comparison_results = self._comparison_results.get(name, + self.ComparisonResults()) + return (comparison_results.public_triage_link, + comparison_results.internal_triage_link) + + def GetTriageLinkOmissionReason(self, name): + """Gets the reason why a triage link is not available for an image. + + Args: + name: The name of the image whose triage link does not exist. + + Returns: + A string containing the reason why a triage link is not available. + """ + if name not in self._comparison_results: + return 'No image comparison performed for %s' % name + results = self._comparison_results[name] + # This method should not be called if there is a valid triage link. + assert results.public_triage_link is None + assert results.internal_triage_link is None + if results.triage_link_omission_reason: + return results.triage_link_omission_reason + if results.local_diff_given_image: + return 'Gold only used to do a local image diff' + raise RuntimeError( + 'Somehow have a ComparisonResults instance for %s that should not ' + 'exist' % name) + + def GetGivenImageLink(self, name): + """Gets the link to the given image used for local diffing. + + Args: + name: The name of the image that was diffed. + + Returns: + A string containing the link to where the image is saved, or None if it + does not exist. + """ + assert name in self._comparison_results + return self._comparison_results[name].local_diff_given_image + + def GetClosestImageLink(self, name): + """Gets the link to the closest known image used for local diffing. + + Args: + name: The name of the image that was diffed. + + Returns: + A string containing the link to where the image is saved, or None if it + does not exist. + """ + assert name in self._comparison_results + return self._comparison_results[name].local_diff_closest_image + + def GetDiffImageLink(self, name): + """Gets the link to the diff between the given and closest images. + + Args: + name: The name of the image that was diffed. + + Returns: + A string containing the link to where the image is saved, or None if it + does not exist. + """ + assert name in self._comparison_results + return self._comparison_results[name].local_diff_diff_image + + def _GeneratePublicTriageLink(self, internal_link): + """Generates a public triage link given an internal one. + + Args: + internal_link: A string containing a triage link pointing to an internal + Gold instance. + + Returns: + A string containing a triage link pointing to the public mirror of the + link pointed to by |internal_link|. + """ + return internal_link.replace('%s-gold' % self._instance, + '%s-public-gold' % self._instance) + + def _ClearTriageLinkFile(self): + """Clears the contents of the triage link file. + + This should be done before every comparison since goldctl appends to the + file instead of overwriting its contents, which results in multiple triage + links getting concatenated together if there are multiple failures. + """ + open(self._triage_link_file, 'w').close() + + def _CreateDiffOutputDir(self, _): + # We don't use self._local_png_directory here since we want it to be + # automatically cleaned up with the working directory. Any subclasses that + # want to keep it around can override this method. + return tempfile.mkdtemp(dir=self._working_dir) + + def _GetDiffGoldInstance(self): + """Gets the Skia Gold instance to use for the Diff step. + + This can differ based on how a particular instance is set up, mainly + depending on whether it is set up for internal results or not. + """ + # TODO(skbug.com/10610): Decide whether to use the public or + # non-public instance once authentication is fixed for the non-public + # instance. + return str(self._instance) + '-public' + + def _StoreDiffLinks(self, image_name, output_manager, output_dir): + """Stores the local diff files as links. + + The ComparisonResults entry for |image_name| should have its *_image fields + filled after this unless corresponding images were not found on disk. + + Args: + image_name: A string containing the name of the image that was diffed. + output_manager: An output manager used used to surface links to users, + if necessary. The expected argument type depends on each subclasses' + implementation of this method. + output_dir: A string containing the path to the directory where diff + output image files where saved. + """ + raise NotImplementedError() + + @staticmethod + def _RunCmdForRcAndOutput(cmd): + """Runs |cmd| and returns its returncode and output. + + Args: + cmd: A list containing the command line to run. + + Returns: + A tuple (rc, output), where, |rc| is the returncode of the command and + |output| is the stdout + stderr of the command. + """ + raise NotImplementedError() diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager.py new file mode 100644 index 0000000000..d4166e1dc2 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager.py @@ -0,0 +1,121 @@ +# Copyright 2020 The Chromium 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 managing multiple SkiaGoldSessions.""" + +import json +import tempfile + + +class SkiaGoldSessionManager(object): + def __init__(self, working_dir, gold_properties): + """Abstract class to manage one or more skia_gold_session.SkiaGoldSessions. + + A separate session is required for each instance/corpus/keys_file + combination, so this class will lazily create them as necessary. + + Args: + working_dir: The working directory under which each individual + SkiaGoldSessions' working directory will be created. + gold_properties: A SkiaGoldProperties instance that will be used to create + any SkiaGoldSessions. + """ + self._working_dir = working_dir + self._gold_properties = gold_properties + self._sessions = {} + + def GetSkiaGoldSession(self, + keys_input, + corpus=None, + instance=None, + bucket=None): + """Gets a SkiaGoldSession for the given arguments. + + Lazily creates one if necessary. + + Args: + keys_input: A way of retrieving various comparison config data such as + corpus and debug information like the hardware/software configuration + the image was produced on. Can be either a dict or a filepath to a + file containing JSON to read. + corpus: A string containing the corpus the session is for. If None, the + corpus will be determined using available information. + instance: The name of the Skia Gold instance to interact with. If None, + will use whatever default the subclass sets. + bucket: Overrides the formulaic Google Storage bucket name generated by + goldctl + """ + instance = instance or self._GetDefaultInstance() + keys_dict = _GetKeysAsDict(keys_input) + keys_string = json.dumps(keys_dict, sort_keys=True) + if corpus is None: + corpus = keys_dict.get('source_type', instance) + # Use the string representation of the keys JSON as a proxy for a hash since + # dicts themselves are not hashable. + session = self._sessions.setdefault(instance, + {}).setdefault(corpus, {}).setdefault( + keys_string, None) + if not session: + working_dir = tempfile.mkdtemp(dir=self._working_dir) + keys_file = _GetKeysAsJson(keys_input, working_dir) + session = self.GetSessionClass()(working_dir, self._gold_properties, + keys_file, corpus, instance, bucket) + self._sessions[instance][corpus][keys_string] = session + return session + + @staticmethod + def _GetDefaultInstance(): + """Gets the default Skia Gold instance. + + Returns: + A string containing the default instance. + """ + return 'chrome' + + @staticmethod + def GetSessionClass(): + """Gets the SkiaGoldSession class to use for session creation. + + Returns: + A reference to a SkiaGoldSession class. + """ + raise NotImplementedError + + +def _GetKeysAsDict(keys_input): + """Converts |keys_input| into a dictionary. + + Args: + keys_input: A dictionary or a string pointing to a JSON file. The contents + of either should be Skia Gold config data. + + Returns: + A dictionary containing the Skia Gold config data. + """ + if isinstance(keys_input, dict): + return keys_input + assert isinstance(keys_input, str) + with open(keys_input) as f: + return json.load(f) + + +def _GetKeysAsJson(keys_input, session_work_dir): + """Converts |keys_input| into a JSON file on disk. + + Args: + keys_input: A dictionary or a string pointing to a JSON file. The contents + of either should be Skia Gold config data. + + Returns: + A string containing a filepath to a JSON file with containing |keys_input|'s + data. + """ + if isinstance(keys_input, str): + return keys_input + assert isinstance(keys_input, dict) + keys_file = tempfile.NamedTemporaryFile(suffix='.json', + dir=session_work_dir, + delete=False).name + with open(keys_file, 'w') as f: + json.dump(keys_input, f) + return keys_file diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager_unittest.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager_unittest.py new file mode 100755 index 0000000000..286fdf2b97 --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_manager_unittest.py @@ -0,0 +1,180 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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=protected-access + +import json +import os +import sys +import tempfile +import unittest + +if sys.version_info[0] == 2: + import mock +else: + import unittest.mock as mock + +from pyfakefs import fake_filesystem_unittest + +from skia_gold_common import skia_gold_properties +from skia_gold_common import skia_gold_session +from skia_gold_common import skia_gold_session_manager +from skia_gold_common import unittest_utils + +createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs + + +class SkiaGoldSessionManagerGetSessionTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSessionManager.GetSkiaGoldSession.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._patcher = mock.patch.object( + skia_gold_session_manager.SkiaGoldSessionManager, 'GetSessionClass') + self._session_class_mock = self._patcher.start() + self._session_class_mock.return_value = skia_gold_session.SkiaGoldSession + self.addCleanup(self._patcher.stop) + + def test_ArgsForwardedToSession(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance') + self.assertTrue(session._keys_file.startswith(self._working_dir)) + self.assertEqual(session._corpus, 'corpus') + self.assertEqual(session._instance, 'instance') + # Make sure the session's working directory is a subdirectory of the + # manager's working directory. + self.assertEqual(os.path.dirname(session._working_dir), self._working_dir) + + def test_corpusFromJson(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session = sgsm.GetSkiaGoldSession({'source_type': 'foobar'}, None, + 'instance') + self.assertTrue(session._keys_file.startswith(self._working_dir)) + self.assertEqual(session._corpus, 'foobar') + self.assertEqual(session._instance, 'instance') + + def test_corpusDefaultsToInstance(self): + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session = sgsm.GetSkiaGoldSession({}, None, 'instance') + self.assertTrue(session._keys_file.startswith(self._working_dir)) + self.assertEqual(session._corpus, 'instance') + self.assertEqual(session._instance, 'instance') + + @mock.patch.object(skia_gold_session_manager.SkiaGoldSessionManager, + '_GetDefaultInstance') + def test_getDefaultInstance(self, default_instance_mock): + default_instance_mock.return_value = 'default' + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session = sgsm.GetSkiaGoldSession({}, None, None) + self.assertTrue(session._keys_file.startswith(self._working_dir)) + self.assertEqual(session._corpus, 'default') + self.assertEqual(session._instance, 'default') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '__init__') + def test_matchingSessionReused(self, session_mock): + session_mock.return_value = None + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session1 = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance') + session2 = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance') + self.assertEqual(session1, session2) + # For some reason, session_mock.assert_called_once() always passes, + # so check the call count directly. + self.assertEqual(session_mock.call_count, 1) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '__init__') + def test_separateSessionsFromKeys(self, session_mock): + session_mock.return_value = None + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session1 = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance') + session2 = sgsm.GetSkiaGoldSession({'something_different': 1}, 'corpus', + 'instance') + self.assertNotEqual(session1, session2) + self.assertEqual(session_mock.call_count, 2) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '__init__') + def test_separateSessionsFromCorpus(self, session_mock): + session_mock.return_value = None + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session1 = sgsm.GetSkiaGoldSession({}, 'corpus1', 'instance') + session2 = sgsm.GetSkiaGoldSession({}, 'corpus2', 'instance') + self.assertNotEqual(session1, session2) + self.assertEqual(session_mock.call_count, 2) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '__init__') + def test_separateSessionsFromInstance(self, session_mock): + session_mock.return_value = None + args = createSkiaGoldArgs() + sgp = skia_gold_properties.SkiaGoldProperties(args) + self._working_dir = tempfile.mkdtemp() + sgsm = skia_gold_session_manager.SkiaGoldSessionManager( + self._working_dir, sgp) + session1 = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance1') + session2 = sgsm.GetSkiaGoldSession({}, 'corpus', 'instance2') + self.assertNotEqual(session1, session2) + self.assertEqual(session_mock.call_count, 2) + + +class SkiaGoldSessionManagerKeyConversionTest(fake_filesystem_unittest.TestCase + ): + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + + def test_getKeysAsDict(self): + keys_dict = {'foo': 'bar'} + keys_file_contents = {'bar': 'baz'} + keys_file = tempfile.NamedTemporaryFile(delete=False).name + with open(keys_file, 'w') as f: + json.dump(keys_file_contents, f) + + self.assertEqual(skia_gold_session_manager._GetKeysAsDict(keys_dict), + keys_dict) + self.assertEqual(skia_gold_session_manager._GetKeysAsDict(keys_file), + keys_file_contents) + with self.assertRaises(AssertionError): + skia_gold_session_manager._GetKeysAsDict(1) + + def test_getKeysAsJson(self): + keys_dict = {'foo': 'bar'} + keys_file_contents = {'bar': 'baz'} + keys_file = tempfile.NamedTemporaryFile(delete=False).name + with open(keys_file, 'w') as f: + json.dump(keys_file_contents, f) + + self.assertEqual(skia_gold_session_manager._GetKeysAsJson(keys_file, None), + keys_file) + keys_dict_as_json = skia_gold_session_manager._GetKeysAsJson( + keys_dict, self._working_dir) + self.assertTrue(keys_dict_as_json.startswith(self._working_dir)) + with open(keys_dict_as_json) as f: + self.assertEqual(json.load(f), keys_dict) + with self.assertRaises(AssertionError): + skia_gold_session_manager._GetKeysAsJson(1, None) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_unittest.py b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_unittest.py new file mode 100755 index 0000000000..15b8a9924c --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/skia_gold_session_unittest.py @@ -0,0 +1,834 @@ +#!/usr/bin/env vpython3 +# Copyright 2020 The Chromium 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=protected-access + +import json +import os +import sys +import tempfile +import unittest + +if sys.version_info[0] == 2: + import mock +else: + import unittest.mock as mock + +from pyfakefs import fake_filesystem_unittest + +from skia_gold_common import skia_gold_properties +from skia_gold_common import skia_gold_session +from skia_gold_common import unittest_utils + +createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs + + +def assertArgWith(test, arg_list, arg, value): + i = arg_list.index(arg) + test.assertEqual(arg_list[i + 1], value) + + +class SkiaGoldSessionRunComparisonTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.RunComparison.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + with open(self._json_keys, 'w') as f: + json.dump({}, f) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_comparisonSuccess(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (0, None) + sgp = skia_gold_properties.SkiaGoldProperties(createSkiaGoldArgs()) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, _ = session.RunComparison(None, None, None) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.SUCCESS) + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_authFailure(self, auth_mock, init_mock, compare_mock, diff_mock): + auth_mock.return_value = (1, 'Auth failed') + sgp = skia_gold_properties.SkiaGoldProperties(createSkiaGoldArgs()) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, None) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.AUTH_FAILURE) + self.assertEqual(error, 'Auth failed') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 0) + self.assertEqual(compare_mock.call_count, 0) + self.assertEqual(diff_mock.call_count, 0) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_initFailure(self, auth_mock, init_mock, compare_mock, diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (1, 'Init failed') + sgp = skia_gold_properties.SkiaGoldProperties(createSkiaGoldArgs()) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, None) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.INIT_FAILURE) + self.assertEqual(error, 'Init failed') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 0) + self.assertEqual(diff_mock.call_count, 0) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_compareFailureRemote(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (1, 'Compare failed') + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, None) + self.assertEqual( + status, + skia_gold_session.SkiaGoldSession.StatusCodes.COMPARISON_FAILURE_REMOTE) + self.assertEqual(error, 'Compare failed') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_compareFailureLocal(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (1, 'Compare failed') + diff_mock.return_value = (0, None) + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, + 'Definitely an output manager') + self.assertEqual( + status, + skia_gold_session.SkiaGoldSession.StatusCodes.COMPARISON_FAILURE_LOCAL) + self.assertEqual(error, 'Compare failed') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 1) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_compareInexactMatching(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (0, None) + diff_mock.return_value = (0, None) + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, _ = session.RunComparison(None, + None, + None, + inexact_matching_args=['--inexact']) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.SUCCESS) + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + compare_mock.assert_called_with(name=None, + png_file=mock.ANY, + inexact_matching_args=['--inexact'], + optional_keys=None, + force_dryrun=False) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_compareOptionalKeys(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (0, None) + diff_mock.return_value = (0, None) + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, _ = session.RunComparison(None, + None, + None, + optional_keys={'foo': 'bar'}) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.SUCCESS) + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + compare_mock.assert_called_with(name=None, + png_file=mock.ANY, + inexact_matching_args=None, + optional_keys={'foo': 'bar'}, + force_dryrun=False) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_compareForceDryrun(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (0, None) + diff_mock.return_value = (0, None) + args = createSkiaGoldArgs(local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, _ = session.RunComparison(None, None, None, force_dryrun=True) + self.assertEqual(status, + skia_gold_session.SkiaGoldSession.StatusCodes.SUCCESS) + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + compare_mock.assert_called_with(name=None, + png_file=mock.ANY, + inexact_matching_args=None, + optional_keys=None, + force_dryrun=True) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_diffFailure(self, auth_mock, init_mock, compare_mock, diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (1, 'Compare failed') + diff_mock.return_value = (1, 'Diff failed') + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, + 'Definitely an output manager') + self.assertEqual( + status, + skia_gold_session.SkiaGoldSession.StatusCodes.LOCAL_DIFF_FAILURE) + self.assertEqual(error, 'Diff failed') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 1) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Diff') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Compare') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Initialize') + @mock.patch.object(skia_gold_session.SkiaGoldSession, 'Authenticate') + def test_noOutputManagerLocal(self, auth_mock, init_mock, compare_mock, + diff_mock): + auth_mock.return_value = (0, None) + init_mock.return_value = (0, None) + compare_mock.return_value = (1, 'Compare failed') + diff_mock.return_value = (0, None) + args = createSkiaGoldArgs(local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + status, error = session.RunComparison(None, None, None) + self.assertEqual( + status, skia_gold_session.SkiaGoldSession.StatusCodes.NO_OUTPUT_MANAGER) + self.assertEqual(error, 'No output manager for local diff images') + self.assertEqual(auth_mock.call_count, 1) + self.assertEqual(compare_mock.call_count, 1) + self.assertEqual(diff_mock.call_count, 0) + + +class SkiaGoldSessionAuthenticateTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.Authenticate.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandOutputReturned(self, cmd_mock): + cmd_mock.return_value = (1, 'Something bad :(') + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, stdout = session.Authenticate() + self.assertEqual(cmd_mock.call_count, 1) + self.assertEqual(rc, 1) + self.assertEqual(stdout, 'Something bad :(') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_bypassSkiaGoldFunctionality(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', + bypass_skia_gold_functionality=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, _ = session.Authenticate() + self.assertEqual(rc, 0) + cmd_mock.assert_not_called() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_shortCircuitAlreadyAuthenticated(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session._authenticated = True + rc, _ = session.Authenticate() + self.assertEqual(rc, 0) + cmd_mock.assert_not_called() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_successSetsShortCircuit(self, cmd_mock): + cmd_mock.return_value = (0, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + self.assertFalse(session._authenticated) + rc, _ = session.Authenticate() + self.assertEqual(rc, 0) + self.assertTrue(session._authenticated) + cmd_mock.assert_called_once() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_failureDoesNotSetShortCircuit(self, cmd_mock): + cmd_mock.return_value = (1, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + self.assertFalse(session._authenticated) + rc, _ = session.Authenticate() + self.assertEqual(rc, 1) + self.assertFalse(session._authenticated) + cmd_mock.assert_called_once() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithUseLuciTrue(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Authenticate(use_luci=True) + self.assertIn('--luci', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithUseLuciFalse(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Authenticate(use_luci=False) + self.assertNotIn('--luci', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithUseLuciFalseNotLocal(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + with self.assertRaises(RuntimeError): + session.Authenticate(use_luci=False) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandCommonArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Authenticate() + call_args = cmd_mock.call_args[0][0] + self.assertIn('auth', call_args) + assertArgWith(self, call_args, '--work-dir', self._working_dir) + + +class SkiaGoldSessionInitializeTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.Initialize.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_bypassSkiaGoldFunctionality(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', + bypass_skia_gold_functionality=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, _ = session.Initialize() + self.assertEqual(rc, 0) + cmd_mock.assert_not_called() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_shortCircuitAlreadyInitialized(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session._initialized = True + rc, _ = session.Initialize() + self.assertEqual(rc, 0) + cmd_mock.assert_not_called() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_successSetsShortCircuit(self, cmd_mock): + cmd_mock.return_value = (0, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + self.assertFalse(session._initialized) + rc, _ = session.Initialize() + self.assertEqual(rc, 0) + self.assertTrue(session._initialized) + cmd_mock.assert_called_once() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_failureDoesNotSetShortCircuit(self, cmd_mock): + cmd_mock.return_value = (1, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + self.assertFalse(session._initialized) + rc, _ = session.Initialize() + self.assertEqual(rc, 1) + self.assertFalse(session._initialized) + cmd_mock.assert_called_once() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandCommonArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, + sgp, + self._json_keys, + 'corpus', + instance='instance', + bucket='bucket') + session.Initialize() + call_args = cmd_mock.call_args[0][0] + self.assertIn('imgtest', call_args) + self.assertIn('init', call_args) + self.assertIn('--passfail', call_args) + assertArgWith(self, call_args, '--instance', 'instance') + assertArgWith(self, call_args, '--bucket', 'bucket') + assertArgWith(self, call_args, '--corpus', 'corpus') + # The keys file should have been copied to the working directory. + assertArgWith(self, call_args, '--keys-file', + os.path.join(self._working_dir, 'gold_keys.json')) + assertArgWith(self, call_args, '--work-dir', self._working_dir) + assertArgWith(self, call_args, '--failure-file', session._triage_link_file) + assertArgWith(self, call_args, '--commit', 'a') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandTryjobArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Initialize() + call_args = cmd_mock.call_args[0][0] + assertArgWith(self, call_args, '--issue', '1') + assertArgWith(self, call_args, '--patchset', '2') + assertArgWith(self, call_args, '--jobid', '3') + assertArgWith(self, call_args, '--crs', 'gerrit') + assertArgWith(self, call_args, '--cis', 'buildbucket') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandTryjobArgsNonDefaultCrs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(code_review_system='foo', + git_revision='a', + gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Initialize() + call_args = cmd_mock.call_args[0][0] + assertArgWith(self, call_args, '--issue', '1') + assertArgWith(self, call_args, '--patchset', '2') + assertArgWith(self, call_args, '--jobid', '3') + assertArgWith(self, call_args, '--crs', 'foo') + assertArgWith(self, call_args, '--cis', 'buildbucket') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandTryjobArgsMissing(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Initialize() + call_args = cmd_mock.call_args[0][0] + self.assertNotIn('--issue', call_args) + self.assertNotIn('--patchset', call_args) + self.assertNotIn('--jobid', call_args) + self.assertNotIn('--crs', call_args) + self.assertNotIn('--cis', call_args) + + +class SkiaGoldSessionCompareTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.Compare.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandOutputReturned(self, cmd_mock): + cmd_mock.return_value = (1, 'Something bad :(') + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, stdout = session.Compare(None, None) + self.assertEqual(cmd_mock.call_count, 1) + self.assertEqual(rc, 1) + self.assertEqual(stdout, 'Something bad :(') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_bypassSkiaGoldFunctionality(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', + bypass_skia_gold_functionality=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, _ = session.Compare(None, None) + self.assertEqual(rc, 0) + cmd_mock.assert_not_called() + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithLocalPixelTestsTrue(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Compare(None, None) + self.assertIn('--dryrun', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithForceDryrunTrue(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Compare(None, None, force_dryrun=True) + self.assertIn('--dryrun', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithLocalPixelTestsFalse(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Compare(None, None) + self.assertNotIn('--dryrun', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandWithInexactArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Compare(None, None, inexact_matching_args=['--inexact', 'foobar']) + self.assertIn('--inexact', cmd_mock.call_args[0][0]) + self.assertIn('foobar', cmd_mock.call_args[0][0]) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandCommonArgs(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, + sgp, + self._json_keys, + 'corpus', + instance='instance') + session.Compare('name', 'png_file') + call_args = cmd_mock.call_args[0][0] + self.assertIn('imgtest', call_args) + self.assertIn('add', call_args) + assertArgWith(self, call_args, '--test-name', 'name') + assertArgWith(self, call_args, '--png-file', 'png_file') + assertArgWith(self, call_args, '--work-dir', self._working_dir) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_noLinkOnSuccess(self, cmd_mock): + cmd_mock.return_value = (0, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, _ = session.Compare('name', 'png_file') + self.assertEqual(rc, 0) + comparison_result = session._comparison_results['name'] + self.assertEqual(comparison_result.public_triage_link, None) + self.assertEqual(comparison_result.internal_triage_link, None) + self.assertNotEqual(comparison_result.triage_link_omission_reason, None) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_clLinkOnTrybot(self, cmd_mock): + cmd_mock.return_value = (1, None) + args = createSkiaGoldArgs(git_revision='a', + gerrit_issue=1, + gerrit_patchset=2, + buildbucket_id=3) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, + sgp, + self._json_keys, + None, + instance='instance') + rc, _ = session.Compare('name', 'png_file') + self.assertEqual(rc, 1) + comparison_result = session._comparison_results['name'] + self.assertNotEqual(comparison_result.public_triage_link, None) + self.assertNotEqual(comparison_result.internal_triage_link, None) + internal_link = 'https://instance-gold.skia.org/cl/gerrit/1' + public_link = 'https://instance-public-gold.skia.org/cl/gerrit/1' + self.assertEqual(comparison_result.internal_triage_link, internal_link) + self.assertEqual(comparison_result.public_triage_link, public_link) + self.assertEqual(comparison_result.triage_link_omission_reason, None) + self.assertEqual(session.GetTriageLinks('name'), + (public_link, internal_link)) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_individualLinkOnCi(self, cmd_mock): + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, + sgp, + self._json_keys, + None, + instance='foobar') + + internal_link = 'foobar-gold.skia.org' + public_link = 'foobar-public-gold.skia.org' + + def WriteTriageLinkFile(_): + with open(session._triage_link_file, 'w') as f: + f.write(internal_link) + return (1, None) + + cmd_mock.side_effect = WriteTriageLinkFile + rc, _ = session.Compare('name', 'png_file') + self.assertEqual(rc, 1) + comparison_result = session._comparison_results['name'] + self.assertNotEqual(comparison_result.public_triage_link, None) + self.assertNotEqual(comparison_result.internal_triage_link, None) + self.assertEqual(comparison_result.internal_triage_link, internal_link) + self.assertEqual(comparison_result.public_triage_link, public_link) + self.assertEqual(comparison_result.triage_link_omission_reason, None) + self.assertEqual(session.GetTriageLinks('name'), + (public_link, internal_link)) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_validOmissionOnMissingLink(self, cmd_mock): + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + + def WriteTriageLinkFile(_): + with open(session._triage_link_file, 'w'): + pass + return (1, None) + + cmd_mock.side_effect = WriteTriageLinkFile + rc, _ = session.Compare('name', 'png_file') + self.assertEqual(rc, 1) + comparison_result = session._comparison_results['name'] + self.assertEqual(comparison_result.public_triage_link, None) + self.assertEqual(comparison_result.internal_triage_link, None) + self.assertIn('Gold did not provide a triage link', + comparison_result.triage_link_omission_reason) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_validOmissionOnIoError(self, cmd_mock): + cmd_mock.return_value = (1, None) + args = createSkiaGoldArgs(git_revision='a') + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + + def DeleteTriageLinkFile(_): + os.remove(session._triage_link_file) + return (1, None) + + cmd_mock.side_effect = DeleteTriageLinkFile + rc, _ = session.Compare('name', 'png_file') + self.assertEqual(rc, 1) + comparison_result = session._comparison_results['name'] + self.assertEqual(comparison_result.public_triage_link, None) + self.assertEqual(comparison_result.internal_triage_link, None) + self.assertNotEqual(comparison_result.triage_link_omission_reason, None) + self.assertIn('Failed to read', + comparison_result.triage_link_omission_reason) + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_optionalKeysPassedToGoldctl(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + session.Compare(None, None, optional_keys={'foo': 'bar'}) + assertArgWith(self, cmd_mock.call_args[0][0], '--add-test-optional-key', + 'foo:bar') + + +class SkiaGoldSessionDiffTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.Diff.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + self._json_keys = tempfile.NamedTemporaryFile(delete=False).name + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_StoreDiffLinks') + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_commandOutputReturned(self, cmd_mock, _): + cmd_mock.return_value = (1, 'Something bad :(') + args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + rc, stdout = session.Diff(None, None, None) + self.assertEqual(cmd_mock.call_count, 1) + self.assertEqual(rc, 1) + self.assertEqual(stdout, 'Something bad :(') + + @mock.patch.object(skia_gold_session.SkiaGoldSession, '_RunCmdForRcAndOutput') + def test_bypassSkiaGoldFunctionality(self, cmd_mock): + cmd_mock.return_value = (None, None) + args = createSkiaGoldArgs(git_revision='a', + bypass_skia_gold_functionality=True) + sgp = skia_gold_properties.SkiaGoldProperties(args) + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + self._json_keys, None, None) + with self.assertRaises(RuntimeError): + session.Diff(None, None, None) + + +class SkiaGoldSessionTriageLinkOmissionTest(fake_filesystem_unittest.TestCase): + """Tests the functionality of SkiaGoldSession.GetTriageLinkOmissionReason.""" + + def setUp(self): + self.setUpPyfakefs() + self._working_dir = tempfile.mkdtemp() + + def _CreateSession(self): + sgp = skia_gold_properties.SkiaGoldProperties(createSkiaGoldArgs()) + json_keys = tempfile.NamedTemporaryFile(delete=False).name + session = skia_gold_session.SkiaGoldSession(self._working_dir, sgp, + json_keys, None, None) + session._comparison_results = { + 'foo': skia_gold_session.SkiaGoldSession.ComparisonResults(), + } + return session + + def test_noComparison(self): + session = self._CreateSession() + session._comparison_results = {} + reason = session.GetTriageLinkOmissionReason('foo') + self.assertEqual(reason, 'No image comparison performed for foo') + + def test_validReason(self): + session = self._CreateSession() + session._comparison_results['foo'].triage_link_omission_reason = 'bar' + reason = session.GetTriageLinkOmissionReason('foo') + self.assertEqual(reason, 'bar') + + def test_onlyLocal(self): + session = self._CreateSession() + session._comparison_results['foo'].local_diff_given_image = 'bar' + reason = session.GetTriageLinkOmissionReason('foo') + self.assertEqual(reason, 'Gold only used to do a local image diff') + + def test_onlyWithoutTriageLink(self): + session = self._CreateSession() + comparison_result = session._comparison_results['foo'] + comparison_result.public_triage_link = 'bar' + with self.assertRaises(AssertionError): + session.GetTriageLinkOmissionReason('foo') + comparison_result.public_triage_link = None + comparison_result.internal_triage_link = 'bar' + with self.assertRaises(AssertionError): + session.GetTriageLinkOmissionReason('foo') + + def test_resultsShouldNotExist(self): + session = self._CreateSession() + with self.assertRaises(RuntimeError): + session.GetTriageLinkOmissionReason('foo') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/skia_gold_common/unittest_utils.py b/third_party/libwebrtc/build/skia_gold_common/unittest_utils.py new file mode 100644 index 0000000000..c4f77a14df --- /dev/null +++ b/third_party/libwebrtc/build/skia_gold_common/unittest_utils.py @@ -0,0 +1,36 @@ +# Copyright 2020 The Chromium 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 methods for Skia Gold functionality unittests.""" + +import collections + +_SkiaGoldArgs = collections.namedtuple('_SkiaGoldArgs', [ + 'local_pixel_tests', + 'no_luci_auth', + 'code_review_system', + 'continuous_integration_system', + 'git_revision', + 'gerrit_issue', + 'gerrit_patchset', + 'buildbucket_id', + 'bypass_skia_gold_functionality', + 'skia_gold_local_png_write_directory', +]) + + +def createSkiaGoldArgs(local_pixel_tests=None, + no_luci_auth=None, + code_review_system=None, + continuous_integration_system=None, + git_revision=None, + gerrit_issue=None, + gerrit_patchset=None, + buildbucket_id=None, + bypass_skia_gold_functionality=None, + skia_gold_local_png_write_directory=None): + return _SkiaGoldArgs(local_pixel_tests, no_luci_auth, code_review_system, + continuous_integration_system, git_revision, + gerrit_issue, gerrit_patchset, buildbucket_id, + bypass_skia_gold_functionality, + skia_gold_local_png_write_directory) diff --git a/third_party/libwebrtc/build/symlink.gni b/third_party/libwebrtc/build/symlink.gni new file mode 100644 index 0000000000..dcaa5e5045 --- /dev/null +++ b/third_party/libwebrtc/build/symlink.gni @@ -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. + +# Creates a symlink. +# Args: +# source: Path to link to. +# output: Where to create the symlink. +template("symlink") { + action(target_name) { + forward_variables_from(invoker, + [ + "data_deps", + "deps", + "testonly", + "visibility", + ]) + outputs = [ invoker.output ] + script = "//build/symlink.py" + args = [ + "-f", + rebase_path(invoker.source, get_path_info(invoker.output, "dir")), + rebase_path(invoker.output, root_build_dir), + ] + } +} + +# Creates a symlink from root_build_dir/target_name to |binary_label|. This rule +# is meant to be used within if (current_toolchain == default_toolchain) blocks +# and point to targets in the non-default toolchain. +# Note that for executables, using a copy (as opposed to a symlink) does not +# work when is_component_build=true, since dependent libraries are found via +# relative location. +# +# Args: +# binary_label: Target that builds the file to symlink to. e.g.: +# ":$target_name($host_toolchain)". +# binary_output_name: The output_name set by the binary_label target +# (if applicable). +# output_name: Where to create the symlink +# (default="$root_out_dir/$binary_output_name"). +# +# Example: +# if (current_toolchain == host_toolchain) { +# executable("foo") { ... } +# } else if (current_toolchain == default_toolchain) { +# binary_symlink("foo") { +# binary_label = ":foo($host_toolchain)" +# } +# } +template("binary_symlink") { + symlink(target_name) { + forward_variables_from(invoker, + [ + "output", + "testonly", + "visibility", + ]) + deps = [ invoker.binary_label ] + data_deps = [ invoker.binary_label ] + if (defined(invoker.data_deps)) { + data_deps += invoker.data_deps + } + + _out_dir = get_label_info(invoker.binary_label, "root_out_dir") + if (defined(invoker.binary_output_name)) { + _name = invoker.binary_output_name + } else { + _name = get_label_info(invoker.binary_label, "name") + } + source = "$_out_dir/$_name" + + _output_name = _name + if (defined(invoker.output_name)) { + _output_name = invoker.output_name + } + output = "$root_out_dir/$_output_name" + } +} diff --git a/third_party/libwebrtc/build/symlink.py b/third_party/libwebrtc/build/symlink.py new file mode 100755 index 0000000000..0f90696a65 --- /dev/null +++ b/third_party/libwebrtc/build/symlink.py @@ -0,0 +1,91 @@ +#!/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. + +description = """ +Make a symlink and optionally touch a file (to handle dependencies). +""" +usage = "%prog [options] source[ source ...] linkname" +epilog = """\ +A symlink to source is created at linkname. If multiple sources are specified, +then linkname is assumed to be a directory, and will contain all the links to +the sources (basenames identical to their source). + +On Windows, this will use hard links (mklink /H) to avoid requiring elevation. +This means that if the original is deleted and replaced, the link will still +have the old contents. +""" + +import errno +import optparse +import os.path +import shutil +import subprocess +import sys + + +def Main(argv): + parser = optparse.OptionParser(usage=usage, description=description, + epilog=epilog) + parser.add_option('-f', '--force', action='store_true') + parser.add_option('--touch') + + options, args = parser.parse_args(argv[1:]) + if len(args) < 2: + parser.error('at least two arguments required.') + + target = args[-1] + sources = args[:-1] + for s in sources: + t = os.path.join(target, os.path.basename(s)) + if len(sources) == 1 and not os.path.isdir(target): + t = target + t = os.path.expanduser(t) + if os.path.realpath(t) == os.path.realpath(s): + continue + try: + # N.B. Python 2.x does not have os.symlink for Windows. + # Python 3 has os.symlink for Windows, but requires either the admin- + # granted privilege SeCreateSymbolicLinkPrivilege or, as of Windows 10 + # 1703, that Developer Mode be enabled. Hard links and junctions do not + # require any extra privileges to create. + if os.name == 'nt': + # mklink does not tolerate /-delimited path names. + t = t.replace('/', '\\') + s = s.replace('/', '\\') + # N.B. This tool only handles file hardlinks, not directory junctions. + subprocess.check_output(['cmd.exe', '/c', 'mklink', '/H', t, s], + stderr=subprocess.STDOUT) + else: + os.symlink(s, t) + except OSError as e: + if e.errno == errno.EEXIST and options.force: + if os.path.isdir(t): + shutil.rmtree(t, ignore_errors=True) + else: + os.remove(t) + os.symlink(s, t) + else: + raise + except subprocess.CalledProcessError as e: + # Since subprocess.check_output does not return an easily checked error + # number, in the 'force' case always assume it is 'file already exists' + # and retry. + if options.force: + if os.path.isdir(t): + shutil.rmtree(t, ignore_errors=True) + else: + os.remove(t) + subprocess.check_output(e.cmd, stderr=subprocess.STDOUT) + else: + raise + + + if options.touch: + with open(options.touch, 'w'): + pass + + +if __name__ == '__main__': + sys.exit(Main(sys.argv)) diff --git a/third_party/libwebrtc/build/timestamp.gni b/third_party/libwebrtc/build/timestamp.gni new file mode 100644 index 0000000000..4d805c0942 --- /dev/null +++ b/third_party/libwebrtc/build/timestamp.gni @@ -0,0 +1,34 @@ +# Copyright 2018 The Chromium 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 build_timestamp variable. + +import("//build/util/lastchange.gni") + +declare_args() { + # This should be the filename of a script that prints a single line + # containing an integer that's a unix timestamp in UTC. + # This timestamp is used as build time and will be compiled into + # other code. + # + # This argument may look unused. Before removing please check with the + # chromecast team to see if they still use it internally. + compute_build_timestamp = "compute_build_timestamp.py" +} + +if (is_official_build) { + official_name = "official" +} else { + official_name = "default" +} + +# This will return a timestamp that's different each day (official builds) +# or each month (regular builds). Just rely on gn rerunning due to other +# changes to keep this up to date. (Bots run gn on each build, and for devs +# the timestamp being 100% accurate doesn't matter.) +# See compute_build_timestamp.py for tradeoffs for picking the timestamp. +build_timestamp = exec_script(compute_build_timestamp, + [ official_name ], + "trim string", + [ lastchange_file ]) diff --git a/third_party/libwebrtc/build/toolchain/BUILD.gn b/third_party/libwebrtc/build/toolchain/BUILD.gn new file mode 100644 index 0000000000..6cf8f1b403 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/BUILD.gn @@ -0,0 +1,26 @@ +# 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. + +import("//build/toolchain/concurrent_links.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") + +declare_args() { + # Pool for non goma tasks. + action_pool_depth = -1 +} + +if (current_toolchain == default_toolchain) { + if (action_pool_depth == -1 || (use_goma || use_rbe)) { + action_pool_depth = exec_script("get_cpu_count.py", [], "value") + } + + pool("link_pool") { + depth = concurrent_links + } + + pool("action_pool") { + depth = action_pool_depth + } +} diff --git a/third_party/libwebrtc/build/toolchain/OWNERS b/third_party/libwebrtc/build/toolchain/OWNERS new file mode 100644 index 0000000000..d7012d39ad --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/OWNERS @@ -0,0 +1,5 @@ +dpranke@google.com +scottmg@chromium.org + +# Code Coverage. +per-file *code_coverage*=liaoyuke@chromium.org diff --git a/third_party/libwebrtc/build/toolchain/aix/BUILD.gn b/third_party/libwebrtc/build/toolchain/aix/BUILD.gn new file mode 100644 index 0000000000..0390a0c42c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/aix/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright 2017 The Chromium 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/toolchain/gcc_toolchain.gni") + +gcc_toolchain("ppc64") { + cc = "gcc" + cxx = "g++" + + readelf = "readelf" + nm = "nm" + ar = "ar" + ld = cxx + + toolchain_args = { + target_cpu = "ppc64" + current_os = "aix" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} diff --git a/third_party/libwebrtc/build/toolchain/android/BUILD.gn b/third_party/libwebrtc/build/toolchain/android/BUILD.gn new file mode 100644 index 0000000000..a611a09f46 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/android/BUILD.gn @@ -0,0 +1,156 @@ +# 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("//build/config/android/config.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/ozone.gni") +import("//build/config/sysroot.gni") # Imports android/config.gni. +import("//build/toolchain/gcc_toolchain.gni") + +declare_args() { + # Whether unstripped binaries, i.e. compiled with debug symbols, should be + # considered runtime_deps rather than stripped ones. + android_unstripped_runtime_outputs = true +} + +# The Android clang toolchains share most of the same parameters, so we have this +# wrapper around gcc_toolchain to avoid duplication of logic. +# +# Parameters: +# - binary_prefix +# Prefix of compiler executables. +template("android_clang_toolchain") { + gcc_toolchain(target_name) { + assert(defined(invoker.toolchain_args), + "toolchain_args must be defined for android_clang_toolchain()") + + # Android toolchains need to declare .dwp files as outputs, so need to know + # the value of "use_debug_fission" when defining them. + # The derived value of "use_debug_fission" varies based on current_os, but + # toolchain definitions are evaluated under the default toolchain. + # Rather than computing the value under current_os="android", just disable + # it if target_os != "android". + _use_debug_fission = use_debug_fission && target_os == "android" + + toolchain_args = { + forward_variables_from(invoker.toolchain_args, "*") + current_os = "android" + use_debug_fission = _use_debug_fission + } + + # Output linker map files for binary size analysis. + enable_linker_map = true + + _android_tool_prefix = + "$android_toolchain_root/bin/${invoker.binary_prefix}-" + + # The tools should be run relative to the build dir. + _tool_prefix = rebase_path("$_android_tool_prefix", root_build_dir) + + _prefix = rebase_path("$clang_base_path/bin", root_build_dir) + cc = "$_prefix/clang" + cxx = "$_prefix/clang++" + ar = "$_prefix/llvm-ar" + ld = cxx + readelf = _tool_prefix + "readelf" + nm = "$_prefix/llvm-nm" + strip = "$_prefix/llvm-strip" + if (_use_debug_fission) { + dwp = _tool_prefix + "dwp" + } + use_unstripped_as_runtime_outputs = android_unstripped_runtime_outputs + + # Don't use .cr.so for loadable_modules since they are always loaded via + # absolute path. + loadable_module_extension = ".so" + } +} + +android_clang_toolchain("android_clang_x86") { + binary_prefix = "i686-linux-android" + toolchain_args = { + target_cpu = "x86" + + # We lack the libclang_rt.profile library for x86 and x86_64, so we cannot + # link any binaries that are generated with coverage instrumentation. + # Therefore we need to turn off 'use_clang_coverage' for this toolchain. + # TODO(crbug.com/865376) + use_clang_coverage = false + + # This turns off all of the LaCrOS-specific flags. A LaCrOS related build + # may use |ash_clang_x64| or |lacros_clang_x64| toolchain, which are + # chromeos toolchains, to build Ash-Chrome or Lacros-Chrome in a + # subdirectory, and because chromeos toolchain uses android toolchain, which + # eventually resulted in that android toolchains being used inside a LaCrOS + # build. + also_build_ash_chrome = false + also_build_lacros_chrome = false + chromeos_is_browser_only = false + ozone_platform = "" + ozone_platform_wayland = false + } +} + +android_clang_toolchain("android_clang_arm") { + binary_prefix = "arm-linux-androideabi" + toolchain_args = { + target_cpu = "arm" + } +} + +android_clang_toolchain("android_clang_mipsel") { + binary_prefix = "mipsel-linux-android" + toolchain_args = { + target_cpu = "mipsel" + } +} + +android_clang_toolchain("android_clang_x64") { + binary_prefix = "x86_64-linux-android" + toolchain_args = { + target_cpu = "x64" + + # We lack the libclang_rt.profile library for x86 and x86_64, so we cannot + # link any binaries that are generated with coverage instrumentation. + # Therefore we need to turn off 'use_clang_coverage' for this toolchain. + # TODO(crbug.com/865376) + use_clang_coverage = false + + # This turns off all of the LaCrOS-specific flags. A LaCrOS related build + # may use |ash_clang_x64| or |lacros_clang_x64| toolchain, which are + # chromeos toolchains, to build Ash-Chrome or Lacros-Chrome in a + # subdirectory, and because chromeos toolchain uses android toolchain, which + # eventually resulted in that android toolchains being used inside a LaCrOS + # build. + also_build_ash_chrome = false + also_build_lacros_chrome = false + chromeos_is_browser_only = false + ozone_platform = "" + ozone_platform_wayland = false + } +} + +android_clang_toolchain("android_clang_arm64") { + binary_prefix = "aarch64-linux-android" + toolchain_args = { + target_cpu = "arm64" + } +} + +android_clang_toolchain("android_clang_arm64_hwasan") { + binary_prefix = "aarch64-linux-android" + toolchain_args = { + target_cpu = "arm64" + is_hwasan = true + android64_ndk_api_level = 29 + } +} + +android_clang_toolchain("android_clang_mips64el") { + binary_prefix = "mips64el-linux-android" + toolchain_args = { + target_cpu = "mips64el" + } +} diff --git a/third_party/libwebrtc/build/toolchain/android/OWNERS b/third_party/libwebrtc/build/toolchain/android/OWNERS new file mode 100644 index 0000000000..a74cfbe228 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/android/OWNERS @@ -0,0 +1 @@ +file://build/android/OWNERS diff --git a/third_party/libwebrtc/build/toolchain/apple/BUILD.gn b/third_party/libwebrtc/build/toolchain/apple/BUILD.gn new file mode 100644 index 0000000000..6f074fd2d8 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 2021 The Chromium 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/toolchain/concurrent_links.gni") + +declare_args() { + # Reduce the number of tasks using the copy_bundle_data and compile_xcassets + # tools as they can cause lots of I/O contention when invoking ninja with a + # large number of parallel jobs (e.g. when using distributed build like goma). + bundle_pool_depth = -1 +} + +if (current_toolchain == default_toolchain) { + pool("bundle_pool") { + if (bundle_pool_depth == -1) { + depth = concurrent_links + } else { + depth = bundle_pool_depth + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/apple/OWNERS b/third_party/libwebrtc/build/toolchain/apple/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/toolchain/apple/filter_libtool.py b/third_party/libwebrtc/build/toolchain/apple/filter_libtool.py new file mode 100644 index 0000000000..44c1c4400d --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/filter_libtool.py @@ -0,0 +1,52 @@ +# 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. + +from __future__ import print_function + +import os +import re +import subprocess +import sys + +# This script executes libool and filters out logspam lines like: +# '/path/to/libtool: file: foo.o has no symbols' + +SUPPRESSED_PATTERNS = [ + re.compile(v) for v in [ + r'^.*libtool: (?:for architecture: \S* )?file: .* has no symbols$', + # Xcode 11 spelling of the "empty archive" warning. + # TODO(thakis): Remove once we require Xcode 12. + r'^.*libtool: warning for library: .* the table of contents is empty ' \ + r'\(no object file members in the library define global symbols\)$', + # Xcode 12 spelling of the "empty archive" warning. + r'^warning: .*libtool: archive library: .* ' \ + r'the table of contents is empty ', + r'\(no object file members in the library define global symbols\)$', + r'^.*libtool: warning same member name \(\S*\) in output file used ' \ + r'for input files: \S* and: \S* \(due to use of basename, ' \ + r'truncation, blank padding or duplicate input files\)$', + ] +] + + +def ShouldSuppressLine(line): + """Returns whether the line should be filtered out.""" + for pattern in SUPPRESSED_PATTERNS: + if pattern.match(line): + return True + return False + + +def Main(cmd_list): + env = os.environ.copy() + libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) + _, err = libtoolout.communicate() + for line in err.decode('UTF-8').splitlines(): + if not ShouldSuppressLine(line): + print(line, file=sys.stderr) + return libtoolout.returncode + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/toolchain/apple/get_tool_mtime.py b/third_party/libwebrtc/build/toolchain/apple/get_tool_mtime.py new file mode 100644 index 0000000000..ff0254c635 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/get_tool_mtime.py @@ -0,0 +1,19 @@ +# 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. + +from __future__ import print_function + +import os +import sys + +# Usage: python get_tool_mtime.py path/to/file1.py path/to/file2.py +# +# Prints a GN scope with the variable name being the basename sans-extension +# and the value being the file modification time. A variable is emitted for +# each file argument on the command line. + +if __name__ == '__main__': + for f in sys.argv[1:]: + variable = os.path.splitext(os.path.basename(f))[0] + print('%s = %d' % (variable, os.path.getmtime(f))) diff --git a/third_party/libwebrtc/build/toolchain/apple/linker_driver.py b/third_party/libwebrtc/build/toolchain/apple/linker_driver.py new file mode 100755 index 0000000000..c21e18a0fb --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/linker_driver.py @@ -0,0 +1,306 @@ +#!/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. + +import os +import os.path +import shutil +import subprocess +import sys + +# On mac, the values of these globals are modified when parsing -Wcrl, flags. On +# ios, the script uses the defaults. +DSYMUTIL_INVOKE = ['xcrun', 'dsymutil'] +STRIP_INVOKE = ['xcrun', 'strip'] + +# Setting this flag will emit a deterministic binary by stripping dates from the +# N_OSO field. +DETERMINISTIC_FLAG = '--deterministic' + +# The linker_driver.py is responsible for forwarding a linker invocation to +# the compiler driver, while processing special arguments itself. +# +# Usage: linker_driver.py clang++ main.o -L. -llib -o prog -Wcrl,dsym,out +# +# On Mac, the logical step of linking is handled by three discrete tools to +# perform the image link, debug info link, and strip. The linker_driver.py +# combines these three steps into a single tool. +# +# The command passed to the linker_driver.py should be the compiler driver +# invocation for the linker. It is first invoked unaltered (except for the +# removal of the special driver arguments, described below). Then the driver +# performs additional actions, based on these arguments: +# +# -Wcrl,dsym, +# After invoking the linker, this will run `dsymutil` on the linker's +# output, producing a dSYM bundle, stored at dsym_path_prefix. As an +# example, if the linker driver were invoked with: +# "... -o out/gn/obj/foo/libbar.dylib ... -Wcrl,dsym,out/gn ..." +# The resulting dSYM would be out/gn/libbar.dylib.dSYM/. +# +# -Wcrl,dsymutilpath, +# Sets the path to the dsymutil to run with -Wcrl,dsym, in which case +# `xcrun` is not used to invoke it. +# +# -Wcrl,unstripped, +# After invoking the linker, and before strip, this will save a copy of +# the unstripped linker output in the directory unstripped_path_prefix. +# +# -Wcrl,strip, +# After invoking the linker, and optionally dsymutil, this will run +# the strip command on the linker's output. strip_arguments are +# comma-separated arguments to be passed to the strip command. +# +# -Wcrl,strippath, +# Sets the path to the strip to run with -Wcrl,strip, in which case +# `xcrun` is not used to invoke it. + + +def Main(args): + """Main function for the linker driver. Separates out the arguments for + the main compiler driver and the linker driver, then invokes all the + required tools. + + Args: + args: list of string, Arguments to the script. + """ + + if len(args) < 2: + raise RuntimeError("Usage: linker_driver.py [linker-invocation]") + + # Collect arguments to the linker driver (this script) and remove them from + # the arguments being passed to the compiler driver. + linker_driver_actions = {} + compiler_driver_args = [] + deterministic = False + for arg in args[1:]: + if arg.startswith(_LINKER_DRIVER_ARG_PREFIX): + # Convert driver actions into a map of name => lambda to invoke. + driver_action = ProcessLinkerDriverArg(arg) + assert driver_action[0] not in linker_driver_actions + linker_driver_actions[driver_action[0]] = driver_action[1] + elif arg == DETERMINISTIC_FLAG: + deterministic = True + else: + compiler_driver_args.append(arg) + + linker_driver_outputs = [_FindLinkerOutput(compiler_driver_args)] + + try: + # Zero the mtime in OSO fields for deterministic builds. + # https://crbug.com/330262. + env = os.environ.copy() + if deterministic: + env['ZERO_AR_DATE'] = '1' + # Run the linker by invoking the compiler driver. + subprocess.check_call(compiler_driver_args, env=env) + + # Run the linker driver actions, in the order specified by the actions list. + for action in _LINKER_DRIVER_ACTIONS: + name = action[0] + if name in linker_driver_actions: + linker_driver_outputs += linker_driver_actions[name](args) + except: + # If a linker driver action failed, remove all the outputs to make the + # build step atomic. + map(_RemovePath, linker_driver_outputs) + + # Re-report the original failure. + raise + + +def ProcessLinkerDriverArg(arg): + """Processes a linker driver argument and returns a tuple containing the + name and unary lambda to invoke for that linker driver action. + + Args: + arg: string, The linker driver argument. + + Returns: + A 2-tuple: + 0: The driver action name, as in _LINKER_DRIVER_ACTIONS. + 1: An 1-ary lambda that takes the full list of arguments passed to + Main(). The lambda should call the linker driver action that + corresponds to the argument and return a list of outputs from the + action. + """ + if not arg.startswith(_LINKER_DRIVER_ARG_PREFIX): + raise ValueError('%s is not a linker driver argument' % (arg, )) + + sub_arg = arg[len(_LINKER_DRIVER_ARG_PREFIX):] + + for driver_action in _LINKER_DRIVER_ACTIONS: + (name, action) = driver_action + if sub_arg.startswith(name): + return (name, lambda full_args: action(sub_arg[len(name):], full_args)) + + raise ValueError('Unknown linker driver argument: %s' % (arg, )) + + +def RunDsymUtil(dsym_path_prefix, full_args): + """Linker driver action for -Wcrl,dsym,. Invokes dsymutil + on the linker's output and produces a dsym file at |dsym_file| path. + + Args: + dsym_path_prefix: string, The path at which the dsymutil output should be + located. + full_args: list of string, Full argument list for the linker driver. + + Returns: + list of string, Build step outputs. + """ + if not len(dsym_path_prefix): + raise ValueError('Unspecified dSYM output file') + + linker_out = _FindLinkerOutput(full_args) + base = os.path.basename(linker_out) + dsym_out = os.path.join(dsym_path_prefix, base + '.dSYM') + + # Remove old dSYMs before invoking dsymutil. + _RemovePath(dsym_out) + + tools_paths = _FindToolsPaths(full_args) + if os.environ.get('PATH'): + tools_paths.append(os.environ['PATH']) + dsymutil_env = os.environ.copy() + dsymutil_env['PATH'] = ':'.join(tools_paths) + subprocess.check_call(DSYMUTIL_INVOKE + ['-o', dsym_out, linker_out], + env=dsymutil_env) + return [dsym_out] + + +def SetDsymutilPath(dsymutil_path, full_args): + """Linker driver action for -Wcrl,dsymutilpath,. + + Sets the invocation command for dsymutil, which allows the caller to specify + an alternate dsymutil. This action is always processed before the RunDsymUtil + action. + + Args: + dsymutil_path: string, The path to the dsymutil binary to run + full_args: list of string, Full argument list for the linker driver. + + Returns: + No output - this step is run purely for its side-effect. + """ + global DSYMUTIL_INVOKE + DSYMUTIL_INVOKE = [dsymutil_path] + return [] + + +def RunSaveUnstripped(unstripped_path_prefix, full_args): + """Linker driver action for -Wcrl,unstripped,. Copies + the linker output to |unstripped_path_prefix| before stripping. + + Args: + unstripped_path_prefix: string, The path at which the unstripped output + should be located. + full_args: list of string, Full argument list for the linker driver. + + Returns: + list of string, Build step outputs. + """ + if not len(unstripped_path_prefix): + raise ValueError('Unspecified unstripped output file') + + linker_out = _FindLinkerOutput(full_args) + base = os.path.basename(linker_out) + unstripped_out = os.path.join(unstripped_path_prefix, base + '.unstripped') + + shutil.copyfile(linker_out, unstripped_out) + return [unstripped_out] + + +def RunStrip(strip_args_string, full_args): + """Linker driver action for -Wcrl,strip,. + + Args: + strip_args_string: string, Comma-separated arguments for `strip`. + full_args: list of string, Full arguments for the linker driver. + + Returns: + list of string, Build step outputs. + """ + strip_command = list(STRIP_INVOKE) + if len(strip_args_string) > 0: + strip_command += strip_args_string.split(',') + strip_command.append(_FindLinkerOutput(full_args)) + subprocess.check_call(strip_command) + return [] + + +def SetStripPath(strip_path, full_args): + """Linker driver action for -Wcrl,strippath,. + + Sets the invocation command for strip, which allows the caller to specify + an alternate strip. This action is always processed before the RunStrip + action. + + Args: + strip_path: string, The path to the strip binary to run + full_args: list of string, Full argument list for the linker driver. + + Returns: + No output - this step is run purely for its side-effect. + """ + global STRIP_INVOKE + STRIP_INVOKE = [strip_path] + return [] + + +def _FindLinkerOutput(full_args): + """Finds the output of the linker by looking for the output flag in its + argument list. As this is a required linker argument, raises an error if it + cannot be found. + """ + # The linker_driver.py script may be used to wrap either the compiler linker + # (uses -o to configure the output) or lipo (uses -output to configure the + # output). Since wrapping the compiler linker is the most likely possibility + # use try/except and fallback to checking for -output if -o is not found. + try: + output_flag_index = full_args.index('-o') + except ValueError: + output_flag_index = full_args.index('-output') + return full_args[output_flag_index + 1] + + +def _FindToolsPaths(full_args): + """Finds all paths where the script should look for additional tools.""" + paths = [] + for idx, arg in enumerate(full_args): + if arg in ['-B', '--prefix']: + paths.append(full_args[idx + 1]) + elif arg.startswith('-B'): + paths.append(arg[2:]) + elif arg.startswith('--prefix='): + paths.append(arg[9:]) + return paths + + +def _RemovePath(path): + """Removes the file or directory at |path| if it exists.""" + if os.path.exists(path): + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.unlink(path) + + +_LINKER_DRIVER_ARG_PREFIX = '-Wcrl,' +"""List of linker driver actions. The sort order of this list affects the +order in which the actions are invoked. The first item in the tuple is the +argument's -Wcrl, and the second is the function to invoke. +""" +_LINKER_DRIVER_ACTIONS = [ + ('dsymutilpath,', SetDsymutilPath), + ('dsym,', RunDsymUtil), + ('unstripped,', RunSaveUnstripped), + ('strippath,', SetStripPath), + ('strip,', RunStrip), +] + +if __name__ == '__main__': + Main(sys.argv) + sys.exit(0) diff --git a/third_party/libwebrtc/build/toolchain/apple/toolchain.gni b/third_party/libwebrtc/build/toolchain/apple/toolchain.gni new file mode 100644 index 0000000000..f914d8d400 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/apple/toolchain.gni @@ -0,0 +1,611 @@ +# 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. + +# TODO(brettw) Use "gcc_toolchain.gni" like the Linux toolchains. This requires +# some enhancements since the commands on Mac are slightly different than on +# Linux. + +import("//build/config/apple/symbols.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/rust.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") +import("//build/toolchain/toolchain.gni") + +assert((target_os == "ios" && host_os == "mac") || host_os != "win") + +declare_args() { + # This controls whether whole module optimization is enabled when building + # Swift modules. If enabled, the compiler will compile the module as one + # unit, generating just one single object file. Otherwise, it will generate + # one object file per .swift file. If unspecified, will default to "true" + # for official builds, and "false" for all other builds. + swift_whole_module_optimization = -1 +} + +if (swift_whole_module_optimization == -1) { + swift_whole_module_optimization = is_official_build +} + +# When implementing tools using Python scripts, a TOOL_VERSION=N env +# variable is placed in front of the command. The N should be incremented +# whenever the script is changed, so that the build system rebuilds all +# edges that utilize the script. Ideally this should be changed to use +# proper input-dirty checking, but that could be expensive. Instead, use a +# script to get the tool scripts' modification time to use as the version. +# This won't cause a re-generation of GN files when the tool script changes +# but it will cause edges to be marked as dirty if the ninja files are +# regenerated. See https://crbug.com/619083 for details. A proper fix +# would be to have inputs to tools (https://crbug.com/621119). +tool_versions = + exec_script("get_tool_mtime.py", + rebase_path([ + "//build/toolchain/apple/filter_libtool.py", + "//build/toolchain/apple/linker_driver.py", + "//build/toolchain/ios/compile_xcassets.py", + "//build/toolchain/ios/swiftc.py", + ], + root_build_dir), + "trim scope") + +# Shared toolchain definition. Invocations should set current_os to set the +# build args in this definition. +template("apple_toolchain") { + toolchain(target_name) { + # When invoking this toolchain not as the default one, these args will be + # passed to the build. They are ignored when this is the default toolchain. + assert(defined(invoker.toolchain_args), + "Toolchains must declare toolchain_args") + toolchain_args = { + # Populate toolchain args from the invoker. + forward_variables_from(invoker.toolchain_args, "*") + + # The host toolchain value computed by the default toolchain's setup + # needs to be passed through unchanged to all secondary toolchains to + # ensure that it's always the same, regardless of the values that may be + # set on those toolchains. + host_toolchain = host_toolchain + } + + # When the invoker has explicitly overridden use_goma or cc_wrapper in the + # toolchain args, use those values, otherwise default to the global one. + # This works because the only reasonable override that toolchains might + # supply for these values are to force-disable them. + if (defined(toolchain_args.use_remoteexec)) { + toolchain_uses_remoteexec = toolchain_args.use_remoteexec + } else { + toolchain_uses_remoteexec = use_remoteexec + } + if (defined(toolchain_args.use_goma)) { + toolchain_uses_goma = toolchain_args.use_goma + } else { + toolchain_uses_goma = use_goma + } + if (defined(toolchain_args.cc_wrapper)) { + toolchain_cc_wrapper = toolchain_args.cc_wrapper + } else { + toolchain_cc_wrapper = cc_wrapper + } + assert(!(toolchain_uses_remoteexec && toolchain_uses_goma), + "Goma and re-client can't be used together.") + assert(!(toolchain_cc_wrapper != "" && toolchain_uses_remoteexec), + "re-client and cc_wrapper can't be used together.") + assert(!(toolchain_cc_wrapper != "" && toolchain_uses_goma), + "Goma and cc_wrapper can't be used together.") + + if (defined(toolchain_args.use_lld)) { + toolchain_uses_lld = toolchain_args.use_lld + } else { + toolchain_uses_lld = use_lld + } + if (defined(toolchain_args.use_xcode_clang)) { + toolchain_uses_xcode_clang = toolchain_args.use_xcode_clang + } else { + toolchain_uses_xcode_clang = use_xcode_clang + } + + # Supports building with the version of clang shipped with Xcode when + # targeting iOS by not respecting clang_base_path. + if (toolchain_uses_xcode_clang) { + prefix = invoker.bin_path + } else { + prefix = rebase_path("$clang_base_path/bin/", root_build_dir) + } + + _cc = "${prefix}clang" + _cxx = "${prefix}clang++" + + swiftmodule_switch = "-Wl,-add_ast_path," + + # Compute the compiler prefix. + if (toolchain_uses_remoteexec) { + if (defined(toolchain_args.rbe_cc_cfg_file)) { + toolchain_rbe_cc_cfg_file = toolchain_args.rbe_cc_cfg_file + } else { + toolchain_rbe_cc_cfg_file = rbe_cc_cfg_file + } + + # C/C++ (clang) rewrapper prefix to use when use_remoteexec is true. + compiler_prefix = "${rbe_bin_dir}/rewrapper -cfg=${toolchain_rbe_cc_cfg_file} -exec_root=${rbe_exec_root} " + } else if (toolchain_uses_goma) { + assert(toolchain_cc_wrapper == "", + "Goma and cc_wrapper can't be used together.") + compiler_prefix = "$goma_dir/gomacc " + if (use_goma_rust) { + rust_compiler_prefix = compiler_prefix + } + } else if (toolchain_cc_wrapper != "") { + compiler_prefix = toolchain_cc_wrapper + " " + } else { + compiler_prefix = "" + } + + cc = compiler_prefix + _cc + cxx = compiler_prefix + _cxx + ld = _cxx + + # Set the explicit search path for clang++ so it uses the right linker + # binary. + if (!toolchain_uses_lld) { + ld += " -B " + invoker.bin_path + } + + if (defined(toolchain_args.coverage_instrumentation_input_file)) { + toolchain_coverage_instrumentation_input_file = + toolchain_args.coverage_instrumentation_input_file + } else { + toolchain_coverage_instrumentation_input_file = + coverage_instrumentation_input_file + } + _use_clang_coverage_wrapper = + toolchain_coverage_instrumentation_input_file != "" + if (_use_clang_coverage_wrapper) { + _coverage_wrapper = + rebase_path("//build/toolchain/clang_code_coverage_wrapper.py", + root_build_dir) + " --files-to-instrument=" + + rebase_path(toolchain_coverage_instrumentation_input_file, + root_build_dir) + " --target-os=" + target_os + cc = "$python_path $_coverage_wrapper ${cc}" + cxx = "$python_path $_coverage_wrapper ${cxx}" + } + + linker_driver = + "TOOL_VERSION=${tool_versions.linker_driver} " + + rebase_path("//build/toolchain/apple/linker_driver.py", root_build_dir) + + # Specify an explicit path for the strip binary. + _strippath = invoker.bin_path + "strip" + linker_driver += " -Wcrl,strippath," + _strippath + + # This makes the linker set timestamps in Mach-O files to 0. + linker_driver += " --deterministic" + + # On iOS, the final applications are assembled using lipo (to support fat + # builds). The correct flags are passed to the linker_driver.py script + # directly during the lipo call. The test is against the target_os because + # there is no need to create .dSYMs for targets compiled for the host. + if (defined(invoker.strip_with_lipo) && invoker.strip_with_lipo) { + _enable_dsyms = false + _save_unstripped_output = false + } else { + _enable_dsyms = enable_dsyms + _save_unstripped_output = save_unstripped_output + } + + # Make these apply to all tools below. + lib_switch = "-l" + lib_dir_switch = "-L" + + # Object files go in this directory. Use label_name instead of + # target_output_name since labels will generally have no spaces and will be + # unique in the directory. + object_subdir = "{{target_out_dir}}/{{label_name}}" + + # If dSYMs are enabled, this flag will be added to the link tools. + if (_enable_dsyms) { + dsym_switch = " -Wcrl,dsym,{{root_out_dir}} " + dsym_switch += "-Wcrl,dsymutilpath," + + rebase_path("//tools/clang/dsymutil/bin/dsymutil", + root_build_dir) + " " + + dsym_output_dir = + "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.dSYM" + dsym_output = [ + "$dsym_output_dir/Contents/Info.plist", + "$dsym_output_dir/Contents/Resources/DWARF/" + + "{{target_output_name}}{{output_extension}}", + ] + } else { + dsym_switch = "" + } + + if (_save_unstripped_output) { + _unstripped_output = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.unstripped" + } + + if (toolchain_has_rust) { + if (!defined(rust_compiler_prefix)) { + rust_compiler_prefix = "" + } + rustc = "$rust_compiler_prefix${rust_prefix}rustc" + + # Ideally, we'd add -Clink-args=\"{{ldflags}}\" to each of the Rust + # tools below which may link (i.e. rust_bin, rust_cdylib, rust_macro). + # However, it seems -fuse-ld=lld causes difficulties. + + tool("rust_staticlib") { + rust_outfile = "{{target_out_dir}}/{{crate_name}}.a" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_rlib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + if (rustc_can_link) { + tool("rust_bin") { + rust_outfile = "{{root_out_dir}}/{{crate_name}}" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_cdylib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_macro") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dylib" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + } + } + + tool("cc") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "CC {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("cxx") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "CXX {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("asm") { + # For GCC we can just use the C compiler to compile assembly. + depfile = "{{output}}.d" + command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "ASM {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("objc") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "OBJC {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("objcxx") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "OBJCXX {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("alink") { + rspfile = "{{output}}.rsp" + + if (!toolchain_uses_lld) { + # Note about -filelist: Apple's linker reads the file list file and + # interprets each newline-separated chunk of text as a file name. It + # doesn't do the things one would expect from the shell like unescaping + # or handling quotes. In contrast, when Ninja finds a file name with + # spaces, it single-quotes them in $inputs_newline as it would normally + # do for command-line arguments. Thus any source names with spaces, or + # label names with spaces (which GN bases the output paths on) will be + # corrupted by this process. Don't use spaces for source files or + # labels. + rspfile_content = "{{inputs_newline}}" + + script = rebase_path("//build/toolchain/apple/filter_libtool.py", + root_build_dir) + + # Specify explicit path for libtool. + libtool = invoker.bin_path + "libtool" + command = "rm -f {{output}} && TOOL_VERSION=${tool_versions.filter_libtool} $python_path $script $libtool -static -D {{arflags}} -o {{output}} -filelist $rspfile" + description = "LIBTOOL-STATIC {{output}}" + } else { + rspfile_content = "{{inputs}}" + ar = "${prefix}llvm-ar" + command = "\"$ar\" {{arflags}} -r -c -s -D {{output}} \"@$rspfile\"" + + # Remove the output file first so that ar doesn't try to modify the + # existing file. + command = "rm -f {{output}} && $command" + description = "AR {{output}}" + } + outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ] + default_output_dir = "{{target_out_dir}}" + default_output_extension = ".a" + output_prefix = "lib" + } + + tool("solink") { + # E.g. "./libfoo.dylib": + dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + rspfile = dylib + ".rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + # These variables are not built into GN but are helpers that implement + # (1) linking to produce a .dylib, (2) extracting the symbols from that + # file to a temporary file, (3) if the temporary file has differences from + # the existing .TOC file, overwrite it, otherwise, don't change it. + # + # As a special case, if the library reexports symbols from other dynamic + # libraries, we always update the .TOC and skip the temporary file and + # diffing steps, since that library always needs to be re-linked. + tocname = dylib + ".TOC" + temporary_tocname = dylib + ".tmp" + + # Use explicit paths to binaries. The binaries present on the default + # search path in /usr/bin are thin wrappers around xcrun, which requires a + # full CommandLineTools or Xcode install, and still may not choose the + # appropriate binary if there are multiple installs. + if (host_os == "mac") { + nm = invoker.bin_path + "nm" + otool = invoker.bin_path + "otool" + } else { + nm = "${prefix}llvm-nm" + otool = "${prefix}llvm-otool" + } + + does_reexport_command = "[ ! -e \"$dylib\" -o ! -e \"$tocname\" ] || $otool -l \"$dylib\" | grep -q LC_REEXPORT_DYLIB" + + link_command = "$linker_driver $ld -shared " + if (is_component_build) { + link_command += " -Wl,-install_name,@rpath/\"{{target_output_name}}{{output_extension}}\" " + } + link_command += dsym_switch + link_command += "{{ldflags}} -o \"$dylib\" -Wl,-filelist,\"$rspfile\" {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}} {{rlibs}}" + + replace_command = "if ! cmp -s \"$temporary_tocname\" \"$tocname\"; then mv \"$temporary_tocname\" \"$tocname\"" + extract_toc_command = "{ $otool -l \"$dylib\" | grep LC_ID_DYLIB -A 5; $nm -gPp \"$dylib\" | cut -f1-2 -d' ' | grep -v U\$\$; true; }" + + command = "if $does_reexport_command ; then $link_command && $extract_toc_command > \"$tocname\"; else $link_command && $extract_toc_command > \"$temporary_tocname\" && $replace_command ; fi; fi" + + rspfile_content = "{{inputs_newline}}" + + description = "SOLINK {{output}}" + + # Use this for {{output_extension}} expansions unless a target manually + # overrides it (in which case {{output_extension}} will be what the target + # specifies). + default_output_dir = "{{root_out_dir}}" + default_output_extension = ".dylib" + + output_prefix = "lib" + + # Since the above commands only updates the .TOC file when it changes, ask + # Ninja to check if the timestamp actually changed to know if downstream + # dependencies should be recompiled. + restat = true + + # Tell GN about the output files. It will link to the dylib but use the + # tocname for dependency management. + outputs = [ + dylib, + tocname, + ] + link_output = dylib + depend_output = tocname + + if (_enable_dsyms) { + outputs += dsym_output + } + if (_save_unstripped_output) { + outputs += [ _unstripped_output ] + } + } + + tool("solink_module") { + # E.g. "./libfoo.so": + sofile = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + rspfile = sofile + ".rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + link_command = "$linker_driver $ld -bundle {{ldflags}} -o \"$sofile\" -Wl,-filelist,\"$rspfile\"" + link_command += dsym_switch + link_command += + " {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}} {{rlibs}}" + command = link_command + + rspfile_content = "{{inputs_newline}}" + + description = "SOLINK_MODULE {{output}}" + + # Use this for {{output_extension}} expansions unless a target manually + # overrides it (in which case {{output_extension}} will be what the target + # specifies). + default_output_dir = "{{root_out_dir}}" + default_output_extension = ".so" + + outputs = [ sofile ] + + if (_enable_dsyms) { + outputs += dsym_output + } + if (_save_unstripped_output) { + outputs += [ _unstripped_output ] + } + } + + tool("link") { + outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + rspfile = "$outfile.rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + # Note about -filelist: Apple's linker reads the file list file and + # interprets each newline-separated chunk of text as a file name. It + # doesn't do the things one would expect from the shell like unescaping + # or handling quotes. In contrast, when Ninja finds a file name with + # spaces, it single-quotes them in $inputs_newline as it would normally + # do for command-line arguments. Thus any source names with spaces, or + # label names with spaces (which GN bases the output paths on) will be + # corrupted by this process. Don't use spaces for source files or labels. + command = "$linker_driver $ld $dsym_switch {{ldflags}} -o \"$outfile\" -Wl,-filelist,\"$rspfile\" {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}} {{rlibs}}" + description = "LINK $outfile" + rspfile_content = "{{inputs_newline}}" + outputs = [ outfile ] + + if (_enable_dsyms) { + outputs += dsym_output + } + if (_save_unstripped_output) { + outputs += [ _unstripped_output ] + } + + default_output_dir = "{{root_out_dir}}" + } + + # These two are really entirely generic, but have to be repeated in + # each toolchain because GN doesn't allow a template to be used here. + # See //build/toolchain/toolchain.gni for details. + tool("stamp") { + command = stamp_command + description = stamp_description + } + tool("copy") { + command = copy_command + description = copy_description + } + + tool("copy_bundle_data") { + # copy_command use hardlink if possible but this does not work with + # directories. Also when running EG2 tests from Xcode, Xcode tries to + # copy some files into the application bundle which fails if source + # and destination are hardlinked together. + # + # Instead use clonefile to copy the files which is as efficient as + # hardlink but ensure the file have distinct metadata (thus avoid the + # error with ditto, see https://crbug.com/1042182). + if (host_os == "mac") { + command = "rm -rf {{output}} && /bin/cp -Rc {{source}} {{output}}" + } else { + command = "rm -rf {{output}} && /bin/cp -Rl {{source}} {{output}}" + } + description = "COPY_BUNDLE_DATA {{source}} {{output}}" + pool = "//build/toolchain/apple:bundle_pool($default_toolchain)" + } + + # Swift is only used on iOS, not macOS. We want to minimize the number + # of Xcode-based tools used by the macOS toolchain, so we intentionally + # disallow future uses of Swift on macOS. https://crbug.com/965663. + if (toolchain_args.current_os == "ios") { + tool("swift") { + _tool = rebase_path("//build/toolchain/ios/swiftc.py", root_build_dir) + + depfile = "{{target_out_dir}}/{{module_name}}.d" + depsformat = "gcc" + + outputs = [ + # The module needs to be the first output listed. The blank line after + # the module is required to prevent `gn format` from changing the file + # order. + "{{target_gen_dir}}/{{module_name}}.swiftmodule", + + "{{target_gen_dir}}/{{module_name}}.h", + "{{target_gen_dir}}/{{module_name}}.swiftdoc", + "{{target_gen_dir}}/{{module_name}}.swiftsourceinfo", + ] + + if (swift_whole_module_optimization) { + _extra_flags = "-whole-module-optimization" + _objects_dir = "{{target_out_dir}}" + + outputs += [ "$_objects_dir/{{module_name}}.o" ] + } else { + _extra_flags = "" + _objects_dir = "{{target_out_dir}}/{{label_name}}" + + partial_outputs = [ "$_objects_dir/{{source_name_part}}.o" ] + } + + _env_vars = "TOOL_VERSION=${tool_versions.swiftc}" + if (invoker.sdk_developer_dir != "") { + _env_vars += " DEVELOPER_DIR=${toolchain_args.sdk_developer_dir}" + } + + command = + "$_env_vars $python_path $_tool -module-name {{module_name}} " + + "-object-dir $_objects_dir " + + "-module-path {{target_gen_dir}}/{{module_name}}.swiftmodule " + + "-header-path {{target_gen_dir}}/{{module_name}}.h " + + "-depfile {{target_out_dir}}/{{module_name}}.d " + + "-depfile-filter {{target_gen_dir}}/{{module_name}}.swiftmodule " + + "-bridge-header {{bridge_header}} $_extra_flags " + + "{{swiftflags}} {{include_dirs}} {{module_dirs}} {{inputs}}" + } + } + + # xcassets are only used on iOS, not macOS. We want to minimize the number + # of Xcode-based tools used by the macOS toolchain, so we intentionally + # disallow future uses of xcassets on macOS. https://crbug.com/965663. + if (toolchain_args.current_os == "ios") { + tool("compile_xcassets") { + _tool = rebase_path("//build/toolchain/ios/compile_xcassets.py", + root_build_dir) + + _env_vars = "TOOL_VERSION=${tool_versions.compile_xcassets}" + if (invoker.sdk_developer_dir != "") { + _env_vars += " DEVELOPER_DIR=${toolchain_args.sdk_developer_dir}" + } + + command = + "$_env_vars $python_path $_tool -p \"${invoker.sdk_name}\" " + + "-t \"${invoker.deployment_target}\" " + + "-T \"{{bundle_product_type}}\" " + + "-P \"{{bundle_partial_info_plist}}\" " + "-o {{output}} {{inputs}}" + + description = "COMPILE_XCASSETS {{output}}" + pool = "//build/toolchain/apple:bundle_pool($default_toolchain)" + } + } + + tool("action") { + pool = "//build/toolchain:action_pool($default_toolchain)" + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/cc_wrapper.gni b/third_party/libwebrtc/build/toolchain/cc_wrapper.gni new file mode 100644 index 0000000000..6186070e3d --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/cc_wrapper.gni @@ -0,0 +1,43 @@ +# 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("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") + +# Defines the configuration of cc wrapper +# ccache: a c/c++ compiler cache which can greatly reduce recompilation times. +# icecc, distcc: it takes compile jobs from a build and distributes them among +# remote machines allowing a parallel build. +# +# TIPS +# +# 1) ccache +# Set clang_use_chrome_plugins=false if using ccache 3.1.9 or earlier, since +# these versions don't support -Xclang. (3.1.10 and later will silently +# ignore -Xclang, so it doesn't matter if you disable clang_use_chrome_plugins +# or not). +# +# Use ccache 3.2 or later to avoid clang unused argument warnings: +# https://bugzilla.samba.org/show_bug.cgi?id=8118 +# +# To avoid -Wparentheses-equality clang warnings, at some cost in terms of +# speed, you can do: +# export CCACHE_CPP2=yes +# +# 2) icecc +# Set clang_use_chrome_plugins=false because icecc cannot distribute custom +# clang libraries. +# +# To use icecc and ccache together, set cc_wrapper = "ccache" with +# export CCACHE_PREFIX=icecc + +declare_args() { + # Set to "ccache", "icecc" or "distcc". Probably doesn't work on windows. + cc_wrapper = "" +} + +assert(!use_goma || cc_wrapper == "", + "use_goma and cc_wrapper can not be used together.") +assert(!use_remoteexec || cc_wrapper == "", + "use_remoteexec and cc_wrapper can not be used together.") diff --git a/third_party/libwebrtc/build/toolchain/clang_code_coverage_wrapper.py b/third_party/libwebrtc/build/toolchain/clang_code_coverage_wrapper.py new file mode 100755 index 0000000000..38d4155213 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/clang_code_coverage_wrapper.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Removes code coverage flags from invocations of the Clang C/C++ compiler. + +If the GN arg `use_clang_coverage=true`, this script will be invoked by default. +GN will add coverage instrumentation flags to almost all source files. + +This script is used to remove instrumentation flags from a subset of the source +files. By default, it will not remove flags from any files. If the option +--files-to-instrument is passed, this script will remove flags from all files +except the ones listed in --files-to-instrument. + +This script also contains hard-coded exclusion lists of files to never +instrument, indexed by target operating system. Files in these lists have their +flags removed in both modes. The OS can be selected with --target-os. + +This script also contains hard-coded force lists of files to always instrument, +indexed by target operating system. Files in these lists never have their flags +removed in either mode. The OS can be selected with --target-os. + +The order of precedence is: force list, exclusion list, --files-to-instrument. + +The path to the coverage instrumentation input file should be relative to the +root build directory, and the file consists of multiple lines where each line +represents a path to a source file, and the specified paths must be relative to +the root build directory. e.g. ../../base/task/post_task.cc for build +directory 'out/Release'. The paths should be written using OS-native path +separators for the current platform. + +One caveat with this compiler wrapper is that it may introduce unexpected +behaviors in incremental builds when the file path to the coverage +instrumentation input file changes between consecutive runs, so callers of this +script are strongly advised to always use the same path such as +"${root_build_dir}/coverage_instrumentation_input.txt". + +It's worth noting on try job builders, if the contents of the instrumentation +file changes so that a file doesn't need to be instrumented any longer, it will +be recompiled automatically because if try job B runs after try job A, the files +that were instrumented in A will be updated (i.e., reverted to the checked in +version) in B, and so they'll be considered out of date by ninja and recompiled. + +Example usage: + clang_code_coverage_wrapper.py \\ + --files-to-instrument=coverage_instrumentation_input.txt +""" + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys + +# Flags used to enable coverage instrumentation. +# Flags should be listed in the same order that they are added in +# build/config/coverage/BUILD.gn +_COVERAGE_FLAGS = [ + '-fprofile-instr-generate', + '-fcoverage-mapping', + # Following experimental flags remove unused header functions from the + # coverage mapping data embedded in the test binaries, and the reduction + # of binary size enables building Chrome's large unit test targets on + # MacOS. Please refer to crbug.com/796290 for more details. + '-mllvm', + '-limited-coverage-experimental=true', +] + +# Files that should not be built with coverage flags by default. +_DEFAULT_COVERAGE_EXCLUSION_LIST = [ + # TODO(crbug.com/1051561): angle_unittests affected by coverage. + '../../base/message_loop/message_pump_default.cc', + '../../base/message_loop/message_pump_libevent.cc', + '../../base/message_loop/message_pump_win.cc', + '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long +] + +# Map of exclusion lists indexed by target OS. +# If no target OS is defined, or one is defined that doesn't have a specific +# entry, use _DEFAULT_COVERAGE_EXCLUSION_LIST. +_COVERAGE_EXCLUSION_LIST_MAP = { + 'android': [ + # This file caused webview native library failed on arm64. + '../../device/gamepad/dualshock4_controller.cc', + ], + 'fuchsia': [ + # TODO(crbug.com/1174725): These files caused clang to crash while + # compiling them. + '../../base/allocator/partition_allocator/pcscan.cc', + '../../third_party/skia/src/core/SkOpts.cpp', + '../../third_party/skia/src/opts/SkOpts_hsw.cpp', + '../../third_party/skia/third_party/skcms/skcms.cc', + ], + 'linux': [ + # These files caused a static initializer to be generated, which + # shouldn't. + # TODO(crbug.com/990948): Remove when the bug is fixed. + '../../chrome/browser/media/router/providers/cast/cast_internal_message_util.cc', #pylint: disable=line-too-long + '../../components/cast_channel/cast_channel_enum.cc', + '../../components/cast_channel/cast_message_util.cc', + '../../components/media_router/common/providers/cast/cast_media_source.cc', #pylint: disable=line-too-long + '../../ui/events/keycodes/dom/keycode_converter.cc', + # TODO(crbug.com/1051561): angle_unittests affected by coverage. + '../../base/message_loop/message_pump_default.cc', + '../../base/message_loop/message_pump_libevent.cc', + '../../base/message_loop/message_pump_win.cc', + '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long + ], + 'chromeos': [ + # These files caused clang to crash while compiling them. They are + # excluded pending an investigation into the underlying compiler bug. + '../../third_party/webrtc/p2p/base/p2p_transport_channel.cc', + '../../third_party/icu/source/common/uts46.cpp', + '../../third_party/icu/source/common/ucnvmbcs.cpp', + '../../base/android/android_image_reader_compat.cc', + # TODO(crbug.com/1051561): angle_unittests affected by coverage. + '../../base/message_loop/message_pump_default.cc', + '../../base/message_loop/message_pump_libevent.cc', + '../../base/message_loop/message_pump_win.cc', + '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long + ], + 'win': [ + # TODO(crbug.com/1051561): angle_unittests affected by coverage. + '../../base/message_loop/message_pump_default.cc', + '../../base/message_loop/message_pump_libevent.cc', + '../../base/message_loop/message_pump_win.cc', + '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long + ], +} + +# Map of force lists indexed by target OS. +_COVERAGE_FORCE_LIST_MAP = { + # clang_profiling.cc refers to the symbol `__llvm_profile_dump` from the + # profiling runtime. In a partial coverage build, it is possible for a + # binary to include clang_profiling.cc but have no instrumented files, thus + # causing an unresolved symbol error because the profiling runtime will not + # be linked in. Therefore we force coverage for this file to ensure that + # any target that includes it will also get the profiling runtime. + 'win': [r'..\..\base\test\clang_profiling.cc'], + # TODO(crbug.com/1141727) We're seeing runtime LLVM errors in mac-rel when + # no files are changed, so we suspect that this is similar to the other + # problem with clang_profiling.cc on Windows. The TODO here is to force + # coverage for this specific file on ALL platforms, if it turns out to fix + # this issue on Mac as well. It's the only file that directly calls + # `__llvm_profile_dump` so it warrants some special treatment. + 'mac': ['../../base/test/clang_profiling.cc'], +} + + +def _remove_flags_from_command(command): + # We need to remove the coverage flags for this file, but we only want to + # remove them if we see the exact sequence defined in _COVERAGE_FLAGS. + # That ensures that we only remove the flags added by GN when + # "use_clang_coverage" is true. Otherwise, we would remove flags set by + # other parts of the build system. + start_flag = _COVERAGE_FLAGS[0] + num_flags = len(_COVERAGE_FLAGS) + start_idx = 0 + try: + while True: + idx = command.index(start_flag, start_idx) + if command[idx:idx + num_flags] == _COVERAGE_FLAGS: + del command[idx:idx + num_flags] + # There can be multiple sets of _COVERAGE_FLAGS. All of these need to be + # removed. + start_idx = idx + else: + start_idx = idx + 1 + except ValueError: + pass + + +def main(): + arg_parser = argparse.ArgumentParser() + arg_parser.usage = __doc__ + arg_parser.add_argument( + '--files-to-instrument', + type=str, + help='Path to a file that contains a list of file names to instrument.') + arg_parser.add_argument( + '--target-os', required=False, help='The OS to compile for.') + arg_parser.add_argument('args', nargs=argparse.REMAINDER) + parsed_args = arg_parser.parse_args() + + if (parsed_args.files_to_instrument and + not os.path.isfile(parsed_args.files_to_instrument)): + raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t ' + 'exist.' % parsed_args.files_to_instrument) + + compile_command = parsed_args.args + if not any('clang' in s for s in compile_command): + return subprocess.call(compile_command) + + target_os = parsed_args.target_os + + try: + # The command is assumed to use Clang as the compiler, and the path to the + # source file is behind the -c argument, and the path to the source path is + # relative to the root build directory. For example: + # clang++ -fvisibility=hidden -c ../../base/files/file_path.cc -o \ + # obj/base/base/file_path.o + # On Windows, clang-cl.exe uses /c instead of -c. + source_flag = '/c' if target_os == 'win' else '-c' + source_flag_index = compile_command.index(source_flag) + except ValueError: + print('%s argument is not found in the compile command.' % source_flag) + raise + + if source_flag_index + 1 >= len(compile_command): + raise Exception('Source file to be compiled is missing from the command.') + + # On Windows, filesystem paths should use '\', but GN creates build commands + # that use '/'. We invoke os.path.normpath to ensure that the path uses the + # correct separator for the current platform (i.e. '\' on Windows and '/' + # otherwise). + compile_source_file = os.path.normpath(compile_command[source_flag_index + 1]) + extension = os.path.splitext(compile_source_file)[1] + if not extension in ['.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.S']: + raise Exception('Invalid source file %s found' % compile_source_file) + exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP.get( + target_os, _DEFAULT_COVERAGE_EXCLUSION_LIST) + force_list = _COVERAGE_FORCE_LIST_MAP.get(target_os, []) + + should_remove_flags = False + if compile_source_file not in force_list: + if compile_source_file in exclusion_list: + should_remove_flags = True + elif parsed_args.files_to_instrument: + with open(parsed_args.files_to_instrument) as f: + if compile_source_file not in f.read(): + should_remove_flags = True + + if should_remove_flags: + _remove_flags_from_command(compile_command) + + return subprocess.call(compile_command) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/concurrent_links.gni b/third_party/libwebrtc/build/toolchain/concurrent_links.gni new file mode 100644 index 0000000000..c0342256a6 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/concurrent_links.gni @@ -0,0 +1,106 @@ +# 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. + +# This file should only be imported from files that define toolchains. +# There's no way to enforce this exactly, but all toolchains are processed +# in the context of the default_toolchain, so we can at least check for that. +assert(current_toolchain == default_toolchain) + +import("//build/config/android/config.gni") +import("//build/config/apple/symbols.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/toolchain/toolchain.gni") + +declare_args() { + # Limit the number of concurrent links; we often want to run fewer + # links at once than we do compiles, because linking is memory-intensive. + # The default to use varies by platform and by the amount of memory + # available, so we call out to a script to get the right value. + concurrent_links = -1 +} + +if (concurrent_links == -1) { + if (use_thin_lto) { + _args = [ "--reserve_mem_gb=10" ] + if (use_goma_thin_lto) { + _args += [ "--thin-lto=goma" ] + } else { + _args += [ "--thin-lto=local" ] + } + if (is_win) { + # Based on measurements of linking chrome.dll and chrome_child.dll, plus + # a little padding to account for future growth. + _args += [ "--mem_per_link_gb=45" ] + } else { + _args += [ "--mem_per_link_gb=16" ] + } + } else if ((use_clang_coverage && + # When coverage_instrumentation_input_file is not empty it means + # we're only instrumenting changed files and not using a lot of + # memory. Likewise, when it's empty we're building everything with + # coverage, which requires more memory. + coverage_instrumentation_input_file == "") || + use_sanitizer_coverage || use_fuzzing_engine) { + # Full sanitizer coverage instrumentation increases linker memory consumption + # significantly. + _args = [ "--mem_per_link_gb=16" ] + } else if (is_win && symbol_level == 1 && !is_debug && is_component_build) { + _args = [ "--mem_per_link_gb=3" ] + } else if (is_win) { + _args = [ "--mem_per_link_gb=6" ] + } else if (is_mac) { + if (enable_dsyms) { + _args = [ "--mem_per_link_gb=12" ] + } else { + _args = [ "--mem_per_link_gb=4" ] + } + } else if (is_android && !is_component_build && symbol_level == 2) { + # Full debug symbols require large memory for link. + _args = [ "--mem_per_link_gb=25" ] + } else if (is_android && !is_debug && !using_sanitizer && is_java_debug && + disable_android_lint && symbol_level < 2) { + if (symbol_level == 1) { + _args = [ "--mem_per_link_gb=6" ] + } else { + _args = [ "--mem_per_link_gb=4" ] + } + } else if ((is_linux || is_chromeos_lacros) && symbol_level == 0) { + # Memory consumption on link without debug symbols is low on linux. + _args = [ "--mem_per_link_gb=3" ] + } else { + _args = [] + } + + # For Android builds, we also need to be wary of: + # * ProGuard / R8 + # * Android Lint + # These both have a peak usage of < 2GB, but that is still large enough for + # them to need to use a pool since they both typically happen at the + # same time as linking. + if (is_android) { + _args += [ "--secondary_mem_per_link=2" ] + } + + # TODO(crbug.com/617429) Pass more build configuration info to the script + # so that we can compute better values. + _command_dict = exec_script("get_concurrent_links.py", _args, "scope") + + concurrent_links = _command_dict.primary_pool_size + concurrent_links_logs = _command_dict.explanation + + if (_command_dict.secondary_pool_size >= concurrent_links) { + # Have R8 / Lint share the link pool unless we would safely get more + # concurrency out of using a separate one. + # On low-RAM machines, this allows an apk's native library to link at the + # same time as its java is optimized with R8. + java_cmd_pool_size = _command_dict.secondary_pool_size + } +} else { + assert(!use_thin_lto, "can't explicitly set concurrent_links with thinlto") + concurrent_links_logs = + [ "concurrent_links set by GN arg (value=$concurrent_links)" ] +} diff --git a/third_party/libwebrtc/build/toolchain/cros/BUILD.gn b/third_party/libwebrtc/build/toolchain/cros/BUILD.gn new file mode 100644 index 0000000000..7140f87d22 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/cros/BUILD.gn @@ -0,0 +1,176 @@ +# 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/compiler/compiler.gni") +import("//build/config/sysroot.gni") +import("//build/toolchain/cros_toolchain.gni") +import("//build/toolchain/gcc_toolchain.gni") + +# This is mostly identical to gcc_toolchain, but handles relativizing toolchain +# paths. This is needed for CrOS since these paths often change based on the +# environment. For example, cxx is a relative path picked up on $PATH in the +# chroot. But in Simple Chrome, cxx is a system-absolute path. +template("cros_toolchain") { + gcc_toolchain(target_name) { + forward_variables_from(invoker, "*") + + # CrOS's target toolchain wrapper prefers to invoke gomacc itself, so pass + # it the gomacc path via cmd-line arg. Otherwise, for both CrOS's host + # wrapper (used in the ebuild) and Chrome's clang (used in Simple Chrome), + # prepend gomacc like normal. + if (use_goma && toolchain_args.needs_gomacc_path_arg) { + extra_cppflags += " --gomacc-path $goma_dir/gomacc" + } + if (use_rbe && toolchain_args.needs_gomacc_path_arg) { + extra_cppflags += " --gomacc-path $rbe_cros_cc_wrapper" + } + + # Relativize path if compiler is specified such that not to lookup from $PATH + # and cc/cxx does not contain additional flags. + if (cc != get_path_info(cc, "file") && string_replace(cc, " ", "") == cc) { + cc = rebase_path(cc, root_build_dir) + } + if (cxx != get_path_info(cxx, "file") && + string_replace(cxx, " ", "") == cxx) { + cxx = rebase_path(cxx, root_build_dir) + } + if (ar != get_path_info(ar, "file") && string_replace(ar, " ", "") == ar) { + ar = rebase_path(ar, root_build_dir) + } + if (ld != get_path_info(ld, "file") && string_replace(ld, " ", "") == ld) { + ld = rebase_path(ld, root_build_dir) + } + } +} + +# This is the normal toolchain for most targets. +cros_toolchain("target") { + ar = cros_target_ar + cc = cros_target_cc + cxx = cros_target_cxx + ld = cros_target_ld + + if (cros_target_nm != "") { + nm = cros_target_nm + } + if (cros_target_readelf != "") { + readelf = cros_target_readelf + } + extra_cflags = cros_target_extra_cflags + extra_cppflags = cros_target_extra_cppflags + extra_cxxflags = cros_target_extra_cxxflags + extra_ldflags = cros_target_extra_ldflags + + toolchain_args = { + cc_wrapper = "" + needs_gomacc_path_arg = true + clang_use_chrome_plugins = false + target_cpu = target_cpu + current_os = "chromeos" + is_clang = is_clang + use_debug_fission = use_debug_fission + use_gold = use_gold + use_sysroot = use_sysroot + sysroot = target_sysroot + } +} + +# This is a special toolchain needed just for the nacl_bootstrap target in +# //native_client/src/trusted/service_runtime/linux. It is identical +# to ":target" except that it forces use_debug_fission, use_gold, and +# use_sysroot off, and allows the user to set different sets of extra flags. +cros_toolchain("nacl_bootstrap") { + ar = cros_target_ar + cc = cros_target_cc + cxx = cros_target_cxx + ld = cros_target_ld + + if (cros_target_nm != "") { + nm = cros_target_nm + } + if (cros_target_readelf != "") { + readelf = cros_target_readelf + } + extra_cflags = cros_nacl_bootstrap_extra_cflags + extra_cppflags = cros_nacl_bootstrap_extra_cppflags + extra_cxxflags = cros_nacl_bootstrap_extra_cxxflags + extra_ldflags = cros_nacl_bootstrap_extra_ldflags + + toolchain_args = { + cc_wrapper = "" + needs_gomacc_path_arg = true + clang_use_chrome_plugins = false + target_cpu = target_cpu + current_os = "chromeos" + is_clang = is_clang + use_debug_fission = false + use_gold = false + use_sysroot = false + } +} + +cros_toolchain("host") { + # These are args for the template. + ar = cros_host_ar + cc = cros_host_cc + cxx = cros_host_cxx + ld = cros_host_ld + + if (cros_host_nm != "") { + nm = cros_host_nm + } + if (cros_host_readelf != "") { + readelf = cros_host_readelf + } + extra_cflags = cros_host_extra_cflags + extra_cppflags = cros_host_extra_cppflags + extra_cxxflags = cros_host_extra_cxxflags + extra_ldflags = cros_host_extra_ldflags + + toolchain_args = { + cc_wrapper = "" + needs_gomacc_path_arg = false + clang_use_chrome_plugins = false + is_clang = cros_host_is_clang + target_cpu = host_cpu + current_os = "linux" + use_sysroot = use_sysroot + sysroot = cros_host_sysroot + } +} + +cros_toolchain("v8_snapshot") { + # These are args for the template. + ar = cros_v8_snapshot_ar + cc = cros_v8_snapshot_cc + cxx = cros_v8_snapshot_cxx + ld = cros_v8_snapshot_ld + + if (cros_v8_snapshot_nm != "") { + nm = cros_v8_snapshot_nm + } + if (cros_v8_snapshot_readelf != "") { + readelf = cros_v8_snapshot_readelf + } + extra_cflags = cros_v8_snapshot_extra_cflags + extra_cppflags = cros_v8_snapshot_extra_cppflags + extra_cxxflags = cros_v8_snapshot_extra_cxxflags + extra_ldflags = cros_v8_snapshot_extra_ldflags + + toolchain_args = { + cc_wrapper = "" + needs_gomacc_path_arg = false + clang_use_chrome_plugins = false + is_clang = cros_v8_snapshot_is_clang + if (target_cpu == "x86" || target_cpu == "arm" || target_cpu == "mipsel") { + target_cpu = "x86" + } else { + target_cpu = "x64" + } + v8_target_cpu = v8_target_cpu + current_os = "linux" + use_sysroot = use_sysroot + sysroot = cros_v8_snapshot_sysroot + } +} diff --git a/third_party/libwebrtc/build/toolchain/cros_toolchain.gni b/third_party/libwebrtc/build/toolchain/cros_toolchain.gni new file mode 100644 index 0000000000..ccc4db293c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/cros_toolchain.gni @@ -0,0 +1,88 @@ +# 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. + +# CrOS builds must cross-compile on a Linux host for the actual CrOS +# device target. There are many different CrOS devices so the build +# system provides configuration variables that permit a CrOS build to +# control the cross-compilation tool chain. However, requiring such +# fine-grain specification is tedious for build-bots and developers. +# Consequently, the CrOS build system defaults to a convenience +# compilation mode where the compilation host is also the build target. +# +# Chrome can be compiled in this way with the gn variable: +# +# target_os = "chromeos" +# +# To perform a board-specific build, first obtain the correct system +# root (http://goo.gl/aFB4XH) for the board. Then configure GN to use it +# by setting appropriate cross-compilation variables. +# +# For example, to compile a Chrome source tree in /g/src for an +# auron_paine CrOS device with the system root cached in /g/.cros_cache, +# the following GN arguments must be provided to configure +# cross-compilation with Goma acceleration. (NB: additional variables +# will be necessary to successfully compile a working CrOS Chrome. See +# the definition of GYP_DEFINES inside a sysroot shell.) +# +# goma_dir = "/g/.cros_cache/common/goma+2" +# target_sysroot= /g/.cros_cache/chrome-sdk/tarballs/auron_paine+7644.0.0+sysroot_chromeos-base_chromeos-chrome.tar.xz" +# cros_target_cc = "x86_64-cros-linux-gnu-gcc -B/g/.cros_cache/chrome-sdk/tarballs/auron_paine+7657.0.0+target_toolchain/usr/x86_64-pc-linux-gnu/x86_64-cros-linux-gnu/binutils-bin/2.25.51-gold" +# cros_target_cxx = "x86_64-cros-linux-gnu-g++ -B/g/.cros_cache/chrome-sdk/tarballs/auron_paine+7657.0.0+target_toolchain/usr/x86_64-pc-linux-gnu/x86_64-cros-linux-gnu/binutils-bin/2.25.51-gold" +# cros_target_ar = "x86_64-cros-linux-gnu-gcc-ar" +# target_cpu = "x64" + +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") + +declare_args() { + # These must be specified for a board-specific build. + cros_target_ar = "ar" + cros_target_cc = "gcc" + cros_target_cxx = "g++" + cros_target_nm = "" + cros_target_readelf = "" + + # These can be optionally set. The "_cppflags" will be applied to *both* + # C and C++ files; use "_cxxflags" for C++-only flags. + cros_target_extra_cflags = "" + cros_target_extra_cppflags = "" + cros_target_extra_cxxflags = "" + cros_target_extra_ldflags = "" + + # is_clang is used instead of cros_target_is_clang + cros_host_ar = "${clang_base_path}/bin/llvm-ar" + cros_host_cc = "${clang_base_path}/bin/clang" + cros_host_cxx = "${clang_base_path}/bin/clang++" + cros_host_is_clang = true + cros_host_nm = "" + cros_host_readelf = "" + cros_host_extra_cflags = "" + cros_host_extra_cppflags = "" + cros_host_extra_cxxflags = "" + cros_host_extra_ldflags = "" + cros_host_sysroot = "" + + cros_v8_snapshot_ar = "${clang_base_path}/bin/llvm-ar" + cros_v8_snapshot_cc = "${clang_base_path}/bin/clang" + cros_v8_snapshot_cxx = "${clang_base_path}/bin/clang++" + cros_v8_snapshot_is_clang = true + cros_v8_snapshot_nm = "" + cros_v8_snapshot_readelf = "" + cros_v8_snapshot_extra_cflags = "" + cros_v8_snapshot_extra_cppflags = "" + cros_v8_snapshot_extra_cxxflags = "" + cros_v8_snapshot_extra_ldflags = "" + cros_v8_snapshot_sysroot = "" + + cros_nacl_bootstrap_extra_cflags = "" + cros_nacl_bootstrap_extra_cppflags = "" + cros_nacl_bootstrap_extra_cxxflags = "" + cros_nacl_bootstrap_extra_ldflags = "" +} + +declare_args() { + cros_target_ld = cros_target_cxx + cros_host_ld = cros_host_cxx + cros_v8_snapshot_ld = cros_v8_snapshot_cxx +} diff --git a/third_party/libwebrtc/build/toolchain/fuchsia/BUILD.gn b/third_party/libwebrtc/build/toolchain/fuchsia/BUILD.gn new file mode 100644 index 0000000000..dbfcc9802f --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/fuchsia/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2017 The Chromium 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/toolchain/gcc_toolchain.gni") + +# Fuchsia builds using the Clang toolchain, with most parameters common across +# the different target architectures. +template("fuchsia_clang_toolchain") { + clang_toolchain(target_name) { + assert(host_os == "linux" || host_os == "mac") + assert(defined(invoker.toolchain_args), + "toolchain_args must be defined for fuchsia_clang_toolchain()") + + # We want to build and strip binaries, but retain the unstripped binaries + # in runtime_deps to make them available for isolates. + strip = rebase_path("${clang_base_path}/bin/llvm-strip", root_build_dir) + use_unstripped_as_runtime_outputs = true + + default_shlib_subdir = "/lib" + + toolchain_args = invoker.toolchain_args + toolchain_args.current_os = "fuchsia" + } +} + +fuchsia_clang_toolchain("x64") { + toolchain_args = { + target_cpu = "x64" + } +} + +fuchsia_clang_toolchain("arm64") { + toolchain_args = { + target_cpu = "arm64" + } +} diff --git a/third_party/libwebrtc/build/toolchain/fuchsia/DIR_METADATA b/third_party/libwebrtc/build/toolchain/fuchsia/DIR_METADATA new file mode 100644 index 0000000000..6d8f079aa5 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/fuchsia/DIR_METADATA @@ -0,0 +1,7 @@ +monorail { + component: "Fuchsia" +} + +team_email: "cr-fuchsia@chromium.org" + +os: FUCHSIA diff --git a/third_party/libwebrtc/build/toolchain/fuchsia/OWNERS b/third_party/libwebrtc/build/toolchain/fuchsia/OWNERS new file mode 100644 index 0000000000..3f809e82b1 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/fuchsia/OWNERS @@ -0,0 +1 @@ +scottmg@chromium.org diff --git a/third_party/libwebrtc/build/toolchain/gcc_link_wrapper.py b/third_party/libwebrtc/build/toolchain/gcc_link_wrapper.py new file mode 100755 index 0000000000..b70de8b31c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/gcc_link_wrapper.py @@ -0,0 +1,94 @@ +#!/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. + +"""Runs a linking command and optionally a strip command. + +This script exists to avoid using complex shell commands in +gcc_toolchain.gni's tool("link"), in case the host running the compiler +does not have a POSIX-like shell (e.g. Windows). +""" + +import argparse +import os +import subprocess +import sys + +import wrapper_utils + + +# When running on a Windows host and using a toolchain whose tools are +# actually wrapper scripts (i.e. .bat files on Windows) rather than binary +# executables, the "command" to run has to be prefixed with this magic. +# The GN toolchain definitions take care of that for when GN/Ninja is +# running the tool directly. When that command is passed in to this +# script, it appears as a unitary string but needs to be split up so that +# just 'cmd' is the actual command given to Python's subprocess module. +BAT_PREFIX = 'cmd /c call ' + +def CommandToRun(command): + if command[0].startswith(BAT_PREFIX): + command = command[0].split(None, 3) + command[1:] + return command + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--strip', + help='The strip binary to run', + metavar='PATH') + parser.add_argument('--unstripped-file', + help='Executable file produced by linking command', + metavar='FILE') + parser.add_argument('--map-file', + help=('Use --Wl,-Map to generate a map file. Will be ' + 'gzipped if extension ends with .gz'), + metavar='FILE') + parser.add_argument('--dwp', help=('The dwp binary to run'), metavar='FILE') + parser.add_argument('--output', + required=True, + help='Final output executable file', + metavar='FILE') + parser.add_argument('command', nargs='+', + help='Linking command') + args = parser.parse_args() + + # Work-around for gold being slow-by-default. http://crbug.com/632230 + fast_env = dict(os.environ) + fast_env['LC_ALL'] = 'C' + result = wrapper_utils.RunLinkWithOptionalMapFile(args.command, env=fast_env, + map_file=args.map_file) + if result != 0: + return result + + # If dwp is set, then package debug info for this exe. + dwp_proc = None + if args.dwp: + exe_file = args.output + if args.unstripped_file: + exe_file = args.unstripped_file + # Suppress output here because it doesn't seem to be useful. The most + # common error is a segfault, which will happen if files are missing. + with open(os.devnull, "w") as devnull: + dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun( + [args.dwp, '-e', exe_file, '-o', exe_file + '.dwp']), + stdout=devnull, + stderr=subprocess.STDOUT) + + # Finally, strip the linked executable (if desired). + if args.strip: + result = subprocess.call( + CommandToRun([args.strip, '-o', args.output, args.unstripped_file])) + + if dwp_proc: + dwp_result = dwp_proc.wait() + if dwp_result != 0: + sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result)) + return dwp_result + + return result + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/gcc_solink_wrapper.py b/third_party/libwebrtc/build/toolchain/gcc_solink_wrapper.py new file mode 100755 index 0000000000..39aef4d1e9 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/gcc_solink_wrapper.py @@ -0,0 +1,198 @@ +#!/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. + +"""Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged. + +This script exists to avoid using complex shell commands in +gcc_toolchain.gni's tool("solink"), in case the host running the compiler +does not have a POSIX-like shell (e.g. Windows). +""" + +import argparse +import os +import shlex +import subprocess +import sys + +import wrapper_utils + + +def CollectSONAME(args): + """Replaces: readelf -d $sofile | grep SONAME""" + toc = '' + readelf = subprocess.Popen(wrapper_utils.CommandToRun( + [args.readelf, '-d', args.sofile]), + stdout=subprocess.PIPE, + bufsize=-1, + universal_newlines=True) + for line in readelf.stdout: + if 'SONAME' in line: + toc += line + return readelf.wait(), toc + + +def CollectDynSym(args): + """Replaces: nm --format=posix -g -D -p $sofile | cut -f1-2 -d' '""" + toc = '' + nm = subprocess.Popen(wrapper_utils.CommandToRun( + [args.nm, '--format=posix', '-g', '-D', '-p', args.sofile]), + stdout=subprocess.PIPE, + bufsize=-1, + universal_newlines=True) + for line in nm.stdout: + toc += ' '.join(line.split(' ', 2)[:2]) + '\n' + return nm.wait(), toc + + +def CollectTOC(args): + result, toc = CollectSONAME(args) + if result == 0: + result, dynsym = CollectDynSym(args) + toc += dynsym + return result, toc + + +def UpdateTOC(tocfile, toc): + if os.path.exists(tocfile): + old_toc = open(tocfile, 'r').read() + else: + old_toc = None + if toc != old_toc: + open(tocfile, 'w').write(toc) + + +def CollectInputs(out, args): + for x in args: + if x.startswith('@'): + with open(x[1:]) as rsp: + CollectInputs(out, shlex.split(rsp.read())) + elif not x.startswith('-') and (x.endswith('.o') or x.endswith('.a')): + out.write(x) + out.write('\n') + + +def InterceptFlag(flag, command): + ret = flag in command + if ret: + command.remove(flag) + return ret + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--readelf', + required=True, + help='The readelf binary to run', + metavar='PATH') + parser.add_argument('--nm', + required=True, + help='The nm binary to run', + metavar='PATH') + parser.add_argument('--strip', + help='The strip binary to run', + metavar='PATH') + parser.add_argument('--dwp', help='The dwp binary to run', metavar='PATH') + parser.add_argument('--sofile', + required=True, + help='Shared object file produced by linking command', + metavar='FILE') + parser.add_argument('--tocfile', + required=True, + help='Output table-of-contents file', + metavar='FILE') + parser.add_argument('--map-file', + help=('Use --Wl,-Map to generate a map file. Will be ' + 'gzipped if extension ends with .gz'), + metavar='FILE') + parser.add_argument('--output', + required=True, + help='Final output shared object file', + metavar='FILE') + parser.add_argument('command', nargs='+', + help='Linking command') + args = parser.parse_args() + + # Work-around for gold being slow-by-default. http://crbug.com/632230 + fast_env = dict(os.environ) + fast_env['LC_ALL'] = 'C' + + # Extract flags passed through ldflags but meant for this script. + # https://crbug.com/954311 tracks finding a better way to plumb these. + link_only = InterceptFlag('--link-only', args.command) + collect_inputs_only = InterceptFlag('--collect-inputs-only', args.command) + + # If only linking, we are likely generating a partitioned .so that will be + # split apart later. In that case: + # + # - The TOC file optimization isn't useful, because the partition libraries + # must always be re-extracted if the combined library changes (and nothing + # should be depending on the combined library's dynamic symbol table). + # - Stripping isn't necessary, because the combined library is not used in + # production or published. + # + # Both of these operations could still be done, they're needless work, and + # tools would need to be updated to handle and/or not complain about + # partitioned libraries. Instead, to keep Ninja happy, simply create dummy + # files for the TOC and stripped lib. + if link_only or collect_inputs_only: + open(args.output, 'w').close() + open(args.tocfile, 'w').close() + if args.dwp: + open(args.sofile + '.dwp', 'w').close() + + # Instead of linking, records all inputs to a file. This is used by + # enable_resource_allowlist_generation in order to avoid needing to + # link (which is slow) to build the resources allowlist. + if collect_inputs_only: + with open(args.sofile, 'w') as f: + CollectInputs(f, args.command) + if args.map_file: + open(args.map_file, 'w').close() + return 0 + + # First, run the actual link. + command = wrapper_utils.CommandToRun(args.command) + result = wrapper_utils.RunLinkWithOptionalMapFile(command, + env=fast_env, + map_file=args.map_file) + + if result != 0 or link_only: + return result + + # If dwp is set, then package debug info for this SO. + dwp_proc = None + if args.dwp: + # Suppress output here because it doesn't seem to be useful. The most + # common error is a segfault, which will happen if files are missing. + with open(os.devnull, "w") as devnull: + dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun( + [args.dwp, '-e', args.sofile, '-o', args.sofile + '.dwp']), + stdout=devnull, + stderr=subprocess.STDOUT) + + # Next, generate the contents of the TOC file. + result, toc = CollectTOC(args) + if result != 0: + return result + + # If there is an existing TOC file with identical contents, leave it alone. + # Otherwise, write out the TOC file. + UpdateTOC(args.tocfile, toc) + + # Finally, strip the linked shared object file (if desired). + if args.strip: + result = subprocess.call(wrapper_utils.CommandToRun( + [args.strip, '-o', args.output, args.sofile])) + + if dwp_proc: + dwp_result = dwp_proc.wait() + if dwp_result != 0: + return dwp_result + + return result + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/gcc_toolchain.gni b/third_party/libwebrtc/build/toolchain/gcc_toolchain.gni new file mode 100644 index 0000000000..2b547c3435 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/gcc_toolchain.gni @@ -0,0 +1,762 @@ +# 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/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/rust.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/v8_target_cpu.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") +import("//build/toolchain/toolchain.gni") + +if (is_nacl) { + # To keep NaCl variables out of builds that don't include NaCl, all + # variables defined in nacl/config.gni referenced here should be protected by + # is_nacl conditions. + import("//build/config/nacl/config.gni") +} + +declare_args() { + # Enables allowlist generation for IDR_ grit defines seen by the compiler. + # Currently works only on some platforms and enabled by default for official + # builds. Requires debug info. + enable_resource_allowlist_generation = + is_official_build && + # Don't enable for Android-on-Chrome OS. + (target_os == "android" || target_os == "win") +} + +# When the arg is set via args.gn, it applies to all toolchains. In order to not +# hit the assert in grit_rule.gni, explicitly disable for host toolchains. +if ((is_linux || is_chromeos) && target_os == "android") { + enable_resource_allowlist_generation = false +} + +# Ensure enable_resource_allowlist_generation is enabled only when it will work. +if (enable_resource_allowlist_generation) { + assert( + !strip_debug_info, + "enable_resource_allowlist_generation=true requires strip_debug_info=false") + assert( + !is_component_build, + "enable_resource_allowlist_generation=true requires is_component_build=false") + assert( + target_os == "android" || target_os == "win", + "enable_resource_allowlist_generation=true does not work for target_os=$target_os") +} + +# This template defines a toolchain for something that works like gcc +# (including clang). +# +# It requires the following variables specifying the executables to run: +# - ar +# - cc +# - cxx +# - ld +# +# Optional parameters that control the tools: +# +# - extra_cflags +# Extra flags to be appended when compiling C files (but not C++ files). +# - extra_cppflags +# Extra flags to be appended when compiling both C and C++ files. "CPP" +# stands for "C PreProcessor" in this context, although it can be +# used for non-preprocessor flags as well. Not to be confused with +# "CXX" (which follows). +# - extra_cxxflags +# Extra flags to be appended when compiling C++ files (but not C files). +# - extra_asmflags +# Extra flags to be appended when compiling assembly. +# - extra_ldflags +# Extra flags to be appended when linking +# +# - link_outputs +# The content of this array, if specified, will be added to the list of +# outputs from the link command. This can be useful in conjunction with +# the post_link parameter. +# - use_unstripped_as_runtime_outputs +# When |strip| is set, mark unstripped executables as runtime deps rather +# than stripped ones. +# - post_link +# The content of this string, if specified, will be run as a separate +# command following the the link command. +# - deps +# Just forwarded to the toolchain definition. +# - executable_extension +# If this string is specified it will be used for the file extension +# for an executable, rather than using no extension; targets will +# still be able to override the extension using the output_extension +# variable. +# - rebuild_define +# The contents of this string, if specified, will be passed as a #define +# to the toolchain. It can be used to force recompiles whenever a +# toolchain is updated. +# - shlib_extension +# If this string is specified it will be used for the file extension +# for a shared library, rather than default value specified in +# toolchain.gni +# - strip +# Location of the strip executable. When specified, strip will be run on +# all shared libraries and executables as they are built. The pre-stripped +# artifacts will be put in lib.unstripped/ and exe.unstripped/. +template("gcc_toolchain") { + toolchain(target_name) { + assert(defined(invoker.ar), "gcc_toolchain() must specify a \"ar\" value") + assert(defined(invoker.cc), "gcc_toolchain() must specify a \"cc\" value") + assert(defined(invoker.cxx), "gcc_toolchain() must specify a \"cxx\" value") + assert(defined(invoker.ld), "gcc_toolchain() must specify a \"ld\" value") + + # This define changes when the toolchain changes, forcing a rebuild. + # Nothing should ever use this define. + if (defined(invoker.rebuild_define)) { + rebuild_string = "-D" + invoker.rebuild_define + " " + } else { + rebuild_string = "" + } + + # GN's syntax can't handle more than one scope dereference at once, like + # "invoker.toolchain_args.foo", so make a temporary to hold the toolchain + # args so we can do "invoker_toolchain_args.foo". + assert(defined(invoker.toolchain_args), + "Toolchains must specify toolchain_args") + invoker_toolchain_args = invoker.toolchain_args + assert(defined(invoker_toolchain_args.target_cpu), + "toolchain_args must specify a target_cpu") + assert(defined(invoker_toolchain_args.current_os), + "toolchain_args must specify a current_os") + + # When invoking this toolchain not as the default one, these args will be + # passed to the build. They are ignored when this is the default toolchain. + toolchain_args = { + # Populate toolchain args from the invoker. + forward_variables_from(invoker_toolchain_args, "*") + + # The host toolchain value computed by the default toolchain's setup + # needs to be passed through unchanged to all secondary toolchains to + # ensure that it's always the same, regardless of the values that may be + # set on those toolchains. + host_toolchain = host_toolchain + + if (!defined(invoker_toolchain_args.v8_target_cpu)) { + v8_target_cpu = invoker_toolchain_args.target_cpu + } + } + + # When the invoker has explicitly overridden use_remoteexec, use_goma or + # cc_wrapper in the toolchain args, use those values, otherwise default + # to the global one. This works because the only reasonable override + # that toolchains might supply for these values are to force-disable them. + if (defined(toolchain_args.use_remoteexec)) { + toolchain_uses_remoteexec = toolchain_args.use_remoteexec + } else { + toolchain_uses_remoteexec = use_remoteexec + } + if (defined(toolchain_args.use_goma)) { + toolchain_uses_goma = toolchain_args.use_goma + } else { + toolchain_uses_goma = use_goma + } + + # x86_64-nacl-* is ELF-32 and Goma/RBE won't support ELF-32. + if (toolchain_uses_goma && + get_path_info(invoker.cc, "name") == "x86_64-nacl-gcc") { + # it will also disable x86_64-nacl-g++ since these are in + # the same toolchain. + toolchain_uses_goma = false + } + if (defined(toolchain_args.cc_wrapper)) { + toolchain_cc_wrapper = toolchain_args.cc_wrapper + } else { + toolchain_cc_wrapper = cc_wrapper + } + assert(!(toolchain_uses_remoteexec && toolchain_uses_goma), + "Goma and re-client can't be used together.") + assert(!(toolchain_cc_wrapper != "" && toolchain_uses_remoteexec), + "re-client and cc_wrapper can't be used together.") + assert(!(toolchain_cc_wrapper != "" && toolchain_uses_goma), + "Goma and cc_wrapper can't be used together.") + + # When the invoker has explicitly overridden use_goma or cc_wrapper in the + # toolchain args, use those values, otherwise default to the global one. + # This works because the only reasonable override that toolchains might + # supply for these values are to force-disable them. + # But if needs_gomacc_path_arg is set in a Chrome OS build, the toolchain + # wrapper will have picked up gomacc via cmd-line arg. So need to prepend + # gomacc in that case. + goma_path = "$goma_dir/gomacc" + if (toolchain_uses_remoteexec && + (!defined(invoker_toolchain_args.needs_gomacc_path_arg) || + !invoker_toolchain_args.needs_gomacc_path_arg)) { + if (defined(toolchain_args.rbe_cc_cfg_file)) { + toolchain_rbe_cc_cfg_file = toolchain_args.rbe_cc_cfg_file + } else { + toolchain_rbe_cc_cfg_file = rbe_cc_cfg_file + } + + # C/C++ (clang) rewrapper prefix to use when use_remoteexec is true. + compiler_prefix = "${rbe_bin_dir}/rewrapper -cfg=${toolchain_rbe_cc_cfg_file} -exec_root=${rbe_exec_root} " + } else if (toolchain_uses_goma && + (!defined(invoker_toolchain_args.needs_gomacc_path_arg) || + !invoker_toolchain_args.needs_gomacc_path_arg)) { + compiler_prefix = "${goma_path} " + if (use_goma_rust) { + rust_compiler_prefix = compiler_prefix + } + } else { + compiler_prefix = "${toolchain_cc_wrapper} " + } + + if (use_goma_thin_lto && toolchain_uses_goma && use_thin_lto) { + # goma_ld.py uses autoninja in an attempt to set a reasonable + # number of jobs, but this results in too low a value on + # Chrome OS builders. So we pass in an explicit value. + link_prefix = + "$python_path " + + rebase_path("//tools/clang/scripts/goma_ld.py", root_build_dir) + + " --gomacc ${goma_path} --jobs 200 -- " + } else { + link_prefix = "" + not_needed([ "goma_path" ]) + } + + # Create a distinct variable for "asm", since coverage runs pass a bunch of + # flags to clang/clang++ that are nonsensical on assembler runs. + asm_prefix = compiler_prefix + + # A specific toolchain may wish to avoid coverage instrumentation, so we + # allow the global "use_clang_coverage" arg to be overridden. + if (defined(toolchain_args.use_clang_coverage)) { + toolchain_use_clang_coverage = toolchain_args.use_clang_coverage + } else { + toolchain_use_clang_coverage = use_clang_coverage + } + + # For a coverage build, we use the wrapper script globally so that it can + # remove coverage cflags from files that should not have them. + if (toolchain_use_clang_coverage) { + # "coverage_instrumentation_input_file" is set in args.gn, but it can be + # overridden by a toolchain config. + if (defined(toolchain_args.coverage_instrumentation_input_file)) { + toolchain_coverage_instrumentation_input_file = + toolchain_args.coverage_instrumentation_input_file + } else { + toolchain_coverage_instrumentation_input_file = + coverage_instrumentation_input_file + } + + _coverage_wrapper = + rebase_path("//build/toolchain/clang_code_coverage_wrapper.py", + root_build_dir) + + # The wrapper needs to know what OS we target because it uses that to + # select a list of files that should not be instrumented. + _coverage_wrapper = _coverage_wrapper + " --target-os=" + + invoker_toolchain_args.current_os + + # We want to instrument everything if there is no input file set. + # If there is a file we need to give it to the wrapper script so it can + # instrument only those files. + if (toolchain_coverage_instrumentation_input_file != "") { + _coverage_wrapper = + _coverage_wrapper + " --files-to-instrument=" + + rebase_path(toolchain_coverage_instrumentation_input_file, + root_build_dir) + } + compiler_prefix = "$python_path ${_coverage_wrapper} " + compiler_prefix + } + + cc = compiler_prefix + invoker.cc + cxx = compiler_prefix + invoker.cxx + asm = asm_prefix + invoker.cc + ar = invoker.ar + ld = link_prefix + invoker.ld + if (defined(invoker.readelf)) { + readelf = invoker.readelf + } else { + readelf = "readelf" + } + if (defined(invoker.nm)) { + nm = invoker.nm + } else { + nm = "nm" + } + if (defined(invoker.dwp)) { + dwp_switch = " --dwp=\"${invoker.dwp}\"" + } else { + dwp_switch = "" + } + + if (defined(invoker.shlib_extension)) { + default_shlib_extension = invoker.shlib_extension + } else { + default_shlib_extension = shlib_extension + } + + if (defined(invoker.default_shlib_subdir)) { + default_shlib_subdir = invoker.default_shlib_subdir + } else { + default_shlib_subdir = "" + } + + if (defined(invoker.executable_extension)) { + default_executable_extension = invoker.executable_extension + } else { + default_executable_extension = "" + } + + # Bring these into our scope for string interpolation with default values. + if (defined(invoker.extra_cflags) && invoker.extra_cflags != "") { + extra_cflags = " " + invoker.extra_cflags + } else { + extra_cflags = "" + } + + if (defined(invoker.extra_cppflags) && invoker.extra_cppflags != "") { + extra_cppflags = " " + invoker.extra_cppflags + } else { + extra_cppflags = "" + } + + if (defined(invoker.extra_cxxflags) && invoker.extra_cxxflags != "") { + extra_cxxflags = " " + invoker.extra_cxxflags + } else { + extra_cxxflags = "" + } + + if (defined(invoker.extra_asmflags) && invoker.extra_asmflags != "") { + extra_asmflags = " " + invoker.extra_asmflags + } else { + extra_asmflags = "" + } + + if (defined(invoker.extra_ldflags) && invoker.extra_ldflags != "") { + extra_ldflags = " " + invoker.extra_ldflags + } else { + extra_ldflags = "" + } + + enable_linker_map = defined(invoker.enable_linker_map) && + invoker.enable_linker_map && generate_linker_map + + # These library switches can apply to all tools below. + lib_switch = "-l" + lib_dir_switch = "-L" + + # Object files go in this directory. + object_subdir = "{{target_out_dir}}/{{label_name}}" + + tool("cc") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cc -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}${extra_cppflags}${extra_cflags} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "CC {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("cxx") { + depfile = "{{output}}.d" + precompiled_header_type = "gcc" + command = "$cxx -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}${extra_cppflags}${extra_cxxflags} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "CXX {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("asm") { + # For GCC we can just use the C compiler to compile assembly. + depfile = "{{output}}.d" + command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}" + depsformat = "gcc" + description = "ASM {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.o" ] + } + + tool("alink") { + if (current_os == "aix") { + # AIX does not support either -D (deterministic output) or response + # files. + command = "$ar -X64 {{arflags}} -r -c -s {{output}} {{inputs}}" + } else { + rspfile = "{{output}}.rsp" + rspfile_content = "{{inputs}}" + command = "\"$ar\" {{arflags}} -r -c -s -D {{output}} @\"$rspfile\"" + } + + # Remove the output file first so that ar doesn't try to modify the + # existing file. + if (host_os == "win") { + tool_wrapper_path = + rebase_path("//build/toolchain/win/tool_wrapper.py", root_build_dir) + command = "cmd /c $python_path $tool_wrapper_path delete-file {{output}} && $command" + } else { + command = "rm -f {{output}} && $command" + } + + # Almost all targets build with //build/config/compiler:thin_archive which + # adds -T to arflags. + description = "AR {{output}}" + outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ] + + # Shared libraries go in the target out directory by default so we can + # generate different targets with the same name and not have them collide. + default_output_dir = "{{target_out_dir}}" + default_output_extension = ".a" + output_prefix = "lib" + } + + tool("solink") { + soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so". + sofile = "{{output_dir}}/$soname" # Possibly including toolchain dir. + rspfile = sofile + ".rsp" + + pool = "//build/toolchain:link_pool($default_toolchain)" + + if (defined(invoker.strip)) { + unstripped_sofile = "{{root_out_dir}}/lib.unstripped/$soname" + } else { + unstripped_sofile = sofile + } + + # These variables are not built into GN but are helpers that + # implement (1) linking to produce a .so, (2) extracting the symbols + # from that file (3) if the extracted list differs from the existing + # .TOC file, overwrite it, otherwise, don't change it. + tocfile = sofile + ".TOC" + + soname_flag = "" + if (current_os != "aix") { + # -soname flag is not available on aix ld + soname_flag = "-Wl,-soname=\"$soname\"" + } + link_command = "$ld -shared $soname_flag {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" @\"$rspfile\"" + + # Generate a map file to be used for binary size analysis. + # Map file adds ~10% to the link time on a z620. + # With target_os="android", libchrome.so.map.gz is ~20MB. + map_switch = "" + if (enable_linker_map) { + map_file = "$unstripped_sofile.map.gz" + map_switch = " --map-file \"$map_file\"" + } + + assert(defined(readelf), "to solink you must have a readelf") + assert(defined(nm), "to solink you must have an nm") + strip_switch = "" + if (defined(invoker.strip)) { + strip_switch = "--strip=${invoker.strip} " + } + + # This needs a Python script to avoid using a complex shell command + # requiring sh control structures, pipelines, and POSIX utilities. + # The host might not have a POSIX shell and utilities (e.g. Windows). + solink_wrapper = + rebase_path("//build/toolchain/gcc_solink_wrapper.py", root_build_dir) + solink_extra_flags = "" + if (current_os == "aix") { + # to be intercepted by solink_wrapper + solink_extra_flags = "--link-only" + } + command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch$dwp_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\"$map_switch --output=\"$sofile\" -- $link_command $solink_extra_flags" + + if (target_cpu == "mipsel" && is_component_build && is_android) { + rspfile_content = "-Wl,--start-group -Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}} -Wl,--end-group {{rlibs}}" + } else if (current_os == "aix") { + # --whole-archive, --no-whole-archive flags are not available on the aix ld. + rspfile_content = "{{inputs}} {{solibs}} {{libs}} {{rlibs}}" + } else { + rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}} {{rlibs}}" + } + + description = "SOLINK $sofile" + + # Use this for {{output_extension}} expansions unless a target manually + # overrides it (in which case {{output_extension}} will be what the target + # specifies). + default_output_extension = default_shlib_extension + + default_output_dir = "{{root_out_dir}}${default_shlib_subdir}" + + output_prefix = "lib" + + # Since the above commands only updates the .TOC file when it changes, ask + # Ninja to check if the timestamp actually changed to know if downstream + # dependencies should be recompiled. + restat = true + + # Tell GN about the output files. It will link to the sofile but use the + # tocfile for dependency management. + outputs = [ + sofile, + tocfile, + ] + if (sofile != unstripped_sofile) { + outputs += [ unstripped_sofile ] + if (defined(invoker.use_unstripped_as_runtime_outputs) && + invoker.use_unstripped_as_runtime_outputs) { + runtime_outputs = [ unstripped_sofile ] + } + } + + # Clank build will generate DWP files when Fission is used. + # Other builds generate DWP files outside of the gn link targets, if at + # all. + if (defined(invoker.dwp)) { + outputs += [ unstripped_sofile + ".dwp" ] + if (defined(invoker.use_unstripped_as_runtime_outputs) && + invoker.use_unstripped_as_runtime_outputs) { + runtime_outputs += [ unstripped_sofile + ".dwp" ] + } + } + if (defined(map_file)) { + outputs += [ map_file ] + } + link_output = sofile + depend_output = tocfile + } + + tool("solink_module") { + soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so". + sofile = "{{output_dir}}/$soname" + rspfile = sofile + ".rsp" + + pool = "//build/toolchain:link_pool($default_toolchain)" + + if (defined(invoker.strip)) { + unstripped_sofile = "{{root_out_dir}}/lib.unstripped/$soname" + } else { + unstripped_sofile = sofile + } + + soname_flag = "" + whole_archive_flag = "" + no_whole_archive_flag = "" + if (current_os != "aix") { + # -soname, --whole-archive, --no-whole-archive flags are not available on aix ld + soname_flag = "-Wl,-soname=\"$soname\"" + whole_archive_flag = "-Wl,--whole-archive" + no_whole_archive_flag = "-Wl,--no-whole-archive" + } + command = "$ld -shared {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" $soname_flag @\"$rspfile\"" + + if (defined(invoker.strip)) { + strip_command = "${invoker.strip} -o \"$sofile\" \"$unstripped_sofile\"" + command += " && " + strip_command + } + rspfile_content = "$whole_archive_flag {{inputs}} {{solibs}} $no_whole_archive_flag {{libs}} {{rlibs}}" + + description = "SOLINK_MODULE $sofile" + + # Use this for {{output_extension}} expansions unless a target manually + # overrides it (in which case {{output_extension}} will be what the target + # specifies). + if (defined(invoker.loadable_module_extension)) { + default_output_extension = invoker.loadable_module_extension + } else { + default_output_extension = default_shlib_extension + } + + default_output_dir = "{{root_out_dir}}${default_shlib_subdir}" + + output_prefix = "lib" + + outputs = [ sofile ] + if (sofile != unstripped_sofile) { + outputs += [ unstripped_sofile ] + if (defined(invoker.use_unstripped_as_runtime_outputs) && + invoker.use_unstripped_as_runtime_outputs) { + runtime_outputs = [ unstripped_sofile ] + } + } + } + + tool("link") { + exename = "{{target_output_name}}{{output_extension}}" + outfile = "{{output_dir}}/$exename" + rspfile = "$outfile.rsp" + unstripped_outfile = outfile + + pool = "//build/toolchain:link_pool($default_toolchain)" + + # Use this for {{output_extension}} expansions unless a target manually + # overrides it (in which case {{output_extension}} will be what the target + # specifies). + default_output_extension = default_executable_extension + + default_output_dir = "{{root_out_dir}}" + + if (defined(invoker.strip)) { + unstripped_outfile = "{{root_out_dir}}/exe.unstripped/$exename" + } + + start_group_flag = "" + end_group_flag = "" + if (current_os != "aix") { + # the "--start-group .. --end-group" feature isn't available on the aix ld. + start_group_flag = "-Wl,--start-group" + end_group_flag = "-Wl,--end-group " + } + link_command = "$ld {{ldflags}}${extra_ldflags} -o \"$unstripped_outfile\" $start_group_flag @\"$rspfile\" {{solibs}} $end_group_flag {{libs}}" + + # Generate a map file to be used for binary size analysis. + # Map file adds ~10% to the link time on a z620. + # With target_os="android", libchrome.so.map.gz is ~20MB. + map_switch = "" + if (enable_linker_map) { + map_file = "$unstripped_outfile.map.gz" + map_switch = " --map-file \"$map_file\"" + } + + strip_switch = "" + if (defined(invoker.strip)) { + strip_switch = " --strip=\"${invoker.strip}\" --unstripped-file=\"$unstripped_outfile\"" + } + + link_wrapper = + rebase_path("//build/toolchain/gcc_link_wrapper.py", root_build_dir) + command = "$python_path \"$link_wrapper\" --output=\"$outfile\"$strip_switch$map_switch$dwp_switch -- $link_command" + + description = "LINK $outfile" + rspfile_content = "{{inputs}} {{rlibs}}" + outputs = [ outfile ] + if (outfile != unstripped_outfile) { + outputs += [ unstripped_outfile ] + if (defined(invoker.use_unstripped_as_runtime_outputs) && + invoker.use_unstripped_as_runtime_outputs) { + runtime_outputs = [ unstripped_outfile ] + } + } + + # Clank build will generate DWP files when Fission is used. + # Other builds generate DWP files outside of the gn link targets, if at + # all. + if (defined(invoker.dwp)) { + outputs += [ unstripped_outfile + ".dwp" ] + if (defined(invoker.use_unstripped_as_runtime_outputs) && + invoker.use_unstripped_as_runtime_outputs) { + runtime_outputs += [ unstripped_outfile + ".dwp" ] + } + } + if (defined(invoker.link_outputs)) { + outputs += invoker.link_outputs + } + if (defined(map_file)) { + outputs += [ map_file ] + } + } + + # These two are really entirely generic, but have to be repeated in + # each toolchain because GN doesn't allow a template to be used here. + # See //build/toolchain/toolchain.gni for details. + tool("stamp") { + command = stamp_command + description = stamp_description + } + tool("copy") { + command = copy_command + description = copy_description + } + + tool("action") { + pool = "//build/toolchain:action_pool($default_toolchain)" + } + + if (toolchain_has_rust) { + rustc = "" + if (defined(rust_compiler_prefix)) { + rustc += rust_compiler_prefix + } + rustc += rust_prefix + rustc += "rustc" + + # RSP files not used due to https://bugs.chromium.org/p/gn/issues/detail?id=249 + tool("rust_staticlib") { + rust_outfile = "{{target_out_dir}}/{{crate_name}}.a" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -Clinker=$ld -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_rlib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -Clinker=$ld -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + if (rustc_can_link) { + tool("rust_bin") { + rust_outfile = "{{root_out_dir}}/{{crate_name}}" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -Clinker=$ld -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_cdylib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.so" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -Clinker=$ld -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_macro") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.so" + depfile = "{{output}}.d" + command = "$rustc $rustc_common_args --emit=dep-info=$depfile,link -Clinker=$ld -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + } + } + + forward_variables_from(invoker, [ "deps" ]) + } +} + +# This is a shorthand for gcc_toolchain instances based on the Chromium-built +# version of Clang. Only the toolchain_cpu and toolchain_os variables need to +# be specified by the invoker, and optionally toolprefix if it's a +# cross-compile case. Note that for a cross-compile case this toolchain +# requires a config to pass the appropriate -target option, or else it will +# actually just be doing a native compile. The invoker can optionally override +# use_gold too. +template("clang_toolchain") { + if (defined(invoker.toolprefix)) { + toolprefix = invoker.toolprefix + } else { + toolprefix = "" + } + + gcc_toolchain(target_name) { + prefix = rebase_path("$clang_base_path/bin", root_build_dir) + cc = "$prefix/clang" + cxx = "$prefix/clang++" + ld = cxx + readelf = "${toolprefix}readelf" + ar = "${prefix}/llvm-ar" + nm = "${prefix}/llvm-nm" + + forward_variables_from(invoker, + [ + "strip", + "default_shlib_subdir", + "enable_linker_map", + "use_unstripped_as_runtime_outputs", + ]) + + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + is_clang = true + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/get_concurrent_links.py b/third_party/libwebrtc/build/toolchain/get_concurrent_links.py new file mode 100755 index 0000000000..d77bb4375e --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/get_concurrent_links.py @@ -0,0 +1,134 @@ +#!/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 script computs the number of concurrent links we want to run in the build +# as a function of machine spec. It's based on GetDefaultConcurrentLinks in GYP. + +from __future__ import print_function + +import argparse +import multiprocessing +import os +import re +import subprocess +import sys + +sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..')) +import gn_helpers + + +def _GetTotalMemoryInBytes(): + if sys.platform in ('win32', 'cygwin'): + import ctypes + + class MEMORYSTATUSEX(ctypes.Structure): + _fields_ = [ + ("dwLength", ctypes.c_ulong), + ("dwMemoryLoad", ctypes.c_ulong), + ("ullTotalPhys", ctypes.c_ulonglong), + ("ullAvailPhys", ctypes.c_ulonglong), + ("ullTotalPageFile", ctypes.c_ulonglong), + ("ullAvailPageFile", ctypes.c_ulonglong), + ("ullTotalVirtual", ctypes.c_ulonglong), + ("ullAvailVirtual", ctypes.c_ulonglong), + ("sullAvailExtendedVirtual", ctypes.c_ulonglong), + ] + + stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX)) + ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) + return stat.ullTotalPhys + elif sys.platform.startswith('linux'): + if os.path.exists("/proc/meminfo"): + with open("/proc/meminfo") as meminfo: + memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') + for line in meminfo: + match = memtotal_re.match(line) + if not match: + continue + return float(match.group(1)) * 2**10 + elif sys.platform == 'darwin': + try: + return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize'])) + except Exception: + return 0 + # TODO(scottmg): Implement this for other platforms. + return 0 + + +def _GetDefaultConcurrentLinks(per_link_gb, reserve_gb, thin_lto_type, + secondary_per_link_gb): + explanation = [] + explanation.append( + 'per_link_gb={} reserve_gb={} secondary_per_link_gb={}'.format( + per_link_gb, reserve_gb, secondary_per_link_gb)) + mem_total_gb = float(_GetTotalMemoryInBytes()) / 2**30 + mem_total_gb = max(0, mem_total_gb - reserve_gb) + mem_cap = int(max(1, mem_total_gb / per_link_gb)) + + try: + cpu_count = multiprocessing.cpu_count() + except: + cpu_count = 1 + + # A local LTO links saturate all cores, but only for some amount of the link. + # Goma LTO runs LTO codegen on goma, only run one of these tasks at once. + cpu_cap = cpu_count + if thin_lto_type is not None: + if thin_lto_type == 'goma': + cpu_cap = 1 + else: + assert thin_lto_type == 'local' + cpu_cap = min(cpu_count, 6) + + explanation.append('cpu_count={} cpu_cap={} mem_total_gb={:.1f}GiB'.format( + cpu_count, cpu_cap, mem_total_gb)) + + num_links = min(mem_cap, cpu_cap) + if num_links == cpu_cap: + if cpu_cap == cpu_count: + reason = 'cpu_count' + else: + reason = 'cpu_cap (thinlto)' + else: + reason = 'RAM' + + explanation.append('concurrent_links={} (reason: {})'.format( + num_links, reason)) + + # See if there is RAM leftover for a secondary pool. + if secondary_per_link_gb and num_links == mem_cap: + mem_remaining = mem_total_gb - mem_cap * per_link_gb + secondary_size = int(max(0, mem_remaining / secondary_per_link_gb)) + explanation.append('secondary_size={} (mem_remaining={:.1f}GiB)'.format( + secondary_size, mem_remaining)) + else: + secondary_size = 0 + + return num_links, secondary_size, explanation + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--mem_per_link_gb', type=int, default=8) + parser.add_argument('--reserve_mem_gb', type=int, default=0) + parser.add_argument('--secondary_mem_per_link', type=int, default=0) + parser.add_argument('--thin-lto') + options = parser.parse_args() + + primary_pool_size, secondary_pool_size, explanation = ( + _GetDefaultConcurrentLinks(options.mem_per_link_gb, + options.reserve_mem_gb, options.thin_lto, + options.secondary_mem_per_link)) + sys.stdout.write( + gn_helpers.ToGNString({ + 'primary_pool_size': primary_pool_size, + 'secondary_pool_size': secondary_pool_size, + 'explanation': explanation, + })) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/get_cpu_count.py b/third_party/libwebrtc/build/toolchain/get_cpu_count.py new file mode 100644 index 0000000000..765c7c78f6 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/get_cpu_count.py @@ -0,0 +1,23 @@ +# Copyright 2018 The Chromium 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 shows cpu count to specify capacity of action pool. + +from __future__ import print_function + +import multiprocessing +import sys + +def main(): + try: + cpu_count = multiprocessing.cpu_count() + except: + cpu_count = 1 + + print(cpu_count) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/get_goma_dir.py b/third_party/libwebrtc/build/toolchain/get_goma_dir.py new file mode 100644 index 0000000000..114da6c0ea --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/get_goma_dir.py @@ -0,0 +1,42 @@ +# Copyright 2020 The Chromium 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 gets default goma_dir for depot_tools goma. + +import os +import sys + + +def main(): + gomacc = 'gomacc' + candidates = [] + if sys.platform in ['win32', 'cygwin']: + gomacc = 'gomacc.exe' + + for path in os.environ.get('PATH', '').split(os.pathsep): + # normpath() required to strip trailing slash when present. + if os.path.basename(os.path.normpath(path)) == 'depot_tools': + candidates.append(os.path.join(path, '.cipd_bin')) + + for d in candidates: + if os.path.isfile(os.path.join(d, gomacc)): + sys.stdout.write(d) + return 0 + # mb analyze step set use_goma=true, but goma_dir="", + # and bot doesn't have goma in default locataion above. + # to mitigate this, just use initial depot_tools path + # or default path as before (if depot_tools doesn't exist + # in PATH). + # TODO(ukai): crbug.com/1073276: fix mb analyze step and make it hard error? + if sys.platform in ['win32', 'cygwin']: + sys.stdout.write('C:\\src\\goma\\goma-win64') + elif 'GOMA_DIR' in os.environ: + sys.stdout.write(os.environ.get('GOMA_DIR')) + else: + sys.stdout.write(os.path.join(os.environ.get('HOME', ''), 'goma')) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/goma.gni b/third_party/libwebrtc/build/toolchain/goma.gni new file mode 100644 index 0000000000..56787f1b9b --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/goma.gni @@ -0,0 +1,35 @@ +# 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. + +# Defines the configuration of Goma. + +declare_args() { + # Set to true to enable distributed compilation using Goma. + use_goma = false + + # This flag is for ChromeOS compiler wrapper. + # By passing gomacc path via cmd-line arg, ChromeOS' compiler wrapper + # invokes gomacc inside it. + needs_gomacc_path_arg = false + + # Absolute directory containing the gomacc binary. + goma_dir = "" +} + +if (use_goma && goma_dir == "") { + goma_dir = exec_script("get_goma_dir.py", [], "string") +} + +declare_args() { + # TODO(crbug.com/726475): true if use_goma = true in the future. + use_java_goma = false + + # Deprecated and ignored as Goma RBE is now the default. Still exists + # to avoid breaking the build on the bots. Will be removed when all + # bots have been configured to not set this variable. + ios_use_goma_rbe = -1 +} + +assert(!is_win || !use_goma || is_clang, + "cl.exe does not work on goma, use clang") diff --git a/third_party/libwebrtc/build/toolchain/ios/BUILD.gn b/third_party/libwebrtc/build/toolchain/ios/BUILD.gn new file mode 100644 index 0000000000..4c9cc586d2 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/ios/BUILD.gn @@ -0,0 +1,113 @@ +# Copyright 2021 The Chromium 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/ios/ios_sdk.gni") +import("//build/toolchain/apple/toolchain.gni") + +# Specialisation of the apple_toolchain template to declare the toolchain +# and its tools to build target for iOS platform. +template("ios_toolchain") { + assert(defined(invoker.toolchain_args), + "Toolchains must declare toolchain_args") + + apple_toolchain(target_name) { + forward_variables_from(invoker, "*", [ "toolchain_args" ]) + + sdk_developer_dir = ios_sdk_developer_dir + deployment_target = ios_deployment_target + sdk_name = ios_sdk_name + bin_path = ios_bin_path + strip_with_lipo = true + + toolchain_args = { + forward_variables_from(invoker.toolchain_args, "*") + current_os = "ios" + } + } +} + +ios_toolchain("ios_clang_arm64") { + toolchain_args = { + target_cpu = "arm64" + } +} + +ios_toolchain("ios_clang_arm64_13_0") { + toolchain_args = { + target_cpu = "arm64" + ios_deployment_target = "13.0" + } +} + +ios_toolchain("ios_clang_arm64_14_0") { + toolchain_args = { + target_cpu = "arm64" + ios_deployment_target = "14.0" + } +} + +ios_toolchain("ios_clang_arm64_fat_arm") { + toolchain_args = { + target_cpu = "arm" + is_fat_secondary_toolchain = true + primary_fat_toolchain_name = "//build/toolchain/ios:ios_clang_arm64" + } +} + +ios_toolchain("ios_clang_arm") { + toolchain_args = { + target_cpu = "arm" + } +} + +ios_toolchain("ios_clang_x64") { + toolchain_args = { + target_cpu = "x64" + } +} + +ios_toolchain("ios_clang_x64_fat_arm64") { + toolchain_args = { + target_cpu = "arm64" + is_fat_secondary_toolchain = true + primary_fat_toolchain_name = "//build/toolchain/ios:ios_clang_x64" + } +} + +ios_toolchain("ios_clang_x64_13_0") { + toolchain_args = { + target_cpu = "x64" + ios_deployment_target = "13.0" + } +} + +ios_toolchain("ios_clang_x64_14_0") { + toolchain_args = { + target_cpu = "x64" + ios_deployment_target = "14.0" + } +} + +ios_toolchain("ios_clang_x64_14_0_fat_arm64") { + toolchain_args = { + target_cpu = "arm64" + ios_deployment_target = "14.0" + is_fat_secondary_toolchain = true + primary_fat_toolchain_name = "//build/toolchain/ios:ios_clang_x64_14_0" + } +} + +ios_toolchain("ios_clang_x64_fat_x86") { + toolchain_args = { + target_cpu = "x86" + is_fat_secondary_toolchain = true + primary_fat_toolchain_name = "//build/toolchain/ios:ios_clang_x64" + } +} + +ios_toolchain("ios_clang_x86") { + toolchain_args = { + target_cpu = "x86" + } +} diff --git a/third_party/libwebrtc/build/toolchain/ios/OWNERS b/third_party/libwebrtc/build/toolchain/ios/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/ios/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/toolchain/ios/compile_xcassets.py b/third_party/libwebrtc/build/toolchain/ios/compile_xcassets.py new file mode 100644 index 0000000000..8b86713d14 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/ios/compile_xcassets.py @@ -0,0 +1,258 @@ +# 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. + +"""Wrapper around actool to compile assets catalog. + +The script compile_xcassets.py is a wrapper around actool to compile +assets catalog to Assets.car that turns warning into errors. It also +fixes some quirks of actool to make it work from ninja (mostly that +actool seems to require absolute path but gn generates command-line +with relative paths). + +The wrapper filter out any message that is not a section header and +not a warning or error message, and fails if filtered output is not +empty. This should to treat all warnings as error until actool has +an option to fail with non-zero error code when there are warnings. +""" + +import argparse +import os +import re +import shutil +import subprocess +import sys +import tempfile + +# Pattern matching a section header in the output of actool. +SECTION_HEADER = re.compile('^/\\* ([^ ]*) \\*/$') + +# Name of the section containing informational messages that can be ignored. +NOTICE_SECTION = 'com.apple.actool.compilation-results' + +# Map special type of asset catalog to the corresponding command-line +# parameter that need to be passed to actool. +ACTOOL_FLAG_FOR_ASSET_TYPE = { + '.appiconset': '--app-icon', + '.launchimage': '--launch-image', +} + +def FixAbsolutePathInLine(line, relative_paths): + """Fix absolute paths present in |line| to relative paths.""" + absolute_path = line.split(':')[0] + relative_path = relative_paths.get(absolute_path, absolute_path) + if absolute_path == relative_path: + return line + return relative_path + line[len(absolute_path):] + + +def FilterCompilerOutput(compiler_output, relative_paths): + """Filers actool compilation output. + + The compiler output is composed of multiple sections for each different + level of output (error, warning, notices, ...). Each section starts with + the section name on a single line, followed by all the messages from the + section. + + The function filter any lines that are not in com.apple.actool.errors or + com.apple.actool.document.warnings sections (as spurious messages comes + before any section of the output). + + See crbug.com/730054, crbug.com/739163 and crbug.com/770634 for some example + messages that pollute the output of actool and cause flaky builds. + + Args: + compiler_output: string containing the output generated by the + compiler (contains both stdout and stderr) + relative_paths: mapping from absolute to relative paths used to + convert paths in the warning and error messages (unknown paths + will be left unaltered) + + Returns: + The filtered output of the compiler. If the compilation was a + success, then the output will be empty, otherwise it will use + relative path and omit any irrelevant output. + """ + + filtered_output = [] + current_section = None + data_in_section = False + for line in compiler_output.splitlines(): + match = SECTION_HEADER.search(line) + if match is not None: + data_in_section = False + current_section = match.group(1) + continue + if current_section and current_section != NOTICE_SECTION: + if not data_in_section: + data_in_section = True + filtered_output.append('/* %s */\n' % current_section) + + fixed_line = FixAbsolutePathInLine(line, relative_paths) + filtered_output.append(fixed_line + '\n') + + return ''.join(filtered_output) + + +def CompileAssetCatalog(output, platform, product_type, min_deployment_target, + inputs, compress_pngs, partial_info_plist): + """Compile the .xcassets bundles to an asset catalog using actool. + + Args: + output: absolute path to the containing bundle + platform: the targeted platform + product_type: the bundle type + min_deployment_target: minimum deployment target + inputs: list of absolute paths to .xcassets bundles + compress_pngs: whether to enable compression of pngs + partial_info_plist: path to partial Info.plist to generate + """ + command = [ + 'xcrun', + 'actool', + '--output-format=human-readable-text', + '--notices', + '--warnings', + '--errors', + '--platform', + platform, + '--minimum-deployment-target', + min_deployment_target, + ] + + if compress_pngs: + command.extend(['--compress-pngs']) + + if product_type != '': + command.extend(['--product-type', product_type]) + + if platform == 'macosx': + command.extend(['--target-device', 'mac']) + else: + command.extend(['--target-device', 'iphone', '--target-device', 'ipad']) + + # Scan the input directories for the presence of asset catalog types that + # require special treatment, and if so, add them to the actool command-line. + for relative_path in inputs: + + if not os.path.isdir(relative_path): + continue + + for file_or_dir_name in os.listdir(relative_path): + if not os.path.isdir(os.path.join(relative_path, file_or_dir_name)): + continue + + asset_name, asset_type = os.path.splitext(file_or_dir_name) + if asset_type not in ACTOOL_FLAG_FOR_ASSET_TYPE: + continue + + command.extend([ACTOOL_FLAG_FOR_ASSET_TYPE[asset_type], asset_name]) + + # Always ask actool to generate a partial Info.plist file. If no path + # has been given by the caller, use a temporary file name. + temporary_file = None + if not partial_info_plist: + temporary_file = tempfile.NamedTemporaryFile(suffix='.plist') + partial_info_plist = temporary_file.name + + command.extend(['--output-partial-info-plist', partial_info_plist]) + + # Dictionary used to convert absolute paths back to their relative form + # in the output of actool. + relative_paths = {} + + # actool crashes if paths are relative, so convert input and output paths + # to absolute paths, and record the relative paths to fix them back when + # filtering the output. + absolute_output = os.path.abspath(output) + relative_paths[output] = absolute_output + relative_paths[os.path.dirname(output)] = os.path.dirname(absolute_output) + command.extend(['--compile', os.path.dirname(os.path.abspath(output))]) + + for relative_path in inputs: + absolute_path = os.path.abspath(relative_path) + relative_paths[absolute_path] = relative_path + command.append(absolute_path) + + try: + # Run actool and redirect stdout and stderr to the same pipe (as actool + # is confused about what should go to stderr/stdout). + process = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout = process.communicate()[0].decode('utf-8') + + # If the invocation of `actool` failed, copy all the compiler output to + # the standard error stream and exit. See https://crbug.com/1205775 for + # example of compilation that failed with no error message due to filter. + if process.returncode: + for line in stdout.splitlines(): + fixed_line = FixAbsolutePathInLine(line, relative_paths) + sys.stderr.write(fixed_line + '\n') + sys.exit(1) + + # Filter the output to remove all garbage and to fix the paths. If the + # output is not empty after filtering, then report the compilation as a + # failure (as some version of `actool` report error to stdout, yet exit + # with an return code of zero). + stdout = FilterCompilerOutput(stdout, relative_paths) + if stdout: + sys.stderr.write(stdout) + sys.exit(1) + + finally: + if temporary_file: + temporary_file.close() + + +def Main(): + parser = argparse.ArgumentParser( + description='compile assets catalog for a bundle') + parser.add_argument('--platform', + '-p', + required=True, + choices=('macosx', 'iphoneos', 'iphonesimulator'), + help='target platform for the compiled assets catalog') + parser.add_argument( + '--minimum-deployment-target', + '-t', + required=True, + help='minimum deployment target for the compiled assets catalog') + parser.add_argument('--output', + '-o', + required=True, + help='path to the compiled assets catalog') + parser.add_argument('--compress-pngs', + '-c', + action='store_true', + default=False, + help='recompress PNGs while compiling assets catalog') + parser.add_argument('--product-type', + '-T', + help='type of the containing bundle') + parser.add_argument('--partial-info-plist', + '-P', + help='path to partial info plist to create') + parser.add_argument('inputs', + nargs='+', + help='path to input assets catalog sources') + args = parser.parse_args() + + if os.path.basename(args.output) != 'Assets.car': + sys.stderr.write('output should be path to compiled asset catalog, not ' + 'to the containing bundle: %s\n' % (args.output, )) + sys.exit(1) + + if os.path.exists(args.output): + if os.path.isfile(args.output): + os.unlink(args.output) + else: + shutil.rmtree(args.output) + + CompileAssetCatalog(args.output, args.platform, args.product_type, + args.minimum_deployment_target, args.inputs, + args.compress_pngs, args.partial_info_plist) + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/third_party/libwebrtc/build/toolchain/ios/swiftc.py b/third_party/libwebrtc/build/toolchain/ios/swiftc.py new file mode 100644 index 0000000000..a08dc8d129 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/ios/swiftc.py @@ -0,0 +1,174 @@ +# Copyright 2020 The Chromium 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 collections +import json +import os +import subprocess +import sys +import tempfile + + +class OrderedSet(collections.OrderedDict): + def add(self, value): + self[value] = True + + +def compile_module(module, sources, settings, extras, tmpdir): + output_file_map = {} + if settings.whole_module_optimization: + output_file_map[''] = { + 'object': os.path.join(settings.object_dir, module + '.o'), + 'dependencies': os.path.join(tmpdir, module + '.d'), + } + else: + for source in sources: + name, _ = os.path.splitext(os.path.basename(source)) + output_file_map[source] = { + 'object': os.path.join(settings.object_dir, name + '.o'), + 'dependencies': os.path.join(tmpdir, name + '.d'), + } + + for key in ('module_path', 'header_path', 'depfile'): + path = getattr(settings, key) + if os.path.exists(path): + os.unlink(path) + if key == 'module_path': + for ext in '.swiftdoc', '.swiftsourceinfo': + path = os.path.splitext(getattr(settings, key))[0] + ext + if os.path.exists(path): + os.unlink(path) + directory = os.path.dirname(path) + if not os.path.exists(directory): + os.makedirs(directory) + + if not os.path.exists(settings.object_dir): + os.makedirs(settings.object_dir) + + for key in output_file_map: + path = output_file_map[key]['object'] + if os.path.exists(path): + os.unlink(path) + + output_file_map_path = os.path.join(tmpdir, module + '.json') + with open(output_file_map_path, 'w') as output_file_map_file: + output_file_map_file.write(json.dumps(output_file_map)) + output_file_map_file.flush() + + extra_args = [] + if settings.bridge_header: + extra_args.extend([ + '-import-objc-header', + os.path.abspath(settings.bridge_header), + ]) + + if settings.whole_module_optimization: + extra_args.append('-whole-module-optimization') + + if settings.target: + extra_args.extend([ + '-target', + settings.target, + ]) + + if settings.sdk: + extra_args.extend([ + '-sdk', + os.path.abspath(settings.sdk), + ]) + + if settings.swift_version: + extra_args.extend([ + '-swift-version', + settings.swift_version, + ]) + + if settings.include_dirs: + for include_dir in settings.include_dirs: + extra_args.append('-I' + include_dir) + + process = subprocess.Popen([ + 'swiftc', + '-parse-as-library', + '-module-name', + module, + '-emit-object', + '-emit-dependencies', + '-emit-module', + '-emit-module-path', + settings.module_path, + '-emit-objc-header', + '-emit-objc-header-path', + settings.header_path, + '-output-file-map', + output_file_map_path, + ] + extra_args + extras + sources, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + + stdout, stderr = process.communicate() + if process.returncode: + sys.stdout.write(stdout) + sys.stderr.write(stderr) + sys.exit(process.returncode) + + depfile_content = collections.OrderedDict() + for key in output_file_map: + for line in open(output_file_map[key]['dependencies']): + output, inputs = line.split(' : ', 2) + _, ext = os.path.splitext(output) + if ext == '.o': + key = output + else: + key = os.path.splitext(settings.module_path)[0] + ext + if key not in depfile_content: + depfile_content[key] = OrderedSet() + for path in inputs.split(): + depfile_content[key].add(path) + + with open(settings.depfile, 'w') as depfile: + for key in depfile_content: + if not settings.depfile_filter or key in settings.depfile_filter: + inputs = depfile_content[key] + depfile.write('%s : %s\n' % (key, ' '.join(inputs))) + + +def main(args): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('-module-name', help='name of the Swift module') + parser.add_argument('-include', + '-I', + action='append', + dest='include_dirs', + help='add directory to header search path') + parser.add_argument('sources', nargs='+', help='Swift source file to compile') + parser.add_argument('-whole-module-optimization', + action='store_true', + help='enable whole module optimization') + parser.add_argument('-object-dir', + help='path to the generated object files directory') + parser.add_argument('-module-path', help='path to the generated module file') + parser.add_argument('-header-path', help='path to the generated header file') + parser.add_argument('-bridge-header', + help='path to the Objective-C bridge header') + parser.add_argument('-depfile', help='path to the generated depfile') + parser.add_argument('-swift-version', + help='version of Swift language to support') + parser.add_argument('-depfile-filter', + action='append', + help='limit depfile to those files') + parser.add_argument('-target', + action='store', + help='generate code for the given target ') + parser.add_argument('-sdk', action='store', help='compile against sdk') + + parsed, extras = parser.parse_known_args(args) + with tempfile.TemporaryDirectory() as tmpdir: + compile_module(parsed.module_name, parsed.sources, parsed, extras, tmpdir) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/toolchain/kythe.gni b/third_party/libwebrtc/build/toolchain/kythe.gni new file mode 100644 index 0000000000..7486a4f15b --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/kythe.gni @@ -0,0 +1,11 @@ +# Copyright 2020 The Chromium 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 configuration for Kythe, an indexer and cross-referencer +# that powers codesearch. + +declare_args() { + # Enables Kythe annotations necessary to build cross references. + enable_kythe_annotations = false +} diff --git a/third_party/libwebrtc/build/toolchain/linux/BUILD.gn b/third_party/libwebrtc/build/toolchain/linux/BUILD.gn new file mode 100644 index 0000000000..697247abcd --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/linux/BUILD.gn @@ -0,0 +1,445 @@ +# 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("//build/config/ozone.gni") +import("//build/config/sysroot.gni") +import("//build/toolchain/gcc_toolchain.gni") + +clang_toolchain("clang_ppc64") { + enable_linker_map = true + toolchain_args = { + target_cpu = "ppc64" + current_os = "linux" + } +} + +clang_toolchain("clang_arm") { + toolprefix = "arm-linux-gnueabihf-" + toolchain_args = { + target_cpu = "arm" + current_os = "linux" + } +} + +clang_toolchain("clang_arm64") { + toolprefix = "aarch64-linux-gnu-" + toolchain_args = { + target_cpu = "arm64" + current_os = "linux" + } +} + +gcc_toolchain("arm64") { + toolprefix = "aarch64-linux-gnu-" + + cc = "${toolprefix}gcc" + cxx = "${toolprefix}g++" + + ar = "${toolprefix}ar" + ld = cxx + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + + toolchain_args = { + target_cpu = "arm64" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +gcc_toolchain("arm") { + toolprefix = "arm-linux-gnueabihf-" + + cc = "${toolprefix}gcc" + cxx = "${toolprefix}g++" + + ar = "${toolprefix}ar" + ld = cxx + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + + toolchain_args = { + target_cpu = "arm" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +clang_toolchain("clang_x86") { + # Output linker map files for binary size analysis. + enable_linker_map = true + + toolchain_args = { + target_cpu = "x86" + current_os = "linux" + } +} + +clang_toolchain("clang_x86_v8_arm") { + toolchain_args = { + target_cpu = "x86" + v8_target_cpu = "arm" + current_os = "linux" + } +} + +clang_toolchain("clang_x86_v8_mipsel") { + toolchain_args = { + target_cpu = "x86" + v8_target_cpu = "mipsel" + current_os = "linux" + } +} + +clang_toolchain("clang_x86_v8_mips") { + toolchain_args = { + target_cpu = "x86" + v8_target_cpu = "mips" + current_os = "linux" + } +} + +gcc_toolchain("x86") { + cc = "gcc" + cxx = "g++" + + readelf = "readelf" + nm = "nm" + ar = "ar" + ld = cxx + + # Output linker map files for binary size analysis. + enable_linker_map = true + + toolchain_args = { + target_cpu = "x86" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +clang_toolchain("clang_x64") { + # Output linker map files for binary size analysis. + enable_linker_map = true + + toolchain_args = { + target_cpu = "x64" + current_os = "linux" + } +} + +clang_toolchain("clang_x64_v8_arm64") { + toolchain_args = { + target_cpu = "x64" + v8_target_cpu = "arm64" + current_os = "linux" + } +} + +clang_toolchain("clang_x64_v8_mips64el") { + toolchain_args = { + target_cpu = "x64" + v8_target_cpu = "mips64el" + current_os = "linux" + } +} + +clang_toolchain("clang_x64_v8_mips64") { + toolchain_args = { + target_cpu = "x64" + v8_target_cpu = "mips64" + current_os = "linux" + } +} + +clang_toolchain("clang_x64_v8_riscv64") { + toolchain_args = { + target_cpu = "x64" + v8_target_cpu = "riscv64" + current_os = "linux" + } +} + +clang_toolchain("clang_x64_v8_loong64") { + toolchain_args = { + target_cpu = "x64" + v8_target_cpu = "loong64" + current_os = "linux" + } +} + +# In a LaCrOS build, this toolchain is intended to be used as an alternate +# toolchain to build Ash-Chrome in a subdirectory. +clang_toolchain("ash_clang_x64") { + toolchain_args = { + # This turns the toolchain into the "Linux ChromeOS" build + current_os = "chromeos" + target_os = "chromeos" + target_cpu = target_cpu + + # This turns off all of the LaCrOS-specific flags. + also_build_ash_chrome = false + chromeos_is_browser_only = false + use_clang_coverage = false + } +} + +# In an ash build, this toolchain is intended to be used as an alternate +# toolchain to build lacros-Chrome in a subdirectory. +clang_toolchain("lacros_clang_x64") { + toolchain_args = { + # This turns the toolchain into the "Lacros" build + current_os = "chromeos" + target_os = "chromeos" + target_cpu = target_cpu + + # This turns on the LaCrOS-specific flag. + also_build_lacros_chrome = false + chromeos_is_browser_only = true + use_clang_coverage = false + } +} + +gcc_toolchain("x64") { + cc = "gcc" + cxx = "g++" + + readelf = "readelf" + nm = "nm" + ar = "ar" + ld = cxx + + # Output linker map files for binary size analysis. + enable_linker_map = true + + toolchain_args = { + target_cpu = "x64" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +clang_toolchain("clang_mipsel") { + toolchain_args = { + target_cpu = "mipsel" + current_os = "linux" + } +} + +clang_toolchain("clang_mips64el") { + toolchain_args = { + target_cpu = "mips64el" + current_os = "linux" + } +} + +clang_toolchain("clang_mips64") { + toolchain_args = { + target_cpu = "mips64" + current_os = "linux" + } +} + +gcc_toolchain("mipsel") { + toolprefix = "mipsel-linux-gnu-" + + cc = "${toolprefix}gcc" + cxx = " ${toolprefix}g++" + ar = "${toolprefix}ar" + ld = cxx + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + + toolchain_args = { + cc_wrapper = "" + target_cpu = "mipsel" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + use_goma = false + } +} + +gcc_toolchain("mips64el") { + toolprefix = "mips64el-linux-gnuabi64-" + + cc = "${toolprefix}gcc" + cxx = "${toolprefix}g++" + ar = "${toolprefix}ar" + ld = cxx + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + + toolchain_args = { + cc_wrapper = "" + target_cpu = "mips64el" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + use_goma = false + } +} + +gcc_toolchain("riscv64") { + toolprefix = "riscv64-linux-gnu" + + cc = "${toolprefix}-gcc" + cxx = "${toolprefix}-g++" + + readelf = "${toolprefix}-readelf" + nm = "${toolprefix}-nm" + ar = "${toolprefix}-ar" + ld = cxx + + toolchain_args = { + target_cpu = "riscv64" + current_os = "linux" + is_clang = false + } +} + +clang_toolchain("clang_s390x") { + toolchain_args = { + target_cpu = "s390x" + current_os = "linux" + is_clang = true + } +} + +gcc_toolchain("s390x") { + cc = "gcc" + cxx = "g++" + + readelf = "readelf" + nm = "nm" + ar = "ar" + ld = cxx + + toolchain_args = { + target_cpu = "s390x" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +#gcc_toolchain("mips64") { +# cc = "gcc" +# cxx = "g++" +# +# readelf = "readelf" +# nm = "nm" +# ar = "ar" +# ld = cxx +# +# toolchain_args = { +# target_cpu = "mips64" +# current_os = "linux" +# is_clang = false +# } +#} + +gcc_toolchain("ppc64") { + cc = "gcc" + cxx = "g++" + + readelf = "readelf" + nm = "nm" + ar = "ar" + ld = cxx + + toolchain_args = { + target_cpu = "ppc64" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +gcc_toolchain("mips") { + toolprefix = "mips-linux-gnu-" + + cc = "${toolprefix}gcc" + cxx = "${toolprefix}g++" + + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + ar = "${toolprefix}ar" + ld = cxx + + toolchain_args = { + target_cpu = "mips" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +gcc_toolchain("mips64") { + toolprefix = "mips64-linux-gnuabi64-" + + cc = "${toolprefix}gcc" + cxx = "${toolprefix}g++" + + readelf = "${toolprefix}readelf" + nm = "${toolprefix}nm" + ar = "${toolprefix}ar" + ld = cxx + + toolchain_args = { + target_cpu = "mips64" + current_os = "linux" + + # reclient does not support gcc. + use_rbe = false + is_clang = false + } +} + +clang_toolchain("clang_loong64") { + toolchain_args = { + target_cpu = "loong64" + current_os = "linux" + } +} + +gcc_toolchain("loong64") { + toolprefix = "loongarch64-linux-gnu" + + cc = "${toolprefix}-gcc" + cxx = "${toolprefix}-g++" + + readelf = "${toolprefix}-readelf" + nm = "${toolprefix}-nm" + ar = "${toolprefix}-ar" + ld = cxx + + toolchain_args = { + target_cpu = "loong64" + current_os = "linux" + is_clang = false + } +} diff --git a/third_party/libwebrtc/build/toolchain/linux/unbundle/BUILD.gn b/third_party/libwebrtc/build/toolchain/linux/unbundle/BUILD.gn new file mode 100644 index 0000000000..0e20f271f0 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/linux/unbundle/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright 2017 The Chromium 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/toolchain/gcc_toolchain.gni") + +gcc_toolchain("default") { + cc = getenv("CC") + cxx = getenv("CXX") + ar = getenv("AR") + nm = getenv("NM") + ld = cxx + + extra_cflags = getenv("CFLAGS") + extra_cppflags = getenv("CPPFLAGS") + extra_cxxflags = getenv("CXXFLAGS") + extra_ldflags = getenv("LDFLAGS") + + toolchain_args = { + target_cpu = target_cpu + current_os = current_os + } +} + +gcc_toolchain("host") { + cc = getenv("BUILD_CC") + cxx = getenv("BUILD_CXX") + ar = getenv("BUILD_AR") + nm = getenv("BUILD_NM") + ld = cxx + + extra_cflags = getenv("BUILD_CFLAGS") + extra_cppflags = getenv("BUILD_CPPFLAGS") + extra_cxxflags = getenv("BUILD_CXXFLAGS") + extra_ldflags = getenv("BUILD_LDFLAGS") + + toolchain_args = { + target_cpu = target_cpu + current_os = current_os + } +} diff --git a/third_party/libwebrtc/build/toolchain/linux/unbundle/README.md b/third_party/libwebrtc/build/toolchain/linux/unbundle/README.md new file mode 100644 index 0000000000..17b93c9fde --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/linux/unbundle/README.md @@ -0,0 +1,41 @@ +# Overview + +This directory contains files that make it possible for Linux +distributions to build Chromium using system toolchain. + +For more info on established way such builds are configured, +please read the following: + + - https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html + +Why do distros want CFLAGS, LDFLAGS, etc? Please read the following +for some examples. This is not an exhaustive list. + + - https://wiki.debian.org/Hardening + - https://wiki.ubuntu.com/DistCompilerFlags + - https://fedoraproject.org/wiki/Changes/Harden_All_Packages + - https://fedoraproject.org/wiki/Changes/Modernise_GCC_Flags + - https://fedoraproject.org/wiki/Packaging:Guidelines#Compiler_flags + - https://blog.flameeyes.eu/2010/09/are-we-done-with-ldflags/ + - https://blog.flameeyes.eu/2008/08/flags-and-flags/ + +# Usage + +Add the following to GN args: + +``` +custom_toolchain="//build/toolchain/linux/unbundle:default" +host_toolchain="//build/toolchain/linux/unbundle:default" +``` + +See [more docs on GN](https://gn.googlesource.com/gn/+/master/docs/quick_start.md). + +To cross-compile (not fully tested), add the following: + +``` +host_toolchain="//build/toolchain/linux/unbundle:host" +v8_snapshot_toolchain="//build/toolchain/linux/unbundle:host" +``` + +Note: when cross-compiling for a 32-bit target, a matching 32-bit toolchain +may be needed. diff --git a/third_party/libwebrtc/build/toolchain/mac/BUILD.gn b/third_party/libwebrtc/build/toolchain/mac/BUILD.gn new file mode 100644 index 0000000000..4809b4ba0a --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/mac/BUILD.gn @@ -0,0 +1,102 @@ +# Copyright 2021 The Chromium 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/mac/mac_sdk.gni") +import("//build/toolchain/apple/toolchain.gni") + +# Specialisation of the apple_toolchain template to declare the toolchain +# and its tools to build target for macOS platform. +template("mac_toolchain") { + assert(defined(invoker.toolchain_args), + "Toolchains must declare toolchain_args") + + apple_toolchain(target_name) { + forward_variables_from(invoker, "*", [ "toolchain_args" ]) + + bin_path = mac_bin_path + + toolchain_args = { + forward_variables_from(invoker.toolchain_args, "*") + current_os = "mac" + + if (target_os == "ios") { + # Use LLD for the host part of a chrome/ios build. + use_lld = true + + # TODO(crbug.com/753445): the use_sanitizer_coverage arg is currently + # not supported by the Chromium mac_clang_x64 toolchain on iOS + # distribution. + use_sanitizer_coverage = false + + # Do not use Xcode version of clang when building macOS tools for the + # host even if this is the version used to build for the iOS target. + use_xcode_clang = false + } + } + } +} + +mac_toolchain("clang_arm") { + toolchain_args = { + target_cpu = "arm" + } +} + +mac_toolchain("clang_arm64") { + toolchain_args = { + target_cpu = "arm64" + } +} + +mac_toolchain("clang_x64") { + toolchain_args = { + target_cpu = "x64" + } +} + +mac_toolchain("clang_x86") { + toolchain_args = { + target_cpu = "x86" + } +} + +mac_toolchain("clang_x86_v8_arm") { + toolchain_args = { + target_cpu = "x86" + + if (defined(v8_target_cpu)) { + v8_target_cpu = "arm" + } + } +} + +mac_toolchain("clang_x86_v8_mipsel") { + toolchain_args = { + target_cpu = "x86" + + if (defined(v8_target_cpu)) { + v8_target_cpu = "mipsel" + } + } +} + +mac_toolchain("clang_x64_v8_arm64") { + toolchain_args = { + target_cpu = "x64" + + if (defined(v8_target_cpu)) { + v8_target_cpu = "arm64" + } + } +} + +mac_toolchain("clang_x64_v8_mips64el") { + toolchain_args = { + target_cpu = "x64" + + if (defined(v8_target_cpu)) { + v8_target_cpu = "mips64el" + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/mac/OWNERS b/third_party/libwebrtc/build/toolchain/mac/OWNERS new file mode 100644 index 0000000000..6f3324f07c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/mac/OWNERS @@ -0,0 +1 @@ +file://build/apple/OWNERS diff --git a/third_party/libwebrtc/build/toolchain/nacl/BUILD.gn b/third_party/libwebrtc/build/toolchain/nacl/BUILD.gn new file mode 100644 index 0000000000..259e6347ae --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/nacl/BUILD.gn @@ -0,0 +1,305 @@ +# Copyright (c) 2014 The Native Client 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/nacl/config.gni") +import("//build/config/sysroot.gni") +import("//build/toolchain/nacl_toolchain.gni") + +# Add the toolchain revision as a preprocessor define so that sources are +# rebuilt when a toolchain is updated. +# Idea we could use the toolchain deps feature, but currently that feature is +# bugged and does not trigger a rebuild. +# https://code.google.com/p/chromium/issues/detail?id=431880 +# Calls to get the toolchain revision are relatively slow, so do them all in a +# single batch to amortize python startup, etc. +revisions = exec_script("//native_client/build/get_toolchain_revision.py", + [ + "nacl_x86_glibc", + "nacl_arm_glibc", + "pnacl_newlib", + "saigo_newlib", + ], + "trim list lines") +nacl_x86_glibc_rev = revisions[0] +nacl_arm_glibc_rev = revisions[1] + +pnacl_newlib_rev = revisions[2] +saigo_newlib_rev = revisions[3] + +use_saigo = true + +if (host_os == "win") { + toolsuffix = ".exe" +} else { + toolsuffix = "" +} + +# The PNaCl toolchain tools are all wrapper scripts rather than binary +# executables. On POSIX systems, nobody cares what kind of executable +# file you are. But on Windows, scripts (.bat files) cannot be run +# directly and need the Windows shell (cmd.exe) specified explicily. +if (host_os == "win") { + # NOTE! The //build/toolchain/gcc_*_wrapper.py scripts recognize + # this exact prefix string, so they must be updated if this string + # is changed in any way. + scriptprefix = "cmd /c call " + scriptsuffix = ".bat" +} else { + scriptprefix = "" + scriptsuffix = "" +} + +# When the compilers are run via goma, rbe or ccache rather than directly by +# GN/Ninja, the rbe/goma/ccache wrapper handles .bat files but gets confused +# by being given the scriptprefix. +if (host_os == "win" && !use_goma && !use_rbe && cc_wrapper == "") { + compiler_scriptprefix = scriptprefix +} else { + compiler_scriptprefix = "" +} + +template("pnacl_toolchain") { + assert(defined(invoker.executable_extension), + "Must define executable_extension") + + nacl_toolchain(target_name) { + toolchain_package = "pnacl_newlib" + toolchain_revision = pnacl_newlib_rev + toolprefix = + rebase_path("${nacl_toolchain_dir}/${toolchain_package}/bin/pnacl-", + root_build_dir) + + if (host_os == "win") { + # Flip the slashes so that copy/paste of the commands works. + # This is also done throughout build\toolchain\win\BUILD.gn + toolprefix = string_replace(toolprefix, "/", "\\") + } + + cc = compiler_scriptprefix + toolprefix + "clang" + scriptsuffix + cxx = compiler_scriptprefix + toolprefix + "clang++" + scriptsuffix + ar = toolprefix + "ar" + scriptsuffix + readelf = scriptprefix + toolprefix + "readelf" + scriptsuffix + nm = scriptprefix + toolprefix + "nm" + scriptsuffix + if (defined(invoker.strip)) { + strip = scriptprefix + toolprefix + invoker.strip + scriptsuffix + } + forward_variables_from(invoker, + [ + "executable_extension", + "is_clang_analysis_supported", + "extra_cppflags", + ]) + + # Note this is not the usual "ld = cxx" because "ld" uses are + # never run via goma, so this needs scriptprefix. + ld = scriptprefix + toolprefix + "clang++" + scriptsuffix + + toolchain_args = { + is_clang = true + target_cpu = "pnacl" + use_lld = false + } + } +} + +pnacl_toolchain("newlib_pnacl") { + executable_extension = ".pexe" + + # The pnacl-finalize tool turns a .pexe.debug file into a .pexe file. + # It's very similar in purpose to the traditional "strip" utility: it + # turns what comes out of the linker into what you actually want to + # distribute and run. PNaCl doesn't have a "strip"-like utility that + # you ever actually want to use other than pnacl-finalize, so just + # make pnacl-finalize the strip tool rather than adding an additional + # step like "postlink" to run pnacl-finalize. + strip = "finalize" +} + +pnacl_toolchain("newlib_pnacl_nonsfi") { + executable_extension = "" + strip = "strip" + + # This macro is embedded on nonsfi toolchains but reclient can't figure + # that out itself, so we make it explicit. + extra_cppflags = "-D__native_client_nonsfi__" +} + +template("nacl_glibc_toolchain") { + toolchain_cpu = target_name + assert(defined(invoker.toolchain_tuple), "Must define toolchain_tuple") + assert(defined(invoker.toolchain_package), "Must define toolchain_package") + assert(defined(invoker.toolchain_revision), "Must define toolchain_revision") + forward_variables_from(invoker, + [ + "toolchain_package", + "toolchain_revision", + ]) + + toolprefix = rebase_path("${nacl_toolchain_dir}/${toolchain_package}/bin/" + + invoker.toolchain_tuple + "-", + root_build_dir) + + if (host_os == "win") { + # Flip the slashes so that copy/paste of the commands works. + # This is also done throughout build\toolchain\win\BUILD.gn + toolprefix = string_replace(toolprefix, "/", "\\") + } + + nacl_toolchain("glibc_" + toolchain_cpu) { + cc = toolprefix + "gcc" + toolsuffix + cxx = toolprefix + "g++" + toolsuffix + ar = toolprefix + "ar" + toolsuffix + ld = cxx + readelf = toolprefix + "readelf" + toolsuffix + nm = toolprefix + "nm" + toolsuffix + strip = toolprefix + "strip" + toolsuffix + + toolchain_args = { + target_cpu = toolchain_cpu + + # reclient does not support gcc. + use_rbe = false + is_clang = false + is_nacl_glibc = true + use_lld = false + } + } +} + +nacl_glibc_toolchain("x86") { + toolchain_package = "nacl_x86_glibc" + toolchain_revision = nacl_x86_glibc_rev + + # Rely on the :compiler_cpu_abi config adding the -m32 flag here rather + # than using the i686-nacl binary directly. This is a because i686-nacl-gcc + # is a shell script wrapper around x86_64-nacl-gcc and goma has trouble with + # compiler executables that are shell scripts (so the i686 'compiler' is not + # currently in goma). + toolchain_tuple = "x86_64-nacl" +} + +nacl_glibc_toolchain("x64") { + toolchain_package = "nacl_x86_glibc" + toolchain_revision = nacl_x86_glibc_rev + toolchain_tuple = "x86_64-nacl" +} + +nacl_glibc_toolchain("arm") { + toolchain_package = "nacl_arm_glibc" + toolchain_revision = nacl_arm_glibc_rev + toolchain_tuple = "arm-nacl" +} + +template("nacl_clang_toolchain") { + toolchain_cpu = target_name + assert(defined(invoker.toolchain_tuple), "Must define toolchain_tuple") + + toolchain_package = "pnacl_newlib" + toolchain_revision = pnacl_newlib_rev + toolprefix = rebase_path("${nacl_toolchain_dir}/${toolchain_package}/bin/" + + invoker.toolchain_tuple + "-", + root_build_dir) + + if (host_os == "win") { + # Flip the slashes so that copy/paste of the commands works. + # This is also done throughout build\toolchain\win\BUILD.gn + toolprefix = string_replace(toolprefix, "/", "\\") + } + + nacl_toolchain("clang_newlib_" + toolchain_cpu) { + cc = toolprefix + "clang" + toolsuffix + cxx = toolprefix + "clang++" + toolsuffix + ar = toolprefix + "ar" + toolsuffix + ld = cxx + readelf = toolprefix + "readelf" + toolsuffix + nm = toolprefix + "nm" + toolsuffix + strip = toolprefix + "strip" + toolsuffix + + toolchain_args = { + target_cpu = toolchain_cpu + is_clang = true + use_lld = false + } + } +} + +template("nacl_irt_toolchain") { + toolchain_cpu = target_name + assert(defined(invoker.toolchain_tuple), "Must define toolchain_tuple") + + toolchain_package = "pnacl_newlib" + toolchain_revision = pnacl_newlib_rev + if (use_saigo) { + toolchain_package = "saigo_newlib" + toolchain_revision = saigo_newlib_rev + } + toolprefix = rebase_path("${nacl_toolchain_dir}/${toolchain_package}/bin/" + + invoker.toolchain_tuple + "-", + root_build_dir) + + if (host_os == "win") { + # Flip the slashes so that copy/paste of the commands works. + # This is also done throughout build\toolchain\win\BUILD.gn + toolprefix = string_replace(toolprefix, "/", "\\") + } + + link_irt = rebase_path("//native_client/build/link_irt.py", root_build_dir) + + tls_edit_label = + "//native_client/src/tools/tls_edit:tls_edit($host_toolchain)" + host_toolchain_out_dir = + rebase_path(get_label_info(tls_edit_label, "root_out_dir"), + root_build_dir) + tls_edit = "${host_toolchain_out_dir}/tls_edit" + + nacl_toolchain("irt_" + toolchain_cpu) { + cc = toolprefix + "clang" + toolsuffix + cxx = toolprefix + "clang++" + toolsuffix + ar = toolprefix + "ar" + toolsuffix + readelf = toolprefix + "readelf" + toolsuffix + nm = toolprefix + "nm" + toolsuffix + strip = toolprefix + "strip" + toolsuffix + + # Some IRT implementations (notably, Chromium's) contain C++ code, + # so we need to link w/ the C++ linker. + ld = "${python_path} ${link_irt} --tls-edit=${tls_edit} --link-cmd=${cxx} --readelf-cmd=${readelf}" + + toolchain_args = { + target_cpu = toolchain_cpu + is_clang = true + use_lld = false + is_nacl_saigo = use_saigo + } + + # TODO(ncbray): depend on link script + deps = [ tls_edit_label ] + } +} + +template("nacl_clang_toolchains") { + assert(defined(invoker.toolchain_tuple), "Must define toolchain_tuple") + nacl_clang_toolchain(target_name) { + toolchain_tuple = invoker.toolchain_tuple + } + nacl_irt_toolchain(target_name) { + toolchain_tuple = invoker.toolchain_tuple + } +} + +nacl_clang_toolchains("x86") { + # Rely on :compiler_cpu_abi adding -m32. See nacl_x86_glibc above. + toolchain_tuple = "x86_64-nacl" +} + +nacl_clang_toolchains("x64") { + toolchain_tuple = "x86_64-nacl" +} + +nacl_clang_toolchains("arm") { + toolchain_tuple = "arm-nacl" +} + +nacl_clang_toolchains("mipsel") { + toolchain_tuple = "mipsel-nacl" +} diff --git a/third_party/libwebrtc/build/toolchain/nacl_toolchain.gni b/third_party/libwebrtc/build/toolchain/nacl_toolchain.gni new file mode 100644 index 0000000000..25fbd6405b --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/nacl_toolchain.gni @@ -0,0 +1,73 @@ +# Copyright (c) 2014 The Native Client 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/nacl/config.gni") +import("//build/toolchain/gcc_toolchain.gni") + +# This template defines a NaCl toolchain. +# +# It requires the following variables specifying the executables to run: +# - cc +# - cxx +# - ar +# - ld + +template("nacl_toolchain") { + assert(defined(invoker.cc), "nacl_toolchain() must specify a \"cc\" value") + assert(defined(invoker.cxx), "nacl_toolchain() must specify a \"cxx\" value") + assert(defined(invoker.ar), "nacl_toolchain() must specify a \"ar\" value") + assert(defined(invoker.ld), "nacl_toolchain() must specify a \"ld\" value") + gcc_toolchain(target_name) { + if (defined(invoker.executable_extension)) { + executable_extension = invoker.executable_extension + } else { + executable_extension = ".nexe" + } + rebuild_define = "NACL_TC_REV=" + invoker.toolchain_revision + + forward_variables_from(invoker, + [ + "ar", + "cc", + "cxx", + "deps", + "ld", + "link_outputs", + "nm", + "readelf", + "strip", + "extra_cppflags", + ]) + + toolchain_args = { + # Use all values set on the invoker's toolchain_args. + forward_variables_from(invoker.toolchain_args, "*") + + current_os = "nacl" + + # We do not support component builds with the NaCl toolchains. + is_component_build = false + + # We do not support tcmalloc in the NaCl toolchains. + use_allocator = "none" + use_allocator_shim = false + + # We do not support clang profiling in the NaCl toolchains. + use_clang_profiling = false + use_clang_coverage = false + coverage_instrumentation_input_file = "" + + if (use_remoteexec) { + if (is_win) { + rbe_cc_cfg_file = "${rbe_cfg_dir}/nacl/rewrapper_windows.cfg" + } else if (is_mac) { + rbe_cc_cfg_file = "${rbe_cfg_dir}/nacl/rewrapper_mac.cfg" + } else { + # TODO(ukai): non linux? + rbe_cc_cfg_file = "${rbe_cfg_dir}/nacl/rewrapper_linux.cfg" + } + } + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/rbe.gni b/third_party/libwebrtc/build/toolchain/rbe.gni new file mode 100644 index 0000000000..baf7462bb5 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/rbe.gni @@ -0,0 +1,45 @@ +# Defines the configuration of Remote Build Execution (RBE). + +# The directory where the re-client tooling binaries are. +rbe_bin_dir = rebase_path("//buildtools/reclient", root_build_dir) + +# Execution root - this should be the root of the source tree. +# This is defined here instead of in the config file because +# this will vary depending on where the user has placed the +# chromium source on their system. +rbe_exec_root = rebase_path("//") + +declare_args() { + # Set to true to enable remote compilation using reclient. + use_remoteexec = false + + # Set to true to enable remote compilation using reclient (deprecated). + use_rbe = false + + # The directory where the re-client configuration files are. + rbe_cfg_dir = rebase_path("//buildtools/reclient_cfgs", root_build_dir) + + # Set to the path of the RBE reclient configuration file. + rbe_cc_cfg_file = "" + + # Set to the path of the RBE recleint wrapper for ChromeOS. + rbe_cros_cc_wrapper = "" +} + +# Set use_remoteexec if use_rbe is set. Remove this once use_rbe is no longer +# used to configure builds. +if (!use_remoteexec && use_rbe) { + use_remoteexec = true +} + +# Configuration file selection based on operating system. +if (is_linux || is_android || is_chromeos) { + rbe_cc_cfg_file = "${rbe_cfg_dir}/chromium-browser-clang/rewrapper_linux.cfg" +} +if (is_win) { + rbe_cc_cfg_file = + "${rbe_cfg_dir}/chromium-browser-clang/rewrapper_windows.cfg" +} +if (is_mac || is_ios) { + rbe_cc_cfg_file = "${rbe_cfg_dir}/chromium-browser-clang/rewrapper_mac.cfg" +} diff --git a/third_party/libwebrtc/build/toolchain/toolchain.gni b/third_party/libwebrtc/build/toolchain/toolchain.gni new file mode 100644 index 0000000000..6d8f162d82 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/toolchain.gni @@ -0,0 +1,107 @@ +# 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. + +# Toolchain-related configuration that may be needed outside the context of the +# toolchain() rules themselves. + +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build_overrides/build.gni") + +declare_args() { + # If this is set to true, we use the revision in the llvm repo to determine + # the CLANG_REVISION to use, instead of the version hard-coded into + # //tools/clang/scripts/update.py. This should only be used in + # conjunction with setting the llvm_force_head_revision DEPS variable when + # `gclient runhooks` is run as well. + llvm_force_head_revision = false + + # Compile with Xcode version of clang instead of hermetic version shipped + # with the build. Used to be used iOS for official builds, but is now off by + # default for all configurations. + use_xcode_clang = false + + # Used for binary size analysis. + generate_linker_map = is_android && is_official_build +} + +if (generate_linker_map) { + assert(is_official_build || is_chromecast, + "Linker map files should only be generated when is_official_build = " + + "true or is_chromecast = true") + assert(current_os == "android" || current_os == "linux" || + target_os == "android" || target_os == "linux" || + target_os == "chromeos", + "Linker map files should only be generated for Android, Linux, " + + "or ChromeOS.") +} + +declare_args() { + clang_version = "14.0.0" +} + +# Check target_os here instead of is_ios as this file is loaded for secondary +# toolchain (host toolchain in particular) but the argument is the same for +# all toolchains. +assert(!use_xcode_clang || target_os == "ios", + "Using Xcode's clang is only supported in iOS builds") + +# Extension for shared library files (including leading dot). +if (is_apple) { + shlib_extension = ".dylib" +} else if (is_android && is_component_build) { + # By appending .cr, we prevent name collisions with libraries already + # loaded by the Android zygote. + shlib_extension = ".cr.so" +} else if (is_posix || is_fuchsia) { + shlib_extension = ".so" +} else if (is_win) { + shlib_extension = ".dll" +} else { + assert(false, "Platform not supported") +} + +# Prefix for shared library files. +if (is_posix || is_fuchsia) { + shlib_prefix = "lib" +} else { + shlib_prefix = "" +} + +# Directory for shared library files. +if (is_fuchsia) { + shlib_subdir = "/lib" +} else { + shlib_subdir = "" +} + +# While other "tool"s in a toolchain are specific to the target of that +# toolchain, the "stamp" and "copy" tools are really generic to the host; +# but each toolchain must define them separately. GN doesn't allow a +# template instantiation inside a toolchain definition, so some boilerplate +# has to be repeated in each toolchain to define these two tools. These +# four variables reduce the duplication in that boilerplate. +stamp_description = "STAMP {{output}}" +copy_description = "COPY {{source}} {{output}}" +if (host_os == "win") { + _tool_wrapper_path = + rebase_path("//build/toolchain/win/tool_wrapper.py", root_build_dir) + + stamp_command = "cmd /c type nul > \"{{output}}\"" + copy_command = + "$python_path $_tool_wrapper_path recursive-mirror {{source}} {{output}}" +} else { + stamp_command = "touch {{output}}" + copy_command = "ln -f {{source}} {{output}} 2>/dev/null || (rm -rf {{output}} && cp -af {{source}} {{output}})" +} + +# This variable is true if the current toolchain is one of the target +# toolchains, i.e. a toolchain which is being used to build the main Chrome +# binary. This generally means "not the host toolchain", but in the case where +# we're targeting the host it's true then as well. We do require current_os to +# match target_os so that for example we avoid considering Android as a target +# toolchain when targeting CrOS. +is_a_target_toolchain = + (current_toolchain != host_toolchain || + default_toolchain == host_toolchain) && current_os == target_os diff --git a/third_party/libwebrtc/build/toolchain/win/BUILD.gn b/third_party/libwebrtc/build/toolchain/win/BUILD.gn new file mode 100644 index 0000000000..571235286e --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/BUILD.gn @@ -0,0 +1,587 @@ +# 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/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/rust.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/win/visual_studio_version.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") +import("//build/toolchain/toolchain.gni") + +# Should only be running on Windows. +assert(is_win) + +# Setup the Visual Studio state. +# +# Its arguments are the VS path and the compiler wrapper tool. It will write +# "environment.x86" and "environment.x64" to the build directory and return a +# list to us. + +# This tool will is used as a wrapper for various commands below. +tool_wrapper_path = rebase_path("tool_wrapper.py", root_build_dir) + +if (use_rbe) { + goma_prefix = "" + rbe_prefix = "${rbe_bin_dir}/rewrapper -cfg=${rbe_cc_cfg_file} -exec_root=${rbe_exec_root} " + clang_prefix = rbe_prefix +} else if (use_goma) { + if (host_os == "win") { + goma_prefix = "$goma_dir/gomacc.exe " + } else { + goma_prefix = "$goma_dir/gomacc " + } + clang_prefix = goma_prefix +} else { + goma_prefix = "" + if (cc_wrapper != "") { + clang_prefix = cc_wrapper + " " + } else { + clang_prefix = "" + } +} + +# Copy the VS runtime DLL for the default toolchain to the root build directory +# so things will run. +if (current_toolchain == default_toolchain) { + if (is_debug) { + configuration_name = "Debug" + } else { + configuration_name = "Release" + } + exec_script("../../vs_toolchain.py", + [ + "copy_dlls", + rebase_path(root_build_dir), + configuration_name, + target_cpu, + ]) +} + +if (host_os == "win") { + clang_cl = "clang-cl.exe" +} else { + clang_cl = "clang-cl" +} + +# Parameters: +# environment: File name of environment file. +# +# You would also define a toolchain_args variable with at least these set: +# target_cpu: target_cpu to pass as a build arg +# current_os: current_os to pass as a build arg +template("msvc_toolchain") { + toolchain(target_name) { + # When invoking this toolchain not as the default one, these args will be + # passed to the build. They are ignored when this is the default toolchain. + assert(defined(invoker.toolchain_args)) + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + + # This value needs to be passed through unchanged. + host_toolchain = host_toolchain + } + + # Make these apply to all tools below. + lib_switch = "" + lib_dir_switch = "/LIBPATH:" + + # Object files go in this directory. + object_subdir = "{{target_out_dir}}/{{label_name}}" + + env = invoker.environment + + cl = invoker.cl + + if (use_lld) { + if (host_os == "win") { + lld_link = "lld-link.exe" + } else { + lld_link = "lld-link" + } + prefix = rebase_path("$clang_base_path/bin", root_build_dir) + + # lld-link includes a replacement for lib.exe that can produce thin + # archives and understands bitcode (for lto builds). + link = "$prefix/$lld_link" + if (host_os == "win") { + # Flip the slashes so that copy/paste of the commands works. + link = string_replace(link, "/", "\\") + } + lib = "$link /lib" + if (host_os != "win") { + # See comment adding --rsp-quoting to $cl above for more information. + link = "$link --rsp-quoting=posix" + } + } else { + lib = "lib.exe" + link = "link.exe" + } + + # If possible, pass system includes as flags to the compiler. When that's + # not possible, load a full environment file (containing %INCLUDE% and + # %PATH%) -- e.g. 32-bit MSVS builds require %PATH% to be set and just + # passing in a list of include directories isn't enough. + if (defined(invoker.sys_include_flags)) { + env_wrapper = "" + sys_include_flags = + "${invoker.sys_include_flags} " # Note trailing space. + } else { + # clang-cl doesn't need this env hoop, so omit it there. + assert((defined(toolchain_args.is_clang) && !toolchain_args.is_clang) || + !is_clang) + env_wrapper = "ninja -t msvc -e $env -- " # Note trailing space. + sys_include_flags = "" + } + + # ninja does not have -t msvc other than windows, and lld doesn't depend on + # mt.exe in PATH on non-Windows, so it's not needed there anyways. + if (host_os != "win") { + linker_wrapper = "" + sys_lib_flags = "${invoker.sys_lib_flags} " # Note trailing space. + } else if (defined(invoker.sys_lib_flags)) { + # Invoke ninja as wrapper instead of tool wrapper, because python + # invocation requires higher cpu usage compared to ninja invocation, and + # the python wrapper is only needed to work around link.exe problems. + # TODO(thakis): Remove wrapper once lld-link can merge manifests without + # relying on mt.exe being in %PATH% on Windows, https://crbug.com/872740 + linker_wrapper = "ninja -t msvc -e $env -- " # Note trailing space. + sys_lib_flags = "${invoker.sys_lib_flags} " # Note trailing space. + } else { + # Note trailing space: + linker_wrapper = + "$python_path $tool_wrapper_path link-wrapper $env False " + sys_lib_flags = "" + } + + if (defined(toolchain_args.use_clang_coverage)) { + toolchain_use_clang_coverage = toolchain_args.use_clang_coverage + } else { + toolchain_use_clang_coverage = use_clang_coverage + } + + if (toolchain_use_clang_coverage) { + assert(toolchain_args.is_clang, + "use_clang_coverage should only be used with Clang") + if (defined(toolchain_args.coverage_instrumentation_input_file)) { + toolchain_coverage_instrumentation_input_file = + toolchain_args.coverage_instrumentation_input_file + } else { + toolchain_coverage_instrumentation_input_file = + coverage_instrumentation_input_file + } + + coverage_wrapper = + rebase_path("//build/toolchain/clang_code_coverage_wrapper.py", + root_build_dir) + coverage_wrapper = coverage_wrapper + " --target-os=" + target_os + if (toolchain_coverage_instrumentation_input_file != "") { + coverage_wrapper = + coverage_wrapper + " --files-to-instrument=" + + rebase_path(toolchain_coverage_instrumentation_input_file, + root_build_dir) + } + coverage_wrapper = "$python_path " + coverage_wrapper + " " + } else { + coverage_wrapper = "" + } + + # Disabled with cc_wrapper because of https://github.com/mozilla/sccache/issues/1013 + if (toolchain_args.is_clang && cc_wrapper == "") { + # This flag omits system includes from /showIncludes output, to reduce the + # amount of data to parse and store in .ninja_deps. We do this on non-Windows too, + # and already make sure rebuilds after win sdk / libc++ / clang header updates happen via + # changing commandline flags. + show_includes = "/showIncludes:user" + } else { + show_includes = "/showIncludes" + } + + tool("cc") { + precompiled_header_type = "msvc" + pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb" + + # Label names may have spaces in them so the pdbname must be quoted. The + # source and output don't need to be quoted because GN knows they're a + # full file name and will quote automatically when necessary. + depsformat = "msvc" + description = "CC {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.obj" ] + + # Note that the code coverage wrapper scripts assumes that {{source}} + # comes immediately after /c. + command = "$coverage_wrapper$env_wrapper$cl /c {{source}} /nologo $show_includes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /Fo{{output}} /Fd\"$pdbname\"" + } + + tool("cxx") { + precompiled_header_type = "msvc" + + # The PDB name needs to be different between C and C++ compiled files. + pdbname = "{{target_out_dir}}/{{label_name}}_cc.pdb" + + # See comment in CC tool about quoting. + depsformat = "msvc" + description = "CXX {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.obj" ] + + # Note that the code coverage wrapper scripts assumes that {{source}} + # comes immediately after /c. + command = "$coverage_wrapper$env_wrapper$cl /c {{source}} /Fo{{output}} /nologo $show_includes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /Fd\"$pdbname\"" + } + + tool("rc") { + command = "$python_path $tool_wrapper_path rc-wrapper $env rc.exe /nologo $sys_include_flags{{defines}} {{include_dirs}} /fo{{output}} {{source}}" + depsformat = "msvc" + outputs = [ "$object_subdir/{{source_name_part}}.res" ] + description = "RC {{output}}" + } + + tool("asm") { + is_msvc_assembler = true + + if (toolchain_args.target_cpu == "arm64") { + if (is_clang) { + prefix = rebase_path("$clang_base_path/bin", root_build_dir) + ml = "${clang_prefix}${prefix}/${clang_cl} --target=arm64-windows" + if (host_os == "win") { + # Flip the slashes so that copy/paste of the command works. + ml = string_replace(ml, "/", "\\") + } + ml += " -c -o{{output}}" + is_msvc_assembler = false + } else { + # Only affects Arm builds with is_clang = false, implemented for building + # V8 for Windows on Arm systems with the MSVC toolchain. + ml = "armasm64.exe" + } + } else { + # x86/x64 builds always use the MSVC assembler. + if (toolchain_args.target_cpu == "x64") { + ml = "ml64.exe" + } else { + ml = "ml.exe" + } + } + + if (is_msvc_assembler) { + ml += " /nologo /Fo{{output}}" + + # Suppress final-stage linking on x64/x86 builds. (Armasm64 does not + # require /c because it doesn't support linking.) + if (toolchain_args.target_cpu != "arm64") { + ml += " /c" + } + if (use_lld) { + # Wrap ml(64).exe with a script that makes its output deterministic. + # It's lld only because the script zaps obj Timestamp which + # link.exe /incremental looks at. + # TODO(https://crbug.com/762167): If we end up writing an llvm-ml64, + # make sure it has deterministic output (maybe with /Brepro or + # something) and remove this wrapper. + ml_py = rebase_path("ml.py", root_build_dir) + ml = "$python_path $ml_py $ml" + } + } + if (toolchain_args.target_cpu != "arm64" || is_clang) { + command = "$python_path $tool_wrapper_path asm-wrapper $env $ml {{defines}} {{include_dirs}} {{asmflags}} {{source}}" + } else { + # armasm64.exe does not support definitions passed via the command line. + # (Fortunately, they're not needed for compiling the V8 snapshot, which + # is the only time this assembler is required.) + command = "$python_path $tool_wrapper_path asm-wrapper $env $ml {{include_dirs}} {{asmflags}} {{source}}" + } + + description = "ASM {{output}}" + outputs = [ "$object_subdir/{{source_name_part}}.obj" ] + } + + if (toolchain_has_rust) { + tool("rust_staticlib") { + rust_outfile = "{{target_out_dir}}/{{crate_name}}.lib" + depfile = "{{crate_name}}.d" + command = "${rust_prefix}/rustc $rustc_common_args --emit=dep-info={{target_out_dir}}/$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_rlib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib" + depfile = "{{crate_name}}.d" + command = "${rust_prefix}/rustc $rustc_common_args --emit=dep-info={{target_out_dir}}/$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + if (rustc_can_link) { + tool("rust_bin") { + rust_outfile = "{{root_out_dir}}/{{crate_name}}.exe" + depfile = "{{crate_name}}.d" + command = "${rust_prefix}/rustc $rustc_common_args --emit=dep-info={{target_out_dir}}/$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_cdylib") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dll" + depfile = "{{crate_name}}.d" + command = "${rust_prefix}/rustc $rustc_common_args --emit=dep-info={{target_out_dir}}/$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + + tool("rust_macro") { + rust_outfile = "{{target_out_dir}}/lib{{crate_name}}.dll" + depfile = "{{crate_name}}.d" + command = "${rust_prefix}/rustc $rustc_common_args --emit=dep-info={{target_out_dir}}/$depfile,link -o $rust_outfile" + description = "RUST $rust_outfile" + outputs = [ rust_outfile ] + } + } + } + + tool("alink") { + rspfile = "{{output}}.rsp" + command = "$linker_wrapper$lib /OUT:{{output}} /nologo ${sys_lib_flags}{{arflags}} @$rspfile" + description = "LIB {{output}}" + outputs = [ + # Ignore {{output_extension}} and always use .lib, there's no reason to + # allow targets to override this extension on Windows. + "{{output_dir}}/{{target_output_name}}.lib", + ] + default_output_extension = ".lib" + default_output_dir = "{{target_out_dir}}" + + # The use of inputs_newline is to work around a fixed per-line buffer + # size in the linker. + rspfile_content = "{{inputs_newline}}" + } + + tool("solink") { + # E.g. "foo.dll": + dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + libname = "${dllname}.lib" # e.g. foo.dll.lib + pdbname = "${dllname}.pdb" + rspfile = "${dllname}.rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + command = "$linker_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/IMPLIB:$libname /DLL /PDB:$pdbname @$rspfile" + + default_output_extension = ".dll" + default_output_dir = "{{root_out_dir}}" + description = "LINK(DLL) {{output}}" + outputs = [ + dllname, + libname, + pdbname, + ] + link_output = libname + depend_output = libname + runtime_outputs = [ + dllname, + pdbname, + ] + + # Since the above commands only updates the .lib file when it changes, ask + # Ninja to check if the timestamp actually changed to know if downstream + # dependencies should be recompiled. + restat = true + + # The use of inputs_newline is to work around a fixed per-line buffer + # size in the linker. + rspfile_content = + "{{libs}} {{solibs}} {{inputs_newline}} {{ldflags}} {{rlibs}}" + } + + tool("solink_module") { + # E.g. "foo.dll": + dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + pdbname = "${dllname}.pdb" + rspfile = "${dllname}.rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + command = "$linker_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/DLL /PDB:$pdbname @$rspfile" + + default_output_extension = ".dll" + default_output_dir = "{{root_out_dir}}" + description = "LINK_MODULE(DLL) {{output}}" + outputs = [ + dllname, + pdbname, + ] + runtime_outputs = outputs + + # The use of inputs_newline is to work around a fixed per-line buffer + # size in the linker. + rspfile_content = + "{{libs}} {{solibs}} {{inputs_newline}} {{ldflags}} {{rlibs}}" + } + + tool("link") { + exename = "{{output_dir}}/{{target_output_name}}{{output_extension}}" + pdbname = "$exename.pdb" + rspfile = "$exename.rsp" + pool = "//build/toolchain:link_pool($default_toolchain)" + + command = "$linker_wrapper$link /OUT:$exename /nologo ${sys_lib_flags} /PDB:$pdbname @$rspfile" + + default_output_extension = ".exe" + default_output_dir = "{{root_out_dir}}" + description = "LINK {{output}}" + outputs = [ + exename, + pdbname, + ] + runtime_outputs = outputs + + # The use of inputs_newline is to work around a fixed per-line buffer + # size in the linker. + rspfile_content = + "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}} {{rlibs}}" + } + + # These two are really entirely generic, but have to be repeated in + # each toolchain because GN doesn't allow a template to be used here. + # See //build/toolchain/toolchain.gni for details. + tool("stamp") { + command = stamp_command + description = stamp_description + pool = "//build/toolchain:action_pool($default_toolchain)" + } + tool("copy") { + command = copy_command + description = copy_description + pool = "//build/toolchain:action_pool($default_toolchain)" + } + + tool("action") { + pool = "//build/toolchain:action_pool($default_toolchain)" + } + } +} + +template("win_toolchains") { + assert(defined(invoker.toolchain_arch)) + toolchain_arch = invoker.toolchain_arch + + win_toolchain_data = exec_script("setup_toolchain.py", + [ + visual_studio_path, + windows_sdk_path, + visual_studio_runtime_dirs, + "win", + toolchain_arch, + "environment." + toolchain_arch, + ], + "scope") + + # The toolchain using MSVC only makes sense when not doing cross builds. + # Chromium exclusively uses the win_clang_ toolchain below, but V8 and + # WebRTC still use this MSVC toolchain in some cases. + if (host_os == "win") { + msvc_toolchain(target_name) { + environment = "environment." + toolchain_arch + cl = "${goma_prefix}\"${win_toolchain_data.vc_bin_dir}/cl.exe\"" + + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + is_clang = false + use_clang_coverage = false + current_os = "win" + target_cpu = "arm64" + } + } + } + + msvc_toolchain("win_clang_" + target_name) { + environment = "environment." + toolchain_arch + prefix = rebase_path("$clang_base_path/bin", root_build_dir) + cl = "${clang_prefix}$prefix/${clang_cl}" + _clang_lib_dir = + rebase_path("$clang_base_path/lib/clang/$clang_version/lib/windows", + root_build_dir) + if (host_os == "win") { + # Flip the slashes so that copy/paste of the command works. + cl = string_replace(cl, "/", "\\") + + # And to match the other -libpath flags. + _clang_lib_dir = string_replace(_clang_lib_dir, "/", "\\") + } + + sys_include_flags = "${win_toolchain_data.include_flags_imsvc}" + sys_lib_flags = + "-libpath:$_clang_lib_dir ${win_toolchain_data.libpath_flags}" + + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + is_clang = true + current_os = "win" + target_cpu = "arm64" + } + } +} + +if (target_cpu == "x86" || target_cpu == "x64") { + win_toolchains("x86") { + toolchain_arch = "x86" + } + win_toolchains("x64") { + toolchain_arch = "x64" + } +} + +if (target_cpu == "arm64") { + win_toolchains("arm64") { + toolchain_arch = "arm64" + } + win_toolchains(host_cpu) { + toolchain_arch = host_cpu + } +} + +# The nacl_win64 toolchain is nearly identical to the plain x64 toolchain. +# It's used solely for building nacl64.exe (//components/nacl/broker:nacl64). +# The only reason it's a separate toolchain is so that it can force +# is_component_build to false in the toolchain_args() block, because +# building nacl64.exe in component style does not work. +win_toolchains("nacl_win64") { + toolchain_arch = "x64" + toolchain_args = { + is_component_build = false + } +} + +# WinUWP toolchains. Only define these when targeting them. + +if (target_os == "winuwp") { + assert(target_cpu == "x64" || target_cpu == "x86" || target_cpu == "arm" || + target_cpu == "arm64") + store_cpu_toolchain_data = exec_script("setup_toolchain.py", + [ + visual_studio_path, + windows_sdk_path, + visual_studio_runtime_dirs, + target_os, + target_cpu, + "environment.store_" + target_cpu, + ], + "scope") + + msvc_toolchain("uwp_" + target_cpu) { + environment = "environment.store_" + target_cpu + cl = "${goma_prefix}\"${store_cpu_toolchain_data.vc_bin_dir}/cl.exe\"" + toolchain_args = { + current_os = "winuwp" + target_cpu = target_cpu + is_clang = false + } + } +} diff --git a/third_party/libwebrtc/build/toolchain/win/midl.gni b/third_party/libwebrtc/build/toolchain/win/midl.gni new file mode 100644 index 0000000000..ecf02acd3c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/midl.gni @@ -0,0 +1,199 @@ +# 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. + +assert(is_win) + +import("//build/config/win/visual_studio_version.gni") + +# This template defines a rule to invoke the MS IDL compiler. The generated +# source code will be compiled and linked into targets that depend on this. +# +# Parameters +# +# sources +# List of .idl file to process. +# +# header_file (optional) +# File name of generated header file. Defaults to the basename of the +# source idl file with a .h extension. +# +# out_dir (optional) +# Directory to write the generated files to. Defaults to target_gen_dir. +# +# generated_dir (optional) +# Directory where generated files were previously persisted. +# Defaults to third_party\win_build_output\midl\|out_dir|. +# +# dynamic_guids (optional) +# If the GUIDs are not constant across builds, the current GUID +# substitutions. +# |dynamic_guids| is of the form: +# "PLACEHOLDER-GUID-158428a4-6014-4978-83ba-9fad0dabe791=" +# "3d852661-c795-4d20-9b95-5561e9a1d2d9," +# "PLACEHOLDER-GUID-63B8FFB1-5314-48C9-9C57-93EC8BC6184B=" +# "D0E1CACC-C63C-4192-94AB-BF8EAD0E3B83". +# See midl.py for more details. +# +# writes_tlb (optional) +# Whether a .tlb file should be added to outputs. Defaults to false. +# +# writes_proxy(optional) +# Whether a _p.c file should be added to outputs. Defaults to true. +# +# writes_dlldata(optional) +# Whether a .dlldata.c file should be added to outputs. Defaults to true. +# +# deps (optional) +# +# defines (optional) +# Build time defines to be passed to midl.exe as /D parameter. +# +# visibility (optional) + +template("midl") { + action_name = "${target_name}_idl_action" + source_set_name = target_name + + assert(defined(invoker.sources), "Source must be defined for $target_name") + + if (defined(invoker.out_dir)) { + out_dir = invoker.out_dir + } else { + out_dir = target_gen_dir + } + + if (defined(invoker.generated_dir)) { + generated_dir = rebase_path(invoker.generated_dir) + } else { + # midl.py expects 'gen' to be replaced with 'midl'. + generated_dir = rebase_path("//third_party/win_build_output") + "/midl/" + + rebase_path(out_dir, root_gen_dir) + } + + if (defined(invoker.dynamic_guids)) { + dynamic_guids = invoker.dynamic_guids + } else { + dynamic_guids = "none" + } + + if (defined(invoker.header_file)) { + header_file = invoker.header_file + } else { + header_file = "{{source_name_part}}.h" + } + + if (defined(invoker.writes_tlb)) { + writes_tlb = invoker.writes_tlb + } else { + writes_tlb = false + } + + if (defined(invoker.writes_proxy)) { + writes_proxy = invoker.writes_proxy + } else { + writes_proxy = true + } + + if (defined(invoker.writes_dlldata)) { + writes_dlldata = invoker.writes_dlldata + } else { + writes_dlldata = true + } + + if (writes_tlb) { + type_library_file = "{{source_name_part}}.tlb" + } else { + type_library_file = "none" + } + + if (writes_dlldata) { + dlldata_file = "{{source_name_part}}.dlldata.c" + } else { + dlldata_file = "none" + } + + if (writes_proxy) { + proxy_file = "{{source_name_part}}_p.c" + } else { + proxy_file = "none" + } + + interface_identifier_file = "{{source_name_part}}_i.c" + + action_foreach(action_name) { + visibility = [ ":$source_set_name" ] + script = "//build/toolchain/win/midl.py" + + sources = invoker.sources + + outputs = [ + "$out_dir/$header_file", + "$out_dir/$interface_identifier_file", + ] + + # These files are only added to outputs if the invoker so desires, as it + # they are not always generated depending on the content of the input idl + # file. + if (writes_tlb) { + outputs += [ "$out_dir/$type_library_file" ] + } + if (writes_dlldata) { + outputs += [ "$out_dir/$dlldata_file" ] + } + if (writes_proxy) { + outputs += [ "$out_dir/$proxy_file" ] + } + + if (target_cpu == "x86") { + win_tool_arch = "environment.x86" + idl_target_platform = "win32" + } else if (target_cpu == "x64") { + win_tool_arch = "environment.x64" + idl_target_platform = "x64" + } else if (target_cpu == "arm64") { + win_tool_arch = "environment.arm64" + idl_target_platform = "arm64" + } else { + assert(false, "Need environment for this arch") + } + + args = [ + win_tool_arch, + generated_dir, + rebase_path(out_dir, root_build_dir), + dynamic_guids, + type_library_file, + header_file, + dlldata_file, + interface_identifier_file, + proxy_file, + rebase_path("//third_party/llvm-build/Release+Asserts/bin/clang-cl.exe", + root_build_dir), + "{{source}}", + "/char", + "signed", + "/env", + idl_target_platform, + "/Oicf", + ] + + if (defined(invoker.defines)) { + foreach(define, invoker.defines) { + args += [ "/D" + define ] + } + } + + forward_variables_from(invoker, [ "deps" ]) + } + + source_set(target_name) { + forward_variables_from(invoker, [ "visibility" ]) + + # We only compile the IID files from the IDL tool rather than all outputs. + sources = process_file_template(invoker.sources, + [ "$out_dir/$interface_identifier_file" ]) + + public_deps = [ ":$action_name" ] + } +} diff --git a/third_party/libwebrtc/build/toolchain/win/midl.py b/third_party/libwebrtc/build/toolchain/win/midl.py new file mode 100644 index 0000000000..cfb4220133 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/midl.py @@ -0,0 +1,487 @@ +# Copyright 2017 The Chromium 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 division +from __future__ import print_function + +import array +import difflib +import distutils.dir_util +import filecmp +import io +import operator +import os +import posixpath +import re +import shutil +import struct +import subprocess +import sys +import tempfile +import uuid + +from functools import reduce + + +def ZapTimestamp(filename): + contents = open(filename, 'rb').read() + # midl.exe writes timestamp 2147483647 (2^31 - 1) as creation date into its + # outputs, but using the local timezone. To make the output timezone- + # independent, replace that date with a fixed string of the same length. + # Also blank out the minor version number. + if filename.endswith('.tlb'): + # See https://chromium-review.googlesource.com/c/chromium/src/+/693223 for + # a fairly complete description of the .tlb binary format. + # TLB files start with a 54 byte header. Offset 0x20 stores how many types + # are defined in the file, and the header is followed by that many uint32s. + # After that, 15 section headers appear. Each section header is 16 bytes, + # starting with offset and length uint32s. + # Section 12 in the file contains custom() data. custom() data has a type + # (int, string, etc). Each custom data chunk starts with a uint16_t + # describing its type. Type 8 is string data, consisting of a uint32_t + # len, followed by that many data bytes, followed by 'W' bytes to pad to a + # 4 byte boundary. Type 0x13 is uint32 data, followed by 4 data bytes, + # followed by two 'W' to pad to a 4 byte boundary. + # The custom block always starts with one string containing "Created by + # MIDL version 8...", followed by one uint32 containing 0x7fffffff, + # followed by another uint32 containing the MIDL compiler version (e.g. + # 0x0801026e for v8.1.622 -- 0x26e == 622). These 3 fields take 0x54 bytes. + # There might be more custom data after that, but these 3 blocks are always + # there for file-level metadata. + # All data is little-endian in the file. + assert contents[0:8] == b'MSFT\x02\x00\x01\x00' + ntypes, = struct.unpack_from('= 0x54 + # First: Type string (0x8), followed by 0x3e characters. + assert contents[custom_off:custom_off + 6] == b'\x08\x00\x3e\x00\x00\x00' + assert re.match( + br'Created by MIDL version 8\.\d\d\.\d{4} ' + br'at ... Jan 1. ..:..:.. 2038\n', + contents[custom_off + 6:custom_off + 6 + 0x3e]) + # Second: Type uint32 (0x13) storing 0x7fffffff (followed by WW / 0x57 pad) + assert contents[custom_off+6+0x3e:custom_off+6+0x3e+8] == \ + b'\x13\x00\xff\xff\xff\x7f\x57\x57' + # Third: Type uint32 (0x13) storing MIDL compiler version. + assert contents[custom_off + 6 + 0x3e + 8:custom_off + 6 + 0x3e + 8 + + 2] == b'\x13\x00' + # Replace "Created by" string with fixed string, and fixed MIDL version with + # 8.1.622 always. + contents = ( + contents[0:custom_off + 6] + + b'Created by MIDL version 8.xx.xxxx at a redacted point in time\n' + + # uint32 (0x13) val 0x7fffffff, WW, uint32 (0x13), val 0x0801026e, WW + b'\x13\x00\xff\xff\xff\x7f\x57\x57\x13\x00\x6e\x02\x01\x08\x57\x57' + + contents[custom_off + 0x54:]) + else: + contents = re.sub( + br'File created by MIDL compiler version 8\.\d\d\.\d{4} \*/\r\n' + br'/\* at ... Jan 1. ..:..:.. 2038', + br'File created by MIDL compiler version 8.xx.xxxx */\r\n' + br'/* at a redacted point in time', contents) + contents = re.sub( + br' Oicf, W1, Zp8, env=(.....) \(32b run\), ' + br'target_arch=(AMD64|X86) 8\.\d\d\.\d{4}', + br' Oicf, W1, Zp8, env=\1 (32b run), target_arch=\2 8.xx.xxxx', + contents) + # TODO(thakis): If we need more hacks than these, try to verify checked-in + # outputs when we're using the hermetic toolchain. + # midl.exe older than 8.1.622 omit '//' after #endif, fix that: + contents = contents.replace(b'#endif !_MIDL_USE_GUIDDEF_', + b'#endif // !_MIDL_USE_GUIDDEF_') + # midl.exe puts the midl version into code in one place. To have + # predictable output, lie about the midl version if it's not 8.1.622. + # This is unfortunate, but remember that there's beauty too in imperfection. + contents = contents.replace(b'0x801026c, /* MIDL Version 8.1.620 */', + b'0x801026e, /* MIDL Version 8.1.622 */') + open(filename, 'wb').write(contents) + + +def get_tlb_contents(tlb_file): + # See ZapTimestamp() for a short overview of the .tlb format. + contents = open(tlb_file, 'rb').read() + assert contents[0:8] == b'MSFT\x02\x00\x01\x00' + ntypes, = struct.unpack_from(' debug_offset + assert header.PointerToRelocations == 0 + assert header.PointerToLineNumbers == 0 + + # Make sure the first non-empty section's data starts right after the section + # headers. + for section_header in section_headers: + if section_header.PointerToRawData == 0: + assert section_header.PointerToRelocations == 0 + assert section_header.PointerToLineNumbers == 0 + continue + assert section_header.PointerToRawData == data_start + break + + # Make sure the symbol table (and hence, string table) appear after the last + # section: + assert (coff_header.PointerToSymbolTable >= + section_headers[-1].PointerToRawData + section_headers[-1].SizeOfRawData) + + # The symbol table contains a symbol for the no-longer-present .debug$S + # section. If we leave it there, lld-link will complain: + # + # lld-link: error: .debug$S should not refer to non-existent section 5 + # + # so we need to remove that symbol table entry as well. This shifts symbol + # entries around and we need to update symbol table indices in: + # - relocations + # - line number records (never present) + # - one aux symbol entry (IMAGE_SYM_CLASS_CLR_TOKEN; not present in ml output) + SYM = Struct('SYM', + '8s', 'Name', + 'I', 'Value', + 'h', 'SectionNumber', # Note: Signed! + 'H', 'Type', + + 'B', 'StorageClass', + 'B', 'NumberOfAuxSymbols') + i = 0 + debug_sym = -1 + while i < coff_header.NumberOfSymbols: + sym_offset = coff_header.PointerToSymbolTable + i * SYM.size() + sym = SYM.unpack_from(objdata, sym_offset) + + # 107 is IMAGE_SYM_CLASS_CLR_TOKEN, which has aux entry "CLR Token + # Definition", which contains a symbol index. Check it's never present. + assert sym.StorageClass != 107 + + # Note: sym.SectionNumber is 1-based, debug_section_index is 0-based. + if sym.SectionNumber - 1 == debug_section_index: + assert debug_sym == -1, 'more than one .debug$S symbol found' + debug_sym = i + # Make sure the .debug$S symbol looks like we expect. + # In particular, it should have exactly one aux symbol. + assert sym.Name == b'.debug$S' + assert sym.Value == 0 + assert sym.Type == 0 + assert sym.StorageClass == 3 + assert sym.NumberOfAuxSymbols == 1 + elif sym.SectionNumber > debug_section_index: + sym = Subtract(sym, SectionNumber=1) + SYM.pack_into(objdata, sym_offset, sym) + i += 1 + sym.NumberOfAuxSymbols + assert debug_sym != -1, '.debug$S symbol not found' + + # Note: Usually the .debug$S section is the last, but for files saying + # `includelib foo.lib`, like safe_terminate_process.asm in 32-bit builds, + # this isn't true: .drectve is after .debug$S. + + # Update symbol table indices in relocations. + # There are a few processor types that have one or two relocation types + # where SymbolTableIndex has a different meaning, but not for x86. + REL = Struct('REL', + 'I', 'VirtualAddress', + 'I', 'SymbolTableIndex', + 'H', 'Type') + for header in section_headers[0:debug_section_index]: + for j in range(0, header.NumberOfRelocations): + rel_offset = header.PointerToRelocations + j * REL.size() + rel = REL.unpack_from(objdata, rel_offset) + assert rel.SymbolTableIndex != debug_sym + if rel.SymbolTableIndex > debug_sym: + rel = Subtract(rel, SymbolTableIndex=2) + REL.pack_into(objdata, rel_offset, rel) + + # Update symbol table indices in line numbers -- just check they don't exist. + for header in section_headers: + assert header.NumberOfLineNumbers == 0 + + # Now that all indices are updated, remove the symbol table entry referring to + # .debug$S and its aux entry. + del objdata[coff_header.PointerToSymbolTable + debug_sym * SYM.size(): + coff_header.PointerToSymbolTable + (debug_sym + 2) * SYM.size()] + + # Now we know that it's safe to write out the input data, with just the + # timestamp overwritten to 0, the last section header cut out (and the + # offsets of all other section headers decremented by the size of that + # one section header), and the last section's data cut out. The symbol + # table offset needs to be reduced by one section header and the size of + # the missing section. + # (The COFF spec only requires on-disk sections to be aligned in image files, + # for obj files it's not required. If that wasn't the case, deleting slices + # if data would not generally be safe.) + + # Update section offsets and remove .debug$S section data. + for i in range(0, debug_section_index): + header = section_headers[i] + if header.SizeOfRawData: + header = Subtract(header, PointerToRawData=SECTIONHEADER.size()) + if header.NumberOfRelocations: + header = Subtract(header, PointerToRelocations=SECTIONHEADER.size()) + if header.NumberOfLineNumbers: + header = Subtract(header, PointerToLineNumbers=SECTIONHEADER.size()) + SECTIONHEADER.pack_into( + objdata, COFFHEADER.size() + i * SECTIONHEADER.size(), header) + for i in range(debug_section_index + 1, len(section_headers)): + header = section_headers[i] + shift = SECTIONHEADER.size() + debug_size + if header.SizeOfRawData: + header = Subtract(header, PointerToRawData=shift) + if header.NumberOfRelocations: + header = Subtract(header, PointerToRelocations=shift) + if header.NumberOfLineNumbers: + header = Subtract(header, PointerToLineNumbers=shift) + SECTIONHEADER.pack_into( + objdata, COFFHEADER.size() + i * SECTIONHEADER.size(), header) + + del objdata[debug_offset:debug_offset + debug_size] + + # Finally, remove .debug$S section header and update coff header. + coff_header = coff_header._replace(TimeDateStamp=0) + coff_header = Subtract(coff_header, + NumberOfSections=1, + PointerToSymbolTable=SECTIONHEADER.size() + debug_size, + NumberOfSymbols=2) + COFFHEADER.pack_into(objdata, 0, coff_header) + + del objdata[ + COFFHEADER.size() + debug_section_index * SECTIONHEADER.size(): + COFFHEADER.size() + (debug_section_index + 1) * SECTIONHEADER.size()] + + # All done! + if sys.version_info.major == 2: + return objdata.tostring() + else: + return objdata.tobytes() + + +def main(): + ml_result = subprocess.call(sys.argv[1:]) + if ml_result != 0: + return ml_result + + objfile = None + for i in range(1, len(sys.argv)): + if sys.argv[i].startswith('/Fo'): + objfile = sys.argv[i][len('/Fo'):] + assert objfile, 'failed to find ml output' + + with open(objfile, 'rb') as f: + objdata = f.read() + objdata = MakeDeterministic(objdata) + with open(objfile, 'wb') as f: + f.write(objdata) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/win/rc/.gitignore b/third_party/libwebrtc/build/toolchain/win/rc/.gitignore new file mode 100644 index 0000000000..e8fc4d3e1f --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/.gitignore @@ -0,0 +1,3 @@ +linux64/rc +mac/rc +win/rc.exe diff --git a/third_party/libwebrtc/build/toolchain/win/rc/README.md b/third_party/libwebrtc/build/toolchain/win/rc/README.md new file mode 100644 index 0000000000..e6d38f9709 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/README.md @@ -0,0 +1,30 @@ +# rc + +This contains a cross-platform reimplementation of rc.exe. + +This exists mainly to compile .rc files on non-Windows hosts for cross builds. +However, it also runs on Windows for two reasons: + +1. To compare the output of Microsoft's rc.exe and the reimplementation and to + check that they produce bitwise identical output. +2. The reimplementation supports printing resource files in /showIncludes + output, which helps getting build dependencies right. + +The resource compiler consists of two parts: + +1. A python script rc.py that serves as the driver. It does unicode + conversions, runs the input through the preprocessor, and then calls the + actual resource compiler. +2. The resource compiler, a C++ binary obtained via sha1 files from Google + Storage. The binary's code currenty lives at + https://github.com/nico/hack/tree/master/res, even though work is (slowly) + underway to upstream it into LLVM. + +To update the rc binary, run `upload_rc_binaries.sh` in this directory, on a +Mac. + +rc isn't built from source as part of the regular chrome build because +it's needed in a gn toolchain tool, and these currently cannot have deps. +Alternatively, gn could be taught about deps on tools, or rc invocations could +be not a tool but a template like e.g. yasm invocations (which can have deps), +then the prebuilt binaries wouldn't be needed. diff --git a/third_party/libwebrtc/build/toolchain/win/rc/linux64/rc.sha1 b/third_party/libwebrtc/build/toolchain/win/rc/linux64/rc.sha1 new file mode 100644 index 0000000000..ad14ca46a9 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/linux64/rc.sha1 @@ -0,0 +1 @@ +2d0c766039264dc2514d005a42f074af4838a446 \ No newline at end of file diff --git a/third_party/libwebrtc/build/toolchain/win/rc/mac/rc.sha1 b/third_party/libwebrtc/build/toolchain/win/rc/mac/rc.sha1 new file mode 100644 index 0000000000..dbd6302a35 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/mac/rc.sha1 @@ -0,0 +1 @@ +4c25c3bcb6608109bb52028d008835895cf72629 \ No newline at end of file diff --git a/third_party/libwebrtc/build/toolchain/win/rc/rc.py b/third_party/libwebrtc/build/toolchain/win/rc/rc.py new file mode 100755 index 0000000000..2ab41225fb --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/rc.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium 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: rc.py [options] input.res +A resource compiler for .rc files. + +options: +-h, --help Print this message. +-I Add include path, used for both headers and resources. +-imsvc Add system include path, used for preprocessing only. +/winsysroot Set winsysroot, used for preprocessing only. +-D Define a macro for the preprocessor. +/fo Set path of output .res file. +/nologo Ignored (rc.py doesn't print a logo by default). +/showIncludes Print referenced header and resource files.""" + +from __future__ import print_function +from collections import namedtuple +import codecs +import os +import re +import subprocess +import sys +import tempfile + + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +SRC_DIR = \ + os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(THIS_DIR)))) + + +def ParseFlags(): + """Parses flags off sys.argv and returns the parsed flags.""" + # Can't use optparse / argparse because of /fo flag :-/ + includes = [] + imsvcs = [] + winsysroot = [] + defines = [] + output = None + input = None + show_includes = False + # Parse. + for flag in sys.argv[1:]: + if flag == '-h' or flag == '--help': + print(__doc__) + sys.exit(0) + if flag.startswith('-I'): + includes.append(flag) + elif flag.startswith('-imsvc'): + imsvcs.append(flag) + elif flag.startswith('/winsysroot'): + winsysroot = [flag] + elif flag.startswith('-D'): + defines.append(flag) + elif flag.startswith('/fo'): + if output: + print('rc.py: error: multiple /fo flags', '/fo' + output, flag, + file=sys.stderr) + sys.exit(1) + output = flag[3:] + elif flag == '/nologo': + pass + elif flag == '/showIncludes': + show_includes = True + elif (flag.startswith('-') or + (flag.startswith('/') and not os.path.exists(flag))): + print('rc.py: error: unknown flag', flag, file=sys.stderr) + print(__doc__, file=sys.stderr) + sys.exit(1) + else: + if input: + print('rc.py: error: multiple inputs:', input, flag, file=sys.stderr) + sys.exit(1) + input = flag + # Validate and set default values. + if not input: + print('rc.py: error: no input file', file=sys.stderr) + sys.exit(1) + if not output: + output = os.path.splitext(input)[0] + '.res' + Flags = namedtuple('Flags', [ + 'includes', 'defines', 'output', 'imsvcs', 'winsysroot', 'input', + 'show_includes' + ]) + return Flags(includes=includes, + defines=defines, + output=output, + imsvcs=imsvcs, + winsysroot=winsysroot, + input=input, + show_includes=show_includes) + + +def ReadInput(input): + """"Reads input and returns it. For UTF-16LEBOM input, converts to UTF-8.""" + # Microsoft's rc.exe only supports unicode in the form of UTF-16LE with a BOM. + # Our rc binary sniffs for UTF-16LE. If that's not found, if /utf-8 is + # passed, the input is treated as UTF-8. If /utf-8 is not passed and the + # input is not UTF-16LE, then our rc errors out on characters outside of + # 7-bit ASCII. Since the driver always converts UTF-16LE to UTF-8 here (for + # the preprocessor, which doesn't support UTF-16LE), our rc will either see + # UTF-8 with the /utf-8 flag (for UTF-16LE input), or ASCII input. + # This is compatible with Microsoft rc.exe. If we wanted, we could expose + # a /utf-8 flag for the driver for UTF-8 .rc inputs too. + # TODO(thakis): Microsoft's rc.exe supports BOM-less UTF-16LE. We currently + # don't, but for chrome it currently doesn't matter. + is_utf8 = False + try: + with open(input, 'rb') as rc_file: + rc_file_data = rc_file.read() + if rc_file_data.startswith(codecs.BOM_UTF16_LE): + rc_file_data = rc_file_data[2:].decode('utf-16le').encode('utf-8') + is_utf8 = True + except IOError: + print('rc.py: failed to open', input, file=sys.stderr) + sys.exit(1) + except UnicodeDecodeError: + print('rc.py: failed to decode UTF-16 despite BOM', input, file=sys.stderr) + sys.exit(1) + return rc_file_data, is_utf8 + + +def Preprocess(rc_file_data, flags): + """Runs the input file through the preprocessor.""" + clang = os.path.join(SRC_DIR, 'third_party', 'llvm-build', + 'Release+Asserts', 'bin', 'clang-cl') + # Let preprocessor write to a temp file so that it doesn't interfere + # with /showIncludes output on stdout. + if sys.platform == 'win32': + clang += '.exe' + temp_handle, temp_file = tempfile.mkstemp(suffix='.i') + # Closing temp_handle immediately defeats the purpose of mkstemp(), but I + # can't figure out how to let write to the temp file on Windows otherwise. + os.close(temp_handle) + clang_cmd = [clang, '/P', '/DRC_INVOKED', '/TC', '-', '/Fi' + temp_file] + if flags.imsvcs: + clang_cmd += ['/X'] + if os.path.dirname(flags.input): + # This must precede flags.includes. + clang_cmd.append('-I' + os.path.dirname(flags.input)) + if flags.show_includes: + clang_cmd.append('/showIncludes') + clang_cmd += flags.imsvcs + flags.winsysroot + flags.includes + flags.defines + p = subprocess.Popen(clang_cmd, stdin=subprocess.PIPE) + p.communicate(input=rc_file_data) + if p.returncode != 0: + sys.exit(p.returncode) + preprocessed_output = open(temp_file, 'rb').read() + os.remove(temp_file) + + # rc.exe has a wacko preprocessor: + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx + # """RC treats files with the .c and .h extensions in a special manner. It + # assumes that a file with one of these extensions does not contain + # resources. If a file has the .c or .h file name extension, RC ignores all + # lines in the file except the preprocessor directives.""" + # Thankfully, the Microsoft headers are mostly good about putting everything + # in the system headers behind `if !defined(RC_INVOKED)`, so regular + # preprocessing with RC_INVOKED defined works. + return preprocessed_output + + +def RunRc(preprocessed_output, is_utf8, flags): + if sys.platform.startswith('linux'): + rc = os.path.join(THIS_DIR, 'linux64', 'rc') + elif sys.platform == 'darwin': + rc = os.path.join(THIS_DIR, 'mac', 'rc') + elif sys.platform == 'win32': + rc = os.path.join(THIS_DIR, 'win', 'rc.exe') + else: + print('rc.py: error: unsupported platform', sys.platform, file=sys.stderr) + sys.exit(1) + rc_cmd = [rc] + # Make sure rc-relative resources can be found: + if os.path.dirname(flags.input): + rc_cmd.append('/cd' + os.path.dirname(flags.input)) + rc_cmd.append('/fo' + flags.output) + if is_utf8: + rc_cmd.append('/utf-8') + # TODO(thakis): cl currently always prints full paths for /showIncludes, + # but clang-cl /P doesn't. Which one is right? + if flags.show_includes: + rc_cmd.append('/showIncludes') + # Microsoft rc.exe searches for referenced files relative to -I flags in + # addition to the pwd, so -I flags need to be passed both to both + # the preprocessor and rc. + rc_cmd += flags.includes + p = subprocess.Popen(rc_cmd, stdin=subprocess.PIPE) + p.communicate(input=preprocessed_output) + + if flags.show_includes and p.returncode == 0: + TOOL_DIR = os.path.dirname(os.path.relpath(THIS_DIR)).replace("\\", "/") + # Since tool("rc") can't have deps, add deps on this script and on rc.py + # and its deps here, so that rc edges become dirty if rc.py changes. + print('Note: including file: {}/tool_wrapper.py'.format(TOOL_DIR)) + print('Note: including file: {}/rc/rc.py'.format(TOOL_DIR)) + print( + 'Note: including file: {}/rc/linux64/rc.sha1'.format(TOOL_DIR)) + print('Note: including file: {}/rc/mac/rc.sha1'.format(TOOL_DIR)) + print( + 'Note: including file: {}/rc/win/rc.exe.sha1'.format(TOOL_DIR)) + + return p.returncode + + +def CompareToMsRcOutput(preprocessed_output, is_utf8, flags): + msrc_in = flags.output + '.preprocessed.rc' + + # Strip preprocessor line markers. + preprocessed_output = re.sub(br'^#.*$', b'', preprocessed_output, flags=re.M) + if is_utf8: + preprocessed_output = preprocessed_output.decode('utf-8').encode('utf-16le') + with open(msrc_in, 'wb') as f: + f.write(preprocessed_output) + + msrc_out = flags.output + '_ms_rc' + msrc_cmd = ['rc', '/nologo', '/x', '/fo' + msrc_out] + + # Make sure rc-relative resources can be found. rc.exe looks for external + # resource files next to the file, but the preprocessed file isn't where the + # input was. + # Note that rc searches external resource files in the order of + # 1. next to the input file + # 2. relative to cwd + # 3. next to -I directories + # Changing the cwd means we'd have to rewrite all -I flags, so just add + # the input file dir as -I flag. That technically gets the order of 1 and 2 + # wrong, but in Chromium's build the cwd is the gn out dir, and generated + # files there are in obj/ and gen/, so this difference doesn't matter in + # practice. + if os.path.dirname(flags.input): + msrc_cmd += [ '-I' + os.path.dirname(flags.input) ] + + # Microsoft rc.exe searches for referenced files relative to -I flags in + # addition to the pwd, so -I flags need to be passed both to both + # the preprocessor and rc. + msrc_cmd += flags.includes + + # Input must come last. + msrc_cmd += [ msrc_in ] + + rc_exe_exit_code = subprocess.call(msrc_cmd) + # Assert Microsoft rc.exe and rc.py produced identical .res files. + if rc_exe_exit_code == 0: + import filecmp + assert filecmp.cmp(msrc_out, flags.output) + return rc_exe_exit_code + + +def main(): + # This driver has to do these things: + # 1. Parse flags. + # 2. Convert the input from UTF-16LE to UTF-8 if needed. + # 3. Pass the input through a preprocessor (and clean up the preprocessor's + # output in minor ways). + # 4. Call rc for the heavy lifting. + flags = ParseFlags() + rc_file_data, is_utf8 = ReadInput(flags.input) + preprocessed_output = Preprocess(rc_file_data, flags) + rc_exe_exit_code = RunRc(preprocessed_output, is_utf8, flags) + + # 5. On Windows, we also call Microsoft's rc.exe and check that we produced + # the same output. + # Since Microsoft's rc has a preprocessor that only accepts 32 characters + # for macro names, feed the clang-preprocessed source into it instead + # of using ms rc's preprocessor. + if sys.platform == 'win32' and rc_exe_exit_code == 0: + rc_exe_exit_code = CompareToMsRcOutput(preprocessed_output, is_utf8, flags) + + return rc_exe_exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/toolchain/win/rc/upload_rc_binaries.sh b/third_party/libwebrtc/build/toolchain/win/rc/upload_rc_binaries.sh new file mode 100755 index 0000000000..ec4df4cbce --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/upload_rc_binaries.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +set -eu + +# Builds new rc binaries at head and uploads them to google storage. +# The new .sha1 files will be in the tree after this has run. + +if [[ "$OSTYPE" != "darwin"* ]]; then + echo "this script must run on a mac" + exit 1 +fi + +DIR="$(cd "$(dirname "${0}" )" && pwd)" +SRC_DIR="$DIR/../../../.." + +# Make sure Linux and Windows sysroots are installed, for distrib.py. +$SRC_DIR/build/linux/sysroot_scripts/install-sysroot.py --arch amd64 +$SRC_DIR/build/vs_toolchain.py update --force + +# Make a temporary directory. +WORK_DIR=$(mktemp -d) +if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then + echo "could not create temp dir" + exit 1 +fi +function cleanup { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +# Check out rc and build it in the temporary directory. Copy binaries over. +pushd "$WORK_DIR" > /dev/null +git clone -q https://github.com/nico/hack +cd hack/res +./distrib.py "$SRC_DIR" +popd > /dev/null +cp "$WORK_DIR/hack/res/rc-linux64" "$DIR/linux64/rc" +cp "$WORK_DIR/hack/res/rc-mac" "$DIR/mac/rc" +cp "$WORK_DIR/hack/res/rc-win.exe" "$DIR/win/rc.exe" + +# Upload binaries to cloud storage. +upload_to_google_storage.py -b chromium-browser-clang/rc "$DIR/linux64/rc" +upload_to_google_storage.py -b chromium-browser-clang/rc "$DIR/mac/rc" +upload_to_google_storage.py -b chromium-browser-clang/rc "$DIR/win/rc.exe" diff --git a/third_party/libwebrtc/build/toolchain/win/rc/win/rc.exe.sha1 b/third_party/libwebrtc/build/toolchain/win/rc/win/rc.exe.sha1 new file mode 100644 index 0000000000..3fdbfc0c20 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/win/rc.exe.sha1 @@ -0,0 +1 @@ +ba51d69039ffb88310b72b6568efa9f0de148f8f \ No newline at end of file diff --git a/third_party/libwebrtc/build/toolchain/win/setup_toolchain.py b/third_party/libwebrtc/build/toolchain/win/setup_toolchain.py new file mode 100644 index 0000000000..1ff3608f0c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/setup_toolchain.py @@ -0,0 +1,314 @@ +# 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. +# +# Copies the given "win tool" (which the toolchain uses to wrap compiler +# invocations) and the environment blocks for the 32-bit and 64-bit builds on +# Windows to the build directory. +# +# The arguments are the visual studio install location and the location of the +# win tool. The script assumes that the root build directory is the current dir +# and the files will be written to the current directory. + +from __future__ import print_function + +import errno +import json +import os +import re +import subprocess +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +import gn_helpers + +SCRIPT_DIR = os.path.dirname(__file__) + +def _ExtractImportantEnvironment(output_of_set): + """Extracts environment variables required for the toolchain to run from + a textual dump output by the cmd.exe 'set' command.""" + envvars_to_save = ( + 'cipd_cache_dir', # needed by vpython + 'homedrive', # needed by vpython + 'homepath', # needed by vpython + 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma. + 'include', + 'lib', + 'libpath', + 'luci_context', # needed by vpython + 'path', + 'pathext', + 'systemroot', + 'temp', + 'tmp', + 'userprofile', # needed by vpython + 'vpython_virtualenv_root' # needed by vpython + ) + env = {} + # This occasionally happens and leads to misleading SYSTEMROOT error messages + # if not caught here. + if output_of_set.count('=') == 0: + raise Exception('Invalid output_of_set. Value is:\n%s' % output_of_set) + for line in output_of_set.splitlines(): + for envvar in envvars_to_save: + if re.match(envvar + '=', line.lower()): + var, setting = line.split('=', 1) + if envvar == 'path': + # Our own rules and actions in Chromium rely on python being in the + # path. Add the path to this python here so that if it's not in the + # path when ninja is run later, python will still be found. + setting = os.path.dirname(sys.executable) + os.pathsep + setting + env[var.upper()] = setting + break + if sys.platform in ('win32', 'cygwin'): + for required in ('SYSTEMROOT', 'TEMP', 'TMP'): + if required not in env: + raise Exception('Environment variable "%s" ' + 'required to be set to valid path' % required) + return env + + +def _DetectVisualStudioPath(): + """Return path to the installed Visual Studio. + """ + + # Use the code in build/vs_toolchain.py to avoid duplicating code. + chromium_dir = os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..', '..')) + sys.path.append(os.path.join(chromium_dir, 'build')) + import vs_toolchain + return vs_toolchain.DetectVisualStudioPath() + + +def _LoadEnvFromBat(args): + """Given a bat command, runs it and returns env vars set by it.""" + args = args[:] + args.extend(('&&', 'set')) + popen = subprocess.Popen( + args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + variables, _ = popen.communicate() + if popen.returncode != 0: + raise Exception('"%s" failed with error %d' % (args, popen.returncode)) + return variables.decode(errors='ignore') + + +def _LoadToolchainEnv(cpu, toolchain_root, sdk_dir, target_store): + """Returns a dictionary with environment variables that must be set while + running binaries from the toolchain (e.g. INCLUDE and PATH for cl.exe).""" + # Check if we are running in the SDK command line environment and use + # the setup script from the SDK if so. |cpu| should be either + # 'x86' or 'x64' or 'arm' or 'arm64'. + assert cpu in ('x86', 'x64', 'arm', 'arm64') + if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and sdk_dir: + # Load environment from json file. + env = os.path.normpath(os.path.join(sdk_dir, 'bin/SetEnv.%s.json' % cpu)) + env = json.load(open(env))['env'] + if env['VSINSTALLDIR'] == [["..", "..\\"]]: + # Old-style paths were relative to the win_sdk\bin directory. + json_relative_dir = os.path.join(sdk_dir, 'bin') + else: + # New-style paths are relative to the toolchain directory. + json_relative_dir = toolchain_root + for k in env: + entries = [os.path.join(*([json_relative_dir] + e)) for e in env[k]] + # clang-cl wants INCLUDE to be ;-separated even on non-Windows, + # lld-link wants LIB to be ;-separated even on non-Windows. Path gets :. + # The separator for INCLUDE here must match the one used in main() below. + sep = os.pathsep if k == 'PATH' else ';' + env[k] = sep.join(entries) + # PATH is a bit of a special case, it's in addition to the current PATH. + env['PATH'] = env['PATH'] + os.pathsep + os.environ['PATH'] + # Augment with the current env to pick up TEMP and friends. + for k in os.environ: + if k not in env: + env[k] = os.environ[k] + + varlines = [] + for k in sorted(env.keys()): + varlines.append('%s=%s' % (str(k), str(env[k]))) + variables = '\n'.join(varlines) + + # Check that the json file contained the same environment as the .cmd file. + if sys.platform in ('win32', 'cygwin'): + script = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.cmd')) + arg = '/' + cpu + json_env = _ExtractImportantEnvironment(variables) + cmd_env = _ExtractImportantEnvironment(_LoadEnvFromBat([script, arg])) + assert _LowercaseDict(json_env) == _LowercaseDict(cmd_env) + else: + if 'GYP_MSVS_OVERRIDE_PATH' not in os.environ: + os.environ['GYP_MSVS_OVERRIDE_PATH'] = _DetectVisualStudioPath() + # We only support x64-hosted tools. + script_path = os.path.normpath(os.path.join( + os.environ['GYP_MSVS_OVERRIDE_PATH'], + 'VC/vcvarsall.bat')) + if not os.path.exists(script_path): + # vcvarsall.bat for VS 2017 fails if run after running vcvarsall.bat from + # VS 2013 or VS 2015. Fix this by clearing the vsinstalldir environment + # variable. Since vcvarsall.bat appends to the INCLUDE, LIB, and LIBPATH + # environment variables we need to clear those to avoid getting double + # entries when vcvarsall.bat has been run before gn gen. vcvarsall.bat + # also adds to PATH, but there is no clean way of clearing that and it + # doesn't seem to cause problems. + if 'VSINSTALLDIR' in os.environ: + del os.environ['VSINSTALLDIR'] + del os.environ['INCLUDE'] + del os.environ['LIB'] + del os.environ['LIBPATH'] + other_path = os.path.normpath(os.path.join( + os.environ['GYP_MSVS_OVERRIDE_PATH'], + 'VC/Auxiliary/Build/vcvarsall.bat')) + if not os.path.exists(other_path): + raise Exception('%s is missing - make sure VC++ tools are installed.' % + script_path) + script_path = other_path + cpu_arg = "amd64" + if (cpu != 'x64'): + # x64 is default target CPU thus any other CPU requires a target set + cpu_arg += '_' + cpu + args = [script_path, cpu_arg, ] + # Store target must come before any SDK version declaration + if (target_store): + args.append('store') + # Explicitly specifying the SDK version to build with to avoid accidentally + # building with a new and untested SDK. This should stay in sync with the + # packaged toolchain in build/vs_toolchain.py. + args.append('10.0.19041.0') + variables = _LoadEnvFromBat(args) + return _ExtractImportantEnvironment(variables) + + +def _FormatAsEnvironmentBlock(envvar_dict): + """Format as an 'environment block' directly suitable for CreateProcess. + Briefly this is a list of key=value\0, terminated by an additional \0. See + CreateProcess documentation for more details.""" + block = '' + nul = '\0' + for key, value in envvar_dict.items(): + block += key + '=' + value + nul + block += nul + return block + + +def _LowercaseDict(d): + """Returns a copy of `d` with both key and values lowercased. + + Args: + d: dict to lowercase (e.g. {'A': 'BcD'}). + + Returns: + A dict with both keys and values lowercased (e.g.: {'a': 'bcd'}). + """ + return {k.lower(): d[k].lower() for k in d} + + +def FindFileInEnvList(env, env_name, separator, file_name, optional=False): + parts = env[env_name].split(separator) + for path in parts: + if os.path.exists(os.path.join(path, file_name)): + return os.path.realpath(path) + assert optional, "%s is not found in %s:\n%s\nCheck if it is installed." % ( + file_name, env_name, '\n'.join(parts)) + return '' + + +def main(): + if len(sys.argv) != 7: + print('Usage setup_toolchain.py ' + ' ' + ' ' + '') + sys.exit(2) + # toolchain_root and win_sdk_path are only read if the hermetic Windows + # toolchain is set, that is if DEPOT_TOOLS_WIN_TOOLCHAIN is not set to 0. + # With the hermetic Windows toolchain, the visual studio path in argv[1] + # is the root of the Windows toolchain directory. + toolchain_root = sys.argv[1] + win_sdk_path = sys.argv[2] + + runtime_dirs = sys.argv[3] + target_os = sys.argv[4] + target_cpu = sys.argv[5] + environment_block_name = sys.argv[6] + if (environment_block_name == 'none'): + environment_block_name = '' + + if (target_os == 'winuwp'): + target_store = True + else: + target_store = False + + cpus = ('x86', 'x64', 'arm', 'arm64') + assert target_cpu in cpus + vc_bin_dir = 'fake_path/cl.exe' + vc_lib_path = 'fake_path/lib' + vc_lib_atlmfc_path = 'fake_path/atlmfc' + vc_lib_um_path = 'fake_path/lib_um' + include = '' + lib = '' + + # TODO(scottmg|goma): Do we need an equivalent of + # ninja_use_custom_environment_files? + + def relflag(s): # Make s relative to builddir when cwd and sdk on same drive. + try: + return os.path.relpath(s).replace('\\', '/') + except ValueError: + return s + + def q(s): # Quote s if it contains spaces or other weird characters. + return s if re.match(r'^[a-zA-Z0-9._/\\:-]*$', s) else '"' + s + '"' + +# for cpu in cpus: +# if cpu == target_cpu: +# # Extract environment variables for subprocesses. +# env = _LoadToolchainEnv(cpu, toolchain_root, win_sdk_path, target_store) +# env['PATH'] = runtime_dirs + os.pathsep + env['PATH'] +# +# vc_bin_dir = FindFileInEnvList(env, 'PATH', os.pathsep, 'cl.exe') +# vc_lib_path = FindFileInEnvList(env, 'LIB', ';', 'msvcrt.lib') +# vc_lib_atlmfc_path = FindFileInEnvList( +# env, 'LIB', ';', 'atls.lib', optional=True) +# vc_lib_um_path = FindFileInEnvList(env, 'LIB', ';', 'user32.lib') +# +# # The separator for INCLUDE here must match the one used in +# # _LoadToolchainEnv() above. +# include = [p.replace('"', r'\"') for p in env['INCLUDE'].split(';') if p] +# include = list(map(relflag, include)) +# +# lib = [p.replace('"', r'\"') for p in env['LIB'].split(';') if p] +# lib = list(map(relflag, lib)) +# +# include_I = ' '.join([q('/I' + i) for i in include]) +# include_imsvc = ' '.join([q('-imsvc' + i) for i in include]) +# libpath_flags = ' '.join([q('-libpath:' + i) for i in lib]) +# +# if (environment_block_name != ''): +# env_block = _FormatAsEnvironmentBlock(env) +# with open(environment_block_name, 'w') as f: +# f.write(env_block) + +# We don't really use any of this information so it can be skipped altogether + env = {} + env['PATH'] = '' + include_I = include + include_imsvc = include + libpath_flags = '' + print('vc_bin_dir = ' + gn_helpers.ToGNString(vc_bin_dir)) + print('include_flags_I = ' + gn_helpers.ToGNString(include_I)) + if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and win_sdk_path: + print('include_flags_imsvc = ' + + gn_helpers.ToGNString(q('/winsysroot' + relflag(toolchain_root)))) + else: + print('include_flags_imsvc = ' + gn_helpers.ToGNString(include_imsvc)) + print('vc_lib_path = ' + gn_helpers.ToGNString(vc_lib_path)) + # Possible atlmfc library path gets introduced in the future for store thus + # output result if a result exists. + if (vc_lib_atlmfc_path != ''): + print('vc_lib_atlmfc_path = ' + gn_helpers.ToGNString(vc_lib_atlmfc_path)) + print('vc_lib_um_path = ' + gn_helpers.ToGNString(vc_lib_um_path)) + print('paths = ' + gn_helpers.ToGNString(env['PATH'])) + print('libpath_flags = ' + gn_helpers.ToGNString(libpath_flags)) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/toolchain/win/tool_wrapper.py b/third_party/libwebrtc/build/toolchain/win/tool_wrapper.py new file mode 100644 index 0000000000..9327369181 --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/tool_wrapper.py @@ -0,0 +1,190 @@ +# 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 functions for Windows builds. + +This file is copied to the build directory as part of toolchain setup and +is used to set up calls to tools used by the build that need wrappers. +""" + +from __future__ import print_function + +import os +import re +import shutil +import subprocess +import stat +import sys + + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# A regex matching an argument corresponding to the output filename passed to +# link.exe. +_LINK_EXE_OUT_ARG = re.compile('/OUT:(?P.+)$', re.IGNORECASE) + +def main(args): + exit_code = WinTool().Dispatch(args) + if exit_code is not None: + sys.exit(exit_code) + + +class WinTool(object): + """This class performs all the Windows tooling steps. The methods can either + be executed directly, or dispatched from an argument list.""" + + def _UseSeparateMspdbsrv(self, env, args): + """Allows to use a unique instance of mspdbsrv.exe per linker instead of a + shared one.""" + if len(args) < 1: + raise Exception("Not enough arguments") + + if args[0] != 'link.exe': + return + + # Use the output filename passed to the linker to generate an endpoint name + # for mspdbsrv.exe. + endpoint_name = None + for arg in args: + m = _LINK_EXE_OUT_ARG.match(arg) + if m: + endpoint_name = re.sub(r'\W+', '', + '%s_%d' % (m.group('out'), os.getpid())) + break + + if endpoint_name is None: + return + + # Adds the appropriate environment variable. This will be read by link.exe + # to know which instance of mspdbsrv.exe it should connect to (if it's + # not set then the default endpoint is used). + env['_MSPDBSRV_ENDPOINT_'] = endpoint_name + + def Dispatch(self, args): + """Dispatches a string command to a method.""" + if len(args) < 1: + raise Exception("Not enough arguments") + + method = "Exec%s" % self._CommandifyName(args[0]) + return getattr(self, method)(*args[1:]) + + def _CommandifyName(self, name_string): + """Transforms a tool name like recursive-mirror to RecursiveMirror.""" + return name_string.title().replace('-', '') + + def _GetEnv(self, arch): + """Gets the saved environment from a file for a given architecture.""" + # The environment is saved as an "environment block" (see CreateProcess + # and msvs_emulation for details). We convert to a dict here. + # Drop last 2 NULs, one for list terminator, one for trailing vs. separator. + pairs = open(arch).read()[:-2].split('\0') + kvs = [item.split('=', 1) for item in pairs] + return dict(kvs) + + def ExecDeleteFile(self, path): + """Simple file delete command.""" + if os.path.exists(path): + os.unlink(path) + + def ExecRecursiveMirror(self, source, dest): + """Emulation of rm -rf out && cp -af in out.""" + if os.path.exists(dest): + if os.path.isdir(dest): + def _on_error(fn, path, dummy_excinfo): + # The operation failed, possibly because the file is set to + # read-only. If that's why, make it writable and try the op again. + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWRITE) + fn(path) + shutil.rmtree(dest, onerror=_on_error) + else: + if not os.access(dest, os.W_OK): + # Attempt to make the file writable before deleting it. + os.chmod(dest, stat.S_IWRITE) + os.unlink(dest) + + if os.path.isdir(source): + shutil.copytree(source, dest) + else: + shutil.copy2(source, dest) + # Try to diagnose crbug.com/741603 + if not os.path.exists(dest): + raise Exception("Copying of %s to %s failed" % (source, dest)) + + def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args): + """Filter diagnostic output from link that looks like: + ' Creating library ui.dll.lib and object ui.dll.exp' + This happens when there are exports from the dll or exe. + """ + env = self._GetEnv(arch) + if use_separate_mspdbsrv == 'True': + self._UseSeparateMspdbsrv(env, args) + if sys.platform == 'win32': + args = list(args) # *args is a tuple by default, which is read-only. + args[0] = args[0].replace('/', '\\') + # https://docs.python.org/2/library/subprocess.html: + # "On Unix with shell=True [...] if args is a sequence, the first item + # specifies the command string, and any additional items will be treated as + # additional arguments to the shell itself. That is to say, Popen does the + # equivalent of: + # Popen(['/bin/sh', '-c', args[0], args[1], ...])" + # For that reason, since going through the shell doesn't seem necessary on + # non-Windows don't do that there. + pe_name = None + for arg in args: + m = _LINK_EXE_OUT_ARG.match(arg) + if m: + pe_name = m.group('out') + link = subprocess.Popen(args, shell=sys.platform == 'win32', env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # Read output one line at a time as it shows up to avoid OOM failures when + # GBs of output is produced. + for line in link.stdout: + if (not line.startswith(b' Creating library ') + and not line.startswith(b'Generating code') + and not line.startswith(b'Finished generating code')): + print(line) + return link.wait() + + def ExecAsmWrapper(self, arch, *args): + """Filter logo banner from invocations of asm.exe.""" + env = self._GetEnv(arch) + if sys.platform == 'win32': + # Windows ARM64 uses clang-cl as assembler which has '/' as path + # separator, convert it to '\\' when running on Windows. + args = list(args) # *args is a tuple by default, which is read-only + args[0] = args[0].replace('/', '\\') + popen = subprocess.Popen(args, shell=True, env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = popen.communicate() + for line in out.decode('utf8').splitlines(): + if not line.startswith(' Assembling: '): + print(line) + return popen.returncode + + def ExecRcWrapper(self, arch, *args): + """Converts .rc files to .res files.""" + env = self._GetEnv(arch) + args = list(args) + rcpy_args = args[:] + rcpy_args[0:1] = [sys.executable, os.path.join(BASE_DIR, 'rc', 'rc.py')] + rcpy_args.append('/showIncludes') + return subprocess.call(rcpy_args, env=env) + + def ExecActionWrapper(self, arch, rspfile, *dirname): + """Runs an action command line from a response file using the environment + for |arch|. If |dirname| is supplied, use that as the working directory.""" + env = self._GetEnv(arch) + # TODO(scottmg): This is a temporary hack to get some specific variables + # through to actions that are set after GN-time. http://crbug.com/333738. + for k, v in os.environ.items(): + if k not in env: + env[k] = v + args = open(rspfile).read() + dirname = dirname[0] if dirname else None + return subprocess.call(args, shell=True, env=env, cwd=dirname) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/toolchain/wrapper_utils.py b/third_party/libwebrtc/build/toolchain/wrapper_utils.py new file mode 100644 index 0000000000..5949a3727c --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/wrapper_utils.py @@ -0,0 +1,93 @@ +# Copyright (c) 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. + +"""Helper functions for gcc_toolchain.gni wrappers.""" + +import gzip +import os +import re +import subprocess +import shlex +import shutil +import sys +import threading + +_BAT_PREFIX = 'cmd /c call ' + + +def _GzipThenDelete(src_path, dest_path): + # Results for Android map file with GCC on a z620: + # Uncompressed: 207MB + # gzip -9: 16.4MB, takes 8.7 seconds. + # gzip -1: 21.8MB, takes 2.0 seconds. + # Piping directly from the linker via -print-map (or via -Map with a fifo) + # adds a whopping 30-45 seconds! + with open(src_path, 'rb') as f_in, gzip.GzipFile(dest_path, 'wb', 1) as f_out: + shutil.copyfileobj(f_in, f_out) + os.unlink(src_path) + + +def CommandToRun(command): + """Generates commands compatible with Windows. + + When running on a Windows host and using a toolchain whose tools are + actually wrapper scripts (i.e. .bat files on Windows) rather than binary + executables, the |command| to run has to be prefixed with this magic. + The GN toolchain definitions take care of that for when GN/Ninja is + running the tool directly. When that command is passed in to this + script, it appears as a unitary string but needs to be split up so that + just 'cmd' is the actual command given to Python's subprocess module. + + Args: + command: List containing the UNIX style |command|. + + Returns: + A list containing the Windows version of the |command|. + """ + if command[0].startswith(_BAT_PREFIX): + command = command[0].split(None, 3) + command[1:] + return command + + +def RunLinkWithOptionalMapFile(command, env=None, map_file=None): + """Runs the given command, adding in -Wl,-Map when |map_file| is given. + + Also takes care of gzipping when |map_file| ends with .gz. + + Args: + command: List of arguments comprising the command. + env: Environment variables. + map_file: Path to output map_file. + + Returns: + The exit code of running |command|. + """ + tmp_map_path = None + if map_file and map_file.endswith('.gz'): + tmp_map_path = map_file + '.tmp' + command.append('-Wl,-Map,' + tmp_map_path) + elif map_file: + command.append('-Wl,-Map,' + map_file) + + result = subprocess.call(command, env=env) + + if tmp_map_path and result == 0: + threading.Thread( + target=lambda: _GzipThenDelete(tmp_map_path, map_file)).start() + elif tmp_map_path and os.path.exists(tmp_map_path): + os.unlink(tmp_map_path) + + return result + + +def CaptureCommandStderr(command, env=None): + """Returns the stderr of a command. + + Args: + command: A list containing the command and arguments. + env: Environment variables for the new process. + """ + child = subprocess.Popen(command, stderr=subprocess.PIPE, env=env) + _, stderr = child.communicate() + return child.returncode, stderr diff --git a/third_party/libwebrtc/build/tree_truth.sh b/third_party/libwebrtc/build/tree_truth.sh new file mode 100755 index 0000000000..617092dc8a --- /dev/null +++ b/third_party/libwebrtc/build/tree_truth.sh @@ -0,0 +1,102 @@ +#!/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. +# +# Script for printing recent commits in a buildbot run. + +# Return the sha1 of the given tag. If not present, return "". +# $1: path to repo +# $2: tag name +tt_sha1_for_tag() { + oneline=$(cd $1 && git log -1 $2 --format='%H' 2>/dev/null) + if [ $? -eq 0 ] ; then + echo $oneline + fi +} + +# Return the sha1 of HEAD, or "" +# $1: path to repo +tt_sha1_for_head() { + ( cd $1 && git log HEAD -n1 --format='%H' | cat ) +} + +# For the given repo, set tag to HEAD. +# $1: path to repo +# $2: tag name +tt_tag_head() { + ( cd $1 && git tag -f $2 ) +} + +# For the given repo, delete the tag. +# $1: path to repo +# $2: tag name +tt_delete_tag() { + ( cd $1 && git tag -d $2 ) +} + +# For the given repo, set tag to "three commits ago" (for testing). +# $1: path to repo +# $2: tag name +tt_tag_three_ago() { + local sh=$(cd $1 && git log --pretty=oneline -n 3 | tail -1 | awk '{print $1}') + ( cd $1 && git tag -f $2 $sh ) +} + +# List the commits between the given tag and HEAD. +# If the tag does not exist, only list the last few. +# If the tag is at HEAD, list nothing. +# Output format has distinct build steps for repos with changes. +# $1: path to repo +# $2: tag name +# $3: simple/short repo name to use for display +tt_list_commits() { + local tag_sha1=$(tt_sha1_for_tag $1 $2) + local head_sha1=$(tt_sha1_for_head $1) + local display_name=$(echo $3 | sed 's#/#_#g') + if [ "${tag_sha1}" = "${head_sha1}" ] ; then + return + fi + if [ "${tag_sha1}" = "" ] ; then + echo "@@@BUILD_STEP Recent commits in repo $display_name@@@" + echo "NOTE: git tag was not found so we have no baseline." + echo "Here are some recent commits, but they may not be new for this build." + ( cd $1 && git log -n 10 --stat | cat) + else + echo "@@@BUILD_STEP New commits in repo $display_name@@@" + ( cd $1 && git log -n 500 $2..HEAD --stat | cat) + fi +} + +# Clean out the tree truth tags in all repos. For testing. +tt_clean_all() { + for project in $@; do + tt_delete_tag $CHROME_SRC/../$project tree_truth + done +} + +# Print tree truth for all clank repos. +tt_print_all() { + for project in $@; do + local full_path=$CHROME_SRC/../$project + tt_list_commits $full_path tree_truth $project + tt_tag_head $full_path tree_truth + done +} + +# Print a summary of the last 10 commits for each repo. +tt_brief_summary() { + echo "@@@BUILD_STEP Brief summary of recent CLs in every branch@@@" + for project in $@; do + echo $project: + local full_path=$CHROME_SRC/../$project + (cd $full_path && git log -n 10 --format=" %H %s %an, %ad" | cat) + echo "=================================================================" + done +} + +CHROME_SRC=$1 +shift +PROJECT_LIST=$@ +tt_brief_summary $PROJECT_LIST +tt_print_all $PROJECT_LIST diff --git a/third_party/libwebrtc/build/update-linux-sandbox.sh b/third_party/libwebrtc/build/update-linux-sandbox.sh new file mode 100755 index 0000000000..d24cf2602d --- /dev/null +++ b/third_party/libwebrtc/build/update-linux-sandbox.sh @@ -0,0 +1,82 @@ +#!/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. + +BUILDTYPE="${BUILDTYPE:-Debug}" +CHROME_SRC_DIR="${CHROME_SRC_DIR:-$(dirname -- $(readlink -fn -- "$0"))/..}" +CHROME_OUT_DIR="${CHROME_SRC_DIR}/${CHROMIUM_OUT_DIR:-out}/${BUILDTYPE}" +CHROME_SANDBOX_BUILD_PATH="${CHROME_OUT_DIR}/chrome_sandbox" +CHROME_SANDBOX_INST_PATH="/usr/local/sbin/chrome-devel-sandbox" +CHROME_SANDBOX_INST_DIR=$(dirname -- "$CHROME_SANDBOX_INST_PATH") + +TARGET_DIR_TYPE=$(stat -f -c %t -- "${CHROME_SANDBOX_INST_DIR}" 2>/dev/null) +if [ $? -ne 0 ]; then + echo "Could not get status of ${CHROME_SANDBOX_INST_DIR}" + exit 1 +fi + +# Make sure the path is not on NFS. +if [ "${TARGET_DIR_TYPE}" = "6969" ]; then + echo "Please make sure ${CHROME_SANDBOX_INST_PATH} is not on NFS!" + exit 1 +fi + +installsandbox() { + echo "(using sudo so you may be asked for your password)" + sudo -- cp "${CHROME_SANDBOX_BUILD_PATH}" \ + "${CHROME_SANDBOX_INST_PATH}" && + sudo -- chown root:root "${CHROME_SANDBOX_INST_PATH}" && + sudo -- chmod 4755 "${CHROME_SANDBOX_INST_PATH}" + return $? +} + +if [ ! -d "${CHROME_OUT_DIR}" ]; then + echo -n "${CHROME_OUT_DIR} does not exist. Use \"BUILDTYPE=Release ${0}\" " + echo "If you are building in Release mode" + exit 1 +fi + +if [ ! -f "${CHROME_SANDBOX_BUILD_PATH}" ]; then + echo "Could not find ${CHROME_SANDBOX_BUILD_PATH}" + echo -n "BUILDTYPE is $BUILDTYPE, use \"BUILDTYPE= ${0}\" to override " + echo "after you build the chrome_sandbox target" + exit 1 +fi + +if readelf -d "${CHROME_SANDBOX_BUILD_PATH}" | \ + grep "(RPATH)" > /dev/null 2>&1; then + echo "Build requires is_component_build=false in ${CHROME_OUT_DIR}/args.gn." + exit 1 +fi + +if [ ! -f "${CHROME_SANDBOX_INST_PATH}" ]; then + echo -n "Could not find ${CHROME_SANDBOX_INST_PATH}, " + echo "installing it now." + installsandbox +fi + +if [ ! -f "${CHROME_SANDBOX_INST_PATH}" ]; then + echo "Failed to install ${CHROME_SANDBOX_INST_PATH}" + exit 1 +fi + +CURRENT_API=$("${CHROME_SANDBOX_BUILD_PATH}" --get-api) +INSTALLED_API=$("${CHROME_SANDBOX_INST_PATH}" --get-api) + +if [ "${CURRENT_API}" != "${INSTALLED_API}" ]; then + echo "Your installed setuid sandbox is too old, installing it now." + if ! installsandbox; then + echo "Failed to install ${CHROME_SANDBOX_INST_PATH}" + exit 1 + fi +else + echo "Your setuid sandbox is up to date" + if [ "${CHROME_DEVEL_SANDBOX}" != "${CHROME_SANDBOX_INST_PATH}" ]; then + echo -n "Make sure you have \"export " + echo -n "CHROME_DEVEL_SANDBOX=${CHROME_SANDBOX_INST_PATH}\" " + echo "somewhere in your .bashrc" + echo "This variable is currently: ${CHROME_DEVEL_SANDBOX:-empty}" + fi +fi diff --git a/third_party/libwebrtc/build/util/BUILD.gn b/third_party/libwebrtc/build/util/BUILD.gn new file mode 100644 index 0000000000..2745449ea7 --- /dev/null +++ b/third_party/libwebrtc/build/util/BUILD.gn @@ -0,0 +1,38 @@ +# 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/util/lastchange.gni") + +action("chromium_git_revision") { + script = "version.py" + + template_file = "chromium_git_revision.h.in" + inputs = [ + lastchange_file, + template_file, + ] + + output_file = "$target_gen_dir/chromium_git_revision.h" + outputs = [ output_file ] + + args = [ + # LASTCHANGE contains "-". The user agent only wants the + # "" bit, so chop off everything after it. + "-e", + "LASTCHANGE=LASTCHANGE[:LASTCHANGE.find('-')]", + "-f", + rebase_path(lastchange_file, root_build_dir), + rebase_path(template_file, root_build_dir), + rebase_path(output_file, root_build_dir), + ] +} + +group("test_results") { + data = [ + "//.vpython", + "//.vpython3", + "//build/util/lib/__init__.py", + "//build/util/lib/results/", + ] +} diff --git a/third_party/libwebrtc/build/util/LASTCHANGE.committime b/third_party/libwebrtc/build/util/LASTCHANGE.committime new file mode 100644 index 0000000000..c5633f84f5 --- /dev/null +++ b/third_party/libwebrtc/build/util/LASTCHANGE.committime @@ -0,0 +1 @@ +1597352396 \ No newline at end of file diff --git a/third_party/libwebrtc/build/util/LASTCHANGE.dummy b/third_party/libwebrtc/build/util/LASTCHANGE.dummy new file mode 100644 index 0000000000..21bb3c33c7 --- /dev/null +++ b/third_party/libwebrtc/build/util/LASTCHANGE.dummy @@ -0,0 +1 @@ +LASTCHANGE=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 diff --git a/third_party/libwebrtc/build/util/PRESUBMIT.py b/third_party/libwebrtc/build/util/PRESUBMIT.py new file mode 100644 index 0000000000..575c806d3c --- /dev/null +++ b/third_party/libwebrtc/build/util/PRESUBMIT.py @@ -0,0 +1,64 @@ +# Copyright 2019 The Chromium 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 re +"""Presubmit for build/util""" + + +USE_PYTHON3 = True + + +def _GetFilesToSkip(input_api): + files_to_skip = [] + affected_files = input_api.change.AffectedFiles() + version_script_change = next( + (f for f in affected_files + if re.search('\\/version\\.py$|\\/version_test\\.py$', f.LocalPath())), + None) + + if version_script_change is None: + files_to_skip.append('version_test\\.py$') + + android_chrome_version_script_change = next( + (f for f in affected_files if re.search( + '\\/android_chrome_version\\.py$|' + '\\/android_chrome_version_test\\.py$', f.LocalPath())), None) + + if android_chrome_version_script_change is None: + files_to_skip.append('android_chrome_version_test\\.py$') + + return files_to_skip + + +def _GetPythonUnitTests(input_api, output_api): + # No need to test if files are unchanged + files_to_skip = _GetFilesToSkip(input_api) + + return input_api.canned_checks.GetUnitTestsRecursively( + input_api, + output_api, + input_api.PresubmitLocalPath(), + files_to_check=['.*_test\\.py$'], + files_to_skip=files_to_skip, + run_on_python2=False, + run_on_python3=True, + skip_shebang_check=True) + + +def CommonChecks(input_api, output_api): + """Presubmit checks run on both upload and commit. + """ + checks = [] + checks.extend(_GetPythonUnitTests(input_api, output_api)) + return input_api.RunTests(checks, False) + + +def CheckChangeOnUpload(input_api, output_api): + """Presubmit checks on CL upload.""" + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + """Presubmit checks on commit.""" + return CommonChecks(input_api, output_api) diff --git a/third_party/libwebrtc/build/util/android_chrome_version.py b/third_party/libwebrtc/build/util/android_chrome_version.py new file mode 100644 index 0000000000..5ec9e48d15 --- /dev/null +++ b/third_party/libwebrtc/build/util/android_chrome_version.py @@ -0,0 +1,214 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Different build variants of Chrome for Android have different version codes. + +For targets that have the same package name (e.g. Chrome, Chrome Modern, +Monochrome, Trichrome), Play Store considers them the same app and will push the +supported app with the highest version code to devices. Note that Play Store +does not support hosting two different apps with same version code and package +name. + +Each version code generated by this script will be used by one or more APKs. + +Webview channels must have unique version codes for a couple reasons: +a) Play Store does not support having the same version code for different + versions of a package. Without unique codes, promoting a beta apk to stable + would require first removing the beta version. +b) Firebase project support (used by official builders) requires unique + [version code + package name]. + We cannot add new webview package names for new channels because webview + packages are allowlisted by Android as webview providers. + +WEBVIEW_STABLE, WEBVIEW_BETA, WEBVIEW_DEV are all used for standalone webview, +whereas the others are used for various chrome APKs. + +Note that a package digit of '3' for Webview is reserved for Trichrome Webview. +The same versionCode is used for both Trichrome Chrome and Trichrome Webview. + +Version code values are constructed like this: + + {full BUILD number}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}. + +For example: + + Build 3721, patch 0, ChromeModern (1), on ARM64 (5): 372100015 + Build 3721, patch 9, Monochrome (2), on ARM (0): 372100920 + +""" + +# Package name version bits. +_PACKAGE_NAMES = { + 'CHROME': 0, + 'CHROME_MODERN': 10, + 'MONOCHROME': 20, + 'TRICHROME': 30, + 'WEBVIEW_STABLE': 0, + 'WEBVIEW_BETA': 10, + 'WEBVIEW_DEV': 20, +} + +""" "Next" builds get +5 on their package version code digit. + +We choose 5 because it won't conflict with values in _PACKAGE_NAMES. +""" +_NEXT_BUILD_VERSION_CODE_DIFF = 50 + +"""List of version numbers to be created for each build configuration. +Tuple format: + + (version code name), (package name), (supported ABIs) + +Here, (supported ABIs) is referring to the combination of browser ABI and +webview library ABI present in a particular APK. For example, 64_32 implies a +64-bit browser with an extra 32-bit Webview library. See also +_ABIS_TO_BIT_MASK. +""" +_APKS = { + '32': [ + ('CHROME', 'CHROME', '32'), + ('CHROME_MODERN', 'CHROME_MODERN', '32'), + ('MONOCHROME', 'MONOCHROME', '32'), + ('TRICHROME', 'TRICHROME', '32'), + ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32'), + ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32'), + ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32'), + ], + '64': [ + ('CHROME', 'CHROME', '64'), + ('CHROME_MODERN', 'CHROME_MODERN', '64'), + ('MONOCHROME', 'MONOCHROME', '32_64'), + ('MONOCHROME_32', 'MONOCHROME', '32'), + ('MONOCHROME_32_64', 'MONOCHROME', '32_64'), + ('MONOCHROME_64_32', 'MONOCHROME', '64_32'), + ('MONOCHROME_64', 'MONOCHROME', '64'), + ('TRICHROME', 'TRICHROME', '32_64'), + ('TRICHROME_32', 'TRICHROME', '32'), + ('TRICHROME_32_64', 'TRICHROME', '32_64'), + ('TRICHROME_64_32', 'TRICHROME', '64_32'), + ('TRICHROME_64', 'TRICHROME', '64'), + ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32_64'), + ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32_64'), + ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32_64'), + ('WEBVIEW_32_STABLE', 'WEBVIEW_STABLE', '32'), + ('WEBVIEW_32_BETA', 'WEBVIEW_BETA', '32'), + ('WEBVIEW_32_DEV', 'WEBVIEW_DEV', '32'), + ('WEBVIEW_64_STABLE', 'WEBVIEW_STABLE', '64'), + ('WEBVIEW_64_BETA', 'WEBVIEW_BETA', '64'), + ('WEBVIEW_64_DEV', 'WEBVIEW_DEV', '64'), + ] +} + +# Splits input build config architecture to manufacturer and bitness. +_ARCH_TO_MFG_AND_BITNESS = { + 'arm': ('arm', '32'), + 'arm64': ('arm', '64'), + 'x86': ('intel', '32'), + 'x64': ('intel', '64'), + 'mipsel': ('mipsel', '32'), +} + +# Expose the available choices to other scripts. +ARCH_CHOICES = _ARCH_TO_MFG_AND_BITNESS.keys() +""" +The architecture preference is encoded into the version_code for devices +that support multiple architectures. (exploiting play store logic that pushes +apk with highest version code) + +Detail: +Many Android devices support multiple architectures, and can run applications +built for any of them; the Play Store considers all of the supported +architectures compatible and does not, itself, have any preference for which +is "better". The common cases here: + +- All production arm64 devices can also run arm +- All production x64 devices can also run x86 +- Pretty much all production x86/x64 devices can also run arm (via a binary + translator) + +Since the Play Store has no particular preferences, you have to encode your own +preferences into the ordering of the version codes. There's a few relevant +things here: + +- For any android app, it's theoretically preferable to ship a 64-bit version to + 64-bit devices if it exists, because the 64-bit architectures are supposed to + be "better" than their 32-bit predecessors (unfortunately this is not always + true due to the effect on memory usage, but we currently deal with this by + simply not shipping a 64-bit version *at all* on the configurations where we + want the 32-bit version to be used). +- For any android app, it's definitely preferable to ship an x86 version to x86 + devices if it exists instead of an arm version, because running things through + the binary translator is a performance hit. +- For WebView, Monochrome, and Trichrome specifically, they are a special class + of APK called "multiarch" which means that they actually need to *use* more + than one architecture at runtime (rather than simply being compatible with + more than one). The 64-bit builds of these multiarch APKs contain both 32-bit + and 64-bit code, so that Webview is available for both ABIs. If you're + multiarch you *must* have a version that supports both 32-bit and 64-bit + version on a 64-bit device, otherwise it won't work properly. So, the 64-bit + version needs to be a higher versionCode, as otherwise a 64-bit device would + prefer the 32-bit version that does not include any 64-bit code, and fail. +- The relative order of mips isn't important, but it needs to be a *distinct* + value to the other architectures because all builds need unique version codes. +""" +_ABIS_TO_BIT_MASK = { + 'arm': { + '32': 0, + '32_64': 3, + '64_32': 4, + '64': 5, + }, + 'intel': { + '32': 1, + '32_64': 6, + '64_32': 7, + '64': 8, + }, + 'mipsel': { + '32': 2, + } +} + +def GenerateVersionCodes(version_values, arch, is_next_build): + """Build dict of version codes for the specified build architecture. Eg: + + { + 'CHROME_VERSION_CODE': '378100010', + 'MONOCHROME_VERSION_CODE': '378100013', + ... + } + + versionCode values are built like this: + {full BUILD int}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}. + + MAJOR and MINOR values are not used for generating versionCode. + - MINOR is always 0. It was used for something long ago in Chrome's history + but has not been used since, and has never been nonzero on Android. + - MAJOR is cosmetic and controlled by the release managers. MAJOR and BUILD + always have reasonable sort ordering: for two version codes A and B, it's + always the case that (A.MAJOR < B.MAJOR) implies (A.BUILD < B.BUILD), and + that (A.MAJOR > B.MAJOR) implies (A.BUILD > B.BUILD). This property is just + maintained by the humans who set MAJOR. + + Thus, this method is responsible for the final two digits of versionCode. + """ + + base_version_code = int( + '%s%03d00' % (version_values['BUILD'], int(version_values['PATCH']))) + + if is_next_build: + base_version_code += _NEXT_BUILD_VERSION_CODE_DIFF + + mfg, bitness = _ARCH_TO_MFG_AND_BITNESS[arch] + + version_codes = {} + + for apk, package, abis in _APKS[bitness]: + abi_bits = _ABIS_TO_BIT_MASK[mfg][abis] + package_bits = _PACKAGE_NAMES[package] + + version_code_name = apk + '_VERSION_CODE' + version_code_val = base_version_code + abi_bits + package_bits + version_codes[version_code_name] = str(version_code_val) + + return version_codes diff --git a/third_party/libwebrtc/build/util/android_chrome_version_test.py b/third_party/libwebrtc/build/util/android_chrome_version_test.py new file mode 100644 index 0000000000..eed77488cc --- /dev/null +++ b/third_party/libwebrtc/build/util/android_chrome_version_test.py @@ -0,0 +1,308 @@ +# Copyright 2019 The Chromium 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 android_chrome_version import GenerateVersionCodes + + +class _VersionTest(unittest.TestCase): + """Unittests for the android_chrome_version module. + """ + + EXAMPLE_VERSION_VALUES = { + 'MAJOR': '74', + 'MINOR': '0', + 'BUILD': '3720', + 'PATCH': '0', + } + + def testGenerateVersionCodesAndroidChrome(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(chrome_version_code, '372000000') + + def testGenerateVersionCodesAndroidChromeModern(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + chrome_modern_version_code = output['CHROME_MODERN_VERSION_CODE'] + + self.assertEqual(chrome_modern_version_code, '372000010') + + def testGenerateVersionCodesAndroidMonochrome(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + monochrome_version_code = output['MONOCHROME_VERSION_CODE'] + + self.assertEqual(monochrome_version_code, '372000020') + + def testGenerateVersionCodesAndroidTrichrome(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + trichrome_version_code = output['TRICHROME_VERSION_CODE'] + + self.assertEqual(trichrome_version_code, '372000030') + + def testGenerateVersionCodesAndroidWebviewStable(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + webview_stable_version_code = output['WEBVIEW_STABLE_VERSION_CODE'] + + self.assertEqual(webview_stable_version_code, '372000000') + + def testGenerateVersionCodesAndroidWebviewBeta(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + webview_beta_version_code = output['WEBVIEW_BETA_VERSION_CODE'] + + self.assertEqual(webview_beta_version_code, '372000010') + + def testGenerateVersionCodesAndroidWebviewDev(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + webview_dev_version_code = output['WEBVIEW_DEV_VERSION_CODE'] + + self.assertEqual(webview_dev_version_code, '372000020') + + def testGenerateVersionCodesAndroidNextBuild(self): + """Assert it handles "next" builds correctly""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=True) + + # Get just a sample of values + chrome_version_code = output['CHROME_VERSION_CODE'] + monochrome_version_code = output['MONOCHROME_VERSION_CODE'] + webview_stable_version_code = output['WEBVIEW_STABLE_VERSION_CODE'] + webview_beta_version_code = output['WEBVIEW_BETA_VERSION_CODE'] + + self.assertEqual(chrome_version_code, '372000050') + self.assertEqual(monochrome_version_code, '372000070') + self.assertEqual(webview_stable_version_code, '372000050') + self.assertEqual(webview_beta_version_code, '372000060') + + def testGenerateVersionCodesAndroidArchArm(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docs in android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + arch_chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(arch_chrome_version_code, '372000000') + + def testGenerateVersionCodesAndroidArchX86(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='x86', is_next_build=False) + arch_chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(arch_chrome_version_code, '372000001') + + def testGenerateVersionCodesAndroidArchMips(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='mipsel', is_next_build=False) + arch_chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(arch_chrome_version_code, '372000002') + + def testGenerateVersionCodesAndroidArchArm64(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm64', is_next_build=False) + arch_chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(arch_chrome_version_code, '372000005') + + def testGenerateVersionCodesAndroidArchArm64Variants(self): + """Assert it handles 64-bit-specific additional version codes correctly. + + Some additional version codes are generated for 64-bit architectures. + See docstring on android_chrome_version.ARCH64_APK_VARIANTS for more info. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm64', is_next_build=False) + arch_monochrome_version_code = output['MONOCHROME_VERSION_CODE'] + arch_monochrome_32_version_code = output['MONOCHROME_32_VERSION_CODE'] + arch_monochrome_32_64_version_code = output['MONOCHROME_32_64_VERSION_CODE'] + arch_monochrome_64_32_version_code = output['MONOCHROME_64_32_VERSION_CODE'] + arch_monochrome_64_version_code = output['MONOCHROME_64_VERSION_CODE'] + arch_trichrome_version_code = output['TRICHROME_VERSION_CODE'] + arch_trichrome_32_version_code = output['TRICHROME_32_VERSION_CODE'] + arch_trichrome_32_64_version_code = output['TRICHROME_32_64_VERSION_CODE'] + arch_trichrome_64_32_version_code = output['TRICHROME_64_32_VERSION_CODE'] + arch_trichrome_64_version_code = output['TRICHROME_64_VERSION_CODE'] + + self.assertEqual(arch_monochrome_32_version_code, '372000020') + self.assertEqual(arch_monochrome_32_64_version_code, '372000023') + self.assertEqual(arch_monochrome_version_code, '372000023') + self.assertEqual(arch_monochrome_64_32_version_code, '372000024') + self.assertEqual(arch_monochrome_64_version_code, '372000025') + self.assertEqual(arch_trichrome_32_version_code, '372000030') + self.assertEqual(arch_trichrome_32_64_version_code, '372000033') + self.assertEqual(arch_trichrome_version_code, '372000033') + self.assertEqual(arch_trichrome_64_32_version_code, '372000034') + self.assertEqual(arch_trichrome_64_version_code, '372000035') + + def testGenerateVersionCodesAndroidArchX64(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='x64', is_next_build=False) + arch_chrome_version_code = output['CHROME_VERSION_CODE'] + + self.assertEqual(arch_chrome_version_code, '372000008') + + def testGenerateVersionCodesAndroidArchX64Variants(self): + """Assert it handles 64-bit-specific additional version codes correctly. + + Some additional version codes are generated for 64-bit architectures. + See docstring on android_chrome_version.ARCH64_APK_VARIANTS for more info. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='x64', is_next_build=False) + arch_monochrome_32_version_code = output['MONOCHROME_32_VERSION_CODE'] + arch_monochrome_32_64_version_code = output['MONOCHROME_32_64_VERSION_CODE'] + arch_monochrome_version_code = output['MONOCHROME_VERSION_CODE'] + arch_monochrome_64_32_version_code = output['MONOCHROME_64_32_VERSION_CODE'] + arch_monochrome_64_version_code = output['MONOCHROME_64_VERSION_CODE'] + arch_trichrome_32_version_code = output['TRICHROME_32_VERSION_CODE'] + arch_trichrome_32_64_version_code = output['TRICHROME_32_64_VERSION_CODE'] + arch_trichrome_version_code = output['TRICHROME_VERSION_CODE'] + arch_trichrome_64_32_version_code = output['TRICHROME_64_32_VERSION_CODE'] + arch_trichrome_64_version_code = output['TRICHROME_64_VERSION_CODE'] + + self.assertEqual(arch_monochrome_32_version_code, '372000021') + self.assertEqual(arch_monochrome_32_64_version_code, '372000026') + self.assertEqual(arch_monochrome_version_code, '372000026') + self.assertEqual(arch_monochrome_64_32_version_code, '372000027') + self.assertEqual(arch_monochrome_64_version_code, '372000028') + self.assertEqual(arch_trichrome_32_version_code, '372000031') + self.assertEqual(arch_trichrome_32_64_version_code, '372000036') + self.assertEqual(arch_trichrome_version_code, '372000036') + self.assertEqual(arch_trichrome_64_32_version_code, '372000037') + self.assertEqual(arch_trichrome_64_version_code, '372000038') + + def testGenerateVersionCodesAndroidArchOrderArm(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + + Test arm-related values. + """ + arm_output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + arm64_output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm64', is_next_build=False) + + arm_chrome_version_code = arm_output['CHROME_VERSION_CODE'] + arm64_chrome_version_code = arm64_output['CHROME_VERSION_CODE'] + + self.assertLess(arm_chrome_version_code, arm64_chrome_version_code) + + def testGenerateVersionCodesAndroidArchOrderX86(self): + """Assert it handles different architectures correctly. + + Version codes for different builds need to be distinct and maintain a + certain ordering. + See docstring on android_chrome_version._ABIS_TO_BIT_MASK for + reasoning. + + Test x86-related values. + """ + x86_output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='x86', is_next_build=False) + x64_output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='x64', is_next_build=False) + + x86_chrome_version_code = x86_output['CHROME_VERSION_CODE'] + x64_chrome_version_code = x64_output['CHROME_VERSION_CODE'] + + self.assertLess(x86_chrome_version_code, x64_chrome_version_code) + + def testGenerateVersionCodesAndroidWebviewChannelOrderBeta(self): + """Assert webview beta channel is higher than stable. + + The channel-specific version codes for standalone webview needs to follow + the order stable < beta < dev. + + This allows that if a user opts into beta track, they will always have the + beta apk, including any finch experiments targeted at beta users, even when + beta and stable channels are otherwise on the same version. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + webview_stable_version_code = output['WEBVIEW_STABLE_VERSION_CODE'] + webview_beta_version_code = output['WEBVIEW_BETA_VERSION_CODE'] + + self.assertGreater(webview_beta_version_code, webview_stable_version_code) + + def testGenerateVersionCodesAndroidWebviewChannelOrderDev(self): + """Assert webview dev channel is higher than beta. + + The channel-specific version codes for standalone webview needs to follow + the order stable < beta < dev. + + This allows that if a user opts into dev track, they will always have the + dev apk, including any finch experiments targeted at dev users, even when + dev and beta channels are otherwise on the same version. + """ + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + webview_beta_version_code = output['WEBVIEW_BETA_VERSION_CODE'] + webview_dev_version_code = output['WEBVIEW_DEV_VERSION_CODE'] + + self.assertGreater(webview_dev_version_code, webview_beta_version_code) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/util/branding.gni b/third_party/libwebrtc/build/util/branding.gni new file mode 100644 index 0000000000..aa758e6a0e --- /dev/null +++ b/third_party/libwebrtc/build/util/branding.gni @@ -0,0 +1,45 @@ +# 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. + +# This exposes the Chrome branding as GN variables for use in build files. +# +# PREFER NOT TO USE THESE. The GYP build uses this kind of thing extensively. +# However, it is far better to write an action to generate a file at +# build-time with the information you need. This allows better dependency +# checking and GN will run faster. +# +# These values should only be used if you REALLY need to depend on them at +# build-time, for example, in the computation of output file names. + +import("//build/config/chrome_build.gni") + +_branding_dictionary_template = + "full_name = \"@PRODUCT_FULLNAME@\" " + + "short_name = \"@PRODUCT_SHORTNAME@\" " + + "bundle_id = \"@MAC_BUNDLE_ID@\" " + + "creator_code = \"@MAC_CREATOR_CODE@\" " + + "installer_full_name = \"@PRODUCT_INSTALLER_FULLNAME@\" " + + "installer_short_name = \"@PRODUCT_INSTALLER_SHORTNAME@\" " + + "team_id = \"@MAC_TEAM_ID@\" " + +_result = exec_script("version.py", + [ + "-f", + rebase_path(branding_file_path, root_build_dir), + "-t", + _branding_dictionary_template, + ], + "scope", + [ branding_file_path ]) + +chrome_product_full_name = _result.full_name +chrome_product_short_name = _result.short_name +chrome_product_installer_full_name = _result.installer_full_name +chrome_product_installer_short_name = _result.installer_short_name + +if (is_mac) { + chrome_mac_bundle_id = _result.bundle_id + chrome_mac_creator_code = _result.creator_code + chrome_mac_team_id = _result.team_id +} diff --git a/third_party/libwebrtc/build/util/chromium_git_revision.h.in b/third_party/libwebrtc/build/util/chromium_git_revision.h.in new file mode 100644 index 0000000000..41c115979c --- /dev/null +++ b/third_party/libwebrtc/build/util/chromium_git_revision.h.in @@ -0,0 +1,8 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source is governed by a BSD-style license that can be +// found in the LICENSE file. + +// chromium_git_revision.h is generated from chromium_git_revision.h.in. Edit +// the source! + +#define CHROMIUM_GIT_REVISION "@@LASTCHANGE@" diff --git a/third_party/libwebrtc/build/util/generate_wrapper.gni b/third_party/libwebrtc/build/util/generate_wrapper.gni new file mode 100644 index 0000000000..02e8bca1ff --- /dev/null +++ b/third_party/libwebrtc/build/util/generate_wrapper.gni @@ -0,0 +1,105 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Wraps a target and any of its arguments to an executable script. +# +# Many executable targets have build-time-constant arguments. This +# template allows those to be wrapped into a single, user- or bot-friendly +# script at build time. +# +# Paths to be wrapped should be relative to root_build_dir and should be +# wrapped in "@WrappedPath(...)"; see Example below. +# +# Variables: +# generator_script: Path to the script to use to perform the wrapping. +# Defaults to //build/util/generate_wrapper.py. Generally should only +# be set by other templates. +# wrapper_script: Output path. +# executable: Path to the executable to wrap. Can be a script or a +# build product. Paths can be relative to the containing gn file +# or source-absolute. +# executable_args: List of arguments to write into the wrapper. +# use_vpython3: If true, invoke the generated wrapper with vpython3 instead +# of vpython. +# +# Example wrapping a checked-in script: +# generate_wrapper("sample_wrapper") { +# executable = "//for/bar/sample.py" +# wrapper_script = "$root_build_dir/bin/run_sample" +# +# _sample_argument_path = "//sample/$target_cpu/lib/sample_lib.so" +# _rebased_sample_argument_path = rebase_path( +# _sample_argument_path, +# root_build_dir) +# executable_args = [ +# "--sample-lib", "@WrappedPath(${_rebased_sample_argument_path})", +# ] +# } +# +# Example wrapping a build product: +# generate_wrapper("sample_wrapper") { +# executable = "$root_build_dir/sample_build_product" +# wrapper_script = "$root_build_dir/bin/run_sample_build_product" +# } +template("generate_wrapper") { + _generator_script = "//build/util/generate_wrapper.py" + if (defined(invoker.generator_script)) { + _generator_script = invoker.generator_script + } + _executable_to_wrap = invoker.executable + _wrapper_script = invoker.wrapper_script + if (is_win) { + _wrapper_script += ".bat" + } + if (defined(invoker.executable_args)) { + _wrapped_arguments = invoker.executable_args + } else { + _wrapped_arguments = [] + } + + action(target_name) { + forward_variables_from(invoker, + TESTONLY_AND_VISIBILITY + [ + "data", + "data_deps", + "deps", + "sources", + ]) + script = _generator_script + if (!defined(data)) { + data = [] + } + data += [ _wrapper_script ] + outputs = [ _wrapper_script ] + + _rebased_executable_to_wrap = + rebase_path(_executable_to_wrap, root_build_dir) + _rebased_wrapper_script = rebase_path(_wrapper_script, root_build_dir) + if (is_win) { + _script_language = "batch" + } else { + _script_language = "bash" + } + args = [ + "--executable", + "@WrappedPath(${_rebased_executable_to_wrap})", + "--wrapper-script", + _rebased_wrapper_script, + "--output-directory", + rebase_path(root_build_dir, root_build_dir), + "--script-language", + _script_language, + ] + + if (defined(invoker.use_vpython3) && invoker.use_vpython3) { + args += [ "--use-vpython3" ] + } + args += [ "--" ] + args += _wrapped_arguments + + if (defined(invoker.write_runtime_deps)) { + write_runtime_deps = invoker.write_runtime_deps + } + } +} diff --git a/third_party/libwebrtc/build/util/generate_wrapper.py b/third_party/libwebrtc/build/util/generate_wrapper.py new file mode 100755 index 0000000000..07167e8655 --- /dev/null +++ b/third_party/libwebrtc/build/util/generate_wrapper.py @@ -0,0 +1,217 @@ +#!/usr/bin/env vpython +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Wraps an executable and any provided arguments into an executable script.""" + +import argparse +import os +import sys +import textwrap + + +# The bash template passes the python script into vpython via stdin. +# The interpreter doesn't know about the script, so we have bash +# inject the script location. +BASH_TEMPLATE = textwrap.dedent("""\ + #!/usr/bin/env {vpython} + _SCRIPT_LOCATION = __file__ + {script} + """) + + +# The batch template reruns the batch script with vpython, with the -x +# flag instructing the interpreter to ignore the first line. The interpreter +# knows about the (batch) script in this case, so it can get the file location +# directly. +BATCH_TEMPLATE = textwrap.dedent("""\ + @SETLOCAL ENABLEDELAYEDEXPANSION \ + & {vpython}.bat -x "%~f0" %* \ + & EXIT /B !ERRORLEVEL! + _SCRIPT_LOCATION = __file__ + {script} + """) + + +SCRIPT_TEMPLATES = { + 'bash': BASH_TEMPLATE, + 'batch': BATCH_TEMPLATE, +} + + +PY_TEMPLATE = textwrap.dedent("""\ + import os + import re + import subprocess + import sys + + _WRAPPED_PATH_RE = re.compile(r'@WrappedPath\(([^)]+)\)') + _PATH_TO_OUTPUT_DIR = '{path_to_output_dir}' + _SCRIPT_DIR = os.path.dirname(os.path.realpath(_SCRIPT_LOCATION)) + + + def ExpandWrappedPath(arg): + m = _WRAPPED_PATH_RE.match(arg) + if m: + relpath = os.path.join( + os.path.relpath(_SCRIPT_DIR), _PATH_TO_OUTPUT_DIR, m.group(1)) + npath = os.path.normpath(relpath) + if os.path.sep not in npath: + # If the original path points to something in the current directory, + # returning the normalized version of it can be a problem. + # normpath() strips off the './' part of the path + # ('./foo' becomes 'foo'), which can be a problem if the result + # is passed to something like os.execvp(); in that case + # osexecvp() will search $PATH for the executable, rather than + # just execing the arg directly, and if '.' isn't in $PATH, this + # results in an error. + # + # So, we need to explicitly return './foo' (or '.\\foo' on windows) + # instead of 'foo'. + # + # Hopefully there are no cases where this causes a problem; if + # there are, we will either need to change the interface to + # WrappedPath() somehow to distinguish between the two, or + # somehow ensure that the wrapped executable doesn't hit cases + # like this. + return '.' + os.path.sep + npath + return npath + return arg + + + def ExpandWrappedPaths(args): + for i, arg in enumerate(args): + args[i] = ExpandWrappedPath(arg) + return args + + + def FindIsolatedOutdir(raw_args): + outdir = None + i = 0 + remaining_args = [] + while i < len(raw_args): + if raw_args[i] == '--isolated-outdir' and i < len(raw_args)-1: + outdir = raw_args[i+1] + i += 2 + elif raw_args[i].startswith('--isolated-outdir='): + outdir = raw_args[i][len('--isolated-outdir='):] + i += 1 + else: + remaining_args.append(raw_args[i]) + i += 1 + if not outdir and 'ISOLATED_OUTDIR' in os.environ: + outdir = os.environ['ISOLATED_OUTDIR'] + return outdir, remaining_args + + + def FilterIsolatedOutdirBasedArgs(outdir, args): + rargs = [] + i = 0 + while i < len(args): + if 'ISOLATED_OUTDIR' in args[i]: + if outdir: + # Rewrite the arg. + rargs.append(args[i].replace('${{ISOLATED_OUTDIR}}', + outdir).replace( + '$ISOLATED_OUTDIR', outdir)) + i += 1 + else: + # Simply drop the arg. + i += 1 + elif (not outdir and + args[i].startswith('-') and + '=' not in args[i] and + i < len(args) - 1 and + 'ISOLATED_OUTDIR' in args[i+1]): + # Parsing this case is ambiguous; if we're given + # `--foo $ISOLATED_OUTDIR` we can't tell if $ISOLATED_OUTDIR + # is meant to be the value of foo, or if foo takes no argument + # and $ISOLATED_OUTDIR is the first positional arg. + # + # We assume the former will be much more common, and so we + # need to drop --foo and $ISOLATED_OUTDIR. + i += 2 + else: + rargs.append(args[i]) + i += 1 + return rargs + + + def main(raw_args): + executable_path = ExpandWrappedPath('{executable_path}') + outdir, remaining_args = FindIsolatedOutdir(raw_args) + args = {executable_args} + args = FilterIsolatedOutdirBasedArgs(outdir, args) + executable_args = ExpandWrappedPaths(args) + cmd = [executable_path] + args + remaining_args + if executable_path.endswith('.py'): + cmd = [sys.executable] + cmd + return subprocess.call(cmd) + + + if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) + """) + + +def Wrap(args): + """Writes a wrapped script according to the provided arguments. + + Arguments: + args: an argparse.Namespace object containing command-line arguments + as parsed by a parser returned by CreateArgumentParser. + """ + path_to_output_dir = os.path.relpath( + args.output_directory, + os.path.dirname(args.wrapper_script)) + + with open(args.wrapper_script, 'w') as wrapper_script: + py_contents = PY_TEMPLATE.format( + path_to_output_dir=path_to_output_dir, + executable_path=str(args.executable), + executable_args=str(args.executable_args)) + template = SCRIPT_TEMPLATES[args.script_language] + wrapper_script.write( + template.format(script=py_contents, vpython=args.vpython)) + os.chmod(args.wrapper_script, 0o750) + + return 0 + + +def CreateArgumentParser(): + """Creates an argparse.ArgumentParser instance.""" + parser = argparse.ArgumentParser() + parser.add_argument( + '--executable', + help='Executable to wrap.') + parser.add_argument( + '--wrapper-script', + help='Path to which the wrapper script will be written.') + parser.add_argument( + '--output-directory', + help='Path to the output directory.') + parser.add_argument( + '--script-language', + choices=SCRIPT_TEMPLATES.keys(), + help='Language in which the wrapper script will be written.') + parser.add_argument('--use-vpython3', + dest='vpython', + action='store_const', + const='vpython3', + default='vpython', + help='Use vpython3 instead of vpython') + parser.add_argument( + 'executable_args', nargs='*', + help='Arguments to wrap into the executable.') + return parser + + +def main(raw_args): + parser = CreateArgumentParser() + args = parser.parse_args(raw_args) + return Wrap(args) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/util/java_action.gni b/third_party/libwebrtc/build/util/java_action.gni new file mode 100644 index 0000000000..0615b38782 --- /dev/null +++ b/third_party/libwebrtc/build/util/java_action.gni @@ -0,0 +1,99 @@ +# 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. + +jarrunner = "//build/util/java_action.py" + +# Declare a target that runs a java command a single time. +# +# This target type allows you to run a java command a single time to produce +# one or more output files. If you want to run a java command for each of a +# set of input files, see "java_action_foreach". +# +# See "gn help action" for more information on how to use this target. This +# template is based on the "action" and supports the same variables. +template("java_action") { + assert(defined(invoker.script), + "Need script in $target_name listing the .jar file to run.") + assert(defined(invoker.outputs), + "Need outputs in $target_name listing the generated outputs.") + + jarscript = invoker.script + action(target_name) { + script = jarrunner + + inputs = [ jarscript ] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + + args = [ + "-jar", + rebase_path(jarscript, root_build_dir), + ] + if (defined(invoker.args)) { + args += invoker.args + } + + forward_variables_from(invoker, + [ + "console", + "data", + "data_deps", + "depfile", + "deps", + "outputs", + "sources", + "testonly", + "visibility", + ]) + } +} + +# Declare a target that runs a java command over a set of files. +# +# This target type allows you to run a java command once-per-file over a set of +# sources. If you want to run a java command once that takes many files as +# input, see "java_action". +# +# See "gn help action_foreach" for more information on how to use this target. +# This template is based on the "action_foreach" supports the same variables. +template("java_action_foreach") { + assert(defined(invoker.script), + "Need script in $target_name listing the .jar file to run.") + assert(defined(invoker.outputs), + "Need outputs in $target_name listing the generated outputs.") + assert(defined(invoker.sources), + "Need sources in $target_name listing the target inputs.") + + jarscript = invoker.script + action_foreach(target_name) { + script = jarrunner + + inputs = [ jarscript ] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + + args = [ + "-jar", + rebase_path(jarscript, root_build_dir), + ] + if (defined(invoker.args)) { + args += invoker.args + } + + forward_variables_from(invoker, + [ + "console", + "data", + "data_deps", + "depfile", + "deps", + "outputs", + "sources", + "testonly", + "visibility", + ]) + } +} diff --git a/third_party/libwebrtc/build/util/java_action.py b/third_party/libwebrtc/build/util/java_action.py new file mode 100755 index 0000000000..ed9bb601de --- /dev/null +++ b/third_party/libwebrtc/build/util/java_action.py @@ -0,0 +1,82 @@ +#!/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. + +"""Wrapper script to run java command as action with gn.""" + +import os +import subprocess +import sys + +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + + +def IsExecutable(path): + """Returns whether file at |path| exists and is executable. + + Args: + path: absolute or relative path to test. + + Returns: + True if the file at |path| exists, False otherwise. + """ + return os.path.isfile(path) and os.access(path, os.X_OK) + + +def FindCommand(command): + """Looks up for |command| in PATH. + + Args: + command: name of the command to lookup, if command is a relative or + absolute path (i.e. contains some path separator) then only that + path will be tested. + + Returns: + Full path to command or None if the command was not found. + + On Windows, this respects the PATHEXT environment variable when the + command name does not have an extension. + """ + fpath, _ = os.path.split(command) + if fpath: + if IsExecutable(command): + return command + + if sys.platform == 'win32': + # On Windows, if the command does not have an extension, cmd.exe will + # try all extensions from PATHEXT when resolving the full path. + command, ext = os.path.splitext(command) + if not ext: + exts = os.environ['PATHEXT'].split(os.path.pathsep) + else: + exts = [ext] + else: + exts = [''] + + for path in os.environ['PATH'].split(os.path.pathsep): + for ext in exts: + path = os.path.join(path, command) + ext + if IsExecutable(path): + return path + + return None + + +def main(): + java_path = FindCommand('java') + if not java_path: + sys.stderr.write('java: command not found\n') + sys.exit(EXIT_FAILURE) + + args = sys.argv[1:] + if len(args) < 2 or args[0] != '-jar': + sys.stderr.write('usage: %s -jar JARPATH [java_args]...\n' % sys.argv[0]) + sys.exit(EXIT_FAILURE) + + return subprocess.check_call([java_path] + args) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/util/lastchange.gni b/third_party/libwebrtc/build/util/lastchange.gni new file mode 100644 index 0000000000..a13295900d --- /dev/null +++ b/third_party/libwebrtc/build/util/lastchange.gni @@ -0,0 +1,16 @@ +# Copyright 2018 The Chromium 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 used to inject fixed dummy commit for commit independent +# reproducible binaries. + +declare_args() { + use_dummy_lastchange = false +} + +if (use_dummy_lastchange) { + lastchange_file = "//build/util/LASTCHANGE.dummy" +} else { + lastchange_file = "//build/util/LASTCHANGE" +} diff --git a/third_party/libwebrtc/build/util/lastchange.py b/third_party/libwebrtc/build/util/lastchange.py new file mode 100755 index 0000000000..02a36642b1 --- /dev/null +++ b/third_party/libwebrtc/build/util/lastchange.py @@ -0,0 +1,332 @@ +#!/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. + +""" +lastchange.py -- Chromium revision fetching utility. +""" +from __future__ import print_function + +import argparse +import collections +import datetime +import logging +import os +import subprocess +import sys + +VersionInfo = collections.namedtuple("VersionInfo", + ("revision_id", "revision", "timestamp")) + +class GitError(Exception): + pass + +# This function exists for compatibility with logic outside this +# repository that uses this file as a library. +# TODO(eliribble) remove this function after it has been ported into +# the repositories that depend on it +def RunGitCommand(directory, command): + """ + Launches git subcommand. + + Errors are swallowed. + + Returns: + A process object or None. + """ + command = ['git'] + command + # Force shell usage under cygwin. This is a workaround for + # mysterious loss of cwd while invoking cygwin's git. + # We can't just pass shell=True to Popen, as under win32 this will + # cause CMD to be used, while we explicitly want a cygwin shell. + if sys.platform == 'cygwin': + command = ['sh', '-c', ' '.join(command)] + try: + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=directory, + shell=(sys.platform=='win32')) + return proc + except OSError as e: + logging.error('Command %r failed: %s' % (' '.join(command), e)) + return None + + +def _RunGitCommand(directory, command): + """Launches git subcommand. + + Returns: + The stripped stdout of the git command. + Raises: + GitError on failure, including a nonzero return code. + """ + command = ['git'] + command + # Force shell usage under cygwin. This is a workaround for + # mysterious loss of cwd while invoking cygwin's git. + # We can't just pass shell=True to Popen, as under win32 this will + # cause CMD to be used, while we explicitly want a cygwin shell. + if sys.platform == 'cygwin': + command = ['sh', '-c', ' '.join(command)] + try: + logging.info("Executing '%s' in %s", ' '.join(command), directory) + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=directory, + shell=(sys.platform=='win32')) + stdout, stderr = tuple(x.decode(encoding='utf_8') + for x in proc.communicate()) + stdout = stdout.strip() + logging.debug("returncode: %d", proc.returncode) + logging.debug("stdout: %s", stdout) + logging.debug("stderr: %s", stderr) + if proc.returncode != 0 or not stdout: + raise GitError(( + "Git command '{}' in {} failed: " + "rc={}, stdout='{}' stderr='{}'").format( + " ".join(command), directory, proc.returncode, stdout, stderr)) + return stdout + except OSError as e: + raise GitError("Git command 'git {}' in {} failed: {}".format( + " ".join(command), directory, e)) + + +def GetMergeBase(directory, ref): + """ + Return the merge-base of HEAD and ref. + + Args: + directory: The directory containing the .git directory. + ref: The ref to use to find the merge base. + Returns: + The git commit SHA of the merge-base as a string. + """ + logging.debug("Calculating merge base between HEAD and %s in %s", + ref, directory) + command = ['merge-base', 'HEAD', ref] + return _RunGitCommand(directory, command) + + +def FetchGitRevision(directory, commit_filter, start_commit="HEAD"): + """ + Fetch the Git hash (and Cr-Commit-Position if any) for a given directory. + + Args: + directory: The directory containing the .git directory. + commit_filter: A filter to supply to grep to filter commits + start_commit: A commit identifier. The result of this function + will be limited to only consider commits before the provided + commit. + Returns: + A VersionInfo object. On error all values will be 0. + """ + hash_ = '' + + git_args = ['log', '-1', '--format=%H %ct'] + if commit_filter is not None: + git_args.append('--grep=' + commit_filter) + + git_args.append(start_commit) + + output = _RunGitCommand(directory, git_args) + hash_, commit_timestamp = output.split() + if not hash_: + return VersionInfo('0', '0', 0) + + revision = hash_ + output = _RunGitCommand(directory, ['cat-file', 'commit', hash_]) + for line in reversed(output.splitlines()): + if line.startswith('Cr-Commit-Position:'): + pos = line.rsplit()[-1].strip() + logging.debug("Found Cr-Commit-Position '%s'", pos) + revision = "{}-{}".format(hash_, pos) + break + return VersionInfo(hash_, revision, int(commit_timestamp)) + + +def GetHeaderGuard(path): + """ + Returns the header #define guard for the given file path. + This treats everything after the last instance of "src/" as being a + relevant part of the guard. If there is no "src/", then the entire path + is used. + """ + src_index = path.rfind('src/') + if src_index != -1: + guard = path[src_index + 4:] + else: + guard = path + guard = guard.upper() + return guard.replace('/', '_').replace('.', '_').replace('\\', '_') + '_' + + +def GetHeaderContents(path, define, version): + """ + Returns what the contents of the header file should be that indicate the given + revision. + """ + header_guard = GetHeaderGuard(path) + + header_contents = """/* Generated by lastchange.py, do not edit.*/ + +#ifndef %(header_guard)s +#define %(header_guard)s + +#define %(define)s "%(version)s" + +#endif // %(header_guard)s +""" + header_contents = header_contents % { 'header_guard': header_guard, + 'define': define, + 'version': version } + return header_contents + + +def GetGitTopDirectory(source_dir): + """Get the top git directory - the directory that contains the .git directory. + + Args: + source_dir: The directory to search. + Returns: + The output of "git rev-parse --show-toplevel" as a string + """ + return _RunGitCommand(source_dir, ['rev-parse', '--show-toplevel']) + + +def WriteIfChanged(file_name, contents): + """ + Writes the specified contents to the specified file_name + iff the contents are different than the current contents. + Returns if new data was written. + """ + try: + old_contents = open(file_name, 'r').read() + except EnvironmentError: + pass + else: + if contents == old_contents: + return False + os.unlink(file_name) + open(file_name, 'w').write(contents) + return True + + +def main(argv=None): + if argv is None: + argv = sys.argv + + parser = argparse.ArgumentParser(usage="lastchange.py [options]") + parser.add_argument("-m", "--version-macro", + help=("Name of C #define when using --header. Defaults to " + "LAST_CHANGE.")) + parser.add_argument("-o", "--output", metavar="FILE", + help=("Write last change to FILE. " + "Can be combined with --header to write both files.")) + parser.add_argument("--header", metavar="FILE", + help=("Write last change to FILE as a C/C++ header. " + "Can be combined with --output to write both files.")) + parser.add_argument("--merge-base-ref", + default=None, + help=("Only consider changes since the merge " + "base between HEAD and the provided ref")) + parser.add_argument("--revision-id-only", action='store_true', + help=("Output the revision as a VCS revision ID only (in " + "Git, a 40-character commit hash, excluding the " + "Cr-Commit-Position).")) + parser.add_argument("--revision-id-prefix", + metavar="PREFIX", + help=("Adds a string prefix to the VCS revision ID.")) + parser.add_argument("--print-only", action="store_true", + help=("Just print the revision string. Overrides any " + "file-output-related options.")) + parser.add_argument("-s", "--source-dir", metavar="DIR", + help="Use repository in the given directory.") + parser.add_argument("--filter", metavar="REGEX", + help=("Only use log entries where the commit message " + "matches the supplied filter regex. Defaults to " + "'^Change-Id:' to suppress local commits."), + default='^Change-Id:') + + args, extras = parser.parse_known_args(argv[1:]) + + logging.basicConfig(level=logging.WARNING) + + out_file = args.output + header = args.header + commit_filter=args.filter + + while len(extras) and out_file is None: + if out_file is None: + out_file = extras.pop(0) + if extras: + sys.stderr.write('Unexpected arguments: %r\n\n' % extras) + parser.print_help() + sys.exit(2) + + source_dir = args.source_dir or os.path.dirname(os.path.abspath(__file__)) + try: + git_top_dir = GetGitTopDirectory(source_dir) + except GitError as e: + logging.error("Failed to get git top directory from '%s': %s", + source_dir, e) + return 2 + + if args.merge_base_ref: + try: + merge_base_sha = GetMergeBase(git_top_dir, args.merge_base_ref) + except GitError as e: + logging.error("You requested a --merge-base-ref value of '%s' but no " + "merge base could be found between it and HEAD. Git " + "reports: %s", args.merge_base_ref, e) + return 3 + else: + merge_base_sha = 'HEAD' + + try: + version_info = FetchGitRevision(git_top_dir, commit_filter, merge_base_sha) + except GitError as e: + logging.error("Failed to get version info: %s", e) + logging.info(("Falling back to a version of 0.0.0 to allow script to " + "finish. This is normal if you are bootstrapping a new environment " + "or do not have a git repository for any other reason. If not, this " + "could represent a serious error.")) + version_info = VersionInfo('0', '0', 0) + + revision_string = version_info.revision + if args.revision_id_only: + revision_string = version_info.revision_id + + if args.revision_id_prefix: + revision_string = args.revision_id_prefix + revision_string + + if args.print_only: + print(revision_string) + else: + lastchange_year = datetime.datetime.utcfromtimestamp( + version_info.timestamp).year + contents_lines = [ + "LASTCHANGE=%s" % revision_string, + "LASTCHANGE_YEAR=%s" % lastchange_year, + ] + contents = '\n'.join(contents_lines) + '\n' + if not out_file and not args.header: + sys.stdout.write(contents) + else: + if out_file: + committime_file = out_file + '.committime' + out_changed = WriteIfChanged(out_file, contents) + if out_changed or not os.path.exists(committime_file): + with open(committime_file, 'w') as timefile: + timefile.write(str(version_info.timestamp)) + if header: + WriteIfChanged(header, + GetHeaderContents(header, args.version_macro, + revision_string)) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/util/lib/__init__.py b/third_party/libwebrtc/build/util/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py b/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py new file mode 100644 index 0000000000..7f280e5f5e --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/PRESUBMIT.py @@ -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. + + +USE_PYTHON3 = True + + +def _RunTests(input_api, output_api): + return (input_api.canned_checks.RunUnitTestsInDirectory( + input_api, output_api, '.', files_to_check=[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/third_party/libwebrtc/build/util/lib/common/__init__.py b/third_party/libwebrtc/build/util/lib/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py b/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py new file mode 100644 index 0000000000..bec81558d1 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/chrome_test_server_spawner.py @@ -0,0 +1,503 @@ +# Copyright 2017 The Chromium 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 json +import logging +import os +import select +import struct +import subprocess +import sys +import threading +import time + +from six.moves import BaseHTTPServer, urllib + + +SERVER_TYPES = { + 'http': '', + 'ftp': '-f', + 'ws': '--websocket', +} + + +_DIR_SOURCE_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, + os.pardir)) + + +_logger = logging.getLogger(__name__) + + +# Path that are needed to import necessary modules when launching a testserver. +os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + (':%s:%s:%s' + % (os.path.join(_DIR_SOURCE_ROOT, 'third_party'), + os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'tlslite'), + os.path.join(_DIR_SOURCE_ROOT, 'net', 'tools', 'testserver'))) + + +# The timeout (in seconds) of starting up the Python test server. +_TEST_SERVER_STARTUP_TIMEOUT = 10 + + +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) + return SERVER_TYPES[server_type] + + +class PortForwarder: + def Map(self, port_pairs): + pass + + def GetDevicePortForHostPort(self, host_port): + """Returns the device port that corresponds to a given host port.""" + return host_port + + def WaitHostPortAvailable(self, port): + """Returns True if |port| is available.""" + return True + + def WaitPortNotAvailable(self, port): + """Returns True if |port| is not available.""" + return True + + def WaitDevicePortReady(self, port): + """Returns whether the provided port is used.""" + return True + + def Unmap(self, device_port): + """Unmaps specified port""" + pass + + +class TestServerThread(threading.Thread): + """A thread to run the test server in a separate process.""" + + def __init__(self, ready_event, arguments, port_forwarder): + """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_event = threading.Event() + self.ready_event = ready_event + self.ready_event.clear() + self.arguments = arguments + self.port_forwarder = port_forwarder + self.test_server_process = None + self.is_ready = False + self.host_port = self.arguments['port'] + self.host_ocsp_port = 0 + assert isinstance(self.host_port, int) + # The forwarder device port now is dynamically allocated. + self.forwarder_device_port = 0 + self.forwarder_ocsp_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: + _logger.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: + _logger.error('Failed to get length of server data.') + return False + server_data_json = os.read(self.pipe_in, data_length) + if not server_data_json: + _logger.error('Failed to get server data.') + return False + _logger.info('Got port json data: %s', server_data_json) + + parsed_server_data = None + try: + parsed_server_data = json.loads(server_data_json) + except ValueError: + pass + + if not isinstance(parsed_server_data, dict): + _logger.error('Failed to parse server_data: %s' % server_data_json) + return False + + if not isinstance(parsed_server_data.get('port'), int): + _logger.error('Failed to get port information from the server data.') + return False + + self.host_port = parsed_server_data['port'] + self.host_ocsp_port = parsed_server_data.get('ocsp_port', 0) + + return self.port_forwarder.WaitPortNotAvailable(self.host_port) + + 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. Note stdin, stdout, and stderr (0-2) are left + # alone and redirected with subprocess.Popen. It is important to leave those + # fds filled, or the test server will accidentally open other fds at those + # numbers. + for fd in xrange(3, 1024): + if fd != self.pipe_out: + try: + os.close(fd) + except: + pass + + def run(self): + _logger.info('Start running the thread!') + self.wait_event.clear() + self._GenerateCommandLineArguments() + # TODO(crbug.com/941669): When this script is ported to Python 3, replace + # 'vpython3' below with sys.executable. The call to + # vpython3 -vpython-tool install below can also be removed. + command = [ + 'vpython3', + os.path.join(_DIR_SOURCE_ROOT, 'net', 'tools', 'testserver', + 'testserver.py') + ] + self.command_line + _logger.info('Running: %s', command) + + # Disable PYTHONUNBUFFERED because it has a bad interaction with the + # testserver. Remove once this interaction is fixed. + unbuf = os.environ.pop('PYTHONUNBUFFERED', None) + + # Pass _DIR_SOURCE_ROOT as the child's working directory so that relative + # paths in the arguments are resolved correctly. devnull can be replaced + # with subprocess.DEVNULL in Python 3. + with open(os.devnull, 'r+b') as devnull: + # _WaitToStartAndGetPortFromTestServer has a short timeout. If the + # vpython3 cache is not initialized, launching the test server can take + # some time. Prewarm the cache before running the server. + subprocess.check_call( + [ + 'vpython3', '-vpython-spec', + os.path.join(_DIR_SOURCE_ROOT, '.vpython3'), '-vpython-tool', + 'install' + ], + preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess, + stdin=devnull, + stdout=None, + stderr=None, + cwd=_DIR_SOURCE_ROOT) + + self.process = subprocess.Popen( + command, + preexec_fn=self._CloseUnnecessaryFDsForTestServerProcess, + stdin=devnull, + # Preserve stdout and stderr from the test server. + stdout=None, + stderr=None, + cwd=_DIR_SOURCE_ROOT) + if unbuf: + os.environ['PYTHONUNBUFFERED'] = unbuf + if self.process: + if self.pipe_out: + self.is_ready = self._WaitToStartAndGetPortFromTestServer() + else: + self.is_ready = self.port_forwarder.WaitPortNotAvailable(self.host_port) + + if self.is_ready: + port_map = [(0, self.host_port)] + if self.host_ocsp_port: + port_map.extend([(0, self.host_ocsp_port)]) + self.port_forwarder.Map(port_map) + + self.forwarder_device_port = \ + self.port_forwarder.GetDevicePortForHostPort(self.host_port) + if self.host_ocsp_port: + self.forwarder_ocsp_device_port = \ + self.port_forwarder.GetDevicePortForHostPort(self.host_ocsp_port) + + # Check whether the forwarder is ready on the device. + self.is_ready = self.forwarder_device_port and \ + self.port_forwarder.WaitDevicePortReady(self.forwarder_device_port) + + # Wake up the request handler thread. + self.ready_event.set() + # Keep thread running until Stop() gets called. + self.stop_event.wait() + if self.process.poll() is None: + self.process.kill() + # Wait for process to actually terminate. + # (crbug.com/946475) + self.process.wait() + + self.port_forwarder.Unmap(self.forwarder_device_port) + 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 + _logger.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_event.set() + 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.""" + _logger.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.') + _logger.info(content_length) + test_server_argument_json = self.rfile.read(content_length) + _logger.info(test_server_argument_json) + + if len(self.server.test_servers) >= self.server.max_instances: + self._SendResponse(400, 'Invalid request', {}, + 'Too many test servers running') + return + + ready_event = threading.Event() + new_server = TestServerThread(ready_event, + json.loads(test_server_argument_json), + self.server.port_forwarder) + new_server.setDaemon(True) + new_server.start() + ready_event.wait() + if new_server.is_ready: + response = {'port': new_server.forwarder_device_port, + 'message': 'started'}; + if new_server.forwarder_ocsp_device_port: + response['ocsp_port'] = new_server.forwarder_ocsp_device_port + self._SendResponse(200, 'OK', {}, json.dumps(response)) + _logger.info('Test server is running on port %d forwarded to %d.' % + (new_server.forwarder_device_port, new_server.host_port)) + port = new_server.forwarder_device_port + assert port not in self.server.test_servers + self.server.test_servers[port] = new_server + else: + new_server.Stop() + self._SendResponse(500, 'Test Server Error.', {}, '') + _logger.info('Encounter problem during starting a test server.') + + def _KillTestServer(self, params): + """Stops the test server instance.""" + try: + port = int(params['port'][0]) + except ValueError: + port = None + if port == None or port <= 0: + self._SendResponse(400, 'Invalid request.', {}, 'port must be specified') + return + + if port not in self.server.test_servers: + self._SendResponse(400, 'Invalid request.', {}, + "testserver isn't running on port %d" % port) + return + + server = self.server.test_servers.pop(port) + + _logger.info('Handling request to kill a test server on port: %d.', port) + server.Stop() + + # Make sure the status of test server is correct before sending response. + if self.server.port_forwarder.WaitHostPortAvailable(port): + self._SendResponse(200, 'OK', {}, 'killed') + _logger.info('Test server on port %d is killed', port) + else: + # We expect the port to be free, but nothing stops the system from + # binding something else to that port, so don't throw error. + # (crbug.com/946475) + self._SendResponse(200, 'OK', {}, '') + _logger.warn('Port %s is not free after killing test server.' % port) + + def log_message(self, format, *args): + # Suppress the default HTTP logging behavior if the logging level is higher + # than INFO. + if _logger.getEffectiveLevel() <= logging.INFO: + pass + + def do_POST(self): + parsed_path = urllib.parse.urlparse(self.path) + action = parsed_path.path + _logger.info('Action for POST method is: %s.', action) + if action == '/start': + self._StartTestServer() + else: + self._SendResponse(400, 'Unknown request.', {}, '') + _logger.info('Encounter unknown request: %s.', action) + + def do_GET(self): + parsed_path = urllib.parse.urlparse(self.path) + action = parsed_path.path + params = urllib.parse.parse_qs(parsed_path.query, keep_blank_values=1) + _logger.info('Action for GET method is: %s.', action) + for param in params: + _logger.info('%s=%s', param, params[param][0]) + if action == '/kill': + self._KillTestServer(params) + 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') + _logger.info('Handled ping request and sent response.') + else: + self._SendResponse(400, 'Unknown request', {}, '') + _logger.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, port_forwarder, max_instances): + self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port), + SpawningServerRequestHandler) + self.server_port = self.server.server_port + _logger.info('Started test server spawner on port: %d.', self.server_port) + + self.server.port_forwarder = port_forwarder + self.server.test_servers = {} + self.server.max_instances = max_instances + + def _Listen(self): + _logger.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_servers: + _logger.warning('Not all test servers were stopped.') + for port in self.server.test_servers: + _logger.warning('Stopping test server on port %d' % port) + self.server.test_servers[port].Stop() + self.server.test_servers = {} diff --git a/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py b/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.py new file mode 100644 index 0000000000..67b550a46c --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/perf_result_data_type.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. + +DEFAULT = 'default' +UNIMPORTANT = 'unimportant' +HISTOGRAM = 'histogram' +UNIMPORTANT_HISTOGRAM = 'unimportant-histogram' +INFORMATIONAL = 'informational' + +ALL_TYPES = [DEFAULT, UNIMPORTANT, HISTOGRAM, UNIMPORTANT_HISTOGRAM, + INFORMATIONAL] + + +def IsValidType(datatype): + return datatype in ALL_TYPES + + +def IsHistogram(datatype): + return (datatype == HISTOGRAM or datatype == UNIMPORTANT_HISTOGRAM) diff --git a/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py b/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py new file mode 100644 index 0000000000..153886dce5 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/perf_tests_results_helper.py @@ -0,0 +1,202 @@ +# 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 __future__ import print_function + +import re +import sys + +import json +import logging +import math + +import perf_result_data_type + + +# Mapping from result type to test output +RESULT_TYPES = {perf_result_data_type.UNIMPORTANT: 'RESULT ', + perf_result_data_type.DEFAULT: '*RESULT ', + perf_result_data_type.INFORMATIONAL: '', + perf_result_data_type.UNIMPORTANT_HISTOGRAM: 'HISTOGRAM ', + perf_result_data_type.HISTOGRAM: '*HISTOGRAM '} + + +def _EscapePerfResult(s): + """Escapes |s| for use in a perf result.""" + return re.sub('[\:|=/#&,]', '_', s) + + +def FlattenList(values): + """Returns a simple list without sub-lists.""" + ret = [] + for entry in values: + if isinstance(entry, list): + ret.extend(FlattenList(entry)) + else: + ret.append(entry) + return ret + + +def GeomMeanAndStdDevFromHistogram(histogram_json): + histogram = json.loads(histogram_json) + # Handle empty histograms gracefully. + if not 'buckets' in histogram: + return 0.0, 0.0 + count = 0 + sum_of_logs = 0 + for bucket in histogram['buckets']: + if 'high' in bucket: + bucket['mean'] = (bucket['low'] + bucket['high']) / 2.0 + else: + bucket['mean'] = bucket['low'] + if bucket['mean'] > 0: + sum_of_logs += math.log(bucket['mean']) * bucket['count'] + count += bucket['count'] + + if count == 0: + return 0.0, 0.0 + + sum_of_squares = 0 + geom_mean = math.exp(sum_of_logs / count) + for bucket in histogram['buckets']: + if bucket['mean'] > 0: + sum_of_squares += (bucket['mean'] - geom_mean) ** 2 * bucket['count'] + return geom_mean, math.sqrt(sum_of_squares / count) + + +def _ValueToString(v): + # Special case for floats so we don't print using scientific notation. + if isinstance(v, float): + return '%f' % v + else: + return str(v) + + +def _MeanAndStdDevFromList(values): + avg = None + sd = None + if len(values) > 1: + try: + value = '[%s]' % ','.join([_ValueToString(v) for v in values]) + avg = sum([float(v) for v in values]) / len(values) + sqdiffs = [(float(v) - avg) ** 2 for v in values] + variance = sum(sqdiffs) / (len(values) - 1) + sd = math.sqrt(variance) + except ValueError: + value = ', '.join(values) + else: + value = values[0] + return value, avg, sd + + +def PrintPages(page_list): + """Prints list of pages to stdout in the format required by perf tests.""" + print('Pages: [%s]' % ','.join([_EscapePerfResult(p) for p in page_list])) + + +def PrintPerfResult(measurement, trace, values, units, + result_type=perf_result_data_type.DEFAULT, + print_to_stdout=True): + """Prints numerical data to stdout in the format required by perf tests. + + The string args may be empty but they must not contain any colons (:) or + equals signs (=). + This is parsed by the buildbot using: + http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/process_log_utils.py + + Args: + measurement: A description of the quantity being measured, e.g. "vm_peak". + On the dashboard, this maps to a particular graph. Mandatory. + trace: A description of the particular data point, e.g. "reference". + On the dashboard, this maps to a particular "line" in the graph. + Mandatory. + values: A list of numeric measured values. An N-dimensional list will be + flattened and treated as a simple list. + units: A description of the units of measure, e.g. "bytes". + result_type: Accepts values of perf_result_data_type.ALL_TYPES. + print_to_stdout: If True, prints the output in stdout instead of returning + the output to caller. + + Returns: + String of the formated perf result. + """ + assert perf_result_data_type.IsValidType(result_type), \ + 'result type: %s is invalid' % result_type + + trace_name = _EscapePerfResult(trace) + + if (result_type == perf_result_data_type.UNIMPORTANT or + result_type == perf_result_data_type.DEFAULT or + result_type == perf_result_data_type.INFORMATIONAL): + assert isinstance(values, list) + assert '/' not in measurement + flattened_values = FlattenList(values) + assert len(flattened_values) + value, avg, sd = _MeanAndStdDevFromList(flattened_values) + output = '%s%s: %s%s%s %s' % ( + RESULT_TYPES[result_type], + _EscapePerfResult(measurement), + trace_name, + # Do not show equal sign if the trace is empty. Usually it happens when + # measurement is enough clear to describe the result. + '= ' if trace_name else '', + value, + units) + else: + assert perf_result_data_type.IsHistogram(result_type) + assert isinstance(values, list) + # The histograms can only be printed individually, there's no computation + # across different histograms. + assert len(values) == 1 + value = values[0] + output = '%s%s: %s= %s %s' % ( + RESULT_TYPES[result_type], + _EscapePerfResult(measurement), + trace_name, + value, + units) + avg, sd = GeomMeanAndStdDevFromHistogram(value) + + if avg: + output += '\nAvg %s: %f%s' % (measurement, avg, units) + if sd: + output += '\nSd %s: %f%s' % (measurement, sd, units) + if print_to_stdout: + print(output) + sys.stdout.flush() + return output + + +def ReportPerfResult(chart_data, graph_title, trace_title, value, units, + improvement_direction='down', important=True): + """Outputs test results in correct format. + + If chart_data is None, it outputs data in old format. If chart_data is a + dictionary, formats in chartjson format. If any other format defaults to + old format. + + Args: + chart_data: A dictionary corresponding to perf results in the chartjson + format. + graph_title: A string containing the name of the chart to add the result + to. + trace_title: A string containing the name of the trace within the chart + to add the result to. + value: The value of the result being reported. + units: The units of the value being reported. + improvement_direction: A string denoting whether higher or lower is + better for the result. Either 'up' or 'down'. + important: A boolean denoting whether the result is important or not. + """ + if chart_data and isinstance(chart_data, dict): + chart_data['charts'].setdefault(graph_title, {}) + chart_data['charts'][graph_title][trace_title] = { + 'type': 'scalar', + 'value': value, + 'units': units, + 'improvement_direction': improvement_direction, + 'important': important + } + else: + PrintPerfResult(graph_title, trace_title, [value], units) diff --git a/third_party/libwebrtc/build/util/lib/common/unittest_util.py b/third_party/libwebrtc/build/util/lib/common/unittest_util.py new file mode 100644 index 0000000000..d6ff7f6c22 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/unittest_util.py @@ -0,0 +1,155 @@ +# 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. + +"""Utilities for dealing with the python unittest module.""" + +import fnmatch +import re +import sys +import unittest + + +class _TextTestResult(unittest._TextTestResult): + """A test result class that can print formatted text results to a stream. + + Results printed in conformance with gtest output format, like: + [ RUN ] autofill.AutofillTest.testAutofillInvalid: "test desc." + [ OK ] autofill.AutofillTest.testAutofillInvalid + [ RUN ] autofill.AutofillTest.testFillProfile: "test desc." + [ OK ] autofill.AutofillTest.testFillProfile + [ RUN ] autofill.AutofillTest.testFillProfileCrazyCharacters: "Test." + [ OK ] autofill.AutofillTest.testFillProfileCrazyCharacters + """ + def __init__(self, stream, descriptions, verbosity): + unittest._TextTestResult.__init__(self, stream, descriptions, verbosity) + self._fails = set() + + def _GetTestURI(self, test): + return '%s.%s.%s' % (test.__class__.__module__, + test.__class__.__name__, + test._testMethodName) + + def getDescription(self, test): + return '%s: "%s"' % (self._GetTestURI(test), test.shortDescription()) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + self.stream.writeln('[ RUN ] %s' % self.getDescription(test)) + + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + self.stream.writeln('[ OK ] %s' % self._GetTestURI(test)) + + def addError(self, test, err): + unittest.TestResult.addError(self, test, err) + self.stream.writeln('[ ERROR ] %s' % self._GetTestURI(test)) + self._fails.add(self._GetTestURI(test)) + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test)) + self._fails.add(self._GetTestURI(test)) + + def getRetestFilter(self): + return ':'.join(self._fails) + + +class TextTestRunner(unittest.TextTestRunner): + """Test Runner for displaying test results in textual format. + + Results are displayed in conformance with google test output. + """ + + def __init__(self, verbosity=1): + unittest.TextTestRunner.__init__(self, stream=sys.stderr, + verbosity=verbosity) + + def _makeResult(self): + return _TextTestResult(self.stream, self.descriptions, self.verbosity) + + +def GetTestsFromSuite(suite): + """Returns all the tests from a given test suite.""" + tests = [] + for x in suite: + if isinstance(x, unittest.TestSuite): + tests += GetTestsFromSuite(x) + else: + tests += [x] + return tests + + +def GetTestNamesFromSuite(suite): + """Returns a list of every test name in the given suite.""" + return map(lambda x: GetTestName(x), GetTestsFromSuite(suite)) + + +def GetTestName(test): + """Gets the test name of the given unittest test.""" + return '.'.join([test.__class__.__module__, + test.__class__.__name__, + test._testMethodName]) + + +def FilterTestSuite(suite, gtest_filter): + """Returns a new filtered tests suite based on the given gtest filter. + + See https://github.com/google/googletest/blob/master/docs/advanced.md + for gtest_filter specification. + """ + return unittest.TestSuite(FilterTests(GetTestsFromSuite(suite), gtest_filter)) + + +def FilterTests(all_tests, gtest_filter): + """Filter a list of tests based on the given gtest filter. + + Args: + all_tests: List of tests (unittest.TestSuite) + gtest_filter: Filter to apply. + + Returns: + Filtered subset of the given list of tests. + """ + test_names = [GetTestName(test) for test in all_tests] + filtered_names = FilterTestNames(test_names, gtest_filter) + return [test for test in all_tests if GetTestName(test) in filtered_names] + + +def FilterTestNames(all_tests, gtest_filter): + """Filter a list of test names based on the given gtest filter. + + See https://github.com/google/googletest/blob/master/docs/advanced.md + for gtest_filter specification. + + Args: + all_tests: List of test names. + gtest_filter: Filter to apply. + + Returns: + Filtered subset of the given list of test names. + """ + pattern_groups = gtest_filter.split('-') + positive_patterns = ['*'] + if pattern_groups[0]: + positive_patterns = pattern_groups[0].split(':') + negative_patterns = [] + if len(pattern_groups) > 1: + negative_patterns = pattern_groups[1].split(':') + + neg_pats = None + if negative_patterns: + neg_pats = re.compile('|'.join(fnmatch.translate(p) for p in + negative_patterns)) + + tests = [] + test_set = set() + for pattern in positive_patterns: + pattern_tests = [ + test for test in all_tests + if (fnmatch.fnmatch(test, pattern) + and not (neg_pats and neg_pats.match(test)) + and test not in test_set)] + tests.extend(pattern_tests) + test_set.update(pattern_tests) + return tests diff --git a/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py b/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py new file mode 100755 index 0000000000..1514c9b6d4 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/unittest_util_test.py @@ -0,0 +1,65 @@ +#!/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. + +# pylint: disable=protected-access + +import logging +import sys +import unittest +import unittest_util + + +class FilterTestNamesTest(unittest.TestCase): + + possible_list = ["Foo.One", + "Foo.Two", + "Foo.Three", + "Bar.One", + "Bar.Two", + "Bar.Three", + "Quux.One", + "Quux.Two", + "Quux.Three"] + + def testMatchAll(self): + x = unittest_util.FilterTestNames(self.possible_list, "*") + self.assertEquals(x, self.possible_list) + + def testMatchPartial(self): + x = unittest_util.FilterTestNames(self.possible_list, "Foo.*") + self.assertEquals(x, ["Foo.One", "Foo.Two", "Foo.Three"]) + + def testMatchFull(self): + x = unittest_util.FilterTestNames(self.possible_list, "Foo.Two") + self.assertEquals(x, ["Foo.Two"]) + + def testMatchTwo(self): + x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:Foo.*") + self.assertEquals(x, ["Bar.One", + "Bar.Two", + "Bar.Three", + "Foo.One", + "Foo.Two", + "Foo.Three"]) + + def testMatchWithNegative(self): + x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:Foo.*-*.Three") + self.assertEquals(x, ["Bar.One", + "Bar.Two", + "Foo.One", + "Foo.Two"]) + + def testMatchOverlapping(self): + x = unittest_util.FilterTestNames(self.possible_list, "Bar.*:*.Two") + self.assertEquals(x, ["Bar.One", + "Bar.Two", + "Bar.Three", + "Foo.Two", + "Quux.Two"]) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.DEBUG) + unittest.main(verbosity=2) diff --git a/third_party/libwebrtc/build/util/lib/common/util.py b/third_party/libwebrtc/build/util/lib/common/util.py new file mode 100644 index 0000000000..a415b1f534 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/common/util.py @@ -0,0 +1,151 @@ +# 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. + +"""Generic utilities for all python scripts.""" + +import atexit +import httplib +import os +import signal +import stat +import subprocess +import sys +import tempfile +import urlparse + + +def GetPlatformName(): + """Return a string to be used in paths for the platform.""" + if IsWindows(): + return 'win' + if IsMac(): + return 'mac' + if IsLinux(): + return 'linux' + raise NotImplementedError('Unknown platform "%s".' % sys.platform) + + +def IsWindows(): + return sys.platform == 'cygwin' or sys.platform.startswith('win') + + +def IsLinux(): + return sys.platform.startswith('linux') + + +def IsMac(): + return sys.platform.startswith('darwin') + + +def _DeleteDir(path): + """Deletes a directory recursively, which must exist.""" + # Don't use shutil.rmtree because it can't delete read-only files on Win. + for root, dirs, files in os.walk(path, topdown=False): + for name in files: + filename = os.path.join(root, name) + os.chmod(filename, stat.S_IWRITE) + os.remove(filename) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(path) + + +def Delete(path): + """Deletes the given file or directory (recursively), which must exist.""" + if os.path.isdir(path): + _DeleteDir(path) + else: + os.remove(path) + + +def MaybeDelete(path): + """Deletes the given file or directory (recurisvely), if it exists.""" + if os.path.exists(path): + Delete(path) + + +def MakeTempDir(parent_dir=None): + """Creates a temporary directory and returns an absolute path to it. + + The temporary directory is automatically deleted when the python interpreter + exits normally. + + Args: + parent_dir: the directory to create the temp dir in. If None, the system + temp dir is used. + + Returns: + The absolute path to the temporary directory. + """ + path = tempfile.mkdtemp(dir=parent_dir) + atexit.register(MaybeDelete, path) + return path + + +def Unzip(zip_path, output_dir): + """Unzips the given zip file using a system installed unzip tool. + + Args: + zip_path: zip file to unzip. + output_dir: directory to unzip the contents of the zip file. The directory + must exist. + + Raises: + RuntimeError if the unzip operation fails. + """ + if IsWindows(): + unzip_cmd = ['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y'] + else: + unzip_cmd = ['unzip', '-o'] + unzip_cmd += [zip_path] + if RunCommand(unzip_cmd, output_dir) != 0: + raise RuntimeError('Unable to unzip %s to %s' % (zip_path, output_dir)) + + +def Kill(pid): + """Terminate the given pid.""" + if IsWindows(): + subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) + else: + os.kill(pid, signal.SIGTERM) + + +def RunCommand(cmd, cwd=None): + """Runs the given command and returns the exit code. + + Args: + cmd: list of command arguments. + cwd: working directory to execute the command, or None if the current + working directory should be used. + + Returns: + The exit code of the command. + """ + process = subprocess.Popen(cmd, cwd=cwd) + process.wait() + return process.returncode + + +def DoesUrlExist(url): + """Determines whether a resource exists at the given URL. + + Args: + url: URL to be verified. + + Returns: + True if url exists, otherwise False. + """ + parsed = urlparse.urlparse(url) + try: + conn = httplib.HTTPConnection(parsed.netloc) + conn.request('HEAD', parsed.path) + response = conn.getresponse() + except (socket.gaierror, socket.error): + return False + finally: + conn.close() + # Follow both permanent (301) and temporary (302) redirects. + if response.status == 302 or response.status == 301: + return DoesUrlExist(response.getheader('location')) + return response.status == 200 diff --git a/third_party/libwebrtc/build/util/lib/results/__init__.py b/third_party/libwebrtc/build/util/lib/results/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/libwebrtc/build/util/lib/results/result_sink.py b/third_party/libwebrtc/build/util/lib/results/result_sink.py new file mode 100644 index 0000000000..7d5ce6f980 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_sink.py @@ -0,0 +1,180 @@ +# Copyright 2020 The Chromium 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 base64 +import json +import os + +import six + +import requests # pylint: disable=import-error +from lib.results import result_types + +# Maps result_types to the luci test-result.proto. +# https://godoc.org/go.chromium.org/luci/resultdb/proto/v1#TestStatus +RESULT_MAP = { + result_types.UNKNOWN: 'ABORT', + result_types.PASS: 'PASS', + result_types.FAIL: 'FAIL', + result_types.CRASH: 'CRASH', + result_types.TIMEOUT: 'ABORT', + result_types.SKIP: 'SKIP', + result_types.NOTRUN: 'SKIP', +} + + +def TryInitClient(): + """Tries to initialize a result_sink_client object. + + Assumes that rdb stream is already running. + + Returns: + A ResultSinkClient for the result_sink server else returns None. + """ + try: + with open(os.environ['LUCI_CONTEXT']) as f: + sink = json.load(f)['result_sink'] + return ResultSinkClient(sink) + except KeyError: + return None + + +class ResultSinkClient(object): + """A class to store the sink's post configurations and make post requests. + + This assumes that the rdb stream has been called already and that the + server is listening. + """ + + def __init__(self, context): + base_url = 'http://%s/prpc/luci.resultsink.v1.Sink' % context['address'] + self.test_results_url = base_url + '/ReportTestResults' + self.report_artifacts_url = base_url + '/ReportInvocationLevelArtifacts' + + self.headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'ResultSink %s' % context['auth_token'], + } + + def Post(self, + test_id, + status, + duration, + test_log, + test_file, + artifacts=None, + failure_reason=None): + """Uploads the test result to the ResultSink server. + + This assumes that the rdb stream has been called already and that + server is ready listening. + + Args: + test_id: A string representing the test's name. + status: A string representing if the test passed, failed, etc... + duration: An int representing time in ms. + test_log: A string representing the test's output. + test_file: A string representing the file location of the test. + artifacts: An optional dict of artifacts to attach to the test. + failure_reason: An optional string with the reason why the test failed. + Should be None if the test did not fail. + + Returns: + N/A + """ + assert status in RESULT_MAP + expected = status in (result_types.PASS, result_types.SKIP) + result_db_status = RESULT_MAP[status] + + tr = { + 'expected': + expected, + 'status': + result_db_status, + 'tags': [ + { + 'key': 'test_name', + 'value': test_id, + }, + { + # Status before getting mapped to result_db statuses. + 'key': 'raw_status', + 'value': status, + } + ], + 'testId': + test_id, + 'testMetadata': { + 'name': test_id, + } + } + + artifacts = artifacts or {} + if test_log: + # Upload the original log without any modifications. + b64_log = six.ensure_str(base64.b64encode(six.ensure_binary(test_log))) + artifacts.update({'Test Log': {'contents': b64_log}}) + tr['summaryHtml'] = '' + if artifacts: + tr['artifacts'] = artifacts + if failure_reason: + tr['failureReason'] = { + 'primaryErrorMessage': _TruncateToUTF8Bytes(failure_reason, 1024) + } + + if duration is not None: + # Duration must be formatted to avoid scientific notation in case + # number is too small or too large. Result_db takes seconds, not ms. + # Need to use float() otherwise it does substitution first then divides. + tr['duration'] = '%.9fs' % float(duration / 1000.0) + + if test_file and str(test_file).startswith('//'): + tr['testMetadata']['location'] = { + 'file_name': test_file, + 'repo': 'https://chromium.googlesource.com/chromium/src', + } + + res = requests.post(url=self.test_results_url, + headers=self.headers, + data=json.dumps({'testResults': [tr]})) + res.raise_for_status() + + def ReportInvocationLevelArtifacts(self, artifacts): + """Uploads invocation-level artifacts to the ResultSink server. + + This is for artifacts that don't apply to a single test but to the test + invocation as a whole (eg: system logs). + + Args: + artifacts: A dict of artifacts to attach to the invocation. + """ + req = {'artifacts': artifacts} + res = requests.post(url=self.report_artifacts_url, + headers=self.headers, + data=json.dumps(req)) + res.raise_for_status() + + +def _TruncateToUTF8Bytes(s, length): + """ Truncates a string to a given number of bytes when encoded as UTF-8. + + Ensures the given string does not take more than length bytes when encoded + as UTF-8. Adds trailing ellipsis (...) if truncation occurred. A truncated + string may end up encoding to a length slightly shorter than length because + only whole Unicode codepoints are dropped. + + Args: + s: The string to truncate. + length: the length (in bytes) to truncate to. + """ + encoded = s.encode('utf-8') + if len(encoded) > length: + # Truncate, leaving space for trailing ellipsis (...). + encoded = encoded[:length - 3] + # Truncating the string encoded as UTF-8 may have left the final codepoint + # only partially present. Pass 'ignore' to acknowledge and ensure this is + # dropped. + return encoded.decode('utf-8', 'ignore') + "..." + return s diff --git a/third_party/libwebrtc/build/util/lib/results/result_sink_test.py b/third_party/libwebrtc/build/util/lib/results/result_sink_test.py new file mode 100755 index 0000000000..3486ad90d1 --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_sink_test.py @@ -0,0 +1,102 @@ +#!/usr/bin/env vpython3 +# Copyright 2021 The Chromium 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 +import unittest + +# The following non-std imports are fetched via vpython. See the list at +# //.vpython3 +import mock # pylint: disable=import-error +import six + +_BUILD_UTIL_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) +if _BUILD_UTIL_PATH not in sys.path: + sys.path.insert(0, _BUILD_UTIL_PATH) + +from lib.results import result_sink +from lib.results import result_types + + +class InitClientTest(unittest.TestCase): + @mock.patch.dict(os.environ, {}, clear=True) + def testEmptyClient(self): + # No LUCI_CONTEXT env var should prevent a client from being created. + client = result_sink.TryInitClient() + self.assertIsNone(client) + + @mock.patch.dict(os.environ, {'LUCI_CONTEXT': 'some-file.json'}) + def testBasicClient(self): + luci_context_json = { + 'result_sink': { + 'address': 'some-ip-address', + 'auth_token': 'some-auth-token', + }, + } + if six.PY2: + open_builtin_path = '__builtin__.open' + else: + open_builtin_path = 'builtins.open' + with mock.patch(open_builtin_path, + mock.mock_open(read_data=json.dumps(luci_context_json))): + client = result_sink.TryInitClient() + self.assertEqual( + client.test_results_url, + 'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults') + self.assertEqual(client.headers['Authorization'], + 'ResultSink some-auth-token') + + +class ClientTest(unittest.TestCase): + def setUp(self): + context = { + 'address': 'some-ip-address', + 'auth_token': 'some-auth-token', + } + self.client = result_sink.ResultSinkClient(context) + + @mock.patch('requests.post') + def testPostPassingTest(self, mock_post): + self.client.Post('some-test', result_types.PASS, 0, 'some-test-log', None) + self.assertEqual( + mock_post.call_args[1]['url'], + 'http://some-ip-address/prpc/luci.resultsink.v1.Sink/ReportTestResults') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual(data['testResults'][0]['testId'], 'some-test') + self.assertEqual(data['testResults'][0]['status'], 'PASS') + + @mock.patch('requests.post') + def testPostFailingTest(self, mock_post): + self.client.Post('some-test', + result_types.FAIL, + 0, + 'some-test-log', + None, + failure_reason='omg test failure') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual(data['testResults'][0]['status'], 'FAIL') + self.assertEqual(data['testResults'][0]['testMetadata']['name'], + 'some-test') + self.assertEqual( + data['testResults'][0]['failureReason']['primaryErrorMessage'], + 'omg test failure') + + @mock.patch('requests.post') + def testPostWithTestFile(self, mock_post): + self.client.Post('some-test', result_types.PASS, 0, 'some-test-log', + '//some/test.cc') + data = json.loads(mock_post.call_args[1]['data']) + self.assertEqual( + data['testResults'][0]['testMetadata']['location']['file_name'], + '//some/test.cc') + self.assertEqual(data['testResults'][0]['testMetadata']['name'], + 'some-test') + self.assertIsNotNone(data['testResults'][0]['summaryHtml']) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/util/lib/results/result_types.py b/third_party/libwebrtc/build/util/lib/results/result_types.py new file mode 100644 index 0000000000..48ba88cdbe --- /dev/null +++ b/third_party/libwebrtc/build/util/lib/results/result_types.py @@ -0,0 +1,25 @@ +# Copyright 2021 The Chromium 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.""" + +# The test passed. +PASS = 'SUCCESS' + +# The test was intentionally skipped. +SKIP = 'SKIPPED' + +# The test failed. +FAIL = 'FAILURE' + +# The test caused the containing process to crash. +CRASH = 'CRASH' + +# The test timed out. +TIMEOUT = 'TIMEOUT' + +# The test ran, but we couldn't determine what happened. +UNKNOWN = 'UNKNOWN' + +# The test did not run. +NOTRUN = 'NOTRUN' diff --git a/third_party/libwebrtc/build/util/process_version.gni b/third_party/libwebrtc/build/util/process_version.gni new file mode 100644 index 0000000000..591e0a4a9b --- /dev/null +++ b/third_party/libwebrtc/build/util/process_version.gni @@ -0,0 +1,128 @@ +# 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/util/lastchange.gni") + +# Runs the version processing script over the given template file to produce +# an output file. This is used for generating various forms of files that +# incorporate the product name and version. +# +# Unlike GYP, this will actually compile the resulting file, so you don't need +# to add it separately to the sources, just depend on the target. +# +# In GYP this is a rule that runs once per ".ver" file. In GN this just +# processes one file per invocation of the template so you may have to have +# multiple targets. +# +# Parameters: +# sources (optional): +# List of file names to read. When converting a GYP target, this should +# list the 'source' (see above) as well as any extra_variable_files. +# The files will be passed to version.py in the order specified here. +# +# output: +# File name of file to write. In GYP this is unspecified and it will +# make up a file name for you based on the input name, and tack on +# "_version.rc" to the end. But in GN you need to specify the full name. +# +# template_file (optional): +# Template file to use (not a list). Most Windows users that want to use +# this to process a .rc template should use process_version_rc_template(), +# defined in //chrome/process_version_rc_template.gni, instead. +# +# extra_args (optional): +# Extra arguments to pass to version.py. Any "-f " args should +# use sources instead. +# +# process_only (optional, defaults to false) +# Set to generate only one action that processes the version file and +# doesn't attempt to link the result into a source set. This is for if +# you are processing the version as data only. +# +# visibility (optional) +# +# Example: +# process_version("myversion") { +# sources = [ +# "//chrome/VERSION" +# "myfile.h.in" +# ] +# output = "$target_gen_dir/myfile.h" +# extra_args = [ "-e", "FOO=42" ] +# } +template("process_version") { + assert(defined(invoker.output), "Output must be defined for $target_name") + + process_only = defined(invoker.process_only) && invoker.process_only + + if (process_only) { + action_name = target_name + } else { + action_name = target_name + "_action" + source_set_name = target_name + } + + action(action_name) { + script = "//build/util/version.py" + + inputs = [ lastchange_file ] + if (defined(invoker.inputs)) { + inputs += invoker.inputs + } + if (defined(invoker.template_file)) { + inputs += [ invoker.template_file ] + } + + outputs = [ invoker.output ] + + args = [] + + if (is_official_build) { + args += [ "--official" ] + } + + if (defined(invoker.sources)) { + inputs += invoker.sources + foreach(i, invoker.sources) { + args += [ + "-f", + rebase_path(i, root_build_dir), + ] + } + } + + if (defined(invoker.extra_args)) { + args += invoker.extra_args + } + args += [ + "-o", + rebase_path(invoker.output, root_build_dir), + ] + if (defined(invoker.template_file)) { + args += [ rebase_path(invoker.template_file, root_build_dir) ] + } + + forward_variables_from(invoker, [ "deps" ]) + + if (process_only) { + # When processing only, visibility gets applied to this target. + forward_variables_from(invoker, [ "visibility" ]) + } else { + # When linking the result, only the source set can depend on the action. + visibility = [ ":$source_set_name" ] + } + } + + if (!process_only) { + source_set(source_set_name) { + forward_variables_from(invoker, + [ + "visibility", + "deps", + ]) + sources = get_target_outputs(":$action_name") + public_deps = [ ":$action_name" ] + } + } +} diff --git a/third_party/libwebrtc/build/util/version.gni b/third_party/libwebrtc/build/util/version.gni new file mode 100644 index 0000000000..998ce077d2 --- /dev/null +++ b/third_party/libwebrtc/build/util/version.gni @@ -0,0 +1,155 @@ +# 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 exposes the Chrome version as GN variables for use in build files. +# This also generates the various version codes used for builds of chrome for +# android. +# +# PREFER NOT TO USE THESE. The GYP build uses this kind of thing extensively. +# However, it is far better to write an action (or use the process_version +# wrapper in build/util/version.gni) to generate a file at build-time with the +# information you need. This allows better dependency checking and GN will +# run faster. +# +# These values should only be used if you REALLY need to depend on them at +# build-time, for example, in the computation of output file names. + +# Give version.py a pattern that will expand to a GN scope consisting of +# all values we need at once. +_version_dictionary_template = "full = \"@MAJOR@.@MINOR@.@BUILD@.@PATCH@\" " + + "major = \"@MAJOR@\" minor = \"@MINOR@\" " + + "build = \"@BUILD@\" patch = \"@PATCH@\" " + +# The file containing the Chrome version number. +chrome_version_file = "//chrome/VERSION" + +_script_arguments = [] + +if (is_mac) { + _version_dictionary_template += "patch_hi = @PATCH_HI@ patch_lo = @PATCH_LO@ " + + _script_arguments += [ + "-e", + "PATCH_HI=int(PATCH)//256", + "-e", + "PATCH_LO=int(PATCH)%256", + ] +} else if (target_os == "android") { + import("//build/config/android/config.gni") + + _version_dictionary_template += + "chrome_version_code = " + "\"@CHROME_VERSION_CODE@\" " + + "chrome_modern_version_code = \"@CHROME_MODERN_VERSION_CODE@\" " + + "monochrome_version_code = \"@MONOCHROME_VERSION_CODE@\" " + + "trichrome_version_code = \"@TRICHROME_VERSION_CODE@\" " + + "webview_stable_version_code = \"@WEBVIEW_STABLE_VERSION_CODE@\" " + + "webview_beta_version_code = \"@WEBVIEW_BETA_VERSION_CODE@\" " + + "webview_dev_version_code = \"@WEBVIEW_DEV_VERSION_CODE@\" " + + if (target_cpu == "arm64" || target_cpu == "x64") { + _version_dictionary_template += "monochrome_32_version_code = \"@MONOCHROME_32_VERSION_CODE@\" " + "monochrome_32_64_version_code = \"@MONOCHROME_32_64_VERSION_CODE@\" " + "monochrome_64_32_version_code = \"@MONOCHROME_64_32_VERSION_CODE@\" " + "monochrome_64_version_code = \"@MONOCHROME_64_VERSION_CODE@\" " + "trichrome_32_version_code = \"@TRICHROME_32_VERSION_CODE@\" " + "trichrome_32_64_version_code = \"@TRICHROME_32_64_VERSION_CODE@\" " + "trichrome_64_32_version_code = \"@TRICHROME_64_32_VERSION_CODE@\" " + "trichrome_64_version_code = \"@TRICHROME_64_VERSION_CODE@\" " + "webview_32_stable_version_code = \"@WEBVIEW_32_STABLE_VERSION_CODE@\" " + "webview_32_beta_version_code = \"@WEBVIEW_32_BETA_VERSION_CODE@\" " + "webview_32_dev_version_code = \"@WEBVIEW_32_DEV_VERSION_CODE@\" " + "webview_64_stable_version_code = \"@WEBVIEW_64_STABLE_VERSION_CODE@\" " + "webview_64_beta_version_code = \"@WEBVIEW_64_BETA_VERSION_CODE@\" " + "webview_64_dev_version_code = \"@WEBVIEW_64_DEV_VERSION_CODE@\" " + } + + _script_arguments += [ + "-a", + target_cpu, + ] + + if (defined(final_android_sdk) && !final_android_sdk) { + _script_arguments += [ "--next" ] + } +} + +_script_arguments += [ + "-f", + rebase_path(chrome_version_file, root_build_dir), + "-t", + _version_dictionary_template, + "--os", + target_os, +] + +_result = exec_script("version.py", + _script_arguments, + "scope", + [ + chrome_version_file, + "android_chrome_version.py", + ]) + +# Full version. For example "45.0.12321.0" +chrome_version_full = _result.full + +# The consituent parts of the full version. +chrome_version_major = _result.major +chrome_version_minor = _result.minor +chrome_version_build = _result.build +chrome_version_patch = _result.patch + +if (is_mac) { + chrome_version_patch_hi = _result.patch_hi + chrome_version_patch_lo = _result.patch_lo + + chrome_dylib_version = "$chrome_version_build.$chrome_version_patch_hi" + + ".$chrome_version_patch_lo" +} else if (target_os == "android") { + forward_variables_from(_result, + [ + "chrome_modern_version_code", + "chrome_version_code", + "monochrome_version_code", + "monochrome_32_version_code", + "monochrome_32_64_version_code", + "monochrome_64_32_version_code", + "monochrome_64_version_code", + "trichrome_version_code", + "trichrome_32_version_code", + "trichrome_32_64_version_code", + "trichrome_64_32_version_code", + "trichrome_64_version_code", + "webview_beta_version_code", + "webview_dev_version_code", + "webview_stable_version_code", + "webview_32_beta_version_code", + "webview_32_dev_version_code", + "webview_32_stable_version_code", + "webview_64_beta_version_code", + "webview_64_dev_version_code", + "webview_64_stable_version_code", + ]) + + chrome_version_name = chrome_version_full + + lines_to_write = [ + "VersionName: $chrome_version_name", + "Chrome: $chrome_version_code", + "ChromeModern: $chrome_modern_version_code", + "Monochrome: $monochrome_version_code", + "TrichromeChrome: $trichrome_version_code", + "AndroidWebviewStable: $webview_stable_version_code", + "AndroidWebviewBeta: $webview_beta_version_code", + "AndroidWebviewDev: $webview_dev_version_code", + ] + + if (target_cpu == "arm64" || target_cpu == "x64") { + lines_to_write += [ + "Monochrome32: $monochrome_32_version_code", + "Monochrome3264: $monochrome_32_64_version_code", + "Monochrome6432: $monochrome_64_32_version_code", + "Monochrome64: $monochrome_64_version_code", + "TrichromeChrome32: $trichrome_32_version_code", + "TrichromeChrome3264: $trichrome_32_64_version_code", + "TrichromeChrome6432: $trichrome_64_32_version_code", + "TrichromeChrome64: $trichrome_64_version_code", + "AndroidWebview32Stable: $webview_32_stable_version_code", + "AndroidWebview32Beta: $webview_32_beta_version_code", + "AndroidWebview32Dev: $webview_32_dev_version_code", + "AndroidWebview64Stable: $webview_64_stable_version_code", + "AndroidWebview64Beta: $webview_64_beta_version_code", + "AndroidWebview64Dev: $webview_64_dev_version_code", + ] + } + + write_file("$root_out_dir/android_chrome_versions.txt", lines_to_write) +} diff --git a/third_party/libwebrtc/build/util/version.py b/third_party/libwebrtc/build/util/version.py new file mode 100755 index 0000000000..e93cfe40dc --- /dev/null +++ b/third_party/libwebrtc/build/util/version.py @@ -0,0 +1,268 @@ +#!/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. + +""" +version.py -- Chromium version string substitution utility. +""" + +from __future__ import print_function + +import argparse +import os +import sys + +import android_chrome_version + + +def FetchValuesFromFile(values_dict, file_name): + """ + Fetches KEYWORD=VALUE settings from the specified file. + + Everything to the left of the first '=' is the keyword, + everything to the right is the value. No stripping of + white space, so beware. + + The file must exist, otherwise you get the Python exception from open(). + """ + for line in open(file_name, 'r').readlines(): + key, val = line.rstrip('\r\n').split('=', 1) + values_dict[key] = val + + +def FetchValues(file_list, is_official_build=None): + """ + Returns a dictionary of values to be used for substitution. + + Populates the dictionary with KEYWORD=VALUE settings from the files in + 'file_list'. + + Explicitly adds the following value from internal calculations: + + OFFICIAL_BUILD + """ + CHROME_BUILD_TYPE = os.environ.get('CHROME_BUILD_TYPE') + if CHROME_BUILD_TYPE == '_official' or is_official_build: + official_build = '1' + else: + official_build = '0' + + values = dict( + OFFICIAL_BUILD = official_build, + ) + + for file_name in file_list: + FetchValuesFromFile(values, file_name) + + script_dirname = os.path.dirname(os.path.realpath(__file__)) + lastchange_filename = os.path.join(script_dirname, "LASTCHANGE") + lastchange_values = {} + FetchValuesFromFile(lastchange_values, lastchange_filename) + + for placeholder_key, placeholder_value in values.items(): + values[placeholder_key] = SubstTemplate(placeholder_value, + lastchange_values) + + return values + + +def SubstTemplate(contents, values): + """ + Returns the template with substituted values from the specified dictionary. + + Keywords to be substituted are surrounded by '@': @KEYWORD@. + + No attempt is made to avoid recursive substitution. The order + of evaluation is random based on the order of the keywords returned + by the Python dictionary. So do NOT substitute a value that + contains any @KEYWORD@ strings expecting them to be recursively + substituted, okay? + """ + for key, val in values.items(): + try: + contents = contents.replace('@' + key + '@', val) + except TypeError: + print(repr(key), repr(val)) + return contents + + +def SubstFile(file_name, values): + """ + Returns the contents of the specified file_name with substituted values. + + Substituted values come from the specified dictionary. + + This is like SubstTemplate, except it operates on a file. + """ + template = open(file_name, 'r').read() + return SubstTemplate(template, values) + + +def WriteIfChanged(file_name, contents): + """ + Writes the specified contents to the specified file_name. + + Does nothing if the contents aren't different than the current contents. + """ + try: + old_contents = open(file_name, 'r').read() + except EnvironmentError: + pass + else: + if contents == old_contents: + return + os.unlink(file_name) + open(file_name, 'w').write(contents) + + +def BuildParser(): + """Build argparse parser, with added arguments.""" + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--file', action='append', default=[], + help='Read variables from FILE.') + parser.add_argument('-i', '--input', default=None, + help='Read strings to substitute from FILE.') + parser.add_argument('-o', '--output', default=None, + help='Write substituted strings to FILE.') + parser.add_argument('-t', '--template', default=None, + help='Use TEMPLATE as the strings to substitute.') + parser.add_argument( + '-e', + '--eval', + action='append', + default=[], + help='Evaluate VAL after reading variables. Can be used ' + 'to synthesize variables. e.g. -e \'PATCH_HI=int(' + 'PATCH)//256.') + parser.add_argument( + '-a', + '--arch', + default=None, + choices=android_chrome_version.ARCH_CHOICES, + help='Set which cpu architecture the build is for.') + parser.add_argument('--os', default=None, help='Set the target os.') + parser.add_argument('--official', action='store_true', + help='Whether the current build should be an official ' + 'build, used in addition to the environment ' + 'variable.') + parser.add_argument( + '--next', + action='store_true', + help='Whether the current build should be a "next" ' + 'build, which targets pre-release versions of ' + 'Android') + parser.add_argument('args', nargs=argparse.REMAINDER, + help='For compatibility: INPUT and OUTPUT can be ' + 'passed as positional arguments.') + return parser + + +def BuildEvals(options, parser): + """Construct a dict of passed '-e' arguments for evaluating.""" + evals = {} + for expression in options.eval: + try: + evals.update(dict([expression.split('=', 1)])) + except ValueError: + parser.error('-e requires VAR=VAL') + return evals + + +def ModifyOptionsCompat(options, parser): + """Support compatibility with old versions. + + Specifically, for old versions that considered the first two + positional arguments shorthands for --input and --output. + """ + while len(options.args) and (options.input is None or options.output is None): + if options.input is None: + options.input = options.args.pop(0) + elif options.output is None: + options.output = options.args.pop(0) + if options.args: + parser.error('Unexpected arguments: %r' % options.args) + + +def GenerateValues(options, evals): + """Construct a dict of raw values used to generate output. + + e.g. this could return a dict like + { + 'BUILD': 74, + } + + which would be used to resolve a template like + 'build = "@BUILD@"' into 'build = "74"' + + """ + values = FetchValues(options.file, options.official) + + for key, val in evals.items(): + values[key] = str(eval(val, globals(), values)) + + if options.os == 'android': + android_chrome_version_codes = android_chrome_version.GenerateVersionCodes( + values, options.arch, options.next) + values.update(android_chrome_version_codes) + + return values + + +def GenerateOutputContents(options, values): + """Construct output string (e.g. from template). + + Arguments: + options -- argparse parsed arguments + values -- dict with raw values used to resolve the keywords in a template + string + """ + + if options.template is not None: + return SubstTemplate(options.template, values) + elif options.input: + return SubstFile(options.input, values) + else: + # Generate a default set of version information. + return """MAJOR=%(MAJOR)s +MINOR=%(MINOR)s +BUILD=%(BUILD)s +PATCH=%(PATCH)s +LASTCHANGE=%(LASTCHANGE)s +OFFICIAL_BUILD=%(OFFICIAL_BUILD)s +""" % values + + +def BuildOutput(args): + """Gets all input and output values needed for writing output.""" + # Build argparse parser with arguments + parser = BuildParser() + options = parser.parse_args(args) + + # Get dict of passed '-e' arguments for evaluating + evals = BuildEvals(options, parser) + # For compatibility with interface that considered first two positional + # arguments shorthands for --input and --output. + ModifyOptionsCompat(options, parser) + + # Get the raw values that will be used the generate the output + values = GenerateValues(options, evals) + # Get the output string + contents = GenerateOutputContents(options, values) + + return {'options': options, 'contents': contents} + + +def main(): + output = BuildOutput(sys.argv[1:]) + + if output['options'].output is not None: + WriteIfChanged(output['options'].output, output['contents']) + else: + print(output['contents']) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/util/version_test.py b/third_party/libwebrtc/build/util/version_test.py new file mode 100644 index 0000000000..2a65ddc716 --- /dev/null +++ b/third_party/libwebrtc/build/util/version_test.py @@ -0,0 +1,174 @@ +# Copyright 2019 The Chromium 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 unittest + +import mock +import version + + +def _ReplaceArgs(args, *replacements): + new_args = args[:] + for flag, val in replacements: + flag_index = args.index(flag) + new_args[flag_index + 1] = val + return new_args + + +class _VersionTest(unittest.TestCase): + """Unittests for the version module. + """ + + _CHROME_VERSION_FILE = os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, 'chrome', 'VERSION') + + _SCRIPT = os.path.join(os.path.dirname(__file__), 'version.py') + + _EXAMPLE_VERSION = { + 'MAJOR': '74', + 'MINOR': '0', + 'BUILD': '3720', + 'PATCH': '0', + } + + _EXAMPLE_TEMPLATE = ( + 'full = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@" ' + 'major = "@MAJOR@" minor = "@MINOR@" ' + 'build = "@BUILD@" patch = "@PATCH@" version_id = @VERSION_ID@ ') + + _ANDROID_CHROME_VARS = [ + 'chrome_version_code', + 'chrome_modern_version_code', + 'monochrome_version_code', + 'trichrome_version_code', + 'webview_stable_version_code', + 'webview_beta_version_code', + 'webview_dev_version_code', + ] + + _EXAMPLE_ANDROID_TEMPLATE = ( + _EXAMPLE_TEMPLATE + ''.join( + ['%s = "@%s@" ' % (el, el.upper()) for el in _ANDROID_CHROME_VARS])) + + _EXAMPLE_ARGS = [ + '-f', + _CHROME_VERSION_FILE, + '-t', + _EXAMPLE_TEMPLATE, + ] + + _EXAMPLE_ANDROID_ARGS = _ReplaceArgs(_EXAMPLE_ARGS, + ['-t', _EXAMPLE_ANDROID_TEMPLATE]) + [ + '-a', + 'arm', + '--os', + 'android', + ] + + @staticmethod + def _RunBuildOutput(new_version_values={}, + get_new_args=lambda old_args: old_args): + """Parameterized helper method for running the main testable method in + version.py. + + Keyword arguments: + new_version_values -- dict used to update _EXAMPLE_VERSION + get_new_args -- lambda for updating _EXAMPLE_ANDROID_ARGS + """ + + with mock.patch('version.FetchValuesFromFile') as \ + fetch_values_from_file_mock: + + fetch_values_from_file_mock.side_effect = (lambda values, file : + values.update( + dict(_VersionTest._EXAMPLE_VERSION, **new_version_values))) + + new_args = get_new_args(_VersionTest._EXAMPLE_ARGS) + return version.BuildOutput(new_args) + + def testFetchValuesFromFile(self): + """It returns a dict in correct format - { : }, to verify + assumption of other tests that mock this function + """ + result = {} + version.FetchValuesFromFile(result, self._CHROME_VERSION_FILE) + + for key, val in result.iteritems(): + self.assertIsInstance(key, str) + self.assertIsInstance(val, str) + + def testBuildOutputAndroid(self): + """Assert it gives includes assignments of expected variables""" + output = self._RunBuildOutput( + get_new_args=lambda args: self._EXAMPLE_ANDROID_ARGS) + contents = output['contents'] + + self.assertRegexpMatches(contents, r'\bchrome_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\bchrome_modern_version_code = "\d+"\s') + self.assertRegexpMatches(contents, r'\bmonochrome_version_code = "\d+"\s') + self.assertRegexpMatches(contents, r'\btrichrome_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\bwebview_stable_version_code = "\d+"\s') + self.assertRegexpMatches(contents, r'\bwebview_beta_version_code = "\d+"\s') + self.assertRegexpMatches(contents, r'\bwebview_dev_version_code = "\d+"\s') + + def testBuildOutputAndroidArchVariantsArm64(self): + """Assert 64-bit-specific version codes""" + new_template = ( + self._EXAMPLE_ANDROID_TEMPLATE + + "monochrome_64_32_version_code = \"@MONOCHROME_64_32_VERSION_CODE@\" " + "monochrome_64_version_code = \"@MONOCHROME_64_VERSION_CODE@\" " + "trichrome_64_32_version_code = \"@TRICHROME_64_32_VERSION_CODE@\" " + "trichrome_64_version_code = \"@TRICHROME_64_VERSION_CODE@\" ") + args_with_template = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, + ['-t', new_template]) + new_args = _ReplaceArgs(args_with_template, ['-a', 'arm64']) + output = self._RunBuildOutput(get_new_args=lambda args: new_args) + contents = output['contents'] + + self.assertRegexpMatches(contents, + r'\bmonochrome_64_32_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\bmonochrome_64_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\btrichrome_64_32_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\btrichrome_64_version_code = "\d+"\s') + + def testBuildOutputAndroidArchVariantsX64(self): + """Assert 64-bit-specific version codes""" + new_template = ( + self._EXAMPLE_ANDROID_TEMPLATE + + "monochrome_64_32_version_code = \"@MONOCHROME_64_32_VERSION_CODE@\" " + "monochrome_64_version_code = \"@MONOCHROME_64_VERSION_CODE@\" " + "trichrome_64_32_version_code = \"@TRICHROME_64_32_VERSION_CODE@\" " + "trichrome_64_version_code = \"@TRICHROME_64_VERSION_CODE@\" ") + args_with_template = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, + ['-t', new_template]) + new_args = _ReplaceArgs(args_with_template, ['-a', 'x64']) + output = self._RunBuildOutput(get_new_args=lambda args: new_args) + contents = output['contents'] + + self.assertRegexpMatches(contents, + r'\bmonochrome_64_32_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\bmonochrome_64_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\btrichrome_64_32_version_code = "\d+"\s') + self.assertRegexpMatches(contents, + r'\btrichrome_64_version_code = "\d+"\s') + + def testBuildOutputAndroidChromeArchInput(self): + """Assert it raises an exception when using an invalid architecture input""" + new_args = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, ['-a', 'foobar']) + with self.assertRaises(SystemExit) as cm: + self._RunBuildOutput(get_new_args=lambda args: new_args) + + self.assertEqual(cm.exception.code, 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/libwebrtc/build/vs_toolchain.py b/third_party/libwebrtc/build/vs_toolchain.py new file mode 100755 index 0000000000..55372ba3a8 --- /dev/null +++ b/third_party/libwebrtc/build/vs_toolchain.py @@ -0,0 +1,575 @@ +#!/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. + +from __future__ import print_function + +import collections +import glob +import json +import os +import pipes +import platform +import re +import shutil +import stat +import subprocess +import sys + +from gn_helpers import ToGNString + +# VS 2019 16.61 with 10.0.19041 SDK, and 10.0.20348 version of +# d3dcompiler_47.dll, with ARM64 libraries and UWP support. +# See go/chromium-msvc-toolchain for instructions about how to update the +# toolchain. +# +# When updating the toolchain, consider the following areas impacted by the +# toolchain version: +# +# * //base/win/windows_version.cc NTDDI preprocessor check +# Triggers a compiler error if the available SDK is older than the minimum. +# * //build/config/win/BUILD.gn NTDDI_VERSION value +# Affects the availability of APIs in the toolchain headers. +# * //docs/windows_build_instructions.md mentions of VS or Windows SDK. +# Keeps the document consistent with the toolchain version. +TOOLCHAIN_HASH = '3bda71a11e' + +script_dir = os.path.dirname(os.path.realpath(__file__)) +json_data_file = os.path.join(script_dir, 'win_toolchain.json') + +# VS versions are listed in descending order of priority (highest first). +MSVS_VERSIONS = collections.OrderedDict([ + ('2019', '16.0'), + ('2017', '15.0'), +]) + +# List of preferred VC toolset version based on MSVS +MSVC_TOOLSET_VERSION = { + '2019' : 'VC142', + '2017' : 'VC141', +} + +def _HostIsWindows(): + """Returns True if running on a Windows host (including under cygwin).""" + return sys.platform in ('win32', 'cygwin') + +def SetEnvironmentAndGetRuntimeDllDirs(): + """Sets up os.environ to use the depot_tools VS toolchain with gyp, and + returns the location of the VC runtime DLLs so they can be copied into + the output directory after gyp generation. + + Return value is [x64path, x86path, 'Arm64Unused'] or None. arm64path is + generated separately because there are multiple folders for the arm64 VC + runtime. + """ + vs_runtime_dll_dirs = None + depot_tools_win_toolchain = \ + bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) + # When running on a non-Windows host, only do this if the SDK has explicitly + # been downloaded before (in which case json_data_file will exist). + if ((_HostIsWindows() or os.path.exists(json_data_file)) + and depot_tools_win_toolchain): + if ShouldUpdateToolchain(): + if len(sys.argv) > 1 and sys.argv[1] == 'update': + update_result = Update() + else: + update_result = Update(no_download=True) + if update_result != 0: + raise Exception('Failed to update, error code %d.' % update_result) + with open(json_data_file, 'r') as tempf: + toolchain_data = json.load(tempf) + + toolchain = toolchain_data['path'] + version = toolchain_data['version'] + win_sdk = toolchain_data.get('win_sdk') + wdk = toolchain_data['wdk'] + # TODO(scottmg): The order unfortunately matters in these. They should be + # split into separate keys for x64/x86/arm64. (See CopyDlls call below). + # http://crbug.com/345992 + vs_runtime_dll_dirs = toolchain_data['runtime_dirs'] + # The number of runtime_dirs in the toolchain_data was two (x64/x86) but + # changed to three (x64/x86/arm64) and this code needs to handle both + # possibilities, which can change independently from this code. + if len(vs_runtime_dll_dirs) == 2: + vs_runtime_dll_dirs.append('Arm64Unused') + + os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain + + os.environ['WINDOWSSDKDIR'] = win_sdk + os.environ['WDK_DIR'] = wdk + # Include the VS runtime in the PATH in case it's not machine-installed. + runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs) + os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH'] + elif sys.platform == 'win32' and not depot_tools_win_toolchain: + if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ: + os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath() + + # When using an installed toolchain these files aren't needed in the output + # directory in order to run binaries locally, but they are needed in order + # to create isolates or the mini_installer. Copying them to the output + # directory ensures that they are available when needed. + bitness = platform.architecture()[0] + # When running 64-bit python the x64 DLLs will be in System32 + # ARM64 binaries will not be available in the system directories because we + # don't build on ARM64 machines. + x64_path = 'System32' if bitness == '64bit' else 'Sysnative' + x64_path = os.path.join(os.path.expandvars('%windir%'), x64_path) + vs_runtime_dll_dirs = [x64_path, + os.path.join(os.path.expandvars('%windir%'), + 'SysWOW64'), + 'Arm64Unused'] + + return vs_runtime_dll_dirs + + +def _RegistryGetValueUsingWinReg(key, value): + """Use the _winreg module to obtain the value of a registry key. + + Args: + key: The registry key. + value: The particular registry value to read. + Return: + contents of the registry key's value, or None on failure. Throws + ImportError if _winreg is unavailable. + """ + import _winreg + try: + root, subkey = key.split('\\', 1) + assert root == 'HKLM' # Only need HKLM for now. + with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey: + return _winreg.QueryValueEx(hkey, value)[0] + except WindowsError: + return None + + +def _RegistryGetValue(key, value): + try: + return _RegistryGetValueUsingWinReg(key, value) + except ImportError: + raise Exception('The python library _winreg not found.') + + +def GetVisualStudioVersion(): + """Return best available version of Visual Studio. + """ + supported_versions = list(MSVS_VERSIONS.keys()) + + # VS installed in depot_tools for Googlers + if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))): + return supported_versions[0] + + # VS installed in system for external developers + supported_versions_str = ', '.join('{} ({})'.format(v,k) + for k,v in MSVS_VERSIONS.items()) + available_versions = [] + for version in supported_versions: + # Checking vs%s_install environment variables. + # For example, vs2019_install could have the value + # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community". + # Only vs2017_install and vs2019_install are supported. + path = os.environ.get('vs%s_install' % version) + if path and os.path.exists(path): + available_versions.append(version) + break + # Detecting VS under possible paths. + path = os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s' % version) + if path and any( + os.path.exists(os.path.join(path, edition)) + for edition in ('Enterprise', 'Professional', 'Community', 'Preview', + 'BuildTools')): + available_versions.append(version) + break + + if not available_versions: + raise Exception('No supported Visual Studio can be found.' + ' Supported versions are: %s.' % supported_versions_str) + return available_versions[0] + + +def DetectVisualStudioPath(): + """Return path to the installed Visual Studio. + """ + + # Note that this code is used from + # build/toolchain/win/setup_toolchain.py as well. + version_as_year = GetVisualStudioVersion() + + # The VC++ >=2017 install location needs to be located using COM instead of + # the registry. For details see: + # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/ + # For now we use a hardcoded default with an environment variable override. + for path in ( + os.environ.get('vs%s_install' % version_as_year), + os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s/Enterprise' % + version_as_year), + os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s/Professional' % + version_as_year), + os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s/Community' % + version_as_year), + os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s/Preview' % + version_as_year), + os.path.expandvars('%ProgramFiles(x86)%' + + '/Microsoft Visual Studio/%s/BuildTools' % + version_as_year)): + if path and os.path.exists(path): + return path + + raise Exception('Visual Studio Version %s not found.' % version_as_year) + + +def _CopyRuntimeImpl(target, source, verbose=True): + """Copy |source| to |target| if it doesn't already exist or if it needs to be + updated (comparing last modified time as an approximate float match as for + some reason the values tend to differ by ~1e-07 despite being copies of the + same file... https://crbug.com/603603). + """ + if (os.path.isdir(os.path.dirname(target)) and + (not os.path.isfile(target) or + abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)): + if verbose: + print('Copying %s to %s...' % (source, target)) + if os.path.exists(target): + # Make the file writable so that we can delete it now, and keep it + # readable. + os.chmod(target, stat.S_IWRITE | stat.S_IREAD) + os.unlink(target) + shutil.copy2(source, target) + # Make the file writable so that we can overwrite or delete it later, + # keep it readable. + os.chmod(target, stat.S_IWRITE | stat.S_IREAD) + +def _SortByHighestVersionNumberFirst(list_of_str_versions): + """This sorts |list_of_str_versions| according to version number rules + so that version "1.12" is higher than version "1.9". Does not work + with non-numeric versions like 1.4.a8 which will be higher than + 1.4.a12. It does handle the versions being embedded in file paths. + """ + def to_int_if_int(x): + try: + return int(x) + except ValueError: + return x + + def to_number_sequence(x): + part_sequence = re.split(r'[\\/\.]', x) + return [to_int_if_int(x) for x in part_sequence] + + list_of_str_versions.sort(key=to_number_sequence, reverse=True) + + +def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix): + """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't + exist, but the target directory does exist.""" + if target_cpu == 'arm64': + # Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/ + # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/. + # Select VC toolset directory based on Visual Studio version + vc_redist_root = FindVCRedistRoot() + if suffix.startswith('.'): + vc_toolset_dir = 'Microsoft.{}.CRT' \ + .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) + source_dir = os.path.join(vc_redist_root, + 'arm64', vc_toolset_dir) + else: + vc_toolset_dir = 'Microsoft.{}.DebugCRT' \ + .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) + source_dir = os.path.join(vc_redist_root, 'debug_nonredist', + 'arm64', vc_toolset_dir) + file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140') + if target_cpu == 'x64' and GetVisualStudioVersion() != '2017': + file_parts = file_parts + ('vcruntime140_1', ) + for file_part in file_parts: + dll = file_part + suffix + target = os.path.join(target_dir, dll) + source = os.path.join(source_dir, dll) + _CopyRuntimeImpl(target, source) + # Copy the UCRT files from the Windows SDK. This location includes the + # api-ms-win-crt-*.dll files that are not found in the Windows directory. + # These files are needed for component builds. If WINDOWSSDKDIR is not set + # use the default SDK path. This will be the case when + # DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run. + win_sdk_dir = os.path.normpath( + os.environ.get('WINDOWSSDKDIR', + os.path.expandvars('%ProgramFiles(x86)%' + '\\Windows Kits\\10'))) + # ARM64 doesn't have a redist for the ucrt DLLs because they are always + # present in the OS. + if target_cpu != 'arm64': + # Starting with the 10.0.17763 SDK the ucrt files are in a version-named + # directory - this handles both cases. + redist_dir = os.path.join(win_sdk_dir, 'Redist') + version_dirs = glob.glob(os.path.join(redist_dir, '10.*')) + if len(version_dirs) > 0: + _SortByHighestVersionNumberFirst(version_dirs) + redist_dir = version_dirs[0] + ucrt_dll_dirs = os.path.join(redist_dir, 'ucrt', 'DLLs', target_cpu) + ucrt_files = glob.glob(os.path.join(ucrt_dll_dirs, 'api-ms-win-*.dll')) + assert len(ucrt_files) > 0 + for ucrt_src_file in ucrt_files: + file_part = os.path.basename(ucrt_src_file) + ucrt_dst_file = os.path.join(target_dir, file_part) + _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False) + # We must copy ucrtbase.dll for x64/x86, and ucrtbased.dll for all CPU types. + if target_cpu != 'arm64' or not suffix.startswith('.'): + if not suffix.startswith('.'): + # ucrtbased.dll is located at {win_sdk_dir}/bin/{a.b.c.d}/{target_cpu}/ + # ucrt/. + sdk_bin_root = os.path.join(win_sdk_dir, 'bin') + sdk_bin_sub_dirs = glob.glob(os.path.join(sdk_bin_root, '10.*')) + # Select the most recent SDK if there are multiple versions installed. + _SortByHighestVersionNumberFirst(sdk_bin_sub_dirs) + for directory in sdk_bin_sub_dirs: + sdk_redist_root_version = os.path.join(sdk_bin_root, directory) + if not os.path.isdir(sdk_redist_root_version): + continue + source_dir = os.path.join(sdk_redist_root_version, target_cpu, 'ucrt') + break + _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix), + os.path.join(source_dir, 'ucrtbase' + suffix)) + + +def FindVCComponentRoot(component): + """Find the most recent Tools or Redist or other directory in an MSVC install. + Typical results are {toolchain_root}/VC/{component}/MSVC/{x.y.z}. The {x.y.z} + version number part changes frequently so the highest version number found is + used. + """ + + SetEnvironmentAndGetRuntimeDllDirs() + assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ) + vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'], + 'VC', component, 'MSVC') + vc_component_msvc_contents = glob.glob( + os.path.join(vc_component_msvc_root, '14.*')) + # Select the most recent toolchain if there are several. + _SortByHighestVersionNumberFirst(vc_component_msvc_contents) + for directory in vc_component_msvc_contents: + if os.path.isdir(directory): + return directory + raise Exception('Unable to find the VC %s directory.' % component) + + +def FindVCRedistRoot(): + """In >=VS2017, Redist binaries are located in + {toolchain_root}/VC/Redist/MSVC/{x.y.z}/{target_cpu}/. + + This returns the '{toolchain_root}/VC/Redist/MSVC/{x.y.z}/' path. + """ + return FindVCComponentRoot('Redist') + + +def _CopyRuntime(target_dir, source_dir, target_cpu, debug): + """Copy the VS runtime DLLs, only if the target doesn't exist, but the target + directory does exist. Handles VS 2015, 2017 and 2019.""" + suffix = 'd.dll' if debug else '.dll' + # VS 2015, 2017 and 2019 use the same CRT DLLs. + _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix) + + +def CopyDlls(target_dir, configuration, target_cpu): + """Copy the VS runtime DLLs into the requested directory as needed. + + configuration is one of 'Debug' or 'Release'. + target_cpu is one of 'x86', 'x64' or 'arm64'. + + The debug configuration gets both the debug and release DLLs; the + release config only the latter. + """ + vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() + if not vs_runtime_dll_dirs: + return + + x64_runtime, x86_runtime, arm64_runtime = vs_runtime_dll_dirs + if target_cpu == 'x64': + runtime_dir = x64_runtime + elif target_cpu == 'x86': + runtime_dir = x86_runtime + elif target_cpu == 'arm64': + runtime_dir = arm64_runtime + else: + raise Exception('Unknown target_cpu: ' + target_cpu) + _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False) + if configuration == 'Debug': + _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True) + _CopyDebugger(target_dir, target_cpu) + + +def _CopyDebugger(target_dir, target_cpu): + """Copy dbghelp.dll and dbgcore.dll into the requested directory as needed. + + target_cpu is one of 'x86', 'x64' or 'arm64'. + + dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file + from the SDK directory avoids using the system copy of dbghelp.dll which then + ensures compatibility with recent debug information formats, such as VS + 2017 /debug:fastlink PDBs. + + dbgcore.dll is needed when using some functions from dbghelp.dll (like + MinidumpWriteDump). + """ + win_sdk_dir = SetEnvironmentAndGetSDKDir() + if not win_sdk_dir: + return + + # List of debug files that should be copied, the first element of the tuple is + # the name of the file and the second indicates if it's optional. + debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)] + # The UCRT is not a redistributable component on arm64. + if target_cpu != 'arm64': + debug_files.extend([('api-ms-win-downlevel-kernel32-l2-1-0.dll', False), + ('api-ms-win-eventing-provider-l1-1-0.dll', False)]) + for debug_file, is_optional in debug_files: + full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file) + if not os.path.exists(full_path): + if is_optional: + continue + else: + raise Exception('%s not found in "%s"\r\nYou must install' + 'Windows 10 SDK version 10.0.19041.0 including the ' + '"Debugging Tools for Windows" feature.' % + (debug_file, full_path)) + target_path = os.path.join(target_dir, debug_file) + _CopyRuntimeImpl(target_path, full_path) + + +def _GetDesiredVsToolchainHashes(): + """Load a list of SHA1s corresponding to the toolchains that we want installed + to build with.""" + # Third parties that do not have access to the canonical toolchain can map + # canonical toolchain version to their own toolchain versions. + toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % TOOLCHAIN_HASH + return [os.environ.get(toolchain_hash_mapping_key, TOOLCHAIN_HASH)] + + +def ShouldUpdateToolchain(): + """Check if the toolchain should be upgraded.""" + if not os.path.exists(json_data_file): + return True + with open(json_data_file, 'r') as tempf: + toolchain_data = json.load(tempf) + version = toolchain_data['version'] + env_version = GetVisualStudioVersion() + # If there's a mismatch between the version set in the environment and the one + # in the json file then the toolchain should be updated. + return version != env_version + + +def Update(force=False, no_download=False): + """Requests an update of the toolchain to the specific hashes we have at + this revision. The update outputs a .json of the various configuration + information required to pass to gyp which we use in |GetToolchainDir()|. + If no_download is true then the toolchain will be configured if present but + will not be downloaded. + """ + if force != False and force != '--force': + print('Unknown parameter "%s"' % force, file=sys.stderr) + return 1 + if force == '--force' or os.path.exists(json_data_file): + force = True + + depot_tools_win_toolchain = \ + bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) + if (_HostIsWindows() or force) and depot_tools_win_toolchain: + import find_depot_tools + depot_tools_path = find_depot_tools.add_depot_tools_to_path() + + # On Linux, the file system is usually case-sensitive while the Windows + # SDK only works on case-insensitive file systems. If it doesn't already + # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive + # part of the file system. + toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain', 'vs_files') + # For testing this block, unmount existing mounts with + # fusermount -u third_party/depot_tools/win_toolchain/vs_files + if sys.platform.startswith('linux') and not os.path.ismount(toolchain_dir): + import distutils.spawn + ciopfs = distutils.spawn.find_executable('ciopfs') + if not ciopfs: + # ciopfs not found in PATH; try the one downloaded from the DEPS hook. + ciopfs = os.path.join(script_dir, 'ciopfs') + if not os.path.isdir(toolchain_dir): + os.mkdir(toolchain_dir) + if not os.path.isdir(toolchain_dir + '.ciopfs'): + os.mkdir(toolchain_dir + '.ciopfs') + # Without use_ino, clang's #pragma once and Wnonportable-include-path + # both don't work right, see https://llvm.org/PR34931 + # use_ino doesn't slow down builds, so it seems there's no drawback to + # just using it always. + subprocess.check_call([ + ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir]) + + get_toolchain_args = [ + sys.executable, + os.path.join(depot_tools_path, + 'win_toolchain', + 'get_toolchain_if_necessary.py'), + '--output-json', json_data_file, + ] + _GetDesiredVsToolchainHashes() + if force: + get_toolchain_args.append('--force') + if no_download: + get_toolchain_args.append('--no-download') + subprocess.check_call(get_toolchain_args) + + return 0 + + +def NormalizePath(path): + while path.endswith('\\'): + path = path[:-1] + return path + + +def SetEnvironmentAndGetSDKDir(): + """Gets location information about the current sdk (must have been + previously updated by 'update'). This is used for the GN build.""" + SetEnvironmentAndGetRuntimeDllDirs() + + # If WINDOWSSDKDIR is not set, search the default SDK path and set it. + if not 'WINDOWSSDKDIR' in os.environ: + default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%' + '\\Windows Kits\\10') + if os.path.isdir(default_sdk_path): + os.environ['WINDOWSSDKDIR'] = default_sdk_path + + return NormalizePath(os.environ['WINDOWSSDKDIR']) + + +def GetToolchainDir(): + """Gets location information about the current toolchain (must have been + previously updated by 'update'). This is used for the GN build.""" + runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() + win_sdk_dir = SetEnvironmentAndGetSDKDir() + + print('''vs_path = %s +sdk_path = %s +vs_version = %s +wdk_dir = %s +runtime_dirs = %s +''' % (ToGNString(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH'])), + ToGNString(win_sdk_dir), ToGNString(GetVisualStudioVersion()), + ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))), + ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None'])))) + + +def main(): + commands = { + 'update': Update, + 'get_toolchain_dir': GetToolchainDir, + 'copy_dlls': CopyDlls, + } + if len(sys.argv) < 2 or sys.argv[1] not in commands: + print('Expected one of: %s' % ', '.join(commands), file=sys.stderr) + return 1 + if sys.argv[1] == 'copy_dlls': + return 0 + return commands[sys.argv[1]](*sys.argv[2:]) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/whitespace_file.txt b/third_party/libwebrtc/build/whitespace_file.txt new file mode 100644 index 0000000000..c6768a3aa2 --- /dev/null +++ b/third_party/libwebrtc/build/whitespace_file.txt @@ -0,0 +1,190 @@ +Copyright 2014 The Chromium Authors. All rights reserved. +Use of this useless file is governed by a BSD-style license that can be +found in the LICENSE file. + + +This file is used for making non-code changes to trigger buildbot cycles. Make +any modification below this line. + +====================================================================== + +Let's make a story. Add zero+ sentences for every commit: + +CHÄPTER 1: +It was a dark and blinky night; the rain fell in torrents -- except at +occasional intervals, when it was checked by a violent gust of wind which +swept up the streets (for it is in London that our scene lies), rattling along +the housetops, and fiercely agitating the scanty flame of the lamps that +struggled against the elements. A hooded figure emerged. + +It was a Domo-Kun. + +"What took you so long?", inquired his wife. + +Silence. Oblivious to his silence, she continued, "Did Mr. Usagi enjoy the +waffles you brought him?" "You know him, he's not one to forego a waffle, +no matter how burnt," he snickered. + +The pause was filled with the sound of compile errors. + +CHAPTER 2: +The jelly was as dark as night, and just as runny. +The Domo-Kun shuddered, remembering the way Mr. Usagi had speared his waffles +with his fork, watching the runny jelly spread and pool across his plate, +like the blood of a dying fawn. "It reminds me of that time --" he started, as +his wife cut in quickly: "-- please. I can't bear to hear it.". A flury of +images coming from the past flowed through his mind. + +"You recall what happened on Mulholland drive?" The ceiling fan rotated slowly +overhead, barely disturbing the thick cigarette smoke. No doubt was left about +when the fan was last cleaned. + +There was a poignant pause. + +CHAPTER 3: +Mr. Usagi felt that something wasn't right. Shortly after the Domo-Kun left he +began feeling sick. He thought out loud to himself, "No, he wouldn't have done +that to me." He considered that perhaps he shouldn't have pushed so hard. +Perhaps he shouldn't have been so cold and sarcastic, after the unimaginable +horror that had occurred just the week before. + +Next time, there won't be any sushi. Why sushi with waffles anyway? It's like +adorning breakfast cereal with halibut -- shameful. + +CHAPTER 4: +The taste of stale sushi in his mouth the next morning was unbearable. He +wondered where the sushi came from as he attempted to wash the taste away with +a bottle of 3000¥ sake. He tries to recall the cook's face. Green? Probably. + +CHAPTER 5: +Many tears later, Mr. Usagi would laugh at the memory of the earnest, +well-intentioned Domo-Kun. Another day in the life. That is when he realized that +life goes on. + +$CHAPTER6 + +TRUISMS (1978-1983) +JENNY HOLZER +A LITTLE KNOWLEDGE CAN GO A LONG WAY +A LOT OF PROFESSIONALS ARE CRACKPOTS +A MAN CAN'T KNOW WHAT IT IS TO BE A MOTHER +A NAME MEANS A LOT JUST BY ITSELF +A POSITIVE ATTITUDE MEANS ALL THE DIFFERENCE IN THE WORLD +A RELAXED MAN IS NOT NECESSARILY A BETTER MAN +NO ONE SHOULD EVER USE SVN +AN INFLEXIBLE POSITION SOMETIMES IS A SIGN OF PARALYSIS +IT IS MANS FATE TO OUTSMART HIMSELF +BEING SURE OF YOURSELF MEANS YOU'RE A FOOL +AM NOT +ARE TOO +IF AT FIRST YOU DON'T SUCCEED: TRY, EXCEPT, FINALLY +AND THEN, TIME LEAPT BACKWARDS +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhh LOT +I'm really tempted to change something above the line. +Reeccciiiipppppeeeeeesssssss!!!!!!!!! +PEOPLE SAY "FAILURE IS NOT AN OPTION", BUT FAILURE IS ALWAYS AN OPTION. +WHAT GOES UP MUST HAVE A NON-ZERO VELOCITY + +I can feel the heat closing in, feel them out there making their moves... +What could possibly go wrong? We've already ate our cake. + +Stand Still. Pause Clocks. We can make the World Stop. +WUBWUBWUBWUBWUB + +I want a 1917 build and you will give me what I want. + +This sentence is false. + +Beauty is in the eyes of a Beholder. + +I'm the best at space. + +The first time Yossarian saw the chaplain, he fell madly in love with him. +* +* +* +Give not thyself up, then, to fire, lest it invert thee, deaden thee; as for +the time it did me. There is a wisdom that is woe; but there is a woe that is +madness. And there is a Catskill eagle in some souls that can alike dive down +into the blackest gorges, and soar out of them again and become invisible in +the sunny spaces. And even if he for ever flies within the gorge, that gorge +is in the mountains; so that even in his lowest swoop the mountain eagle is +still higher than other birds upon the plain, even though they soar. +* +* +* + +I'm here to commit lines and drop rhymes +* +This is a line to test and try uploading a cl. +* +Yay, another first commit! What a beautiful day! + +And lo, in the year 2014, there was verily an attempt to upgrade to GCC 4.8 on +the Android bots, and it was good. Except on one bot, where it was bad. And +lo, the change was reverted, and GCC went back to 4.6, where code is slower +and less optimized. And verily did it break the build, because artifacts had +been created with 4.8, and alignment was no longer the same, and a great +sadness descended upon the Android GN buildbot, and it did refuseth to build +any more. But the sheriffs thought to themselves: Placebo! Let us clobber the +bot, and perhaps it will rebuild with GCC 4.6, which hath worked for many many +seasons. And so they modified the whitespace file with these immortal lines, +and visited it upon the bots, that great destruction might be wrought upon +their outdated binaries. In clobberus, veritas. + +As the git approaches, light begins to shine through the SCM thrice again... +However, the git, is, after all, quite stupid. + +Suddenly Domo-Kun found itself in a room filled with dazzling mirrors. As +Domo-Kun looked around, it realized that some of the mirrors were actually but +pale reflections of true reality. + +A herd of wild gits appears! Time for CQ :D +And one more for sizes.py... + +What's an overmarketed dietary supplement expressing sadness, relief, +tiredness, or a similar feeling.? Ah-Sigh-ee. + +It was love at first sight. The moment Yossarian first laid eyes on the chaplain, he fell madly in love with him. + +Cool whitespace change for git-cl land + +Oh god the bots are red! I'm blind! Mmmm, cronuts. + +If you stand on your head, you will get footprints in your hair. + +sigh +sigher +pick up cls + +In the BUILD we trust. +^_^ + +In the masters we don't. +In the tryservers, we don't either. +In the CQ sometimes. +Auto-generated by git-eject-upstream (http://goo.gl/cIHsYR) +My sandwiches are like my children: I love them all. +No, really, I couldn't eat another bit. +When I hunger I think of you, and a pastrami sandwich. +Do make a terrible mistake every once in a while. +I just made two. +Mistakes are the best sometimes. +\o/ +This is groovy. + +SECRET ENDING: IT WAS _____ ALL ALONG! +testing trailing line. + +So many books, so little time. + +Hi there, it's my first commit. +!false isn't funny because it's true. + +Lalala + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Vestibulum rhoncus neque sodales nibh lobortis, non fringilla odio aliquet. +Praesent ultrices quam eu pretium ultrices. +Quisque et consequat ex. Curabitur sed nunc neque. +foo diff --git a/third_party/libwebrtc/build/win/BUILD.gn b/third_party/libwebrtc/build/win/BUILD.gn new file mode 100644 index 0000000000..d41e55dcbb --- /dev/null +++ b/third_party/libwebrtc/build/win/BUILD.gn @@ -0,0 +1,177 @@ +# 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("//webrtc.gni") +import("//build/config/clang/clang.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/win/manifest.gni") + +# Depending on this target will cause the manifests for Chrome's default +# Windows and common control compatibility and elevation for executables. +windows_manifest("default_exe_manifest") { + sources = [ + as_invoker_manifest, + common_controls_manifest, + default_compatibility_manifest, + ] +} + +build_with_mozilla = false +if (is_win && !build_with_mozilla) { +# assert(host_os != "mac" || target_cpu != "x86", +# "Windows cross-builds from Mac must be 64-bit.") + + action("copy_cdb_to_output") { + script = "//build/win/copy_cdb_to_output.py" + inputs = [ + script, + "//build/vs_toolchain.py", + ] + outputs = [ + "$root_out_dir/cdb/cdb.exe", + "$root_out_dir/cdb/dbgeng.dll", + "$root_out_dir/cdb/dbghelp.dll", + "$root_out_dir/cdb/dbgmodel.dll", + "$root_out_dir/cdb/winext/ext.dll", + "$root_out_dir/cdb/winext/uext.dll", + "$root_out_dir/cdb/winxp/exts.dll", + "$root_out_dir/cdb/winxp/ntsdexts.dll", + ] + if (target_cpu != "arm64") { + # The UCRT files are not redistributable for ARM64 Win32. + outputs += [ + "$root_out_dir/cdb/api-ms-win-core-console-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-datetime-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-debug-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-errorhandling-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-file-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-file-l1-2-0.dll", + "$root_out_dir/cdb/api-ms-win-core-file-l2-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-handle-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-heap-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-interlocked-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-libraryloader-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-localization-l1-2-0.dll", + "$root_out_dir/cdb/api-ms-win-core-memory-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-namedpipe-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-processenvironment-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-1.dll", + "$root_out_dir/cdb/api-ms-win-core-profile-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-rtlsupport-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-string-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-synch-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-synch-l1-2-0.dll", + "$root_out_dir/cdb/api-ms-win-core-sysinfo-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-timezone-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-core-util-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-conio-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-convert-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-environment-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-filesystem-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-heap-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-locale-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-math-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-multibyte-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-private-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-process-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-runtime-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-stdio-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-string-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-time-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-crt-utility-l1-1-0.dll", + "$root_out_dir/cdb/api-ms-win-downlevel-kernel32-l2-1-0.dll", + "$root_out_dir/cdb/api-ms-win-eventing-provider-l1-1-0.dll", + "$root_out_dir/cdb/ucrtbase.dll", + ] + } + args = [ + rebase_path("$root_out_dir/cdb", root_out_dir), + target_cpu, + ] + } + + group("runtime_libs") { + data = [] + if (is_component_build) { + # Copy the VS runtime DLLs into the isolate so that they don't have to be + # preinstalled on the target machine. The debug runtimes have a "d" at + # the end. + if (is_debug) { + vcrt_suffix = "d" + } else { + vcrt_suffix = "" + } + + # These runtime files are copied to the output directory by the + # vs_toolchain script that runs as part of toolchain configuration. + data += [ + "$root_out_dir/msvcp140${vcrt_suffix}.dll", + "$root_out_dir/vccorlib140${vcrt_suffix}.dll", + "$root_out_dir/vcruntime140${vcrt_suffix}.dll", + ] + if (target_cpu == "x64") { + data += [ "$root_out_dir/vcruntime140_1${vcrt_suffix}.dll" ] + } + if (is_debug) { + data += [ "$root_out_dir/ucrtbased.dll" ] + } + if (is_asan) { + assert(target_cpu == "x64", + "ASan is only supported in 64-bit builds on Windows.") + data += [ "$clang_base_path/lib/clang/$clang_version/lib/windows/clang_rt.asan_dynamic-x86_64.dll" ] + } + if (target_cpu != "arm64") { + data += [ + # Universal Windows 10 CRT files + "$root_out_dir/api-ms-win-core-console-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-datetime-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-debug-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-errorhandling-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-file-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-file-l1-2-0.dll", + "$root_out_dir/api-ms-win-core-file-l2-1-0.dll", + "$root_out_dir/api-ms-win-core-handle-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-heap-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-interlocked-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-libraryloader-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-localization-l1-2-0.dll", + "$root_out_dir/api-ms-win-core-memory-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-namedpipe-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-processenvironment-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-processthreads-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-processthreads-l1-1-1.dll", + "$root_out_dir/api-ms-win-core-profile-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-rtlsupport-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-string-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-synch-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-synch-l1-2-0.dll", + "$root_out_dir/api-ms-win-core-sysinfo-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-timezone-l1-1-0.dll", + "$root_out_dir/api-ms-win-core-util-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-conio-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-convert-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-environment-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-filesystem-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-heap-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-locale-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-math-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-multibyte-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-private-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-process-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-runtime-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-stdio-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-string-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-time-l1-1-0.dll", + "$root_out_dir/api-ms-win-crt-utility-l1-1-0.dll", + "$root_out_dir/api-ms-win-downlevel-kernel32-l2-1-0.dll", + "$root_out_dir/api-ms-win-eventing-provider-l1-1-0.dll", + ] + if (!is_debug) { + data += [ "$root_out_dir/ucrtbase.dll" ] + } + } + } + } +} diff --git a/third_party/libwebrtc/build/win/as_invoker.manifest b/third_party/libwebrtc/build/win/as_invoker.manifest new file mode 100644 index 0000000000..df046fdf68 --- /dev/null +++ b/third_party/libwebrtc/build/win/as_invoker.manifest @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/third_party/libwebrtc/build/win/common_controls.manifest b/third_party/libwebrtc/build/win/common_controls.manifest new file mode 100644 index 0000000000..1710196fce --- /dev/null +++ b/third_party/libwebrtc/build/win/common_controls.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/third_party/libwebrtc/build/win/compatibility.manifest b/third_party/libwebrtc/build/win/compatibility.manifest new file mode 100644 index 0000000000..10d10da382 --- /dev/null +++ b/third_party/libwebrtc/build/win/compatibility.manifest @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/third_party/libwebrtc/build/win/copy_cdb_to_output.py b/third_party/libwebrtc/build/win/copy_cdb_to_output.py new file mode 100755 index 0000000000..a0b99bb776 --- /dev/null +++ b/third_party/libwebrtc/build/win/copy_cdb_to_output.py @@ -0,0 +1,127 @@ +#!/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. + +from __future__ import print_function + +import glob +import hashlib +import os +import shutil +import sys + +script_dir = os.path.dirname(os.path.realpath(__file__)) +src_build_dir = os.path.abspath(os.path.join(script_dir, os.pardir)) +sys.path.insert(0, src_build_dir) + +import vs_toolchain + + +def _HexDigest(file_name): + hasher = hashlib.sha256() + afile = open(file_name, 'rb') + blocksize = 65536 + buf = afile.read(blocksize) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(blocksize) + afile.close() + return hasher.hexdigest() + + +def _CopyImpl(file_name, target_dir, source_dir, verbose=False): + """Copy |source| to |target| if it doesn't already exist or if it + needs to be updated. + """ + target = os.path.join(target_dir, file_name) + source = os.path.join(source_dir, file_name) + if (os.path.isdir(os.path.dirname(target)) and + ((not os.path.isfile(target)) or + _HexDigest(source) != _HexDigest(target))): + if verbose: + print('Copying %s to %s...' % (source, target)) + if os.path.exists(target): + os.unlink(target) + shutil.copy(source, target) + + +def _ConditionalMkdir(output_dir): + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + + +def _CopyCDBToOutput(output_dir, target_arch): + """Copies the Windows debugging executable cdb.exe to the output + directory, which is created if it does not exist. The output + directory, and target architecture that should be copied, are + passed. Supported values for the target architecture are the GYP + values "ia32", "x64", "arm64" and the GN values "x86", "x64", "arm64". + """ + _ConditionalMkdir(output_dir) + vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs() + # If WINDOWSSDKDIR is not set use the default SDK path. This will be the case + # when DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run. + win_sdk_dir = os.path.normpath( + os.environ.get('WINDOWSSDKDIR', + os.path.expandvars('%ProgramFiles(x86)%' + '\\Windows Kits\\10'))) + if target_arch == 'ia32' or target_arch == 'x86': + src_arch = 'x86' + elif target_arch in ['x64', 'arm64']: + src_arch = target_arch + else: + print('copy_cdb_to_output.py: unknown target_arch %s' % target_arch) + sys.exit(1) + # We need to copy multiple files, so cache the computed source directory. + src_dir = os.path.join(win_sdk_dir, 'Debuggers', src_arch) + # We need to copy some helper DLLs to get access to the !uniqstack + # command to dump all threads' stacks. + src_winext_dir = os.path.join(src_dir, 'winext') + dst_winext_dir = os.path.join(output_dir, 'winext') + src_winxp_dir = os.path.join(src_dir, 'winxp') + dst_winxp_dir = os.path.join(output_dir, 'winxp') + # Starting with the 10.0.17763 SDK the ucrt files are in a version-named + # directory - this handles both cases. + redist_dir = os.path.join(win_sdk_dir, 'Redist') + version_dirs = glob.glob(os.path.join(redist_dir, '10.*')) + if len(version_dirs) > 0: + version_dirs.sort(reverse=True) + redist_dir = version_dirs[0] + src_crt_dir = os.path.join(redist_dir, 'ucrt', 'DLLs', src_arch) + _ConditionalMkdir(dst_winext_dir) + _ConditionalMkdir(dst_winxp_dir) + # Note that the outputs from the "copy_cdb_to_output" target need to + # be kept in sync with this list. + _CopyImpl('cdb.exe', output_dir, src_dir) + _CopyImpl('dbgeng.dll', output_dir, src_dir) + _CopyImpl('dbghelp.dll', output_dir, src_dir) + _CopyImpl('dbgmodel.dll', output_dir, src_dir) + _CopyImpl('ext.dll', dst_winext_dir, src_winext_dir) + _CopyImpl('uext.dll', dst_winext_dir, src_winext_dir) + _CopyImpl('exts.dll', dst_winxp_dir, src_winxp_dir) + _CopyImpl('ntsdexts.dll', dst_winxp_dir, src_winxp_dir) + if src_arch in ['x64', 'x86']: + # Copy all UCRT files from the debuggers directory, for compatibility with + # the Windows 10 18362 SDK (one UCRT file) and later versions (two UCRT + # files). The new file is api-ms-win-downlevel-kernel32-l2-1-0.dll and + # should be added to the copy_cdb_to_output outputs when we require a newer + # SDK. + for file in glob.glob(os.path.join(src_dir, 'api-ms-win*.dll')): + _CopyImpl(os.path.split(file)[1], output_dir, src_dir) + _CopyImpl('ucrtbase.dll', output_dir, src_crt_dir) + for dll_path in glob.glob(os.path.join(src_crt_dir, 'api-ms-win-*.dll')): + _CopyImpl(os.path.split(dll_path)[1], output_dir, src_crt_dir) + return 0 + + +def main(): + if len(sys.argv) < 2: + print('Usage: copy_cdb_to_output.py ' + \ + '', file=sys.stderr) + return 1 + return _CopyCDBToOutput(sys.argv[1], sys.argv[2]) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/win/gn_meta_sln.py b/third_party/libwebrtc/build/win/gn_meta_sln.py new file mode 100644 index 0000000000..862d278248 --- /dev/null +++ b/third_party/libwebrtc/build/win/gn_meta_sln.py @@ -0,0 +1,214 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# gn_meta_sln.py +# Helper utility to combine GN-generated Visual Studio projects into +# a single meta-solution. + +from __future__ import print_function + +import os +import glob +import re +import sys +from shutil import copyfile + +# Helpers +def EnsureExists(path): + try: + os.makedirs(path) + except OSError: + pass + +def WriteLinesToFile(lines, file_name): + EnsureExists(os.path.dirname(file_name)) + with open(file_name, "w") as f: + f.writelines(lines) + +def ExtractIdg(proj_file_name): + result = [] + with open(proj_file_name) as proj_file: + lines = iter(proj_file) + for p_line in lines: + if " [ (config, pathToProject, GUID, arch), ... ] +all_projects = {} +project_pattern = (r'Project\("\{' + cpp_type_guid + + r'\}"\) = "([^"]*)", "([^"]*)", "\{([^\}]*)\}"') + +# We need something to work with. Typically, this will fail if no GN folders +# have IDE files +if len(configs) == 0: + print("ERROR: At least one GN directory must have been built with --ide=vs") + sys.exit() + +# Filter out configs which don't match the name and vs version of the first. +name = configs[0][1] +vs_version = configs[0][2] + +for config in configs: + if config[1] != name or config[2] != vs_version: + continue + + sln_lines = iter(open(os.path.join("out", config[0], config[1]))) + for sln_line in sln_lines: + match_obj = re.match(project_pattern, sln_line) + if match_obj: + proj_name = match_obj.group(1) + if proj_name not in all_projects: + all_projects[proj_name] = [] + all_projects[proj_name].append((config[0], match_obj.group(2), + match_obj.group(3))) + +# We need something to work with. Typically, this will fail if no GN folders +# have IDE files +if len(all_projects) == 0: + print("ERROR: At least one GN directory must have been built with --ide=vs") + sys.exit() + +# Create a new solution. We arbitrarily use the first config as the GUID source +# (but we need to match that behavior later, when we copy/generate the project +# files). +new_sln_lines = [] +new_sln_lines.append( + 'Microsoft Visual Studio Solution File, Format Version 12.00\n') +new_sln_lines.append('# Visual Studio ' + vs_version + '\n') +for proj_name, proj_configs in all_projects.items(): + new_sln_lines.append('Project("{' + cpp_type_guid + '}") = "' + proj_name + + '", "' + proj_configs[0][1] + '", "{' + + proj_configs[0][2] + '}"\n') + new_sln_lines.append('EndProject\n') + +new_sln_lines.append('Global\n') +new_sln_lines.append( + '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') +for config in configs: + match = config[0] + '|' + hard_coded_arch + new_sln_lines.append('\t\t' + match + ' = ' + match + '\n') +new_sln_lines.append('\tEndGlobalSection\n') +new_sln_lines.append( + '\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') +for proj_name, proj_configs in all_projects.items(): + proj_guid = proj_configs[0][2] + for config in configs: + match = config[0] + '|' + hard_coded_arch + new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + + '.ActiveCfg = ' + match + '\n') + new_sln_lines.append('\t\t{' + proj_guid + '}.' + match + + '.Build.0 = ' + match + '\n') +new_sln_lines.append('\tEndGlobalSection\n') +new_sln_lines.append('\tGlobalSection(SolutionProperties) = preSolution\n') +new_sln_lines.append('\t\tHideSolutionNode = FALSE\n') +new_sln_lines.append('\tEndGlobalSection\n') +new_sln_lines.append('\tGlobalSection(NestedProjects) = preSolution\n') +new_sln_lines.append('\tEndGlobalSection\n') +new_sln_lines.append('EndGlobal\n') + +# Write solution file +WriteLinesToFile(new_sln_lines, 'out/sln/' + name) + +idg_hdr = " + {config} + {arch} + +""" + +def FormatProjectConfig(config): + return configuration_template.format( + config = config[0], arch = hard_coded_arch) + +# Now, bring over the project files +for proj_name, proj_configs in all_projects.items(): + # Paths to project and filter file in src and dst locations + src_proj_path = os.path.join("out", proj_configs[0][0], proj_configs[0][1]) + dst_proj_path = os.path.join("out", "sln", proj_configs[0][1]) + src_filter_path = src_proj_path + ".filters" + dst_filter_path = dst_proj_path + ".filters" + + # Copy the filter file unmodified + EnsureExists(os.path.dirname(dst_proj_path)) + copyfile(src_filter_path, dst_filter_path) + + preferred_tool_arch = None + config_arch = {} + + # Bring over the project file, modified with extra configs + with open(src_proj_path) as src_proj_file: + proj_lines = iter(src_proj_file) + new_proj_lines = [] + for line in proj_lines: + if "\n") + for idg_line in config_idg_lines[1:]: + new_proj_lines.append(idg_line) + elif "ProjectConfigurations" in line: + new_proj_lines.append(line) + proj_lines.next() + proj_lines.next() + proj_lines.next() + proj_lines.next() + for config in configs: + new_proj_lines.append(FormatProjectConfig(config)) + + elif "" + + hard_coded_arch + + "\n") + else: + new_proj_lines.append(line) + with open(dst_proj_path, "w") as new_proj: + new_proj.writelines(new_proj_lines) + +print('Wrote meta solution to out/sln/' + name) diff --git a/third_party/libwebrtc/build/win/message_compiler.gni b/third_party/libwebrtc/build/win/message_compiler.gni new file mode 100644 index 0000000000..21f4ec8b11 --- /dev/null +++ b/third_party/libwebrtc/build/win/message_compiler.gni @@ -0,0 +1,87 @@ +# 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. + +assert(is_win, "This only runs on Windows.") + +# Runs mc.exe over a list of sources. The outputs (a header and rc file) are +# placed in the target gen dir, and compiled. +# +# sources +# List of message files to process. +# +# user_mode_logging (optional bool) +# Generates user-mode logging code. Defaults to false (no logging code). +# +# compile_generated_code (optional, deafults = true) +# If unset or true, the generated code will be compiled and linked into +# targets that depend on it. If set to false, the .h and .rc files will only +# be generated. +# +# deps, public_deps, visibility +# Normal meaning. +template("message_compiler") { + if (defined(invoker.compile_generated_code) && + !invoker.compile_generated_code) { + compile_generated_code = false + action_name = target_name + } else { + compile_generated_code = true + action_name = "${target_name}_mc" + source_set_name = target_name + } + + action_foreach(action_name) { + if (compile_generated_code) { + visibility = [ ":$source_set_name" ] + } else { + forward_variables_from(invoker, [ "visibility" ]) + } + + script = "//build/win/message_compiler.py" + + outputs = [ + "$target_gen_dir/{{source_name_part}}.h", + "$target_gen_dir/{{source_name_part}}.rc", + ] + + args = [ + # The first argument is the environment file saved to the build + # directory. This is required because the Windows toolchain setup saves + # the VC paths and such so that running "mc.exe" will work with the + # configured toolchain. This file is in the root build dir. + "environment.$target_cpu", + + # Where to put the header. + "-h", + rebase_path(target_gen_dir, root_build_dir), + + # Where to put the .rc file. + "-r", + rebase_path(target_gen_dir, root_build_dir), + + # Input is Unicode. + "-u", + ] + if (defined(invoker.user_mode_logging) && invoker.user_mode_logging) { + args += [ "-um" ] + } + args += [ "{{source}}" ] + + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "sources", + ]) + } + + if (compile_generated_code) { + # Compile the generated rc file. + source_set(source_set_name) { + forward_variables_from(invoker, [ "visibility" ]) + sources = get_target_outputs(":$action_name") + deps = [ ":$action_name" ] + } + } +} diff --git a/third_party/libwebrtc/build/win/message_compiler.py b/third_party/libwebrtc/build/win/message_compiler.py new file mode 100644 index 0000000000..51de52f0fc --- /dev/null +++ b/third_party/libwebrtc/build/win/message_compiler.py @@ -0,0 +1,148 @@ +# 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. + +# Runs the Microsoft Message Compiler (mc.exe). +# +# Usage: message_compiler.py [*] + +from __future__ import print_function + +import difflib +import distutils.dir_util +import filecmp +import os +import re +import shutil +import subprocess +import sys +import tempfile + +def main(): + env_file, rest = sys.argv[1], sys.argv[2:] + + # Parse some argument flags. + header_dir = None + resource_dir = None + input_file = None + for i, arg in enumerate(rest): + if arg == '-h' and len(rest) > i + 1: + assert header_dir == None + header_dir = rest[i + 1] + elif arg == '-r' and len(rest) > i + 1: + assert resource_dir == None + resource_dir = rest[i + 1] + elif arg.endswith('.mc') or arg.endswith('.man'): + assert input_file == None + input_file = arg + + # Copy checked-in outputs to final location. + THIS_DIR = os.path.abspath(os.path.dirname(__file__)) + assert header_dir == resource_dir + source = os.path.join(THIS_DIR, "..", "..", + "third_party", "win_build_output", + re.sub(r'^(?:[^/]+/)?gen/', 'mc/', header_dir)) + distutils.dir_util.copy_tree(source, header_dir, preserve_times=False) + + # On non-Windows, that's all we can do. + if sys.platform != 'win32': + return + + # On Windows, run mc.exe on the input and check that its outputs are + # identical to the checked-in outputs. + + # Read the environment block from the file. This is stored in the format used + # by CreateProcess. Drop last 2 NULs, one for list terminator, one for + # trailing vs. separator. + env_pairs = open(env_file).read()[:-2].split('\0') + env_dict = dict([item.split('=', 1) for item in env_pairs]) + + extension = os.path.splitext(input_file)[1] + if extension in ['.man', '.mc']: + # For .man files, mc's output changed significantly from Version 10.0.15063 + # to Version 10.0.16299. We should always have the output of the current + # default SDK checked in and compare to that. Early out if a different SDK + # is active. This also happens with .mc files. + # TODO(thakis): Check in new baselines and compare to 16299 instead once + # we use the 2017 Fall Creator's Update by default. + mc_help = subprocess.check_output(['mc.exe', '/?'], env=env_dict, + stderr=subprocess.STDOUT, shell=True) + version = re.search(br'Message Compiler\s+Version (\S+)', mc_help).group(1) + if version != '10.0.15063': + return + + # mc writes to stderr, so this explicitly redirects to stdout and eats it. + try: + tmp_dir = tempfile.mkdtemp() + delete_tmp_dir = True + if header_dir: + rest[rest.index('-h') + 1] = tmp_dir + header_dir = tmp_dir + if resource_dir: + rest[rest.index('-r') + 1] = tmp_dir + resource_dir = tmp_dir + + # This needs shell=True to search the path in env_dict for the mc + # executable. + subprocess.check_output(['mc.exe'] + rest, + env=env_dict, + stderr=subprocess.STDOUT, + shell=True) + # We require all source code (in particular, the header generated here) to + # be UTF-8. jinja can output the intermediate .mc file in UTF-8 or UTF-16LE. + # However, mc.exe only supports Unicode via the -u flag, and it assumes when + # that is specified that the input is UTF-16LE (and errors out on UTF-8 + # files, assuming they're ANSI). Even with -u specified and UTF16-LE input, + # it generates an ANSI header, and includes broken versions of the message + # text in the comment before the value. To work around this, for any invalid + # // comment lines, we simply drop the line in the header after building it. + # Also, mc.exe apparently doesn't always write #define lines in + # deterministic order, so manually sort each block of #defines. + if header_dir: + header_file = os.path.join( + header_dir, os.path.splitext(os.path.basename(input_file))[0] + '.h') + header_contents = [] + with open(header_file, 'rb') as f: + define_block = [] # The current contiguous block of #defines. + for line in f.readlines(): + if line.startswith('//') and '?' in line: + continue + if line.startswith('#define '): + define_block.append(line) + continue + # On the first non-#define line, emit the sorted preceding #define + # block. + header_contents += sorted(define_block, key=lambda s: s.split()[-1]) + define_block = [] + header_contents.append(line) + # If the .h file ends with a #define block, flush the final block. + header_contents += sorted(define_block, key=lambda s: s.split()[-1]) + with open(header_file, 'wb') as f: + f.write(''.join(header_contents)) + + # mc.exe invocation and post-processing are complete, now compare the output + # in tmp_dir to the checked-in outputs. + diff = filecmp.dircmp(tmp_dir, source) + if diff.diff_files or set(diff.left_list) != set(diff.right_list): + print('mc.exe output different from files in %s, see %s' % (source, + tmp_dir)) + diff.report() + for f in diff.diff_files: + if f.endswith('.bin'): continue + fromfile = os.path.join(source, f) + tofile = os.path.join(tmp_dir, f) + print(''.join( + difflib.unified_diff( + open(fromfile, 'U').readlines(), + open(tofile, 'U').readlines(), fromfile, tofile))) + delete_tmp_dir = False + sys.exit(1) + except subprocess.CalledProcessError as e: + print(e.output) + sys.exit(e.returncode) + finally: + if os.path.exists(tmp_dir) and delete_tmp_dir: + shutil.rmtree(tmp_dir) + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/win/reorder-imports.py b/third_party/libwebrtc/build/win/reorder-imports.py new file mode 100755 index 0000000000..0995ae0058 --- /dev/null +++ b/third_party/libwebrtc/build/win/reorder-imports.py @@ -0,0 +1,98 @@ +#!/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 glob +import optparse +import os +import shutil +import subprocess +import sys + +sys.path.insert( + 0, + os.path.join(os.path.dirname(__file__), '..', '..', 'third_party', + 'pefile_py3')) +import pefile + +def reorder_imports(input_dir, output_dir, architecture): + """Swap chrome_elf.dll to be the first import of chrome.exe. + Also copy over any related files that might be needed + (pdbs, manifests etc.). + """ + # TODO(thakis): See if there is a reliable way to write the + # correct executable in the first place, so that this script + # only needs to verify that and not write a whole new exe. + + input_image = os.path.join(input_dir, 'chrome.exe') + output_image = os.path.join(output_dir, 'chrome.exe') + + # pefile mmap()s the whole executable, and then parses parts of + # it into python data structures for ease of processing. + # To write the file again, only the mmap'd data is written back, + # so modifying the parsed python objects generally has no effect. + # However, parsed raw data ends up in pe.Structure instances, + # and these all get serialized back when the file gets written. + # So things that are in a Structure must have their data set + # through the Structure, while other data must bet set through + # the set_bytes_*() methods. + pe = pefile.PE(input_image, fast_load=True) + if architecture == 'x64' or architecture == 'arm64': + assert pe.PE_TYPE == pefile.OPTIONAL_HEADER_MAGIC_PE_PLUS + else: + assert pe.PE_TYPE == pefile.OPTIONAL_HEADER_MAGIC_PE + + pe.parse_data_directories(directories=[ + pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']]) + + found_elf = False + for i, peimport in enumerate(pe.DIRECTORY_ENTRY_IMPORT): + if peimport.dll.lower() == b'chrome_elf.dll': + assert not found_elf, 'only one chrome_elf.dll import expected' + found_elf = True + if i > 0: + swap = pe.DIRECTORY_ENTRY_IMPORT[0] + + # Morally we want to swap peimport.struct and swap.struct here, + # but the pe module doesn't expose a public method on Structure + # to get all data of a Structure without explicitly listing all + # field names. + # NB: OriginalFirstThunk and Characteristics are an union both at + # offset 0, handling just one of them is enough. + peimport.struct.OriginalFirstThunk, swap.struct.OriginalFirstThunk = \ + swap.struct.OriginalFirstThunk, peimport.struct.OriginalFirstThunk + peimport.struct.TimeDateStamp, swap.struct.TimeDateStamp = \ + swap.struct.TimeDateStamp, peimport.struct.TimeDateStamp + peimport.struct.ForwarderChain, swap.struct.ForwarderChain = \ + swap.struct.ForwarderChain, peimport.struct.ForwarderChain + peimport.struct.Name, swap.struct.Name = \ + swap.struct.Name, peimport.struct.Name + peimport.struct.FirstThunk, swap.struct.FirstThunk = \ + swap.struct.FirstThunk, peimport.struct.FirstThunk + assert found_elf, 'chrome_elf.dll import not found' + + pe.write(filename=output_image) + + for fname in glob.iglob(os.path.join(input_dir, 'chrome.exe.*')): + shutil.copy(fname, os.path.join(output_dir, os.path.basename(fname))) + return 0 + + +def main(argv): + usage = 'reorder_imports.py -i -o -a ' + parser = optparse.OptionParser(usage=usage) + parser.add_option('-i', '--input', help='reorder chrome.exe in DIR', + metavar='DIR') + parser.add_option('-o', '--output', help='write new chrome.exe to DIR', + metavar='DIR') + parser.add_option('-a', '--arch', help='architecture of build (optional)', + default='ia32') + opts, args = parser.parse_args() + + if not opts.input or not opts.output: + parser.error('Please provide and input and output directory') + return reorder_imports(opts.input, opts.output, opts.arch) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/third_party/libwebrtc/build/win/require_administrator.manifest b/third_party/libwebrtc/build/win/require_administrator.manifest new file mode 100644 index 0000000000..4142e73342 --- /dev/null +++ b/third_party/libwebrtc/build/win/require_administrator.manifest @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/third_party/libwebrtc/build/win/segment_heap.manifest b/third_party/libwebrtc/build/win/segment_heap.manifest new file mode 100644 index 0000000000..fc930a4d56 --- /dev/null +++ b/third_party/libwebrtc/build/win/segment_heap.manifest @@ -0,0 +1,8 @@ + + + + + SegmentHeap + + + diff --git a/third_party/libwebrtc/build/win/set_appcontainer_acls.py b/third_party/libwebrtc/build/win/set_appcontainer_acls.py new file mode 100755 index 0000000000..4a4e616cf2 --- /dev/null +++ b/third_party/libwebrtc/build/win/set_appcontainer_acls.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright 2021 The Chromium 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 the app container ACLs on directory.""" + +import os +import argparse +import sys + +SRC_DIR = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +sys.path.append(os.path.join(SRC_DIR, 'testing', 'scripts')) + +import common + + +def main(): + parser = argparse.ArgumentParser( + description='Sets App Container ACL on a directory.') + parser.add_argument('--stamp', + required=False, + help='Touch this stamp file on success.') + parser.add_argument('--dir', required=True, help='Set ACL on this directory.') + # parser.add_argument('--fail', required=True, help='Argument to fail.') + args = parser.parse_args() + + common.set_lpac_acls(os.path.abspath(args.dir)) + if args.stamp: + open(args.stamp, 'w').close() # Update mtime on stamp file. + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/win/use_ansi_codes.py b/third_party/libwebrtc/build/win/use_ansi_codes.py new file mode 100755 index 0000000000..5951c2ab4d --- /dev/null +++ b/third_party/libwebrtc/build/win/use_ansi_codes.py @@ -0,0 +1,12 @@ +#!/usr/bin/env 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. +"""Prints if the the terminal is likely to understand ANSI codes.""" + +from __future__ import print_function + +import os + +# Add more terminals here as needed. +print('ANSICON' in os.environ) diff --git a/third_party/libwebrtc/build/write_build_date_header.py b/third_party/libwebrtc/build/write_build_date_header.py new file mode 100755 index 0000000000..77388288b3 --- /dev/null +++ b/third_party/libwebrtc/build/write_build_date_header.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# Copyright (c) 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. +"""Takes a timestamp and writes it in as readable text to a .h file.""" + +import argparse +import datetime +import os +import sys + + +def main(): + argument_parser = argparse.ArgumentParser() + argument_parser.add_argument('output_file', help='The file to write to') + argument_parser.add_argument('timestamp') + args = argument_parser.parse_args() + + date = datetime.datetime.utcfromtimestamp(int(args.timestamp)) + output = ('// Generated by //build/write_build_date_header.py\n' + '#ifndef BUILD_DATE\n' + '#define BUILD_DATE "{:%b %d %Y %H:%M:%S}"\n' + '#endif // BUILD_DATE\n'.format(date)) + + current_contents = '' + if os.path.isfile(args.output_file): + with open(args.output_file, 'r') as current_file: + current_contents = current_file.read() + + if current_contents != output: + with open(args.output_file, 'w') as output_file: + output_file.write(output) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/write_buildflag_header.py b/third_party/libwebrtc/build/write_buildflag_header.py new file mode 100755 index 0000000000..47b9a03265 --- /dev/null +++ b/third_party/libwebrtc/build/write_buildflag_header.py @@ -0,0 +1,103 @@ +#!/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 writes headers for build flags. See buildflag_header.gni for usage of +# this system as a whole. +# +# The parameters are passed in a response file so we don't have to worry +# about command line lengths. The name of the response file is passed on the +# command line. +# +# The format of the response file is: +# [--flags ] + +import optparse +import os +import shlex +import sys + + +class Options: + def __init__(self, output, rulename, header_guard, flags): + self.output = output + self.rulename = rulename + self.header_guard = header_guard + self.flags = flags + + +def GetOptions(): + parser = optparse.OptionParser() + parser.add_option('--output', help="Output header name inside --gen-dir.") + parser.add_option('--rulename', + help="Helpful name of build rule for including in the " + + "comment at the top of the file.") + parser.add_option('--gen-dir', + help="Path to root of generated file directory tree.") + parser.add_option('--definitions', + help="Name of the response file containing the flags.") + cmdline_options, cmdline_flags = parser.parse_args() + + # Compute header guard by replacing some chars with _ and upper-casing. + header_guard = cmdline_options.output.upper() + header_guard = \ + header_guard.replace('/', '_').replace('\\', '_').replace('.', '_') + header_guard += '_' + + # The actual output file is inside the gen dir. + output = os.path.join(cmdline_options.gen_dir, cmdline_options.output) + + # Definition file in GYP is newline separated, in GN they are shell formatted. + # shlex can parse both of these. + with open(cmdline_options.definitions, 'r') as def_file: + defs = shlex.split(def_file.read()) + flags_index = defs.index('--flags') + + # Everything after --flags are flags. true/false are remapped to 1/0, + # everything else is passed through. + flags = [] + for flag in defs[flags_index + 1 :]: + equals_index = flag.index('=') + key = flag[:equals_index] + value = flag[equals_index + 1:] + + # Canonicalize and validate the value. + if value == 'true': + value = '1' + elif value == 'false': + value = '0' + flags.append((key, str(value))) + + return Options(output=output, + rulename=cmdline_options.rulename, + header_guard=header_guard, + flags=flags) + + +def WriteHeader(options): + with open(options.output, 'w') as output_file: + output_file.write("// Generated by build/write_buildflag_header.py\n") + if options.rulename: + output_file.write('// From "' + options.rulename + '"\n') + + output_file.write('\n#ifndef %s\n' % options.header_guard) + output_file.write('#define %s\n\n' % options.header_guard) + output_file.write('#include "build/buildflag.h"\n\n') + + for pair in options.flags: + output_file.write('#define BUILDFLAG_INTERNAL_%s() (%s)\n' % pair) + + output_file.write('\n#endif // %s\n' % options.header_guard) + + +if os.name == 'nt': + major, minor, build, platform, service_pack = sys.getwindowsversion() + # Windows 10 will be 6.2 on Python 2 and 10.0 on Python 3. This check + # handles both. + if major < 6 or (major == 6 and minor < 2): + raise Exception( + 'Unsupported OS. Building Chromium requires Windows 10. %s detected.' % + str(sys.getwindowsversion())) +options = GetOptions() +WriteHeader(options) diff --git a/third_party/libwebrtc/build/xcode_binaries.yaml b/third_party/libwebrtc/build/xcode_binaries.yaml new file mode 100644 index 0000000000..779b3ac15f --- /dev/null +++ b/third_party/libwebrtc/build/xcode_binaries.yaml @@ -0,0 +1,57 @@ +# This yaml file is used to package binaries from Xcode.app. +# To use this: +# 1) Move Xcode.app to the same directory as this file, +# and rename Xcode.app to xcode_binaries. Or make a symlink: +# $ rm -rf build/xcode_binaries && \ +# ln -s /Applications/Xcode.app build/xcode_binaries +# 2) Call `cipd create --pkg-def build/xcode_binaries.yaml` +# To deploy the newly created cipd package across the fleet, modify +# mac_toolchain.py to point to the new cipd hash. +# +# Note that runhooks extracts the cipd file to build/mac/xcode_binaries -- your +# build/xcode_binaries you're creating in step 1 above isn't used as part of +# the Chromium build, build/mac_files/xcode_binaries is. So you need to +# `runhooks` after updating the hash in mac_toolchain.py like everyone else to +# get the new bits for your local build. +# +# The ACLs for this package are determined by the directory structure. The +# nomenclature mirrors that of the hermetic toolchain to avoid ACL duplication. +package: infra_internal/ios/xcode/xcode_binaries/mac-amd64 +description: A hermetic deployment of all Xcode binaries used to build Chromium. +root: "xcode_binaries" +data: + - dir: Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ + - dir: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/bison + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/bison + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/gm4 + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/gperf + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-nm + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-objdump + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-otool + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-size + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/mig + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool-classic + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size-classic + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libLTO.dylib + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libcodedirectory.dylib + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libtapi.dylib + - file: Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/libexec/migcom + - file: Contents/Developer/usr/bin/atos + - file: Contents/Resources/English.lproj/License.rtf + - file: Contents/Resources/LicenseInfo.plist + - dir: Contents/SharedFrameworks/CoreSymbolicationDT.framework + - dir: Contents/SharedFrameworks/DebugSymbolsDT.framework + - dir: Contents/SharedFrameworks/MallocStackLoggingDT.framework + - dir: Contents/SharedFrameworks/SymbolicationDT.framework + - file: Contents/version.plist +# llvm-size and size are run on swarming, those are symlinked to out of +# isolated tree and produce invdalid symlink if we don't use copy mode here. +install_mode: copy -- cgit v1.2.3