Skip to main content

palladium/package/
mod.rs

1// Package manager for Palladium
2// "Managing legends, one package at a time"
3
4pub mod build;
5pub mod cli;
6pub mod dependency;
7pub mod lockfile;
8pub mod registry;
9
10use crate::errors::{CompileError, Result};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::fs;
14use std::path::{Path, PathBuf};
15
16use dependency::{DependencyResolver, Package, Version, VersionRequirement};
17use lockfile::{Lockfile, LockedPackage, PackageSource};
18use registry::RegistryClient;
19
20/// Package manifest structure (package.pd)
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PackageManifest {
23    /// Package name
24    pub name: String,
25
26    /// Package version (semver)
27    pub version: String,
28
29    /// Package description
30    pub description: Option<String>,
31
32    /// Package authors
33    pub authors: Vec<String>,
34
35    /// Package license
36    pub license: Option<String>,
37
38    /// Package dependencies
39    pub dependencies: HashMap<String, Dependency>,
40
41    /// Dev dependencies (for tests/examples)
42    pub dev_dependencies: HashMap<String, Dependency>,
43
44    /// Build dependencies (for build scripts)
45    pub build_dependencies: HashMap<String, Dependency>,
46
47    /// Entry point (defaults to src/main.pd)
48    pub main: Option<String>,
49
50    /// Library entry point (defaults to src/lib.pd)
51    pub lib: Option<String>,
52
53    /// Binary targets
54    pub bin: Vec<BinaryTarget>,
55
56    /// Example targets
57    pub examples: Vec<ExampleTarget>,
58
59    /// Test targets
60    pub tests: Vec<TestTarget>,
61}
62
63/// Dependency specification
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum Dependency {
67    /// Simple version string
68    Version(String),
69
70    /// Detailed dependency
71    Detailed {
72        version: Option<String>,
73        path: Option<String>,
74        git: Option<String>,
75        branch: Option<String>,
76        tag: Option<String>,
77        rev: Option<String>,
78        features: Vec<String>,
79        optional: bool,
80    },
81}
82
83/// Binary target specification
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct BinaryTarget {
86    pub name: String,
87    pub path: String,
88}
89
90/// Example target specification
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ExampleTarget {
93    pub name: String,
94    pub path: String,
95}
96
97/// Test target specification
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TestTarget {
100    pub name: String,
101    pub path: String,
102}
103
104/// Package manager
105pub struct PackageManager {
106    /// Cache directory for downloaded packages
107    cache_dir: PathBuf,
108
109    /// Registry URL
110    registry_url: String,
111
112    /// Loaded package manifests
113    #[allow(dead_code)]
114    manifests: HashMap<String, PackageManifest>,
115    
116    /// Registry client
117    registry_client: Option<RegistryClient>,
118    
119    /// Dependency resolver
120    resolver: DependencyResolver,
121    
122    /// Current lockfile
123    lockfile: Option<Lockfile>,
124}
125
126impl PackageManager {
127    /// Create a new package manager
128    pub fn new() -> Result<Self> {
129        let home_dir = dirs::home_dir()
130            .ok_or_else(|| CompileError::Generic("Could not find home directory".to_string()))?;
131
132        let cache_dir = home_dir.join(".palladium").join("cache");
133
134        // Create cache directory if it doesn't exist
135        if !cache_dir.exists() {
136            fs::create_dir_all(&cache_dir).map_err(CompileError::IoError)?;
137        }
138
139        let registry_url = "https://packages.palladium-lang.org".to_string();
140        let registry_client = RegistryClient::new(registry_url.clone(), cache_dir.clone()).ok();
141        
142        Ok(Self {
143            cache_dir,
144            registry_url,
145            manifests: HashMap::new(),
146            registry_client,
147            resolver: DependencyResolver::new(),
148            lockfile: None,
149        })
150    }
151
152    /// Load package manifest from a file
153    pub fn load_manifest(path: &Path) -> Result<PackageManifest> {
154        let content = fs::read_to_string(path).map_err(CompileError::IoError)?;
155
156        // For now, we'll parse a simple format
157        // In the future, this could be TOML or a custom format
158        Self::parse_manifest(&content)
159    }
160
161    /// Parse manifest from string
162    fn parse_manifest(content: &str) -> Result<PackageManifest> {
163        // Simple parser for package.pd format
164        // Format example:
165        // name = "my_package"
166        // version = "0.1.0"
167        // description = "A cool package"
168        // authors = ["John Doe <john@example.com>"]
169        //
170        // [dependencies]
171        // std = "1.0"
172        // http = { version = "0.2", features = ["client"] }
173
174        let mut manifest = PackageManifest {
175            name: String::new(),
176            version: String::new(),
177            description: None,
178            authors: Vec::new(),
179            license: None,
180            dependencies: HashMap::new(),
181            dev_dependencies: HashMap::new(),
182            build_dependencies: HashMap::new(),
183            main: None,
184            lib: None,
185            bin: Vec::new(),
186            examples: Vec::new(),
187            tests: Vec::new(),
188        };
189
190        let mut current_section = "";
191
192        for line in content.lines() {
193            let line = line.trim();
194
195            // Skip empty lines and comments
196            if line.is_empty() || line.starts_with("//") || line.starts_with("#") {
197                continue;
198            }
199
200            // Check for section headers
201            if line.starts_with('[') && line.ends_with(']') {
202                current_section = &line[1..line.len() - 1];
203                continue;
204            }
205
206            // Parse key-value pairs
207            if let Some(eq_pos) = line.find('=') {
208                let key = line[..eq_pos].trim();
209                let value = line[eq_pos + 1..].trim();
210
211                match current_section {
212                    "" => {
213                        // Top-level fields
214                        match key {
215                            "name" => manifest.name = Self::parse_string(value)?,
216                            "version" => manifest.version = Self::parse_string(value)?,
217                            "description" => {
218                                manifest.description = Some(Self::parse_string(value)?)
219                            }
220                            "license" => manifest.license = Some(Self::parse_string(value)?),
221                            "main" => manifest.main = Some(Self::parse_string(value)?),
222                            "lib" => manifest.lib = Some(Self::parse_string(value)?),
223                            "authors" => manifest.authors = Self::parse_string_array(value)?,
224                            _ => {} // Ignore unknown fields
225                        }
226                    }
227                    "dependencies" => {
228                        let dep = Self::parse_dependency(value)?;
229                        manifest.dependencies.insert(key.to_string(), dep);
230                    }
231                    "dev-dependencies" => {
232                        let dep = Self::parse_dependency(value)?;
233                        manifest.dev_dependencies.insert(key.to_string(), dep);
234                    }
235                    "build-dependencies" => {
236                        let dep = Self::parse_dependency(value)?;
237                        manifest.build_dependencies.insert(key.to_string(), dep);
238                    }
239                    _ => {} // Ignore unknown sections
240                }
241            }
242        }
243
244        // Validate required fields
245        if manifest.name.is_empty() {
246            return Err(CompileError::Generic(
247                "Package name is required".to_string(),
248            ));
249        }
250        if manifest.version.is_empty() {
251            return Err(CompileError::Generic(
252                "Package version is required".to_string(),
253            ));
254        }
255
256        Ok(manifest)
257    }
258
259    /// Parse a quoted string
260    fn parse_string(value: &str) -> Result<String> {
261        if value.starts_with('"') && value.ends_with('"') {
262            Ok(value[1..value.len() - 1].to_string())
263        } else {
264            Err(CompileError::Generic(format!(
265                "Expected quoted string, got: {}",
266                value
267            )))
268        }
269    }
270
271    /// Parse an array of strings
272    fn parse_string_array(value: &str) -> Result<Vec<String>> {
273        if value.starts_with('[') && value.ends_with(']') {
274            let inner = &value[1..value.len() - 1];
275            let mut result = Vec::new();
276
277            for item in inner.split(',') {
278                let item = item.trim();
279                if !item.is_empty() {
280                    result.push(Self::parse_string(item)?);
281                }
282            }
283
284            Ok(result)
285        } else {
286            Err(CompileError::Generic(format!(
287                "Expected array, got: {}",
288                value
289            )))
290        }
291    }
292
293    /// Parse a dependency specification
294    fn parse_dependency(value: &str) -> Result<Dependency> {
295        if value.starts_with('"') && value.ends_with('"') {
296            // Simple version string
297            Ok(Dependency::Version(value[1..value.len() - 1].to_string()))
298        } else if value.starts_with('{') && value.ends_with('}') {
299            // Detailed dependency
300            // For now, just return a simple version
301            // TODO: Implement proper parsing
302            Ok(Dependency::Version("*".to_string()))
303        } else {
304            Err(CompileError::Generic(format!(
305                "Invalid dependency format: {}",
306                value
307            )))
308        }
309    }
310
311    /// Initialize a new package in the current directory
312    pub fn init(name: &str, path: &Path) -> Result<()> {
313        // Create directory structure
314        let src_dir = path.join("src");
315        if !src_dir.exists() {
316            fs::create_dir_all(&src_dir).map_err(CompileError::IoError)?;
317        }
318
319        // Create default package.pd
320        let manifest = PackageManifest {
321            name: name.to_string(),
322            version: "0.1.0".to_string(),
323            description: Some("A new Palladium package".to_string()),
324            authors: vec![Self::get_default_author()],
325            license: Some("MIT".to_string()),
326            dependencies: HashMap::new(),
327            dev_dependencies: HashMap::new(),
328            build_dependencies: HashMap::new(),
329            main: None,
330            lib: None,
331            bin: Vec::new(),
332            examples: Vec::new(),
333            tests: Vec::new(),
334        };
335
336        let manifest_path = path.join("package.pd");
337        let manifest_content = Self::manifest_to_string(&manifest);
338        fs::write(&manifest_path, manifest_content).map_err(CompileError::IoError)?;
339
340        // Create default main.pd
341        let main_path = src_dir.join("main.pd");
342        let main_content = r#"// Entry point for the package
343
344fn main() {
345    print("Hello from {}!\n");
346}
347"#
348        .replace("{}", name);
349
350        fs::write(&main_path, main_content).map_err(CompileError::IoError)?;
351
352        println!("✅ Created package '{}' at {}", name, path.display());
353
354        Ok(())
355    }
356
357    /// Get default author from git config or environment
358    fn get_default_author() -> String {
359        // Try to get from git config
360        if let Ok(output) = std::process::Command::new("git")
361            .args(["config", "--global", "user.name"])
362            .output()
363        {
364            if output.status.success() {
365                if let Ok(name) = String::from_utf8(output.stdout) {
366                    let name = name.trim();
367
368                    // Also try to get email
369                    if let Ok(email_output) = std::process::Command::new("git")
370                        .args(["config", "--global", "user.email"])
371                        .output()
372                    {
373                        if email_output.status.success() {
374                            if let Ok(email) = String::from_utf8(email_output.stdout) {
375                                let email = email.trim();
376                                return format!("{} <{}>", name, email);
377                            }
378                        }
379                    }
380
381                    return name.to_string();
382                }
383            }
384        }
385
386        // Fall back to environment
387        if let Ok(user) = std::env::var("USER") {
388            return user;
389        }
390
391        "Unknown Author".to_string()
392    }
393
394    /// Install dependencies for the current package
395    pub fn install(&mut self) -> Result<()> {
396        // Load manifest
397        let manifest_path = Path::new("package.pd");
398        let manifest = Self::load_manifest(manifest_path)?;
399        
400        println!("📦 Installing dependencies for '{}'...", manifest.name);
401        
402        // Check if lockfile exists
403        let lockfile_path = Path::new("package.lock");
404        if lockfile_path.exists() {
405            println!("🔒 Found lockfile, installing exact versions...");
406            self.lockfile = Some(Lockfile::load(lockfile_path)?);
407            return self.install_from_lockfile();
408        }
409        
410        // Resolve dependencies
411        println!("🔍 Resolving dependencies...");
412        let resolved = self.resolve_dependencies(&manifest)?;
413        
414        // Create lockfile
415        let mut lockfile = Lockfile::new(&manifest.name, &manifest.version);
416        
417        // Download and install packages
418        for (package_name, version) in &resolved.packages {
419            if package_name == &manifest.name {
420                continue; // Skip root package
421            }
422            
423            println!("📥 Installing {} v{}...", package_name, version);
424            
425            if let Some(registry) = &self.registry_client {
426                let package_path = registry.download_package(package_name, &version.to_string())?;
427                
428                // Add to lockfile
429                lockfile.add_package(LockedPackage {
430                    name: package_name.clone(),
431                    version: version.to_string(),
432                    source: PackageSource::Registry {
433                        url: self.registry_url.clone(),
434                    },
435                    dependencies: vec![], // TODO: Fill in dependencies
436                    checksum: "TODO".to_string(), // TODO: Calculate checksum
437                });
438                
439                println!("   ✅ Installed to {}", package_path.display());
440            } else {
441                return Err(CompileError::Generic(
442                    "Registry client not available".to_string(),
443                ));
444            }
445        }
446        
447        // Save lockfile
448        lockfile.save(lockfile_path)?;
449        println!("🔒 Created lockfile");
450        
451        println!("✅ Installation complete! {} packages installed", resolved.packages.len() - 1);
452        Ok(())
453    }
454    
455    /// Install from existing lockfile
456    fn install_from_lockfile(&mut self) -> Result<()> {
457        let lockfile = self.lockfile.as_ref().unwrap();
458        
459        // Verify checksums
460        lockfile.verify_checksums(&self.cache_dir)?;
461        
462        // Download missing packages
463        for package in &lockfile.packages {
464            let package_dir = self.cache_dir.join(&package.name).join(&package.version);
465            
466            if !package_dir.exists() {
467                println!("📥 Installing {} v{}...", package.name, package.version);
468                
469                if let Some(registry) = &self.registry_client {
470                    registry.download_package(&package.name, &package.version)?;
471                    println!("   ✅ Installed");
472                } else {
473                    return Err(CompileError::Generic(
474                        "Registry client not available".to_string(),
475                    ));
476                }
477            }
478        }
479        
480        println!("✅ All dependencies installed from lockfile");
481        Ok(())
482    }
483    
484    /// Resolve dependencies for a manifest
485    fn resolve_dependencies(&mut self, manifest: &PackageManifest) -> Result<dependency::ResolvedDependencies> {
486        // Load available packages from registry
487        if let Some(registry) = &self.registry_client {
488            let available = registry.get_all_packages()?;
489            for package in available {
490                self.resolver.add_available_package(package);
491            }
492        }
493        
494        // Create root package
495        let mut root_deps = HashMap::new();
496        for (name, dep) in &manifest.dependencies {
497            let version_req = match dep {
498                Dependency::Version(v) => VersionRequirement::parse(v)?,
499                Dependency::Detailed { version, .. } => {
500                    if let Some(v) = version {
501                        VersionRequirement::parse(v)?
502                    } else {
503                        VersionRequirement::Wildcard
504                    }
505                }
506            };
507            root_deps.insert(name.clone(), version_req);
508        }
509        
510        let root_package = Package {
511            name: manifest.name.clone(),
512            version: Version::parse(&manifest.version)?,
513            dependencies: root_deps,
514        };
515        
516        // Resolve
517        self.resolver.resolve(&root_package)
518    }
519    
520    /// Update dependencies to latest compatible versions
521    pub fn update(&mut self, package: Option<&str>) -> Result<()> {
522        // Load manifest
523        let manifest_path = Path::new("package.pd");
524        let manifest = Self::load_manifest(manifest_path)?;
525        
526        if let Some(pkg_name) = package {
527            println!("📦 Updating {}...", pkg_name);
528        } else {
529            println!("📦 Updating all dependencies...");
530        }
531        
532        // Resolve with latest versions
533        let resolved = self.resolve_dependencies(&manifest)?;
534        
535        // Compare with existing lockfile if any
536        let lockfile_path = Path::new("package.lock");
537        if lockfile_path.exists() {
538            let old_lockfile = Lockfile::load(lockfile_path)?;
539            let mut new_lockfile = Lockfile::new(&manifest.name, &manifest.version);
540            
541            // Add resolved packages to new lockfile
542            for (package_name, version) in &resolved.packages {
543                if package_name == &manifest.name {
544                    continue;
545                }
546                
547                new_lockfile.add_package(LockedPackage {
548                    name: package_name.clone(),
549                    version: version.to_string(),
550                    source: PackageSource::Registry {
551                        url: self.registry_url.clone(),
552                    },
553                    dependencies: vec![], // TODO
554                    checksum: "TODO".to_string(), // TODO
555                });
556            }
557            
558            // Show diff
559            let diff = lockfile::LockfileDiff::compute(&old_lockfile, &new_lockfile);
560            println!("\n{}", diff.display());
561            
562            // Save new lockfile
563            new_lockfile.save(lockfile_path)?;
564        } else {
565            // No existing lockfile, just install
566            self.install()?;
567        }
568        
569        Ok(())
570    }
571    
572    /// Convert manifest to string format
573    pub fn manifest_to_string(manifest: &PackageManifest) -> String {
574        let mut result = String::new();
575
576        // Basic fields
577        result.push_str(&format!("name = \"{}\"\n", manifest.name));
578        result.push_str(&format!("version = \"{}\"\n", manifest.version));
579
580        if let Some(desc) = &manifest.description {
581            result.push_str(&format!("description = \"{}\"\n", desc));
582        }
583
584        if !manifest.authors.is_empty() {
585            result.push_str("authors = [");
586            for (i, author) in manifest.authors.iter().enumerate() {
587                if i > 0 {
588                    result.push_str(", ");
589                }
590                result.push_str(&format!("\"{}\"", author));
591            }
592            result.push_str("]\n");
593        }
594
595        if let Some(license) = &manifest.license {
596            result.push_str(&format!("license = \"{}\"\n", license));
597        }
598
599        // Dependencies
600        if !manifest.dependencies.is_empty() {
601            result.push_str("\n[dependencies]\n");
602            for (name, dep) in &manifest.dependencies {
603                match dep {
604                    Dependency::Version(v) => {
605                        result.push_str(&format!("{} = \"{}\"\n", name, v));
606                    }
607                    Dependency::Detailed { .. } => {
608                        // TODO: Implement detailed format
609                        result.push_str(&format!("{} = \"*\"\n", name));
610                    }
611                }
612            }
613        }
614
615        // Dev dependencies
616        if !manifest.dev_dependencies.is_empty() {
617            result.push_str("\n[dev-dependencies]\n");
618            for (name, dep) in &manifest.dev_dependencies {
619                match dep {
620                    Dependency::Version(v) => {
621                        result.push_str(&format!("{} = \"{}\"\n", name, v));
622                    }
623                    Dependency::Detailed { .. } => {
624                        result.push_str(&format!("{} = \"*\"\n", name));
625                    }
626                }
627            }
628        }
629
630        result
631    }
632
633    /// Add a dependency to the current package
634    pub fn add_dependency(&mut self, name: &str, version: &str, dev: bool) -> Result<()> {
635        // Load current manifest
636        let manifest_path = Path::new("package.pd");
637        let mut manifest = Self::load_manifest(manifest_path)?;
638
639        // Add dependency
640        let dep = Dependency::Version(version.to_string());
641        if dev {
642            manifest.dev_dependencies.insert(name.to_string(), dep);
643            println!("➕ Added dev dependency: {} = \"{}\"", name, version);
644        } else {
645            manifest.dependencies.insert(name.to_string(), dep);
646            println!("➕ Added dependency: {} = \"{}\"", name, version);
647        }
648
649        // Save manifest
650        let content = Self::manifest_to_string(&manifest);
651        fs::write(manifest_path, content).map_err(CompileError::IoError)?;
652
653        Ok(())
654    }
655
656    /// Build the current package
657    pub fn build(&self, release: bool) -> Result<()> {
658        // Load manifest
659        let manifest_path = Path::new("package.pd");
660        let manifest = Self::load_manifest(manifest_path)?;
661
662        println!("🔨 Building package '{}'...", manifest.name);
663
664        // Determine entry point
665        let entry = manifest.main.as_deref().unwrap_or("src/main.pd");
666        let entry_path = Path::new(entry);
667
668        if !entry_path.exists() {
669            return Err(CompileError::Generic(format!(
670                "Entry point '{}' not found",
671                entry
672            )));
673        }
674
675        // Use the driver to compile
676        let driver = crate::Driver::new();
677        // TODO: Add optimization flags for release builds
678
679        // Create build directory
680        let build_dir = Path::new("target").join(if release { "release" } else { "debug" });
681        if !build_dir.exists() {
682            fs::create_dir_all(&build_dir).map_err(CompileError::IoError)?;
683        }
684
685        // Compile the package
686        let output = driver.compile_file(entry_path)?;
687
688        // Move output to target directory
689        let target_name = format!("{}.c", manifest.name);
690        let target_path = build_dir.join(&target_name);
691        fs::rename(&output, &target_path).map_err(CompileError::IoError)?;
692
693        println!("✅ Build complete: {}", target_path.display());
694
695        Ok(())
696    }
697
698    /// Run the current package
699    pub fn run(&self, args: Vec<String>, release: bool) -> Result<()> {
700        // First build
701        self.build(release)?;
702
703        // Load manifest to get package name
704        let manifest = Self::load_manifest(Path::new("package.pd"))?;
705
706        // Find the built executable
707        let build_dir = Path::new("target").join(if release { "release" } else { "debug" });
708        let c_file = build_dir.join(format!("{}.c", manifest.name));
709        let exe_file = build_dir.join(&manifest.name);
710
711        // Compile C to executable
712        println!("🔗 Linking executable...");
713        
714        // Get the runtime library path
715        let runtime_path = PathBuf::from("runtime/palladium_runtime.c");
716        
717        let gcc_output = std::process::Command::new("gcc")
718            .arg(&c_file)
719            .arg(&runtime_path)
720            .arg("-o")
721            .arg(&exe_file)
722            .output()
723            .map_err(|e| CompileError::Generic(format!("Failed to run gcc: {}", e)))?;
724
725        if !gcc_output.status.success() {
726            let stderr = String::from_utf8_lossy(&gcc_output.stderr);
727            return Err(CompileError::Generic(format!(
728                "gcc compilation failed:\n{}",
729                stderr
730            )));
731        }
732
733        // Run the executable
734        println!("🚀 Running '{}'...", manifest.name);
735        println!("─────────────────────────────────────");
736
737        let mut cmd = std::process::Command::new(&exe_file);
738        cmd.args(&args);
739
740        let status = cmd
741            .status()
742            .map_err(|e| CompileError::Generic(format!("Failed to run program: {}", e)))?;
743
744        println!("─────────────────────────────────────");
745
746        if !status.success() {
747            let exit_code = status.code().unwrap_or(-1);
748            println!("⚠️  Program exited with code: {}", exit_code);
749        } else {
750            println!("✅ Program completed successfully");
751        }
752
753        Ok(())
754    }
755}
756
757impl Default for PackageManager {
758    fn default() -> Self {
759        Self::new().expect("Failed to create package manager")
760    }
761}