make_tree_writable: handle junctions and add tests
As found out here, os.walk() by default follows junctions, which we don't want and can even lead to loops: https://github.com/msys2/msys2-autobuild/issues/101#issuecomment-2583121845 Integrate the workaround mentioned in the CPython bug report: https://github.com/python/cpython/issues/67596#issuecomment-1918112817 Since this is Python 3.12+ only and we still support 3.10 make it optional though. This also adds tests, which uncovered some other minor issues: It was not chmoding top-down, which meant that os.walk would skip things if there were no read permissions. So chmod before os.walk() lists the dir.
This commit is contained in:
parent
35ff0b71b6
commit
4f60392b3e
@ -122,6 +122,29 @@ def run_cmd(msys2_root: PathLike, args: Sequence[PathLike], **kwargs: Any) -> No
|
|||||||
check_call([executable, '-lc'] + [shlex_join([str(a) for a in args])], env=env, **kwargs)
|
check_call([executable, '-lc'] + [shlex_join([str(a) for a in args])], env=env, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def make_tree_writable(topdir: PathLike) -> None:
|
||||||
|
# Ensure all files and directories under topdir are writable
|
||||||
|
# (and readable) by owner.
|
||||||
|
# Taken from meson, and adjusted
|
||||||
|
|
||||||
|
def chmod(p: PathLike) -> None:
|
||||||
|
print(p)
|
||||||
|
os.chmod(p, os.stat(p).st_mode | stat.S_IWRITE | stat.S_IREAD)
|
||||||
|
|
||||||
|
chmod(topdir)
|
||||||
|
for root, dirs, files in os.walk(topdir):
|
||||||
|
for d in dirs:
|
||||||
|
chmod(os.path.join(root, d))
|
||||||
|
# Work around Python bug following junctions
|
||||||
|
# https://github.com/python/cpython/issues/67596#issuecomment-1918112817
|
||||||
|
if hasattr(os.path, 'isjunction'): # Python 3.12 only
|
||||||
|
dirs[:] = [d for d in dirs if not os.path.isjunction(os.path.join(root, d))]
|
||||||
|
for fname in files:
|
||||||
|
fpath = os.path.join(root, fname)
|
||||||
|
if os.path.isfile(fpath):
|
||||||
|
chmod(fpath)
|
||||||
|
|
||||||
|
|
||||||
def reset_git_repo(path: PathLike):
|
def reset_git_repo(path: PathLike):
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
@ -129,17 +152,6 @@ def reset_git_repo(path: PathLike):
|
|||||||
check_call(["git", "clean", "-xfdf"], cwd=path)
|
check_call(["git", "clean", "-xfdf"], cwd=path)
|
||||||
check_call(["git", "reset", "--hard", "HEAD"], cwd=path)
|
check_call(["git", "reset", "--hard", "HEAD"], cwd=path)
|
||||||
|
|
||||||
def make_tree_writable(topdir: PathLike) -> None:
|
|
||||||
# Ensure all files and directories under topdir are writable
|
|
||||||
# (and readable) by owner.
|
|
||||||
# Taken from meson
|
|
||||||
for d, _, files in os.walk(topdir):
|
|
||||||
os.chmod(d, os.stat(d).st_mode | stat.S_IWRITE | stat.S_IREAD)
|
|
||||||
for fname in files:
|
|
||||||
fpath = os.path.join(d, fname)
|
|
||||||
if os.path.isfile(fpath):
|
|
||||||
os.chmod(fpath, os.stat(fpath).st_mode | stat.S_IWRITE | stat.S_IREAD)
|
|
||||||
|
|
||||||
made_writable = False
|
made_writable = False
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,7 +1,40 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
|
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from msys2_autobuild.utils import parse_optional_deps
|
from msys2_autobuild.utils import parse_optional_deps
|
||||||
from msys2_autobuild.queue import parse_buildqueue, get_cycles
|
from msys2_autobuild.queue import parse_buildqueue, get_cycles
|
||||||
|
from msys2_autobuild.build import make_tree_writable
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_tree_writable():
|
||||||
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
|
nested_dir = Path(tempdir) / "nested"
|
||||||
|
nested_junction = nested_dir / "junction"
|
||||||
|
nested_dir.mkdir()
|
||||||
|
file_path = nested_dir / "test_file.txt"
|
||||||
|
file_path.write_text("content")
|
||||||
|
|
||||||
|
# Create a junction loop if possible, to make sure we ignore it
|
||||||
|
if hasattr(os.path, 'isjunction') and os.name == 'nt':
|
||||||
|
import _winapi
|
||||||
|
_winapi.CreateJunction(str(nested_dir), str(nested_junction))
|
||||||
|
else:
|
||||||
|
nested_junction.mkdir()
|
||||||
|
|
||||||
|
# Remove permissions
|
||||||
|
for p in [tempdir, nested_dir, file_path, nested_junction]:
|
||||||
|
os.chmod(p, os.stat(p).st_mode & ~stat.S_IWRITE & ~stat.S_IREAD)
|
||||||
|
|
||||||
|
make_tree_writable(tempdir)
|
||||||
|
|
||||||
|
assert os.access(tempdir, os.W_OK) and os.access(tempdir, os.R_OK)
|
||||||
|
assert os.access(nested_dir, os.W_OK) and os.access(nested_dir, os.R_OK)
|
||||||
|
assert os.access(file_path, os.W_OK) and os.access(file_path, os.R_OK)
|
||||||
|
assert os.access(nested_junction, os.W_OK) and os.access(nested_junction, os.R_OK)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_optional_deps():
|
def test_parse_optional_deps():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user