1# Copyright (C) 2021 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Recipe for building Perfetto.""" 15 16from recipe_engine.recipe_api import Property 17 18DEPS = [ 19 'depot_tools/gsutil', 20 'recipe_engine/buildbucket', 21 'recipe_engine/cipd', 22 'recipe_engine/context', 23 'recipe_engine/file', 24 'recipe_engine/path', 25 'recipe_engine/platform', 26 'recipe_engine/properties', 27 'recipe_engine/raw_io', 28 'recipe_engine/step', 29 'macos_sdk', 30 'windows_sdk', 31] 32 33PROPERTIES = { 34 'repository': 35 Property( 36 kind=str, 37 default='https://android.googlesource.com/platform/external/perfetto' 38 ), 39} 40 41ARTIFACTS = [ 42 { 43 'name': 'trace_processor_shell' 44 }, 45 { 46 'name': 'traceconv', 47 }, 48 { 49 'name': 'tracebox', 50 'exclude_platforms': ['windows-amd64'] 51 }, 52 { 53 'name': 'perfetto' 54 }, 55 { 56 'name': 'traced' 57 }, 58 { 59 'name': 'traced_probes', 60 'exclude_platforms': ['windows-amd64'] 61 }, 62] 63 64 65class BuildContext: 66 67 def __init__(self, src_dir): 68 self.src_dir = src_dir 69 self.git_revision = None 70 self.maybe_git_tag = None 71 72 73def GnArgs(platform): 74 (os, cpu) = platform.split('-') 75 base_args = 'is_debug=false monolithic_binaries=true' 76 if os not in ('android', 'linux', 'mac'): 77 return base_args # No cross-compiling on Windows. 78 cpu = 'x64' if cpu == 'amd64' else cpu # GN calls it "x64". 79 return base_args + ' target_os="{}" target_cpu="{}"'.format(os, cpu) 80 81 82def UploadArtifact(api, ctx, platform, out_dir, artifact): 83 exclude_platforms = artifact.get('exclude_platforms', []) 84 if platform in exclude_platforms: 85 return 86 87 # We want to use the stripped binaries except on Windows where we don't generate 88 # them. 89 exe_dir = out_dir if api.platform.is_win else out_dir.join('stripped') 90 91 # Compute the exact artifact path 92 gcs_upload_dir = ctx.maybe_git_tag if ctx.maybe_git_tag else ctx.git_revision 93 artifact_ext = artifact['name'] + ('.exe' if api.platform.is_win else '') 94 source_path = exe_dir.join(artifact_ext) 95 96 # Upload to GCS bucket. 97 gcs_target_path = '{}/{}/{}'.format(gcs_upload_dir, platform, artifact_ext) 98 api.gsutil.upload(source_path, 'perfetto-luci-artifacts', gcs_target_path) 99 100 # Uploads also the .pdb (debug symbols) to GCS. 101 pdb_path = exe_dir.join(artifact_ext + '.pdb') 102 if api.platform.is_win: 103 api.gsutil.upload(pdb_path, 'perfetto-luci-artifacts', 104 gcs_target_path + '.pdb') 105 106 # Create the CIPD package definition from the artifact path. 107 cipd_pkg_name = 'perfetto/{}/{}'.format(artifact['name'], platform) 108 pkg_def = api.cipd.PackageDefinition( 109 package_name=cipd_pkg_name, package_root=exe_dir) 110 pkg_def.add_file(source_path) 111 112 # Actually build the CIPD pakcage 113 cipd_pkg_file_name = '{}-{}.cipd'.format(artifact['name'], platform) 114 cipd_pkg_file = api.path['cleanup'].join(cipd_pkg_file_name) 115 api.cipd.build_from_pkg( 116 pkg_def=pkg_def, 117 output_package=cipd_pkg_file, 118 ) 119 120 # If we have a git tag, add that to the CIPD tags. 121 tags = { 122 'git_revision': ctx.git_revision, 123 } 124 if ctx.maybe_git_tag: 125 tags['git_tag'] = ctx.maybe_git_tag 126 127 # Upload the package and regisiter with the 'latest' tag. 128 api.cipd.register( 129 package_name=cipd_pkg_name, 130 package_path=cipd_pkg_file, 131 refs=['latest'], 132 tags=tags, 133 ) 134 135 136def BuildForPlatform(api, ctx, platform): 137 out_dir = ctx.src_dir.join('out', platform) 138 139 # Build Perfetto. 140 # There should be no need for internet access here. 141 142 with api.context(cwd=ctx.src_dir), api.macos_sdk(), api.windows_sdk(): 143 targets = [ 144 x['name'] 145 for x in ARTIFACTS 146 if platform not in x.get('exclude_platforms', []) 147 ] 148 args = GnArgs(platform) 149 api.step('gn gen', 150 ['python3', 'tools/gn', 'gen', out_dir, '--args={}'.format(args)]) 151 api.step('gn clean', ['python3', 'tools/gn', 'clean', out_dir]) 152 api.step('ninja', ['python3', 'tools/ninja', '-C', out_dir] + targets) 153 154 # Upload stripped artifacts using gsutil if we're on the official builder. 155 if 'official' not in api.buildbucket.builder_id.builder: 156 return 157 158 with api.step.nest('Artifact upload'), api.context(cwd=ctx.src_dir): 159 for artifact in ARTIFACTS: 160 UploadArtifact(api, ctx, platform, out_dir, artifact) 161 162 163def RunSteps(api, repository): 164 builder_cache_dir = api.path['cache'].join('builder') 165 src_dir = builder_cache_dir.join('perfetto') 166 167 # Crate the context we use in all the building stages. 168 ctx = BuildContext(src_dir) 169 170 # Fetch the Perfetto repo. 171 with api.step.nest('git'), api.context(infra_steps=True): 172 api.file.ensure_directory('ensure source dir', src_dir) 173 api.step('init', ['git', 'init', src_dir]) 174 with api.context(cwd=src_dir): 175 build_input = api.buildbucket.build_input 176 ref = ( 177 build_input.gitiles_commit.ref 178 if build_input.gitiles_commit else 'refs/heads/main') 179 # Fetch tags so `git describe` works. 180 api.step('fetch', ['git', 'fetch', '--tags', repository, ref]) 181 api.step('checkout', ['git', 'checkout', 'FETCH_HEAD']) 182 183 # Store information about the git revision and the tag if available. 184 ctx.git_revision = api.step( 185 'rev-parse', ['git', 'rev-parse', 'HEAD'], 186 stdout=api.raw_io.output_text()).stdout.strip() 187 ctx.maybe_git_tag = ref.replace( 188 'refs/tags/', '') if ref.startswith('refs/tags/') else None 189 190 # Pull all deps here. 191 with api.context(cwd=src_dir, infra_steps=True): 192 extra_args = [] 193 if 'android' in api.buildbucket.builder_id.builder: 194 extra_args += ['--android'] 195 elif api.platform.is_linux: 196 # Pull the cross-toolchains for building for linux-arm{,64}. 197 extra_args += ['--linux-arm'] 198 api.step('build-deps', ['python3', 'tools/install-build-deps'] + extra_args) 199 200 if api.platform.is_win: 201 BuildForPlatform(api, ctx, 'windows-amd64') 202 elif api.platform.is_mac: 203 with api.step.nest('mac-amd64'): 204 BuildForPlatform(api, ctx, 'mac-amd64') 205 with api.step.nest('mac-arm64'): 206 BuildForPlatform(api, ctx, 'mac-arm64') 207 elif 'android' in api.buildbucket.builder_id.builder: 208 with api.step.nest('android-arm'): 209 BuildForPlatform(api, ctx, 'android-arm') 210 with api.step.nest('android-arm64'): 211 BuildForPlatform(api, ctx, 'android-arm64') 212 with api.step.nest('android-x86'): 213 BuildForPlatform(api, ctx, 'android-x86') 214 with api.step.nest('android-x64'): 215 BuildForPlatform(api, ctx, 'android-x64') 216 elif api.platform.is_linux: 217 with api.step.nest('linux-amd64'): 218 BuildForPlatform(api, ctx, 'linux-amd64') 219 with api.step.nest('linux-arm'): 220 BuildForPlatform(api, ctx, 'linux-arm') 221 with api.step.nest('linux-arm64'): 222 BuildForPlatform(api, ctx, 'linux-arm64') 223 224 225def GenTests(api): 226 for target in ('android', 'linux', 'mac', 'win'): 227 host = 'linux' if target == 'android' else target 228 yield (api.test('ci_' + target) + api.platform.name(host) + 229 api.buildbucket.ci_build( 230 project='perfetto', 231 builder='perfetto-official-builder-%s' % target, 232 git_repo='android.googlesource.com/platform/external/perfetto', 233 )) 234 235 yield (api.test('ci_tag') + api.platform.name('linux') + 236 api.buildbucket.ci_build( 237 project='perfetto', 238 builder='official', 239 git_repo='android.googlesource.com/platform/external/perfetto', 240 git_ref='refs/tags/v13.0')) 241 242 yield (api.test('unofficial') + api.platform.name('linux') + 243 api.buildbucket.ci_build( 244 project='perfetto', 245 git_repo='android.googlesource.com/platform/external/perfetto')) 246