1#!/usr/bin/env python3
2
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import os
18import glob
19import argparse
20import pathlib
21import shutil
22import subprocess
23import re
24
25
26def sed(pattern, replacement, file):
27    """ Performs an in-place string replacement of pattern in a target file
28
29    Args:
30        pattern: pattern to replace
31        replacement: replacement for the pattern matches
32        file: target file
33
34    Returns:
35        Nothing
36    """
37
38    with open(file) as reader:
39        file_contents = reader.read()
40    new_file_contents = re.sub(pattern, replacement, file_contents)
41    with open(file, "w") as writer:
42        writer.write(new_file_contents)
43
44
45def single(items):
46    """ Returns the only item from a list of just one item
47
48    Raises a ValueError if the list does not contain exactly one element
49
50    Args:
51        items: a list of one item
52
53    Returns:
54        The only item from a single-item-list
55    """
56
57    if len(items) != 1:
58        raise ValueError('Expected a list of size 1. Found: %s' % items)
59    return items[0]
60
61
62def update_tracing_perfetto(old_version, new_version, core_path, force_unstripped_binaries=False):
63    """Updates tracing-perfetto version and artifacts (including building new binaries)
64
65    Args:
66        old_version: old version of the existing library
67        new_version: new version of the library; defaults to incrementing the old_version
68        core_path: path to frameworks/support directory
69        force_unstripped_binaries: flag allowing to force unstripped variant of binaries
70    Returns:
71        Nothing
72    """
73
74    print("Updating tracing-perfetto, this can take a while...")
75
76    # update version in code
77    sed('tracingPerfettoVersion = "%s"' % old_version,
78        'tracingPerfettoVersion = "%s"' % new_version,
79        os.path.join(core_path, 'benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/'
80                                'macro/perfetto/PerfettoSdkHandshakeTest.kt'))
81    sed('TRACING_PERFETTO = "%s"' % old_version,
82        'TRACING_PERFETTO = "%s"' % new_version,
83        os.path.join(core_path, 'libraryversions.toml'))
84    sed('#define VERSION "%s"' % old_version,
85        '#define VERSION "%s"' % new_version,
86        os.path.join(core_path, 'tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc'))
87    sed('const val libraryVersion = "%s"' % old_version,
88        'const val libraryVersion = "%s"' % new_version,
89        os.path.join(core_path, 'tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/'
90                                'perfetto/jni/test/PerfettoNativeTest.kt'))
91    sed('const val version = "%s"' % old_version,
92        'const val version = "%s"' % new_version,
93        os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/'
94                                'jni/PerfettoNative.kt'))
95
96    # build new binaries
97    subprocess.check_call(["./gradlew",
98                           ":tracing:tracing-perfetto-binary:createProjectZip",
99                           "-Pandroidx.constraints=true",
100                           "-DTRACING_PERFETTO_REUSE_PREBUILTS_AAR=false"],
101                          cwd=core_path)
102
103    # copy binaries to prebuilts
104    project_zip_dir = os.path.join(core_path, '../../out/dist/per-project-zips')
105    project_zip_file = os.path.join(
106        project_zip_dir,
107        single(glob.glob('%s/*tracing*perfetto*binary*%s*.zip' % (project_zip_dir, new_version))))
108    dst_dir = pathlib.Path(os.path.join(
109        core_path,
110        "../../prebuilts/androidx/internal/androidx/tracing/tracing-perfetto-binary",
111        new_version))
112    if dst_dir.exists():
113        shutil.rmtree(dst_dir)
114    dst_dir.mkdir()
115    subprocess.check_call(
116        ["unzip", "-xjqq", project_zip_file, '**/%s/**' % new_version, "-d", dst_dir])
117
118    # force unstripped binaries if the flag is enabled
119    if force_unstripped_binaries:
120        # locate unstripped binaries
121        out_dir = pathlib.Path(core_path, "../../out")
122        arm64_lib_file = out_dir.joinpath(single(subprocess.check_output(
123            'find . -type f -name "libtracing_perfetto.so"'
124            ' -and -path "*RelWithDebInfo/*/obj/arm64*"'
125            ' -exec stat -c "%Y %n" {} \\; |'
126            ' sort | tail -1 | cut -d " " -f2-',
127            cwd=out_dir,
128            shell=True).splitlines()).decode())
129        base_dir = arm64_lib_file.parent.parent.parent
130        obj_dir = base_dir.joinpath('obj')
131        if not obj_dir.exists():
132            raise RuntimeError('Expected path %s to exist' % repr(obj_dir))
133        jni_dir = base_dir.joinpath('jni')
134
135        # prepare a jni folder to inject into the destination aar
136        if jni_dir.exists():
137            shutil.rmtree(jni_dir)
138        shutil.copytree(obj_dir, jni_dir)
139
140        # inject the jni folder into the aar
141        dst_aar = os.path.join(dst_dir, 'tracing-perfetto-binary-%s.aar' % new_version)
142        subprocess.check_call(['zip', '-r', dst_aar, 'jni'], cwd=base_dir)
143
144        # clean up
145        if jni_dir.exists():
146            shutil.rmtree(jni_dir)
147
148    # update SHA
149    for arch in ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']:
150        checksum = subprocess.check_output(
151            'unzip -cxqq "*tracing*binary*%s*.aar" "**/%s/libtracing_perfetto.so" | shasum -a256 |'
152            ' awk \'{print $1}\' | tr -d "\n"' % (new_version, arch),
153            cwd=dst_dir,
154            shell=True
155        ).decode()
156        if not re.fullmatch('^[0-9a-z]{64}$', checksum):
157            raise ValueError('Expecting a sha256 sum. Got: %s' % checksum)
158        sed(
159            '"%s" to "[0-9a-z]{64}"' % arch,
160            '"%s" to "%s"' % (arch, checksum),
161            os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/'
162                                    'perfetto/jni/PerfettoNative.kt'))
163
164    print("Updated tracing-perfetto.")
165
166
167if __name__ == '__main__':
168    parser = argparse.ArgumentParser(
169        description='Updates tracing-perfetto in the source code, which involves:'
170                    ' 1) updating hardcoded version references in code'
171                    ' 2) building binaries and updating them in the prebuilts folder'
172                    ' 3) updating SHA checksums hardcoded in code.')
173    parser.add_argument('-f', '--frameworks-support-dir',
174                        required=True,
175                        help='Path to frameworks/support directory')
176    parser.add_argument('-c', '--current-version',
177                        required=True,
178                        help='Current version, e.g. 1.0.0-alpha07')
179    parser.add_argument('-t', '--target-version',
180                        required=True,
181                        help='Target version, e.g. 1.0.0-alpha08')
182    parser.add_argument('-k', '--keep-binary-debug-symbols',
183                        required=False,
184                        default=False,
185                        action='store_true',
186                        help='Keeps debug symbols in the built binaries. Useful when profiling '
187                             'performance of the library. ')
188    args = parser.parse_args()
189    core_path_abs = pathlib.Path(args.frameworks_support_dir).resolve()
190    update_tracing_perfetto(args.current_version, args.target_version, core_path_abs,
191                            args.keep_binary_debug_symbols)
192