NikGapps 3.94__tar.gz → 3.96__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.
Files changed (95) hide show
  1. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/Build.py +25 -0
  2. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build_config.py +10 -3
  3. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/Args.py +10 -0
  4. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/Config.py +18 -0
  5. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/config_versions.json +3 -2
  6. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/main.py +41 -7
  7. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/overlay_control.py +53 -1
  8. {nikgapps-3.94 → nikgapps-3.96/NikGapps.egg-info}/PKG-INFO +1 -1
  9. {nikgapps-3.94 → nikgapps-3.96}/NikGapps.egg-info/SOURCES.txt +27 -1
  10. {nikgapps-3.94 → nikgapps-3.96}/NikGapps.egg-info/entry_points.txt +1 -0
  11. nikgapps-3.96/NikGapps.egg-info/top_level.txt +5 -0
  12. {nikgapps-3.94/NikGapps.egg-info → nikgapps-3.96}/PKG-INFO +1 -1
  13. nikgapps-3.96/nikgapps_package_pipeline/__init__.py +3 -0
  14. nikgapps-3.96/nikgapps_package_pipeline/__main__.py +3 -0
  15. nikgapps-3.96/nikgapps_package_pipeline/apk.py +136 -0
  16. nikgapps-3.96/nikgapps_package_pipeline/archive.py +176 -0
  17. nikgapps-3.96/nikgapps_package_pipeline/cli.py +369 -0
  18. nikgapps-3.96/nikgapps_package_pipeline/compat.py +44 -0
  19. nikgapps-3.96/nikgapps_package_pipeline/config.py +118 -0
  20. nikgapps-3.96/nikgapps_package_pipeline/consumer.py +318 -0
  21. nikgapps-3.96/nikgapps_package_pipeline/errors.py +2 -0
  22. nikgapps-3.96/nikgapps_package_pipeline/gitlab.py +267 -0
  23. nikgapps-3.96/nikgapps_package_pipeline/models.py +102 -0
  24. nikgapps-3.96/nikgapps_package_pipeline/pipeline.py +673 -0
  25. nikgapps-3.96/nikgapps_package_pipeline/scanner.py +103 -0
  26. nikgapps-3.96/nikgapps_package_pipeline/upgrade.py +247 -0
  27. nikgapps-3.96/nikgapps_package_pipeline/util.py +83 -0
  28. nikgapps-3.96/nikgapps_registry/__init__.py +5 -0
  29. nikgapps-3.96/nikgapps_registry/__main__.py +3 -0
  30. nikgapps-3.96/nikgapps_registry/cli.py +98 -0
  31. nikgapps-3.96/nikgapps_registry/service.py +197 -0
  32. {nikgapps-3.94 → nikgapps-3.96}/setup.py +3 -2
  33. nikgapps-3.96/tests_package_pipeline/__init__.py +1 -0
  34. nikgapps-3.96/tests_package_pipeline/test_consumer.py +153 -0
  35. nikgapps-3.96/tests_package_pipeline/test_overlay_control.py +63 -0
  36. nikgapps-3.96/tests_package_pipeline/test_pipeline.py +509 -0
  37. nikgapps-3.96/tests_package_pipeline/test_upgrade.py +101 -0
  38. nikgapps-3.96/tests_registry/__init__.py +0 -0
  39. nikgapps-3.96/tests_registry/test_registry.py +60 -0
  40. nikgapps-3.94/NikGapps.egg-info/top_level.txt +0 -1
  41. {nikgapps-3.94 → nikgapps-3.96}/LICENSE +0 -0
  42. {nikgapps-3.94 → nikgapps-3.96}/MANIFEST.in +0 -0
  43. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/__init__.py +0 -0
  44. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/NikGappsManager.py +0 -0
  45. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/NikGappsOverlays.py +0 -0
  46. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/PackageConstants.py +0 -0
  47. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/Release.py +0 -0
  48. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/build/__init__.py +0 -0
  49. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/cache.py +0 -0
  50. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/config/NikGappsConfig.py +0 -0
  51. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/config/__init__.py +0 -0
  52. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/config_upload.py +0 -0
  53. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/copy_repos.py +0 -0
  54. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/AppSet.py +0 -0
  55. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/Assets.py +0 -0
  56. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/ConfigObj.py +0 -0
  57. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/Package.py +0 -0
  58. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/__init__.py +0 -0
  59. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/addon.sh +0 -0
  60. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/appsets.json +0 -0
  61. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/changelogs.yaml +0 -0
  62. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/customize.sh +0 -0
  63. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/debloater.config +0 -0
  64. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/functions.sh +0 -0
  65. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/gofile.sh +0 -0
  66. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/header.sh +0 -0
  67. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/installer.sh +0 -0
  68. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/magisk-update-binary.sh +0 -0
  69. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/module.prop +0 -0
  70. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/mount.sh +0 -0
  71. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/mtg_mount.sh +0 -0
  72. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/nikgapps.config +0 -0
  73. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/nikgapps_functions.sh +0 -0
  74. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/packages.json +0 -0
  75. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/uninstaller.sh +0 -0
  76. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/assets/unmount.sh +0 -0
  77. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/compression/CompOps.py +0 -0
  78. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/compression/Export.py +0 -0
  79. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/compression/__init__.py +0 -0
  80. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/git/TestGit.py +0 -0
  81. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/git/__init__.py +0 -0
  82. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/ApkMetaInfo.py +0 -0
  83. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/Bool.py +0 -0
  84. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/Library.py +0 -0
  85. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/Manifest.py +0 -0
  86. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/Overlay.py +0 -0
  87. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/Resources.py +0 -0
  88. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/String.py +0 -0
  89. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/StringArray.py +0 -0
  90. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/helper/overlay/__init__.py +0 -0
  91. {nikgapps-3.94 → nikgapps-3.96}/NikGapps/test.py +0 -0
  92. {nikgapps-3.94 → nikgapps-3.96}/NikGapps.egg-info/dependency_links.txt +0 -0
  93. {nikgapps-3.94 → nikgapps-3.96}/NikGapps.egg-info/requires.txt +0 -0
  94. {nikgapps-3.94 → nikgapps-3.96}/README.md +0 -0
  95. {nikgapps-3.94 → nikgapps-3.96}/setup.cfg +0 -0
