Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / hex.rs
11 kB 330 lines
1use camino::Utf8Path; 2use debug_ignore::DebugIgnore; 3use flate2::read::GzDecoder; 4use futures::future; 5use hexpm::{ApiError, version::Version}; 6use tar::Archive; 7 8use crate::{ 9 Error, Result, 10 io::{FileSystemReader, FileSystemWriter, HttpClient, TarUnpacker}, 11 manifest::{ManifestPackage, ManifestPackageSource}, 12 paths::{self, ProjectPaths}, 13}; 14 15pub const HEXPM_PUBLIC_KEY: &[u8] = b"-----BEGIN PUBLIC KEY----- 16MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApqREcFDt5vV21JVe2QNB 17Edvzk6w36aNFhVGWN5toNJRjRJ6m4hIuG4KaXtDWVLjnvct6MYMfqhC79HAGwyF+ 18IqR6Q6a5bbFSsImgBJwz1oadoVKD6ZNetAuCIK84cjMrEFRkELtEIPNHblCzUkkM 193rS9+DPlnfG8hBvGi6tvQIuZmXGCxF/73hU0/MyGhbmEjIKRtG6b0sJYKelRLTPW 20XgK7s5pESgiwf2YC/2MGDXjAJfpfCd0RpLdvd4eRiXtVlE9qO9bND94E7PgQ/xqZ 21J1i2xWFndWa6nfFnRxZmCStCOZWYYPlaxr+FZceFbpMwzTNs4g3d4tLNUcbKAIH4 220wIDAQAB 23-----END PUBLIC KEY----- 24"; 25 26fn key_name(hostname: &str) -> String { 27 format!("gleam-{hostname}") 28} 29 30pub async fn publish_package<Http: HttpClient>( 31 release_tarball: Vec<u8>, 32 version: String, 33 api_key: &str, 34 config: &hexpm::Config, 35 replace: bool, 36 http: &Http, 37) -> Result<()> { 38 tracing::info!("Publishing package, replace: {}", replace); 39 let request = hexpm::api_publish_package_request(release_tarball, api_key, config, replace); 40 let response = http.send(request).await?; 41 hexpm::api_publish_package_response(response).map_err(|e| match e { 42 ApiError::NotReplacing => Error::HexPublishReplaceRequired { version }, 43 err => Error::hex(err), 44 }) 45} 46 47pub async fn transfer_owner<Http: HttpClient>( 48 api_key: &str, 49 package_name: String, 50 new_owner_username_or_email: String, 51 config: &hexpm::Config, 52 http: &Http, 53) -> Result<()> { 54 tracing::info!( 55 "Transferring ownership of `{}` to {}", 56 package_name, 57 new_owner_username_or_email 58 ); 59 let request = hexpm::api_transfer_owner_request( 60 &package_name, 61 &new_owner_username_or_email, 62 api_key, 63 config, 64 ); 65 let response = http.send(request).await?; 66 hexpm::api_transfer_owner_response(response).map_err(Error::hex) 67} 68 69#[derive(Debug, strum::EnumString, strum::VariantNames, Clone, Copy, PartialEq, Eq)] 70#[strum(serialize_all = "lowercase")] 71pub enum RetirementReason { 72 Other, 73 Invalid, 74 Security, 75 Deprecated, 76 Renamed, 77} 78 79impl RetirementReason { 80 pub fn to_library_enum(&self) -> hexpm::RetirementReason { 81 match self { 82 RetirementReason::Other => hexpm::RetirementReason::Other, 83 RetirementReason::Invalid => hexpm::RetirementReason::Invalid, 84 RetirementReason::Security => hexpm::RetirementReason::Security, 85 RetirementReason::Deprecated => hexpm::RetirementReason::Deprecated, 86 RetirementReason::Renamed => hexpm::RetirementReason::Renamed, 87 } 88 } 89} 90 91pub async fn retire_release<Http: HttpClient>( 92 package: &str, 93 version: &str, 94 reason: RetirementReason, 95 message: Option<&str>, 96 api_key: &str, 97 config: &hexpm::Config, 98 http: &Http, 99) -> Result<()> { 100 tracing::info!(package=%package, version=%version, "retiring_hex_release"); 101 let request = hexpm::api_retire_release_request( 102 package, 103 version, 104 reason.to_library_enum(), 105 message, 106 api_key, 107 config, 108 ); 109 let response = http.send(request).await?; 110 hexpm::api_retire_release_response(response).map_err(Error::hex) 111} 112 113pub async fn unretire_release<Http: HttpClient>( 114 package: &str, 115 version: &str, 116 api_key: &str, 117 config: &hexpm::Config, 118 http: &Http, 119) -> Result<()> { 120 tracing::info!(package=%package, version=%version, "retiring_hex_release"); 121 let request = hexpm::api_unretire_release_request(package, version, api_key, config); 122 let response = http.send(request).await?; 123 hexpm::api_unretire_release_response(response).map_err(Error::hex) 124} 125 126pub async fn create_api_key<Http: HttpClient>( 127 hostname: &str, 128 username: &str, 129 password: &str, 130 config: &hexpm::Config, 131 http: &Http, 132) -> Result<String> { 133 tracing::info!("Creating API key with Hex"); 134 let request = 135 hexpm::api_create_api_key_request(username, password, &key_name(hostname), config); 136 let response = http.send(request).await?; 137 hexpm::api_create_api_key_response(response).map_err(Error::hex) 138} 139 140pub async fn remove_api_key<Http: HttpClient>( 141 hostname: &str, 142 config: &hexpm::Config, 143 auth_key: &str, 144 http: &Http, 145) -> Result<()> { 146 tracing::info!("Deleting API key from Hex"); 147 let request = hexpm::api_remove_api_key_request(&key_name(hostname), auth_key, config); 148 let response = http.send(request).await?; 149 hexpm::api_remove_api_key_response(response).map_err(Error::hex) 150} 151 152#[derive(Debug)] 153pub struct Downloader { 154 fs_reader: DebugIgnore<Box<dyn FileSystemReader>>, 155 fs_writer: DebugIgnore<Box<dyn FileSystemWriter>>, 156 http: DebugIgnore<Box<dyn HttpClient>>, 157 untar: DebugIgnore<Box<dyn TarUnpacker>>, 158 hex_config: hexpm::Config, 159 paths: ProjectPaths, 160} 161 162impl Downloader { 163 pub fn new( 164 fs_reader: Box<dyn FileSystemReader>, 165 fs_writer: Box<dyn FileSystemWriter>, 166 http: Box<dyn HttpClient>, 167 untar: Box<dyn TarUnpacker>, 168 paths: ProjectPaths, 169 ) -> Self { 170 Self { 171 fs_reader: DebugIgnore(fs_reader), 172 fs_writer: DebugIgnore(fs_writer), 173 http: DebugIgnore(http), 174 untar: DebugIgnore(untar), 175 hex_config: hexpm::Config::new(), 176 paths, 177 } 178 } 179 180 pub async fn ensure_package_downloaded( 181 &self, 182 package: &ManifestPackage, 183 ) -> Result<bool, Error> { 184 let outer_checksum = match &package.source { 185 ManifestPackageSource::Hex { outer_checksum } => outer_checksum, 186 _ => { 187 panic!("Attempt to download non-hex package from hex") 188 } 189 }; 190 191 let tarball_path = paths::global_package_cache_package_tarball( 192 &package.name, 193 &package.version.to_string(), 194 ); 195 if self.fs_reader.is_file(&tarball_path) { 196 tracing::info!( 197 package = package.name.as_str(), 198 version = %package.version, 199 "package_in_cache" 200 ); 201 return Ok(false); 202 } 203 tracing::info!( 204 package = &package.name.as_str(), 205 version = %package.version, 206 "downloading_package_to_cache" 207 ); 208 209 let request = hexpm::repository_get_package_tarball_request( 210 &package.name, 211 &package.version.to_string(), 212 None, 213 &self.hex_config, 214 ); 215 let response = self.http.send(request).await?; 216 217 let tarball = hexpm::repository_get_package_tarball_response(response, &outer_checksum.0) 218 .map_err(|error| Error::DownloadPackageError { 219 package_name: package.name.to_string(), 220 package_version: package.version.to_string(), 221 error: error.to_string(), 222 })?; 223 self.fs_writer.write_bytes(&tarball_path, &tarball)?; 224 Ok(true) 225 } 226 227 pub async fn ensure_package_in_build_directory( 228 &self, 229 package: &ManifestPackage, 230 ) -> Result<bool> { 231 let _ = self.ensure_package_downloaded(package).await?; 232 self.extract_package_from_cache(&package.name, &package.version) 233 } 234 235 // It would be really nice if this was async but the library is sync 236 pub fn extract_package_from_cache(&self, name: &str, version: &Version) -> Result<bool> { 237 let contents_path = Utf8Path::new("contents.tar.gz"); 238 let destination = self.paths.build_packages_package(name); 239 240 // If the directory already exists then there's nothing for us to do 241 if self.fs_reader.is_directory(&destination) { 242 tracing::info!(package = name, "Package already in build directory"); 243 return Ok(false); 244 } 245 246 tracing::info!(package = name, "writing_package_to_target"); 247 let tarball = paths::global_package_cache_package_tarball(name, &version.to_string()); 248 let reader = self.fs_reader.reader(&tarball)?; 249 let mut archive = Archive::new(reader); 250 251 // Find the source code from within the outer tarball 252 for entry in self.untar.entries(&mut archive)? { 253 let file = entry.map_err(Error::expand_tar)?; 254 255 let path = file.header().path().map_err(Error::expand_tar)?; 256 if path.as_ref() == contents_path { 257 // Expand this inner source code and write to the file system 258 let archive = Archive::new(GzDecoder::new(file)); 259 let result = self.untar.unpack(&destination, archive); 260 261 // If we failed to expand the tarball remove any source code 262 // that was partially written so that we don't mistakenly think 263 // the operation succeeded next time we run. 264 return match result { 265 Ok(()) => Ok(true), 266 Err(err) => { 267 self.fs_writer.delete_directory(&destination)?; 268 Err(err) 269 } 270 }; 271 } 272 } 273 274 Err(Error::ExpandTar { 275 error: "Unable to locate Hex package contents.tar.gz".into(), 276 }) 277 } 278 279 pub async fn download_hex_packages<'a, Packages: Iterator<Item = &'a ManifestPackage>>( 280 &self, 281 packages: Packages, 282 project_name: &str, 283 ) -> Result<()> { 284 let futures = packages 285 .filter(|package| project_name != package.name) 286 .map(|package| self.ensure_package_in_build_directory(package)); 287 288 // Run the futures to download the packages concurrently 289 let results = future::join_all(futures).await; 290 291 // Count the number of packages downloaded while checking for errors 292 for result in results { 293 let _ = result?; 294 } 295 Ok(()) 296 } 297} 298 299pub async fn publish_documentation<Http: HttpClient>( 300 name: &str, 301 version: &Version, 302 archive: Vec<u8>, 303 api_key: &str, 304 config: &hexpm::Config, 305 http: &Http, 306) -> Result<()> { 307 tracing::info!("publishing_documentation"); 308 let request = 309 hexpm::api_publish_docs_request(name, &version.to_string(), archive, api_key, config) 310 .map_err(Error::hex)?; 311 let response = http.send(request).await?; 312 hexpm::api_publish_docs_response(response).map_err(Error::hex) 313} 314 315pub async fn get_package_release<Http: HttpClient>( 316 name: &str, 317 version: &Version, 318 config: &hexpm::Config, 319 http: &Http, 320) -> Result<hexpm::Release<hexpm::ReleaseMeta>> { 321 let version = version.to_string(); 322 tracing::info!( 323 name = name, 324 version = version.as_str(), 325 "looking_up_package_release" 326 ); 327 let request = hexpm::api_get_package_release_request(name, &version, None, config); 328 let response = http.send(request).await?; 329 hexpm::api_get_package_release_response(response).map_err(Error::hex) 330}