-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate-release.py
More file actions
executable file
·180 lines (162 loc) · 7.2 KB
/
Copy pathcreate-release.py
File metadata and controls
executable file
·180 lines (162 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
"""
Creates a clean, distributable .zip of the CppMode folder for a release.
Includes everything in CppMode/ EXCEPT the contents of cache/ subfolders
(cache/linux-x64, cache/windows-x64, etc.) -- those are machine-specific
compiled build artifacts (Processing.o and similar), not something that
belongs in a release archive; they get rebuilt fresh on whatever machine
actually uses the release.
Usage:
create-release.py [version]
If no version is given, the zip is named CppMode.zip; otherwise it's
named CppMode-<version>.zip (e.g. CppMode-0.2.0.zip).
"""
import os
import sys
import zipfile
# This script lives in CppMode/scripts/, so CppMode's own root is one
# directory up from wherever this script actually is.
CPPMODE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUTPUT_DIR = os.path.dirname(CPPMODE_DIR) # one level above CppMode/ itself
def should_skip_file(path):
"""
Returns True for .o files inside cache/<platform>/, or anything
inside .git/ or .github/. The cache/<platform>/ DIRECTORIES
themselves are kept (so the release has the right folder layout
ready for a fresh rebuild), just not the compiled .o artifacts
sitting inside them -- those are machine-specific and get rebuilt
fresh wherever the release is actually used.
"""
rel = os.path.relpath(path, CPPMODE_DIR)
parts = rel.split(os.sep)
if len(parts) >= 1 and parts[0] in (".git", ".github"):
return True
if False: # .o files are included in release (downloaded from CI)
return True
return False
def should_prune_dir(path):
"""Directories to not even descend into at all (.git, .github)."""
rel = os.path.relpath(path, CPPMODE_DIR)
parts = rel.split(os.sep)
return len(parts) >= 1 and parts[0] in (".git", ".github")
def bump_version_files(tag):
"""
Updates mode.properties and CppMode.txt to match the given release
tag: sets prettyVersion/prettyversion to the tag exactly as given,
and increments the plain integer "version=" build number by 1 in
both files. Both files get updated together so they never drift out
of sync with each other.
"""
targets = [
(os.path.join(CPPMODE_DIR, "mode.properties"), "prettyVersion"),
(os.path.join(CPPMODE_DIR, "CppMode.txt"), "prettyVersion"),
]
for path, pretty_key in targets:
if not os.path.exists(path):
print(f"WARNING: {path} not found, skipping version bump for it.")
continue
lines = open(path).readlines()
new_lines = []
for line in lines:
if line.startswith("version="):
try:
current = int(line.strip().split("=", 1)[1])
except ValueError:
current = 0
new_lines.append(f"version={current + 1}\n")
elif line.startswith(pretty_key + "="):
new_lines.append(f"{pretty_key}={tag}\n")
else:
new_lines.append(line)
with open(path, "w") as out:
out.writelines(new_lines)
print(f"Updated {path}: {pretty_key}={tag}, version bumped by 1")
def download_cache_artifacts():
"""Download precompiled .o files from latest successful build-cache CI run."""
import subprocess, json
from pathlib import Path
repo = "processing-cpp/processing.cpp"
root = Path(__file__).parent.parent
platforms = ["linux-x64", "windows-x64", "macos-arm64", "macos-x64"]
print("Downloading precompiled cache from CI artifacts...")
# Find the latest successful build-cache run
r = subprocess.run(
["gh", "run", "list", "--repo", repo, "--workflow", "build-cache.yml",
"--status", "success", "--limit", "1", "--json", "databaseId"],
capture_output=True, text=True
)
if r.returncode != 0 or not r.stdout.strip():
print(" WARNING: Could not find successful build-cache run")
return
run_id = json.loads(r.stdout)[0]["databaseId"]
print(f" Using run {run_id}")
for platform in platforms:
dest = root / "cache" / platform
# Clear existing files so gh run download can overwrite
import shutil
if dest.exists(): shutil.rmtree(dest)
dest.mkdir(parents=True, exist_ok=True)
result = subprocess.run(
["gh", "run", "download", str(run_id), "--repo", repo,
"--name", f"cache-{platform}", "--dir", str(dest)],
capture_output=True, text=True
)
if result.returncode != 0:
print(f" WARNING: {platform}: {result.stderr.strip()[:100]}")
else:
files = [f.name for f in dest.iterdir()]
print(f" {platform}: {files}")
def main():
version = sys.argv[1] if len(sys.argv) > 1 else None
if version:
bump_version_files(version)
download_cache_artifacts()
zip_name = f"CppMode-{version}.zip" if version else "CppMode.zip"
zip_path = os.path.join(OUTPUT_DIR, zip_name)
if os.path.exists(zip_path):
os.remove(zip_path)
included = 0
skipped = 0
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(CPPMODE_DIR):
# Prune .git/.github entirely -- never even descend into
# them. cache/<platform> directories are walked normally
# (their FOLDER STRUCTURE is kept), only individual .o
# files inside them get filtered out below.
dirs[:] = [
d for d in dirs
if not should_prune_dir(os.path.join(root, d))
]
# If this directory ends up with no files going into the
# zip -- either because it was ALREADY empty (e.g. a fresh
# cache/windows-x64/ that's never been built into yet) or
# BECAME empty after filtering out its .o files -- add an
# explicit directory entry so the folder still exists after
# extraction. Zip files don't store empty directories
# unless told to, and this needs to cover both cases, not
# just the "had files, now has none" case.
rel_dir = os.path.relpath(root, CPPMODE_DIR)
kept_any_file_here = any(
not should_skip_file(os.path.join(root, f)) for f in files
)
if not kept_any_file_here:
dir_arcname = os.path.join("CppMode", rel_dir) + "/"
zf.write(root, dir_arcname)
for fname in files:
full_path = os.path.join(root, fname)
if should_skip_file(full_path):
skipped += 1
continue
# Store paths inside the zip relative to CppMode's own
# parent, so extracting the zip recreates a top-level
# "CppMode/" folder rather than dumping files loose.
arcname = os.path.join(
"CppMode", os.path.relpath(full_path, CPPMODE_DIR)
)
zf.write(full_path, arcname)
included += 1
size_mb = os.path.getsize(zip_path) / (1024 * 1024)
print(f"Created {zip_path} ({size_mb:.2f} MB)")
print(f" {included} files included, {skipped} cache files skipped")
if __name__ == "__main__":
main()