diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 11:40:17 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-24 12:42:11 +0000 |
commit | 5d87695f37678f96492b258bbab36486c59866b4 (patch) | |
tree | be9783bbaf04fb930c4d74ca9c00b5e7954c8bc6 /chromium/build/util | |
parent | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (diff) | |
download | qtwebengine-chromium-5d87695f37678f96492b258bbab36486c59866b4.tar.gz |
BASELINE: Update Chromium to 75.0.3770.56
Change-Id: I86d2007fd27a45d5797eee06f4c9369b8b50ac4f
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/build/util')
-rw-r--r-- | chromium/build/util/LASTCHANGE | 2 | ||||
-rw-r--r-- | chromium/build/util/LASTCHANGE.committime | 2 | ||||
-rw-r--r-- | chromium/build/util/PRESUBMIT.py | 58 | ||||
-rw-r--r-- | chromium/build/util/android_chrome_version.py | 167 | ||||
-rw-r--r-- | chromium/build/util/android_chrome_version_test.py | 293 | ||||
-rw-r--r-- | chromium/build/util/generate_wrapper.gni | 98 | ||||
-rwxr-xr-x | chromium/build/util/generate_wrapper.py | 136 | ||||
-rwxr-xr-x | chromium/build/util/lastchange.py | 201 | ||||
-rw-r--r-- | chromium/build/util/version.gni | 123 | ||||
-rwxr-xr-x | chromium/build/util/version.py | 157 | ||||
-rw-r--r-- | chromium/build/util/version_test.py | 174 |
11 files changed, 1295 insertions, 116 deletions
diff --git a/chromium/build/util/LASTCHANGE b/chromium/build/util/LASTCHANGE index d553b21db54..803868098e1 100644 --- a/chromium/build/util/LASTCHANGE +++ b/chromium/build/util/LASTCHANGE @@ -1 +1 @@ -LASTCHANGE=7e9e689503f506e8e943b05c4f50c0c3e6597794-refs/branch-heads/3729@{#1001} +LASTCHANGE=75fcf24dd8773d5959a7177f492969e627d83c01-refs/branch-heads/3770@{#819} diff --git a/chromium/build/util/LASTCHANGE.committime b/chromium/build/util/LASTCHANGE.committime index 7d9b0e21a75..f3a9396894b 100644 --- a/chromium/build/util/LASTCHANGE.committime +++ b/chromium/build/util/LASTCHANGE.committime @@ -1 +1 @@ -1557879296
\ No newline at end of file +1558658817
\ No newline at end of file diff --git a/chromium/build/util/PRESUBMIT.py b/chromium/build/util/PRESUBMIT.py new file mode 100644 index 00000000000..271afbbb625 --- /dev/null +++ b/chromium/build/util/PRESUBMIT.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. + +import re +"""Presubmit for build/util""" + + +def _GetBlacklist(input_api): + blacklist = [] + 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: + blacklist.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: + blacklist.append('android_chrome_version_test\\.py$') + + return blacklist + + +def _GetPythonUnitTests(input_api, output_api): + # No need to test if files are unchanged + blacklist = _GetBlacklist(input_api) + + return input_api.canned_checks.GetUnitTestsRecursively( + input_api, + output_api, + input_api.PresubmitLocalPath(), + whitelist=['.*_test\\.py$'], + blacklist=blacklist) + + +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/chromium/build/util/android_chrome_version.py b/chromium/build/util/android_chrome_version.py new file mode 100644 index 00000000000..eb58b2f97e9 --- /dev/null +++ b/chromium/build/util/android_chrome_version.py @@ -0,0 +1,167 @@ +# 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. +Reason: for targets that have the same package name (e.g. chrome, chome +modern, monochrome, trichrome), Play Store considers them the same app +and will push the supported app with the highest version code to devices. +(note Play Store does not support hosting two different apps with same +version code and package name) + +Each key in this dict represents a unique version code that will be used for +one or more android chrome 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 whitelisted 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 final digit of '3' for webview is reserved for Trichrome Webview. +The same versionCode is used for both Trichrome Chrome and Trichrome Webview. +""" +ANDROID_CHROME_APK_VERSION_CODE_DIFFS = { + 'CHROME': 0, + 'CHROME_MODERN': 1, + 'MONOCHROME': 2, + 'TRICHROME': 3, + 'NOTOUCH_CHROME': 4, + 'WEBVIEW_STABLE': 0, + 'WEBVIEW_BETA': 1, + 'WEBVIEW_DEV': 2, +} + +"""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. +""" +ARCH_VERSION_CODE_DIFF = { + 'arm': 0, + 'x86': 10, + 'mipsel': 20, + 'arm64': 30, + 'x64': 60 +} +ARCH_CHOICES = ARCH_VERSION_CODE_DIFF.keys() + +""" "Next" builds get +5 last version code digit. + +We choose 5 because it won't conflict with values in +ANDROID_CHROME_APK_VERSION_CODE_DIFFS +""" +NEXT_BUILD_VERSION_CODE_DIFF = 5 + +"""For 64-bit architectures, some packages have multiple targets with version +codes that differ by the second-to-last digit (the architecture digit). This is +for various combinations of 32-bit vs 64-bit chrome and webview. The +default/traditional configuration is 32-bit chrome with 64-bit webview, but we +are adding: ++ 64-bit chrome with 32-bit webview ++ 64-bit combined Chrome and Webview (only one library) ++ (maybe someday 32-bit chrome with 32-bit webview) + +The naming scheme followed here is <chrome>_<webview>, +e.g. 64_32 is 64-bit chrome with 32-bit webview. +""" +ARCH64_APK_VARIANTS = { + '64_32': { + 'PACKAGES': frozenset(['MONOCHROME', 'TRICHROME']), + 'MODIFIER': 10 + }, + '64': { + 'PACKAGES': frozenset(['MONOCHROME', 'TRICHROME']), + 'MODIFIER': 20 + } +} + + +def GenerateVersionCodes(version_values, arch, is_next_build): + """Get dict of version codes for chrome-for-android-related targets + + e.g. + { + 'CHROME_VERSION_CODE': '378100010', + 'MONOCHROME_VERSION_CODE': '378100013', + ... + } + + versionCode values are built like this: + {full BUILD int}{3 digits for PATCH}{1 digit for architecture}{final digit}. + + 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 = '%s%03d00' % (version_values['BUILD'], + int(version_values['PATCH'])) + new_version_code = int(base_version_code) + + new_version_code += ARCH_VERSION_CODE_DIFF[arch] + if is_next_build: + new_version_code += NEXT_BUILD_VERSION_CODE_DIFF + + version_codes = {} + for apk, diff in ANDROID_CHROME_APK_VERSION_CODE_DIFFS.iteritems(): + version_code_name = apk + '_VERSION_CODE' + version_code_val = new_version_code + diff + version_codes[version_code_name] = str(version_code_val) + + if arch == 'arm64' or arch == 'x64': + for variant, config in ARCH64_APK_VARIANTS.iteritems(): + if apk in config['PACKAGES']: + variant_name = apk + '_' + variant + '_VERSION_CODE' + variant_val = version_code_val + config['MODIFIER'] + version_codes[variant_name] = str(variant_val) + + + return version_codes diff --git a/chromium/build/util/android_chrome_version_test.py b/chromium/build/util/android_chrome_version_test.py new file mode 100644 index 00000000000..529ccd33aa1 --- /dev/null +++ b/chromium/build/util/android_chrome_version_test.py @@ -0,0 +1,293 @@ +# 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, '372000001') + + 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, '372000002') + + 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, '372000003') + + def testGenerateVersionCodesAndroidNoTouch(self): + """Assert it gives correct values for standard/example inputs""" + output = GenerateVersionCodes( + self.EXAMPLE_VERSION_VALUES, arch='arm', is_next_build=False) + + notouch_chrome_version_code = output['NOTOUCH_CHROME_VERSION_CODE'] + + self.assertEqual(notouch_chrome_version_code, '372000004') + + 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, '372000001') + + 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, '372000002') + + 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, '372000005') + self.assertEqual(monochrome_version_code, '372000007') + self.assertEqual(webview_stable_version_code, '372000005') + self.assertEqual(webview_beta_version_code, '372000006') + + def testGenerateVersionCodesAndroidArchArm(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.ARCH_VERSION_CODE_DIFF 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.ARCH_VERSION_CODE_DIFF 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, '372000010') + + 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.ARCH_VERSION_CODE_DIFF 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, '372000020') + + 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.ARCH_VERSION_CODE_DIFF 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, '372000030') + + 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_64_32_version_code = output['MONOCHROME_64_32_VERSION_CODE'] + arch_monochrome_64_version_code = output['MONOCHROME_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_64_32_version_code, '372000042') + self.assertEqual(arch_monochrome_64_version_code, '372000052') + self.assertEqual(arch_trichrome_64_32_version_code, '372000043') + self.assertEqual(arch_trichrome_64_version_code, '372000053') + + 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.ARCH_VERSION_CODE_DIFF 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, '372000060') + + 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_64_32_version_code = output['MONOCHROME_64_32_VERSION_CODE'] + arch_monochrome_64_version_code = output['MONOCHROME_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_64_32_version_code, '372000072') + self.assertEqual(arch_monochrome_64_version_code, '372000082') + self.assertEqual(arch_trichrome_64_32_version_code, '372000073') + self.assertEqual(arch_trichrome_64_version_code, '372000083') + + 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.ARCH_VERSION_CODE_DIFF 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.ARCH_VERSION_CODE_DIFF 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/chromium/build/util/generate_wrapper.gni b/chromium/build/util/generate_wrapper.gni new file mode 100644 index 00000000000..74d94330da0 --- /dev/null +++ b/chromium/build/util/generate_wrapper.gni @@ -0,0 +1,98 @@ +# 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. +# +# 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, + [ + "data", + "data_deps", + "deps", + "sources", + "testonly", + ]) + 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, + "--", + ] + args += _wrapped_arguments + } +} diff --git a/chromium/build/util/generate_wrapper.py b/chromium/build/util/generate_wrapper.py new file mode 100755 index 00000000000..5373e1ea2e0 --- /dev/null +++ b/chromium/build/util/generate_wrapper.py @@ -0,0 +1,136 @@ +#!/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: + return os.path.join( + os.path.relpath(_SCRIPT_DIR), _PATH_TO_OUTPUT_DIR, m.group(1)) + return arg + + + def ExpandWrappedPaths(args): + for i, arg in enumerate(args): + args[i] = ExpandWrappedPath(arg) + return args + + + def main(raw_args): + executable_path = ExpandWrappedPath('{executable_path}') + executable_args = ExpandWrappedPaths({executable_args}) + + return subprocess.call([executable_path] + executable_args + raw_args) + + + 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)) + os.chmod(args.wrapper_script, 0750) + + 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 warpper script will be written.') + 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/chromium/build/util/lastchange.py b/chromium/build/util/lastchange.py index 81c74312366..6d704b7afa4 100755 --- a/chromium/build/util/lastchange.py +++ b/chromium/build/util/lastchange.py @@ -6,21 +6,25 @@ """ lastchange.py -- Chromium revision fetching utility. """ +from __future__ import print_function -import re -import logging import argparse +import collections +import logging import os import subprocess import sys -class VersionInfo(object): - def __init__(self, revision_id, full_revision_string, timestamp): - self.revision_id = revision_id - self.revision = full_revision_string - self.timestamp = timestamp +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. @@ -49,53 +53,95 @@ def RunGitCommand(directory, command): return None -def FetchGitRevision(directory, git_log_filter): +def _RunGitCommand(directory, command): + """Launches git subcommand. + + Returns: + The stripped stdout of the git command. + Raises: + GitError on failure, including a nonzero return code. """ - Fetch the Git hash (and Cr-Commit-Position if any) for a given directory. + 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 = 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)) - Errors are swallowed. - Args: - git_log_filter: a string to be used for filtering git log result. +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: - A VersionInfo object or None on error. + The git commit SHA of the merge-base as a string. """ - hsh = '' - git_args = ['log', '-1', '--format=%H %ct'] - if git_log_filter is not None: - git_args.append('--grep=' + git_log_filter) - proc = RunGitCommand(directory, git_args) - if proc: - output = proc.communicate()[0].strip() - if proc.returncode == 0 and output: - hsh, ct = output.split() - else: - logging.error('Git error: rc=%d, output=%r' % - (proc.returncode, output)) - if not hsh: - return None - pos = '' - proc = RunGitCommand(directory, ['cat-file', 'commit', hsh]) - if proc: - output = proc.communicate()[0] - if proc.returncode == 0 and output: - for line in reversed(output.splitlines()): - if line.startswith('Cr-Commit-Position:'): - pos = line.rsplit()[-1].strip() - break - return VersionInfo(hsh, '%s-%s' % (hsh, pos), int(ct)) - - -def FetchVersionInfo(directory=None, git_log_filter=None): + 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"): """ - Returns the last change (as a VersionInfo object) - from some appropriate revision control system. + 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. """ - version_info = FetchGitRevision(directory, git_log_filter) - if not version_info: - version_info = VersionInfo('0', '0', 0) - return version_info + 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): @@ -136,6 +182,17 @@ def GetHeaderContents(path, define, 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 @@ -160,20 +217,23 @@ def main(argv=None): 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.", - default="LAST_CHANGE") + 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.") + 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("--print-only", action='store_true', + 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", @@ -183,13 +243,14 @@ def main(argv=None): "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 - git_log_filter=args.filter + commit_filter=args.filter while len(extras) and out_file is None: if out_file is None: @@ -199,19 +260,41 @@ def main(argv=None): parser.print_help() sys.exit(2) - if args.source_dir: - src_dir = args.source_dir + 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: - src_dir = os.path.dirname(os.path.abspath(__file__)) + 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) - version_info = FetchVersionInfo(directory=src_dir, - git_log_filter=git_log_filter) revision_string = version_info.revision if args.revision_id_only: revision_string = version_info.revision_id if args.print_only: - print revision_string + print(revision_string) else: contents = "LASTCHANGE=%s\n" % revision_string if not out_file and not args.header: diff --git a/chromium/build/util/version.gni b/chromium/build/util/version.gni index 189d109f1fd..ef52bf6ce64 100644 --- a/chromium/build/util/version.gni +++ b/chromium/build/util/version.gni @@ -3,6 +3,8 @@ # 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 @@ -15,28 +17,66 @@ # 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@\" " + "version_id = @VERSION_ID@ " + - "patch_hi = @PATCH_HI@ " + "patch_lo = @PATCH_LO@ " +_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 (target_os == "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@\" " + + "notouch_chrome_version_code = \"@NOTOUCH_CHROME_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_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@\" " + } + + _script_arguments += [ + "-a", + target_cpu, + ] + + if (!public_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", - [ - "-f", - rebase_path(chrome_version_file, root_build_dir), - "-t", - _version_dictionary_template, - "-e", - "VERSION_ID='%s%03d00' % (BUILD, int(PATCH))", - "-e", - "PATCH_HI=int(PATCH)/256", - "-e", - "PATCH_LO=int(PATCH)%256", - ], + _script_arguments, "scope", [ chrome_version_file ]) @@ -48,11 +88,52 @@ chrome_version_major = _result.major chrome_version_minor = _result.minor chrome_version_build = _result.build chrome_version_patch = _result.patch -chrome_version_id = _result.version_id -chrome_version_patch_hi = _result.patch_hi -chrome_version_patch_lo = _result.patch_lo -if (is_mac) { +if (target_os == "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_64_32_version_code", + "monochrome_64_version_code", + "monochrome_version_code", + "notouch_chrome_version_code", + "trichrome_64_32_version_code", + "trichrome_64_version_code", + "trichrome_version_code", + "webview_beta_version_code", + "webview_dev_version_code", + "webview_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", + "MonochromeFP: $notouch_chrome_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 += [ + "Monochrome6432: $monochrome_64_32_version_code", + "Monochrome64: $monochrome_64_version_code", + "TrichromeChrome6432: $trichrome_64_32_version_code", + "TrichromeChrome64: $trichrome_64_version_code", + ] + } + + write_file("$root_out_dir/android_chrome_versions.txt", lines_to_write) } diff --git a/chromium/build/util/version.py b/chromium/build/util/version.py index 767412e93d1..4f440c4ee7b 100755 --- a/chromium/build/util/version.py +++ b/chromium/build/util/version.py @@ -7,12 +7,16 @@ version.py -- Chromium version string substitution utility. """ +from __future__ import print_function + import argparse import os import sys +import android_chrome_version + -def fetch_values_from_file(values_dict, file_name): +def FetchValuesFromFile(values_dict, file_name): """ Fetches KEYWORD=VALUE settings from the specified file. @@ -27,10 +31,12 @@ def fetch_values_from_file(values_dict, file_name): values_dict[key] = val -def fetch_values(file_list, is_official_build=None): +def FetchValues(file_list, is_official_build=None): """ - Returns a dictionary of values to be used for substitution, populating - the dictionary with KEYWORD=VALUE settings from the files in 'file_list'. + 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: @@ -47,12 +53,12 @@ def fetch_values(file_list, is_official_build=None): ) for file_name in file_list: - fetch_values_from_file(values, file_name) + FetchValuesFromFile(values, file_name) return values -def subst_template(contents, values): +def SubstTemplate(contents, values): """ Returns the template with substituted values from the specified dictionary. @@ -64,29 +70,31 @@ def subst_template(contents, values): contains any @KEYWORD@ strings expecting them to be recursively substituted, okay? """ - for key, val in values.iteritems(): + for key, val in values.items(): try: contents = contents.replace('@' + key + '@', val) except TypeError: - print repr(key), repr(val) + print(repr(key), repr(val)) return contents -def subst_file(file_name, values): +def SubstFile(file_name, values): """ - Returns the contents of the specified file_name with substituted - values from the specified dictionary. + Returns the contents of the specified file_name with substituted values. - This is like subst_template, except it operates on a file. + Substituted values come from the specified dictionary. + + This is like SubstTemplate, except it operates on a file. """ template = open(file_name, 'r').read() - return subst_template(template, values); + return SubstTemplate(template, values) -def write_if_changed(file_name, contents): +def WriteIfChanged(file_name, contents): """ - Writes the specified contents to the specified file_name - iff the contents are different than the current 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() @@ -99,7 +107,8 @@ def write_if_changed(file_name, contents): open(file_name, 'w').write(contents) -def main(): +def BuildParser(): + """Build argparse parser, with added arguments.""" parser = argparse.ArgumentParser() parser.add_argument('-f', '--file', action='append', default=[], help='Read variables from FILE.') @@ -109,30 +118,55 @@ def main(): 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( + '-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.') - options = parser.parse_args() + 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 - # Compatibility with 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): + +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: @@ -140,17 +174,48 @@ def main(): if options.args: parser.error('Unexpected arguments: %r' % options.args) - values = fetch_values(options.file, options.official) - for key, val in evals.iteritems(): + +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: - contents = subst_template(options.template, values) + return SubstTemplate(options.template, values) elif options.input: - contents = subst_file(options.input, values) + return SubstFile(options.input, values) else: # Generate a default set of version information. - contents = """MAJOR=%(MAJOR)s + return """MAJOR=%(MAJOR)s MINOR=%(MINOR)s BUILD=%(BUILD)s PATCH=%(PATCH)s @@ -158,10 +223,34 @@ LASTCHANGE=%(LASTCHANGE)s OFFICIAL_BUILD=%(OFFICIAL_BUILD)s """ % values - if options.output is not None: - write_if_changed(options.output, contents) + +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 contents + print(output['contents']) return 0 diff --git a/chromium/build/util/version_test.py b/chromium/build/util/version_test.py new file mode 100644 index 00000000000..2a65ddc7163 --- /dev/null +++ b/chromium/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 - { <str>: <str> }, 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() |