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

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

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

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

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

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

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

            
38
    // Build all workspaces in release mode
39
    for (workspace_path, binaries) in &workspace_binaries {
40
        cmd!("cargo", "build", "--release").dir(workspace_path).run()?;
41

            
42
        let target_release_dir = workspace_path.join("target").join("release");
43

            
44
        for binary_name in binaries {
45
            let source_path = if cfg!(windows) {
46
                target_release_dir.join(format!("{binary_name}.exe"))
47
            } else {
48
                target_release_dir.join(binary_name)
49
            };
50

            
51
            let dest_path = if cfg!(windows) {
52
                package_dir.join(format!("{binary_name}.exe"))
53
            } else {
54
                package_dir.join(binary_name)
55
            };
56

            
57
            // Precondition: Binary must exist after successful build
58
            debug_assert!(
59
                source_path.exists(),
60
                "Binary {binary_name} should exist after cargo build --release"
61
            );
62

            
63
            copy(&source_path, &dest_path)?;
64
            println!("Copied {binary_name} to package directory");
65
        }
66
    }
67

            
68
    println!("=== Package creation completed ===");
69
    println!("Package directory: {}", package_dir.display());
70

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

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

            
89
    // Add the LICENSE to the package
90
    let license_src = workspace_root.join("LICENSE");
91
    let license_dest = package_dir.join("LICENSE");
92
    copy(&license_src, &license_dest)?;
93

            
94
    Ok(())
95
}