1
//!
2
//! Package command for creating release distributions.
3
//!
4

            
5
use duct::cmd;
6
use std::env;
7
use std::error::Error;
8
use std::fs::copy;
9
use std::fs::create_dir_all;
10
use std::path::PathBuf;
11

            
12
/// Builds the project in release mode and packages specified binaries into a
13
/// newly created 'package' directory.
14
pub fn package() -> Result<(), Box<dyn Error>> {
15
    // Get the workspace root directory
16
    let workspace_root = env::current_dir()?;
17

            
18
    // Precondition: Ensure we're in a valid Rust workspace
19
    debug_assert!(
20
        workspace_root.join("Cargo.toml").exists(),
21
        "Must be run from workspace root containing Cargo.toml"
22
    );
23

            
24
    println!("=== Creating package directory ===");
25

            
26
    // Create package directory for distribution artifacts
27
    let package_dir = workspace_root.join("package");
28
    create_dir_all(&package_dir)?;
29

            
30
    println!("=== Building and copying release binaries ===");
31

            
32
    // Mapping from workspace paths to their binaries
33
    let workspace_binaries = [
34
        (
35
            workspace_root.clone(),
36
            vec!["merc-lts", "merc-rewrite", "merc-vpg", "merc-sym"],
37
        ),
38
        (workspace_root.join("tools/gui"), vec!["merc-ltsgraph"]),
39
        (workspace_root.join("tools/mcrl2"), vec!["merc-pbes", "merc-lps"]),
40
    ];
41

            
42
    // Build all workspaces in release mode
43
    for (workspace_path, binaries) in &workspace_binaries {
44
        cmd!("cargo", "build", "--release").dir(workspace_path).run()?;
45

            
46
        let target_release_dir = PathBuf::new().join("target").join("release");
47

            
48
        for binary_name in binaries {
49
            let source_path = if cfg!(windows) {
50
                target_release_dir.join(format!("{binary_name}.exe"))
51
            } else {
52
                target_release_dir.join(binary_name)
53
            };
54

            
55
            let dest_path = if cfg!(windows) {
56
                package_dir.join(format!("{binary_name}.exe"))
57
            } else {
58
                package_dir.join(binary_name)
59
            };
60

            
61
            // Precondition: Binary must exist after successful build
62
            debug_assert!(
63
                source_path.exists(),
64
                "Binary {binary_name} should exist after cargo build --release"
65
            );
66

            
67
            copy(&source_path, &dest_path)?;
68
            println!("Copied {binary_name} to package directory");
69
        }
70
    }
71

            
72
    println!("=== Package creation completed ===");
73
    println!("Package directory: {}", package_dir.display());
74

            
75
    // Postcondition: All required binaries should be in package directory
76
    let all_binaries: Vec<&str> = workspace_binaries
77
        .iter()
78
        .flat_map(|(_, bins)| bins.iter().copied())
79
        .collect();
80

            
81
    assert!(
82
        all_binaries.iter().all(|name| {
83
            let expected_path = if cfg!(windows) {
84
                package_dir.join(format!("{name}.exe"))
85
            } else {
86
                package_dir.join(name)
87
            };
88
            expected_path.exists()
89
        }),
90
        "All binaries should be copied to package directory"
91
    );
92

            
93
    // Add the LICENSE to the package
94
    let license_src = workspace_root.join("LICENSE");
95
    let license_dest = package_dir.join("LICENSE");
96
    copy(&license_src, &license_dest)?;
97

            
98
    // Add KaHyPar configuration used by the symbolic crate
99
    let kahypar_ini_src = workspace_root.join("crates/symbolic/data/kahypar.ini");
100
    let kahypar_ini_dest = package_dir.join("kahypar.ini");
101
    copy(&kahypar_ini_src, &kahypar_ini_dest)?;
102

            
103
    Ok(())
104
}