Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e092a96

Browse files
committedDec 12, 2024··
SCons: Integrate cache_limit from main repo
1 parent 27ffd8c commit e092a96

File tree

6 files changed

+141
-13
lines changed

6 files changed

+141
-13
lines changed
 

‎.github/actions/godot-cache-restore/action.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ inputs:
66
default: ${{ github.job }}
77
scons-cache:
88
description: The SCons cache path.
9-
default: ${{ github.workspace }}/.scons-cache/
9+
default: ${{ github.workspace }}/.scons_cache/
1010

1111
runs:
1212
using: composite
@@ -18,7 +18,6 @@ runs:
1818
key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }}
1919

2020
restore-keys: |
21-
${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }}
2221
${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}
2322
${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-refs/heads/${{ env.GODOT_BASE_BRANCH }}
2423
${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}

‎.github/actions/godot-cache-save/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ inputs:
66
default: ${{ github.job }}
77
scons-cache:
88
description: The SCons cache path.
9-
default: ${{ github.workspace }}/.scons-cache/
9+
default: ${{ github.workspace }}/.scons_cache/
1010

1111
runs:
1212
using: composite

‎.github/workflows/ci.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ jobs:
9393
cache-name: web-wasm32
9494

9595
env:
96-
SCONS_CACHE: ${{ github.workspace }}/.scons-cache/
9796
EM_VERSION: 3.1.39
9897

9998
steps:
@@ -116,22 +115,22 @@ jobs:
116115

117116
- name: Generate godot-cpp sources only
118117
run: |
119-
scons platform=${{ matrix.platform }} verbose=yes build_library=no ${{ matrix.flags }}
118+
scons platform=${{ matrix.platform }} verbose=yes build_library=no ${{ matrix.flags }} "cache_path=${{ github.workspace }}/.scons_cache"
120119
scons -c
121120
122121
- name: Build godot-cpp (debug)
123122
run: |
124-
scons platform=${{ matrix.platform }} verbose=yes target=template_debug ${{ matrix.flags }}
123+
scons platform=${{ matrix.platform }} verbose=yes target=template_debug ${{ matrix.flags }} "cache_path=${{ github.workspace }}/.scons_cache"
125124
126125
- name: Build test without rebuilding godot-cpp (debug)
127126
run: |
128127
cd test
129-
scons platform=${{ matrix.platform }} verbose=yes target=template_debug ${{ matrix.flags }} build_library=no
128+
scons platform=${{ matrix.platform }} verbose=yes target=template_debug ${{ matrix.flags }} "cache_path=${{ github.workspace }}/.scons_cache" build_library=no
130129
131130
- name: Build test and godot-cpp (release)
132131
run: |
133132
cd test
134-
scons platform=${{ matrix.platform }} verbose=yes target=template_release ${{ matrix.flags }}
133+
scons platform=${{ matrix.platform }} verbose=yes target=template_release ${{ matrix.flags }} "cache_path=${{ github.workspace }}/.scons_cache" cache_limit=1
135134
136135
- name: Save Godot build cache
137136
uses: ./.github/actions/godot-cache-save

‎.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ compile_commands.json
196196
.venv
197197
venv
198198

199+
# Python modules
200+
.*_cache/
201+
199202
# Clion Configuration
200203
.idea/
201204
cmake-build*/

‎SConstruct

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ if unknown:
4040
for item in unknown.items():
4141
print(" " + item[0] + "=" + item[1])
4242

43-
scons_cache_path = os.environ.get("SCONS_CACHE")
44-
if scons_cache_path is not None:
45-
CacheDir(scons_cache_path)
46-
Decider("MD5")
47-
4843
cpp_tool.generate(env)
4944
library = env.GodotCPP()
5045

‎tools/godotcpp.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import platform
33
import sys
4+
from typing import cast
45

56
from SCons.Action import Action
67
from SCons.Builder import Builder
@@ -337,6 +338,10 @@ def options(opts, env):
337338
opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True))
338339
opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False))
339340
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
341+
opts.Add(
342+
"cache_path", "Path to a directory where SCons cache files will be stored. No value disables the cache.", ""
343+
)
344+
opts.Add("cache_limit", "Max size (in GiB) for the SCons cache. 0 means no limit.", "0")
340345

341346
# Add platform options (custom tools can override platforms)
342347
for pl in sorted(set(platforms + custom_platforms)):
@@ -390,7 +395,134 @@ def make_doc_source(target, source, env):
390395
g.close()
391396

392397

