Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/cache.py
4128 views
1
# Copyright 2013 The Emscripten Authors. All rights reserved.
2
# Emscripten is available under two separate licenses, the MIT license and the
3
# University of Illinois/NCSA Open Source License. Both these licenses can be
4
# found in the LICENSE file.
5
6
"""Permanent cache for system libraries and ports.
7
"""
8
9
import contextlib
10
import logging
11
import os
12
from pathlib import Path
13
14
from . import filelock, config, utils
15
from .settings import settings
16
17
logger = logging.getLogger('cache')
18
19
20
acquired_count = 0
21
cachedir = None
22
cachelock = None
23
cachelock_name = None
24
25
26
def is_writable(path):
27
return os.access(path, os.W_OK)
28
29
30
def acquire_cache_lock(reason):
31
global acquired_count
32
if config.FROZEN_CACHE:
33
# Raise an exception here rather than exit_with_error since in practice this
34
# should never happen
35
raise Exception('Attempt to lock the cache but FROZEN_CACHE is set')
36
37
if not is_writable(cachedir):
38
utils.exit_with_error(f'cache directory "{cachedir}" is not writable while accessing cache for: {reason} (see https://emscripten.org/docs/tools_reference/emcc.html for info on setting the cache directory)')
39
40
if acquired_count == 0:
41
logger.debug(f'PID {os.getpid()} acquiring multiprocess file lock to Emscripten cache at {cachedir}')
42
assert 'EM_CACHE_IS_LOCKED' not in os.environ, f'attempt to lock the cache while a parent process is holding the lock ({reason})'
43
try:
44
cachelock.acquire(10 * 60)
45
except filelock.Timeout:
46
logger.warning(f'Accessing the Emscripten cache at "{cachedir}" (for "{reason}") is taking a long time, another process should be writing to it. If there are none and you suspect this process has deadlocked, try deleting the lock file "{cachelock_name}" and try again. If this occurs deterministically, consider filing a bug.')
47
cachelock.acquire()
48
49
os.environ['EM_CACHE_IS_LOCKED'] = '1'
50
logger.debug('done')
51
acquired_count += 1
52
53
54
def release_cache_lock():
55
global acquired_count
56
acquired_count -= 1
57
assert acquired_count >= 0, "Called release more times than acquire"
58
if acquired_count == 0:
59
assert os.environ['EM_CACHE_IS_LOCKED'] == '1'
60
del os.environ['EM_CACHE_IS_LOCKED']
61
cachelock.release()
62
logger.debug(f'PID {os.getpid()} released multiprocess file lock to Emscripten cache at {cachedir}')
63
64
65
@contextlib.contextmanager
66
def lock(reason):
67
"""A context manager that performs actions in the given directory."""
68
acquire_cache_lock(reason)
69
try:
70
yield
71
finally:
72
release_cache_lock()
73
74
75
def ensure():
76
ensure_setup()
77
if not os.path.isdir(cachedir):
78
try:
79
utils.safe_ensure_dirs(cachedir)
80
except Exception as e:
81
utils.exit_with_error(f'unable to create cache directory "{cachedir}": {e} (see https://emscripten.org/docs/tools_reference/emcc.html for info on setting the cache directory)')
82
83
84
def erase():
85
ensure_setup()
86
with lock('erase'):
87
# Delete everything except the lockfile itself
88
utils.delete_contents(cachedir, exclude=[os.path.basename(cachelock_name)])
89
90
91
def get_path(name):
92
ensure_setup()
93
return Path(cachedir, name)
94
95
96
def get_sysroot(absolute):
97
ensure_setup()
98
if absolute:
99
return os.path.join(cachedir, 'sysroot')
100
return 'sysroot'
101
102
103
def get_include_dir(*parts):
104
return str(get_sysroot_dir('include', *parts))
105
106
107
def get_sysroot_dir(*parts):
108
return str(Path(get_sysroot(absolute=True), *parts))
109
110
111
def get_lib_dir(absolute):
112
ensure_setup()
113
path = Path(get_sysroot(absolute=absolute), 'lib')
114
if settings.MEMORY64:
115
path = Path(path, 'wasm64-emscripten')
116
else:
117
path = Path(path, 'wasm32-emscripten')
118
# if relevant, use a subdir of the cache
119
subdir = []
120
if settings.LTO:
121
if settings.LTO == 'thin':
122
subdir.append('thinlto')
123
else:
124
subdir.append('lto')
125
if settings.RELOCATABLE:
126
subdir.append('pic')
127
if subdir:
128
path = Path(path, '-'.join(subdir))
129
return path
130
131
132
def get_lib_name(name, absolute=False):
133
return str(get_lib_dir(absolute=absolute).joinpath(name))
134
135
136
def erase_lib(name):
137
erase_file(get_lib_name(name))
138
139
140
def erase_file(shortname):
141
with lock('erase: ' + shortname):
142
name = Path(cachedir, shortname)
143
if name.exists():
144
logger.info(f'deleting cached file: {name}')
145
utils.delete_file(name)
146
147
148
def get_lib(libname, *args, **kwargs):
149
name = get_lib_name(libname)
150
return get(name, *args, **kwargs)
151
152
153
# Request a cached file. If it isn't in the cache, it will be created with
154
# the given creator function
155
def get(shortname, creator, what=None, force=False, quiet=False):
156
ensure_setup()
157
cachename = Path(cachedir, shortname)
158
# Check for existence before taking the lock in case we can avoid the
159
# lock completely.
160
if cachename.exists() and not force:
161
return str(cachename)
162
163
if config.FROZEN_CACHE:
164
# Raise an exception here rather than exit_with_error since in practice this
165
# should never happen
166
raise Exception(f'FROZEN_CACHE is set, but cache file is missing: "{shortname}" (in cache root path "{cachedir}")')
167
168
with lock(shortname):
169
if cachename.exists() and not force:
170
return str(cachename)
171
if what is None:
172
if shortname.endswith(('.bc', '.so', '.a')):
173
what = 'system library'
174
else:
175
what = 'system asset'
176
message = f'generating {what}: {shortname}... (this will be cached in "{cachename}" for subsequent builds)'
177
logger.info(message)
178
utils.safe_ensure_dirs(cachename.parent)
179
creator(str(cachename))
180
# In embuilder/deferred building mode, the library is not actually compiled at
181
# "creation" time; instead, the ninja files are built up incrementally, and
182
# compiled all at once with a single ninja invocation. So in that case we
183
# can't assert that the library was correctly built here.
184
if not os.getenv('EMBUILDER_PORT_BUILD_DEFERRED'):
185
assert cachename.is_file()
186
if not quiet:
187
logger.info(' - ok')
188
189
return str(cachename)
190
191
192
def setup():
193
global cachedir, cachelock, cachelock_name
194
# figure out the root directory for all caching
195
cachedir = Path(config.CACHE).resolve()
196
197
# since the lock itself lives inside the cache directory we need to ensure it
198
# exists.
199
ensure()
200
cachelock_name = Path(cachedir, 'cache.lock')
201
cachelock = filelock.FileLock(cachelock_name)
202
203
204
def ensure_setup():
205
if not cachedir:
206
setup()
207
208