mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
fix(setup): repair a moved checkout instead of keeping stale paths
Moving the checkout to another path or account left three kinds of stale state behind (issue #142): - configure.py kept the previous home's default prefix. A saved prefix that was exactly <previous home>/.darling now follows the current host home; a user-chosen prefix is kept, and one inside the previous home is reported with the --prefix escape hatch. - build/runtime/build was an absolute symlink, so it dangled after a move and the "not link.exists()" guard treated the dangling entry as absent. It is now a relative link and an absolute or dangling leftover is replaced. A real directory at that path is reported and left untouched. - A CMake cache generated for another source directory made the build fail with permission errors against the old path. configure.py now reports the cache and the exact directory to remove, and never deletes it itself. Patches are rendered at prepare.py time, so the report also points at the re-render path. Documents the moved-checkout steps and the opt-in RenderDoc diagnostic, which still expects files no setup script creates. Adds 7 tests to tests/test_setup_portability.py. Signed-off-by: zero <zero@localhost>
This commit is contained in:
1 parent
24276b91c6
commit
2f594b568d
3 files changed
+170
-4
No files matched your search
@@ -453,6 +453,31 @@ A separate multi-window path stopped the active async presenter when another dif
|
||||
- **Prebuilt Swift file contains Git LFS pointer text:** install Git LFS and fetch the original upstream assets; pointer text is not a usable library.
|
||||
- **Submodule fetch stops with no output right after `src/external/sqlite`:** the next submodule is `src/external/swift`, whose pinned `.lfsconfig` points at the retired `git-lfs.darlinghq.org`. The checkout is blocked on that host, not on sqlite. `prepare.py` redirects that submodule's LFS requests to GitHub. To finish a fetch that was interrupted before any patches were applied, run `git -C build/darling-src -c lfs.url=https://github.com/darlinghq/darling-swift.git/info/lfs submodule update --init --recursive --progress`, then re-run `./scripts/build.sh`. Do not run that submodule update once `prepare.py` has applied patches: it resets every submodule working tree to the pinned revision and discards the patches.
|
||||
|
||||
## Checkout moved to another path or account
|
||||
|
||||
`build/config.json`, the generated helpers and the CMake cache record absolute paths, so a checkout that was moved (for example from `/root/darling-lol` to `/home/<user>/darling-lol`, or renamed while staying in the same account) keeps pointing at the old location ([issue #142](https://github.com/lucannez64/darling-lol/issues/142)). `scripts/configure.py` now repairs the parts it owns and reports the rest on stderr:
|
||||
|
||||
- **Prefix:** a saved prefix that was exactly `<previous home>/.darling` follows the new host home. A prefix you chose yourself is kept, and a prefix inside the previous home prints a reminder to pass `--prefix` explicitly.
|
||||
- **`build/runtime/build`:** recreated as a relative link, so it survives later moves. An absolute or dangling leftover is replaced; a real directory at that path is reported and left untouched.
|
||||
- **CMake cache:** a `build/darling-src/build/CMakeCache.txt` whose `CMAKE_HOME_DIRECTORY` names a different checkout is reported, together with the path to remove. It is never deleted automatically because it can hold local build state.
|
||||
|
||||
After moving a checkout, run:
|
||||
|
||||
```sh
|
||||
python3 scripts/configure.py
|
||||
```
|
||||
|
||||
If the warning names another source directory, the cache still holds object files and dependency lists for the old path, and the build fails with `Permission denied` or `No such file or directory` against it. Remove it and rebuild (as the same user that owns the checkout):
|
||||
|
||||
```sh
|
||||
rm -rf build/darling-src/build
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
Patches are rendered at `scripts/prepare.py` time, so `@PROJECT_ROOT@` in a patched file keeps the old path until it runs again. `./scripts/build.sh` and `./scripts/update.sh` both do that; do not run `prepare.py` by hand while the checkout has other edits in `build/darling-src`, because it refuses rather than resetting them.
|
||||
|
||||
The opt-in RenderDoc capture bridge (`RCLOL_RENDERDOC_CAPTURE=1`) is a developer diagnostic, not part of the installer. It expects `build/renderdoc/capture-bridge.so` and a RenderDoc build under `build/renderdoc/`, and neither setup script creates them. Without those files the capture is skipped and the game runs normally; the absolute path baked into the Indium patch is refreshed by the same `prepare.py` run.
|
||||
|
||||
## launchd crashes at boot after changing compiler flags
|
||||
|
||||
`darling shell` fails with `Error connecting to shellspawn` and `/tmp/lolsig.log` shows `LOLSIGFAIL pid=1 sig=11 code=1 addr=0x0` in `memmove`, called from `snprintf` in libkqueue's `epoll_event_dump`. The binaries were tuned for AMD Zen: clang 23 with `-mtune=znver*` (also selected by `-march=native` or `-mtune=native` on Zen) loads a thread-local variable's descriptor into a register other than `%rdi`, but dyld's `tlv_get_addr` reads `%rdi`, so every `__thread` and `thread_local` access returns NULL. Rebuild with `-mtune=generic` (the default in `scripts/build.sh` and the overlay builders), and check any binary with:
|
||||
|
||||
+60
-4
@@ -1,15 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render local paths into ignored runtime tools; never alter the source snapshot."""
|
||||
import argparse,json,os,shutil
|
||||
import argparse,json,os,shutil,sys
|
||||
from pathlib import Path
|
||||
from common import ROOT,configuration,render
|
||||
|
||||
def warn(message):
|
||||
"""Diagnostics go to stderr: launcher/run.sh keep stdout for their own output."""
|
||||
print('configure:',message,file=sys.stderr)
|
||||
|
||||
def rehome_prefix(c,requested):
|
||||
"""The default prefix is derived from the host home, so a checkout that moved to another
|
||||
machine or account must follow the new home instead of keeping the previous one (issue #142)."""
|
||||
home=Path.home()
|
||||
if requested is not None:return str(requested)
|
||||
saved=c.get('prefix')
|
||||
if not saved:return str(home/'.darling')
|
||||
old_home=c.get('host_home')
|
||||
if old_home and old_home!=str(home):
|
||||
if saved==str(Path(old_home)/'.darling'):
|
||||
warn(f'previous home {old_home} is gone; the default prefix moved to {home/".darling"}')
|
||||
return str(home/'.darling')
|
||||
try:relative=Path(saved).is_relative_to(old_home)
|
||||
except (TypeError,ValueError):relative=False
|
||||
if relative:
|
||||
warn(f'prefix {saved} is inside the previous home {old_home}; pass --prefix to move it')
|
||||
return saved
|
||||
|
||||
def link_build(runtime):
|
||||
"""build/runtime/build must resolve to this checkout's build directory. A relative link keeps
|
||||
working after the checkout moves, and any absolute or dangling leftover is replaced instead of
|
||||
being treated as already correct (issue #142)."""
|
||||
link=runtime/'build'
|
||||
target='..' # build/runtime/build -> build
|
||||
if link.is_symlink():
|
||||
if os.readlink(link)==target:return
|
||||
link.unlink()
|
||||
elif os.path.lexists(link):
|
||||
warn(f'{link} is not a symlink; leaving it untouched')
|
||||
return
|
||||
link.symlink_to(target,target_is_directory=True)
|
||||
|
||||
def stale_cache(build):
|
||||
"""Return (cache, recorded source directory) when the CMake cache belongs to another checkout."""
|
||||
cache=build/'darling-src/build/CMakeCache.txt'
|
||||
try:lines=cache.read_text(errors='replace').splitlines()
|
||||
except OSError:return None
|
||||
expected=os.path.realpath(build/'darling-src')
|
||||
for line in lines:
|
||||
key,_,value=line.partition('=')
|
||||
if key.split(':')[0]!='CMAKE_HOME_DIRECTORY':continue
|
||||
if os.path.realpath(value)!=expected:return cache,value
|
||||
return None
|
||||
return None
|
||||
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--guest-user');p.add_argument('--prefix',type=Path);p.add_argument('--sdk',type=Path)
|
||||
a=p.parse_args();c=configuration()
|
||||
if a.guest_user:
|
||||
if not a.guest_user.replace('_','').replace('-','').isalnum():p.error('guest user must be a simple account name')
|
||||
c['guest_user']=a.guest_user;c['guest_home']='/Users/'+a.guest_user
|
||||
if a.prefix:c['prefix']=str(a.prefix.expanduser().resolve())
|
||||
# Resolve the prefix before overwriting host_home, so it can still see the previous home.
|
||||
c['prefix']=rehome_prefix(c,a.prefix.expanduser().resolve() if a.prefix else None)
|
||||
if a.sdk:c['sdk']=str(a.sdk.expanduser().resolve())
|
||||
c['project_root']=str(ROOT);c['host_home']=str(Path.home())
|
||||
build=ROOT/'build';build.mkdir(exist_ok=True)
|
||||
@@ -23,6 +74,11 @@ for source in (ROOT/'tools').iterdir():
|
||||
target.unlink(missing_ok=True)
|
||||
target.write_text(render(source.read_text(),c))
|
||||
os.chmod(target,source.stat().st_mode & 0o777)
|
||||
link=folder.parent/'build'
|
||||
if not link.exists():link.symlink_to(build,target_is_directory=True)
|
||||
link_build(folder.parent)
|
||||
stale=stale_cache(build)
|
||||
if stale:
|
||||
cache,previous=stale
|
||||
warn(f'{cache} was generated for {previous}, not {build/"darling-src"}; builds will fail with '
|
||||
f'missing files or permission errors against the old path. Remove {build/"darling-src/build"} '
|
||||
'and build again (it is never removed automatically because it may hold local work).')
|
||||
print('Configured',len(list(folder.iterdir())),'runtime sources for',c['guest_home'])
|
||||
@@ -120,6 +120,91 @@ exec REAL_INSTALL "$@"'''.replace('REAL_INSTALL', shutil.which('install')))
|
||||
self.assertFalse(target.is_symlink())
|
||||
|
||||
|
||||
def write_config(self, **changes):
|
||||
(self.root / 'build/config.json').write_text(json.dumps(dict(self.config, **changes)))
|
||||
|
||||
|
||||
def config_value(self, key):
|
||||
return json.loads((self.root / 'build/config.json').read_text())[key]
|
||||
|
||||
|
||||
def test_configure_rehomes_default_prefix_after_home_change(self):
|
||||
previous = Path(self.tmp.name) / 'previous home'
|
||||
self.write_config(host_home=str(previous), prefix=str(previous / '.darling'))
|
||||
result = self.run_script('scripts/configure.py')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(self.config_value('prefix'), str(self.home / '.darling'))
|
||||
self.assertIn('previous home', result.stderr)
|
||||
|
||||
|
||||
def test_configure_keeps_explicit_prefix_after_home_change(self):
|
||||
previous = Path(self.tmp.name) / 'previous home'
|
||||
self.write_config(host_home=str(previous), prefix=str(self.prefix))
|
||||
self.configure()
|
||||
self.assertEqual(self.config_value('prefix'), str(self.prefix))
|
||||
self.assertEqual(self.config_value('host_home'), str(self.home))
|
||||
|
||||
|
||||
def test_configure_warns_about_prefix_inside_previous_home(self):
|
||||
previous = Path(self.tmp.name) / 'previous home'
|
||||
self.write_config(host_home=str(previous), prefix=str(previous / 'custom prefix'))
|
||||
result = self.run_script('scripts/configure.py')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(self.config_value('prefix'), str(previous / 'custom prefix'))
|
||||
self.assertIn('--prefix', result.stderr)
|
||||
|
||||
|
||||
def test_configure_creates_relative_build_symlink(self):
|
||||
self.configure()
|
||||
link = self.root / 'build/runtime/build'
|
||||
self.assertEqual(os.readlink(link), '..')
|
||||
self.assertEqual(link.resolve(), (self.root / 'build').resolve())
|
||||
|
||||
|
||||
def test_configure_repairs_absolute_and_dangling_build_symlink(self):
|
||||
link = self.root / 'build/runtime/build'
|
||||
link.parent.mkdir(parents=True)
|
||||
link.symlink_to(self.root / 'build')
|
||||
self.configure()
|
||||
self.assertEqual(os.readlink(link), '..')
|
||||
link.unlink()
|
||||
link.symlink_to(self.root / 'moved away/build')
|
||||
self.configure()
|
||||
self.assertTrue(link.is_symlink())
|
||||
self.assertEqual(link.resolve(), (self.root / 'build').resolve())
|
||||
|
||||
|
||||
def test_configure_leaves_foreign_build_directory(self):
|
||||
link = self.root / 'build/runtime/build'
|
||||
link.mkdir(parents=True)
|
||||
(link / 'keep').write_text('local build')
|
||||
result = self.run_script('scripts/configure.py')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertFalse(link.is_symlink())
|
||||
self.assertEqual((link / 'keep').read_text(), 'local build')
|
||||
self.assertIn('not a symlink', result.stderr)
|
||||
|
||||
|
||||
def test_configure_warns_about_stale_cmake_cache(self):
|
||||
cache = self.root / 'build/darling-src/build/CMakeCache.txt'
|
||||
cache.parent.mkdir(parents=True)
|
||||
cache.write_text('// generated\nCMAKE_HOME_DIRECTORY:INTERNAL=/root/old checkout/build/darling-src\n')
|
||||
result = self.run_script('scripts/configure.py')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue(cache.is_file(), 'configure removed the CMake cache')
|
||||
self.assertIn('/root/old checkout/build/darling-src', result.stderr)
|
||||
self.assertIn('Remove', result.stderr)
|
||||
|
||||
|
||||
def test_configure_accepts_current_cmake_cache(self):
|
||||
cache = self.root / 'build/darling-src/build/CMakeCache.txt'
|
||||
cache.parent.mkdir(parents=True)
|
||||
cache.write_text(f'CMAKE_HOME_DIRECTORY:INTERNAL={self.root}/build/darling-src\n')
|
||||
result = self.run_script('scripts/configure.py')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(result.stderr, '')
|
||||
|
||||
|
||||
def test_presentation_creates_prerequisites_and_repeat_install(self):
|
||||
self.configure()
|
||||
self.runtime_stubs()
|
||||
|
||||
Reference in new issue
Block a user