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