| 199 | |
| 200 | |
| 201 | def main(argv: Sequence[str] | None = None) -> int: |
| 202 | args = parse_args(argv) |
| 203 | |
| 204 | try: |
| 205 | uploads, missing = build_upload_entries(args.dist, args.tag) |
| 206 | |
| 207 | for filename in missing: |
| 208 | print(f"missing release artifact: {filename}") |
| 209 | |
| 210 | if missing and not args.ignore_missing: |
| 211 | raise SystemExit(f"missing {len(missing)} release artifacts") |
| 212 | |
| 213 | if not missing: |
| 214 | print(f"found all {len(uploads)} release artifacts") |
| 215 | |
| 216 | client = None |
| 217 | transfer_config = None |
| 218 | if not args.dry_run: |
| 219 | client, transfer_config = make_s3_client() |
| 220 | |
| 221 | mirror = S3MirrorClient( |
| 222 | client=client, |
| 223 | transfer_config=transfer_config, |
| 224 | dry_run=args.dry_run, |
| 225 | ) |
| 226 | |
| 227 | errors: list[str] = [] |
| 228 | with concurrent.futures.ThreadPoolExecutor( |
| 229 | max_workers=UPLOAD_CONCURRENCY |
| 230 | ) as executor: |
| 231 | futures = [ |
| 232 | executor.submit( |
| 233 | mirror.upload_file, |
| 234 | args.bucket, |
| 235 | f"{args.prefix}{entry.dest_name}", |
| 236 | args.dist / entry.source_name, |
| 237 | ) |
| 238 | for entry in uploads |
| 239 | ] |
| 240 | |
| 241 | for future in concurrent.futures.as_completed(futures): |
| 242 | try: |
| 243 | future.result() |
| 244 | except MirrorError as e: |
| 245 | errors.append(str(e)) |
| 246 | |
| 247 | if errors: |
| 248 | error_lines = "\n".join(f"- {error}" for error in sorted(errors)) |
| 249 | raise MirrorError( |
| 250 | f"encountered {len(errors)} upload errors:\n{error_lines}" |
| 251 | ) |
| 252 | |
| 253 | mirror.upload_file( |
| 254 | args.bucket, |
| 255 | f"{args.prefix}SHA256SUMS", |
| 256 | args.dist / "SHA256SUMS", |
| 257 | ) |
| 258 | except MirrorError as e: |