era_compiler_downloader/
lib.rs1pub(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#[derive(Debug)]
24pub struct Downloader {
25 http_client: reqwest::blocking::Client,
27 compiler_list: Option<CompilerList>,
29}
30
31impl Downloader {
32 pub fn new(http_client: reqwest::blocking::Client) -> Self {
36 Self {
37 http_client,
38 compiler_list: None,
39 }
40 }
41
42 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}