ONE - On-device Neural Engine
Loading...
Searching...
No Matches
validate_global_conf.py
Go to the documentation of this file.
1# Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""
15Validator for ONE global target configuration packages.
16
17What this script checks:
181) Target INI existence & minimal required keys (TARGET, BACKEND).
192) Command schema files (codegen.py, profile.py) exist for BACKEND.
203) Command schema is importable without ONE runtime by shimming `onelib.argumentparse`.
214) "Required" arguments are present in schemas:
22 - codegen: DriverName, TargetOption, input{,_path}, output{,_path}
23 - profile: DriverName, TargetOption, input{,_path}
24
25Usage:
26 python tools/validate_global_conf.py --root . \
27 --target {TARGET_NAME} --backend {BACKEND_NAME}
28
29You can also point to the "installed" layout:
30 python tools/validate_global_conf.py --installed \
31 --target {TARGET_NAME} --backend {BACKEND_NAME}
32"""
33import argparse
34import configparser
35import importlib.util
36import io
37import os
38import sys
39import types
40from dataclasses import dataclass, field
41from typing import Dict, List, Optional, Tuple
42
43
44# -----------------------------
45# Minimal shim for onelib.argumentparse
46# -----------------------------
47class _Action:
48 pass
49
50
52 name = "DriverName"
53
54
56 name = "TargetOption"
57
58
60 name = "NormalOption"
61
62
63@dataclass
65 names: Tuple[str, ...]
66 action: type
67 dtype: Optional[type] = None
68
69
71 """
72 A very small recorder that emulates the subset used by command_schema().
73 """
74 def __init__(self):
75 self.args: List[_ArgSpec] = []
76
77 def add_argument(self, *names: str, action=_Action, dtype: Optional[type] = None):
78 if not names:
79 raise ValueError("add_argument requires at least one name")
80 # Normalize to strings
81 names = tuple(str(n) for n in names)
82 self.args.append(_ArgSpec(names=names, action=action, dtype=dtype))
83 return self
84
85
86# Expose shim as "onelib.argumentparse"
88 mod_onelib = types.ModuleType("onelib")
89 mod_argparse = types.ModuleType("onelib.argumentparse")
90 mod_argparse.ArgumentParser = ArgumentParser
91 mod_argparse.DriverName = DriverName
92 mod_argparse.TargetOption = TargetOption
93 mod_argparse.NormalOption = NormalOption
94
95 # Both import styles are used in examples:
96 # from onelib import argumentparse
97 # from onelib.argumentparse import DriverName, ...
98 sys.modules["onelib"] = mod_onelib
99 sys.modules["onelib.argumentparse"] = mod_argparse
100 mod_onelib.argumentparse = mod_argparse
101
102
103# -----------------------------
104# Utilities
105# -----------------------------
106@dataclass
108 file_path: str
109 ok: bool
110 messages: List[str] = field(default_factory=list)
111
112
113def _load_schema(file_path: str) -> Tuple[List[_ArgSpec], SchemaReport]:
114 rep = SchemaReport(file_path=file_path, ok=False)
115 if not os.path.isfile(file_path):
116 rep.messages.append(f"Missing schema file: {file_path}")
117 return [], rep
118
120 spec = importlib.util.spec_from_file_location("schema_module", file_path)
121 mod = importlib.util.module_from_spec(spec)
122 try:
123 assert spec and spec.loader
124 spec.loader.exec_module(mod) # type: ignore
125 except Exception as e:
126 rep.messages.append(f"Import error: {e}")
127 return [], rep
128 if not hasattr(mod, "command_schema"):
129 rep.messages.append("Schema module has no `command_schema()` function.")
130 return [], rep
131 try:
132 parser = mod.command_schema()
133 except Exception as e:
134 rep.messages.append(f"command_schema() execution failed: {e}")
135 return [], rep
136 if not isinstance(parser, ArgumentParser):
137 rep.messages.append(
138 "command_schema() did not return an ArgumentParser instance (shim).")
139 return [], rep
140
141 rep.ok = True
142 return parser.args, rep
143
144
145def _has_action(args: List[_ArgSpec], action_type: type) -> bool:
146 return any(a.action is action_type for a in args)
147
148
149def _has_any_name(args: List[_ArgSpec], names: List[str]) -> bool:
150 for a in args:
151 for n in a.names:
152 if n in names:
153 return True
154 return False
155
156
157def _check_codegen_contract(args: List[_ArgSpec]) -> List[str]:
158 errs = []
159 if not _has_action(args, DriverName):
160 errs.append("Missing DriverName action.")
161 if not _has_action(args, TargetOption):
162 errs.append("Missing TargetOption action.")
163 if not _has_any_name(args, ["input", "input_path"]):
164 errs.append("Missing input/input_path argument.")
165 if not _has_any_name(args, ["--output", "--output_path"]):
166 errs.append("Missing --output/--output_path option.")
167 return errs
168
169
170def _check_profile_contract(args: List[_ArgSpec]) -> Tuple[List[str], List[str]]:
171 errs, warns = [], []
172 if not _has_action(args, DriverName):
173 errs.append("Missing DriverName action.")
174 if not _has_action(args, TargetOption):
175 errs.append("Missing TargetOption action.")
176 if not _has_any_name(args, ["input", "input_path"]):
177 errs.append("Missing input/input_path argument.")
178 return errs, warns
179
180
181def _read_ini(path: str) -> Dict[str, str]:
182 # configparser with ; as comment
183 parser = configparser.ConfigParser(strict=False,
184 interpolation=None,
185 delimiters=("=", ))
186 # Treat keys as case-sensitive; normalize ourselves by not lowercasing
187 parser.optionxform = str # preserve case
188 with io.open(path, "r", encoding="utf-8") as f:
189 # Place everything in DEFAULT to keep simple key=value structure
190 content = "[DEFAULT]\n" + f.read()
191 parser.read_string(content)
192 return dict(parser["DEFAULT"])
193
194
195def _resolve_paths(root: str, target: str, backend: str, installed: bool):
196 if installed:
197 target_ini = f"/usr/share/one/target/{target}.ini"
198 codegen_py = f"/usr/share/one/backends/command/{backend}/codegen.py"
199 profile_py = f"/usr/share/one/backends/command/{backend}/profile.py"
200 else:
201 target_ini = os.path.join(root, "target", target, f"{target}.ini")
202 codegen_py = os.path.join(root, "backend", backend, "one-cmds", "codegen.py")
203 profile_py = os.path.join(root, "backend", backend, "one-cmds", "profile.py")
204 return target_ini, codegen_py, profile_py
205
206
207def main():
208 ap = argparse.ArgumentParser(
209 description="Validate ONE global target configuration package.")
210 ap.add_argument("--root", default=".", help="Repo root (for source-tree validation).")
211 ap.add_argument("--target", required=True, help="Target name, e.g., Rose")
212 ap.add_argument("--backend", required=True, help="Backend name, e.g., dummy")
213 ap.add_argument("--installed",
214 action="store_true",
215 help="Validate installed paths instead of source tree.")
216 args = ap.parse_args()
217
218 target_ini, codegen_py, profile_py = _resolve_paths(args.root, args.target,
219 args.backend, args.installed)
220
221 print("== ONE Global Conf Validator ==")
222 print(f"Mode : {'installed' if args.installed else 'source-tree'}")
223 print(f"Target INI: {target_ini}")
224 print(f"Codegen : {codegen_py}")
225 print(f"Profile : {profile_py}")
226 print("")
227
228 # 1) INI
229 if not os.path.isfile(target_ini):
230 print(f"[ERROR] Target INI not found: {target_ini}")
231 return 2
232 kv = _read_ini(target_ini)
233 errors = []
234 for k in ("TARGET", "BACKEND"):
235 if k not in kv or not kv[k].strip():
236 errors.append(f"Missing required key {k} in INI.")
237 if errors:
238 for e in errors:
239 print(f"[ERROR] {e}")
240 return 2
241
242 target_val = kv["TARGET"].strip()
243 backend_val = kv["BACKEND"].strip()
244 if target_val != args.target:
245 print(f"[WARN] TARGET in INI is '{target_val}' but --target is '{args.target}'")
246 if backend_val != args.backend:
247 print(
248 f"[WARN] BACKEND in INI is '{backend_val}' but --backend is '{args.backend}'")
249
250 # 2) Load schemas
251 codegen_args, codegen_rep = _load_schema(codegen_py)
252 profile_args, profile_rep = _load_schema(profile_py)
253
254 ok = True
255
256 def print_report(name: str, rep: SchemaReport):
257 nonlocal ok
258 status = "OK" if rep.ok else "FAIL"
259 print(f"[{name}] {rep.file_path}: {status}")
260 for m in rep.messages:
261 print(f" - {m}")
262 if not rep.ok:
263 ok = False
264
265 print_report("SCHEMA", codegen_rep)
266 print_report("SCHEMA", profile_rep)
267
268 # 3) Contracts
269 if codegen_rep.ok:
270 cerrs = _check_codegen_contract(codegen_args)
271 for e in cerrs:
272 print(f"[ERROR] codegen schema: {e}")
273 ok = ok and not cerrs
274 if profile_rep.ok:
275 perrs, pwrns = _check_profile_contract(profile_args)
276 for e in perrs:
277 print(f"[ERROR] profile schema: {e}")
278 for w in pwrns:
279 print(f"[WARN] profile schema: {w}")
280 ok = ok and not perrs
281
282 print("")
283 if ok:
284 print("[PASS] Validation succeeded.")
285 return 0
286 else:
287 print("[FAIL] Validation failed.")
288 return 2
289
290
291if __name__ == "__main__":
292 sys.exit(main())
add_argument(self, *str names, action=_Action, Optional[type] dtype=None)
Tuple[List[str], List[str]] _check_profile_contract(List[_ArgSpec] args)
Tuple[List[_ArgSpec], SchemaReport] _load_schema(str file_path)
Dict[str, str] _read_ini(str path)
_resolve_paths(str root, str target, str backend, bool installed)
bool _has_any_name(List[_ArgSpec] args, List[str] names)
bool _has_action(List[_ArgSpec] args, type action_type)
List[str] _check_codegen_contract(List[_ArgSpec] args)