mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-30 15:48:36 +00:00
Flathub doesn't accept the release manifest, which repackages CI's prebuilt tree. desktopApp/packaging/flatpak/flathub/ builds the tag offline instead: - com.vitorpamplona.amethyst.Amethyst.yml compiles the tag tarball with `:desktopApp:createReleaseDistributable` on the JetBrains Runtime SDK 21.0.11 (prebuilt, as Flathub ships it for com.prof18.feedflow), so the bundled runtime keeps JBR's Wayland toolkit. Sources: the tarball, JBR per arch, Gradle 9.8.0, the init script and gradle-sources.json. - gradle-sources.json: the 2,185 Maven files the build resolves on x86_64 and aarch64 (1,566 Maven Central, 591 Google, 28 JitPack), each with its sha256, laid out as a local Maven repo. - flathub-offline.init.gradle replaces every repository with that repo. - tools/regenerate-gradle-sources.sh reruns the build online per arch in Docker on a fresh Gradle home and turns the module cache into the list (tools/gradle-cache-to-sources.py, which also maps Gradle module metadata's local file names to their published URLs). Checked on aarch64: flatpak-builder builds it without network, the installed Flatpak is 1.19.0 on runtime 26.08 with libawt_wlawt.so, and `flatpak run` reaches the login screen. flatpak-builder-lint passes the manifest and the metainfo; on the build it reports only the two media-URL checks, which expect the absolute dl.flathub.org/media URLs Flathub's builder writes where Debian's AppStream 1.0.5 writes relative ones. Not checked: x86_64 through flatpak-builder, and Flathub's own builder (it can't nest in Docker). The flatpak README's Flathub section now tracks what is left: the AI policy (these files are AI-written, the manifest says so, and the submission must disclose it), domain verification, and submitting from the next tag, whose metainfo has the screenshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
4.8 KiB
Python
Executable File
133 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Turn a Gradle user home's module cache into flatpak-builder sources.
|
|
|
|
Run the build once online with a fresh GRADLE_USER_HOME; the cache then holds
|
|
exactly what the build resolved. Every file becomes a `type: file` source laid
|
|
out as a Maven repository under `offline-repository/`, which the offline build
|
|
reads through an init script. URLs are found by asking the repositories
|
|
settings.gradle.kts declares, in its order, with a HEAD request; a file no
|
|
repository serves is an error, never a silent omission.
|
|
|
|
usage: gradle-cache-to-sources.py GRADLE_USER_HOME OUT.json [MORE_HOMES...]
|
|
"""
|
|
import concurrent.futures
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
GOOGLE = "https://dl.google.com/dl/android/maven2"
|
|
CENTRAL = "https://repo.maven.apache.org/maven2"
|
|
PORTAL = "https://plugins.gradle.org/m2"
|
|
JITPACK = "https://jitpack.io"
|
|
|
|
|
|
def repos_for(group):
|
|
google_first = group.startswith(("com.android", "com.google", "androidx"))
|
|
order = [GOOGLE, CENTRAL, PORTAL, JITPACK] if google_first else [CENTRAL, PORTAL, GOOGLE, JITPACK]
|
|
return order
|
|
|
|
|
|
def exists(url):
|
|
req = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "flatpak-sources-generator"})
|
|
for _ in range(3):
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
|
return r.status == 200
|
|
except urllib.error.HTTPError as e:
|
|
if e.code in (404, 403, 410):
|
|
return False
|
|
except Exception:
|
|
pass
|
|
return False
|
|
|
|
|
|
def sha256(path):
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def module_urls(vdir):
|
|
"""Gradle module metadata names a file one way (the cache keeps `name`) and
|
|
serves it under another (`url`, relative to the module's directory)."""
|
|
urls = {}
|
|
for sha1dir in os.listdir(vdir):
|
|
for name in os.listdir(os.path.join(vdir, sha1dir)):
|
|
if not name.endswith(".module"):
|
|
continue
|
|
with open(os.path.join(vdir, sha1dir, name)) as f:
|
|
meta = json.load(f)
|
|
for variant in meta.get("variants", []):
|
|
for entry in variant.get("files", []):
|
|
urls[entry["name"]] = entry["url"]
|
|
return urls
|
|
|
|
|
|
def collect(home, found):
|
|
root = os.path.join(home, "caches", "modules-2", "files-2.1")
|
|
for group in os.listdir(root):
|
|
for module in os.listdir(os.path.join(root, group)):
|
|
for version in os.listdir(os.path.join(root, group, module)):
|
|
vdir = os.path.join(root, group, module, version)
|
|
urls = module_urls(vdir)
|
|
for sha1dir in os.listdir(vdir):
|
|
for cached in os.listdir(os.path.join(vdir, sha1dir)):
|
|
path = os.path.join(vdir, sha1dir, cached)
|
|
# The repository (and so the offline one) has it under its url.
|
|
name = urls.get(cached, cached)
|
|
key = (group, module, version, name)
|
|
digest = sha256(path)
|
|
prev = found.get(key)
|
|
if prev and prev[1] != digest:
|
|
print(f"warning: two different {key}; keeping the first", file=sys.stderr)
|
|
continue
|
|
found.setdefault(key, (path, digest))
|
|
|
|
|
|
def resolve(item):
|
|
(group, module, version, name), (_, digest) = item
|
|
rel = f"{group.replace('.', '/')}/{module}/{version}/{name}"
|
|
for repo in repos_for(group):
|
|
url = f"{repo}/{rel}"
|
|
if exists(url):
|
|
return {
|
|
"type": "file",
|
|
"url": url,
|
|
"sha256": digest,
|
|
"dest": f"offline-repository/{group.replace('.', '/')}/{module}/{version}",
|
|
"dest-filename": name,
|
|
}
|
|
return {"missing": rel}
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 3:
|
|
sys.exit(__doc__)
|
|
out = sys.argv[2]
|
|
found = {}
|
|
for home in [sys.argv[1]] + sys.argv[3:]:
|
|
collect(home, found)
|
|
print(f"{len(found)} files", file=sys.stderr)
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=32) as pool:
|
|
results = list(pool.map(resolve, sorted(found.items())))
|
|
missing = [r["missing"] for r in results if "missing" in r]
|
|
if missing:
|
|
print(f"{len(missing)} files no repository serves:", file=sys.stderr)
|
|
for m in missing:
|
|
print(" " + m, file=sys.stderr)
|
|
sys.exit(1)
|
|
results.sort(key=lambda r: (r["dest"], r["dest-filename"]))
|
|
with open(out, "w") as f:
|
|
json.dump(results, f, indent=2)
|
|
f.write("\n")
|
|
print(f"wrote {len(results)} sources to {out}", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|