xref: /aosp_15_r20/external/perfetto/tools/write_version_header.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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