proj-flow 0.18.0__py3-none-any.whl → 0.20.0__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.
- proj_flow/__init__.py +1 -1
- proj_flow/api/makefile.py +1 -1
- proj_flow/ext/tools/__init__.py +13 -0
- proj_flow/ext/tools/pragma_once.py +40 -0
- proj_flow/ext/tools/run_linter.py +228 -0
- proj_flow/ext/webidl/__init__.py +12 -0
- proj_flow/ext/webidl/base/__init__.py +2 -0
- proj_flow/ext/webidl/base/config.py +425 -0
- proj_flow/ext/webidl/cli/__init__.py +10 -0
- proj_flow/ext/webidl/cli/cmake.py +82 -0
- proj_flow/ext/webidl/cli/depfile.py +83 -0
- proj_flow/ext/webidl/cli/gen.py +157 -0
- proj_flow/ext/webidl/cli/init.py +26 -0
- proj_flow/ext/webidl/cli/root.py +12 -0
- proj_flow/ext/webidl/cli/updater.py +20 -0
- proj_flow/ext/webidl/data/init/flow_webidl.cmake +26 -0
- proj_flow/ext/webidl/data/templates/cmake.mustache +45 -0
- proj_flow/ext/webidl/data/templates/depfile.mustache +6 -0
- proj_flow/ext/webidl/data/templates/partials/cxx/attribute-decl.mustache +1 -0
- proj_flow/ext/webidl/data/templates/partials/cxx/in-out.mustache +2 -0
- proj_flow/ext/webidl/data/templates/partials/cxx/includes.mustache +6 -0
- proj_flow/ext/webidl/data/templates/partials/cxx/operation-decl.mustache +12 -0
- proj_flow/ext/webidl/data/templates/partials/cxx/type.mustache +6 -0
- proj_flow/ext/webidl/data/types/cxx.json +47 -0
- proj_flow/ext/webidl/model/__init__.py +2 -0
- proj_flow/ext/webidl/model/ast.py +586 -0
- proj_flow/ext/webidl/model/builders.py +230 -0
- proj_flow/ext/webidl/registry.py +23 -0
- {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/METADATA +2 -1
- {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/RECORD +33 -7
- {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/WHEEL +0 -0
- {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/entry_points.txt +0 -0
- {proj_flow-0.18.0.dist-info → proj_flow-0.20.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, cast
|
|
10
|
+
|
|
11
|
+
import chevron
|
|
12
|
+
|
|
13
|
+
from proj_flow.ext.webidl.model.builders import ExtAttrsContextBuilders, TypeReplacement
|
|
14
|
+
|
|
15
|
+
package_root = Path(__file__).parent.parent
|
|
16
|
+
templates_dir = package_root / "data" / "templates"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_package_template(name: str):
|
|
20
|
+
path = templates_dir / f"{name}.mustache"
|
|
21
|
+
return path.read_text(encoding="UTF-8")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class CMakeDirs:
|
|
26
|
+
base_dir: Path
|
|
27
|
+
project_source_dir: Path
|
|
28
|
+
project_binary_dir: Path
|
|
29
|
+
current_source_dir: Path
|
|
30
|
+
current_binary_dir: Path
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def from_config_path(config: Path, project_binary_dir: Path):
|
|
34
|
+
config = config.absolute()
|
|
35
|
+
project_binary_dir = project_binary_dir.absolute()
|
|
36
|
+
base_dir = config.parent
|
|
37
|
+
project_source_dir = Path().absolute()
|
|
38
|
+
current_source_dir = project_source_dir
|
|
39
|
+
|
|
40
|
+
for parent in config.parents:
|
|
41
|
+
cmake_lists = parent / "CMakeLists.txt"
|
|
42
|
+
if cmake_lists.exists():
|
|
43
|
+
current_source_dir = parent
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
current_binary_dir = project_binary_dir / current_source_dir.relative_to(
|
|
47
|
+
project_source_dir
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return CMakeDirs(
|
|
51
|
+
base_dir=base_dir,
|
|
52
|
+
project_source_dir=project_source_dir,
|
|
53
|
+
project_binary_dir=project_binary_dir,
|
|
54
|
+
current_source_dir=current_source_dir,
|
|
55
|
+
current_binary_dir=current_binary_dir,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def filename_from_config(self, config: Path, ext: str):
|
|
59
|
+
in_source = config.absolute().relative_to(self.current_source_dir)
|
|
60
|
+
in_build = self.current_binary_dir / in_source
|
|
61
|
+
return Path(in_build.as_posix() + ext)
|
|
62
|
+
|
|
63
|
+
def dependency_from_config(self, config: Path):
|
|
64
|
+
return self.filename_from_config(config, ".deps")
|
|
65
|
+
|
|
66
|
+
def cmake_from_config(self, config: Path):
|
|
67
|
+
return self.filename_from_config(config, ".cmake")
|
|
68
|
+
|
|
69
|
+
def my_defines(self):
|
|
70
|
+
return {
|
|
71
|
+
"PROJECT_SOURCE_DIR": self.project_source_dir.as_posix(),
|
|
72
|
+
"PROJECT_BINARY_DIR": self.project_binary_dir.as_posix(),
|
|
73
|
+
"CMAKE_CURRENT_SOURCE_DIR": self.current_source_dir.as_posix(),
|
|
74
|
+
"CMAKE_CURRENT_BINARY_DIR": self.current_binary_dir.as_posix(),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def prefix(self, filename: str):
|
|
78
|
+
if "${" in filename:
|
|
79
|
+
return Path(filename)
|
|
80
|
+
|
|
81
|
+
full = self.base_dir / filename
|
|
82
|
+
|
|
83
|
+
if full.is_relative_to(self.current_binary_dir):
|
|
84
|
+
return Path("${CMAKE_CURRENT_BINARY_DIR}") / full.relative_to(
|
|
85
|
+
self.current_binary_dir
|
|
86
|
+
)
|
|
87
|
+
if full.is_relative_to(self.project_binary_dir):
|
|
88
|
+
return Path("${PROJECT_BINARY_DIR}") / full.relative_to(
|
|
89
|
+
self.project_binary_dir
|
|
90
|
+
)
|
|
91
|
+
if full.is_relative_to(self.current_source_dir):
|
|
92
|
+
return Path("${CMAKE_CURRENT_SOURCE_DIR}") / full.relative_to(
|
|
93
|
+
self.current_source_dir
|
|
94
|
+
)
|
|
95
|
+
if full.is_relative_to(self.project_source_dir):
|
|
96
|
+
return Path("${PROJECT_SOURCE_DIR}") / full.relative_to(
|
|
97
|
+
self.project_source_dir
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return Path(filename)
|
|
101
|
+
|
|
102
|
+
def make_abs_path(self):
|
|
103
|
+
def impl(filename: str):
|
|
104
|
+
return self.prefix(filename)
|
|
105
|
+
|
|
106
|
+
return impl
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _relative(path: Path, basedir: Path | None):
|
|
110
|
+
if not basedir:
|
|
111
|
+
return path
|
|
112
|
+
return path.relative_to(basedir, walk_up=True)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Rebuild:
|
|
116
|
+
@staticmethod
|
|
117
|
+
def __rebuild_map(data: dict, global_ctx: dict[str, str]):
|
|
118
|
+
for key, value in data.items():
|
|
119
|
+
data[key] = Rebuild.__rebuild_any(value, global_ctx)
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def __rebuild_list(data: list, global_ctx: dict[str, str]):
|
|
124
|
+
for index, value in enumerate(data):
|
|
125
|
+
data[index] = Rebuild.__rebuild_any(value, global_ctx)
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def __rebuild_str(data: str, global_ctx: dict[str, str]):
|
|
130
|
+
return chevron.render(data, global_ctx)
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def __rebuild_any(data, global_ctx: dict[str, str]):
|
|
134
|
+
if isinstance(data, str):
|
|
135
|
+
return Rebuild.__rebuild_str(data, global_ctx)
|
|
136
|
+
if isinstance(data, list):
|
|
137
|
+
return Rebuild.__rebuild_list(data, global_ctx)
|
|
138
|
+
if isinstance(data, dict):
|
|
139
|
+
return Rebuild.__rebuild_map(data, global_ctx)
|
|
140
|
+
|
|
141
|
+
return data
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def rebuild(initial_context: dict, global_ctx: dict[str, str]):
|
|
145
|
+
return Rebuild.__rebuild_map(initial_context, global_ctx)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class ConfigOutput:
|
|
150
|
+
output_name: str
|
|
151
|
+
mustache_template: str | None
|
|
152
|
+
output_template: str | None
|
|
153
|
+
lang: str | None
|
|
154
|
+
types: str | None
|
|
155
|
+
partials: str | None
|
|
156
|
+
initial_context: dict
|
|
157
|
+
debug: bool
|
|
158
|
+
ndebug: bool
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def from_config(
|
|
162
|
+
data: dict[str, str | dict[str, Any]], root_default: "ConfigOutput"
|
|
163
|
+
):
|
|
164
|
+
result = [ConfigOutput.from_dict(key, item) for key, item in data.items()]
|
|
165
|
+
|
|
166
|
+
for index in range(len(result)):
|
|
167
|
+
default = result[index]
|
|
168
|
+
if default.output_name != "":
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
del result[index]
|
|
172
|
+
for item in result:
|
|
173
|
+
item.update_from(default)
|
|
174
|
+
|
|
175
|
+
break
|
|
176
|
+
|
|
177
|
+
for item in result:
|
|
178
|
+
item.update_from(root_default)
|
|
179
|
+
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def from_dict(
|
|
184
|
+
output_name: str, data: str | dict[str, str | dict]
|
|
185
|
+
) -> "ConfigOutput":
|
|
186
|
+
if isinstance(data, str):
|
|
187
|
+
return ConfigOutput(
|
|
188
|
+
output_name=output_name,
|
|
189
|
+
mustache_template=data,
|
|
190
|
+
output_template=None,
|
|
191
|
+
lang=None,
|
|
192
|
+
types=None,
|
|
193
|
+
partials=None,
|
|
194
|
+
initial_context={},
|
|
195
|
+
debug=False,
|
|
196
|
+
ndebug=False,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
mustache_template = cast(str | None, data.get("template", None))
|
|
200
|
+
output_template = cast(str | None, data.get("path", None))
|
|
201
|
+
lang = cast(str | None, data.get("lang", None))
|
|
202
|
+
types = cast(str | None, data.get("types", None))
|
|
203
|
+
partials = cast(str | None, data.get("partials", None))
|
|
204
|
+
initial_context = copy.deepcopy(cast(dict, data.get("context", {})))
|
|
205
|
+
debug = not not data.get("debug", {})
|
|
206
|
+
ndebug = not not data.get("ndebug", {})
|
|
207
|
+
|
|
208
|
+
return ConfigOutput(
|
|
209
|
+
output_name=output_name,
|
|
210
|
+
mustache_template=mustache_template,
|
|
211
|
+
output_template=output_template,
|
|
212
|
+
lang=lang,
|
|
213
|
+
types=types,
|
|
214
|
+
partials=partials,
|
|
215
|
+
initial_context=initial_context,
|
|
216
|
+
debug=debug,
|
|
217
|
+
ndebug=ndebug,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def update_from(self, default: "ConfigOutput"):
|
|
221
|
+
self.mustache_template = self.mustache_template or default.mustache_template
|
|
222
|
+
self.output_template = self.output_template or default.output_template
|
|
223
|
+
self.lang = self.lang or default.lang
|
|
224
|
+
self.types = self.types or default.types
|
|
225
|
+
self.partials = self.partials or default.partials
|
|
226
|
+
self.initial_context = {
|
|
227
|
+
**copy.deepcopy(self.initial_context),
|
|
228
|
+
**copy.deepcopy(default.initial_context),
|
|
229
|
+
}
|
|
230
|
+
self.debug = self.debug or default.debug
|
|
231
|
+
self.ndebug = self.ndebug or default.ndebug
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _file_props(path: Path):
|
|
235
|
+
suffixes = "".join(path.suffixes)
|
|
236
|
+
name = path.name
|
|
237
|
+
shortest = name[: -len(suffixes)]
|
|
238
|
+
return {
|
|
239
|
+
"filename": path.as_posix(),
|
|
240
|
+
"dirname": path.parent.as_posix(),
|
|
241
|
+
"basename": path.name,
|
|
242
|
+
"stem": path.stem,
|
|
243
|
+
"suffix": path.suffix,
|
|
244
|
+
"suffixes": suffixes,
|
|
245
|
+
"shortest_stem": shortest,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _rel_file_props(path: Path, root: Path):
|
|
250
|
+
props: dict = _file_props(path)
|
|
251
|
+
if path.is_relative_to(root):
|
|
252
|
+
rel = path.relative_to(root)
|
|
253
|
+
props["relative"] = {
|
|
254
|
+
"filename": rel.as_posix(),
|
|
255
|
+
"dirname": rel.parent.as_posix(),
|
|
256
|
+
}
|
|
257
|
+
return props
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class Output:
|
|
262
|
+
output: Path
|
|
263
|
+
mustache_template: Path | None
|
|
264
|
+
lang: str | None
|
|
265
|
+
types: Path | None
|
|
266
|
+
partials: Path | None
|
|
267
|
+
initial_context: dict
|
|
268
|
+
debug: bool
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def filename_context(filename: str, root_dir: Path):
|
|
272
|
+
return _rel_file_props(Path(filename), root_dir)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def abs_path(basedir: Path, path: str):
|
|
276
|
+
return basedir / path
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def from_config(
|
|
280
|
+
context: dict[str, str],
|
|
281
|
+
root_default: ConfigOutput,
|
|
282
|
+
data: dict[str, str | dict[str, Any]],
|
|
283
|
+
abs_path: Callable[[str], Path],
|
|
284
|
+
):
|
|
285
|
+
expand_path = lambda text: chevron.render(template=text, data=context)
|
|
286
|
+
|
|
287
|
+
result: list[Output] = []
|
|
288
|
+
|
|
289
|
+
for output in ConfigOutput.from_config(data, root_default):
|
|
290
|
+
output_name = expand_path(output.output_name)
|
|
291
|
+
context["PATH"] = str(output_name)
|
|
292
|
+
|
|
293
|
+
if output.output_template:
|
|
294
|
+
output_name = expand_path(output.output_template)
|
|
295
|
+
output_name = abs_path(output_name)
|
|
296
|
+
|
|
297
|
+
mustache_template = None
|
|
298
|
+
if output.mustache_template:
|
|
299
|
+
mustache_template = abs_path(expand_path(output.mustache_template))
|
|
300
|
+
|
|
301
|
+
types = None
|
|
302
|
+
if output.types:
|
|
303
|
+
types = abs_path(expand_path(output.types))
|
|
304
|
+
|
|
305
|
+
partials = None
|
|
306
|
+
if output.partials:
|
|
307
|
+
partials = abs_path(expand_path(output.partials))
|
|
308
|
+
|
|
309
|
+
result.append(
|
|
310
|
+
Output(
|
|
311
|
+
output=output_name,
|
|
312
|
+
mustache_template=mustache_template,
|
|
313
|
+
lang=output.lang,
|
|
314
|
+
types=types,
|
|
315
|
+
partials=partials,
|
|
316
|
+
initial_context=Rebuild.rebuild(output.initial_context, context),
|
|
317
|
+
debug=output.debug and not output.ndebug,
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
def get_type_replacements(self):
|
|
324
|
+
return TypeReplacement.load_config(self.lang, self.types)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass
|
|
328
|
+
class TemplateRule:
|
|
329
|
+
inputs: list[Path]
|
|
330
|
+
outputs: list[Output]
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def from_config(
|
|
334
|
+
global_ctx: dict, source_dir: Path, data: dict, abs_path: Callable[[str], Path]
|
|
335
|
+
):
|
|
336
|
+
templates = cast(dict[str, dict[str, str | dict]], data.get("templates", {}))
|
|
337
|
+
inputs = cast(list[dict[str, str]], data.get("inputs", []))
|
|
338
|
+
return cast(
|
|
339
|
+
list[TemplateRule],
|
|
340
|
+
list(
|
|
341
|
+
filter(
|
|
342
|
+
lambda v: v is not None,
|
|
343
|
+
[
|
|
344
|
+
TemplateRule.__get_rule(
|
|
345
|
+
cast(list[str] | str, input.get("idl", [])),
|
|
346
|
+
cast(str | None, input.get("template")),
|
|
347
|
+
global_ctx,
|
|
348
|
+
source_dir,
|
|
349
|
+
templates,
|
|
350
|
+
abs_path,
|
|
351
|
+
)
|
|
352
|
+
for input in inputs
|
|
353
|
+
],
|
|
354
|
+
)
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def __get_rule(
|
|
360
|
+
input_names: str | list[str],
|
|
361
|
+
suite_id: str | None,
|
|
362
|
+
global_ctx: dict,
|
|
363
|
+
source_dir: Path,
|
|
364
|
+
templates: dict[str, dict[str, str | dict[str, Any]]],
|
|
365
|
+
abs_path: Callable[[str], Path],
|
|
366
|
+
):
|
|
367
|
+
if suite_id is None:
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
names = [input_names] if isinstance(input_names, str) else input_names
|
|
371
|
+
context = {**global_ctx, "input": Output.filename_context(names[0], source_dir)}
|
|
372
|
+
|
|
373
|
+
inputs = [abs_path(name) for name in names]
|
|
374
|
+
root_config = ConfigOutput.from_dict("", templates.get("", {}).get("", {}))
|
|
375
|
+
outputs = Output.from_config(
|
|
376
|
+
context, root_config, templates.get(suite_id, {}), abs_path
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return TemplateRule(inputs=inputs, outputs=outputs)
|
|
380
|
+
|
|
381
|
+
def get_dependencies(
|
|
382
|
+
self, CMAKE_CURRENT_BINARY_DIR: Path | None, *additional_inputs: Path
|
|
383
|
+
) -> dict[str, list[str]]:
|
|
384
|
+
def rel(path: Path):
|
|
385
|
+
return _relative(path, CMAKE_CURRENT_BINARY_DIR)
|
|
386
|
+
|
|
387
|
+
result: dict[str, list[str]] = {}
|
|
388
|
+
for out in self.outputs:
|
|
389
|
+
current_inputs: list[Path] = [*self.inputs, *additional_inputs]
|
|
390
|
+
if out.mustache_template:
|
|
391
|
+
current_inputs.append(out.mustache_template)
|
|
392
|
+
result[rel(out.output).as_posix()] = [
|
|
393
|
+
path.as_posix() for path in current_inputs
|
|
394
|
+
]
|
|
395
|
+
return result
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@dataclass
|
|
399
|
+
class TemplateConfig:
|
|
400
|
+
version: int
|
|
401
|
+
rules: list[TemplateRule]
|
|
402
|
+
ext_attrs: ExtAttrsContextBuilders
|
|
403
|
+
|
|
404
|
+
@staticmethod
|
|
405
|
+
def load_config(
|
|
406
|
+
config: Path,
|
|
407
|
+
context: dict,
|
|
408
|
+
source_dir: Path,
|
|
409
|
+
abs_path: Callable[[str], Path] | None = None,
|
|
410
|
+
):
|
|
411
|
+
with config.open(encoding="UTF-8") as json_file:
|
|
412
|
+
data = cast(dict, json.load(json_file))
|
|
413
|
+
|
|
414
|
+
config_dep = config.absolute()
|
|
415
|
+
basedir = config_dep.parent
|
|
416
|
+
|
|
417
|
+
return TemplateConfig(
|
|
418
|
+
version=cast(int, data.get("version", 1)),
|
|
419
|
+
rules=TemplateRule.from_config(
|
|
420
|
+
context, source_dir, data, abs_path or (lambda path: basedir / path)
|
|
421
|
+
),
|
|
422
|
+
ext_attrs=ExtAttrsContextBuilders.builtin().merge(
|
|
423
|
+
ExtAttrsContextBuilders.from_config(data)
|
|
424
|
+
),
|
|
425
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
from proj_flow.ext.webidl.cli.cmake import cmake
|
|
5
|
+
from proj_flow.ext.webidl.cli.depfile import depfile
|
|
6
|
+
from proj_flow.ext.webidl.cli.gen import gen
|
|
7
|
+
from proj_flow.ext.webidl.cli.init import init
|
|
8
|
+
from proj_flow.ext.webidl.cli.root import webidl
|
|
9
|
+
|
|
10
|
+
__all__ = ["webidl", "init", "cmake", "depfile", "gen"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
import typing
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import chevron
|
|
8
|
+
|
|
9
|
+
from proj_flow.api import arg, env
|
|
10
|
+
from proj_flow.ext.webidl.base.config import (
|
|
11
|
+
CMakeDirs,
|
|
12
|
+
TemplateConfig,
|
|
13
|
+
load_package_template,
|
|
14
|
+
)
|
|
15
|
+
from proj_flow.ext.webidl.cli.updater import update_file_if_needed
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@arg.command("webidl", "cmake")
|
|
19
|
+
def cmake(
|
|
20
|
+
config_path: typing.Annotated[
|
|
21
|
+
str, arg.Argument(help="Input configuration", names=["--cfg"], meta="json-file")
|
|
22
|
+
],
|
|
23
|
+
binary_dir: typing.Annotated[
|
|
24
|
+
str,
|
|
25
|
+
arg.Argument(
|
|
26
|
+
help="Project binary dir", names=["--binary-dir"], meta="project-binary-dir"
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
target: typing.Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
arg.Argument(
|
|
32
|
+
help="Name of the target to add output files to", meta="name", opt=True
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
rt: env.Runtime,
|
|
36
|
+
):
|
|
37
|
+
"""Write the CMake configuration based on given config"""
|
|
38
|
+
|
|
39
|
+
global_ctx = {
|
|
40
|
+
key: f"${{{key}}}"
|
|
41
|
+
for key in [
|
|
42
|
+
"CMAKE_CURRENT_SOURCE_DIR",
|
|
43
|
+
"CMAKE_CURRENT_BINARY_DIR",
|
|
44
|
+
"PROJECT_SOURCE_DIR",
|
|
45
|
+
"PROJECT_BINARY_DIR",
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
config_filename = Path(config_path)
|
|
49
|
+
cmake_dirs = CMakeDirs.from_config_path(
|
|
50
|
+
config_filename, project_binary_dir=Path(binary_dir)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
abs_path = cmake_dirs.make_abs_path()
|
|
54
|
+
|
|
55
|
+
depfile_ref = cmake_dirs.prefix(
|
|
56
|
+
str(cmake_dirs.dependency_from_config(config_filename))
|
|
57
|
+
)
|
|
58
|
+
cmake_filename = cmake_dirs.cmake_from_config(config_filename)
|
|
59
|
+
config_noext = cmake_dirs.filename_from_config(config_filename, "")
|
|
60
|
+
|
|
61
|
+
config = TemplateConfig.load_config(
|
|
62
|
+
config_filename.absolute(), global_ctx, cmake_dirs.project_source_dir, abs_path
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
context = {
|
|
66
|
+
"config": {
|
|
67
|
+
"binary": cmake_dirs.prefix(str(config_noext)).as_posix(),
|
|
68
|
+
"source": cmake_dirs.prefix(str(config_filename.absolute())).as_posix(),
|
|
69
|
+
"basename": config_filename.name,
|
|
70
|
+
},
|
|
71
|
+
"output": [
|
|
72
|
+
out.output.as_posix() for rule in config.rules for out in rule.outputs
|
|
73
|
+
],
|
|
74
|
+
"target": target,
|
|
75
|
+
"depfile": depfile_ref.as_posix(),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
text = load_package_template("cmake")
|
|
79
|
+
text = chevron.render(text, data=context, partials_path=None)
|
|
80
|
+
update_file_if_needed(
|
|
81
|
+
cmake_filename, text, f"Generating {cmake_filename.as_posix()}"
|
|
82
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
import typing
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import chevron
|
|
8
|
+
|
|
9
|
+
from proj_flow.api import arg, env
|
|
10
|
+
from proj_flow.ext.webidl.base.config import (
|
|
11
|
+
CMakeDirs,
|
|
12
|
+
TemplateConfig,
|
|
13
|
+
load_package_template,
|
|
14
|
+
)
|
|
15
|
+
from proj_flow.ext.webidl.cli.updater import update_file_if_needed
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@arg.command("webidl", "depfile")
|
|
19
|
+
def depfile(
|
|
20
|
+
config_path: typing.Annotated[
|
|
21
|
+
str, arg.Argument(help="Input configuration", names=["--cfg"], meta="json-file")
|
|
22
|
+
],
|
|
23
|
+
binary_dir: typing.Annotated[
|
|
24
|
+
str,
|
|
25
|
+
arg.Argument(
|
|
26
|
+
help="Project binary dir", names=["--binary-dir"], meta="project-binary-dir"
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
output_path: typing.Annotated[
|
|
30
|
+
str | None,
|
|
31
|
+
arg.Argument(
|
|
32
|
+
help="Output path", names=["--out"], meta="dependency-file", opt=True
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
rt: env.Runtime,
|
|
36
|
+
):
|
|
37
|
+
"""Write the dependency file for CMake generators to use"""
|
|
38
|
+
|
|
39
|
+
cmake_dirs = CMakeDirs.from_config_path(
|
|
40
|
+
Path(config_path), project_binary_dir=Path(binary_dir)
|
|
41
|
+
)
|
|
42
|
+
global_ctx = cmake_dirs.my_defines()
|
|
43
|
+
|
|
44
|
+
config_filename = Path(config_path).absolute()
|
|
45
|
+
config = TemplateConfig.load_config(
|
|
46
|
+
config_filename, global_ctx, cmake_dirs.project_source_dir
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
output = (
|
|
50
|
+
Path(output_path)
|
|
51
|
+
if output_path
|
|
52
|
+
else cmake_dirs.dependency_from_config(Path(config_path))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
flat_deps: dict[str, set[str]] = {}
|
|
56
|
+
|
|
57
|
+
for rule in config.rules:
|
|
58
|
+
for outname, inputs in rule.get_dependencies(None, config_filename).items():
|
|
59
|
+
try:
|
|
60
|
+
flat_deps[outname].update(inputs)
|
|
61
|
+
except KeyError:
|
|
62
|
+
flat_deps[outname] = set(inputs)
|
|
63
|
+
reverse: dict[str, set[str]] = {}
|
|
64
|
+
for key, value in flat_deps.items():
|
|
65
|
+
val = ";".join(sorted(value))
|
|
66
|
+
try:
|
|
67
|
+
reverse[val].update(key)
|
|
68
|
+
except KeyError:
|
|
69
|
+
reverse[val] = {key}
|
|
70
|
+
|
|
71
|
+
rules: list[dict] = []
|
|
72
|
+
for deps, tgts in reverse.items():
|
|
73
|
+
dependency = deps.split(";")
|
|
74
|
+
target = [{"sep": " \\\n", "pathname": pathname} for pathname in tgts]
|
|
75
|
+
target[0]["sep"] = ""
|
|
76
|
+
rules.append({"dependency": dependency, "target": target})
|
|
77
|
+
|
|
78
|
+
text = load_package_template("depfile")
|
|
79
|
+
update_file_if_needed(
|
|
80
|
+
output,
|
|
81
|
+
chevron.render(text, {"rule": rules}),
|
|
82
|
+
f"Generating {output.as_posix()}",
|
|
83
|
+
)
|