Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: option to download vc/ucrt debug binaries #148

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ impl Ctx {
payloads: Vec<WorkItem>,
crt_version: String,
sdk_version: String,
vcrd_version: Option<String>,
arches: u32,
variants: u32,
ops: crate::Ops,
Expand Down Expand Up @@ -376,6 +377,7 @@ impl Ctx {
map.as_ref()
.filter(|_m| !matches!(ops, crate::Ops::Minimize(_))),
&sdk_version,
vcrd_version.clone(),
arches,
variants,
)
Expand Down
92 changes: 91 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

use anyhow::{Context as _, Error};
pub use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use manifest::{Chip, ManifestItem};
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
fmt::{self, Debug},
};

mod ctx;
Expand Down Expand Up @@ -183,11 +184,13 @@ pub enum PayloadKind {
SdkLibs,
SdkStoreLibs,
Ucrt,
VcrDebug,
}

pub struct PrunedPackageList {
pub crt_version: String,
pub sdk_version: String,
pub vcr_version: Option<String>,
pub payloads: Vec<Payload>,
}

Expand All @@ -197,6 +200,7 @@ pub fn prune_pkg_list(
arches: u32,
variants: u32,
include_atl: bool,
include_debug_runtime: bool,
sdk_version: Option<String>,
crt_version: Option<String>,
) -> Result<PrunedPackageList, Error> {
Expand All @@ -215,13 +219,96 @@ pub fn prune_pkg_list(
)?;
let sdk_version = get_sdk(pkgs, arches, sdk_version, &mut payloads)?;

let vcr_version = include_debug_runtime
.then(|| get_vcrd(pkgs, arches, &mut payloads).ok())
.flatten();

Ok(PrunedPackageList {
crt_version,
sdk_version,
vcr_version,
payloads,
})
}

fn get_vcrd(
pkgs: &BTreeMap<String, manifest::ManifestItem>,
arches: u32,
pruned: &mut Vec<Payload>,
) -> Result<String, Error> {
let mut vcrd_version: Option<String> = None;

// determine target architecture for the vcrd package
fn determine_vcrd_arch(
manifest_item: &ManifestItem,
payload: &manifest::Payload,
) -> Option<Arch> {
if payload.file_name.contains("arm64") {
Some(Arch::Aarch64)
} else if payload.file_name.contains("arm") {
Some(Arch::Aarch)
} else {
[(Chip::X64, Arch::X86_64), (Chip::X86, Arch::X86)]
.iter()
.find_map(|(s, arch)| manifest_item.chip.unwrap().eq(s).then_some(*arch))
}
}

pkgs.iter()
// get all vc debug runtime items (key is renamed by manifest::get_package_manifest)
.filter(|(key, _)| {
key.starts_with("Microsoft.VisualCpp.RuntimeDebug")
|| key.starts_with("Microsoft.Windows.UniversalCRT.Tools.Msi")
})
// get the first payload (which contains the MSI)
.filter_map(|(_, manifest_item)| {
manifest_item
.payloads
.first()
.map(|payload| (manifest_item, payload))
})
.for_each(|(manifest_item, payload)| {
let target_arch = determine_vcrd_arch(manifest_item, payload);

// skip if target arch is not requested
if target_arch
.is_none_or(|target_arch| !Arch::iter(arches).any(|arch| arch.eq(&target_arch)))
{
return;
}

let filename_prefix = if manifest_item.id.contains("UniversalCRT") {
"Microsoft.UCRT.Debug"
} else {
// set vcrd_version from manifest item
vcrd_version = Some(manifest_item.version.clone());
"Microsoft.VC.Runtime.Debug"
};

pruned.push(Payload {
filename: format_args!(
"{}.{}.{}.msi",
filename_prefix,
manifest_item.version,
target_arch.unwrap().as_str()
)
.to_string()
.into(),
sha256: payload.sha256.clone(),
url: payload.url.clone(),
size: payload.size,
kind: PayloadKind::VcrDebug,
target_arch,
variant: Some(Variant::Desktop),
install_size: (manifest_item.payloads.len() == 1)
.then_some(manifest_item)
.and_then(|mi| mi.install_sizes.as_ref().and_then(|is| is.target_drive)),
});
});

vcrd_version.with_context(|| "failed to find vcr debug version")
}