@@ -22,6 +22,31 @@ class Build:
22
22
  if cached:
23
23
  return app_set_build_list
24
24
  building = T()
25
+ if Config.PACKAGE_SOURCE == "registry":
26
+ print("[Registry] Package source mode is enabled; skipping the legacy APK repository")
27
+ from nikgapps_package_pipeline.consumer import (
28
+ CatalogPackageSource,
29
+ channel_overrides_from_env,
30
+ )
31
+ registry_source = CatalogPackageSource(
32
+ Config.PACKAGE_CATALOG_URL,
33
+ Config.PACKAGE_APPSETS_URL,
34
+ Path(Config.PACKAGE_CACHE),
35
+ channel=Config.PACKAGE_CHANNEL,
36
+ channel_overrides=channel_overrides_from_env(Config.PACKAGE_CHANNEL_OVERRIDES),
37
+ token=os.environ.get("GITLAB_TOKEN"),
38
+ )
39
+ Config.APK_SOURCE = str(registry_source.prepare(
40
+ app_set_build_list,
41
+ android_version,
42
+ getattr(Config, "PACKAGE_ARCH", "arm64"),
43
+ ))
44
+ print(f"[Registry] Handing prepared source to the existing builder: {Config.APK_SOURCE}")
45
+ print("[Registry] Final NikGapps ZIP assembly remains unchanged")
46
+ if Config.OVERLAY_SOURCE is None:
47
+ overlay_path = Path(Config.APK_SOURCE) / ".overlays"
48
+ overlay_path.mkdir(parents=True, exist_ok=True)
49
+ Config.OVERLAY_SOURCE = str(overlay_path)
25
50
  source_directory = Config.APK_SOURCE
26
51
  print(f"Building from {source_directory}")
27
52
  cmd = Cmd()
@@ -26,6 +26,11 @@ def build_config():
26
26
  if len(args.get_android_versions()) > 0:
27
27
  android_versions = args.get_android_versions()
28
28
  Config.UPLOAD_FILES = args.upload
29
+ if args.package_source:
30
+ Config.PACKAGE_SOURCE = args.package_source
31
+ if args.package_channel:
32
+ Config.PACKAGE_CHANNEL = args.package_channel
33
+ Config.PACKAGE_ARCH = args.arch
29
34
  print("---------------------------------------")
30
35
  print("Android Versions to build: " + str(android_versions))
31
36
  print("---------------------------------------")
@@ -48,10 +53,12 @@ def build_config():
48
53
  path = config_name
49
54
  F.write_string_in_lf_file(str_data=config_string, file_path=path)
50
55
  for android_version in android_versions:
