1212"""
1313import hashlib
1414import json
15+ import re
1516import sysconfig
1617from dataclasses import dataclass , field
1718from pathlib import Path
@@ -33,6 +34,11 @@ class Result:
3334 diagnostics : list [verify .Diagnostic ] = field (default_factory = list )
3435
3536
37+ def _relative (path : Path , root : Path ) -> str :
38+ """The module's path relative to the project root, posix separators (what the C embeds)."""
39+ return Path (path ).resolve ().relative_to (Path (root ).resolve ()).as_posix ()
40+
41+
3642def _gate_or_raise (path : Path ) -> None :
3743 errors = [d for d in gate .check ([path ], mode = "compiled" ) if d .severity == "error" ]
3844 if errors :
@@ -47,8 +53,12 @@ def compile_module(
4753 python_include : Path | None = None ,
4854 ext_suffix : str | None = None ,
4955 runtime_dir : Path | None = None ,
56+ project_root : Path | None = None ,
5057) -> Result :
58+ """`project_root`: the root the embedded source path is relative to (#112); default: the source's
59+ own directory, so the file name alone is embedded."""
5160 path = Path (path ).resolve ()
61+ root = path .parent if project_root is None else Path (project_root ).resolve ()
5262 _gate_or_raise (path )
5363
5464 lowered = frontend .lower (path )
@@ -57,7 +67,7 @@ def compile_module(
5767 if not proved .functions :
5868 return Result (path , proved , None , skipped , diagnostics )
5969
60- c_source = cgen .generate (proved , path )
70+ c_source = cgen .generate (proved , path , _relative ( path , root ) )
6171 extension = cbuild .build (
6272 c_source , path .stem , Path (out_dir ),
6373 runtime_dir = runtime_dir , python_include = python_include , ext_suffix = ext_suffix ,
@@ -98,6 +108,36 @@ def _tree_hash(*dirs: Path) -> str:
98108_NO_EXTENSION = ".noext" # cache artefact of a module with nothing compiled
99109
100110
111+ @dataclass (frozen = True )
112+ class SkippedItem :
113+ """A function or class that stays interpreted, and why (pypackpack's native-level report)."""
114+ name : str
115+ file : str # package-relative, posix
116+ line : int | None
117+ message : str # without the "line N: " prefix
118+
119+
120+ _LINE_PREFIX = re .compile (r"line (\d+): " )
121+
122+
123+ def skipped_items (skipped : dict [str , str ], diagnostics : list [verify .Diagnostic ], file : str ) -> list [SkippedItem ]:
124+ """Structure `ir.Module.skipped` (name -> text, which stays as it is: the incremental interface
125+ hash reads its keys only). The front end writes `line N: reason`; the verifier writes `rule:
126+ message` and records the line in its diagnostic for the function."""
127+ lines : dict [str , int ] = {}
128+ for d in diagnostics :
129+ lines .setdefault (d .function , d .source_line )
130+ out = []
131+ for name , text in sorted (skipped .items ()):
132+ m = _LINE_PREFIX .match (text )
133+ if m :
134+ line , message = int (m .group (1 )), text [m .end ():]
135+ else :
136+ line , message = lines .get (name ), text
137+ out .append (SkippedItem (name , file , line , message ))
138+ return out
139+
140+
101141@dataclass
102142class ModuleResult :
103143 path : Path
@@ -107,6 +147,8 @@ class ModuleResult:
107147 reasons : list [str ] = field (default_factory = list ) # why it was rebuilt (empty if reused)
108148 skipped : dict [str , str ] = field (default_factory = dict ) # function -> why it stays interpreted
109149 interface : str = ""
150+ kind : str = "compiled" # "compiled" | "nothing_compiled" | "not_marked"
151+ skipped_items : list [SkippedItem ] = field (default_factory = list ) # `skipped`, structured
110152
111153
112154@dataclass
@@ -129,16 +171,19 @@ def compile_project(
129171) -> ProjectResult :
130172 sources = [Path (s ).resolve () for s in sources ]
131173 cache_dir = Path (cache_dir )
132- verified : dict [Path , ir .Module ] = {}
174+ verified : dict [Path , list [SkippedItem ]] = {}
175+ verified_module_skipped : dict [Path , dict [str , str ]] = {}
176+ root = Path (project_root ).resolve ()
133177
134178 def lower (path : Path ) -> ir .Module :
135179 _gate_or_raise (path )
136- proved , _ = verify .verify (frontend .lower (path ))
137- verified [path ] = proved # the VERIFIED module: its signatures are what gets compiled
180+ proved , diagnostics = verify .verify (frontend .lower (path ))
181+ verified_module_skipped [path ] = dict (proved .skipped )
182+ verified [path ] = skipped_items (proved .skipped , diagnostics , _relative (path , root )) # the VERIFIED module: its signatures are what gets compiled
138183 return proved
139184
140185 def generate (module : ir .Module , path : Path ) -> str :
141- return cgen .generate (module , path ) if module .functions else ""
186+ return cgen .generate (module , path , _relative ( path , root ) ) if module .functions else ""
142187
143188 def compile_c (c_source : str , name : str , out_dir : Path ) -> Path :
144189 if not c_source :
@@ -166,11 +211,18 @@ def compile_c(c_source: str, name: str, out_dir: Path) -> Path:
166211 for m in report .modules :
167212 record = side / f"{ m .key } .json"
168213 if m .status == "rebuilt" :
169- record .write_text (json .dumps (dict (verified [m .path ].skipped )))
214+ record .write_text (json .dumps ({
215+ "skipped" : dict (verified_module_skipped [m .path ]),
216+ "items" : [vars (i ) for i in verified [m .path ]]}))
170217 try :
171- skipped = json .loads (record .read_text ())
172- except (OSError , ValueError ):
173- skipped = {}
218+ data = json .loads (record .read_text ())
219+ skipped = dict (data ["skipped" ])
220+ items = [SkippedItem (** i ) for i in data ["items" ]]
221+ except (OSError , ValueError , KeyError , TypeError ):
222+ skipped , items = {}, []
174223 ext = None if m .artifact is None or m .artifact .suffix == _NO_EXTENSION else m .artifact
175- out .append (ModuleResult (m .path , m .name , ext , m .status , list (m .reasons ), skipped , m .interface ))
224+ kind = ("not_marked" if not frontend .is_opted_in (m .path )
225+ else "compiled" if ext is not None else "nothing_compiled" )
226+ out .append (ModuleResult (m .path , m .name , ext , m .status , list (m .reasons ), skipped , m .interface ,
227+ kind , items ))
176228 return ProjectResult (out )
0 commit comments