fn get_crt(
pkgs: &BTreeMap<String, manifest::ManifestItem>,
arches: u32,
Expand Down Expand Up @@ -736,12 +823,14 @@ fn get_sdk(
pub struct Map {
pub crt: Block,
pub sdk: Block,
pub vcrd: Block,
}

impl Map {
fn clear(&mut self) {
self.crt.clear();
self.sdk.clear();
self.vcrd.clear();
}
}

Expand All @@ -764,6 +853,7 @@ pub enum SectionKind {
CrtLib,
SdkHeader,
SdkLib,
VcrDebug,
}

#[derive(serde::Serialize, serde::Deserialize, Default)]
Expand Down
17 changes: 17 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ pub struct Args {
/// Whether to include the Active Template Library (ATL) in the installation
#[arg(long)]
include_atl: bool,
/// Whether to include VCR debug libraries
#[arg(long)]
include_debug_runtime: bool,
/// Specifies a timeout for how long a single download is allowed to take.
#[arg(short, long, value_parser = parse_duration, default_value = "60s")]
timeout: Duration,
Expand Down Expand Up @@ -321,6 +324,7 @@ fn main() -> Result<(), Error> {
arches,
variants,
args.include_atl,
args.include_debug_runtime,
args.sdk_version,
args.crt_version,
)?;
Expand Down Expand Up @@ -412,6 +416,18 @@ fn main() -> Result<(), Error> {
}
PayloadKind::SdkStoreLibs => "SDK.libs.store.all".to_owned(),
PayloadKind::Ucrt => "SDK.ucrt.all".to_owned(),
PayloadKind::VcrDebug => {
let prefix = match pay.filename.to_string().contains("UCRT") {
true => "UCRT.Debug",
false => "VC.Runtime.Debug"
};

format!(
"{}.{}",
prefix,
pay.target_arch.map_or("all", |ta| ta.as_str())
)
}
};

let pb = mp.add(
Expand All @@ -437,6 +453,7 @@ fn main() -> Result<(), Error> {
work_items,
pruned.crt_version,
pruned.sdk_version,
pruned.vcr_version,
arches,
variants,
op,
Expand Down
25 changes: 24 additions & 1 deletion src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ pub enum Chip {
Neutral,
}

impl Chip {
#[inline]
pub fn as_str(&self) -> &'static str {
match self {
Self::X86 => "x86",
Self::X64 => "x64",
Self::Arm => "arm",
Self::Arm64 => "arm64",
Self::Neutral => "neutral",
}
}
}

#[derive(Copy, Clone, Deserialize, PartialEq, Eq, Debug)]
pub enum ItemKind {
/// Unused.
Expand Down Expand Up @@ -166,9 +179,19 @@ pub fn get_package_manifest(
serde_json::from_slice(&manifest_bytes).context("unable to parse manifest")?;

let mut packages = BTreeMap::new();
let mut package_counts = BTreeMap::new();

for pkg in manifest.packages {
packages.insert(pkg.id.clone(), pkg);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manifest.packages contains lots of packages which have the same id, the vc debug runtime packages are one example. To prevent overriding packages in the packages map the key needs to be slightly modified for those. I decided to simply append the count of duplicate packages to the key, this doesn't conflict with the current behavior and allows to grab all the packages.

// built a unqiue key for each duplicate id to prevent overriding distinct packages
let pkg_id = if packages.contains_key(&pkg.id) {
let count = package_counts.get(&pkg.id).unwrap_or(&0) + 1;
package_counts.insert(pkg.id.clone(), count);
format!("{}#{}", pkg.id, count)
} else {
pkg.id.clone()
};

packages.insert(pkg_id, pkg);
}

Ok(PackageManifest { packages })
Expand Down
1 change: 1 addition & 0 deletions src/minimize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ pub(crate) fn minimize(
SectionKind::SdkLib => (&sdk_lib_prefix, &mut map.sdk.libs),
SectionKind::CrtHeader => (&crt_hdr_prefix, &mut map.crt.headers),
SectionKind::CrtLib => (&crt_lib_prefix, &mut map.crt.libs),
SectionKind::VcrDebug => (&roots.vcrd, &mut map.vcrd.libs),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very uncertain how to handle this as the vcr shouldn't be part of the minimize logic at all. Is there a way to "skip" this ?

};

let path = p
Expand Down
51 changes: 47 additions & 4 deletions src/splat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) struct SplatRoots {
pub root: PathBuf,
pub crt: PathBuf,
pub sdk: PathBuf,
pub vcrd: PathBuf,
src: PathBuf,
}

Expand All @@ -65,16 +66,18 @@ pub(crate) fn prep_splat(

let root = crate::util::canonicalize(root)?;

let (crt_root, sdk_root) = if let Some(crt_version) = winroot {
let (crt_root, sdk_root, vcrd_root) = if let Some(crt_version) = winroot {
let mut crt = root.join("VC/Tools/MSVC");
crt.push(crt_version);

let mut sdk = root.join("Windows Kits");
sdk.push("10");

(crt, sdk)
let vcrd = root.join("VCR");

(crt, sdk, vcrd)
} else {
(root.join("crt"), root.join("sdk"))
(root.join("crt"), root.join("sdk"), root.join("vcrd"))
};

if crt_root.exists() {
Expand All @@ -87,6 +90,11 @@ pub(crate) fn prep_splat(
.with_context(|| format!("unable to delete existing SDK directory {sdk_root}"))?;
}

if vcrd_root.exists() {
std::fs::remove_dir_all(&vcrd_root)
.with_context(|| format!("unable to delete existing VCR directory {vcrd_root}"))?;
}

std::fs::create_dir_all(&crt_root)
.with_context(|| format!("unable to create CRT directory {crt_root}"))?;
std::fs::create_dir_all(&sdk_root)
Expand All @@ -98,6 +106,7 @@ pub(crate) fn prep_splat(
root,
crt: crt_root,
sdk: sdk_root,
vcrd: vcrd_root,
src: src_root,
})
}
Expand All @@ -110,6 +119,7 @@ pub(crate) fn splat(
tree: &crate::unpack::FileTree,
map: Option<&crate::Map>,
sdk_version: &str,
vcrd_version: Option<String>,
arches: u32,
variants: u32,
) -> Result<Option<SdkHeaders>, Error> {
Expand Down Expand Up @@ -397,6 +407,37 @@ pub(crate) fn splat(

mappings
}
PayloadKind::VcrDebug => {
if vcrd_version.is_some() {
let mut src = src.clone();
let mut target = roots.vcrd.clone();

src.push("SourceDir");
if !item.payload.filename.to_string().contains("UCRT") {
src.push(match item.payload.target_arch.unwrap() {
Arch::Aarch | Arch::X86 => "System",
Arch::Aarch64 | Arch::X86_64 => "System64",
});
};

target.push(vcrd_version.unwrap());
target.push("bin");
target.push(item.payload.target_arch.unwrap().as_str());

let tree = get_tree(&src)?;

vec![Mapping {
src,
target,
tree,
kind,
variant,
section: SectionKind::VcrDebug,
}]
} else {
vec![]
}
}
};

let mut results = Vec::new();
Expand Down Expand Up @@ -441,6 +482,7 @@ pub(crate) fn splat(
&map.crt.libs,
)
}
SectionKind::VcrDebug => (roots.vcrd.clone(), &map.vcrd.libs),
};

let mut dir_stack = vec![Dir {
Expand Down Expand Up @@ -588,7 +630,8 @@ pub(crate) fn splat(
PayloadKind::CrtHeaders
| PayloadKind::AtlHeaders
| PayloadKind::Ucrt
| PayloadKind::AtlLibs => {}
| PayloadKind::AtlLibs
| PayloadKind::VcrDebug => {}

PayloadKind::SdkHeaders => {
if let Some(sdk_headers) = &mut sdk_headers {
Expand Down
6 changes: 5 additions & 1 deletion src/unpack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,11 @@ pub(crate) fn unpack(
cab_contents
};

anyhow::ensure!(!cabs.is_empty(), "no cab files were referenced by the MSI");
anyhow::ensure!(
!cabs.is_empty(),
"no cab files were referenced by the MSI {}",
pkg.as_str()
);

struct CabFile {
id: String,
Expand Down
Loading