unity-devkit 0.1.17__py3-none-any.whl → 0.1.18__py3-none-any.whl

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.
@@ -9,7 +9,7 @@ app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
9
9
 
10
10
  @app.command()
11
11
  def build_unity(
12
- project: Annotated[str, typer.Option(help="Unity project name (catalog key in unity-devkit.json)")],
12
+ project: Annotated[str, typer.Option(help="Unity project name (the 'name' field of its unity-devkit.json)")],
13
13
  build: Annotated[str, typer.Option(help="Build target from the project's builds list (e.g. AndroidMobile)")],
14
14
  version: Annotated[
15
15
  str,
@@ -63,7 +63,7 @@ def ci_build_unity(
63
63
  unity_project_path = resolve_unity_project(project).path
64
64
  if unity_project_path.resolve() != project_path.resolve():
65
65
  raise SystemExit(
66
- f"--project-path {project_path} does not match catalog entry '{project}' at {unity_project_path}"
66
+ f"--project-path {project_path} does not match discovered project '{project}' at {unity_project_path}"
67
67
  )
68
68
 
69
69
  with ci_step(f"Build {project} [{platform}]"):
@@ -11,7 +11,7 @@ COMPILE_ERROR_SIGNATURES = ("error CS",)
11
11
 
12
12
  @app.command()
13
13
  def compile_check_unity(
14
- project: Annotated[str, typer.Option(help="Unity project name (catalog key in unity-devkit.json)")],
14
+ project: Annotated[str, typer.Option(help="Unity project name (the 'name' field of its unity-devkit.json)")],
15
15
  execute_method: Annotated[
16
16
  str | None,
17
17
  typer.Option(help="Static method to run after load (Class.Method) — one editor session per invocation"),
@@ -7,7 +7,7 @@ import typer
7
7
  from pydantic import BaseModel, ConfigDict
8
8
 
9
9
  from .compile_check_unity import COMPILE_ERROR_SIGNATURES
10
- from .projects import load_catalog
10
+ from .projects import discover_projects
11
11
  from .player_build import prepare_unity_project, run_unity_batchmode
12
12
 
13
13
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
@@ -58,7 +58,7 @@ class EnvironmentDump(BaseModel):
58
58
 
59
59
  @app.command()
60
60
  def unity_dispatch(
61
- project: Annotated[str, typer.Option(help="Unity project name (catalog key in unity-devkit.json)")],
61
+ project: Annotated[str, typer.Option(help="Unity project name (the 'name' field of its unity-devkit.json)")],
62
62
  output: Annotated[Path, typer.Option(help="Workflow file to write (or check with --check)")],
63
63
  build_workflow: Annotated[
64
64
  str,
@@ -69,13 +69,13 @@ def unity_dispatch(
69
69
  ],
70
70
  environment_config_class: Annotated[
71
71
  str | None,
72
- typer.Option(help="Env class file path override (defaults to build-config.json's environment_config)"),
72
+ typer.Option(help="Env class file path override (defaults to unity-devkit.json's environment_config)"),
73
73
  ] = None,
74
74
  check: Annotated[
75
75
  bool, typer.Option("--check", help="Compare against the existing file instead of writing; fail on drift")
76
76
  ] = False,
77
77
  ) -> None:
78
- projects = load_catalog()
78
+ projects = discover_projects()
79
79
  if project not in projects:
80
80
  raise SystemExit(f"Unknown project '{project}'. Valid: {', '.join(projects)}")
81
81
 
unity_devkit/install.py CHANGED
@@ -8,7 +8,7 @@ from typing import Annotated, Any
8
8
  import typer
9
9
  from bashrun.bash import bash, bash_handoff, bash_output
10
10
 
11
- from .projects import load_catalog
11
+ from .projects import discover_projects
12
12
  from .player_build import build_player
13
13
 
14
14
  INSTALLABLE_TARGETS = {"AndroidMobile", "MagicLeap2", "Linux"}
@@ -43,7 +43,7 @@ def main(
43
43
  ),
44
44
  ] = False,
45
45
  ) -> None:
46
- projects = load_catalog()
46
+ projects = discover_projects()
47
47
 
48
48
  project_name = next((name for name in projects if name.lower() == project.lower()), None)
49
49
  if project_name is None:
@@ -2,7 +2,7 @@ from typing import Annotated
2
2
 
3
3
  import typer
4
4
 
5
- from .projects import load_catalog
5
+ from .projects import discover_projects
6
6
  from .player_build import prepare_unity_project, run_unity_batchmode
7
7
 
8
8
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
@@ -10,7 +10,7 @@ app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
10
10
 
11
11
  @app.command()
12
12
  def lock_unity(project: Annotated[str | None, typer.Option(help="Limit to a specific project.")] = None) -> None:
13
- config = load_catalog()
13
+ config = discover_projects()
14
14
 
15
15
  if project is not None and project not in config:
16
16
  raise typer.BadParameter(f"Unknown project '{project}'. Valid: {', '.join(config)}")
unity_devkit/matrix.py CHANGED
@@ -1,12 +1,12 @@
1
1
  import json
2
2
  from pathlib import Path
3
3
 
4
- from .projects import load_catalog
4
+ from .projects import discover_projects
5
5
  from .player_build import LICENSE_IMAGE_MODULE, PLATFORM_CONFIGS, UNITYCI_IMAGE_REVISION, read_editor_version
6
6
 
7
7
 
8
8
  def build_matrix() -> None:
9
- projects = load_catalog()
9
+ projects = discover_projects()
10
10
  matrix: list[dict[str, str]] = []
11
11
  editor_versions: set[str] = set()
12
12
 
@@ -28,7 +28,7 @@ def build_matrix() -> None:
28
28
 
29
29
  if not editor_versions:
30
30
  raise SystemExit(
31
- "No projects with builds declared — build-unity-matrix needs at least one unity-devkit.json entry with a 'builds' list"
31
+ "No projects with builds declared — build-unity-matrix needs at least one unity-devkit.json with a non-empty 'platforms' map"
32
32
  )
33
33
  license_version = max(editor_versions)
34
34
  print(f"matrix={json.dumps({'include': matrix})}")
@@ -36,7 +36,7 @@ def build_matrix() -> None:
36
36
 
37
37
 
38
38
  def compile_check_matrix() -> None:
39
- projects = load_catalog()
39
+ projects = discover_projects()
40
40
  matrix: list[dict[str, str]] = []
41
41
  editor_versions: set[str] = set()
42
42
 
@@ -9,7 +9,7 @@ from typing import TypedDict
9
9
 
10
10
  from bashrun.bash import CalledProcessError, bash, bash_pipe
11
11
 
12
- from .projects import CatalogEntry, load_catalog
12
+ from .projects import ProjectEntry, discover_projects
13
13
 
14
14
 
15
15
  class PlatformConfig(TypedDict):
@@ -135,8 +135,8 @@ def editor_version(project_path: Path) -> str | None:
135
135
  return None
136
136
 
137
137
 
138
- def resolve_unity_project(project: str) -> CatalogEntry:
139
- projects = load_catalog()
138
+ def resolve_unity_project(project: str) -> ProjectEntry:
139
+ projects = discover_projects()
140
140
  if project not in projects:
141
141
  raise SystemExit(f"Unknown project '{project}'. Valid: {', '.join(projects)}")
142
142
  return projects[project]
@@ -182,7 +182,7 @@ def build_player(
182
182
  valid_builds = project_config.builds or []
183
183
  if not valid_builds:
184
184
  raise SystemExit(
185
- f"Project '{project}' declares no builds — add a 'builds' list to its entry in unity-devkit.json"
185
+ f"Project '{project}' declares no builds — add platform keys to its unity-devkit.json 'platforms' map"
186
186
  )
187
187
  if build not in valid_builds:
188
188
  raise SystemExit(f"Unknown build '{build}' for project '{project}'. Valid: {', '.join(valid_builds)}")
unity_devkit/projects.py CHANGED
@@ -1,46 +1,64 @@
1
1
  import os
2
2
  from collections.abc import Iterator
3
3
  from pathlib import Path
4
+ from typing import Any
4
5
 
5
- from pydantic import BaseModel, ConfigDict, TypeAdapter, field_validator
6
+ from pydantic import BaseModel, ConfigDict, field_validator
6
7
 
7
- CATALOG_FILENAME = "unity-devkit.json"
8
+ BUILD_CONFIG_FILENAME = "unity-devkit.json"
8
9
  PROJECT_MARKER = Path("ProjectSettings") / "ProjectVersion.txt"
9
10
  PRUNE_DIRECTORIES = {".git", "Library", "Temp", "obj", "Build", "node_modules", "__pycache__"}
10
11
 
11
12
 
12
- class CatalogEntry(BaseModel):
13
- model_config = ConfigDict(extra="forbid")
13
+ class BuildConfigFile(BaseModel):
14
+ model_config = ConfigDict(extra="ignore")
14
15
 
15
- path: Path
16
- builds: list[str] | None = None
16
+ name: str
17
+ platforms: dict[str, Any] | None = None
17
18
 
18
- @field_validator("path")
19
+ @field_validator("name")
19
20
  @classmethod
20
- def anchor_relative_path(cls, value: Path) -> Path:
21
- return value if value.is_absolute() else Path.cwd() / value
22
-
21
+ def nonempty_name(cls, value: str) -> str:
22
+ if not value.strip():
23
+ raise ValueError("unity-devkit.json 'name' must be a non-empty string")
24
+ return value
23
25
 
24
- CATALOG_ADAPTER = TypeAdapter(dict[str, CatalogEntry])
25
26
 
27
+ class ProjectEntry(BaseModel):
28
+ path: Path
29
+ builds: list[str] | None = None
26
30
 
27
- def load_catalog() -> dict[str, CatalogEntry]:
28
- catalog_path = Path.cwd() / CATALOG_FILENAME
29
- if not catalog_path.is_file():
30
- raise SystemExit(
31
- f"No {CATALOG_FILENAME} catalog at {catalog_path} — declare Unity projects as catalog entries at the repo root"
32
- )
33
31
 
34
- catalog = CATALOG_ADAPTER.validate_json(catalog_path.read_text(encoding="utf-8"))
35
- if not catalog:
36
- raise SystemExit(f"{catalog_path} declares no projects — at least one catalog entry is required")
32
+ def discover_projects() -> dict[str, ProjectEntry]:
33
+ root = Path.cwd()
34
+ discovered: dict[str, ProjectEntry] = {}
35
+ first_seen: dict[str, Path] = {}
37
36
 
38
- for name, entry in catalog.items():
39
- if not (entry.path / PROJECT_MARKER).is_file():
37
+ for directory in visible_directories(root):
38
+ config_path = directory / BUILD_CONFIG_FILENAME
39
+ if not config_path.is_file():
40
+ continue
41
+ if not (directory / PROJECT_MARKER).is_file():
42
+ raise SystemExit(
43
+ f"{config_path} sits in {directory}, which is not a Unity project: missing {PROJECT_MARKER}"
44
+ )
45
+ parsed = BuildConfigFile.model_validate_json(config_path.read_text(encoding="utf-8"))
46
+ if parsed.name in discovered:
40
47
  raise SystemExit(
41
- f"Catalog entry '{name}' points at {entry.path}, which is not a Unity project: missing {PROJECT_MARKER}"
48
+ f"Duplicate project name '{parsed.name}' — declared at {config_path} "
49
+ f"and previously at {first_seen[parsed.name]}"
42
50
  )
43
- return catalog
51
+ first_seen[parsed.name] = config_path
52
+ builds = list(parsed.platforms.keys()) if parsed.platforms else None
53
+ discovered[parsed.name] = ProjectEntry(path=directory, builds=builds)
54
+
55
+ if not discovered:
56
+ raise SystemExit(
57
+ f"No {BUILD_CONFIG_FILENAME} found under {root} — declare Unity projects with a "
58
+ f"{BUILD_CONFIG_FILENAME} per project root (carrying a 'name' field; 'platforms' "
59
+ f"keys are the build targets)"
60
+ )
61
+ return discovered
44
62
 
45
63
 
46
64
  def visible_directories(root: Path) -> Iterator[Path]:
@@ -5,7 +5,7 @@ from typing import Annotated
5
5
 
6
6
  import typer
7
7
 
8
- from .projects import load_catalog
8
+ from .projects import discover_projects
9
9
  from .player_build import run_unity_batchmode
10
10
 
11
11
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
@@ -13,13 +13,13 @@ app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
13
13
 
14
14
  @app.command()
15
15
  def main(
16
- project: Annotated[str, typer.Option(help="Unity project name (catalog key in unity-devkit.json)")],
16
+ project: Annotated[str, typer.Option(help="Unity project name (the 'name' field of its unity-devkit.json)")],
17
17
  test_platform: Annotated[str, typer.Option(help="Unity test platform (EditMode or PlayMode)")] = "EditMode",
18
18
  results: Annotated[Path, typer.Option(help="Output path for NUnit XML results")] = Path(
19
19
  "artifacts/unity-test-results.xml"
20
20
  ),
21
21
  ) -> None:
22
- projects = load_catalog()
22
+ projects = discover_projects()
23
23
  if project not in projects:
24
24
  raise SystemExit(f"Unknown project '{project}'. Valid: {', '.join(projects)}")
25
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unity-devkit
3
- Version: 0.1.17
3
+ Version: 0.1.18
4
4
  Summary: Unity build, license, and CI tooling
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: bashrun>=0.1.0
@@ -0,0 +1,18 @@
1
+ unity_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ unity_devkit/build_unity.py,sha256=gYkfbMqZJ43DMvpRhp9WZ1zdkJhpRJ2uBXI2bGdIoGo,1545
3
+ unity_devkit/ci_build_unity.py,sha256=93HyQhZl4IcG0dqkwjOq9SqKWBYP6WzzDlrfqfcwu6o,3931
4
+ unity_devkit/compile_check_unity.py,sha256=WHg0YztiYes37dvnZrTNcUFO5T0MLSx_cCvA85wvr9k,1236
5
+ unity_devkit/dispatch_workflow_generator.py,sha256=l85pGXl7XgHIzDJQmhFwuYONm5sz_90ziNE_8aQqhpM,9305
6
+ unity_devkit/install.py,sha256=pKyweb7wrKU7UavmMWPV9wVdLB1Fs0G0cDgRBDro34c,5955
7
+ unity_devkit/license.py,sha256=iiMdos8kMk4p5uYYroIWUzxvusXtcMRdPkIDXsYTC9c,1484
8
+ unity_devkit/license_restore.py,sha256=Tbt2Kt1axszqj4H6K5Fx9_nqRL-dpL5XTVnxnmP4PZw,1077
9
+ unity_devkit/lock_unity.py,sha256=-1d-mHva5XUOlki8LSO5qwABR52q7jzc7FTJnnTH9f8,1012
10
+ unity_devkit/matrix.py,sha256=3fiHxkVyNFfCKzQRMXSo5c2P1tDHn0XteZXV-mQ8cxk,2057
11
+ unity_devkit/player_build.py,sha256=AT6sZ1IoMzzRh0ECKcRL2YC1WrLqP7Kq7gGC0HRq8Iw,9914
12
+ unity_devkit/projects.py,sha256=PO5plIj-mn8KkKOUhGRRIL_sDdd6ohl12neS2poILhs,2665
13
+ unity_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ unity_devkit/test_unity.py,sha256=SpeWnV2hpdZBaGsstL8RTO50C2HEAcvantzBQYeaTpE,1075
15
+ unity_devkit-0.1.18.dist-info/METADATA,sha256=547e4GP7RDUk_IEj4kDhq7Oi8asVScM-CzhFyEagYJI,290
16
+ unity_devkit-0.1.18.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
17
+ unity_devkit-0.1.18.dist-info/entry_points.txt,sha256=wdkAD7hjUPC4tDwe1ulAo5a0vWPXPO5sSGX1iN6VvJc,588
18
+ unity_devkit-0.1.18.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- unity_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- unity_devkit/build_unity.py,sha256=iROcoytNTlDnS2NeGxzARvOyNW08tee2PEBuKV54vFA,1536
3
- unity_devkit/ci_build_unity.py,sha256=XqYX--hUpvwYss7QCL1CNT8Jabiuz-DuEVrqqvK6PTE,3926
4
- unity_devkit/compile_check_unity.py,sha256=dLo2-c7y28SN88-oDsxQQPGLq3oV-MfFWABw3KB97IQ,1227
5
- unity_devkit/dispatch_workflow_generator.py,sha256=lPxCJjU16P-hLX_5z3wJKJ3lpDJ3lR8VnI7iR-1iy5Q,9286
6
- unity_devkit/install.py,sha256=tUzqhobqAL0fI47w4vAMgP3IB69836cuK0iKsoO7btA,5945
7
- unity_devkit/license.py,sha256=iiMdos8kMk4p5uYYroIWUzxvusXtcMRdPkIDXsYTC9c,1484
8
- unity_devkit/license_restore.py,sha256=Tbt2Kt1axszqj4H6K5Fx9_nqRL-dpL5XTVnxnmP4PZw,1077
9
- unity_devkit/lock_unity.py,sha256=VPXlwKTGbbBGmQVjZRQdxX5JmYUY4n9Qu1Cylg8zQRE,1002
10
- unity_devkit/matrix.py,sha256=YlN2sJZsBRzzj1jBFSBkv0jYqhMQyihxIu3cqrH5GBk,2036
11
- unity_devkit/player_build.py,sha256=a3AgpqHk64WfN3TwUMzhF_NuhOOghcbfHhetdhfha8U,9899
12
- unity_devkit/projects.py,sha256=CBiIhnZUeGOSVznmlTCJ_Ey4P3FraBKSfZsvnVU0pXs,1971
13
- unity_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- unity_devkit/test_unity.py,sha256=ayjoleM4Rnj0vbfVxiL52CofBukf12CBwRSrUvirqEA,1056
15
- unity_devkit-0.1.17.dist-info/METADATA,sha256=OnYKknCqRZY-PRT8v5T48AacX4-lqPcfH_Yw6K1BVrE,290
16
- unity_devkit-0.1.17.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
17
- unity_devkit-0.1.17.dist-info/entry_points.txt,sha256=wdkAD7hjUPC4tDwe1ulAo5a0vWPXPO5sSGX1iN6VvJc,588
18
- unity_devkit-0.1.17.dist-info/RECORD,,