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.
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/PKG-INFO +2 -2
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/pyproject.toml +2 -2
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/ci_build_unity.py +33 -26
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/compile_check_unity.py +12 -3
- unity_devkit-0.1.24/src/unity_devkit/identity.py +14 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/install.py +47 -51
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/license.py +4 -1
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/license_restore.py +6 -12
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/matrix.py +2 -5
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/upm_cache.py +4 -13
- unity_devkit-0.1.24/tests/test_identity.py +18 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_matrix.py +2 -19
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/.gitignore +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/__init__.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/build_unity.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/lock_unity.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/player_build.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/projects.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/py.typed +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/src/unity_devkit/test_unity.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/fixtures/environment-dump.json +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_build_player.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_prepare.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_projects.py +0 -0
- {unity_devkit-0.1.23 → unity_devkit-0.1.24}/tests/test_run_unity_batchmode.py +0 -0
- {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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
32
|
-
registry: Annotated[str, typer.Option(help="OCI registry
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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,
|
|
9
|
+
from bashrun.bash import bash, bash_handoff, bash_output
|
|
10
10
|
|
|
11
|
-
from .
|
|
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
|
-
|
|
29
|
-
str
|
|
30
|
-
typer.Option("--
|
|
31
|
-
] =
|
|
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; --
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
print(
|
|
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
|
|
101
|
-
print("Warning: --
|
|
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
|
-
|
|
115
|
-
if
|
|
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
|
-
|
|
123
|
-
reference =
|
|
124
|
-
cache_path = CACHE_ROOT / tag /
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
189
|
+
return builds_registry(f"ghcr.io/{owner_repo}")
|
|
198
190
|
|
|
199
191
|
|
|
200
|
-
def
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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(
|
|
39
|
+
if restore(registry, LICENSE_CACHE_NAME, tag, license_directory):
|
|
43
40
|
return
|
|
44
41
|
if license_activated():
|
|
45
|
-
save(
|
|
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
|
-
#
|
|
79
|
-
#
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|