Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
make-flathub-manifest.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# This file is part of the Ansel project.
3# Copyright (C) 2026 Aurélien PIERRE.
4#
5# Ansel is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansel is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17
18"""Derive the Flathub manifest from the nightly one.
19
20A Flathub build gets no network: every input has to be declared in the manifest and
21hashed up front. The nightly manifest is the opposite by design -- it builds the working
22tree and lets `data/CMakeLists.txt` fetch the current lens database and denoise models at
23configure time, so a nightly always carries today's calibrations.
24
25The two differ in four places and nowhere else, which is why this is a generator and not a
26second manifest to keep in sync:
27
28 * the app module builds a pinned git tag instead of the working directory,
29 * it drops `--share=network`,
30 * the payloads it used to fetch become `type: file` sources, hashed,
31 * and it is told where they landed, with the fetches switched off.
32
33The hashes are read from the manifests the fetches themselves read, so this is never
34hand-maintained: LensSerious publishes `db/v<schema>/manifest.json` and ansel-denoise
35publishes `models/manifest.json`, both carrying a sha256 per file.
36
37Usage:
38 make-flathub-manifest.py --tag v1.0.0 [--commit SHA] [-o photos.ansel.Ansel.json]
39
40With no --commit, the tag is resolved against the local repository.
41"""
42
43import argparse
44import json
45import re
46import subprocess
47import sys
48import urllib.request
49from collections import OrderedDict
50from pathlib import Path
51
52HERE = Path(__file__).resolve().parent
53ROOT = HERE.parent.parent
54
55NIGHTLY_MANIFEST = HERE / "photos.ansel.Ansel.json"
56ANSEL_GIT = "https://github.com/aurelienpierreeng/ansel.git"
57
58LENS_SCHEMA_SQL = ROOT / "src" / "external" / "LensSerious" / "src" / "schema.sql"
59LENS_BASE = "https://raw.githubusercontent.com/aurelienpierreeng/LensSerious/main/db"
60MODELS_BASE = "https://raw.githubusercontent.com/aurelienpierreeng/ansel-denoise/master/models"
61
62# flatpak-builder builds each module in /run/build/<module name>, and a source's "dest" is
63# relative to that. The build itself happens in _flatpak_build beneath it (builddir: true),
64# which is why these are absolute rather than relative to the compiler's working directory.
65BUILD_ROOT = "/run/build/ansel"
66LENS_DEST = "lens-db"
67MODELS_DEST = "nn-models"
68
69
70def fetch_json(url):
71 with urllib.request.urlopen(url, timeout=60) as response:
72 return json.loads(response.read().decode("utf-8"))
73
74
76 """The schema the pinned LensSerious reads, which is the database directory to use."""
77 text = LENS_SCHEMA_SQL.read_text(encoding="utf-8")
78 match = re.search(r"PRAGMA\s+user_version\s*=\s*(\d+)", text)
79 if not match:
80 sys.exit(f"no PRAGMA user_version in {LENS_SCHEMA_SQL}")
81 return int(match.group(1))
82
83
85 result = subprocess.run(["git", "-C", str(ROOT), "rev-list", "-n", "1", tag],
86 capture_output=True, text=True)
87 if result.returncode != 0:
88 sys.exit(f"cannot resolve tag {tag!r} in {ROOT}: {result.stderr.strip()}")
89 return result.stdout.strip()
90
91
93 """Every file the nightly build would have downloaded, as hashed manifest sources."""
94 sources = []
95
96 schema = lens_schema_version()
97 lens_manifest = fetch_json(f"{LENS_BASE}/v{schema}/manifest.json")
98 for name, entry in sorted(lens_manifest["files"].items()):
99 sources.append(OrderedDict([
100 ("type", "file"),
101 ("url", f"{LENS_BASE}/v{schema}/{name}"),
102 ("sha256", entry["sha256"]),
103 ("dest", LENS_DEST),
104 ]))
105
106 models_manifest = fetch_json(f"{MODELS_BASE}/manifest.json")
107 for name, entry in sorted(models_manifest["models"].items()):
108 sources.append(OrderedDict([
109 ("type", "file"),
110 ("url", f"{MODELS_BASE}/{name}"),
111 ("sha256", entry["sha256"]),
112 ("dest", MODELS_DEST),
113 ]))
114
115 return sources, schema
116
117
118def main():
119 parser = argparse.ArgumentParser(description=__doc__,
120 formatter_class=argparse.RawDescriptionHelpFormatter)
121 parser.add_argument("--tag", required=True, help="the release tag to build")
122 parser.add_argument("--commit", help="the commit the tag points at (resolved locally if omitted)")
123 parser.add_argument("-o", "--output", help="write here instead of stdout")
124 args = parser.parse_args()
125
126 commit = args.commit or resolve_commit(args.tag)
127
128 manifest = json.loads(NIGHTLY_MANIFEST.read_text(encoding="utf-8"),
129 object_pairs_hook=OrderedDict)
130
131 sources, schema = payload_sources()
132
133 for module in manifest["modules"]:
134 if not isinstance(module, dict) or module.get("name") != "ansel":
135 continue
136
137 module["sources"] = [OrderedDict([
138 ("type", "git"),
139 ("url", ANSEL_GIT),
140 ("tag", args.tag),
141 ("commit", commit),
142 ])] + sources
143
144 # No network during the build, so nothing may be fetched from inside it.
145 module["config-opts"] += [
146 "-DFETCH_LENS_DB=OFF",
147 f"-DLENS_DB_DIR={BUILD_ROOT}/{LENS_DEST}",
148 "-DFETCH_NN_MODELS=OFF",
149 f"-DNN_MODELS_DIR={BUILD_ROOT}/{MODELS_DEST}",
150 ]
151
152 build_options = module.get("build-options", {})
153 build_options.pop("build-args", None)
154 if build_options:
155 module["build-options"] = build_options
156 else:
157 module.pop("build-options", None)
158 break
159 else:
160 sys.exit("no module named 'ansel' in the nightly manifest")
161
162 text = json.dumps(manifest, indent=4, ensure_ascii=False) + "\n"
163 if args.output:
164 Path(args.output).write_text(text, encoding="utf-8")
165 print(f"wrote {args.output}: {args.tag} at {commit[:12]}, "
166 f"lens database v{schema}, {len(sources)} payload sources", file=sys.stderr)
167 else:
168 sys.stdout.write(text)
169
170
171if __name__ == "__main__":
172 main()
const dt_collection_sort_t items[]
Definition filter.c:102