22from mxdev .hooks import Hook
33from mxdev .state import State
44from pathlib import Path
5+ from typing import Any
56from typing import TYPE_CHECKING
67
78import logging
1516
1617logger = logging .getLogger ("mxdev" )
1718
19+ # Trailing comment used to tag the [tool.uv.sources] entries mxdev writes, so
20+ # stale ones can be pruned without touching user-defined sources.
21+ _UV_SOURCE_MARKER = "managed by mxdev"
22+
23+
24+ def _is_mxdev_managed_source (value : Any ) -> bool :
25+ """Return True if a [tool.uv.sources] value carries the mxdev marker comment."""
26+ trivia = getattr (value , "trivia" , None )
27+ comment = getattr (trivia , "comment" , "" ) or ""
28+ return _UV_SOURCE_MARKER in comment
29+
1830
1931def _constraints_to_uv (constraints : list [str ]) -> list [tuple [str , str ]]:
2032 """Turn resolved constraint lines into ordered uv array items.
@@ -138,31 +150,35 @@ def _update_pyproject(self, doc: "tomlkit.TOMLDocument", state: State) -> None:
138150 write_constraints = to_bool (settings .get ("uv-constraint-dependencies" , "true" ))
139151 constraint_items = _constraints_to_uv (state .constraints ) if write_constraints else []
140152
141- if not packages and not overrides and not constraint_items :
142- # Nothing to add. The only reason to continue is to drop a stale
143- # mxdev-managed constraint-dependencies array when the feature is on.
144- uv_table = doc .get ("tool" , {}).get ("uv" )
145- if not write_constraints or uv_table is None or "constraint-dependencies" not in uv_table :
146- return
153+ # Packages mxdev manages as path sources. A package in "skip" install-mode
154+ # gets no source entry (and an existing one is pruned below).
155+ managed_sources = {name : data for name , data in packages .items () if data .get ("install-mode" ) != "skip" }
147156
148157 if "tool" not in doc :
149158 doc .add ("tool" , tomlkit .table ())
150159 if "uv" not in doc ["tool" ]:
151160 doc ["tool" ]["uv" ] = tomlkit .table ()
152-
153- # 1. Update [tool.uv.sources]
154- if packages :
155- if "sources" not in doc ["tool" ]["uv" ]:
156- doc ["tool" ]["uv" ]["sources" ] = tomlkit .table ()
157-
158- uv_sources = doc ["tool" ]["uv" ]["sources" ]
159-
160- for pkg_name , pkg_data in packages .items ():
161+ uv = doc ["tool" ]["uv" ]
162+
163+ # 1. Reconcile [tool.uv.sources]: write the current managed sources and
164+ # prune mxdev-managed entries whose package was removed from mx.ini.
165+ # Foreign (user-defined) sources without the mxdev marker are never
166+ # touched.
167+ existing_sources = uv .get ("sources" )
168+ if managed_sources or existing_sources is not None :
169+ if existing_sources is None :
170+ uv ["sources" ] = tomlkit .table ()
171+ uv_sources = uv ["sources" ]
172+
173+ # Prune stale mxdev-managed entries.
174+ for key in list (uv_sources .keys ()):
175+ if _is_mxdev_managed_source (uv_sources [key ]) and key not in managed_sources :
176+ del uv_sources [key ]
177+
178+ # Write / refresh current managed entries, each tagged with the marker.
179+ for pkg_name , pkg_data in managed_sources .items ():
161180 install_mode = pkg_data .get ("install-mode" , "editable" )
162181
163- if install_mode == "skip" :
164- continue
165-
166182 target_dir = Path (pkg_data .get ("target" , "sources" ))
167183 package_path = target_dir / pkg_name
168184 subdirectory = pkg_data .get ("subdirectory" , "" )
@@ -186,13 +202,19 @@ def _update_pyproject(self, doc: "tomlkit.TOMLDocument", state: State) -> None:
186202 source_table .append ("editable" , False )
187203
188204 uv_sources [pkg_name ] = source_table
205+ uv_sources [pkg_name ].trivia .comment_ws = " "
206+ uv_sources [pkg_name ].trivia .comment = f"# { _UV_SOURCE_MARKER } "
207+
208+ # Drop the table entirely if reconciliation emptied it.
209+ if len (uv_sources ) == 0 :
210+ del uv ["sources" ]
189211
190212 # 2. Update [tool.uv] override-dependencies from version-overrides
191213 if overrides :
192214 override_array = tomlkit .array ()
193215 override_array .extend (overrides .values ())
194216 override_array .multiline (True )
195- doc [ "tool" ][ "uv" ] ["override-dependencies" ] = override_array
217+ uv ["override-dependencies" ] = override_array
196218
197219 # 3. Update [tool.uv] constraint-dependencies from resolved constraints
198220 if write_constraints :
@@ -205,7 +227,7 @@ def _update_pyproject(self, doc: "tomlkit.TOMLDocument", state: State) -> None:
205227 constraint_array .add_line (comment = text )
206228 else :
207229 constraint_array .add_line (text )
208- doc [ "tool" ][ "uv" ] ["constraint-dependencies" ] = constraint_array
209- elif "constraint-dependencies" in doc [ "tool" ][ "uv" ] :
230+ uv ["constraint-dependencies" ] = constraint_array
231+ elif "constraint-dependencies" in uv :
210232 # Resolved set is empty: drop a stale mxdev-managed array.
211- del doc [ "tool" ][ "uv" ] ["constraint-dependencies" ]
233+ del uv ["constraint-dependencies" ]
0 commit comments