mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-01 12:45:56 +00:00
94 lines
5.3 KiB
Python
94 lines
5.3 KiB
Python
import os
|
|
from pathlib import Path, PurePosixPath
|
|
import re
|
|
import stat
|
|
import tarfile
|
|
import tempfile
|
|
import zipfile
|
|
|
|
from nid_names import CPP_TOKEN
|
|
|
|
|
|
SYSTEM_HEADERS = frozenset("""algorithm any array atomic barrier bit bitset cassert ccomplex cctype cerrno cfenv cfloat charconv chrono
|
|
cinttypes ciso646 climits clocale cmath codecvt compare complex concepts condition_variable coroutine
|
|
csetjmp csignal cstdalign cstdarg cstdbool cstddef cstdint cstdio cstdlib cstring ctgmath ctime cuchar cwchar cwctype
|
|
deque exception execution filesystem format forward_list fstream functional future initializer_list iomanip
|
|
ios iosfwd iostream istream iterator latch limits list locale map memory memory_resource mutex new numbers
|
|
numeric optional ostream queue random ranges ratio regex scoped_allocator semaphore set shared_mutex source_location
|
|
span sstream stack stdexcept stop_token streambuf string string_view strstream syncstream system_error thread tuple
|
|
type_traits typeindex typeinfo unordered_map unordered_set utility valarray variant vector version
|
|
assert.h complex.h ctype.h errno.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h setjmp.h signal.h
|
|
stdalign.h stdarg.h stdatomic.h stdbool.h stddef.h stdint.h stdio.h stdlib.h stdnoreturn.h string.h tgmath.h
|
|
threads.h time.h uchar.h wchar.h wctype.h
|
|
cxxabi.h dispatch/dispatch.h io.h link.h semaphore.h stdio_ext.h sys/uio.h unistd.h xmmintrin.h
|
|
""".split())
|
|
|
|
|
|
def public_headers(root):
|
|
libs = (root / "core/libs").resolve()
|
|
directories = sorted(path for path in (libs / "prx").iterdir() if path.is_dir() and not path.name.startswith("."))
|
|
if not directories:
|
|
raise RuntimeError("No public library headers found")
|
|
pending = [path / (path.name + ".hpp") for path in directories]
|
|
visited = set()
|
|
while pending:
|
|
header = pending.pop().resolve()
|
|
if not header.is_relative_to(libs):
|
|
raise RuntimeError(f"Public header is outside core/libs: {header}")
|
|
if header in visited:
|
|
continue
|
|
if not header.is_file():
|
|
raise RuntimeError(f"Missing public header: {header}")
|
|
visited.add(header)
|
|
text = header.read_text(encoding="utf-8").replace("\\\n", "").replace("\\\r\n", "")
|
|
text = CPP_TOKEN.sub(lambda match: re.sub(r"[^\n]", " ", match.group()) if match.group("comment") or match.group("raw") else match.group(), text)
|
|
for line in re.findall(r"^[ \t]*#[ \t]*include\b([^\n]*)", text, re.M):
|
|
match = re.fullmatch(r'\s*(?:"([^"\n]+)"|<([^>\n]+)>)\s*', line)
|
|
if not match:
|
|
raise RuntimeError(f"Non-literal public include in {header}: {line.strip()}")
|
|
quoted, angled = match.groups()
|
|
name = quoted or angled
|
|
if "\\" in name or ":" in name or PurePosixPath(name).is_absolute() or "3rdparty" in PurePosixPath(name).parts:
|
|
raise RuntimeError(f"Forbidden public include in {header}: {name}")
|
|
candidates = {path.resolve() for path in ((header.parent / name, libs / name) if quoted else (libs / name,)) if path.is_file()}
|
|
if len(candidates) > 1:
|
|
raise RuntimeError(f"Ambiguous public include in {header}: {name}")
|
|
if candidates:
|
|
pending.append(candidates.pop())
|
|
elif quoted or name not in SYSTEM_HEADERS:
|
|
raise RuntimeError(f"Missing project or unsupported external include in {header}: {name}")
|
|
return sorted(visited)
|
|
|
|
|
|
def package(root, output, version):
|
|
if not re.fullmatch(r"v[0-9A-Za-z][0-9A-Za-z._-]*", version) or version.endswith("."):
|
|
raise ValueError(f"Invalid release tag for asset filenames: {version}")
|
|
libs = (root / "core/libs").resolve()
|
|
headers = public_headers(root)
|
|
license_file = root / "LICENSE"
|
|
if not license_file.is_file() or license_file.stat().st_size == 0:
|
|
raise RuntimeError("Missing project license")
|
|
entries = [(path, "include/anyps5/" + path.relative_to(libs).as_posix()) for path in headers]
|
|
entries.append((license_file, "LICENSE"))
|
|
names = [f"prx-headers-{version}.zip", f"prx-headers-{version}.tar.gz"]
|
|
for name in names:
|
|
path = output / name
|
|
if path.exists() and not path.is_file():
|
|
raise RuntimeError(f"Release destination is not a file: {path}")
|
|
if os.name == "nt" and os.path.lexists(path):
|
|
attributes = path.lstat().st_file_attributes
|
|
if attributes & (stat.FILE_ATTRIBUTE_DIRECTORY | stat.FILE_ATTRIBUTE_READONLY):
|
|
raise RuntimeError(f"Release destination is a directory or read-only: {path}")
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
with tempfile.TemporaryDirectory(prefix=".headers-", dir=output) as temporary:
|
|
staging = Path(temporary)
|
|
with zipfile.ZipFile(staging / names[0], "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9, strict_timestamps=False) as archive:
|
|
for source, destination in entries:
|
|
archive.write(source, destination)
|
|
with tarfile.open(staging / names[1], "w:gz", dereference=True) as archive:
|
|
for source, destination in entries:
|
|
archive.add(source, destination)
|
|
for name in names:
|
|
(staging / name).replace(output / name)
|
|
print(f"Packaged {len(headers)} public headers and recursive dependencies")
|