summaryrefslogtreecommitdiff
path: root/crates/build_helper/src/bin/exporter.rs
blob: 4a35de5bac7d4ce7a1b71ec4a87855677ff101b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use std::{collections::VecDeque, env::current_dir};

use colored::Colorize;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("     {} `.cargo/cargo.toml`", "Reading".green().bold());

    let start_time = std::time::Instant::now();

    let target_dir = current_target_dir().expect("Failed to get target directory");
    let publish_dir = current_publish_dir().expect("Failed to get publish directory");
    let publish_binaries = publish_binaries().expect("Failed to get publish binaries");

    // Final, export binaries to publish directory
    let copied_files = export(target_dir, publish_dir, publish_binaries)?;

    let duration = start_time.elapsed();
    println!();
    println!(
        "Done (in {:.1}s) Publish {} {}",
        duration.as_secs_f32(),
        copied_files,
        if copied_files == 1 { "file" } else { "files" }
    );

    Ok(())
}

/// Export binaries to publish directory
fn export(
    target_dir: std::path::PathBuf,
    publish_dir: std::path::PathBuf,
    publish_binaries: Vec<String>,
) -> Result<usize, Box<dyn std::error::Error>> {
    let mut copied_files = 0;

    if publish_dir.exists() {
        std::fs::remove_dir_all(&publish_dir)?;
    }
    std::fs::create_dir_all(&publish_dir)?;

    let mut queue = VecDeque::new();
    queue.push_back(target_dir);

    while let Some(current_dir) = queue.pop_front() {
        let entries = match std::fs::read_dir(&current_dir) {
            Ok(entries) => entries,
            Err(_) => continue,
        };

        for entry in entries.flatten() {
            let path = entry.path();
            let metadata = match entry.metadata() {
                Ok(metadata) => metadata,
                Err(_) => continue,
            };

            if metadata.is_dir() {
                queue.push_back(path);
                continue;
            }

            if let Some(file_name) = path.file_name().and_then(|n| n.to_str())
                && publish_binaries.contains(&file_name.to_string())
            {
                let parent_dir_name = path
                    .parent()
                    .and_then(|p| p.file_name())
                    .and_then(|n| n.to_str())
                    .unwrap_or("");

                let dest_path = publish_dir.join(parent_dir_name).join(file_name);

                if let Some(parent) = dest_path.parent() {
                    std::fs::create_dir_all(parent)?;
                }

                println!(
                    "        {} `{}/{}` ({})",
                    "Copy".green().bold(),
                    parent_dir_name,
                    file_name,
                    path.display()
                );
                std::fs::copy(&path, &dest_path)?;
                copied_files += 1;
            }
        }
    }

    Ok(copied_files)
}

/// Get a target directory from the cargo config
/// Returns the complete path relative to the current directory
fn get_target_dir(section: &str) -> Result<std::path::PathBuf, std::io::Error> {
    let current = current_dir()?;
    let config_file = current.join(".cargo").join("config.toml");
    let config_content = std::fs::read_to_string(&config_file)?;
    let config: toml::Value = toml::from_str(&config_content).map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            format!("Failed to parse config.toml: {}", e),
        )
    })?;
    let target_dir_str = config[section]["target-dir"].as_str().ok_or_else(|| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "target-dir not found or not a string",
        )
    })?;

    Ok(current.join(target_dir_str))
}

/// Get the binaries list from the cargo config
/// Returns a vector of binary names
fn get_array(section: &str, array_name: &str) -> Result<Vec<String>, std::io::Error> {
    let current = current_dir()?;
    let config_file = current.join(".cargo").join("config.toml");
    let config_content = std::fs::read_to_string(&config_file)?;
    let config: toml::Value = toml::from_str(&config_content).map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            format!("Failed to parse config.toml: {}", e),
        )
    })?;

    if let Some(array) = config[section][array_name].as_array() {
        let arr: Vec<String> = array
            .iter()
            .filter_map(|v| v.as_str().map(String::from))
            .collect();
        Ok(arr)
    } else {
        Ok(Vec::new())
    }
}

/// Get the target directory of the current project
/// By reading the `build.target-dir` configuration item in the `.cargo/config.toml` file
/// Returns the complete path relative to the current directory
fn current_target_dir() -> Result<std::path::PathBuf, std::io::Error> {
    get_target_dir("build")
}

/// Get the publish directory of the current project
/// By reading the `publish.target-dir` configuration item in the `.cargo/config.toml` file
/// Returns the complete path relative to the current directory
fn current_publish_dir() -> Result<std::path::PathBuf, std::io::Error> {
    get_target_dir("publish")
}

/// Get the binaries list for publishing
// By reading the `publish.binaries` configuration item in the `.cargo/config.toml` file
// Returns a vector of binary names
fn publish_binaries() -> Result<Vec<String>, std::io::Error> {
    get_array("publish", "binaries")
}