@@ -27,29 +27,161 @@ All files will be placed into /tmp/perfetto-v20.0-github-release/ .
2727"""
2828
2929import argparse
30- import subprocess
3130import os
31+ import subprocess
3232import sys
33- import tempfile
34- import shutil
33+
34+ # Expected LUCI artifact manifest. Must stay in sync with
35+ # infra/luci/recipes/perfetto.py — ARTIFACTS + the platform list in
36+ # RunSteps. Verified after rsync; a mismatch (missing OR extra files) is a
37+ # hard error because it means LUCI's output changed and this script needs to
38+ # be updated.
39+ #
40+ # On Windows, each binary is accompanied by a <name>.pdb (debug symbols),
41+ # expressed as 'windows_pdb': True per artifact. Platform filters match the
42+ # 'exclude_platforms' / 'include_platforms' keys in the recipe.
43+ _ARTIFACTS = [
44+ {
45+ 'name' : 'trace_processor_shell'
46+ },
47+ {
48+ 'name' : 'traceconv'
49+ },
50+ {
51+ 'name' : 'tracebox' ,
52+ 'exclude_platforms' : ['windows-amd64' ],
53+ },
54+ {
55+ 'name' : 'perfetto'
56+ },
57+ {
58+ 'name' : 'traced'
59+ },
60+ {
61+ 'name' : 'traced_probes' ,
62+ 'exclude_platforms' : ['windows-amd64' ],
63+ },
64+ {
65+ 'name' : 'heapprofd_glibc_preload' ,
66+ 'file' : 'libheapprofd_glibc_preload.so' ,
67+ 'include_platforms' : ['linux-amd64' , 'linux-arm' , 'linux-arm64' ],
68+ },
69+ ]
70+
71+ _PLATFORMS = [
72+ 'android-arm' ,
73+ 'android-arm64' ,
74+ 'android-x64' ,
75+ 'android-x86' ,
76+ 'linux-amd64' ,
77+ 'linux-arm' ,
78+ 'linux-arm64' ,
79+ 'mac-amd64' ,
80+ 'mac-arm64' ,
81+ 'windows-amd64' ,
82+ ]
83+
84+ _SDK_ZIPS = [
85+ 'perfetto-cpp-sdk-src.zip' ,
86+ 'perfetto-c-sdk-src.zip' ,
87+ ]
3588
3689
3790def exec (* args ):
3891 print (' ' .join (args ))
3992 subprocess .check_call (args )
4093
4194
42- def get_repo_root ():
43- """Returns the root directory of the Perfetto repository."""
44- script_dir = os .path .dirname (os .path .abspath (__file__ ))
45- return os .path .abspath (os .path .join (script_dir , '..' , '..' ))
95+ def _artifact_filename (artifact , platform ):
96+ """Returns the on-disk filename LUCI uploads for `artifact` on `platform`."""
97+ base = artifact .get ('file' , artifact ['name' ])
98+ if platform == 'windows-amd64' and 'file' not in artifact :
99+ return base + '.exe'
100+ return base
101+
46102
103+ def _expected_manifest ():
104+ """Returns {platform: set(filenames)} for all LUCI-produced binaries."""
105+ manifest = {p : set () for p in _PLATFORMS }
106+ for platform in _PLATFORMS :
107+ for artifact in _ARTIFACTS :
108+ if platform in artifact .get ('exclude_platforms' , []):
109+ continue
110+ include = artifact .get ('include_platforms' )
111+ if include is not None and platform not in include :
112+ continue
113+ fname = _artifact_filename (artifact , platform )
114+ manifest [platform ].add (fname )
115+ if platform == 'windows-amd64' :
116+ manifest [platform ].add (fname + '.pdb' )
117+ return manifest
118+
119+
120+ def verify_downloads (tmpdir ):
121+ """Verifies the rsynced tree matches the expected LUCI manifest.
122+
123+ Fails loudly on anything missing or unexpected — the manifest here must
124+ match LUCI, and drift in either direction should surface immediately.
125+ """
126+ manifest = _expected_manifest ()
127+ expected_dirs = set (manifest .keys ()) | {'sdk' }
128+ actual_dirs = {
129+ d for d in os .listdir (tmpdir ) if os .path .isdir (os .path .join (tmpdir , d ))
130+ }
131+
132+ errors = []
133+ missing_dirs = expected_dirs - actual_dirs
134+ unexpected_dirs = actual_dirs - expected_dirs
135+ if missing_dirs :
136+ errors .append ('Missing platform directories: %s' %
137+ ', ' .join (sorted (missing_dirs )))
138+ if unexpected_dirs :
139+ errors .append ('Unexpected directories under GCS path: %s' %
140+ ', ' .join (sorted (unexpected_dirs )))
141+
142+ for platform , expected_files in manifest .items ():
143+ pdir = os .path .join (tmpdir , platform )
144+ if not os .path .isdir (pdir ):
145+ continue
146+ actual_files = set (os .listdir (pdir ))
147+ missing = expected_files - actual_files
148+ extra = actual_files - expected_files
149+ if missing :
150+ errors .append ('%s: missing binaries: %s' %
151+ (platform , ', ' .join (sorted (missing ))))
152+ if extra :
153+ errors .append ('%s: unexpected binaries: %s' %
154+ (platform , ', ' .join (sorted (extra ))))
47155
48- def verify_git_state (expected_version ):
156+ sdk_dir = os .path .join (tmpdir , 'sdk' )
157+ if os .path .isdir (sdk_dir ):
158+ actual_sdk = set (os .listdir (sdk_dir ))
159+ expected_sdk = set (_SDK_ZIPS )
160+ missing_sdk = expected_sdk - actual_sdk
161+ extra_sdk = actual_sdk - expected_sdk
162+ if missing_sdk :
163+ errors .append ('sdk: missing zips: %s' % ', ' .join (sorted (missing_sdk )))
164+ if extra_sdk :
165+ errors .append ('sdk: unexpected files: %s' % ', ' .join (sorted (extra_sdk )))
166+
167+ if errors :
168+ print ('\n ' .join ('ERROR: ' + e for e in errors ), file = sys .stderr )
169+ print (
170+ '\n The LUCI artifact manifest in this script is out of sync with '
171+ 'infra/luci/recipes/perfetto.py. Update _ARTIFACTS / _PLATFORMS / '
172+ '_SDK_ZIPS above and re-run.' ,
173+ file = sys .stderr )
174+ return False
175+
176+ print ('✓ All expected LUCI artifacts present across %d platforms + sdk.' %
177+ len (_PLATFORMS ))
178+ return True
179+
180+
181+ def verify_git_state (expected_version , assume_yes = False ):
49182 """Verifies git is on the correct tag with no uncommitted changes."""
50183 warnings = []
51184
52- # Check for uncommitted changes
53185 try :
54186 result = subprocess .run (['git' , 'status' , '--porcelain' ],
55187 capture_output = True ,
@@ -60,7 +192,6 @@ def verify_git_state(expected_version):
60192 except Exception as e :
61193 warnings .append (f'Could not check git status: { e } ' )
62194
63- # Check current tag
64195 try :
65196 result = subprocess .run (['git' , 'describe' , '--exact-match' , '--tags' ],
66197 capture_output = True ,
@@ -79,63 +210,60 @@ def verify_git_state(expected_version):
79210 print ('WARNING: SDK sources may not match the release tag:' )
80211 for warning in warnings :
81212 print (f' - { warning } ' )
213+ if assume_yes :
214+ print ('\n --yes passed; proceeding despite warnings.' )
215+ return True
82216 return input ('\n Continue anyway? [y/N] ' ).lower ().strip () in ['y' , 'yes' ]
83217
84218 print (f'✓ On tag { expected_version } with clean working directory' )
85219 return True
86220
87221
88- def download_sdk_sources (tmpdir , version ):
89- """Downloads SDK source zips from GCS."""
90- sdk_zips = [
91- 'perfetto-cpp-sdk-src.zip' ,
92- 'perfetto-c-sdk-src.zip' ,
93- ]
94-
95- print ('\n --- Downloading SDK amalgamated sources from GCS ---' )
96- for zip_name in sdk_zips :
97- url = f'gs://perfetto-luci-artifacts/{ version } /sdk/{ zip_name } '
98- exec ('gsutil' , 'cp' , url , os .path .join (tmpdir , zip_name ))
99-
100- return sdk_zips
101-
102-
103222def main ():
104223 parser = argparse .ArgumentParser (epilog = 'Example: %s v19.0' % __file__ )
105224 parser .add_argument ('version' , help = 'Version tag (e.g., v20.0)' )
106-
225+ parser .add_argument (
226+ '--yes' ,
227+ action = 'store_true' ,
228+ help = 'Skip all interactive confirmations (for CI use).' )
107229 args = parser .parse_args ()
108230
109- # Verify we're on the correct tag with no uncommitted changes
110- if not verify_git_state (args .version ):
231+ if not verify_git_state (args .version , assume_yes = args .yes ):
111232 print ('Aborted.' )
112233 return 1
113234
114235 tmpdir = '/tmp/perfetto-%s-github-release' % args .version
115236 src = 'gs://perfetto-luci-artifacts/%s/' % args .version
116237 os .makedirs (tmpdir , exist_ok = True )
117238
118- # Download and package prebuilts
119239 print ('--- Downloading prebuilts from GCS ---' )
120240 os .chdir (tmpdir )
121241 exec ('gsutil' , '-m' , 'rsync' , '-rc' , src , tmpdir + '/' )
122242
243+ print ('\n --- Verifying artifact manifest ---' )
244+ if not verify_downloads (tmpdir ):
245+ return 1
246+
123247 zips = []
124- for arch in os .listdir (tmpdir ):
248+ for arch in sorted ( os .listdir (tmpdir ) ):
125249 if arch == 'sdk' or not os .path .isdir (os .path .join (tmpdir , arch )):
126250 continue
127251 exec ('zip' , '-9r' , '%s.zip' % arch , arch )
128252 zips .append (arch + '.zip' )
129253
130- # Download SDK source zips
131- sdk_zips = download_sdk_sources (tmpdir , args .version )
132- zips .extend (sdk_zips )
254+ # SDK zips already landed under sdk/ via the rsync above; just move them
255+ # up so everything ready to upload sits in tmpdir's root.
256+ for zip_name in _SDK_ZIPS :
257+ src_path = os .path .join (tmpdir , 'sdk' , zip_name )
258+ dst_path = os .path .join (tmpdir , zip_name )
259+ os .rename (src_path , dst_path )
260+ zips .append (zip_name )
133261
134262 print ('' )
135263 print ('=' * 70 )
136264 print ('%d zip files saved in %s' % (len (zips ), tmpdir ))
137- print ('Prebuilt binaries: %d' % (len (zips ) - len (sdk_zips )))
138- print ('SDK sources: %d' % len (sdk_zips ))
265+ print ('Prebuilt binaries: %d' % (len (zips ) - len (_SDK_ZIPS )))
266+ print ('SDK sources: %d' % len (_SDK_ZIPS ))
139267 print ('Files: %s' % ', ' .join (sorted (zips )))
140268 print ('=' * 70 )
141269
0 commit comments