use std::env;
use std::path::PathBuf;
use std::process::Command;
fn generate_install_parameters() {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
let out_path = std::path::PathBuf::from(&out_dir);
let root_dir = out_path
.parent() .and_then(|p| p.parent()) .and_then(|p| p.parent()) .and_then(|p| p.parent()) .and_then(|p| p.parent()) .and_then(|p| p.parent()) .expect("Could not find root directory from OUT_DIR");
println!("cargo:warning=Root directory: {}", root_dir.display());
let output = std::process::Command::new("cargo")
.current_dir(root_dir)
.args(["metadata", "--format-version", "1", "--no-deps"])
.output()
.expect("Failed to execute cargo metadata");
let metadata: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("Failed to parse cargo metadata output");
println!("cargo:warning=Looking for ledger metadata...");
if let Some(packages) = metadata["packages"].as_array() {
for package in packages {
let pkg_name = package["name"].as_str().unwrap_or("unknown");
println!("cargo:warning=Checking package: {}", pkg_name);
if let Some(metadata_ledger) = package["metadata"]["ledger"].as_object() {
println!(
"cargo:warning=Found ledger metadata in package: {}",
pkg_name
);
let device = env::var_os("CARGO_CFG_TARGET_OS").unwrap();
let device_name = device.to_str().unwrap();
println!("cargo:warning=Device is {}", device_name);
let app_name = metadata_ledger["name"].as_str().expect("name not found");
println!("cargo:rustc-env=APP_NAME={}", app_name);
println!("cargo:warning=APP_NAME is {}", app_name);
const APPLICATION_FLAG_BOLOS_SETTINGS: u32 = 0x200;
let flags = metadata_ledger["flags"].as_str().expect("flags not found");
let app_flags = match device_name {
"nanosplus" => String::from(flags),
"nanox" | "stax" | "flex" | "apex_p" => {
let base = u32::from_str_radix(flags.trim_start_matches("0x"), 16)
.unwrap_or_else(|_| {
panic!(
"package `{pkg_name}`: ledger.flags must be a hex string like \"0x200\", got {flags:?}"
)
});
format!("0x{:x}", base | APPLICATION_FLAG_BOLOS_SETTINGS)
}
other => panic!("Unsupported device target_os: {other:?}"),
};
println!("cargo:rustc-env=APP_FLAGS={}", app_flags);
println!("cargo:warning=APP_FLAGS is {}", app_flags);
let app_version = package["version"].as_str().expect("version not found");
println!("cargo:rustc-env=APP_VERSION={}", app_version);
println!("cargo:warning=APP_VERSION is {}", app_version);
let curves = metadata_ledger["curve"]
.as_array()
.expect("curves not found")
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect::<Vec<_>>();
println!("cargo:warning=curves are {:x?}", curves);
let paths = metadata_ledger["path"]
.as_array()
.expect("paths not found")
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect::<Vec<_>>();
println!("cargo:warning=paths are {:x?}", paths);
let paths_slip21: Vec<String> = metadata_ledger
.get("path_slip21")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect()
})
.unwrap_or_default();
if !paths_slip21.is_empty() {
println!("cargo:warning=paths_slip21 are {:x?}", paths_slip21);
}
let icon = metadata_ledger
.get(device_name)
.and_then(|device_metadata| device_metadata.get("icon"))
.and_then(|icon| icon.as_str())
.unwrap_or_else(|| {
panic!(
"missing Ledger app icon metadata for device `{}`; expected \
`package.metadata.ledger.{device}.icon` to be a string, for \
example: [package.metadata.ledger.{device}] icon = \"path/to/icon.gif\"",
device_name,
device = device_name
)
});
println!("cargo:warning=APP_ICON is {}", icon);
let c_sdk_path = resolve_c_sdk_path(device_name);
println!("cargo:warning=C SDK path is {}", c_sdk_path.display());
let icon_hex_string = convert_icon_to_hex(&c_sdk_path, device_name, root_dir, icon);
let install_params_exe = c_sdk_path.join("install_params.py");
let mut generate_tlv_install_params = std::process::Command::new("python3");
generate_tlv_install_params.arg(&install_params_exe);
generate_tlv_install_params.arg("--appName").arg(app_name);
generate_tlv_install_params
.arg("--appVersion")
.arg(app_version);
curves.iter().for_each(|p| {
generate_tlv_install_params.arg("--curve").arg(p.as_str());
});
paths.iter().for_each(|p| {
generate_tlv_install_params
.arg("--path")
.arg(p.as_str().trim_end_matches('/'));
});
paths_slip21.iter().for_each(|p| {
generate_tlv_install_params
.arg("--path_slip21")
.arg(p.as_str());
});
generate_tlv_install_params
.arg("--icon")
.arg(icon_hex_string);
let output = generate_tlv_install_params
.output()
.expect("Failed to execute install_params_generator");
if !output.status.success() {
panic!(
"call to install_params.py failed: {}",
std::str::from_utf8(&output.stderr).unwrap()
);
}
let tlv_blob = format!(
"[{}]",
std::str::from_utf8(output.stdout.as_slice())
.unwrap()
.trim()
);
let bytes: Vec<u8> = tlv_blob
.trim_matches(|c| c == '[' || c == ']')
.split(',')
.filter_map(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
u8::from_str_radix(trimmed.trim_start_matches("0x"), 16).ok()
}
})
.collect();
let byte_array_str = bytes
.iter()
.map(|b| format!("0x{:02x}", b))
.collect::<Vec<_>>()
.join(",");
let out_dir = std::env::var("OUT_DIR").unwrap();
std::fs::write(
std::path::Path::new(&out_dir).join("install_params.txt"),
format!("[{}]", byte_array_str),
)
.unwrap();
std::fs::write(
std::path::Path::new(&out_dir).join("install_params_len.txt"),
bytes.len().to_string(),
)
.unwrap();
println!("cargo:warning=INSTALL_PARAMS_BYTES is [{}]", byte_array_str);
println!("cargo:warning=INSTALL_PARAMS_LEN is {}", bytes.len());
return;
}
}
}
println!(
"cargo:warning=No [package.metadata.ledger] section found - empty install parameters generation"
);
let out_dir = std::env::var("OUT_DIR").unwrap();
std::fs::write(
std::path::Path::new(&out_dir).join("install_params.txt"),
"[]",
)
.unwrap();
std::fs::write(
std::path::Path::new(&out_dir).join("install_params_len.txt"),
"0",
)
.unwrap();
}
fn resolve_c_sdk_path(device_name: &str) -> PathBuf {
PathBuf::from(env::var("LEDGER_SDK_PATH").unwrap_or_else(|_| {
let var = match device_name {
"nanosplus" => "NANOSP_SDK",
"nanox" => "NANOX_SDK",
"stax" => "STAX_SDK",
"flex" => "FLEX_SDK",
"apex_p" => "APEX_P_SDK",
_ => panic!("Unsupported device: {}", device_name),
};
env::var(var).unwrap_or_else(|_| panic!("{} not set", var))
}))
}
fn convert_icon_to_hex(
c_sdk_path: &std::path::Path,
device_name: &str,
root_dir: &std::path::Path,
icon: &str,
) -> String {
let icon_hex_file = PathBuf::from(env::var("OUT_DIR").unwrap()).join("icon.hex");
println!(
"cargo:warning=Output file for icon2glyph is {}",
icon_hex_file.display()
);
let icon2glyph = c_sdk_path.join("lib_nbgl/tools/icon2glyph.py");
let mut cmd = Command::new("python3");
cmd.arg(&icon2glyph);
cmd.arg("--hexbitmap").arg(&icon_hex_file);
if device_name == "nanosplus" || device_name == "nanox" {
cmd.arg("--reverse");
}
cmd.arg(root_dir.join(icon));
let output = cmd.output().expect("Failed to execute icon2glyph.py");
if !output.status.success() {
panic!(
"call to icon2glyph.py failed: {}",
std::str::from_utf8(&output.stderr).unwrap()
);
}
std::fs::read_to_string(&icon_hex_file)
.expect("Failed to read icon hex file")
.trim()
.to_string()
}
fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
generate_install_parameters();
}