398+
def convert_size(size_bytes: int) -> str:
399+
import math
400+
401+
if size_bytes == 0:
402+
return "0 bytes"
403+
SIZE_NAMES = ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
404+
index = math.floor(math.log(size_bytes, 1024))
405+
power = math.pow(1024, index)
406+
size = round(size_bytes / power, 2)
407+
return f"{size} {SIZE_NAMES[index]}"
408+
409+
410+
def get_size(start_path: str = ".") -> int:
411+
total_size = 0
412+
for dirpath, _, filenames in os.walk(start_path):
413+
for file in filenames:
414+
path = os.path.join(dirpath, file)
415+
total_size += os.path.getsize(path)
416+
return total_size
417+
418+
419+
def clean_cache(cache_path: str, cache_limit: int, verbose: bool):
420+
from glob import glob
421+
422+
files = glob(os.path.join(cache_path, "*", "*"))
423+
if not files:
424+
return
425+
426+
# Remove all text files, store binary files in list of (filename, size, atime).
427+
purge = []
428+
texts = []
429+
stats = []
430+
for file in files:
431+
try:
432+
# Save file stats to rewrite after modifying.
433+
tmp_stat = os.stat(file)
434+
# Failing a utf-8 decode is the easiest way to determine if a file is binary.
435+
try:
436+
with open(file, encoding="utf-8") as out:
437+
out.read(1024)
438+
except UnicodeDecodeError:
439+
stats.append((file, *tmp_stat[6:8]))
440+
# Restore file stats after reading.
441+
os.utime(file, (tmp_stat[7], tmp_stat[8]))
442+
else:
443+
texts.append(file)
444+
except OSError:
445+
print(f'Failed to access cache file "{file}"; skipping.')
446+
447+
if texts:
448+
count = len(texts)
449+
for file in texts:
450+
try:
451+
os.remove(file)
452+
except OSError:
453+
print(f'Failed to remove cache file "{file}"; skipping.')
454+
count -= 1
455+
if verbose:
456+
print("Purging %d text %s from cache..." % (count, "files" if count > 1 else "file"))
457+
458+
if cache_limit:
459+
# Sort by most recent access (most sensible to keep) first. Search for the first entry where
460+
# the cache limit is reached.
461+
stats.sort(key=lambda x: x[2], reverse=True)
462+
sum = 0
463+
for index, stat in enumerate(stats):
464+
sum += stat[1]
465+
if sum > cache_limit:
466+
purge.extend([x[0] for x in stats[index:]])
467+
break
468+
469+
if purge:
470+
count = len(purge)
471+
for file in purge:
472+
try:
473+
os.remove(file)
474+
except OSError:
475+
print(f'Failed to remove cache file "{file}"; skipping.')
476+
count -= 1
477+
if verbose:
478+
print("Purging %d %s from cache..." % (count, "files" if count > 1 else "file"))
479+
480+
481+
def prepare_cache(env) -> None:
482+
import atexit
483+
484+
if env.GetOption("clean"):
485+
return
486+
487+
cache_path = ""
488+
if env["cache_path"]:
489+
cache_path = cast(str, env["cache_path"])
490+
elif os.environ.get("SCONS_CACHE"):
491+
print("Environment variable `SCONS_CACHE` is deprecated; use `cache_path` argument instead.")
492+
cache_path = cast(str, os.environ.get("SCONS_CACHE"))
493+
494+
if not cache_path:
495+
return
496+
497+
env.CacheDir(cache_path)
498+
print(f'SCons cache enabled... (path: "{cache_path}")')
499+
500+
if env["cache_limit"]:
501+
cache_limit = float(env["cache_limit"])
502+
elif os.environ.get("SCONS_CACHE_LIMIT"):
503+
print("Environment variable `SCONS_CACHE_LIMIT` is deprecated; use `cache_limit` argument instead.")
504+
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", "0")) / 1024 # Old method used MiB, convert to GiB
505+
506+
# Convert GiB to bytes; treat negative numbers as 0 (unlimited).
507+
cache_limit = max(0, int(cache_limit * 1024 * 1024 * 1024))
508+
if env["verbose"]:
509+
print(
510+
"Current cache limit is {} (used: {})".format(
511+
convert_size(cache_limit) if cache_limit else "∞",
512+
convert_size(get_size(cache_path)),
513+
)
514+
)
515+
516+
atexit.register(clean_cache, cache_path, cache_limit, env["verbose"])
517+
518+
393519
def generate(env):
520+
# Setup caching logic early to catch everything.
521+
prepare_cache(env)
522+
523+
# Renamed to `content-timestamp` in SCons >= 4.2, keeping MD5 for compat.
524+
env.Decider("MD5-timestamp")
525+
394526
# Default num_jobs to local cpu count if not user specified.
395527
# SCons has a peculiarity where user-specified options won't be overridden
396528
# by SetOption, so we can rely on this to know if we should use our default.

0 commit comments

Comments
 (0)
Please sign in to comment.