1#!/usr/bin/env python3 2# Copyright (C) 2020 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15""" 16Writes the perfetto_version{.gen.h, .ts} files. 17 18This tool is run as part of a genrule from GN, SoonG and Bazel builds. It 19generates a source header (or in the case of --ts_out a TypeScript file) that 20contains: 21- The version number (e.g. v9.0) obtained parsing the CHANGELOG file. 22- The git HEAD's commit-ish (e.g. 6b330b772b0e973f79c70ba2e9bb2b0110c6715d) 23 24The latter is concatenated to the version number to disambiguate builds made 25from release tags vs builds made from the main branch vs UI builds made from the 26ui-canary/ui-stable branch. 27""" 28 29import argparse 30import os 31import re 32import sys 33import subprocess 34 35# Note: PROJECT_ROOT is not accurate in bazel builds, where this script is 36# executed in the bazel sandbox. 37PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 38SCM_REV_NOT_AVAILABLE = 'N/A' 39 40 41def get_latest_release(changelog_path): 42 """Returns a string like 'v9.0'. 43 44 It does so by searching the latest version mentioned in the CHANGELOG.""" 45 if not changelog_path: 46 if os.path.exists('CHANGELOG'): 47 changelog_path = 'CHANGELOG' 48 else: 49 changelog_path = os.path.join(PROJECT_ROOT, 'CHANGELOG') 50 with open(changelog_path) as f: 51 for line in f.readlines(): 52 m = re.match(r'^(v\d+[.]\d+)\s.*$', line) 53 if m is not None: 54 return m.group(1) 55 raise Exception('Failed to fetch Perfetto version from %s' % changelog_path) 56 57 58def get_git_sha1(commitish): 59 """Returns the SHA1 of the provided commit-ish""" 60 commit_sha1 = SCM_REV_NOT_AVAILABLE 61 git_dir = os.path.join(PROJECT_ROOT, '.git') 62 if os.path.exists(git_dir): 63 try: 64 commit_sha1 = subprocess.check_output(['git', 'rev-parse', commitish], 65 cwd=PROJECT_ROOT).strip().decode() 66 except subprocess.CalledProcessError: 67 pass 68 return commit_sha1 69 70 71def write_if_unchanged(path, content): 72 prev_content = None 73 if os.path.exists(path): 74 with open(path, 'r') as fprev: 75 prev_content = fprev.read() 76 if prev_content == content: 77 return 0 78 with open(path, 'w') as fout: 79 fout.write(content) 80 81 82def main(): 83 parser = argparse.ArgumentParser() 84 parser.add_argument('--check_git', action='store_true') 85 parser.add_argument( 86 '--no_git', 87 action='store_true', 88 help='Skips running git rev-parse, emits only the version from CHANGELOG') 89 parser.add_argument('--cpp_out', help='Path of the generated .h file.') 90 parser.add_argument('--ts_out', help='Path of the generated .ts file.') 91 parser.add_argument('--stdout', help='Write to stdout', action='store_true') 92 parser.add_argument('--changelog', help='Path to CHANGELOG.') 93 args = parser.parse_args() 94 95 if args.check_git: 96 has_git = os.path.exists(os.path.join(PROJECT_ROOT, '.git', 'HEAD')) 97 print('1' if has_git else '0') 98 return 0 99 100 release = get_latest_release(args.changelog) 101 102 if args.no_git: 103 head_sha1 = SCM_REV_NOT_AVAILABLE 104 else: 105 head_sha1 = get_git_sha1('HEAD') # SCM_REV_NOT_AVAILABLE on failure. 106 107 if head_sha1 == SCM_REV_NOT_AVAILABLE: 108 version = release # e.g., 'v9.0'. 109 else: 110 sha1_abbrev = head_sha1[:9] 111 version = f'{release}-{sha1_abbrev}' # e.g., 'v9.0-adeadbeef'. 112 113 if args.cpp_out: 114 guard = '%s_' % args.cpp_out.upper() 115 guard = re.sub(r'[^\w]', '_', guard) 116 lines = [] 117 lines.append('// Generated by %s' % os.path.basename(__file__)) 118 lines.append('') 119 lines.append('#ifndef %s' % guard) 120 lines.append('#define %s' % guard) 121 lines.append('') 122 lines.append('#define PERFETTO_VERSION_STRING() "%s"' % version) 123 lines.append('#define PERFETTO_VERSION_SCM_REVISION() "%s"' % head_sha1) 124 lines.append('') 125 lines.append('#endif // %s' % guard) 126 lines.append('') 127 content = '\n'.join(lines) 128 write_if_unchanged(args.cpp_out, content) 129 130 if args.ts_out: 131 lines = [] 132 lines.append('export const VERSION = "%s";' % version) 133 lines.append('export const SCM_REVISION = "%s";' % head_sha1) 134 content = '\n'.join(lines) 135 write_if_unchanged(args.ts_out, content) 136 137 if args.stdout: 138 print(version) 139 140 141if __name__ == '__main__': 142 sys.exit(main()) 143