51
- apk_repo = GitOp.clone_apk_source(android_version, args.arch, release_type=Config.RELEASE_TYPE)
52
- Config.APK_SOURCE = apk_repo.working_tree_dir
56
+ if Config.PACKAGE_SOURCE != "registry":
57
+ apk_repo = GitOp.clone_apk_source(android_version, args.arch, release_type=Config.RELEASE_TYPE)
58
+ Config.APK_SOURCE = apk_repo.working_tree_dir
53
59
  overlay_repo = GitOp.clone_overlay_repo(android_version)
54
- Config.OVERLAY_SOURCE = overlay_repo.working_tree_dir
60
+ if overlay_repo is not None:
61
+ Config.OVERLAY_SOURCE = overlay_repo.working_tree_dir
55
62
  config_obj = NikGappsConfig(android_version=android_version, config_path=path)
56
63
  print(f"Setting up package list from {config_name}...")
57
64
  config_obj.config_package_list = Build.build_from_directory(app_set_build_list=config_obj.config_package_list,
@@ -39,6 +39,14 @@ class Args:
39
39
  '-r', '--releaseType', help="It is the release type for which we need to build the gapps",
40
40
  default="stable", type=str)
41
41
  self.parser.add_argument('-P', '--packageList', help="List of packages to build", type=str)
42
+ self.parser.add_argument(
43
+ '--packageSource', choices=['git', 'registry'],
44
+ help="Use the legacy Git source or the package registry catalog"
45
+ )
46
+ self.parser.add_argument(
47
+ '--packageChannel', choices=['stable', 'beta', 'canary'],
48
+ help="Default registry channel for selected packages"
49
+ )
42
50
 
43
51
  args = self.parser.parse_args()
44
52
 
@@ -60,6 +68,8 @@ class Args:
60
68
  self.release_type = args.releaseType
61
69
  self.ssh_clone = args.sshClone
62
70
  self.send_zip_device = args.sendToDevice
71
+ self.package_source = args.packageSource
72
+ self.package_channel = args.packageChannel
63
73
  # self.oems = args.oems
64
74
 
65
75
  def get_package_list(self):
@@ -53,6 +53,24 @@ CACHED_SOURCE = None
53
53
  APK_SOURCE = None
54
54
  OVERLAY_SOURCE = None
55
55
 
56
+ # Package source. "git" preserves the existing clone behavior; "registry"
57
+ # resolves AppSets through the published package catalog and verified cache.
58
+ PACKAGE_SOURCE = os.environ.get("PACKAGE_SOURCE", "git").lower()
59
+ PACKAGE_CHANNEL = os.environ.get("PACKAGE_CHANNEL", RELEASE_TYPE).lower()
60
+ PACKAGE_CATALOG_URL = os.environ.get(
61
+ "PACKAGE_CATALOG_URL",
62
+ "https://gitlab.com/nikgapps/nikgapps-package-catalog/-/raw/main/catalog.json"
63
+ )
64
+ PACKAGE_APPSETS_URL = os.environ.get(
65
+ "PACKAGE_APPSETS_URL",
66
+ "https://gitlab.com/nikgapps/nikgapps-package-catalog/-/raw/main/appsets.json"
67
+ )
68
+ PACKAGE_CACHE = os.environ.get(
69
+ "PACKAGE_CACHE",
70
+ os.path.abspath(os.path.join(os.getcwd(), ".nikgapps-package-cache"))
71
+ )
72
+ PACKAGE_CHANNEL_OVERRIDES = os.environ.get("PACKAGE_CHANNEL_OVERRIDES", "{}")
73
+
56
74
  # Enabling this will enable the feature of building NikGapps using config file
57
75
  BUILD_CONFIG = True
58
76
  BUILD_EXCLUSIVE = (RELEASE_TYPE.lower().__eq__("stable"))
@@ -6,5 +6,6 @@
6
6
  "13": "37",
7
7
  "14": "37",
8
8
  "15": "37",
9
- "16": "38"
10
- }
9
+ "16": "38",
10
+ "17": "39"
11
+ }
@@ -21,6 +21,11 @@ def main():
21
21
  package_list = Config.BUILD_PACKAGE_LIST
22
22
  Config.UPLOAD_FILES = args.upload
23
23
  Config.USE_CACHED_APKS = args.use_cached_apks
24
+ if args.package_source:
25
+ Config.PACKAGE_SOURCE = args.package_source
26
+ if args.package_channel:
27
+ Config.PACKAGE_CHANNEL = args.package_channel
28
+ Config.PACKAGE_ARCH = args.arch
24
29
  if Config.USE_CACHED_APKS:
