|
1 | 1 | import os |
2 | 2 | import json |
3 | 3 | import argparse |
4 | | -from typing import List, Dict |
5 | 4 |
|
6 | | -# --------------------------------------------------------- |
7 | | -# THE VIP LIST: These will get their own dedicated isolated VMs |
8 | | -# --------------------------------------------------------- |
9 | | -MAMMOTH_OVERRIDES = { |
10 | | - "google-cloud-spanner": 9999, |
11 | | - "google-cloud-compute": 9999, |
12 | | - "google-cloud-compute-v1beta": 9999, |
13 | | - "google-cloud-discoveryengine": 9999 |
| 5 | +VIPS = { |
| 6 | + "google-cloud-spanner", |
| 7 | + "google-cloud-compute", |
| 8 | + "google-cloud-compute-v1beta", |
| 9 | + "google-cloud-discoveryengine" |
14 | 10 | } |
15 | 11 |
|
16 | | -def calculate_package_weight(pkg_path: str) -> int: |
17 | | - pkg_name = os.path.basename(os.path.normpath(pkg_path)) |
18 | | - if pkg_name in MAMMOTH_OVERRIDES: |
19 | | - return MAMMOTH_OVERRIDES[pkg_name] |
| 12 | +def get_valid_packages(directories): |
| 13 | + return [p for p in directories if os.path.isfile(os.path.join(p, "noxfile.py"))] |
20 | 14 |
|
21 | | - base_weight = 1 |
22 | | - meta_path = os.path.join(pkg_path, ".repo-metadata.json") |
23 | | - if os.path.isfile(meta_path): |
24 | | - try: |
25 | | - with open(meta_path, 'r') as f: |
26 | | - if json.load(f).get("library_type") == "GAPIC_AUTO": |
27 | | - return base_weight |
28 | | - except Exception: |
29 | | - pass |
30 | | - |
31 | | - test_dir = os.path.join(pkg_path, "tests") |
32 | | - if os.path.isdir(test_dir): |
33 | | - for root, _, files in os.walk(test_dir): |
34 | | - base_weight += sum(1 for f in files if f.endswith(".py")) |
35 | | - |
36 | | - return base_weight |
37 | | - |
38 | | -def create_balanced_buckets(packages: List[str], max_buckets: int) -> List[Dict]: |
39 | | - valid_pkgs = [p for p in packages if os.path.isfile(os.path.join(p, "noxfile.py"))] |
40 | | - if not valid_pkgs: |
41 | | - return [] |
42 | | - |
43 | | - pkg_weights = [(pkg, calculate_package_weight(pkg)) for pkg in valid_pkgs] |
44 | | - pkg_weights.sort(key=lambda x: x[1], reverse=True) |
45 | | - |
46 | | - num_buckets = min(len(valid_pkgs), max_buckets) |
47 | | - buckets = [{"id": i + 1, "packages": [], "total_weight": 0} for i in range(num_buckets)] |
| 15 | +def distribute_packages(packages, max_buckets): |
| 16 | + # VIPs jump to the front of the line |
| 17 | + packages.sort(key=lambda p: os.path.basename(p) not in VIPS) |
48 | 18 |
|
49 | | - for pkg, weight in pkg_weights: |
50 | | - lightest = min(buckets, key=lambda b: b["total_weight"]) |
51 | | - lightest["packages"].append(pkg) |
52 | | - lightest["total_weight"] += weight |
53 | | - |
54 | | - # Build the final output payload |
55 | | - final_output = [] |
56 | | - for b in buckets: |
57 | | - if not b["packages"]: |
58 | | - continue |
59 | | - |
60 | | - # Clean UI labels for isolated VIPs |
61 | | - first_pkg_name = os.path.basename(os.path.normpath(b["packages"][0])) |
62 | | - if first_pkg_name in MAMMOTH_OVERRIDES: |
63 | | - ui_label = first_pkg_name.replace("google-cloud-", "") |
64 | | - else: |
65 | | - ui_label = f"Bucket {b['id']}" |
66 | | - |
67 | | - final_output.append({ |
68 | | - "id": ui_label, |
69 | | - "packages": " ".join(b["packages"]) |
70 | | - }) |
71 | | - |
72 | | - return final_output |
| 19 | + buckets = [{"weight": 0, "pkgs": []} for _ in range(min(len(packages), max_buckets))] |
| 20 | + |
| 21 | + for pkg in packages: |
| 22 | + # Find the bucket with the lowest weight, add the package, and update its weight |
| 23 | + lightest = min(buckets, key=lambda b: b["weight"]) |
| 24 | + lightest["pkgs"].append(pkg) |
| 25 | + lightest["weight"] += 9999 if os.path.basename(pkg) in VIPS else 1 |
| 26 | + |
| 27 | + return [b["pkgs"] for b in buckets] |
| 28 | + |
| 29 | +def build_github_actions_jobs(buckets): |
| 30 | + jobs = [] |
| 31 | + for bucket in buckets: |
| 32 | + # Create a clean UI label for the GitHub presubmit check |
| 33 | + base_name = os.path.basename(bucket[0]).replace("google-cloud-", "") |
| 34 | + job_label = f"{base_name} + {len(bucket) - 1}" if len(bucket) > 1 else base_name |
| 35 | + jobs.append({"id": job_label, "packages": " ".join(bucket)}) |
| 36 | + return jobs |
73 | 37 |
|
74 | 38 | def main(): |
75 | 39 | parser = argparse.ArgumentParser() |
76 | 40 | parser.add_argument("--matrix-multiplier", type=int, required=True) |
77 | | - parser.add_argument("--max-vms", type=int, default=40) |
| 41 | + parser.add_argument("--max-vms", type=int, default=20) |
78 | 42 | args = parser.parse_args() |
79 | 43 |
|
80 | | - safe_github_limit = 250 // args.matrix_multiplier |
81 | | - max_allowed_buckets = min(safe_github_limit, args.max_vms) |
| 44 | + packages = get_valid_packages(os.environ.get("CHANGED_DIRS", "").split()) |
| 45 | + if not packages: |
| 46 | + return |
82 | 47 |
|
83 | | - changed_dirs = os.environ.get("CHANGED_DIRS", "").split() |
84 | | - buckets = create_balanced_buckets(changed_dirs, max_allowed_buckets) |
| 48 | + max_buckets = min(250 // args.matrix_multiplier, args.max_vms) |
| 49 | + buckets = distribute_packages(packages, max_buckets) |
85 | 50 |
|
86 | | - github_output = os.environ.get("GITHUB_OUTPUT") |
87 | | - if github_output: |
88 | | - with open(github_output, "a") as f: |
89 | | - f.write(f"buckets={json.dumps(buckets)}\n") |
| 51 | + jobs_json = json.dumps(build_github_actions_jobs(buckets)) |
| 52 | + |
| 53 | + if "GITHUB_OUTPUT" in os.environ: |
| 54 | + with open(os.environ["GITHUB_OUTPUT"], "a") as f: |
| 55 | + f.write(f"buckets={jobs_json}\n") |
90 | 56 | else: |
91 | | - print(json.dumps(buckets, indent=2)) |
| 57 | + print(jobs_json) |
92 | 58 |
|
93 | 59 | if __name__ == "__main__": |
94 | 60 | main() |
0 commit comments