Skip to content

Commit 2c2dcda

Browse files
committed
ci: clean windows disk space in background
1 parent a153133 commit 2c2dcda

File tree

5 files changed

+183
-1
lines changed

5 files changed

+183
-1
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ jobs:
223223
cd src/ci/citool
224224
CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=../../../build/citool cargo build
225225
226+
- name: wait for Windows disk cleanup to finish
227+
if: ${{ matrix.free_disk && startsWith(matrix.os, 'windows-') }}
228+
run: |
229+
python3 src/ci/scripts/free-disk-space-windows-wait.py
230+
226231
- name: run the build
227232
run: |
228233
set +e
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Start freeing disk space on Windows in the background by launching
3+
the PowerShell cleanup script, and recording the PID in a file,
4+
so later steps can wait for completion.
5+
"""
6+
7+
import subprocess
8+
from pathlib import Path
9+
from free_disk_space_windows_util import get_pid_file, get_log_file, run
10+
11+
12+
def get_cleanup_script() -> Path:
13+
script_dir = Path(__file__).resolve().parent
14+
cleanup_script = script_dir / "free-disk-space-windows.ps1"
15+
if not cleanup_script.exists():
16+
raise Exception(f"Cleanup script '{cleanup_script}' not found")
17+
return cleanup_script
18+
19+
20+
def write_pid(pid: int):
21+
pid_file = get_pid_file()
22+
if pid_file.exists():
23+
raise Exception(f"Pid file '{pid_file}' already exists")
24+
pid_file.write_text(str(pid))
25+
print(
26+
f"::notice file={__file__}::Started free-disk-space cleanup in background."
27+
f"pid={pid}; pid_file: {pid_file}"
28+
)
29+
30+
31+
def launch_cleanup_process():
32+
cleanup_script = get_cleanup_script()
33+
log_file_path = get_log_file()
34+
# Launch the PowerShell cleanup in the background and redirect logs
35+
try:
36+
with open(log_file_path, "w", encoding="utf-8") as log_file:
37+
proc = subprocess.Popen(
38+
[
39+
"pwsh",
40+
# Suppress PowerShell startup banner/logo for cleaner logs.
41+
"-NoLogo",
42+
# Don't load user/system profiles. Ensures a clean, predictable environment.
43+
"-NoProfile",
44+
# Disable interactive prompts. Required for CI to avoid hangs.
45+
"-NonInteractive",
46+
# Execute the specified script file (next argument).
47+
"-File",
48+
str(cleanup_script),
49+
],
50+
# Write child stdout to the log file
51+
stdout=log_file,
52+
# Merge stderr into stdout for a single, ordered log stream
53+
stderr=subprocess.STDOUT,
54+
)
55+
return proc
56+
except FileNotFoundError as e:
57+
raise Exception(
58+
"pwsh not found on PATH; cannot start disk cleanup."
59+
) from e
60+
61+
62+
def main() -> int:
63+
proc = launch_cleanup_process()
64+
write_pid(proc.pid)
65+
return 0
66+
67+
68+
if __name__ == "__main__":
69+
run(main)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
Wait for the background Windows disk cleanup process.
3+
"""
4+
5+
import ctypes
6+
from pathlib import Path
7+
import sys
8+
import time
9+
from free_disk_space_windows_util import get_pid_file, get_log_file, run
10+
11+
12+
def is_process_running(pid: int) -> bool:
13+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
14+
processHandle = ctypes.windll.kernel32.OpenProcess(
15+
PROCESS_QUERY_LIMITED_INFORMATION, 0, pid
16+
)
17+
if processHandle == 0:
18+
# The process is not running.
19+
# If you don't have the sufficient rights to check if a process is running,
20+
# zero is also returned. But in GitHub Actions we have these rights.
21+
return False
22+
else:
23+
ctypes.windll.kernel32.CloseHandle(processHandle)
24+
return True
25+
26+
27+
def print_logs():
28+
"""Print the logs from the cleanup script."""
29+
log_file = get_log_file()
30+
if log_file.exists():
31+
print("free-disk-space logs:")
32+
# Print entire log; replace undecodable bytes to avoid exceptions.
33+
try:
34+
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
35+
print(f.read())
36+
except Exception as e:
37+
raise Exception(f"Failed to read log file '{log_file}'") from e
38+
else:
39+
print(f"::warning::Log file '{log_file}' not found")
40+
41+
42+
def read_pid_from_file(pid_file: Path) -> int:
43+
"""Read the PID from the pid file."""
44+
pid_file_content = pid_file.read_text().strip()
45+
46+
# Delete the file if it exists
47+
pid_file.unlink(missing_ok=True)
48+
49+
try:
50+
# Read the first line and convert to int
51+
pid = int(pid_file_content.splitlines()[0])
52+
return pid
53+
except Exception as e:
54+
raise Exception(
55+
f"Error while parsing the pid file with content '{pid_file_content!r}'"
56+
) from e
57+
58+
59+
def main() -> int:
60+
pid_file = get_pid_file()
61+
62+
if not pid_file.exists():
63+
raise Exception(
64+
f"No background free-disk-space process to wait for: pid file {pid_file} not found"
65+
)
66+
67+
pid = read_pid_from_file(pid_file)
68+
69+
# Poll until process exits
70+
while is_process_running(pid):
71+
time.sleep(3)
72+
73+
print_logs()
74+
75+
return 0
76+
77+
78+
if __name__ == "__main__":
79+
run(main)

src/ci/scripts/free-disk-space.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -euo pipefail
44
script_dir=$(dirname "$0")
55

66
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
7-
pwsh $script_dir/free-disk-space-windows.ps1
7+
python3 "$script_dir/free-disk-space-windows-start.py"
88
else
99
$script_dir/free-disk-space-linux.sh
1010
fi
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Utilities for Windows disk space cleanup scripts.
3+
"""
4+
5+
import os
6+
from pathlib import Path
7+
import sys
8+
9+
10+
def get_temp_dir() -> Path:
11+
"""Get the temporary directory set by GitHub Actions."""
12+
return Path(os.environ.get("RUNNER_TEMP"))
13+
14+
15+
def get_pid_file() -> Path:
16+
return get_temp_dir() / "free-disk-space.pid"
17+
18+
19+
def get_log_file() -> Path:
20+
return get_temp_dir() / "free-disk-space.log"
21+
22+
23+
def run(fn):
24+
exit_code = 1
25+
try:
26+
exit_code = fn()
27+
except Exception as e:
28+
print(f"::error::{e}")
29+
sys.exit(exit_code)

0 commit comments

Comments
 (0)