25
30
  print("Using Cached Apks")
26
31
  Modes.DEFAULT = Modes.TAR_XZ if args.tar else Modes.ZIP
@@ -39,13 +44,17 @@ def main():
39
44
  Config.TARGET_ANDROID_VERSION = android_version
40
45
  # clone the apk repo if it doesn't exist
41
46
  if args.enable_git_clone:
42
- if Config.USE_CACHED_APKS:
43
- cached_repo = GitOp.clone_apk_source(android_version, release_type=Config.RELEASE_TYPE,
44
- cached=True)
45
- Config.CACHED_SOURCE = cached_repo.working_tree_dir
46
- else:
47
- apk_repo = GitOp.clone_apk_source(android_version, args.arch, release_type=Config.RELEASE_TYPE)
48
- Config.APK_SOURCE = apk_repo.working_tree_dir
47
+ if Config.PACKAGE_SOURCE != "registry":
48
+ if Config.USE_CACHED_APKS:
49
+ cached_repo = GitOp.clone_apk_source(android_version, release_type=Config.RELEASE_TYPE,
50
+ cached=True)
51
+ Config.CACHED_SOURCE = cached_repo.working_tree_dir
52
+ else:
53
+ apk_repo = GitOp.clone_apk_source(android_version, args.arch, release_type=Config.RELEASE_TYPE)
54
+ Config.APK_SOURCE = apk_repo.working_tree_dir
55
+ # Overlays are not catalog artifacts yet. Keep the established
56
+ # overlay source so registry-backed builds retain output parity.
57
+ if not Config.USE_CACHED_APKS:
49
58
  overlay_repo = GitOp.clone_overlay_repo(android_version)
50
59
  if overlay_repo is not None:
51
60
  Config.OVERLAY_SOURCE = overlay_repo.working_tree_dir
@@ -53,6 +62,31 @@ def main():
53
62
  Release.zip(package_list, android_version, args.arch, args.sign)
54
63
  if Config.RELEASE_TYPE and Config.ENVIRONMENT_TYPE == "production":
55
64
  GitOp.mark_a_release(android_version, Config.RELEASE_TYPE)
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
56
90
  if args.release:
57
91
  if args.update_website:
58
92
  website_repo = TestGit(GitStatics.website_repo_dir)
@@ -1,3 +1,4 @@
1
+ import argparse
1
2
  import os
2
3
  from pathlib import Path
3
4
 
@@ -11,11 +12,49 @@ from niklibrary.git.Git import Git
11
12
  from NikGapps.helper.overlay.Overlay import Overlay
12
13
 
13
14
 
15
+ ANDROID_PLATFORM_VERSIONS = {
16
+ # niklibrary 0.28 predates Android 17. Keep the compatibility entry here
17
+ # until the same mapping is available in the shared library.
18
+ "17": {"sdk": "37", "code": "CinnamonBun"},
19
+ }
20
+
21
+
22
+ def register_android_platform(android_version):
23
+ version = str(android_version)
24
+ if version not in Statics.android_versions and version in ANDROID_PLATFORM_VERSIONS:
25
+ Statics.android_versions[version] = ANDROID_PLATFORM_VERSIONS[version]
26
+
27
+
28
+ def build_local_overlays(android_version, source_dir, output_dir):
29
+ """Generate and compile overlays locally without cloning or pushing repos."""
30
+ register_android_platform(android_version)
31
+ source_dir = os.path.abspath(source_dir)
32
+ output_dir = os.path.abspath(output_dir)
33
+ os.makedirs(source_dir, exist_ok=True)
34
+ os.makedirs(output_dir, exist_ok=True)
35
+ for overlay in NikGappsOverlays.get_overlay(android_version=android_version):
36
+ overlay.build_apk_source(source_dir)
37
+ built = []
38
+ for folder in sorted(Path(source_dir).iterdir()):
39
+ if not folder.is_dir() or not F.file_exists(os.path.join(str(folder), "apktool.yml")):
40
+ continue
41
+ overlay_path = Cmd().build_overlay(folder_name=str(folder))
42
+ if not overlay_path:
43
+ raise RuntimeError(f"Failed to build Android {android_version} overlay: {folder.name}")
44
+ destination = os.path.join(output_dir, folder.name, f"{folder.name}.apk")
45
+ F.copy_file(overlay_path, destination)
46
+ F.remove_dir(os.path.join(str(folder), "dist"))
47
+ F.remove_dir(os.path.join(str(folder), "build"))
48
+ built.append(destination)
49
+ return built
50
+
51
+
14
52
  def overlay_control():
