pub mod cache;
mod common;
pub mod hash;
pub mod http;
mod index;
mod manifest;
mod msvc;
pub mod progress;
mod sdk;
mod traits;
#[cfg(test)]
mod common_tests;
use std::collections::HashSet;
use std::path::PathBuf;
use crate::error::Result;
use crate::installer::InstallInfo;
use crate::version::Architecture;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum MsvcComponent {
Spectre,
Mfc,
Atl,
Asan,
Uwp,
Cli,
Modules,
Redist,
Custom(String),
}
impl std::fmt::Display for MsvcComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MsvcComponent::Spectre => write!(f, "spectre"),
MsvcComponent::Mfc => write!(f, "mfc"),
MsvcComponent::Atl => write!(f, "atl"),
MsvcComponent::Asan => write!(f, "asan"),
MsvcComponent::Uwp => write!(f, "uwp"),
MsvcComponent::Cli => write!(f, "cli"),
MsvcComponent::Modules => write!(f, "modules"),
MsvcComponent::Redist => write!(f, "redist"),
MsvcComponent::Custom(s) => write!(f, "custom:{}", s),
}
}
}
impl std::str::FromStr for MsvcComponent {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"spectre" => Ok(MsvcComponent::Spectre),
"mfc" => Ok(MsvcComponent::Mfc),
"atl" => Ok(MsvcComponent::Atl),
"asan" => Ok(MsvcComponent::Asan),
"uwp" | "store" => Ok(MsvcComponent::Uwp),
"cli" | "c++/cli" => Ok(MsvcComponent::Cli),
"modules" => Ok(MsvcComponent::Modules),
"redist" | "redistributable" => Ok(MsvcComponent::Redist),
other => {
if let Some(pattern) = other.strip_prefix("custom:") {
Ok(MsvcComponent::Custom(pattern.to_string()))
} else {
Err(format!(
"Unknown component '{}'. Valid: spectre, mfc, atl, asan, uwp, cli, modules, redist, custom:<pattern>",
s
))
}
}
}
}
}
pub use common::CommonDownloader;
pub use hash::{compute_file_hash, compute_hash, hashes_match};
pub use http::{
create_http_client, create_http_client_with_config, tls_backend_name, HttpClientConfig,
};
pub use index::{DownloadIndex, DownloadStatus, IndexEntry};
pub use manifest::{ChannelManifest, Package, PackagePayload, VsManifest};
pub use msvc::MsvcDownloader;
pub use progress::{
BoxedProgressHandler, IndicatifProgressHandler, NoopProgressHandler, ProgressHandler,
};
pub use sdk::SdkDownloader;
pub use traits::{
BoxedCacheManager, CacheManager, ComponentDownloader, ComponentType, FileSystemCacheManager,
};
#[derive(Clone)]
pub struct DownloadOptions {
pub msvc_version: Option<String>,
pub sdk_version: Option<String>,
pub target_dir: PathBuf,
pub arch: Architecture,
pub host_arch: Option<Architecture>,
pub verify_hashes: bool,
pub parallel_downloads: usize,
pub http_client: Option<reqwest::Client>,
pub progress_handler: Option<BoxedProgressHandler>,
pub cache_manager: Option<BoxedCacheManager>,
pub dry_run: bool,
pub include_components: HashSet<MsvcComponent>,
pub exclude_patterns: Vec<String>,
}
impl std::fmt::Debug for DownloadOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DownloadOptions")
.field("msvc_version", &self.msvc_version)
.field("sdk_version", &self.sdk_version)
.field("target_dir", &self.target_dir)
.field("arch", &self.arch)
.field("host_arch", &self.host_arch)
.field("verify_hashes", &self.verify_hashes)
.field("parallel_downloads", &self.parallel_downloads)
.field("http_client", &self.http_client.is_some())
.field("progress_handler", &self.progress_handler.is_some())
.field("cache_manager", &self.cache_manager.is_some())
.field("dry_run", &self.dry_run)
.field("include_components", &self.include_components)
.field("exclude_patterns", &self.exclude_patterns)
.finish()
}
}
impl Default for DownloadOptions {
fn default() -> Self {
use crate::constants::download::DEFAULT_PARALLEL_DOWNLOADS;
let target_dir = std::env::var("MSVC_KIT_INSTALL_DIR")
.ok()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("msvc-kit"));
let parallel_downloads = std::env::var("MSVC_KIT_PARALLEL_DOWNLOADS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_PARALLEL_DOWNLOADS);
let verify_hashes = std::env::var("MSVC_KIT_VERIFY_HASHES")
.ok()
.map(|s| !matches!(s.to_lowercase().as_str(), "0" | "false" | "no"))
.unwrap_or(true);
let dry_run = std::env::var("MSVC_KIT_DRY_RUN")
.ok()
.map(|s| matches!(s.to_lowercase().as_str(), "1" | "true" | "yes"))
.unwrap_or(false);
let include_components = std::env::var("MSVC_KIT_INCLUDE_COMPONENTS")
.ok()
.map(|s| {
s.split(',')
.filter_map(|c| c.trim().parse::<MsvcComponent>().ok())
.collect()
})
.unwrap_or_default();
let exclude_patterns = std::env::var("MSVC_KIT_EXCLUDE_PATTERNS")
.ok()
.map(|s| {
s.split(',')
.map(|p| p.trim().to_string())
.filter(|p| !p.is_empty())
.collect()
})
.unwrap_or_default();
Self {
msvc_version: std::env::var("MSVC_KIT_MSVC_VERSION").ok(),
sdk_version: std::env::var("MSVC_KIT_SDK_VERSION").ok(),
target_dir,
arch: Architecture::host(),
host_arch: None,
verify_hashes,
parallel_downloads,
http_client: None,
progress_handler: None,
cache_manager: None,
dry_run,
include_components,
exclude_patterns,
}
}
}
impl DownloadOptions {
pub fn builder() -> DownloadOptionsBuilder {
DownloadOptionsBuilder::default()
}
}
#[derive(Default)]
pub struct DownloadOptionsBuilder {
options: DownloadOptions,
}
impl DownloadOptionsBuilder {
pub fn msvc_version(mut self, version: impl Into<String>) -> Self {
self.options.msvc_version = Some(version.into());
self
}
pub fn sdk_version(mut self, version: impl Into<String>) -> Self {
self.options.sdk_version = Some(version.into());
self
}
pub fn target_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.options.target_dir = dir.into();
self
}
pub fn arch(mut self, arch: Architecture) -> Self {
self.options.arch = arch;
self
}
pub fn host_arch(mut self, arch: Architecture) -> Self {
self.options.host_arch = Some(arch);
self
}
pub fn verify_hashes(mut self, verify: bool) -> Self {
self.options.verify_hashes = verify;
self
}
pub fn parallel_downloads(mut self, count: usize) -> Self {
self.options.parallel_downloads = count;
self
}
pub fn http_client(mut self, client: reqwest::Client) -> Self {
self.options.http_client = Some(client);
self
}
pub fn progress_handler(mut self, handler: BoxedProgressHandler) -> Self {
self.options.progress_handler = Some(handler);
self
}
pub fn cache_manager(mut self, manager: BoxedCacheManager) -> Self {
self.options.cache_manager = Some(manager);
self
}
pub fn dry_run(mut self, dry_run: bool) -> Self {
self.options.dry_run = dry_run;
self
}
pub fn include_component(mut self, component: MsvcComponent) -> Self {
self.options.include_components.insert(component);
self
}
pub fn include_components(
mut self,
components: impl IntoIterator<Item = MsvcComponent>,
) -> Self {
self.options.include_components.extend(components);
self
}
pub fn exclude_pattern(mut self, pattern: impl Into<String>) -> Self {
self.options.exclude_patterns.push(pattern.into());
self
}
pub fn build(self) -> DownloadOptions {
self.options
}
}
#[derive(Debug, Clone)]
pub struct DownloadPreview {
pub component: String,
pub version: String,
pub package_count: usize,
pub file_count: usize,
pub total_size: u64,
pub packages: Vec<PackagePreview>,
}
#[derive(Debug, Clone)]
pub struct PackagePreview {
pub id: String,
pub version: String,
pub file_count: usize,
pub size: u64,
}
impl DownloadPreview {
pub fn format(&self) -> String {
let size_str = humansize::format_size(self.total_size, humansize::BINARY);
format!(
"{} v{}: {} packages, {} files, {}",
self.component, self.version, self.package_count, self.file_count, size_str
)
}
}
pub async fn download_msvc(options: &DownloadOptions) -> Result<InstallInfo> {
let downloader = MsvcDownloader::new(options.clone());
downloader.download().await
}
pub async fn download_sdk(options: &DownloadOptions) -> Result<InstallInfo> {
let downloader = SdkDownloader::new(options.clone());
downloader.download().await
}
pub async fn download_all(options: &DownloadOptions) -> Result<(InstallInfo, InstallInfo)> {
let (msvc_result, sdk_result) = tokio::join!(download_msvc(options), download_sdk(options));
let msvc_info = msvc_result?;
let sdk_info = sdk_result?;
Ok((msvc_info, sdk_info))
}
#[derive(Debug, Clone)]
pub struct AvailableVersions {
pub msvc_versions: Vec<String>,
pub sdk_versions: Vec<String>,
pub latest_msvc: Option<String>,
pub latest_sdk: Option<String>,
}
pub async fn list_available_versions() -> Result<AvailableVersions> {
let manifest = VsManifest::fetch().await?;
Ok(AvailableVersions {
msvc_versions: manifest.list_msvc_versions(),
sdk_versions: manifest.list_sdk_versions(),
latest_msvc: manifest.get_latest_msvc_version(),
latest_sdk: manifest.get_latest_sdk_version(),
})
}