mirror of
https://github.com/kuraeow/ultratears.git
synced 2026-11-07 10:16:00 -10:00
162 lines
8.4 KiB
Python
162 lines
8.4 KiB
Python
"""What a build for Linux reads of the Linux kit (deps/linux) and of clang's own headers, measured on a game folder that
|
|
built the game for Linux (tools/setup.py --platform linux or both: build-linux/): the packages carry only these
|
|
(tools/make_package.py trims the kit of the Windows package, tools/make_package_linux.py the kit and LLVM of the Linux
|
|
one and of the AppImage), as totk-pc-recomp's make_release_linux.py keeps only the sysroot files its build read.
|
|
|
|
Measured, not guessed: the headers ninja recorded for every compiled file of the game, the headers of the texture
|
|
encoder's sources (tools/bc7_build.py builds it on Linux too; clang -M with the same flags), and what lld reads when it
|
|
links the game and the encoder (--verbose). The result is a list of paths: linux/<path in deps/linux> and
|
|
clang/include/<header> (clang's resource headers, whatever its version).
|
|
|
|
usage: python tools/linux_used.py --home ../release-test [--out dev-local/release-inputs/linux-used.txt]
|
|
"""
|
|
import argparse
|
|
import pathlib
|
|
import posixpath
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / 'tools'))
|
|
|
|
KIT = re.compile(r'[/\\]deps[/\\]linux[/\\](.+)$', re.I)
|
|
RESOURCE = re.compile(r'[/\\]lib[/\\]clang[/\\]\d+[/\\](include[/\\].+)$', re.I)
|
|
# kept whatever the measurement says: the licenses, the kit's own record of what it is, and the SDL3 the game's build
|
|
# copies beside the game (tools/build_game.py: not read by the compiler or lld)
|
|
ALWAYS = ('linux/licenses/', 'linux/README', 'linux/kit.json', 'linux/sysroot/usr/lib/x86_64-linux-gnu/libSDL3.so.0')
|
|
|
|
|
|
def classify(path, used):
|
|
p = path.strip().strip('"')
|
|
m = KIT.search(p)
|
|
if m: # normalized: libstdc++'s headers come as lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/...
|
|
used.add('linux/' + posixpath.normpath(m.group(1).replace('\\', '/')))
|
|
return
|
|
m = RESOURCE.search(p)
|
|
if m:
|
|
used.add('clang/' + posixpath.normpath(m.group(1).replace('\\', '/')))
|
|
|
|
|
|
def tool(name):
|
|
from toolchain import CLANG64
|
|
return str(CLANG64 / (name + ('.exe' if sys.platform == 'win32' else '')))
|
|
|
|
|
|
def measure(home):
|
|
from toolchain import env as tool_env
|
|
import bc7_build
|
|
used = set()
|
|
ninja = home / 'build-linux/build.ninja'
|
|
if not ninja.is_file():
|
|
sys.exit(f'{ninja} is missing: build the game for Linux there first (setup.py --platform linux or both)')
|
|
deps = subprocess.run([tool('ninja'), '-C', str(home), '-f', 'build-linux/build.ninja', '-t', 'deps'],
|
|
capture_output=True, text=True, env=tool_env()).stdout
|
|
for line in deps.splitlines():
|
|
classify(line, used)
|
|
text = ninja.read_text(encoding='utf-8')
|
|
kit = re.search(r'--sysroot=(\S+)/sysroot', text).group(1) # the kit this build used
|
|
flags = ['--target=x86_64-linux-gnu', f'--sysroot={kit}/sysroot', f'-I{kit}/include']
|
|
tmp = ROOT / 'dev-local/build/linux-used-probe'
|
|
tmp.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
# the texture encoder's headers (bc7_build.build_tool's flags for Linux)
|
|
for s in bc7_build.SOURCES_C + bc7_build.SOURCES_CXX:
|
|
cxx = s.endswith('.cpp')
|
|
cmd = [tool('clang++' if cxx else 'clang'), '-M', '-O2', '-march=x86-64-v3', *flags, '-D_FILE_OFFSET_BITS=64',
|
|
'-D_fseeki64=fseeko', '-D_ftelli64=ftello', str(ROOT / s)]
|
|
if cxx:
|
|
cmd += ['-std=c++17', '-fno-exceptions', '-fno-rtti', '-stdlib=libstdc++']
|
|
out = subprocess.run(cmd, capture_output=True, text=True, env=tool_env())
|
|
if out.returncode:
|
|
sys.exit(f'the header probe of {s} failed:\n{out.stderr[-2000:]}')
|
|
for p in re.split(r'\s+', out.stdout.replace('\\\n', ' ')):
|
|
classify(p, used)
|
|
# the launcher's headers: tools/setup.py builds it in a game folder made from the source (build_launcher_into),
|
|
# which may take its tools from a package (the AppImage's deps/)
|
|
import build_launcher
|
|
import deps_install
|
|
deps_install.launcher_libs(ROOT)
|
|
build_launcher.resources(tmp) # its version.h
|
|
inc = [f'-I{ROOT / d}' for d in ('deps/launcher/imgui', 'deps/launcher/imgui/backends', 'deps/launcher/nanosvg',
|
|
'deps/launcher/stb')] + [f'-I{tmp}', '-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS',
|
|
'-march=x86-64-v2']
|
|
for s in build_launcher.SOURCES:
|
|
cxx = s.endswith('.cpp')
|
|
cmd = [tool('clang++' if cxx else 'clang'), '-M', *flags, *inc, str(ROOT / s)]
|
|
if cxx:
|
|
cmd += ['-std=c++17', '-stdlib=libstdc++']
|
|
out = subprocess.run(cmd, capture_output=True, text=True, env=tool_env())
|
|
if out.returncode:
|
|
sys.exit(f'the header probe of {s} failed:\n{out.stderr[-2000:]}')
|
|
for p in re.split(r'\s+', out.stdout.replace('\\\n', ' ')):
|
|
classify(p, used)
|
|
# what lld reads linking the game (its link line, the objects of build-linux), the encoder and the launcher
|
|
link = re.search(r'^rule link\n command = clang (.*) -o \$out @\$out\.rsp (.*)$', text, re.M)
|
|
objs = re.search(r'^build linux/ultratears: link (.*)$', text, re.M).group(1).split()
|
|
probe = tmp / 'probe.cpp'
|
|
probe.write_text('int main() { return 0; }\n')
|
|
rsp = tmp / 'objs.rsp' # ~1500 objects: past Windows' command-line limit
|
|
rsp.write_text('\n'.join((home / o).as_posix() for o in objs if not o.startswith('|')))
|
|
runs = [[tool('clang'), *link.group(1).replace('$$ORIGIN', '$ORIGIN').split(), '-o', str(tmp / 'game'),
|
|
'@' + str(rsp), *link.group(2).split()],
|
|
[tool('clang++'), *flags, '-fuse-ld=lld', '--rtlib=libgcc', '--unwindlib=libgcc', '-stdlib=libstdc++',
|
|
'-static-libstdc++', '-o', str(tmp / 'bc7build'), str(probe), f'-L{kit}/lib', '-l:libzstd.a', '-pthread',
|
|
'-lm'],
|
|
[tool('clang++'), *flags, '--rtlib=libgcc', '--unwindlib=libgcc', '-no-pie', f'-L{kit}/lib', '-fuse-ld=lld',
|
|
'-o', str(tmp / 'launcher'), str(probe),
|
|
*build_launcher.LINUX_LIBS.replace("'$$ORIGIN'", '$ORIGIN').replace('$$ORIGIN', '$ORIGIN').split()]]
|
|
for cmd in runs:
|
|
cmd = [c for c in cmd if c != '-g'] + ['-Wl,--verbose']
|
|
r = subprocess.run(cmd, capture_output=True, text=True, env=tool_env(), cwd=home)
|
|
if r.returncode:
|
|
sys.exit('a probe link failed:\n' + (r.stdout + r.stderr)[-2000:])
|
|
for line in (r.stdout + r.stderr).splitlines():
|
|
classify(line.split(': ')[-1] if line.startswith('ld.lld:') else line, used)
|
|
finally:
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
return used
|
|
|
|
|
|
def keep(rel, used):
|
|
"""Whether the package keeps a file of the kit (linux/<path>) or of clang's headers (clang/include/<name>)."""
|
|
return rel in used or rel.startswith(ALWAYS)
|
|
|
|
|
|
def load(path):
|
|
"""The list linux_used.py wrote."""
|
|
if not path.is_file():
|
|
sys.exit(f'{path} is missing: python tools/linux_used.py --home <a game folder built for Linux> first')
|
|
return set(path.read_text(encoding='utf-8').split())
|
|
|
|
|
|
def trim(top, prefix, used):
|
|
"""Removes the files under top (the kit: prefix 'linux/'; clang's resource dir lib/clang/<n>: prefix 'clang/') that
|
|
the build does not read; returns the bytes removed."""
|
|
gone = 0
|
|
for p in sorted(top.rglob('*'), reverse=True):
|
|
rel = prefix + p.relative_to(top).as_posix()
|
|
if (p.is_file() or p.is_symlink()) and not keep(rel, used):
|
|
gone += 0 if p.is_symlink() else p.stat().st_size
|
|
p.unlink()
|
|
elif p.is_dir() and not p.is_symlink() and not any(p.iterdir()):
|
|
p.rmdir()
|
|
return gone
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('--home', type=pathlib.Path, required=True, help='a game folder that built the game for Linux')
|
|
ap.add_argument('--out', type=pathlib.Path, default=ROOT / 'dev-local/release-inputs/linux-used.txt')
|
|
a = ap.parse_args()
|
|
used = measure(a.home.resolve())
|
|
a.out.parent.mkdir(parents=True, exist_ok=True)
|
|
a.out.write_text('\n'.join(sorted(used)) + '\n', encoding='utf-8')
|
|
print(f'{a.out}: {sum(u.startswith("linux/") for u in used)} files of the Linux kit, '
|
|
f'{sum(u.startswith("clang/") for u in used)} of clang\'s headers')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|