15
53
  args = Args()
16
54
  android_versions = args.get_android_versions()
17
55
 
18
56
  for android_version in android_versions:
57
+ register_android_platform(android_version)
19
58
  repo_name = Statics.get_overlay_source_repo(android_version)
20
59
  repo_dir = Statics.get_overlay_source_directory(android_version)
21
60
  branch = "main"
@@ -64,4 +103,17 @@ def overlay_control():
64
103
 
65
104
 
66
105
  if __name__ == "__main__":
67
- overlay_control()
106
+ parser = argparse.ArgumentParser(description="Build NikGapps resource overlays")
107
+ parser.add_argument("--android-version")
108
+ parser.add_argument("--source-dir")
109
+ parser.add_argument("--output-dir")
110
+ options = parser.parse_args()
111
+ if options.android_version:
112
+ if not options.source_dir or not options.output_dir:
113
+ parser.error("--source-dir and --output-dir are required in local mode")
114
+ paths = build_local_overlays(
115
+ options.android_version, options.source_dir, options.output_dir
116
+ )
117
+ print(f"Built {len(paths)} overlays for Android {options.android_version}")
118
+ else:
119
+ overlay_control()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NikGapps
3
- Version: 3.94
3
+ Version: 3.96
4
4
  Summary: A short description of your project
5
5
  Home-page: https://github.com/nikgapps/project
6
6
  Author: Nikhil Menghani
@@ -63,4 +63,30 @@ NikGapps/helper/overlay/Overlay.py
63
63
  NikGapps/helper/overlay/Resources.py
64
64
  NikGapps/helper/overlay/String.py
65
65
  NikGapps/helper/overlay/StringArray.py
66
- NikGapps/helper/overlay/__init__.py
66
+ NikGapps/helper/overlay/__init__.py
67
+ nikgapps_package_pipeline/__init__.py
68
+ nikgapps_package_pipeline/__main__.py
69
+ nikgapps_package_pipeline/apk.py
70
+ nikgapps_package_pipeline/archive.py
71
+ nikgapps_package_pipeline/cli.py
72
+ nikgapps_package_pipeline/compat.py
73
+ nikgapps_package_pipeline/config.py
74
+ nikgapps_package_pipeline/consumer.py
75
+ nikgapps_package_pipeline/errors.py
76
+ nikgapps_package_pipeline/gitlab.py
77
+ nikgapps_package_pipeline/models.py
78
+ nikgapps_package_pipeline/pipeline.py
79
+ nikgapps_package_pipeline/scanner.py
80
+ nikgapps_package_pipeline/upgrade.py
81
+ nikgapps_package_pipeline/util.py
82
+ nikgapps_registry/__init__.py
83
+ nikgapps_registry/__main__.py
84
+ nikgapps_registry/cli.py
85
+ nikgapps_registry/service.py
86
+ tests_package_pipeline/__init__.py
87
+ tests_package_pipeline/test_consumer.py
88
+ tests_package_pipeline/test_overlay_control.py
89
+ tests_package_pipeline/test_pipeline.py
90
+ tests_package_pipeline/test_upgrade.py
91
+ tests_registry/__init__.py
92
+ tests_registry/test_registry.py
@@ -3,5 +3,6 @@ build = NikGapps.build_config:build_config
3
3
  cache = NikGapps.cache:cache
4
4
  copy_repos = NikGapps.copy_repos:copy_repos
5
5
  nikgapps = NikGapps.main:main
6
+ nikgapps-registry = nikgapps_registry.cli:main
6
7
  nikgapps_config_upload = NikGapps.config_upload:config_upload
7
8
  nikgapps_overlay = NikGapps.overlay_control:overlay_control
@@ -0,0 +1,5 @@
1
+ NikGapps
2
+ nikgapps_package_pipeline
3
+ nikgapps_registry
4
+ tests_package_pipeline
5
+ tests_registry
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NikGapps
3
- Version: 3.94
3
+ Version: 3.96
4
4
  Summary: A short description of your project
5
5
  Home-page: https://github.com/nikgapps/project
6
6
  Author: Nikhil Menghani
