Skip to content

Commit 6f615f5

Browse files
authored
Fix sysroot installation when emscripten itself is read-only (#24405)
The resulting header tree need to be at least user-writable so that we can incrementally copy files into it, adding new files and also updating previously copied ones. Sadly, python's shutil doesn't seem to have any options for disabling the copying of mode bits. This was originally fixed in #15386 but then regressed in #23417. Fixes: #24404
1 parent fdad3e9 commit 6f615f5

File tree

3 files changed

+45
-6
lines changed

3 files changed

+45
-6
lines changed

test/test_sanity.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import platform
99
import shutil
10+
import stat
1011
import time
1112
import re
1213
import tempfile
@@ -35,6 +36,14 @@ def restore():
3536
shutil.copyfile(EM_CONFIG + '_backup', EM_CONFIG)
3637

3738

39+
def for_all_files(dir, callback):
40+
for root, dirs, files in os.walk(dir):
41+
for d in dirs:
42+
callback(os.path.join(dir, root, d))
43+
for f in files:
44+
callback(os.path.join(dir, root, f))
45+
46+
3847
# restore the config file and set it up for our uses
3948
def restore_and_set_up():
4049
restore()
@@ -460,6 +469,23 @@ def test_emcc_multiprocess_cache_access(self):
460469
# Exactly one child process should have triggered libc build!
461470
self.assertEqual(num_times_libc_was_built, 1)
462471

472+
# Test that sysroot headers can be installed from a read-only
473+
# emscripten tree.
474+
def test_readonly_sysroot_install(self):
475+
restore_and_set_up()
476+
477+
def make_readonly(filename):
478+
old_mode = stat.S_IMODE(os.stat(filename).st_mode)
479+
os.chmod(filename, old_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
480+
481+
try:
482+
for_all_files(path_from_root('system/include'), make_readonly)
483+
484+
with env_modify({'EM_CACHE': self.in_dir('test_cache')}):
485+
self.run_process([EMCC, test_file('hello_world.c'), '-c'])
486+
finally:
487+
for_all_files(path_from_root('system/include'), shared.make_writable)
488+
463489
@parameterized({
464490
'': [False, False],
465491
'response_files': [True, False],

tools/shared.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ def get_file_suffix(filename):
694694

695695

696696
def make_writable(filename):
697-
assert os.path.isfile(filename)
697+
assert os.path.exists(filename)
698698
old_mode = stat.S_IMODE(os.stat(filename).st_mode)
699699
os.chmod(filename, old_mode | stat.S_IWUSR)
700700

tools/system_libs.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2453,8 +2453,21 @@ def calculate(options):
24532453
return ret
24542454

24552455

2456-
def copytree_exist_ok(src, dst):
2457-
shutil.copytree(src, dst, dirs_exist_ok=True)
2456+
def safe_copytree(src, dst):
2457+
# We cannot use `shutil.copytree` there because we need to ensure the
2458+
# output tree is writable, and in some cases the emscripten tree
2459+
# itself is readonly (e.g. NixOS).
2460+
# Even if we pass copy_function=safe_copy python's `shutil.copytree`
2461+
# will use its internal logic for copying directories and it will
2462+
# unconditionally copy the source directory's mode bits.
2463+
os.makedirs(dst, exist_ok=True)
2464+
for entry in os.scandir(src):
2465+
srcname = os.path.join(src, entry.name)
2466+
dstname = os.path.join(dst, entry.name)
2467+
if entry.is_dir():
2468+
safe_copytree(srcname, dstname)
2469+
else:
2470+
shared.safe_copy(srcname, dstname)
24582471

24592472

24602473
def install_system_headers(stamp):
@@ -2477,15 +2490,15 @@ def install_system_headers(stamp):
24772490
for src, dest in install_dirs.items():
24782491
src = utils.path_from_root('system', *src)
24792492
dest = os.path.join(target_include_dir, dest)
2480-
copytree_exist_ok(src, dest)
2493+
safe_copytree(src, dest)
24812494

24822495
pkgconfig_src = utils.path_from_root('system/lib/pkgconfig')
24832496
pkgconfig_dest = cache.get_sysroot_dir('lib/pkgconfig')
2484-
copytree_exist_ok(pkgconfig_src, pkgconfig_dest)
2497+
safe_copytree(pkgconfig_src, pkgconfig_dest)
24852498

24862499
bin_src = utils.path_from_root('system/bin')
24872500
bin_dest = cache.get_sysroot_dir('bin')
2488-
copytree_exist_ok(bin_src, bin_dest)
2501+
safe_copytree(bin_src, bin_dest)
24892502

24902503
# Create a version header based on the emscripten-version.txt
24912504
version_file = cache.get_include_dir('emscripten/version.h')

0 commit comments

Comments
 (0)