ci: clean windows disk space in background

This commit is contained in:
MarcoIeni 2025-08-14 12:38:35 +02:00
parent a1531335fe
commit 75b7d24a98
No known key found for this signature in database
5 changed files with 184 additions and 1 deletions

View file

@ -223,6 +223,11 @@ jobs:
cd src/ci/citool
CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=../../../build/citool cargo build
- name: wait for Windows disk cleanup to finish
if: ${{ matrix.free_disk && startsWith(matrix.os, 'windows-') }}
run: |
python3 src/ci/scripts/free-disk-space-windows-wait.py
- name: run the build
run: |
set +e

View file

@ -0,0 +1,72 @@
"""
Start freeing disk space on Windows in the background by launching
the PowerShell cleanup script, and recording the PID in a file,
so later steps can wait for completion.
"""
import subprocess
from pathlib import Path
from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
def get_cleanup_script() -> Path:
script_dir = Path(__file__).resolve().parent
cleanup_script = script_dir / "free-disk-space-windows.ps1"
if not cleanup_script.exists():
raise Exception(f"Cleanup script '{cleanup_script}' not found")
return cleanup_script
def write_pid(pid: int):
pid_file = get_pid_file()
if pid_file.exists():
raise Exception(f"Pid file '{pid_file}' already exists")
pid_file.write_text(str(pid))
print(f"wrote pid {pid} in file {pid_file}")
def launch_cleanup_process():
cleanup_script = get_cleanup_script()
log_file_path = get_log_file()
# Launch the PowerShell cleanup in the background and redirect logs.
try:
with open(log_file_path, "w", encoding="utf-8") as log_file:
proc = subprocess.Popen(
[
"pwsh",
# Suppress PowerShell startup banner/logo for cleaner logs.
"-NoLogo",
# Don't load user/system profiles. Ensures a clean, predictable environment.
"-NoProfile",
# Disable interactive prompts. Required for CI to avoid hangs.
"-NonInteractive",
# Execute the specified script file (next argument).
"-File",
str(cleanup_script),
],
# Write child stdout to the log file.
stdout=log_file,
# Merge stderr into stdout for a single, ordered log stream.
stderr=subprocess.STDOUT,
)
print(
f"Started free-disk-space cleanup in background. "
f"pid={proc.pid}; log_file={log_file_path}"
)
return proc
except FileNotFoundError as e:
raise Exception("pwsh not found on PATH; cannot start disk cleanup.") from e
def main() -> int:
proc = launch_cleanup_process()
# Write pid of the process to a file, so that later steps can read it and wait
# until the process completes.
write_pid(proc.pid)
return 0
if __name__ == "__main__":
run_main(main)

View file

@ -0,0 +1,77 @@
"""
Wait for the background Windows disk cleanup process.
"""
import ctypes
import time
from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
def is_process_running(pid: int) -> bool:
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
processHandle = ctypes.windll.kernel32.OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION, 0, pid
)
if processHandle == 0:
# The process is not running.
# If you don't have the sufficient rights to check if a process is running,
# zero is also returned. But in GitHub Actions we have these rights.
return False
else:
ctypes.windll.kernel32.CloseHandle(processHandle)
return True
def print_logs():
"""Print the logs from the cleanup script."""
log_file = get_log_file()
if log_file.exists():
print("free-disk-space logs:")
# Print entire log; replace undecodable bytes to avoid exceptions.
try:
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
print(f.read())
except Exception as e:
raise Exception(f"Failed to read log file '{log_file}'") from e
else:
print(f"::warning::Log file '{log_file}' not found")
def read_pid_from_file() -> int:
"""Read the PID from the pid file."""
pid_file = get_pid_file()
if not pid_file.exists():
raise Exception(
f"No background free-disk-space process to wait for: pid file {pid_file} not found"
)
pid_file_content = pid_file.read_text().strip()
# Delete the file if it exists
pid_file.unlink(missing_ok=True)
try:
# Read the first line and convert to int.
pid = int(pid_file_content.splitlines()[0])
return pid
except Exception as e:
raise Exception(
f"Error while parsing the pid file with content '{pid_file_content!r}'"
) from e
def main() -> int:
pid = read_pid_from_file()
# Poll until process exits
while is_process_running(pid):
time.sleep(3)
print_logs()
return 0
if __name__ == "__main__":
run_main(main)

View file

@ -4,7 +4,7 @@ set -euo pipefail
script_dir=$(dirname "$0")
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
pwsh $script_dir/free-disk-space-windows.ps1
python3 "$script_dir/free-disk-space-windows-start.py"
else
$script_dir/free-disk-space-linux.sh
fi

View file

@ -0,0 +1,29 @@
"""
Utilities for Windows disk space cleanup scripts.
"""
import os
from pathlib import Path
import sys
def get_temp_dir() -> Path:
"""Get the temporary directory set by GitHub Actions."""
return Path(os.environ.get("RUNNER_TEMP"))
def get_pid_file() -> Path:
return get_temp_dir() / "free-disk-space.pid"
def get_log_file() -> Path:
return get_temp_dir() / "free-disk-space.log"
def run_main(main_fn):
exit_code = 1
try:
exit_code = main_fn()
except Exception as e:
print(f"::error::{e}")
sys.exit(exit_code)