@@ -0,0 +1,3 @@
1
+ """Independent NikGapps package migration and publishing pipeline."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import subprocess
5
+ import os
6
+ from pathlib import Path
7
+
8
+ from .errors import PipelineError
9
+ from .models import ApkMetadata
10
+
11
+
12
+ class ApkInspector:
13
+ """APK metadata boundary; injectable for tests and alternate tooling."""
14
+
15
+ def inspect(self, apk: Path) -> ApkMetadata:
16
+ raise NotImplementedError
17
+
18
+ def privileged_permissions(self, apk: Path) -> list[str]:
19
+ """Return permissions requested/declared by a privileged APK."""
20
+ return []
21
+
22
+
23
+ class Aapt2Inspector(ApkInspector):
24
+ def __init__(self, executable: str = "aapt2") -> None:
25
+ self.executable = executable
26
+
27
+ def inspect(self, apk: Path) -> ApkMetadata:
28
+ badging = self._dump(["dump", "badging", str(apk)], apk)
29
+ if not re.search(r"^sdkVersion:'\d+'", badging, re.MULTILINE):
30
+ manifest = self._dump(
31
+ ["dump", "xmltree", str(apk), "--file", "AndroidManifest.xml"],
32
+ apk,
33
+ )
34
+ uses_sdk = re.search(
35
+ r"E: uses-sdk\b(?P<body>.*?)(?=\n\s*E: |\Z)",
36
+ manifest,
37
+ re.DOTALL,
38
+ )
39
+ minimum = re.search(
40
+ r"minSdkVersion[^=]*=(?:\(type [^)]+\))?(\d+)",
41
+ uses_sdk.group("body") if uses_sdk else "",
42
+ )
43
+ if not minimum:
44
+ raise PipelineError(
45
+ f"Missing minimum SDK in both badging and manifest output for {apk}"
46
+ )
47
+ badging += f"\nsdkVersion:'{minimum.group(1)}'\n"
48
+ return self.parse(badging, apk)
49
+
50
+ def privileged_permissions(self, apk: Path) -> list[str]:
51
+ output = self._dump(["dump", "permissions", str(apk)], apk)
52
+ permissions: set[str] = set()
53
+ for line in output.splitlines():
54
+ stripped = line.strip()
55
+ requested = re.match(r"uses-permission(?:-sdk-\d+)?:\s+name='([^']+)'", stripped)
56
+ declared = re.match(r"permission:\s+(?:name=)?'?([^'\s]+)'?", stripped)
57
+ match = requested or declared
58
+ if match:
59
+ permissions.add(match.group(1))
60
+ return sorted(permissions)
61
+
62
+ def _dump(self, arguments: list[str], apk: Path) -> str:
63
+ try:
64
+ result = subprocess.run(
65
+ [self.executable, *arguments],
66
+ check=True,
67
+ capture_output=True,
68
+ )
69
+ except FileNotFoundError as exc:
70
+ raise PipelineError(
71
+ f"{self.executable!r} was not found. Install Android build-tools "
72
+ "or pass --aapt2 with its full path."
73
+ ) from exc
74
+ except subprocess.CalledProcessError as exc:
75
+ raw_detail = exc.stderr or exc.stdout or b""
76
+ detail = raw_detail.decode("utf-8", errors="replace").strip()
77
+ raise PipelineError(f"aapt2 failed for {apk}: {detail}") from exc
78
+ return result.stdout.decode("utf-8", errors="replace")
79
+
80
+ @staticmethod
81
+ def parse(text: str, apk: Path | None = None) -> ApkMetadata:
82
+ def required(pattern: str, name: str) -> str:
83
+ match = re.search(pattern, text, re.MULTILINE)
84
+ if not match:
85
+ raise PipelineError(f"Missing {name} in aapt2 output for {apk or 'APK'}")
86
+ return match.group(1)
87
+
88
+ return ApkMetadata(
89
+ package_name=required(r"^package: name='([^']+)'", "package name"),
90
+ version_name=required(r"^package:.*versionName='([^']+)'", "version name"),
91
+ version_code=int(required(r"^package:.*versionCode='(\d+)'", "version code")),
92
+ min_api=int(required(r"^sdkVersion:'(\d+)'", "minimum SDK")),
93
+ target_api=int(required(r"^targetSdkVersion:'(\d+)'", "target SDK")),
94
+ )
95
+
96
+
97
+ def find_aapt2() -> str:
98
+ """Find aapt2 in PATH or a conventional local Android SDK."""
99
+ import shutil
100
+
101
+ on_path = shutil.which("aapt2")
102
+ if on_path:
103
+ return on_path
104
+ project_root = Path(__file__).resolve().parent.parent
105
+ bundled = (
106
+ project_root.parent / "nikassets" / "nikassets" / "helper" / "assets"
107
+ / "bin" / ("Windows" if os.name == "nt" else "Linux")
108
+ / ("aapt2.exe" if os.name == "nt" else "aapt2")
109
+ )
110
+ if bundled.is_file():
111
+ return str(bundled)
112
+ sdk_roots = [
113
+ os.environ.get("ANDROID_SDK_ROOT"),
114
+ os.environ.get("ANDROID_HOME"),
115
+ str(Path.home() / "AppData" / "Local" / "Android" / "Sdk"),
116
+ ]
117
+ for raw_root in sdk_roots:
118
+ if not raw_root:
119
+ continue
120
+ build_tools = Path(raw_root) / "build-tools"
121
+ try:
122
+ exists = build_tools.is_dir()
123
+ except OSError:
124
+ continue
125
+ if not exists:
126
+ continue
127
+ try:
128
+ candidates = sorted(
129
+ build_tools.glob("*/aapt2.exe" if os.name == "nt" else "*/aapt2"),
130
+ reverse=True,
131
+ )
132
+ except OSError:
133
+ continue
134
+ if candidates:
135
+ return str(candidates[0])
136
+ return "aapt2"
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ import zipfile
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from .errors import PipelineError
8
+ from .models import PayloadFile
9
+ from .util import json_data, safe_path
10
+
11
+ ZIP_TIME = (1980, 1, 1, 0, 0, 0)
12
+ ALREADY_COMPRESSED = {
13
+ ".apk", ".zip", ".jar", ".gz", ".png", ".jpg", ".jpeg", ".webp",
14
+ ".mp3", ".mp4", ".so", ".tflite",
15
+ }
16
+
17
+
18
+ def _write_entry(archive: zipfile.ZipFile, name: str, data: bytes, compress: int) -> None:
19
+ safe_path(name)
20
+ info = zipfile.ZipInfo(name, ZIP_TIME)
21
+ info.create_system = 3
22
+ info.external_attr = 0o100644 << 16
23
+ info.compress_type = compress
24
+ archive.writestr(info, data, compress_type=compress, compresslevel=9)
25
+
26
+
27
+ def write_package_zip(
28
+ destination: Path,
29
+ files: list[PayloadFile],
30
+ manifest: dict[str, Any],
31
+ *,
32
+ pretty: bool = True,
33
+ ) -> None:
34
+ if any(item.path in {"package.json", "installer.sh", "uninstaller.sh"} for item in files):
35
+ raise PipelineError("Payload collides with reserved package.json")
36
+ destination.parent.mkdir(parents=True, exist_ok=True)
37
+ with zipfile.ZipFile(destination, "w") as archive:
38
+ for item in sorted(files, key=lambda value: value.path):
39
+ compression = (
40
+ zipfile.ZIP_STORED
41
+ if item.source.suffix.casefold() in ALREADY_COMPRESSED
42
+ else zipfile.ZIP_DEFLATED
43
+ )
44
+ _write_entry(archive, encode_builder_path(item.path), item.source.read_bytes(), compression)
45
+ for generated in sorted(manifest.get("generatedFiles", []), key=lambda value: value["path"]):
46
+ _write_entry(
47
+ archive,
48
+ encode_builder_path(generated["path"]),
49
+ generated["content"].encode("utf-8"),
50
+ zipfile.ZIP_DEFLATED,
51
+ )
52
+ _write_entry(archive, "installer.sh", package_installer(manifest).encode(), zipfile.ZIP_DEFLATED)
53
+ _write_entry(archive, "uninstaller.sh", package_uninstaller(manifest).encode(), zipfile.ZIP_DEFLATED)
54
+ _write_entry(archive, "package.json", json_data(manifest, pretty), zipfile.ZIP_DEFLATED)
55
+
56
+
57
+ def encode_builder_path(path: str) -> str:
58
+ """Encode an install path using the nested-package format used by NikGapps."""
59
+ safe_path(path)
60
+ parts = path.split("/")
61
+ return "___" + "___".join(parts[:-1]) + "/" + parts[-1]
62
+
63
+
64
+ def _lines(values: list[str]) -> str:
65
+ return "\n".join(values)
66
+
67
+
68
+ def _legacy_asset(name: str) -> str:
69
+ path = Path(__file__).resolve().parent.parent / "NikGapps" / "helper" / "assets" / name
70
+ try:
71
+ return path.read_text(encoding="utf-8")
72
+ except OSError as exc:
73
+ raise PipelineError(f"Cannot read legacy package template {path}: {exc}") from exc
74
+
75
+
76
+ def package_installer(manifest: dict[str, Any]) -> str:
77
+ install = manifest["install"]
78
+ apk = manifest.get("apk") or {}
79
+ file_list = [item["archivePath"] for item in manifest["files"]]
80
+ extra = install.get("additionalInstallerScript") or ""
81
+ extra_block = f"{extra}\n" if extra else ""
82
+ validation = install.get("validationScript") or "find_install_mode"
83
+ remove_files = [value for value in install["removeFiles"] if value.strip()]
84
+ remove_overlays = [value for value in install["removeOverlays"] if value.strip()]
85
+ remove_files_block = "".join(f"{value}\n" for value in remove_files)
86
+ remove_overlays_block = "".join(f"{value}\n" for value in remove_overlays)
87
+ file_list_block = "".join(f"{value}\n" for value in file_list)
88
+ folders: list[str] = []
89
+ for row in manifest["files"]:
90
+ parts = row["path"].split("/")[:-1]
91
+ for index in range(1, len(parts) + 1):
92
+ folder = "/".join(parts[:index])
93
+ if parts and parts[0] not in {"system", "vendor", "product", "system_ext", "overlay"} and folder not in folders:
94
+ folders.append(folder)
95
+ make_dirs = "".join(f' make_dir "{folder}"\n' for folder in folders)
96
+ return _legacy_asset("installer.sh") + f'''# Initialize the variables
97
+ default_partition="{manifest["defaultPartition"]}"
98
+ clean_flash_only="{str(install["cleanFlashOnly"]).lower()}"
99
+ product_prefix=$(find_product_prefix "$install_partition")
100
+ title="{install["title"]}"
101
+ package_title="{install["packageTitle"]}"
102
+ pkg_size="{install["payloadSize"]}"
103
+ package_name="{manifest.get("packageName") or ""}"
104
+ packagePath=install{install["packageTitle"]}Files
105
+ deleteFilesPath=delete{install["packageTitle"]}Files
106
+ propFilePath=$(get_prop_file_path $package_title)
107
+
108
+ remove_aosp_apps_from_rom="
109
+ {remove_files_block}"
110
+
111
+ delete_overlays="
112
+ {remove_overlays_block}"
113
+
114
+ file_list="
115
+ {file_list_block}"
116
+
117
+ remove_overlays() {{
118
+ for i in $delete_overlays; do
119
+ delete_overlays "$i" "$propFilePath" "$package_title"
120
+ done
121
+ }}
122
+
123
+ remove_existing_package() {{
124
+ # remove the existing folder for clean install of {install["packageTitle"]}
125
+ delete_package "{install["title"]}" "$package_title"
126
+ }}
127
+
128
+ remove_aosp_apps() {{
129
+ # Delete the folders that we want to remove with installing {install["packageTitle"]}
130
+ for i in $remove_aosp_apps_from_rom; do
131
+ RemoveAospAppsFromRom "$i" "$propFilePath" "$package_title"
132
+ done
133
+ }}
134
+
135
+ install_package() {{
136
+ remove_existing_package
137
+ remove_aosp_apps
138
+ remove_overlays
139
+ # Create folders and set the permissions
140
+ {make_dirs}
141
+ delete_prop_lines "$propFilePath"
142
+
143
+ # Copy the files and set the permissions
144
+ for i in $file_list; do
145
+ install_file "$i"
146
+ done
147
+
148
+ {extra_block} chmod 755 "$COMMONDIR/addon.sh";
149
+ update_prop "$propFilePath" "install" "$propFilePath" "{install["packageTitle"]}"
150
+ . $COMMONDIR/addon.sh "{install["packageTitle"]}" "$propFilePath" "{install["addonIndex"]}"
151
+ copy_file "$propFilePath" "$logDir/addonfiles/$package_title.prop"
152
+ }}
153
+
154
+ {validation}
155
+
156
+ '''
157
+
158
+
159
+ def package_uninstaller(manifest: dict[str, Any]) -> str:
160
+ install = manifest["install"]
161
+ file_list = [item["archivePath"] for item in manifest["files"]]
162
+ return _legacy_asset("uninstaller.sh") + f'''
163
+
164
+ # Initialize the variables
165
+ uninstall_addon=$1
166
+ clean_flash_only="{str(install["cleanFlashOnly"]).lower()}"
167
+ title="{install["title"]}"
168
+ package_title="{install["packageTitle"]}"
169
+ package_name="{manifest.get("packageName") or ""}"
170
+
171
+ file_list="
172
+ {_lines(file_list)}
173
+ "
174
+
175
+ uninstall_package
176
+ '''