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- The number of CLs from the release tag to HEAD. 24 25The latter is concatenated to the version number to distinguish builds made 26fully from release tags (e.g., v9.0.0) vs builds made from the main branch which 27are N cls ahead of the latest monthly release (e.g., v9.0.42). 28""" 29 30import argparse 31import os 32import re 33import sys 34import subprocess 35 36# Note: PROJECT_ROOT is not accurate in bazel builds, where this script is 37# executed in the bazel sandbox. 38PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 39SCM_REV_NOT_AVAILABLE = 'N/A' 40 41 42def get_latest_release(changelog_path): 43 """Returns a string like 'v9.0'. 44 45 It does so by searching the latest version mentioned in the CHANGELOG.""" 46 if not changelog_path: 47 if os.path.exists('CHANGELOG'): 48 changelog_path = 'CHANGELOG' 49 else: 50 changelog_path = os.path.join(PROJECT_ROOT, 'CHANGELOG') 51 with open(changelog_path) as f: 52 for line in f.readlines(): 53 m = re.match('^(v\d+[.]\d+)\s.*$', line) 54 if m is not None: 55 return m.group(1) 56 raise Exception('Failed to fetch Perfetto version from %s' % changelog_path) 57 58 59def get_git_info(last_release_tag): 60 """Returns a tuple ('deadbeef', '1234'). 61 62 The first value is the SHA1 of the HEAD. The second is the number of CLs from 63 the passed |last_release_tag| to HEAD.""" 64 commit_sha1 = SCM_REV_NOT_AVAILABLE 65 commits_since_release = '' 66 git_dir = os.path.join(PROJECT_ROOT, '.git') 67 if os.path.exists(git_dir): 68 try: 69 commit_sha1 = subprocess.check_output(['git', 'rev-parse', 'HEAD'], 70 cwd=PROJECT_ROOT).strip().decode() 71 with open(os.devnull, 'wb') as devnull: 72 commits_since_release = subprocess.check_output( 73 [ 74 'git', 'rev-list', '--count', 75 'refs/tags/%s..HEAD' % last_release_tag 76 ], 77 cwd=PROJECT_ROOT, 78 stderr=devnull).strip().decode() 79 except subprocess.CalledProcessError: 80 pass 81 82 return (commit_sha1, commits_since_release) 83 84 85def write_if_unchanged(path, content): 86 prev_content = None 87 if os.path.exists(path): 88 with open(path, 'r') as fprev: 89 prev_content = fprev.read() 90 if prev_content == content: 91 return 0 92 with open(path, 'w') as fout: 93 fout.write(content) 94 95 96def main(): 97 parser = argparse.ArgumentParser() 98 parser.add_argument( 99 '--no_git', 100 action='store_true', 101 help='Skips running git rev-parse, emits only the version from CHANGELOG') 102 parser.add_argument('--cpp_out', help='Path of the generated .h file.') 103 parser.add_argument('--ts_out', help='Path of the generated .ts file.') 104 parser.add_argument('--stdout', help='Write to stdout', action='store_true') 105 parser.add_argument('--changelog', help='Path to CHANGELOG.') 106 args = parser.parse_args() 107 108 release = get_latest_release(args.changelog) 109 if args.no_git: 110 git_sha1, commits_since_release = (SCM_REV_NOT_AVAILABLE, '') 111 else: 112 git_sha1, commits_since_release = get_git_info(release) 113 114 # Try to compute the number of commits since the last release. This can fail 115 # in some environments (e.g. in android builds) because the bots pull only 116 # the main branch and don't pull the whole list of tags. 117 if commits_since_release: 118 version = '%s.%s' % (release, commits_since_release) # e.g., 'v9.0.42'. 119 else: 120 version = release # e.g., 'v9.0'. 121 122 if args.cpp_out: 123 guard = '%s_' % args.cpp_out.upper() 124 guard = re.sub(r'[^\w]', '_', guard) 125 lines = [] 126 lines.append('// Generated by %s' % os.path.basename(__file__)) 127 lines.append('') 128 lines.append('#ifndef %s' % guard) 129 lines.append('#define %s' % guard) 130 lines.append('') 131 lines.append('#define PERFETTO_VERSION_STRING() "%s"' % version) 132 lines.append('#define PERFETTO_VERSION_SCM_REVISION() "%s"' % git_sha1) 133 lines.append('') 134 lines.append('#endif // %s' % guard) 135 lines.append('') 136 content = '\n'.join(lines) 137 write_if_unchanged(args.cpp_out, content) 138 139 if args.ts_out: 140 lines = [] 141 lines.append('export const VERSION = "%s";' % version) 142 lines.append('export const SCM_REVISION = "%s";' % git_sha1) 143 content = '\n'.join(lines) 144 write_if_unchanged(args.ts_out, content) 145 146 if args.stdout: 147 print(version) 148 149 150if __name__ == '__main__': 151 sys.exit(main()) 152