unity-devkit 0.1.16__tar.gz → 0.1.17__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.16 → unity_devkit-0.1.17}/PKG-INFO +1 -1
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/pyproject.toml +1 -1
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/dispatch_workflow_generator.py +31 -39
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_dispatch_workflow_generator.py +61 -11
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/.gitignore +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/__init__.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/build_unity.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/ci_build_unity.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/compile_check_unity.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/install.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/license.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/license_restore.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/lock_unity.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/matrix.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/player_build.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/projects.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/py.typed +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/src/unity_devkit/test_unity.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/fixtures/environment-dump.json +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_build_player.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_matrix.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_prepare.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_projects.py +0 -0
- {unity_devkit-0.1.16 → unity_devkit-0.1.17}/tests/test_run_unity_batchmode.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from collections import Counter
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Annotated
|
|
4
5
|
|
|
@@ -13,6 +14,7 @@ app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
|
|
13
14
|
|
|
14
15
|
DUMP_EXECUTE_METHOD = "Outernet.PlayerBuild.DumpEnvironment"
|
|
15
16
|
DISPATCH_INPUT_CAP = 25
|
|
17
|
+
RESERVED_INPUT_IDS = frozenset({"environment-preset", "development"})
|
|
16
18
|
NUMERIC_FIELD_TYPES = {
|
|
17
19
|
"Byte",
|
|
18
20
|
"SByte",
|
|
@@ -124,19 +126,26 @@ def extract_environment_dump(log_path: Path) -> EnvironmentDump:
|
|
|
124
126
|
|
|
125
127
|
def render_dispatch_workflow(dump: EnvironmentDump, *, project: str, output: str, build_workflow: str) -> str:
|
|
126
128
|
enums_by_name = {environment_enum.name: environment_enum for environment_enum in dump.enums}
|
|
127
|
-
|
|
129
|
+
emitted_fields = [field for field in dump.fields if field.path != dump.mode_field]
|
|
130
|
+
leaf_counts = Counter(field.path.rsplit(".", 1)[-1] for field in emitted_fields)
|
|
131
|
+
input_ids: dict[str, str] = {}
|
|
132
|
+
for field in emitted_fields:
|
|
133
|
+
leaf = field.path.rsplit(".", 1)[-1]
|
|
134
|
+
input_ids[field.path] = (
|
|
135
|
+
leaf if leaf_counts[leaf] == 1 and leaf not in RESERVED_INPUT_IDS else field.path.replace(".", "-")
|
|
136
|
+
)
|
|
137
|
+
default_preset = next(iter(dump.presets), "")
|
|
138
|
+
input_blocks: list[tuple[str, str | None, list[str], str | None]] = [
|
|
128
139
|
(
|
|
129
140
|
"environment-preset",
|
|
130
|
-
"
|
|
141
|
+
"Preset to apply — blank leaves the workspace untouched; leave fields empty to keep the preset's values",
|
|
131
142
|
["type: choice", "options:", *indent_lines([*(f'- "{preset}"' for preset in ("", *dump.presets))], 2)],
|
|
132
|
-
'default: ""',
|
|
143
|
+
f'default: "{default_preset}"',
|
|
133
144
|
),
|
|
134
|
-
("development", "Development build
|
|
145
|
+
("development", "Development build (debug symbols)", ["type: boolean"], "default: false"),
|
|
135
146
|
]
|
|
136
|
-
for field in
|
|
137
|
-
|
|
138
|
-
continue
|
|
139
|
-
input_blocks.append(field_input_block(field, enums_by_name))
|
|
147
|
+
for field in emitted_fields:
|
|
148
|
+
input_blocks.append(field_input_block(field, input_ids[field.path], enums_by_name))
|
|
140
149
|
|
|
141
150
|
input_count = len(input_blocks)
|
|
142
151
|
if input_count > DISPATCH_INPUT_CAP:
|
|
@@ -145,7 +154,7 @@ def render_dispatch_workflow(dump: EnvironmentDump, *, project: str, output: str
|
|
|
145
154
|
"Shrink the env class or move configuration out of it."
|
|
146
155
|
)
|
|
147
156
|
|
|
148
|
-
transport_lines = [field_transport_line(field) for field in
|
|
157
|
+
transport_lines = [field_transport_line(field, input_ids[field.path]) for field in emitted_fields]
|
|
149
158
|
header = (
|
|
150
159
|
f"# Generated by '{regen_command(project, output, build_workflow)}' from the environment class "
|
|
151
160
|
f"{dump.class_name} —"
|
|
@@ -182,32 +191,23 @@ def regen_command(project: str, output: str, build_workflow: str) -> str:
|
|
|
182
191
|
|
|
183
192
|
|
|
184
193
|
def field_input_block(
|
|
185
|
-
field: EnvironmentFieldDump, enums_by_name: dict[str, EnvironmentEnum]
|
|
186
|
-
) -> tuple[str, str, list[str], str | None]:
|
|
187
|
-
input_id = field.path.replace(".", "-")
|
|
194
|
+
field: EnvironmentFieldDump, input_id: str, enums_by_name: dict[str, EnvironmentEnum]
|
|
195
|
+
) -> tuple[str, str | None, list[str], str | None]:
|
|
188
196
|
if field.field_type == "Boolean":
|
|
189
|
-
return (
|
|
190
|
-
input_id,
|
|
191
|
-
f"environment field {field.path} (bool) — checked overrides the preset value",
|
|
192
|
-
["type: boolean"],
|
|
193
|
-
"default: false",
|
|
194
|
-
)
|
|
197
|
+
return (input_id, None, ["type: boolean"], "default: false")
|
|
195
198
|
|
|
196
199
|
if field.field_type in enums_by_name:
|
|
197
200
|
environment_enum = enums_by_name[field.field_type]
|
|
198
201
|
if environment_enum.flags:
|
|
199
202
|
return (
|
|
200
203
|
input_id,
|
|
201
|
-
(
|
|
202
|
-
f"environment field {field.path} (flags: {', '.join(environment_enum.values)}) — "
|
|
203
|
-
"comma-separated names, empty keeps the preset value"
|
|
204
|
-
),
|
|
204
|
+
f"comma-separated: {', '.join(environment_enum.values)}",
|
|
205
205
|
["type: string"],
|
|
206
206
|
'default: ""',
|
|
207
207
|
)
|
|
208
208
|
return (
|
|
209
209
|
input_id,
|
|
210
|
-
|
|
210
|
+
None,
|
|
211
211
|
[
|
|
212
212
|
"type: choice",
|
|
213
213
|
"options:",
|
|
@@ -217,20 +217,10 @@ def field_input_block(
|
|
|
217
217
|
)
|
|
218
218
|
|
|
219
219
|
if field.field_type == "String":
|
|
220
|
-
return (
|
|
221
|
-
input_id,
|
|
222
|
-
f"environment field {field.path} (string) — empty keeps the preset value",
|
|
223
|
-
["type: string"],
|
|
224
|
-
'default: ""',
|
|
225
|
-
)
|
|
220
|
+
return (input_id, None, ["type: string"], 'default: ""')
|
|
226
221
|
|
|
227
222
|
if field.field_type in NUMERIC_FIELD_TYPES:
|
|
228
|
-
return (
|
|
229
|
-
input_id,
|
|
230
|
-
f"environment field {field.path} ({field.field_type}) — empty keeps the preset value",
|
|
231
|
-
["type: number"],
|
|
232
|
-
None,
|
|
233
|
-
)
|
|
223
|
+
return (input_id, None, ["type: number"], None)
|
|
234
224
|
|
|
235
225
|
raise SystemExit(
|
|
236
226
|
f"Environment field '{field.path}' has type '{field.field_type}' — no dispatch input type maps to it "
|
|
@@ -238,8 +228,7 @@ def field_input_block(
|
|
|
238
228
|
)
|
|
239
229
|
|
|
240
230
|
|
|
241
|
-
def field_transport_line(field: EnvironmentFieldDump) -> str:
|
|
242
|
-
input_id = field.path.replace(".", "-")
|
|
231
|
+
def field_transport_line(field: EnvironmentFieldDump, input_id: str) -> str:
|
|
243
232
|
guard = "" if field.field_type == "Boolean" else " != ''"
|
|
244
233
|
return (
|
|
245
234
|
"${{ inputs['"
|
|
@@ -254,9 +243,12 @@ def field_transport_line(field: EnvironmentFieldDump) -> str:
|
|
|
254
243
|
)
|
|
255
244
|
|
|
256
245
|
|
|
257
|
-
def build_input_block(input_block: tuple[str, str, list[str], str | None]) -> list[str]:
|
|
246
|
+
def build_input_block(input_block: tuple[str, str | None, list[str], str | None]) -> list[str]:
|
|
258
247
|
input_id, description, type_lines, default_line = input_block
|
|
259
|
-
lines = [f"{input_id}:"
|
|
248
|
+
lines = [f"{input_id}:"]
|
|
249
|
+
if description is not None:
|
|
250
|
+
lines.append(f' description: "{description}"')
|
|
251
|
+
lines.extend(f" {line}" for line in type_lines)
|
|
260
252
|
if default_line is not None:
|
|
261
253
|
lines.append(f" {default_line}")
|
|
262
254
|
return lines
|
|
@@ -38,22 +38,72 @@ def test_render_modes_are_deduped_into_the_preset_choice() -> None:
|
|
|
38
38
|
assert 'options:\n - ""\n - "Airgapped"' in workflow
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def
|
|
41
|
+
def test_render_defaults_the_preset_choice_to_the_first_map_key() -> None:
|
|
42
|
+
document = yaml.safe_load(render_fixture_workflow().replace("${{", "__EXPR_OPEN__").replace("}}", "__EXPR_CLOSE__"))
|
|
43
|
+
|
|
44
|
+
trigger = document.get("on", document[True])
|
|
45
|
+
preset = trigger["workflow_dispatch"]["inputs"]["environment-preset"]
|
|
46
|
+
assert preset["options"] == ["", "Airgapped"]
|
|
47
|
+
assert preset["default"] == "Airgapped"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_render_states_the_override_law_once_on_the_preset_input() -> None:
|
|
51
|
+
workflow = render_fixture_workflow()
|
|
52
|
+
|
|
53
|
+
assert "environment field" not in workflow
|
|
54
|
+
assert "empty keeps the preset value" not in workflow
|
|
55
|
+
assert "checked overrides the preset value" not in workflow
|
|
56
|
+
assert workflow.count("description:") == 3
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_render_maps_field_types_to_dispatch_inputs_without_descriptions() -> None:
|
|
60
|
+
workflow = render_fixture_workflow()
|
|
61
|
+
|
|
62
|
+
assert "\n username:\n type: string" in workflow
|
|
63
|
+
assert "\n verboseLogging:\n type: boolean" in workflow
|
|
64
|
+
assert "\n pollIntervalSeconds:\n type: number" in workflow
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_render_uses_leaf_names_as_input_ids_when_unique() -> None:
|
|
42
68
|
workflow = render_fixture_workflow()
|
|
43
69
|
|
|
44
|
-
assert
|
|
45
|
-
assert
|
|
46
|
-
assert "
|
|
47
|
-
assert "localConfig
|
|
48
|
-
|
|
70
|
+
assert "\n apiUrl:" in workflow
|
|
71
|
+
assert "\n portNumber:" in workflow
|
|
72
|
+
assert "localConfig-apiUrl" not in workflow
|
|
73
|
+
assert "${{ inputs['apiUrl'] != '' && format('localConfig.apiUrl={0}', inputs['apiUrl']) || '' }}" in workflow
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_render_falls_back_to_dashed_paths_on_leaf_collision() -> None:
|
|
77
|
+
dump = load_fixture_dump()
|
|
78
|
+
dump.fields.append(EnvironmentFieldDump(path="remoteConfig.apiUrl", field_type="String"))
|
|
79
|
+
|
|
80
|
+
workflow = render_dispatch_workflow(
|
|
81
|
+
dump, project="PlayerBuild", output=".github/workflows/build.yml", build_workflow=PINNED_BUILD_WORKFLOW
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assert "\n apiUrl:" not in workflow
|
|
85
|
+
assert "\n localConfig-apiUrl:" in workflow
|
|
86
|
+
assert "\n remoteConfig-apiUrl:" in workflow
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_render_falls_back_to_dashed_paths_on_control_id_collision() -> None:
|
|
90
|
+
dump = load_fixture_dump()
|
|
91
|
+
dump.fields.append(EnvironmentFieldDump(path="misc.development", field_type="Boolean"))
|
|
92
|
+
|
|
93
|
+
workflow = render_dispatch_workflow(
|
|
94
|
+
dump, project="PlayerBuild", output=".github/workflows/build.yml", build_workflow=PINNED_BUILD_WORKFLOW
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
assert "\n misc-development:" in workflow
|
|
98
|
+
assert workflow.split("jobs:")[0].count("\n development:") == 1
|
|
49
99
|
|
|
50
100
|
|
|
51
101
|
def test_render_flags_enum_is_text_not_choice() -> None:
|
|
52
102
|
workflow = render_fixture_workflow()
|
|
53
103
|
|
|
54
|
-
assert "comma-separated
|
|
55
|
-
|
|
56
|
-
assert
|
|
104
|
+
assert 'description: "comma-separated: None, Capture, Stream, Analyze"' in workflow
|
|
105
|
+
assert "\n capabilities:\n description:" in workflow
|
|
106
|
+
assert "\n capabilities:\n type: string" not in workflow
|
|
57
107
|
assert "type: choice" in workflow
|
|
58
108
|
|
|
59
109
|
|
|
@@ -106,8 +156,8 @@ def test_rendered_workflow_is_valid_yaml() -> None:
|
|
|
106
156
|
"verboseLogging",
|
|
107
157
|
"pollIntervalSeconds",
|
|
108
158
|
"capabilities",
|
|
109
|
-
"
|
|
110
|
-
"
|
|
159
|
+
"apiUrl",
|
|
160
|
+
"portNumber",
|
|
111
161
|
]
|
|
112
162
|
assert document["jobs"]["build"]["uses"] == PINNED_BUILD_WORKFLOW
|
|
113
163
|
assert document["jobs"]["build"]["secrets"] == "inherit"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|