Skip to content

Commit 2f87928

Browse files
committed
Fix sysroot installation when emscripten itself is read-only
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 2ca9ace commit 2f87928

File tree

4 files changed

+49
-8
lines changed

4 files changed

+49
-8
lines changed

test/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,13 +797,13 @@ def make_dir_writeable(dirname):
797797
# Some tests make files and subdirectories read-only, so rmtree/unlink will not delete
798798
# them. Force-make everything writable in the subdirectory to make it
799799
# removable and re-attempt.
800-
os.chmod(dirname, 0o777)
800+
shared.make_writable(dirname)
801801

802802
for directory, subdirs, files in os.walk(dirname):
803803
for item in files + subdirs:
804804
i = os.path.join(directory, item)
805805
if not os.path.islink(i):
806-
os.chmod(i, 0o777)
806+
shared.make_writable(i)
807807

808808

809809
def force_delete_dir(dirname):

test/test_sanity.py

Lines changed: 28 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,25 @@ 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+
473+
474+
# Test that sysroot headers can be installed from a read-only=
475+
# emscripten tree.
476+
def test_readonly_sysroot_install(self):
477+
restore_and_set_up()
478+
479+
def make_readonly(filename):
480+
old_mode = stat.S_IMODE(os.stat(filename).st_mode)
481+
os.chmod(filename, old_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
482+
483+
try:
484+
for_all_files(path_from_root('system/include'), make_readonly)
485+
486+
with env_modify({'EM_CACHE': self.in_dir('test_cache')}):
487+
self.run_process([EMCC, test_file('hello_world.c'), '-c'])
488+
finally:
489+
for_all_files(path_from_root('system/include'), shared.make_writable)
490+
463491
@parameterized({
464492
'': [False, False],
465493
'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)