88
99from __future__ import annotations
1010
11+ import shutil
1112from pathlib import Path
1213
1314from . import BaseBuilder , BuildException , Builder , BuilderFactory
@@ -75,6 +76,12 @@ def pypi(self, *packages: str) -> PixiBuilder:
7576 def env_type (self ) -> str :
7677 return "pixi"
7778
79+ def _add_state_fields (self , state : dict ) -> None :
80+ super ()._add_state_fields (state )
81+ state ["condaPackages" ] = list (self ._conda_packages )
82+ state ["pypiPackages" ] = list (self ._pypi_packages )
83+ state ["pixiEnvironment" ] = self ._pixi_environment
84+
7885 def build (self ) -> Environment :
7986 """
8087 Build the Pixi environment.
@@ -134,21 +141,10 @@ def build(self) -> Environment:
134141 try :
135142 pixi .install ()
136143
137- # Check if this is already a pixi project
138- is_pixi_dir = (
139- (env_dir / "pixi.toml" ).is_file ()
140- or (env_dir / "pyproject.toml" ).is_file ()
141- or (env_dir / ".pixi" ).is_dir ()
142- )
143-
144- if (
145- is_pixi_dir
146- and self ._content is None
147- and not self ._conda_packages
148- and not self ._pypi_packages
149- ):
150- # Environment already exists, just use it
151- return self ._create_environment (pixi , env_dir )
144+ # If the env state matches our current configuration,
145+ # skip all package management and return immediately.
146+ if self ._is_up_to_date (env_dir ):
147+ return self ._build_pixi_environment (pixi , env_dir )
152148
153149 # Handle source-based build (file or content)
154150 if self ._content is not None :
@@ -181,13 +177,10 @@ def build(self) -> Environment:
181177 if self ._channels :
182178 pixi .add_channels (env_dir , * self ._channels )
183179 else :
184- # Programmatic package building
185- if is_pixi_dir :
186- # Already initialized, just use it
187- return self ._create_environment (pixi , env_dir )
188-
189- if not env_dir .exists ():
190- env_dir .mkdir (parents = True , exist_ok = True )
180+ # Programmatic package building: wipe and reinitialize to avoid stale state.
181+ if env_dir .exists ():
182+ shutil .rmtree (env_dir )
183+ env_dir .mkdir (parents = True , exist_ok = True )
191184
192185 pixi .init (env_dir )
193186
@@ -212,21 +205,21 @@ def build(self) -> Environment:
212205 pixi .add_pypi_packages (env_dir , * self ._pypi_packages )
213206
214207 # Verify that appose was included when building programmatically
215- prog_build = bool (self ._conda_packages ) or bool (self ._pypi_packages )
216- if prog_build :
217- import re
218-
219- has_appose = any (
220- re .match (r"^appose\b" , pkg ) for pkg in self ._conda_packages
221- ) or any (re .match (r"^appose\b" , pkg ) for pkg in self ._pypi_packages )
222- if not has_appose :
223- raise BuildException (
224- self ,
225- "Appose package must be explicitly included when building programmatically. "
226- 'Add .conda("appose") or .pypi("appose") to your builder.' ,
227- )
208+ import re
228209
229- return self ._create_environment (pixi , env_dir )
210+ has_appose = any (
211+ re .match (r"^appose\b" , pkg ) for pkg in self ._conda_packages
212+ ) or any (re .match (r"^appose\b" , pkg ) for pkg in self ._pypi_packages )
213+ if not has_appose :
214+ raise BuildException (
215+ self ,
216+ "Appose package must be explicitly included when building programmatically. "
217+ 'Add .conda("appose") or .pypi("appose") to your builder.' ,
218+ )
219+
220+ self ._run_pixi_install (pixi , env_dir )
221+ self ._write_appose_state_file (env_dir )
222+ return self ._build_pixi_environment (pixi , env_dir )
230223
231224 except (IOError , KeyboardInterrupt ) as e :
232225 raise BuildException (self , cause = e )
@@ -268,13 +261,25 @@ def wrap(self, env_dir: str | Path) -> Environment:
268261 self .base (env_path )
269262 return self .build ()
270263
271- def _create_environment (self , pixi : Pixi , env_dir : Path ) -> Environment :
264+ def _run_pixi_install (self , pixi : Pixi , env_dir : Path ) -> None :
265+ """Run pixi install for the given environment directory."""
266+ env_dir_abs = env_dir .absolute ()
267+ manifest_file = env_dir_abs / "pyproject.toml"
268+ if not manifest_file .exists ():
269+ manifest_file = env_dir_abs / "pixi.toml"
270+
271+ install_cmd = ["install" , "--manifest-path" , str (manifest_file .absolute ())]
272+ if self ._pixi_environment is not None :
273+ install_cmd .extend (["--environment" , self ._pixi_environment ])
274+ pixi .exec (* install_cmd )
275+
276+ def _build_pixi_environment (self , pixi : Pixi , env_dir : Path ) -> Environment :
272277 """
273- Create an Environment for the given Pixi directory.
278+ Construct an Environment object for the given Pixi directory.
274279
275280 Args:
276- env_dir: The Pixi environment directory
277281 pixi: The Pixi tool instance
282+ env_dir: The Pixi environment directory
278283
279284 Returns:
280285 Environment configured for this Pixi installation
@@ -287,12 +292,6 @@ def _create_environment(self, pixi: Pixi, env_dir: Path) -> Environment:
287292 if not manifest_file .exists ():
288293 manifest_file = env_dir_abs / "pixi.toml"
289294
290- # Ensure the pixi environment is fully installed.
291- install_cmd = ["install" , "--manifest-path" , str (manifest_file .absolute ())]
292- if self ._pixi_environment is not None :
293- install_cmd .extend (["--environment" , self ._pixi_environment ])
294- pixi .exec (* install_cmd )
295-
296295 base = str (env_dir_abs )
297296 env_name = self ._pixi_environment or "default"
298297
0 commit comments