#!/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-.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//, or anything
inside .git/ or .github/. The cache// 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 len(parts) >= 2 and parts[0] == "cache" and path.endswith(".o"):
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 main():
version = sys.argv[1] if len(sys.argv) > 1 else None
if version:
bump_version_files(version)
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/ 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()