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 4 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
76 changes: 74 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

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

mod ctx;
Expand Down Expand Up @@ -183,11 +183,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 +199,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 +218,79 @@ 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;

tracing::info!("getting vcrd");
// 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"))
// get the first payload (which contains the MSI)
.filter_map(|(_, manifest_item)| {
manifest_item.payloads.get(0).map(|payload| (manifest_item, payload))
})
.for_each(|(manifest_item, payload)| {
// set vcrd_version from manifest item
vcrd_version = Some(manifest_item.version.clone());
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;
}

pruned.push(Payload {
filename: format_args!(
"Microsoft.VC.Runtime.Debug.{}.{}.msi",
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)),
});
});

Ok(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 +805,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 +835,7 @@ pub enum SectionKind {
CrtLib,
SdkHeader,
SdkLib,
VcrDebug
}

#[derive(serde::Serialize, serde::Deserialize, Default)]
Expand Down
11 changes: 11 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,12 @@ fn main() -> Result<(), Error> {
}
PayloadKind::SdkStoreLibs => "SDK.libs.store.all".to_owned(),
PayloadKind::Ucrt => "SDK.ucrt.all".to_owned(),
PayloadKind::VcrDebug => {
format!(
"VC.Runtime.Debug.{}",
pay.target_arch.map_or("all", |ta| ta.as_str())
)
}
};

let pb = mp.add(
Expand All @@ -437,6 +447,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
49 changes: 44 additions & 5 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 @@ -396,6 +406,33 @@ 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");
let dirname = match item.payload.target_arch.unwrap() {
Arch::Aarch | Arch::X86 => "System",
Arch::Aarch64 | Arch::X86_64 => "System64"
};
src.push(dirname);

target.push(vcrd_version.unwrap());
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![]
}
};

Expand Down Expand Up @@ -440,7 +477,8 @@ pub(crate) fn splat(
mapping.target.parent().unwrap().to_owned(),
&map.crt.libs,
)
}
},
SectionKind::VcrDebug => (roots.vcrd.clone(), &map.vcrd.libs)
};

let mut dir_stack = vec![Dir {
Expand Down Expand Up @@ -588,7 +626,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
2 changes: 1 addition & 1 deletion src/unpack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ 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
4 changes: 4 additions & 0 deletions tests/compiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn verify_compiles() {
xwin::Arch::X86_64 as u32,
xwin::Variant::Desktop as u32,
false,
false,
None,
None,
)
Expand Down Expand Up @@ -77,6 +78,7 @@ fn verify_compiles() {
.collect(),
pruned.crt_version.clone(),
pruned.sdk_version.clone(),
pruned.vcr_version.clone(),
xwin::Arch::X86_64 as u32,
xwin::Variant::Desktop as u32,
op,
Expand Down Expand Up @@ -195,6 +197,7 @@ fn verify_compiles_minimized() {
xwin::Arch::X86_64 as u32,
xwin::Variant::Desktop as u32,
false,
false,
None,
None,
)
Expand Down Expand Up @@ -239,6 +242,7 @@ fn verify_compiles_minimized() {
.collect(),
pruned.crt_version,
pruned.sdk_version,
pruned.vcr_version,
xwin::Arch::X86_64 as u32,
xwin::Variant::Desktop as u32,
op,
Expand Down
Loading