unity-devkit 0.1.23__tar.gz → 0.1.24__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 (26) hide show
  1. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/PKG-INFO +2 -2
  2. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/pyproject.toml +2 -2
  3. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/ci_build_unity.py +33 -26
  4. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/compile_check_unity.py +12 -3
  5. unity_devkit-0.1.24/src/unity_devkit/identity.py +14 -0
  6. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/install.py +47 -51
  7. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/license.py +4 -1
  8. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/license_restore.py +6 -12
  9. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/matrix.py +2 -5
  10. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/upm_cache.py +4 -13
  11. unity_devkit-0.1.24/tests/test_identity.py +18 -0
  12. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_matrix.py +2 -19
  13. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/.gitignore +0 -0
  14. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/__init__.py +0 -0
  15. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/build_unity.py +0 -0
  16. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/lock_unity.py +0 -0
  17. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/player_build.py +0 -0
  18. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/projects.py +0 -0
  19. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/py.typed +0 -0
  20. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/test_unity.py +0 -0
  21. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/fixtures/environment-dump.json +0 -0
  22. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_build_player.py +0 -0
  23. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_prepare.py +0 -0
  24. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_projects.py +0 -0
  25. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_run_unity_batchmode.py +0 -0
  26. {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_upm_cache.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unity-devkit
3
- Version: 0.1.23
3
+ Version: 0.1.24
4
4
  Summary: Unity build, license, and CI tooling
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: bashrun>=0.1.0
7
- Requires-Dist: ci-devkit>=0.1.0
7
+ Requires-Dist: ci-devkit==0.1.15.dev36756330648
8
8
  Requires-Dist: pydantic-settings>=2.9.1
9
9
  Requires-Dist: pydantic>=2.11.7
10
10
  Requires-Dist: typer>=0.17.4
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "unity-devkit"
3
- version = "0.1.23"
3
+ version = "0.1.24"
4
4
  description = "Unity build, license, and CI tooling"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = [
7
7
  "bashrun>=0.1.0",
8
- "ci-devkit>=0.1.0",
8
+ "ci-devkit==0.1.15.dev36756330648",
9
9
  "typer>=0.17.4",
10
10
  "pydantic>=2.11.7",
11
11
  "pydantic-settings>=2.9.1",
@@ -8,10 +8,12 @@ import typer
8
8
  from bashrun.bash import bash
9
9
  from pydantic_settings import BaseSettings
10
10
 
11
+ from ci_devkit.builds import build_repository, push_build
11
12
  from ci_devkit.cache import restore, save
12
13
  from ci_devkit.ci_step import ci_step
13
14
  from ci_devkit.setup import configure_git, install_dotnet
14
15
  from ci_devkit.setup_oras import install_oras
16
+ from .identity import builds_registry, cache_key, cache_registry
15
17
  from .license_restore import restore_or_activate_license
16
18
  from .player_build import build_player, parse_environment_fields, resolve_unity_project
17
19
  from .upm_cache import restore_upm_cache, save_upm_cache
@@ -28,11 +30,17 @@ app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
28
30
  def ci_build_unity(
29
31
  project: Annotated[str, typer.Option(help="Project name")],
30
32
  platform: Annotated[str, typer.Option(help="Target platform")],
31
- cache_key: Annotated[str, typer.Option(help="Cache key prefix")],
32
- registry: Annotated[str, typer.Option(help="OCI registry path for the library cache")],
33
- builds_registry: Annotated[str, typer.Option(help="OCI registry path for build artifacts")],
34
- run_number: Annotated[int, typer.Option(help="CI run number")] = 0,
35
- branch: Annotated[str, typer.Option(help="Git branch name")] = "dev",
33
+ run_number: Annotated[int, typer.Option(help="CI run number (also the bundleVersionCode)")],
34
+ registry: Annotated[str, typer.Option(help="OCI registry root; the verb derives the cache and builds namespaces")],
35
+ pr_number: Annotated[
36
+ str, typer.Option(help="Pull-request number; empty on non-PR runs selects the dev cache scope")
37
+ ] = "",
38
+ license_pin: Annotated[
39
+ str,
40
+ typer.Option(
41
+ "--license", help="License cache pin for this run (the matrix verb's license output; empty uses today's)"
42
+ ),
43
+ ] = "",
36
44
  environment_preset: Annotated[
37
45
  str, typer.Option(help="Environment preset name; empty leaves the workspace untouched")
38
46
  ] = "",
@@ -46,22 +54,21 @@ def ci_build_unity(
46
54
  ) -> None:
47
55
  settings = Settings.model_validate({})
48
56
  fields = parse_environment_fields(environment_fields.splitlines())
57
+ parsed_pr_number = int(pr_number) if pr_number else None
58
+ derived_cache_key = cache_key(project, platform, parsed_pr_number)
59
+ library_cache_registry = cache_registry(registry)
60
+ builds_namespace = builds_registry(registry)
49
61
 
50
62
  with ci_step("Setup"):
51
63
  configure_git(settings.github_workspace)
52
64
  install_dotnet("8.0")
53
65
  install_oras()
54
- restore_or_activate_license()
66
+ restore_or_activate_license(library_cache_registry, license_pin or None)
55
67
  project_path = resolve_unity_project(project).path
56
- restore_upm_cache(project_path)
57
-
58
- branch_slug = branch.replace("/", "-")
59
- tag = f"{cache_key}-{platform}-{branch_slug}"
60
- fallback_branch = "dev"
61
- fallback_tags = [f"{cache_key}-{platform}-{fallback_branch}"] if branch_slug != fallback_branch else None
68
+ restore_upm_cache(project_path, library_cache_registry)
62
69
 
63
70
  with ci_step("Restore library cache"):
64
- restore(registry, "unity-library", tag, Path("."), fallback_tags=fallback_tags)
71
+ restore(library_cache_registry, "unity-library", derived_cache_key, Path("."))
65
72
 
66
73
  with ci_step(f"Build {project} [{platform}]"):
67
74
  build_player(
@@ -81,10 +88,12 @@ def ci_build_unity(
81
88
  shutil.rmtree(package_cache)
82
89
 
83
90
  relative_project_path = project_path.relative_to(Path.cwd())
84
- save(registry, "unity-library", tag, Path("."), [f"{relative_project_path}/Library/"])
91
+ save(
92
+ library_cache_registry, "unity-library", derived_cache_key, Path("."), [f"{relative_project_path}/Library/"]
93
+ )
85
94
 
86
95
  with ci_step("Save UPM cache"):
87
- save_upm_cache(project_path)
96
+ save_upm_cache(project_path, library_cache_registry)
88
97
 
89
98
  with ci_step("Collect build artifacts"):
90
99
  build_directory = project_path / "Build"
@@ -107,8 +116,7 @@ def ci_build_unity(
107
116
  if not artifact_directory.is_dir() or not any(artifact_directory.iterdir()):
108
117
  print("No build artifacts to push")
109
118
  else:
110
- reference_base = f"{builds_registry}/{project}-{platform}".lower()
111
- tags = (branch_slug, f"run-{run_number}")
119
+ tags = (derived_cache_key, f"run-{run_number}")
112
120
  if platform == "Linux":
113
121
  staging = Path("/tmp/unity-builds-push")
114
122
  staging.mkdir(parents=True, exist_ok=True)
@@ -116,17 +124,16 @@ def ci_build_unity(
116
124
  bash(f"tar -cf {tar_path} -C {artifact_directory} .")
117
125
  try:
118
126
  for build_tag in tags:
119
- bash(f"oras push {reference_base}:{build_tag} build.tar", cwd=staging)
127
+ push_build(builds_namespace, project, platform, build_tag, staging, ["build.tar"])
120
128
  finally:
121
129
  tar_path.unlink(missing_ok=True)
122
130
  else:
123
- joined = " ".join(
124
- sorted(
125
- str(path.relative_to(artifact_directory))
126
- for path in artifact_directory.rglob("*")
127
- if path.is_file()
128
- )
131
+ paths = sorted(
132
+ str(path.relative_to(artifact_directory))
133
+ for path in artifact_directory.rglob("*")
134
+ if path.is_file()
129
135
  )
130
136
  for build_tag in tags:
131
- bash(f"oras push {reference_base}:{build_tag} {joined}", cwd=artifact_directory)
132
- print(f"Pushed build artifacts: {reference_base} ({', '.join(tags)})")
137
+ push_build(builds_namespace, project, platform, build_tag, artifact_directory, paths)
138
+ repository = build_repository(builds_namespace, project, platform)
139
+ print(f"Pushed build artifacts: {repository} ({', '.join(tags)})")
@@ -8,6 +8,7 @@ from pydantic_settings import BaseSettings
8
8
  from ci_devkit.ci_step import ci_step
9
9
  from ci_devkit.setup import configure_git
10
10
  from ci_devkit.setup_oras import install_oras
11
+ from .identity import cache_registry
11
12
  from .license_restore import restore_or_activate_license
12
13
  from .player_build import prepare_unity_project, resolve_unity_project, run_unity_batchmode
13
14
  from .upm_cache import restore_upm_cache, save_upm_cache
@@ -25,6 +26,13 @@ COMPILE_ERROR_SIGNATURES = ("error CS",)
25
26
  @app.command()
26
27
  def compile_check_unity(
27
28
  project: Annotated[str, typer.Option(help="Unity project name (the 'name' field of its unity-devkit.json)")],
29
+ registry: Annotated[str, typer.Option(help="OCI registry root; the verb derives the cache namespace")],
30
+ license_pin: Annotated[
31
+ str,
32
+ typer.Option(
33
+ "--license", help="License cache pin for this run (the matrix verb's license output; empty uses today's)"
34
+ ),
35
+ ] = "",
28
36
  execute_method: Annotated[
29
37
  str | None,
30
38
  typer.Option(help="Static method to run after load (Class.Method) — one editor session per invocation"),
@@ -36,12 +44,13 @@ def compile_check_unity(
36
44
  ) -> None:
37
45
  settings = Settings.model_validate({})
38
46
  project_path = resolve_unity_project(project).path
47
+ cache_namespace = cache_registry(registry)
39
48
 
40
49
  with ci_step("Setup"):
41
50
  configure_git(settings.github_workspace)
42
51
  install_oras()
43
- restore_or_activate_license()
44
- restore_upm_cache(project_path)
52
+ restore_or_activate_license(cache_namespace, license_pin or None)
53
+ restore_upm_cache(project_path, cache_namespace)
45
54
 
46
55
  extra_flags = ""
47
56
  if build_target:
@@ -51,5 +60,5 @@ def compile_check_unity(
51
60
  print(f"Compile-checking {project}...")
52
61
  prepare_unity_project(project_path)
53
62
  run_unity_batchmode(project_path, extra_flags, extra_failure_signatures=COMPILE_ERROR_SIGNATURES)
54
- save_upm_cache(project_path)
63
+ save_upm_cache(project_path, cache_namespace)
55
64
  print(" Compiles clean")
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def cache_key(project: str, platform: str, pr_number: int | None) -> str:
5
+ scope = f"pr-{pr_number}" if pr_number is not None else "dev"
6
+ return f"{project.lower()}-{platform}-{scope}"
7
+
8
+
9
+ def cache_registry(root: str) -> str:
10
+ return f"{root}/cache"
11
+
12
+
13
+ def builds_registry(root: str) -> str:
14
+ return f"{root}/builds"
@@ -1,15 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- import shutil
5
4
  from pathlib import Path
5
+ from subprocess import CalledProcessError
6
6
  from typing import Annotated
7
7
 
8
8
  import typer
9
- from bashrun.bash import bash, bash_check, bash_handoff, bash_output
9
+ from bashrun.bash import bash, bash_handoff, bash_output
10
10
 
11
- from .projects import discover_projects
11
+ from ci_devkit.builds import build_reference, build_repository, list_build_tags, pull_build
12
+ from ci_devkit.setup_oras import install_oras
13
+
14
+ from .identity import builds_registry, cache_key
12
15
  from .player_build import build_player
16
+ from .projects import discover_projects
13
17
 
14
18
  INSTALLABLE_TARGETS = {"AndroidMobile", "MagicLeap2", "Linux"}
15
19
  ADB_TARGETS = {"AndroidMobile", "MagicLeap2"}
@@ -25,10 +29,10 @@ def main(
25
29
  str | None,
26
30
  typer.Option("--target", "-t", help="Device target (AndroidMobile, MagicLeap2, Linux)"),
27
31
  ] = None,
28
- branch: Annotated[
29
- str | None,
30
- typer.Option("--branch", "-b", help="Branch to pull the build from (default: current git branch)"),
31
- ] = None,
32
+ pr_number: Annotated[
33
+ str,
34
+ typer.Option("--pr-number", help="Pull-request number; empty (outside PRs) selects the dev cache-key tag"),
35
+ ] = "",
32
36
  run: Annotated[
33
37
  int | None,
34
38
  typer.Option("--run", "-r", help="Specific CI run number (github.run_number) to pull"),
@@ -41,7 +45,7 @@ def main(
41
45
  "-B",
42
46
  help=(
43
47
  "Build the project locally via `build-unity` and install the produced APK / "
44
- "linux executable. Skips the OCI pull; --branch / --run are ignored."
48
+ "linux executable. Skips the OCI pull; --pr-number / --run are ignored."
45
49
  ),
46
50
  ),
47
51
  ] = False,
@@ -79,26 +83,20 @@ def main(
79
83
  if serial and target_name not in ADB_TARGETS:
80
84
  print(f"Warning: --serial is ignored for target '{target_name}'")
81
85
 
82
- package_name = f"{project_name}-{target_name}".lower()
83
-
84
86
  if list_tags:
85
- _require_oras()
86
- reference_base = _resolve_reference_base(package_name)
87
- _login_ghcr()
88
- if bash_check(f"oras repo tags {reference_base}"):
89
- output = bash_output(f"oras repo tags {reference_base}")
90
- if output.strip():
91
- print(f"Available tags for {reference_base}:")
92
- print(output, end="")
93
- else:
94
- print(f"No tags found for {reference_base}")
95
- else:
96
- print(f"No tags found for {reference_base}")
87
+ install_oras()
88
+ registry = _builds_registry()
89
+ username, token = _registry_credentials()
90
+ tags = list_build_tags(registry, project_name, target_name, registry_username=username, registry_token=token)
91
+ if tags:
92
+ print(f"Available tags for {build_repository(registry, project_name, target_name)}:")
93
+ for available_tag in tags:
94
+ print(available_tag)
97
95
  return
98
96
 
99
97
  if build_locally:
100
- if branch or run:
101
- print("Warning: --branch / --run are ignored when --build is set")
98
+ if pr_number or run:
99
+ print("Warning: --pr-number / --run are ignored when --build is set")
102
100
  if dry_run:
103
101
  print(f"Would build {project_name} [{target_name}] locally")
104
102
  if target_name in ADB_TARGETS:
@@ -111,17 +109,12 @@ def main(
111
109
  apks = [path for path in produced if path.suffix == ".apk"]
112
110
  executables = [path for path in produced if path.suffix in {".exe", ".x86_64"}]
113
111
  else:
114
- resolved_branch = branch
115
- if not resolved_branch:
116
- resolved_branch = bash_output("git rev-parse --abbrev-ref HEAD").strip()
117
- if resolved_branch == "HEAD":
118
- raise typer.BadParameter("HEAD is detached; pass --branch explicitly")
119
-
120
- tag = f"run-{run}" if run else resolved_branch.replace("/", "-")
112
+ parsed_pr_number = int(pr_number) if pr_number else None
113
+ tag = f"run-{run}" if run else cache_key(project_name, target_name, parsed_pr_number)
121
114
 
122
- reference_base = _resolve_reference_base(package_name)
123
- reference = f"{reference_base}:{tag}"
124
- cache_path = CACHE_ROOT / tag / package_name
115
+ registry = _builds_registry()
116
+ reference = build_reference(registry, project_name, target_name, tag)
117
+ cache_path = CACHE_ROOT / tag / f"{project_name}-{target_name}".lower()
125
118
 
126
119
  if dry_run:
127
120
  print(f"Would pull: {reference}")
@@ -133,14 +126,22 @@ def main(
133
126
  print("Would extract build.tar and launch the executable")
134
127
  return
135
128
 
136
- _require_oras()
137
- _login_ghcr()
129
+ install_oras()
130
+ username, token = _registry_credentials()
138
131
 
139
132
  if cache_path.is_dir() and any(cache_path.iterdir()):
140
133
  print(f"Using cached build: {cache_path}")
141
134
  else:
142
135
  cache_path.mkdir(parents=True, exist_ok=True)
143
- bash(f"oras pull {reference} -o {cache_path}")
136
+ pull_build(
137
+ registry,
138
+ project_name,
139
+ target_name,
140
+ tag,
141
+ cache_path,
142
+ registry_username=username,
143
+ registry_token=token,
144
+ )
144
145
  if target_name == "Linux":
145
146
  tar_path = cache_path / "build.tar"
146
147
  if tar_path.exists():
@@ -183,20 +184,15 @@ def main(
183
184
  bash_handoff(str(executable))
184
185
 
185
186
 
186
- def _require_oras() -> None:
187
- if not shutil.which("oras"):
188
- print("Error: oras is not on PATH. Install it:")
189
- print(" macOS: brew install oras")
190
- print(" Linux: https://oras.land/docs/install")
191
- print(" Windows: scoop install oras")
192
- raise SystemExit(1)
193
-
194
-
195
- def _resolve_reference_base(package_name: str) -> str:
187
+ def _builds_registry() -> str:
196
188
  owner_repo = bash_output("gh repo view --json nameWithOwner --jq .nameWithOwner").strip()
197
- return f"ghcr.io/{owner_repo}/builds/{package_name}".lower()
189
+ return builds_registry(f"ghcr.io/{owner_repo}")
198
190
 
199
191
 
200
- def _login_ghcr() -> None:
201
- token = bash_output("gh auth token").strip()
202
- bash("oras login ghcr.io --username oauth2 --password-stdin", stdin_text=token)
192
+ def _registry_credentials() -> tuple[str | None, str | None]:
193
+ try:
194
+ token = bash_output("gh auth token").strip()
195
+ except CalledProcessError:
196
+ print("gh CLI not authenticated; falling back to ambient registry credentials")
197
+ return None, None
198
+ return "oauth2", token
@@ -23,10 +23,13 @@ def activate_main(
23
23
  oras_push: Annotated[
24
24
  bool, typer.Option(help="Restore from the ORAS cache on miss and push the activated ULF back")
25
25
  ] = False,
26
+ registry: Annotated[str, typer.Option(help="OCI registry namespace for the license cache")] = "",
26
27
  ) -> None:
27
28
  configure_git(settings.github_workspace)
28
29
  install_oras()
29
30
  if oras_push:
30
- restore_or_activate_license()
31
+ if not registry:
32
+ raise SystemExit("--oras-push requires --registry")
33
+ restore_or_activate_license(registry)
31
34
  else:
32
35
  activate_license()
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
3
  import random
5
4
  import time
6
5
  from datetime import UTC, datetime
@@ -14,7 +13,6 @@ from ci_devkit.cache import restore, save
14
13
 
15
14
 
16
15
  class Settings(BaseSettings):
17
- cache_registry: str
18
16
  unity_email: str
19
17
  unity_password: str
20
18
  unity_serial: str
@@ -32,17 +30,16 @@ def tag_main() -> None:
32
30
  print(license_cache_tag())
33
31
 
34
32
 
35
- def restore_or_activate_license() -> None:
36
- settings = Settings.model_validate({})
33
+ def restore_or_activate_license(registry: str, license_tag: str | None = None) -> None:
37
34
  license_directory = Path.home() / ".local" / "share" / "unity3d" / "Unity"
38
35
  license_directory.mkdir(parents=True, exist_ok=True)
39
- tag = license_cache_tag()
36
+ tag = license_tag or license_cache_tag()
40
37
 
41
38
  for attempt in range(1, ACTIVATION_ATTEMPTS + 1):
42
- if restore(settings.cache_registry, LICENSE_CACHE_NAME, tag, license_directory):
39
+ if restore(registry, LICENSE_CACHE_NAME, tag, license_directory):
43
40
  return
44
41
  if license_activated():
45
- save(settings.cache_registry, LICENSE_CACHE_NAME, tag, license_directory, ["Unity_lic.ulf"])
42
+ save(registry, LICENSE_CACHE_NAME, tag, license_directory, ["Unity_lic.ulf"])
46
43
  return
47
44
  if attempt == ACTIVATION_ATTEMPTS:
48
45
  break
@@ -75,10 +72,7 @@ def license_activated() -> bool:
75
72
  return True
76
73
 
77
74
 
78
- # LICENSE_CACHE_TAG env pins the tag for a whole CI run; without it, each call
79
- # re-reads "now UTC" and a run straddling midnight save/restore-misses itself.
75
+ # A whole CI run pins one tag (the matrix verb's license output, passed as
76
+ # --license) so a run straddling midnight UTC does not save/restore-miss itself.
80
77
  def license_cache_tag() -> str:
81
- override = os.environ.get("LICENSE_CACHE_TAG")
82
- if override:
83
- return override
84
78
  return f"v-{datetime.now(UTC).strftime('%Y-%m-%d')}"
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from pathlib import Path
5
4
  from typing import Annotated
6
5
 
7
6
  import typer
@@ -30,9 +29,7 @@ def build_matrix(
30
29
  for platform in entry.builds:
31
30
  image_module = PLATFORM_CONFIGS[platform]["unityci_image_module"]
32
31
  matrix.append({
33
- "project": str(entry.path.relative_to(Path.cwd())),
34
32
  "project-name": name,
35
- "cache-key": name.lower(),
36
33
  "platform": platform,
37
34
  "unityci-image-module": image_module,
38
35
  "editor-image": f"unityci/editor:{version}-{image_module}-{UNITYCI_IMAGE_REVISION}",
@@ -44,7 +41,7 @@ def build_matrix(
44
41
  "with a non-empty 'platforms' map"
45
42
  )
46
43
  print(f"matrix={json.dumps({'include': matrix})}")
47
- print(f"license-tag={license_cache_tag()}")
44
+ print(f"license={license_cache_tag()}")
48
45
 
49
46
 
50
47
  @compile_check_app.command()
@@ -64,7 +61,7 @@ def compile_check_matrix(
64
61
  })
65
62
 
66
63
  print(f"matrix={json.dumps({'include': matrix})}")
67
- print(f"license-tag={license_cache_tag()}")
64
+ print(f"license={license_cache_tag()}")
68
65
 
69
66
 
70
67
  def filtered_projects(project: str | None) -> dict[str, ProjectEntry]:
@@ -3,30 +3,21 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
 
6
- from pydantic_settings import BaseSettings
7
-
8
6
  from ci_devkit.cache import restore, save
9
7
 
10
-
11
- class Settings(BaseSettings):
12
- cache_registry: str
13
-
14
-
15
8
  UPM_CACHE_NAME = "unity-upm"
16
9
  UPM_CACHE_DIRECTORY = Path.home() / ".cache" / "Unity" / "upm"
17
10
  UPM_MANIFESTS = ("manifest.json", "packages-lock.json")
18
11
 
19
12
 
20
- def restore_upm_cache(project_path: Path) -> None:
21
- settings = Settings.model_validate({})
13
+ def restore_upm_cache(project_path: Path, registry: str) -> None:
22
14
  UPM_CACHE_DIRECTORY.mkdir(parents=True, exist_ok=True)
23
- restore(settings.cache_registry, UPM_CACHE_NAME, upm_cache_tag(project_path), UPM_CACHE_DIRECTORY)
15
+ restore(registry, UPM_CACHE_NAME, upm_cache_tag(project_path), UPM_CACHE_DIRECTORY)
24
16
 
25
17
 
26
- def save_upm_cache(project_path: Path) -> None:
27
- settings = Settings.model_validate({})
18
+ def save_upm_cache(project_path: Path, registry: str) -> None:
28
19
  UPM_CACHE_DIRECTORY.mkdir(parents=True, exist_ok=True)
29
- save(settings.cache_registry, UPM_CACHE_NAME, upm_cache_tag(project_path), UPM_CACHE_DIRECTORY, ["."])
20
+ save(registry, UPM_CACHE_NAME, upm_cache_tag(project_path), UPM_CACHE_DIRECTORY, ["."])
30
21
 
31
22
 
32
23
  # A never-resolved project has no packages-lock.json yet; hashing it as empty keeps the
@@ -0,0 +1,18 @@
1
+ from unity_devkit.identity import builds_registry, cache_key, cache_registry
2
+
3
+
4
+ def test_cache_key_scopes_to_pr_when_present() -> None:
5
+ assert cache_key("Tool", "AndroidMobile", 5) == "tool-AndroidMobile-pr-5"
6
+
7
+
8
+ def test_cache_key_falls_back_to_dev_scope() -> None:
9
+ assert cache_key("Tool", "Linux", None) == "tool-Linux-dev"
10
+
11
+
12
+ def test_cache_key_lowercases_project_name_only() -> None:
13
+ assert cache_key("CaptureTool", "Linux", None) == "capturetool-Linux-dev"
14
+
15
+
16
+ def test_registry_namespaces_derive_from_the_repo_root() -> None:
17
+ assert cache_registry("ghcr.io/org/repo") == "ghcr.io/org/repo/cache"
18
+ assert builds_registry("ghcr.io/org/repo") == "ghcr.io/org/repo/builds"
@@ -21,7 +21,6 @@ def test_matrix_emits_entries_from_catalog(
21
21
  ) -> None:
22
22
  write_repository(tmp_path, builds=["Linux"])
23
23
  monkeypatch.chdir(tmp_path)
24
- monkeypatch.delenv("LICENSE_CACHE_TAG", raising=False)
25
24
 
26
25
  matrix.build_matrix()
27
26
 
@@ -29,15 +28,13 @@ def test_matrix_emits_entries_from_catalog(
29
28
  include = json.loads(lines[0][len("matrix=") :])["include"]
30
29
  assert include == [
31
30
  {
32
- "project": str(Path("apps") / "Tool"),
33
31
  "project-name": "tool",
34
- "cache-key": "tool",
35
32
  "platform": "Linux",
36
33
  "unityci-image-module": "linux-il2cpp",
37
34
  "editor-image": "unityci/editor:6000.0.66f1-linux-il2cpp-3",
38
35
  }
39
36
  ]
40
- assert lines[1].startswith("license-tag=v-")
37
+ assert lines[1].startswith("license=v-")
41
38
 
42
39
 
43
40
  def test_matrix_fails_without_builds(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -73,32 +70,18 @@ def test_matrix_project_filter_fails_on_unknown_project(tmp_path: Path, monkeypa
73
70
  matrix.build_matrix(project="missing")
74
71
 
75
72
 
76
- def test_matrix_honors_license_cache_tag_override(
77
- tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
78
- ) -> None:
79
- write_repository(tmp_path, builds=["Linux"])
80
- monkeypatch.chdir(tmp_path)
81
- monkeypatch.setenv("LICENSE_CACHE_TAG", "v-pinned")
82
-
83
- matrix.build_matrix()
84
-
85
- lines = capsys.readouterr().out.splitlines()
86
- assert lines[1] == "license-tag=v-pinned"
87
-
88
-
89
73
  def test_check_matrix_includes_path_only_projects(
90
74
  tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
91
75
  ) -> None:
92
76
  write_repository(tmp_path)
93
77
  monkeypatch.chdir(tmp_path)
94
- monkeypatch.delenv("LICENSE_CACHE_TAG", raising=False)
95
78
 
96
79
  matrix.compile_check_matrix()
97
80
 
98
81
  lines = capsys.readouterr().out.splitlines()
99
82
  include = json.loads(lines[0][len("matrix=") :])["include"]
100
83
  assert include == [{"project-name": "tool", "editor-image": "unityci/editor:6000.0.66f1-linux-il2cpp-3"}]
101
- assert lines[1].startswith("license-tag=v-")
84
+ assert lines[1].startswith("license=v-")
102
85
 
103
86
 
104
87
  def test_check_matrix_project_filter_scopes_to_one_project(
File without changes