Skip to content

Commit b474f89

Browse files
authored
Rollup merge of rust-lang#145311 - marcoieni:clean-disk-in-background-windows, r=Kobzol
ci: clean windows disk space in background
2 parents 87d677b + 75b7d24 commit b474f89

File tree

5 files changed

+184
-1
lines changed

5 files changed

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

0 commit comments

Comments
 (0)