Skip to main content

era_compiler_downloader/
lib.rs

1//!
2//! The compiler downloader config.
3//!
4
5pub(crate) mod config;
6
7#[cfg(target_family = "unix")]
8use std::os::unix::fs::PermissionsExt;
9
10use std::path::Path;
11use std::path::PathBuf;
12use std::str::FromStr;
13
14use colored::Colorize;
15
16use self::config::compiler_list::CompilerList;
17use self::config::executable::protocol::Protocol;
18use self::config::Config;
19
20///
21/// The compiler downloader.
22///
23#[derive(Debug)]
24pub struct Downloader {
25    /// The `reqwest` HTTP client.
26    http_client: reqwest::blocking::Client,
27    /// The compiler-bin JSON list metadata.
28    compiler_list: Option<CompilerList>,
29}
30
31impl Downloader {
32    ///
33    /// A shortcut constructor.
34    ///
35    pub fn new(http_client: reqwest::blocking::Client) -> Self {
36        Self {
37            http_client,
38            compiler_list: None,
39        }
40    }
41
42    ///
43    /// Downloads the compilers described in the config.
44    ///
45    pub fn download(mut self, config_path: &Path) -> anyhow::Result<Config> {
46        let config_file = std::fs::File::open(config_path).map_err(|error| {
47            anyhow::anyhow!("Executable downloader config {config_path:?} opening error: {error}")
48        })?;
49        let config_reader = std::io::BufReader::new(config_file);
50        let config: Config = serde_json::from_reader(config_reader).map_err(|error| {
51            anyhow::anyhow!("Executable downloader config {config_path:?} parsing error: {error}")
52        })?;
53
54        let platform_directory = config.get_remote_platform_directory()?;
55
56        for (version, executable) in config.executables.iter() {
57            if !executable.is_enabled {
58                continue;
59            }
60
61            let mut source_path = executable
62                .source
63                .replace("${PLATFORM}", platform_directory.as_str())
64                .replace("${VERSION}", version.as_str());
65
66            let destination_path = executable
67                .destination
68                .replace("${VERSION}", version.as_str());
69            let destination_path = PathBuf::from_str(
70                format!("{destination_path}{}", std::env::consts::EXE_SUFFIX).as_str(),
71            )
72            .map_err(|_| {
73                anyhow::anyhow!("Executable `{destination_path}` destination is invalid")
74            })?;
75
76            let data = match executable.protocol {
77                Protocol::File => {
78                    source_path += std::env::consts::EXE_SUFFIX;
79                    if source_path == destination_path.to_string_lossy() {
80                        println!(
81                            "    {} executable {destination_path:?}. The source and destination are the same.",
82                            "Skipping".bright_green().bold(),
83                        );
84                        continue;
85                    }
86
87                    println!(
88                        "     {} executable `{source_path}` => {destination_path:?}",
89                        "Copying".bright_green().bold(),
90                    );
91
92                    std::fs::copy(source_path.as_str(), executable.destination.as_str()).map_err(
93                        |error| {
94                            anyhow::anyhow!("Executable {source_path:?} copying error: {error}",)
95                        },
96                    )?;
97                    continue;
98                }
99                Protocol::HTTPS => {
100                    source_path += std::env::consts::EXE_SUFFIX;
101
102                    if destination_path.exists() {
103                        println!(
104                            "    {} executable {destination_path:?}. Already exists.",
105                            "Skipping".bright_green().bold(),
106                        );
107                        continue;
108                    }
109
110                    let source_url =
111                        reqwest::Url::from_str(source_path.as_str()).expect("Always valid");
112                    println!(
113                        " {} executable `{source_url}` => {destination_path:?}",
114                        "Downloading".bright_green().bold(),
115                    );
116                    self.http_client.get(source_url).send()?.bytes()?
117                }
118                Protocol::CompilerBinList => {
119                    if destination_path.exists() {
120                        println!(
121                            "    {} executable {destination_path:?}. Already exists.",
122                            "Skipping".bright_green().bold(),
123                        );
124                        continue;
125                    }
126
127                    let compiler_list_path = PathBuf::from(source_path.as_str());
128                    let compiler_list = self.compiler_list.get_or_insert_with(|| {
129                        CompilerList::try_from(compiler_list_path.as_path())
130                            .expect("compiler-bin JSON list downloading error")
131                    });
132                    if compiler_list.releases.is_empty() {
133                        return Ok(config);
134                    }
135
136                    let source_executable_name =
137                        match compiler_list.releases.get(version.to_string().as_str()) {
138                            Some(source_executable_name) => source_executable_name,
139                            None => anyhow::bail!(
140                            "Executable for version v{version} not found in the compiler JSON list",
141                        ),
142                        };
143                    #[cfg(target_os = "windows")]
144                    if !source_executable_name.ends_with(std::env::consts::EXE_SUFFIX) {
145                        println!(
146                            "    {} downloading {source_executable_name:?}. Not an executable file.",
147                            "Skipping".bright_green().bold(),
148                        );
149                        continue;
150                    }
151                    let mut source_path = compiler_list_path;
152                    source_path.pop();
153                    source_path.push(source_executable_name);
154
155                    let source_url =
156                        reqwest::Url::from_str(source_path.to_str().expect("Always valid"))
157                            .expect("Always valid");
158                    println!(
159                        " {} executable `{source_url}` => {destination_path:?}",
160                        "Downloading".bright_green().bold(),
161                    );
162                    self.http_client.get(source_url).send()?.bytes()?
163                }
164            };
165
166            let mut destination_folder = destination_path.clone();
167            destination_folder.pop();
168            std::fs::create_dir_all(destination_folder)?;
169
170            std::fs::write(&destination_path, data)?;
171
172            #[cfg(target_family = "unix")]
173            std::fs::set_permissions(&destination_path, std::fs::Permissions::from_mode(0o755))?;
174        }
175
176        Ok(config)
177    }
178}