Files
darling-lol/scripts/configure.py
T
zero 2f594b568d 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>
2026-10-25 13:26:54 -06:00

85 lines
4.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Render local paths into ignored runtime tools; never alter the source snapshot."""
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
# 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)
(build/'config.json').write_text(json.dumps(c,indent=2)+'\n')
folder=build/'runtime/tools';folder.mkdir(parents=True,exist_ok=True)
for source in (ROOT/'tools').iterdir():
if not source.is_file():continue
target=folder/source.name
# Unlink the directory entry, including dangling symlinks. Any other error
# must abort rather than following a leftover link or writing through it.
target.unlink(missing_ok=True)
target.write_text(render(source.read_text(),c))
os.chmod(target,source.stat().st_mode & 0o777)
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'])