@@ -42,6 +42,29 @@ def _read_toml(path: Path) -> dict[str, Any]:
4242 return tomllib .load (handle )
4343
4444
45+ def _get_bundle (config : dict [str , Any ]) -> str :
46+ bundle = config .get ("bundle" ) or config .get ("compat" )
47+ if isinstance (bundle , dict ):
48+ bundle = bundle .get ("bundle" )
49+ if not isinstance (bundle , str ) or not bundle .strip ():
50+ raise ValueError ("qsl.bundle (or qsl.compat.bundle) is required" )
51+ return bundle .strip ()
52+
53+
54+ def _get_upgrade_ring (config : dict [str , Any ]) -> str :
55+ upgrade_ring = config .get ("upgrade_ring" , config .get ("ring" ))
56+ if upgrade_ring is None or str (upgrade_ring ).strip () == "" :
57+ raise ValueError ("qsl.upgrade_ring (or qsl.ring) is required" )
58+ return str (upgrade_ring ).strip ()
59+
60+
61+ def _get_enforce_bundle (config : dict [str , Any ]) -> bool :
62+ compat = config .get ("compat" )
63+ if isinstance (compat , dict ) and "enforce_bundle" in compat :
64+ return bool (compat ["enforce_bundle" ])
65+ return bool (config .get ("enforce_bundle" , True ))
66+
67+
4568def _load_qsl_config (repo_root : Path ) -> dict [str , str | bool ]:
4669 qsl_path = repo_root / "qsl.toml"
4770 if not qsl_path .exists ():
@@ -52,25 +75,23 @@ def _load_qsl_config(repo_root: Path) -> dict[str, str | bool]:
5275 if not isinstance (config , dict ):
5376 raise TypeError ("qsl section must be a table" )
5477
55- bundle = config .get ("bundle" ) or config .get ("compat" )
56- if not isinstance (bundle , str ) or not bundle .strip ():
57- raise ValueError ("qsl.bundle (or qsl.compat) is required" )
78+ bundle = _get_bundle (config )
5879
5980 tier = config .get ("tier" )
6081 if not isinstance (tier , str ) or not tier .strip ():
6182 raise ValueError ("qsl.tier is required" )
6283
63- upgrade_ring = config .get ("upgrade_ring" )
64- if not isinstance (upgrade_ring , str ) or not upgrade_ring .strip ():
65- raise ValueError ("qsl.upgrade_ring is required" )
84+ upgrade_ring = _get_upgrade_ring (config )
6685
6786 allow_legacy = bool (config .get ("allow_legacy" , False ))
87+ enforce_bundle = _get_enforce_bundle (config )
6888
6989 return {
70- "bundle" : bundle . strip () ,
90+ "bundle" : bundle ,
7191 "tier" : tier .strip (),
72- "upgrade_ring" : str ( upgrade_ring ). strip () ,
92+ "upgrade_ring" : upgrade_ring ,
7393 "allow_legacy" : allow_legacy ,
94+ "enforce_bundle" : enforce_bundle ,
7495 "qsl_path" : qsl_path .as_posix (),
7596 }
7697
@@ -148,12 +169,14 @@ def _check(repo_root: Path, compat_root: Path) -> tuple[bool, list[str], list[st
148169 tier = str (qsl_cfg ["tier" ])
149170 upgrade_ring = str (qsl_cfg ["upgrade_ring" ])
150171 allow_legacy = bool (qsl_cfg ["allow_legacy" ])
172+ enforce_bundle = bool (qsl_cfg ["enforce_bundle" ])
151173 qsl_path = str (qsl_cfg ["qsl_path" ])
152174
153175 notes .append (f"qsl={ qsl_path } " )
154176 notes .append (f"bundle={ bundle } " )
155177 notes .append (f"tier={ tier } " )
156178 notes .append (f"upgrade_ring={ upgrade_ring } " )
179+ notes .append (f"enforce_bundle={ enforce_bundle } " )
157180
158181 bundle_refs = _load_bundle (compat_root , bundle )
159182
@@ -172,31 +195,39 @@ def _check(repo_root: Path, compat_root: Path) -> tuple[bool, list[str], list[st
172195 f"{ legacy_ref .repo } @{ legacy_ref .ref } "
173196 )
174197 continue
175- _validate_ref (legacy_ref , bundle_refs [legacy_ref .repo ], issues )
198+ _validate_ref (legacy_ref , bundle_refs [legacy_ref .repo ], issues , warnings , enforce_bundle )
176199
177200 discovered = _gather_repo_refs (repo_root )
178201 for pin in discovered :
179202 if pin .repo not in bundle_refs :
180203 issues .append (f"unmanaged qsl dependency in { pin .source } :{ pin .line_no } : { pin .repo } @{ pin .ref } " )
181204 continue
182205 expected_ref = bundle_refs [pin .repo ]
183- _validate_ref (pin , expected_ref , issues )
206+ _validate_ref (pin , expected_ref , issues , warnings , enforce_bundle )
184207
185208 return (len (issues ) == 0 , issues , warnings , notes )
186209
187210
188- def _validate_ref (pin : GitRef , expected_ref : str , issues : list [str ]) -> None :
211+ def _validate_ref (pin : GitRef , expected_ref : str , issues : list [str ], warnings : list [ str ], enforce_bundle : bool ) -> None :
189212 if _is_main_ref (pin .ref ):
190213 issues .append (f"forbidden ref 'main' in { pin .source } :{ pin .line_no } : { pin .repo } " )
191214 return
192215 if not _is_full_sha (pin .ref ):
193- issues .append (f"forbidden short/invalid ref '{ pin .ref } ' in { pin .source } :{ pin .line_no } : { pin .repo } " )
216+ message = f"forbidden short/invalid ref '{ pin .ref } ' in { pin .source } :{ pin .line_no } : { pin .repo } "
217+ if enforce_bundle :
218+ issues .append (message )
219+ else :
220+ warnings .append (message )
194221 return
195222 if pin .ref != expected_ref :
196- issues . append (
223+ message = (
197224 f"bundle pin mismatch for { pin .repo } in { pin .source } :{ pin .line_no } : "
198225 f"expected { expected_ref } , found { pin .ref } "
199226 )
227+ if enforce_bundle :
228+ issues .append (message )
229+ else :
230+ warnings .append (message )
200231
201232
202233def build_parser () -> argparse .ArgumentParser :
0 commit comments