@@ -300,8 +300,24 @@ def archive_of(record):
300300 print (f"Candidate matrix: { len (jobs )} real build/run jobs." )
301301
302302
303+ def replace_candidate (text : str , identity : str , version : str ) -> str :
304+ namespace , name = identity .split (":" , 1 )
305+ text , replacements = re .subn (
306+ rf'(?m)^(\s*(?:{ re .escape (name )} |"{ re .escape (namespace )} \.{ re .escape (name )} ")\s*=\s*(?:\{{\s*version\s*=\s*)?")[^"]+(".*)$' ,
307+ lambda m : m [1 ] + version + m [2 ], text )
308+ if not replacements :
309+ # A platform the member selects (mcpp SPEC-013).
310+ text , replacements = re .subn (
311+ rf'(?m)^(\s*platform\s*=\s*"(?:{ re .escape (namespace )} \.)?{ re .escape (name )} @)[^"]+(".*)$' ,
312+ lambda m : m [1 ] + version + m [2 ], text )
313+ if not replacements :
314+ raise RuntimeError ("candidate dependency was not replaced: " + identity )
315+ return text
316+
317+
303318def prepare (plan : Path , identity : str , output : Path , home : Path | None = None ):
304- record = next (r for r in json .loads (plan .read_text (encoding = "utf-8" ))["packages" ] if r ["id" ] == identity )
319+ candidates = json .loads (plan .read_text (encoding = "utf-8" ))["packages" ]
320+ record = next (r for r in candidates if r ["id" ] == identity )
305321 namespace , name = identity .split (":" , 1 )
306322 version = record ["candidate" ]
307323 member = direct_member (identity )
@@ -311,16 +327,13 @@ def prepare(plan: Path, identity: str, output: Path, home: Path | None = None):
311327 shutil .copytree (source , output , dirs_exist_ok = True ,
312328 ignore = shutil .ignore_patterns ("target" , ".mcpp" , "mcpp.lock" ))
313329 text = (output / "mcpp.toml" ).read_text (encoding = "utf-8" )
314- text , replacements = re .subn (
315- rf'(?m)^(\s*(?:{ re .escape (name )} |"{ re .escape (namespace )} \.{ re .escape (name )} ")\s*=\s*(?:\{{\s*version\s*=\s*)?")[^"]+(".*)$' ,
316- lambda m : m [1 ] + version + m [2 ], text )
317- if not replacements :
318- # A platform the member selects (mcpp SPEC-013): `platform = "<name>@<version>"`.
319- text , replacements = re .subn (
320- rf'(?m)^(\s*platform\s*=\s*"(?:{ re .escape (namespace )} \.)?{ re .escape (name )} @)[^"]+(".*)$' ,
321- lambda m : m [1 ] + version + m [2 ], text )
322- if not replacements :
323- raise RuntimeError ("candidate dependency was not replaced" )
330+ # One change plan may advance an ABI group together. Replacing only
331+ # this row creates a mixture the resolver correctly rejects. Apply
332+ # exactly the planned candidates mapped to this same real consumer;
333+ # unplanned dependencies retain the fixture's constraints.
334+ for candidate in candidates :
335+ if direct_member (candidate ["id" ]) == member :
336+ text = replace_candidate (text , candidate ["id" ], candidate ["candidate" ])
324337 # Standalone consumer: no inherited workspace or stale relative index redirects.
325338 text = re .sub (r'(?ms)^\[indices(?:\.[^\]]+)?\]\s*\n.*?(?=^\[|\Z)' , '' , text )
326339 else :
0 commit comments