Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/cache.py
6162 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 config, filelock, 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
assert not config.FROZEN_CACHE, 'Cache cannot be erased when FROZEN_CACHE is set'
87
88
with lock('erase'):
89
# Delete everything except the lockfile itself
90
utils.delete_contents(cachedir, exclude=[os.path.basename(cachelock_name)])
91
92
93
def get_path(name):
94
ensure_setup()
95
return Path(cachedir, name)
96
97
98
def get_sysroot(absolute):
99
ensure_setup()
100
if absolute:
101
return os.path.join(cachedir, 'sysroot')
102
return 'sysroot'
103
104
105
def get_include_dir(*parts):
106
return str(get_sysroot_dir('include', *parts))
107
108
109
def get_sysroot_dir(*parts):
110
return str(Path(get_sysroot(absolute=True), *parts))
111
112
113
def get_lib_dir(absolute):
114
ensure_setup()
115
path = Path(get_sysroot(absolute=absolute), 'lib')
116
if settings.MEMORY64:
117
path = Path(path, 'wasm64-emscripten')
118
else:
119
path = Path(path, 'wasm32-emscripten')
120
# if relevant, use a subdir of the cache
121
subdir = []
122
if settings.LTO:
123
if settings.LTO == 'thin':
124
subdir.append('thinlto')
125
else:
126
subdir.append('lto')
127
if settings.RELOCATABLE or settings.MAIN_MODULE:
128
subdir.append('pic')
129
if subdir:
130
path = Path(path, '-'.join(subdir))
131
return path
132
133
134
def get_lib_name(name, absolute=False):
135
return str(get_lib_dir(absolute=absolute).joinpath(name))
136
137
138
def erase_lib(name):
139
erase_file(get_lib_name(name))
140
141
142
def erase_file(shortname):
143
with lock('erase: ' + shortname):
144
name = Path(cachedir, shortname)
145
if name.exists():
146
logger.info(f'deleting cached file: {name}')
147
utils.delete_file(name)
148
149
150
def get_lib(libname, *args, **kwargs):
151
name = get_lib_name(libname)
152
return get(name, *args, **kwargs)
153
154
155
# Request a cached file. If it isn't in the cache, it will be created with
156
# the given creator function
157
def get(shortname, creator, what=None, force=False, quiet=False):
158
ensure_setup()
159
cachename = Path(cachedir, shortname)
160
# Check for existence before taking the lock in case we can avoid the
161
# lock completely.
162
if cachename.exists() and not force:
163
return str(cachename)
164
165
if config.FROZEN_CACHE:
166
# Raise an exception here rather than exit_with_error since in practice this
167
# should never happen
168
raise Exception(f'FROZEN_CACHE is set, but cache file is missing: "{shortname}" (in cache root path "{cachedir}")')
169
170
with lock(shortname):
171
if cachename.exists() and not force:
172
return str(cachename)
173
if what is None:
174
if shortname.endswith(('.bc', '.so', '.a')):
175
what = 'system library'
176
else:
177
what = 'system asset'
178
message = f'generating {what}: {shortname}... (this will be cached in "{cachename}" for subsequent builds)'
179
logger.info(message)
180
utils.safe_ensure_dirs(cachename.parent)
181
creator(str(cachename))
182
# In embuilder/deferred building mode, the library is not actually compiled at
183
# "creation" time; instead, the ninja files are built up incrementally, and
184
# compiled all at once with a single ninja invocation. So in that case we
185
# can't assert that the library was correctly built here.
186
if not os.getenv('EMBUILDER_PORT_BUILD_DEFERRED'):
187
assert cachename.is_file()
188
if not quiet:
189
logger.info(' - ok')
190
191
return str(cachename)
192
193
194
def setup():
195
global cachedir, cachelock, cachelock_name
196
# figure out the root directory for all caching
197
cachedir = Path(config.CACHE)
198
199
# since the lock itself lives inside the cache directory we need to ensure it
200
# exists.
201
ensure()
202
cachelock_name = Path(cachedir, 'cache.lock')
203
cachelock = filelock.FileLock(cachelock_name)
204
205
206
def ensure_setup():
207
if not cachedir:
208
setup()
209
210