diff --git a/common/src/lib.rs b/common/src/lib.rs index da1eed9f..04c1dd4c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -188,7 +188,9 @@ pub fn default_applications() -> HashMap { let flmping_cmd = "${FLAME_HOME}/bin/flmping-service".to_string(); let uv_cmd = "${FLAME_HOME}/bin/uv".to_string(); let flmping_url = "file://${FLAME_HOME}/bin/flmping-service".to_string(); - let flamepy_sdk_path = "file://${FLAME_HOME}/sdk/python".to_string(); + // Use pre-built wheel from wheels directory to avoid rebuild on every run + // The wheel is built during installation via "flmadm install" + let flamepy_wheels_dir = "${FLAME_HOME}/wheels".to_string(); HashMap::from([ ( @@ -227,10 +229,12 @@ pub fn default_applications() -> HashMap { command: Some(uv_cmd), arguments: vec![ "run".to_string(), + "--find-links".to_string(), + flamepy_wheels_dir, "--with".to_string(), "pip".to_string(), "--with".to_string(), - format!("flamepy @ {}", flamepy_sdk_path), + "flamepy".to_string(), "python".to_string(), "-m".to_string(), "flamepy.runner.runpy".to_string(), diff --git a/flmadm/src/managers/installation.rs b/flmadm/src/managers/installation.rs index 9482f8c1..ef721fd9 100644 --- a/flmadm/src/managers/installation.rs +++ b/flmadm/src/managers/installation.rs @@ -193,21 +193,72 @@ impl InstallationManager { } // Copy SDK source to the installation directory, excluding development artifacts - // uv will use this directly with --with "flamepy @ file://..." self.copy_sdk_excluding_artifacts(&sdk_src, &paths.sdk_python) .context("Failed to copy SDK to installation directory")?; - println!("✓ Copied Python SDK to: {}", paths.sdk_python.display()); + println!(" ✓ Copied Python SDK to: {}", paths.sdk_python.display()); - // Create a note in the sdk_python directory for reference - let readme_path = paths.sdk_python.join("README.txt"); - std::fs::write( - &readme_path, - "Python SDK source copied to this directory.\n\ - Applications use 'uv run --with \"flamepy @ file://...\"' to access it.\n\ - No separate installation required.\n", - ) - .ok(); // Ignore errors for this informational file + // Build wheel for faster runtime loading + // uv always rebuilds local directory dependencies, but wheel files are cached + self.build_python_wheel(paths)?; + + Ok(()) + } + + /// Build Python wheel from SDK source and pre-cache dependencies + fn build_python_wheel(&self, paths: &InstallationPaths) -> Result<()> { + println!(" 📦 Building Python wheel..."); + + // Create wheels directory + fs::create_dir_all(&paths.wheels).context("Failed to create wheels directory")?; + + // Find uv binary + let uv_path = paths.bin.join("uv"); + if !uv_path.exists() { + println!(" ⚠️ uv not found, skipping wheel build (will build at runtime)"); + return Ok(()); + } + + // Build wheel using uv + let output = std::process::Command::new(&uv_path) + .arg("build") + .arg("--wheel") + .arg("--out-dir") + .arg(&paths.wheels) + .arg(&paths.sdk_python) + .output() + .context("Failed to execute uv build")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("Failed to build wheel: {}", stderr); + } + + println!(" ✓ Built wheel to: {}", paths.wheels.display()); + + // Pre-cache dependencies by downloading them to the wheels directory + // This way "uv run --find-links " can use cached packages + println!(" 📥 Downloading dependencies..."); + + let output = std::process::Command::new(&uv_path) + .arg("pip") + .arg("download") + .arg("--dest") + .arg(&paths.wheels) + .arg(&paths.sdk_python) + .output() + .context("Failed to execute uv pip download")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + // Don't fail installation if download fails (might be offline) + println!( + " ⚠️ Failed to download dependencies (will fetch at runtime): {}", + stderr.lines().next().unwrap_or(&stderr) + ); + } else { + println!(" ✓ Downloaded dependencies to: {}", paths.wheels.display()); + } Ok(()) } @@ -402,6 +453,12 @@ impl InstallationManager { println!(" ✓ Removed Python SDK"); } + // Remove wheels + if paths.wheels.exists() { + fs::remove_dir_all(&paths.wheels).context("Failed to remove wheels directory")?; + println!(" ✓ Removed wheels"); + } + // Remove migrations if paths.migrations.exists() { fs::remove_dir_all(&paths.migrations) diff --git a/flmadm/src/types.rs b/flmadm/src/types.rs index dc068f93..a592c1d9 100644 --- a/flmadm/src/types.rs +++ b/flmadm/src/types.rs @@ -95,6 +95,7 @@ pub struct InstallationPaths { pub prefix: PathBuf, pub bin: PathBuf, pub sdk_python: PathBuf, + pub wheels: PathBuf, pub work: PathBuf, pub logs: PathBuf, pub conf: PathBuf, @@ -108,6 +109,7 @@ impl InstallationPaths { Self { bin: prefix.join("bin"), sdk_python: prefix.join("sdk/python"), + wheels: prefix.join("wheels"), work: prefix.join("work"), logs: prefix.join("logs"), conf: prefix.join("conf"),