NikGapps 3.96__tar.gz → 3.97__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/Build.py +2 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/Assets.py +1 -1
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/Config.py +5 -0
- {nikgapps-3.96 → nikgapps-3.97/NikGapps.egg-info}/PKG-INFO +2 -2
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps.egg-info/requires.txt +1 -1
- {nikgapps-3.96/NikGapps.egg-info → nikgapps-3.97}/PKG-INFO +2 -2
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/cli.py +99 -1
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/config.py +9 -2
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/consumer.py +63 -5
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/gitlab.py +19 -3
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/models.py +12 -3
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/pipeline.py +116 -13
- nikgapps-3.97/nikgapps_registry/cli.py +169 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_registry/service.py +31 -1
- {nikgapps-3.96 → nikgapps-3.97}/setup.py +2 -2
- {nikgapps-3.96 → nikgapps-3.97}/tests_package_pipeline/test_consumer.py +37 -0
- {nikgapps-3.96 → nikgapps-3.97}/tests_package_pipeline/test_pipeline.py +34 -1
- {nikgapps-3.96 → nikgapps-3.97}/tests_registry/test_registry.py +14 -0
- nikgapps-3.96/nikgapps_registry/cli.py +0 -98
- {nikgapps-3.96 → nikgapps-3.97}/LICENSE +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/MANIFEST.in +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/NikGappsManager.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/NikGappsOverlays.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/PackageConstants.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/Release.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/build_config.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/cache.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/config/NikGappsConfig.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/config/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/config_upload.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/copy_repos.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/AppSet.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/Args.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/ConfigObj.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/Package.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/addon.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/appsets.json +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/changelogs.yaml +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/config_versions.json +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/customize.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/debloater.config +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/functions.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/gofile.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/header.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/installer.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/magisk-update-binary.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/module.prop +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/mount.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/mtg_mount.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/nikgapps.config +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/nikgapps_functions.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/packages.json +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/uninstaller.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/assets/unmount.sh +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/compression/CompOps.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/compression/Export.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/compression/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/git/TestGit.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/git/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/ApkMetaInfo.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/Bool.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/Library.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/Manifest.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/Overlay.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/Resources.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/String.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/StringArray.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/helper/overlay/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/main.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/overlay_control.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps/test.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps.egg-info/SOURCES.txt +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps.egg-info/dependency_links.txt +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps.egg-info/entry_points.txt +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/NikGapps.egg-info/top_level.txt +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/README.md +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/__main__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/apk.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/archive.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/compat.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/errors.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/scanner.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/upgrade.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_package_pipeline/util.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_registry/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/nikgapps_registry/__main__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/setup.cfg +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/tests_package_pipeline/__init__.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/tests_package_pipeline/test_overlay_control.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/tests_package_pipeline/test_upgrade.py +0 -0
- {nikgapps-3.96 → nikgapps-3.97}/tests_registry/__init__.py +0 -0
|
@@ -34,6 +34,8 @@ class Build:
|
|
|
34
34
|
Path(Config.PACKAGE_CACHE),
|
|
35
35
|
channel=Config.PACKAGE_CHANNEL,
|
|
36
36
|
channel_overrides=channel_overrides_from_env(Config.PACKAGE_CHANNEL_OVERRIDES),
|
|
37
|
+
release_index_url=Config.PACKAGE_RELEASE_INDEX_URL,
|
|
38
|
+
release_id=Config.PACKAGE_RELEASE_ID,
|
|
37
39
|
token=os.environ.get("GITLAB_TOKEN"),
|
|
38
40
|
)
|
|
39
41
|
Config.APK_SOURCE = str(registry_source.prepare(
|
|
@@ -22,7 +22,7 @@ class Assets:
|
|
|
22
22
|
cwd = assets_folder + os.path.sep
|
|
23
23
|
system_name = A.system_name
|
|
24
24
|
apksigner_path = A.get("apksigner.jar")
|
|
25
|
-
apktool_path = A.get("
|
|
25
|
+
apktool_path = A.get("apktool_3.0.2.jar")
|
|
26
26
|
key_path = A.get("cert.pk8")
|
|
27
27
|
cert_path = A.get("cert.x509.pem")
|
|
28
28
|
private_key_pem = A.get("private_key.pem")
|
|
@@ -65,6 +65,11 @@ PACKAGE_APPSETS_URL = os.environ.get(
|
|
|
65
65
|
"PACKAGE_APPSETS_URL",
|
|
66
66
|
"https://gitlab.com/nikgapps/nikgapps-package-catalog/-/raw/main/appsets.json"
|
|
67
67
|
)
|
|
68
|
+
PACKAGE_RELEASE_INDEX_URL = os.environ.get(
|
|
69
|
+
"PACKAGE_RELEASE_INDEX_URL",
|
|
70
|
+
"https://gitlab.com/nikgapps/nikgapps-package-catalog/-/raw/main/releases/index.json"
|
|
71
|
+
)
|
|
72
|
+
PACKAGE_RELEASE_ID = os.environ.get("PACKAGE_RELEASE_ID")
|
|
68
73
|
PACKAGE_CACHE = os.environ.get(
|
|
69
74
|
"PACKAGE_CACHE",
|
|
70
75
|
os.path.abspath(os.path.join(os.getcwd(), ".nikgapps-package-cache"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NikGapps
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.97
|
|
4
4
|
Summary: A short description of your project
|
|
5
5
|
Home-page: https://github.com/nikgapps/project
|
|
6
6
|
Author: Nikhil Menghani
|
|
@@ -22,7 +22,7 @@ Requires-Dist: PyGithub~=2.8.1
|
|
|
22
22
|
Requires-Dist: python-gitlab~=8.0.0
|
|
23
23
|
Requires-Dist: cryptography~=46.0.3
|
|
24
24
|
Requires-Dist: python-dotenv~=1.2.1
|
|
25
|
-
Requires-Dist: niklibrary~=0.
|
|
25
|
+
Requires-Dist: niklibrary~=0.56
|
|
26
26
|
Dynamic: author
|
|
27
27
|
Dynamic: author-email
|
|
28
28
|
Dynamic: classifier
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NikGapps
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.97
|
|
4
4
|
Summary: A short description of your project
|
|
5
5
|
Home-page: https://github.com/nikgapps/project
|
|
6
6
|
Author: Nikhil Menghani
|
|
@@ -22,7 +22,7 @@ Requires-Dist: PyGithub~=2.8.1
|
|
|
22
22
|
Requires-Dist: python-gitlab~=8.0.0
|
|
23
23
|
Requires-Dist: cryptography~=46.0.3
|
|
24
24
|
Requires-Dist: python-dotenv~=1.2.1
|
|
25
|
-
Requires-Dist: niklibrary~=0.
|
|
25
|
+
Requires-Dist: niklibrary~=0.56
|
|
26
26
|
Dynamic: author
|
|
27
27
|
Dynamic: author-email
|
|
28
28
|
Dynamic: classifier
|
|
@@ -24,6 +24,8 @@ DEFAULT_INPUT = Path(r"D:\workspace\python\16_stable")
|
|
|
24
24
|
DEFAULT_OUTPUT = Path(r"D:\workspace\python\16_STABLE_PREBUILT_RELEASE")
|
|
25
25
|
DEFAULT_CONFIG = Path(__file__).with_name("nikgapps-pipeline.example.json")
|
|
26
26
|
DEFAULT_GITLAB_PROJECT = "85036487"
|
|
27
|
+
DEFAULT_RELEASE_ROOT = Path(__file__).resolve().parents[2]
|
|
28
|
+
DEFAULT_MULTI_RELEASE_OUTPUT = DEFAULT_RELEASE_ROOT / "MULTI_RELEASE_PREVIEW"
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def _load_env_file(path: Path) -> bool:
|
|
@@ -93,7 +95,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
93
95
|
help="override platform API; otherwise derived from --android-version",
|
|
94
96
|
)
|
|
95
97
|
migrate.add_argument("--channel", default="stable")
|
|
98
|
+
migrate.add_argument("--source", default="legacy", help="payload source label recorded in release metadata")
|
|
96
99
|
migrate.add_argument("--aapt2", default=find_aapt2())
|
|
100
|
+
migrate.add_argument(
|
|
101
|
+
"--overlay-root",
|
|
102
|
+
type=Path,
|
|
103
|
+
help="compiled overlay repository to embed into matching packages",
|
|
104
|
+
)
|
|
97
105
|
migrate.add_argument(
|
|
98
106
|
"--package",
|
|
99
107
|
action="append",
|
|
@@ -161,6 +169,38 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
161
169
|
)
|
|
162
170
|
_common_gitlab(reset)
|
|
163
171
|
|
|
172
|
+
reset_sync = commands.add_parser(
|
|
173
|
+
"reset-and-sync",
|
|
174
|
+
help="permanently reset Generic packages, rebuild, publish, and replace metadata",
|
|
175
|
+
)
|
|
176
|
+
reset_sync.add_argument("--verbose", action="store_true")
|
|
177
|
+
reset_sync.add_argument("--confirm-project", required=True,
|
|
178
|
+
help="must exactly match --gitlab-project")
|
|
179
|
+
reset_sync.add_argument("--gitlab-project", default=DEFAULT_GITLAB_PROJECT)
|
|
180
|
+
reset_sync.add_argument("--input", type=Path, default=DEFAULT_INPUT,
|
|
181
|
+
help="advanced single-release input override")
|
|
182
|
+
reset_sync.add_argument("--output", type=Path, default=DEFAULT_MULTI_RELEASE_OUTPUT)
|
|
183
|
+
reset_sync.add_argument("--config", type=Path, default=DEFAULT_CONFIG)
|
|
184
|
+
reset_sync.add_argument(
|
|
185
|
+
"--android-version", default="16",
|
|
186
|
+
help="one version or comma-separated versions; discovers VERSION_stable and overlays_VERSION",
|
|
187
|
+
)
|
|
188
|
+
reset_sync.add_argument("--platform-api", type=int)
|
|
189
|
+
reset_sync.add_argument("--channel", default="stable")
|
|
190
|
+
reset_sync.add_argument("--source", default="legacy")
|
|
191
|
+
reset_sync.add_argument("--aapt2", default=find_aapt2())
|
|
192
|
+
reset_sync.add_argument("--overlay-root", type=Path)
|
|
193
|
+
reset_sync.add_argument(
|
|
194
|
+
"--release", action="append", metavar="VERSION=INPUT[=OVERLAYS]",
|
|
195
|
+
help="release to sync after one reset; repeat for Android 16 and 17",
|
|
196
|
+
)
|
|
197
|
+
reset_sync.add_argument("--package", action="append")
|
|
198
|
+
reset_sync.add_argument("--artifact-base-url")
|
|
199
|
+
reset_sync.add_argument("--metadata-branch", default="main")
|
|
200
|
+
reset_sync.add_argument("--compact", action="store_true")
|
|
201
|
+
reset_sync.add_argument("--compat-output", type=Path)
|
|
202
|
+
_common_gitlab(reset_sync)
|
|
203
|
+
|
|
164
204
|
upgrade = commands.add_parser(
|
|
165
205
|
"upgrade", help="create a stable legacy tree from a newer Mustang image"
|
|
166
206
|
)
|
|
@@ -197,6 +237,7 @@ def _migrate(args: argparse.Namespace) -> int:
|
|
|
197
237
|
android_version=args.android_version,
|
|
198
238
|
platform_api=derived_platform_api,
|
|
199
239
|
channel=args.channel,
|
|
240
|
+
source=args.source,
|
|
200
241
|
)
|
|
201
242
|
client: GitLabClient | None = None
|
|
202
243
|
project = args.gitlab_project
|
|
@@ -237,7 +278,7 @@ def _migrate(args: argparse.Namespace) -> int:
|
|
|
237
278
|
release_path = (
|
|
238
279
|
f"releases/android-{config.android_version}-{config.architecture}.json"
|
|
239
280
|
)
|
|
240
|
-
for relative in ("catalog.json", "appsets.json", release_path):
|
|
281
|
+
for relative in ("catalog.json", "appsets.json", "releases/index.json", release_path):
|
|
241
282
|
local = metadata / Path(relative)
|
|
242
283
|
if local.exists():
|
|
243
284
|
continue
|
|
@@ -267,6 +308,7 @@ def _migrate(args: argparse.Namespace) -> int:
|
|
|
267
308
|
artifact_base_url=base_url,
|
|
268
309
|
pretty=not args.compact,
|
|
269
310
|
package_filter=set(args.package) if args.package else None,
|
|
311
|
+
overlay_root=args.overlay_root,
|
|
270
312
|
)
|
|
271
313
|
if args.compat_output:
|
|
272
314
|
LegacyCompatibilityExporter().export(packages, args.compat_output)
|
|
@@ -314,6 +356,60 @@ def _reset_registry(args: argparse.Namespace) -> int:
|
|
|
314
356
|
return 0
|
|
315
357
|
|
|
316
358
|
|
|
359
|
+
def _reset_and_sync(args: argparse.Namespace) -> int:
|
|
360
|
+
if args.confirm_project != str(args.gitlab_project):
|
|
361
|
+
raise PipelineError(
|
|
362
|
+
"--confirm-project must exactly match --gitlab-project; reset-and-sync is destructive"
|
|
363
|
+
)
|
|
364
|
+
releases: list[tuple[str, Path, Path | None]] = []
|
|
365
|
+
for value in args.release or []:
|
|
366
|
+
parts = value.split("=", 2)
|
|
367
|
+
if len(parts) not in (2, 3) or not parts[0] or not parts[1]:
|
|
368
|
+
raise PipelineError(
|
|
369
|
+
f"Invalid --release {value!r}; expected VERSION=INPUT[=OVERLAYS]"
|
|
370
|
+
)
|
|
371
|
+
releases.append((parts[0], Path(parts[1]), Path(parts[2]) if len(parts) == 3 else None))
|
|
372
|
+
if not releases:
|
|
373
|
+
versions = [value.strip() for value in str(args.android_version).split(",") if value.strip()]
|
|
374
|
+
if not versions:
|
|
375
|
+
raise PipelineError("--android-version must contain at least one Android version")
|
|
376
|
+
for android_version in versions:
|
|
377
|
+
releases.append((
|
|
378
|
+
android_version,
|
|
379
|
+
DEFAULT_RELEASE_ROOT / f"{android_version}_stable",
|
|
380
|
+
DEFAULT_RELEASE_ROOT / f"overlays_{android_version}",
|
|
381
|
+
))
|
|
382
|
+
for android_version, input_path, overlay_root in releases:
|
|
383
|
+
if not input_path.is_dir():
|
|
384
|
+
raise PipelineError(f"Android {android_version} input directory does not exist: {input_path}")
|
|
385
|
+
if overlay_root is not None and not overlay_root.is_dir():
|
|
386
|
+
raise PipelineError(f"Android {android_version} overlay directory does not exist: {overlay_root}")
|
|
387
|
+
|
|
388
|
+
reset_result = _reset_registry(args)
|
|
389
|
+
if reset_result != 0:
|
|
390
|
+
return reset_result
|
|
391
|
+
# Force the only safe post-reset migration mode. These are intentionally
|
|
392
|
+
# not user-selectable flags on this command.
|
|
393
|
+
|
|
394
|
+
print(f"Starting fresh build and registry sync for {len(releases)} release(s)...")
|
|
395
|
+
for index, (android_version, input_path, overlay_root) in enumerate(releases):
|
|
396
|
+
operation = argparse.Namespace(**vars(args))
|
|
397
|
+
operation.android_version = android_version
|
|
398
|
+
operation.input = input_path
|
|
399
|
+
operation.overlay_root = overlay_root
|
|
400
|
+
operation.publish = True
|
|
401
|
+
operation.fresh_metadata = index == 0
|
|
402
|
+
operation.create_project = False
|
|
403
|
+
operation.namespace_id = None
|
|
404
|
+
operation.namespace = "nikgapps"
|
|
405
|
+
operation.visibility = "public"
|
|
406
|
+
print(f"[{index + 1}/{len(releases)}] Syncing Android {android_version} from {input_path}")
|
|
407
|
+
result = _migrate(operation)
|
|
408
|
+
if result != 0:
|
|
409
|
+
return result
|
|
410
|
+
return 0
|
|
411
|
+
|
|
412
|
+
|
|
317
413
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
318
414
|
parser = build_parser()
|
|
319
415
|
args = parser.parse_args(argv)
|
|
@@ -340,6 +436,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
340
436
|
return 0
|
|
341
437
|
if args.command == "reset-registry":
|
|
342
438
|
return _reset_registry(args)
|
|
439
|
+
if args.command == "reset-and-sync":
|
|
440
|
+
return _reset_and_sync(args)
|
|
343
441
|
if args.command == "upgrade":
|
|
344
442
|
overlay_values = (args.android_version, args.overlay_source, args.overlay_output)
|
|
345
443
|
if any(overlay_values) and not all(overlay_values):
|
|
@@ -34,7 +34,9 @@ class PipelineConfig:
|
|
|
34
34
|
android_version: str = "16"
|
|
35
35
|
platform_api: int = 36
|
|
36
36
|
channel: str = "stable"
|
|
37
|
+
source: str = "legacy"
|
|
37
38
|
architecture: str = "arm64-v8a"
|
|
39
|
+
default_device_type: str = "phone"
|
|
38
40
|
default_partition: str = "product"
|
|
39
41
|
packages: dict[str, PackageOverride] = field(default_factory=dict)
|
|
40
42
|
|
|
@@ -48,7 +50,7 @@ class PipelineConfig:
|
|
|
48
50
|
def _override(value: dict[str, Any], default_partition: str) -> PackageOverride:
|
|
49
51
|
accepted = {
|
|
50
52
|
"id", "name", "primaryApk", "defaultPartition", "minApi", "maxApi",
|
|
51
|
-
"architectures", "replaceable", "include", "exclude",
|
|
53
|
+
"architectures", "deviceTypes", "replaceable", "include", "exclude",
|
|
52
54
|
"removeFiles", "removeOverlays", "privilegedPermissions",
|
|
53
55
|
"cleanFlashOnly", "additionalInstallerScript", "validationScript",
|
|
54
56
|
"addonIndex",
|
|
@@ -67,6 +69,7 @@ def _override(value: dict[str, Any], default_partition: str) -> PackageOverride:
|
|
|
67
69
|
min_api=value.get("minApi"),
|
|
68
70
|
max_api=value.get("maxApi"),
|
|
69
71
|
architectures=value.get("architectures"),
|
|
72
|
+
device_types=value.get("deviceTypes"),
|
|
70
73
|
replaceable=value.get("replaceable", True),
|
|
71
74
|
include=value.get("include"),
|
|
72
75
|
exclude=value.get("exclude", []),
|
|
@@ -81,13 +84,15 @@ def _override(value: dict[str, Any], default_partition: str) -> PackageOverride:
|
|
|
81
84
|
|
|
82
85
|
|
|
83
86
|
def load_config(
|
|
84
|
-
path: Path | None, *, android_version: str, platform_api: int, channel: str
|
|
87
|
+
path: Path | None, *, android_version: str, platform_api: int, channel: str,
|
|
88
|
+
source: str = "legacy",
|
|
85
89
|
) -> PipelineConfig:
|
|
86
90
|
if path is None:
|
|
87
91
|
return PipelineConfig(
|
|
88
92
|
android_version=android_version,
|
|
89
93
|
platform_api=platform_api,
|
|
90
94
|
channel=channel,
|
|
95
|
+
source=source,
|
|
91
96
|
)
|
|
92
97
|
try:
|
|
93
98
|
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
@@ -105,7 +110,9 @@ def load_config(
|
|
|
105
110
|
# The channel is an operation-time promotion target. A single config
|
|
106
111
|
# must be reusable for stable, beta, and canary publication.
|
|
107
112
|
channel=channel,
|
|
113
|
+
source=source,
|
|
108
114
|
architecture=raw.get("architecture", "arm64-v8a"),
|
|
115
|
+
default_device_type=raw.get("defaultDeviceType", "phone"),
|
|
109
116
|
default_partition=default_partition,
|
|
110
117
|
)
|
|
111
118
|
packages = raw.get("packages", {})
|
|
@@ -56,6 +56,8 @@ class CatalogPackageSource:
|
|
|
56
56
|
*,
|
|
57
57
|
channel: str = "stable",
|
|
58
58
|
channel_overrides: dict[str, str] | None = None,
|
|
59
|
+
release_index_url: str | None = None,
|
|
60
|
+
release_id: str | None = None,
|
|
59
61
|
token: str | None = None,
|
|
60
62
|
) -> None:
|
|
61
63
|
self.catalog_url = catalog_url
|
|
@@ -63,6 +65,8 @@ class CatalogPackageSource:
|
|
|
63
65
|
self.cache_directory = cache_directory.resolve()
|
|
64
66
|
self.channel = channel
|
|
65
67
|
self.channel_overrides = channel_overrides or {}
|
|
68
|
+
self.release_index_url = release_index_url
|
|
69
|
+
self.release_id = release_id
|
|
66
70
|
self.token = token
|
|
67
71
|
|
|
68
72
|
def prepare(
|
|
@@ -70,6 +74,7 @@ class CatalogPackageSource:
|
|
|
70
74
|
appsets: Iterable[Any],
|
|
71
75
|
android_version: str | int | float,
|
|
72
76
|
architecture: str,
|
|
77
|
+
device_type: str = "phone",
|
|
73
78
|
) -> Path:
|
|
74
79
|
appsets = list(appsets)
|
|
75
80
|
selected_names = ", ".join(appset.title for appset in appsets)
|
|
@@ -80,6 +85,32 @@ class CatalogPackageSource:
|
|
|
80
85
|
_message("Reading catalog and AppSet metadata")
|
|
81
86
|
catalog = self._read_json(self.catalog_url)
|
|
82
87
|
appset_document = self._read_json(self.appsets_url)
|
|
88
|
+
release_versions: dict[str, str] = {}
|
|
89
|
+
if self.release_index_url:
|
|
90
|
+
release_index = self._read_json(self.release_index_url)
|
|
91
|
+
arch = catalog_architecture(architecture)
|
|
92
|
+
selected_id = self.release_id
|
|
93
|
+
if not selected_id:
|
|
94
|
+
selected_id = (
|
|
95
|
+
release_index.get("latest", {}).get(str(android_version), {})
|
|
96
|
+
.get(self.channel, {}).get(arch)
|
|
97
|
+
)
|
|
98
|
+
summary = next((item for item in release_index.get("releases", [])
|
|
99
|
+
if item.get("id") == selected_id), None)
|
|
100
|
+
if not summary:
|
|
101
|
+
raise PipelineError(
|
|
102
|
+
f"No {self.channel} release for Android {android_version} ({arch})"
|
|
103
|
+
)
|
|
104
|
+
metadata_root = self.release_index_url.rsplit("/releases/index.json", 1)[0]
|
|
105
|
+
release = self._read_json(f"{metadata_root}/{summary['manifest']}")
|
|
106
|
+
release_versions = {
|
|
107
|
+
package_id: value["version"]
|
|
108
|
+
for package_id, value in release.get("packages", {}).items()
|
|
109
|
+
}
|
|
110
|
+
appset_document = {
|
|
111
|
+
"schemaVersion": release.get("schemaVersion", 1),
|
|
112
|
+
"appSets": release.get("appSets", []),
|
|
113
|
+
}
|
|
83
114
|
catalog_packages = {
|
|
84
115
|
item["id"]: item for item in catalog.get("packages", [])
|
|
85
116
|
}
|
|
@@ -137,7 +168,10 @@ class CatalogPackageSource:
|
|
|
137
168
|
package = catalog_packages.get(package_id)
|
|
138
169
|
if not package:
|
|
139
170
|
raise PipelineError(f"Resolved package {package_id!r} is absent from catalog.json")
|
|
140
|
-
version = self._resolve_version(
|
|
171
|
+
version = self._resolve_version(
|
|
172
|
+
package, api, arch, release_versions.get(package_id),
|
|
173
|
+
catalog.get("defaults", {}), device_type
|
|
174
|
+
)
|
|
141
175
|
resolutions.append((package_id, package, version))
|
|
142
176
|
fingerprint = hashlib.sha256(
|
|
143
177
|
"\n".join(
|
|
@@ -157,9 +191,15 @@ class CatalogPackageSource:
|
|
|
157
191
|
source_root.mkdir(parents=True, exist_ok=True)
|
|
158
192
|
selected_appset_names = {appset.title for appset in appsets}
|
|
159
193
|
for package_id, package, version in resolutions:
|
|
194
|
+
resolved_version_key = (
|
|
195
|
+
release_versions.get(package_id)
|
|
196
|
+
or package.get("channels", {}).get(
|
|
197
|
+
self.channel_overrides.get(package_id, self.channel)
|
|
198
|
+
)
|
|
199
|
+
or "unknown"
|
|
200
|
+
)
|
|
160
201
|
_message(
|
|
161
|
-
f"Resolving {package_id} -> "
|
|
162
|
-
f"{package.get('channels', {}).get(self.channel, 'channel override')}"
|
|
202
|
+
f"Resolving {package_id} -> {resolved_version_key}"
|
|
163
203
|
)
|
|
164
204
|
archive = self._artifact(version["artifact"])
|
|
165
205
|
self._materialize(
|
|
@@ -181,27 +221,45 @@ class CatalogPackageSource:
|
|
|
181
221
|
package: dict[str, Any],
|
|
182
222
|
api: int,
|
|
183
223
|
architecture: str,
|
|
224
|
+
release_version: str | None = None,
|
|
225
|
+
defaults: dict[str, Any] | None = None,
|
|
226
|
+
device_type: str = "phone",
|
|
184
227
|
) -> dict[str, Any]:
|
|
185
228
|
package_id = package["id"]
|
|
186
229
|
channel = self.channel_overrides.get(package_id, self.channel)
|
|
187
|
-
version_key = package.get("channels", {}).get(channel)
|
|
230
|
+
version_key = release_version or package.get("channels", {}).get(channel)
|
|
188
231
|
if not version_key:
|
|
189
232
|
raise PipelineError(f"Package {package_id} has no {channel!r} channel")
|
|
190
233
|
version = package.get("versions", {}).get(version_key)
|
|
191
234
|
if not version:
|
|
192
235
|
raise PipelineError(f"Package {package_id} channel points to missing {version_key}")
|
|
193
236
|
android = version.get("android", {})
|
|
237
|
+
supported_android = version.get("supportedAndroidVersions", [])
|
|
238
|
+
requested_android = {
|
|
239
|
+
29: "10", 30: "11", 31: "12", 32: "12.1", 33: "13",
|
|
240
|
+
34: "14", 35: "15", 36: "16", 37: "17",
|
|
241
|
+
}.get(api)
|
|
242
|
+
if supported_android and requested_android not in supported_android:
|
|
243
|
+
raise PipelineError(
|
|
244
|
+
f"Package {package_id} is not published for Android {requested_android or api}"
|
|
245
|
+
)
|
|
194
246
|
minimum = android.get("minApi")
|
|
195
247
|
maximum = android.get("maxApi")
|
|
196
248
|
if minimum is not None and api < int(minimum):
|
|
197
249
|
raise PipelineError(f"Package {package_id} requires API {minimum}; build targets API {api}")
|
|
198
250
|
if maximum is not None and api > int(maximum):
|
|
199
251
|
raise PipelineError(f"Package {package_id} supports at most API {maximum}; build targets API {api}")
|
|
200
|
-
|
|
252
|
+
defaults = defaults or {}
|
|
253
|
+
architectures = version.get("architectures", defaults.get("architectures", []))
|
|
201
254
|
if architectures and architecture not in architectures:
|
|
202
255
|
raise PipelineError(
|
|
203
256
|
f"Package {package_id} does not support {architecture}: {architectures}"
|
|
204
257
|
)
|
|
258
|
+
device_types = version.get("deviceTypes", defaults.get("deviceTypes", ["phone"]))
|
|
259
|
+
if device_types and device_type not in device_types:
|
|
260
|
+
raise PipelineError(
|
|
261
|
+
f"Package {package_id} does not support {device_type} devices: {device_types}"
|
|
262
|
+
)
|
|
205
263
|
return version
|
|
206
264
|
|
|
207
265
|
def _artifact(self, artifact: dict[str, Any]) -> Path:
|
|
@@ -216,9 +216,25 @@ class GitLabClient:
|
|
|
216
216
|
|
|
217
217
|
def delete_package(self, project: str, package_id: int) -> None:
|
|
218
218
|
encoded_project = urllib.parse.quote(str(project), safe="")
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
219
|
+
endpoint = f"projects/{encoded_project}/packages/{int(package_id)}"
|
|
220
|
+
last_error: PipelineError | None = None
|
|
221
|
+
for attempt in range(6):
|
|
222
|
+
try:
|
|
223
|
+
self._request("DELETE", endpoint)
|
|
224
|
+
return
|
|
225
|
+
except PipelineError as exc:
|
|
226
|
+
last_error = exc
|
|
227
|
+
retryable = any(f"HTTP {status}" in str(exc) for status in (429, 500, 502, 503, 504))
|
|
228
|
+
if not retryable or attempt == 5:
|
|
229
|
+
raise
|
|
230
|
+
delay = min(30, 2 ** attempt)
|
|
231
|
+
print(
|
|
232
|
+
f"[Registry] Delete rate-limited or temporarily unavailable "
|
|
233
|
+
f"(attempt {attempt + 1}/6); retrying in {delay}s"
|
|
234
|
+
)
|
|
235
|
+
time.sleep(delay)
|
|
236
|
+
assert last_error is not None
|
|
237
|
+
raise last_error
|
|
222
238
|
|
|
223
239
|
def commit_directory(
|
|
224
240
|
self,
|
|
@@ -31,6 +31,7 @@ class PackageOverride:
|
|
|
31
31
|
min_api: int | None = None
|
|
32
32
|
max_api: int | None = None
|
|
33
33
|
architectures: list[str] | None = None
|
|
34
|
+
device_types: list[str] | None = None
|
|
34
35
|
replaceable: bool = True
|
|
35
36
|
include: list[str] | None = None
|
|
36
37
|
exclude: list[str] = field(default_factory=list)
|
|
@@ -64,6 +65,7 @@ class BuiltPackage:
|
|
|
64
65
|
max_api: int | None
|
|
65
66
|
default_partition: str
|
|
66
67
|
architectures: list[str]
|
|
68
|
+
device_types: list[str]
|
|
67
69
|
primary_apk: str | None
|
|
68
70
|
replaceable: bool
|
|
69
71
|
files: list[PayloadFile]
|
|
@@ -75,8 +77,9 @@ class BuiltPackage:
|
|
|
75
77
|
dependencies: list[dict[str, Any]] = field(default_factory=list)
|
|
76
78
|
descriptor: dict[str, Any] = field(default_factory=dict)
|
|
77
79
|
|
|
78
|
-
def catalog_version(self, artifact_url: str
|
|
79
|
-
|
|
80
|
+
def catalog_version(self, artifact_url: str, android_version: str, source: str,
|
|
81
|
+
default_architecture: str, default_device_type: str) -> dict[str, Any]:
|
|
82
|
+
result = {
|
|
80
83
|
"versionName": self.metadata.version_name if self.metadata else self.version_key,
|
|
81
84
|
"versionCode": self.metadata.version_code if self.metadata else 0,
|
|
82
85
|
"packageName": self.metadata.package_name if self.metadata else None,
|
|
@@ -85,7 +88,8 @@ class BuiltPackage:
|
|
|
85
88
|
"targetApi": self.target_api,
|
|
86
89
|
"maxApi": self.max_api,
|
|
87
90
|
},
|
|
88
|
-
"
|
|
91
|
+
"supportedAndroidVersions": [str(android_version)],
|
|
92
|
+
"sources": [source],
|
|
89
93
|
"defaultPartition": self.default_partition,
|
|
90
94
|
"apk": {
|
|
91
95
|
"path": self.primary_apk,
|
|
@@ -100,3 +104,8 @@ class BuiltPackage:
|
|
|
100
104
|
"files": self.descriptor.get("files", []),
|
|
101
105
|
"install": self.descriptor.get("install"),
|
|
102
106
|
}
|
|
107
|
+
if self.architectures != [default_architecture]:
|
|
108
|
+
result["architectures"] = self.architectures
|
|
109
|
+
if self.device_types != [default_device_type]:
|
|
110
|
+
result["deviceTypes"] = self.device_types
|
|
111
|
+
return result
|