extern crate cc;
use glob::glob;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
use std::{env, fs::File, io::BufRead, io::BufReader, io::Read};
const AUX_C_FILES: [&str; 2] = ["./src/c/src.c", "./src/c/sjlj.s"];
const SDK_C_FILES: [&str; 9] = [
"src/os_io_usb.c",
"src/pic.c",
"src/checks.c",
"src/cx_stubs.S",
"src/os.c",
"src/svc_call.s",
"src/svc_cx_call.s",
"src/syscalls.c",
"src/os_printf.c",
];
const SDK_USB_FILES: [&str; 6] = [
"lib_stusb/usbd_conf.c",
"lib_stusb/STM32_USB_Device_Library/Core/Src/usbd_core.c",
"lib_stusb/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c",
"lib_stusb/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c",
"lib_stusb_impl/usbd_impl.c",
"lib_stusb/STM32_USB_Device_Library/Class/HID/Src/usbd_hid.c",
];
const CFLAGS_NANOSPLUS: [&str; 22] = [
"-Oz",
"-g0",
"-fomit-frame-pointer",
"-momit-leaf-frame-pointer",
"-fno-common",
"-mlittle-endian",
"-std=gnu99",
"-fdata-sections",
"-ffunction-sections",
"-funsigned-char",
"-fshort-enums",
"-mno-unaligned-access",
"-fropi",
"-fno-jump-tables",
"-nostdlib",
"-nodefaultlibs",
"-frwpi",
"--target=armv8m-none-eabi",
"-mcpu=cortex-m35p+nodsp",
"-mthumb",
"-msoft-float",
"-Wno-unused-command-line-argument",
];
const CFLAGS_STAX: [&str; 22] = CFLAGS_NANOSPLUS;
const CFLAGS_FLEX: [&str; 22] = CFLAGS_NANOSPLUS;
const CFLAGS_NANOX: [&str; 21] = [
"-Oz",
"-g0",
"-fomit-frame-pointer",
"-momit-leaf-frame-pointer",
"-fno-common",
"-mlittle-endian",
"-std=gnu99",
"-fdata-sections",
"-ffunction-sections",
"-funsigned-char",
"-fshort-enums",
"-mno-unaligned-access",
"-fropi",
"-fno-jump-tables",
"-nostdlib",
"-nodefaultlibs",
"-frwpi",
"-mthumb",
"--target=armv6m-none-eabi",
"-mcpu=cortex-m0plus",
"-Wno-unused-command-line-argument",
];
#[derive(Debug, Default, PartialEq)]
enum DeviceName {
#[default]
NanoSPlus,
NanoX,
Stax,
Flex,
}
#[derive(Debug, Default)]
struct Device<'a> {
pub name: DeviceName,
pub c_sdk: PathBuf,
pub target: &'a str,
pub defines: Vec<(String, Option<String>)>,
pub cflags: Vec<&'a str>,
pub glyphs_folders: Vec<PathBuf>,
pub arm_libs: String,
pub linker_script: &'a str,
}
impl std::fmt::Display for DeviceName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceName::NanoSPlus => write!(f, "nanos2"),
DeviceName::NanoX => write!(f, "nanox"),
DeviceName::Stax => write!(f, "stax"),
DeviceName::Flex => write!(f, "flex"),
}
}
}
#[derive(Default)]
struct CSDKInfo {
pub api_level: Option<u32>,
pub target_id: String,
pub target_name: String,
pub c_sdk_name: String,
pub c_sdk_hash: String,
pub c_sdk_version: String,
}
impl CSDKInfo {
pub fn new() -> Self {
CSDKInfo::default()
}
}
#[derive(Debug)]
enum SDKBuildError {
UnsupportedDevice,
InvalidAPILevel,
MissingSDKName,
TargetFileNotFound,
MissingTargetId,
MissingTargetName,
}
struct SDKBuilder<'a> {
api_level: u32,
gcc_toolchain: PathBuf,
device: Device<'a>,
cxdefines: Vec<String>,
}
impl SDKBuilder<'_> {
pub fn new() -> Self {
SDKBuilder {
api_level: 0,
gcc_toolchain: PathBuf::new(),
device: Device::default(),
cxdefines: Vec::new(),
}
}
pub fn gcc_toolchain(&mut self) -> Result<(), SDKBuildError> {
let output = Command::new("arm-none-eabi-gcc")
.arg("-print-sysroot")
.output()
.ok();
let sysroot = output
.as_ref()
.and_then(|o| std::str::from_utf8(&o.stdout).ok())
.unwrap_or("")
.trim();
let gcc_toolchain = if sysroot.is_empty() {
String::from("/usr/lib/arm-none-eabi")
} else {
format!("{sysroot}")
};
self.gcc_toolchain = PathBuf::from(gcc_toolchain);
Ok(())
}
pub fn device(&mut self) -> Result<(), SDKBuildError> {
println!("cargo:rerun-if-env-changed=LEDGER_SDK_PATH");
self.device = match env::var_os("CARGO_CFG_TARGET_OS")
.unwrap()
.to_str()
.unwrap()
{
"nanosplus" => Device {
name: DeviceName::NanoSPlus,
c_sdk: Default::default(),
target: "thumbv8m.main-none-eabi",
defines: {
let mut v = header2define("csdk_nanos2.h");
if env::var_os("CARGO_FEATURE_NANO_NBGL").is_some() {
println!("cargo:warning=NBGL is built");
v.push((String::from("HAVE_NBGL"), None));
v.push((String::from("NBGL_STEP"), None));
v.push((String::from("NBGL_USE_CASE"), None));
} else {
println!("cargo:warning=BAGL is built");
println!("cargo:rustc-env=C_SDK_GRAPHICS={}", "bagl");
v.push((String::from("HAVE_BAGL"), None));
}
v
},
cflags: Vec::from(CFLAGS_NANOSPLUS),
glyphs_folders: Vec::new(),
arm_libs: Default::default(),
linker_script: "nanosplus_layout.ld",
},
"nanox" => Device {
name: DeviceName::NanoX,
c_sdk: Default::default(),
target: "thumbv6m-none-eabi",
defines: {
let mut v = header2define("csdk_nanox.h");
if env::var_os("CARGO_FEATURE_NANO_NBGL").is_some() {
println!("cargo:warning=NBGL is built");
v.push((String::from("HAVE_NBGL"), None));
v.push((String::from("NBGL_STEP"), None));
v.push((String::from("NBGL_USE_CASE"), None));
} else {
println!("cargo:warning=BAGL is built");
println!("cargo:rustc-env=C_SDK_GRAPHICS={}", "bagl");
v.push((String::from("HAVE_BAGL"), None));
}
v
},
cflags: Vec::from(CFLAGS_NANOX),
glyphs_folders: Vec::new(),
arm_libs: Default::default(),
linker_script: "nanox_layout.ld",
},
"stax" => Device {
name: DeviceName::Stax,
c_sdk: Default::default(),
target: "thumbv8m.main-none-eabi",
defines: header2define("csdk_stax.h"),
cflags: Vec::from(CFLAGS_STAX),
glyphs_folders: Vec::new(),
arm_libs: Default::default(),
linker_script: "stax_layout.ld",
},
"flex" => Device {
name: DeviceName::Flex,
c_sdk: Default::default(),
target: "thumbv8m.main-none-eabi",
defines: header2define("csdk_flex.h"),
cflags: Vec::from(CFLAGS_FLEX),
glyphs_folders: Vec::new(),
arm_libs: Default::default(),
linker_script: "flex_layout.ld",
},
_ => {
return Err(SDKBuildError::UnsupportedDevice);
}
};
self.device.c_sdk = match env::var("LEDGER_SDK_PATH") {
Err(_) => clone_sdk(&self.device.name),
Ok(path) => PathBuf::from(path),
};
match self.device.name {
DeviceName::Flex => {
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/wallet"));
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/64px"));
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/40px"));
}
DeviceName::Stax => {
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/wallet"));
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/64px"));
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/32px"));
}
_ => {
self.device
.glyphs_folders
.push(self.device.c_sdk.join("lib_nbgl/glyphs/nano"));
}
}
self.device.arm_libs = match self.device.name {
DeviceName::NanoX => {
let mut path = self.device.c_sdk.display().to_string();
path.push_str("/arch/st33/lib");
path
}
DeviceName::NanoSPlus | DeviceName::Flex | DeviceName::Stax => {
let mut path = self.device.c_sdk.display().to_string();
path.push_str("/arch/st33k1/lib");
path
}
};
println!("cargo:rustc-env=TARGET={}", self.device.name);
println!("cargo:warning=Device is {:?}", self.device.name);
Ok(())
}
pub fn get_info(&mut self) -> Result<(), SDKBuildError> {
let sdk_info = retrieve_csdk_info(&self.device, &self.device.c_sdk)?;
match sdk_info.api_level {
Some(api_level) => {
self.api_level = api_level;
println!("cargo:rustc-env=API_LEVEL={}", self.api_level);
println!("cargo:warning=API_LEVEL is {}", self.api_level);
}
None => return Err(SDKBuildError::InvalidAPILevel),
}
println!("cargo:rustc-env=TARGET_ID={}", sdk_info.target_id);
println!("cargo:warning=TARGET_ID is {}", sdk_info.target_id);
println!("cargo:rustc-env=TARGET_NAME={}", sdk_info.target_name);
println!("cargo:warning=TARGET_NAME is {}", sdk_info.target_name);
println!("cargo:rustc-env=C_SDK_NAME={}", sdk_info.c_sdk_name);
println!("cargo:warning=C_SDK_NAME is {}", sdk_info.c_sdk_name);
println!("cargo:rustc-env=C_SDK_HASH={}", sdk_info.c_sdk_hash);
println!("cargo:warning=C_SDK_HASH is {}", sdk_info.c_sdk_hash);
println!("cargo:rustc-env=C_SDK_VERSION={}", sdk_info.c_sdk_version);
println!("cargo:warning=C_SDK_VERSION is {}", sdk_info.c_sdk_version);
Ok(())
}
fn cxdefines(&mut self) -> Result<(), SDKBuildError> {
let mut makefile = File::open(self.device.c_sdk.join("Makefile.conf.cx"))
.expect("Could not find Makefile.conf.cx");
let mut content = String::new();
makefile.read_to_string(&mut content).unwrap();
let mut cxdefines = content
.split('\n')
.filter(|line| !line.starts_with('#')) .flat_map(|line| line.split(' ').filter(|word| word.starts_with("HAVE")))
.map(|line| line.to_string())
.collect::<Vec<String>>();
cxdefines.push("NATIVE_LITTLE_ENDIAN".to_string());
self.cxdefines = cxdefines;
Ok(())
}
pub fn build_c_sdk(&self) -> Result<(), SDKBuildError> {
generate_glyphs(&self.device);
let mut command = cc::Build::new();
if env::var_os("CC").is_none() {
command.compiler("clang");
} else {
}
command
.files(&AUX_C_FILES)
.files(str2path(&self.device.c_sdk, &SDK_C_FILES))
.files(str2path(&self.device.c_sdk, &SDK_USB_FILES));
command = command
.include(self.gcc_toolchain.join("include"))
.include(self.device.c_sdk.join("include"))
.include(self.device.c_sdk.join("lib_cxng/include"))
.include(self.device.c_sdk.join("lib_stusb"))
.include(self.device.c_sdk.join("lib_stusb_impl"))
.include(
self.device
.c_sdk
.join("lib_stusb/STM32_USB_Device_Library/Core/Inc"),
)
.include(
self.device
.c_sdk
.join("lib_stusb/STM32_USB_Device_Library/Class/HID/Inc"),
)
.debug(true)
.define("main", "_start")
.clone();
for (define, value) in &self.device.defines {
command.define(define.as_str(), value.as_deref());
}
for cflag in &self.device.cflags {
command.flag(cflag);
}
command.target(self.device.target).include(
self.device
.c_sdk
.join(format!("target/{}/include", self.device.name)),
);
for s in self.device.defines.iter() {
if s.0 == "HAVE_BLE" {
configure_lib_ble(&mut command, &self.device.c_sdk);
}
if s.0 == "HAVE_NBGL" {
configure_lib_nbgl(&mut command, &self.device.c_sdk);
}
}
for define in self.cxdefines.iter() {
command.define(define, None);
}
command.compile("ledger-secure-sdk");
let path = self.device.arm_libs.clone();
println!("cargo:rustc-link-lib=c");
println!("cargo:rustc-link-lib=m");
println!("cargo:rustc-link-lib=gcc");
println!("cargo:rustc-link-search={path}");
Ok(())
}
fn generate_bindings(&self) -> Result<(), SDKBuildError> {
let bsdk = self.device.c_sdk.display().to_string();
let gcc_tc = self.gcc_toolchain.display().to_string();
let args = [
"--target=thumbv6m-none-eabi".to_string(), "-fshort-enums".to_string(),
format!("-I{gcc_tc}/include"),
format!("-I{bsdk}/include"),
format!("-I{bsdk}/lib_cxng/include/"),
format!("-I{bsdk}/lib_stusb/STM32_USB_Device_Library/Core/Inc/"),
format!("-I{bsdk}/lib_stusb/"),
];
let headers = str2path(
&self.device.c_sdk,
&[
"lib_cxng/include/libcxng.h",
"include/os.h",
"include/os_screen.h",
"include/syscalls.h",
"include/os_io_seproxyhal.h",
"include/os_ux.h",
"include/ox.h",
"lib_stusb/STM32_USB_Device_Library/Core/Inc/usbd_def.h",
"include/os_io_usb.h",
"lib_standard_app/swap_lib_calls.h",
],
);
let mut bindings = bindgen::builder()
.clang_args(&args)
.prepend_enum_name(false)
.generate_comments(false)
.derive_default(true)
.use_core();
let csdk_target_name = self.device.name.to_string();
let header = format!("csdk_{csdk_target_name}.h");
bindings = bindings.clang_arg(format!("-I{bsdk}/target/{csdk_target_name}/include/"));
bindings = bindings.header(header);
for header in headers.iter().map(|p| p.to_str().unwrap()) {
bindings = bindings.header(header);
}
if ((self.device.name == DeviceName::NanoX || self.device.name == DeviceName::NanoSPlus)
&& env::var_os("CARGO_FEATURE_NANO_NBGL").is_some())
|| self.device.name == DeviceName::Stax
|| self.device.name == DeviceName::Flex
{
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let mut include_path = "-I".to_string();
let glyphs = out_path.join("glyphs");
include_path += glyphs.to_str().unwrap();
bindings = bindings.clang_args([include_path.as_str()]);
bindings = bindings.clang_args([
format!("-I{bsdk}/lib_nbgl/include/").as_str(),
format!("-I{bsdk}/lib_ux_nbgl/").as_str(),
]);
bindings = bindings
.header(
self.device
.c_sdk
.join("lib_nbgl/include/nbgl_use_case.h")
.to_str()
.unwrap(),
)
.header(
self.device
.c_sdk
.join("lib_ux_nbgl/ux_nbgl.h")
.to_str()
.unwrap(),
);
}
match self.device.name {
DeviceName::NanoX | DeviceName::Flex | DeviceName::Stax => {
bindings = bindings.header(
self.device
.c_sdk
.join("lib_blewbxx_impl/include/ledger_ble.h")
.to_str()
.unwrap(),
)
}
_ => (),
}
for define in &self.cxdefines {
bindings = bindings.clang_arg(format!("-D{define}"));
}
let bindings = bindings
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings");
Ok(())
}
fn generate_heap_size(&self) -> Result<(), SDKBuildError> {
let heap_size = env::var("HEAP_SIZE").unwrap_or_else(|_| "8192".to_string());
let heap_size_value = heap_size.parse::<u32>().unwrap();
assert!(
heap_size_value >= 2048 && heap_size_value <= 24576,
"Invalid heap size: {heap_size}; Shall be included in [2048, 24576]"
);
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("heap_size.rs");
fs::write(
&dest_path,
format!("pub const HEAP_SIZE: usize = {};", heap_size),
)
.expect("Unable to write file");
Ok(())
}
fn copy_linker_script(&self) -> Result<(), SDKBuildError> {
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out_dir.display());
std::fs::copy(
self.device.linker_script,
out_dir.join(self.device.linker_script),
)
.unwrap();
std::fs::copy("link.ld", out_dir.join("link.ld")).unwrap();
Ok(())
}
}
fn main() {
let start = Instant::now();
let mut sdk_builder = SDKBuilder::new();
sdk_builder.gcc_toolchain().unwrap();
sdk_builder.device().unwrap();
sdk_builder.get_info().unwrap();
sdk_builder.cxdefines().unwrap();
sdk_builder.build_c_sdk().unwrap();
sdk_builder.generate_bindings().unwrap();
sdk_builder.generate_heap_size().unwrap();
sdk_builder.copy_linker_script().unwrap();
let end = start.elapsed();
println!(
"cargo:warning=Total build.rs time: {} seconds",
end.as_secs()
);
}
fn configure_lib_ble(command: &mut cc::Build, c_sdk: &Path) {
command
.file(c_sdk.join("src/ledger_protocol.c"))
.file(c_sdk.join("lib_blewbxx/core/auto/ble_gap_aci.c"))
.file(c_sdk.join("lib_blewbxx/core/auto/ble_gatt_aci.c"))
.file(c_sdk.join("lib_blewbxx/core/auto/ble_hal_aci.c"))
.file(c_sdk.join("lib_blewbxx/core/auto/ble_hci_le.c"))
.file(c_sdk.join("lib_blewbxx/core/auto/ble_l2cap_aci.c"))
.file(c_sdk.join("lib_blewbxx/core/template/osal.c"))
.file(c_sdk.join("lib_blewbxx_impl/src/ledger_ble.c"))
.include(c_sdk.join("lib_blewbxx/include"))
.include(c_sdk.join("lib_blewbxx/core"))
.include(c_sdk.join("lib_blewbxx/core/auto"))
.include(c_sdk.join("lib_blewbxx/core/template"))
.include(c_sdk.join("lib_blewbxx_impl/include"));
}
fn configure_lib_nbgl(command: &mut cc::Build, c_sdk: &Path) {
println!("cargo:rustc-env=C_SDK_GRAPHICS={}", "nbgl");
let glyphs_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("glyphs");
command
.include(c_sdk.join("lib_nbgl/include/"))
.include(c_sdk.join("lib_nbgl/include/fonts/"))
.include(c_sdk.join("lib_ux_nbgl/"))
.include(c_sdk.join("qrcode/include/"))
.include(c_sdk.join("lib_bagl/include/"))
.file(c_sdk.join("lib_ux_nbgl/ux.c"))
.file(c_sdk.join("qrcode/src/qrcodegen.c"))
.files(
glob(c_sdk.join("lib_nbgl/src/*.c").to_str().unwrap())
.unwrap()
.map(|x| x.unwrap())
.collect::<Vec<PathBuf>>(),
)
.include(&glyphs_path)
.file(glyphs_path.join("glyphs.c"));
}
fn retrieve_csdk_info(device: &Device, path: &PathBuf) -> Result<CSDKInfo, SDKBuildError> {
let mut csdk_info = CSDKInfo::new();
(csdk_info.api_level, csdk_info.c_sdk_name) = retrieve_makefile_infos(path)?;
(csdk_info.target_id, csdk_info.target_name) = retrieve_target_file_infos(device, path)?;
(csdk_info.c_sdk_hash, csdk_info.c_sdk_version) = retrieve_csdk_git_info(path);
Ok(csdk_info)
}
fn retrieve_csdk_git_info(c_sdk: &Path) -> (String, String) {
let c_sdk_hash = match Command::new("git")
.arg("-C")
.arg(c_sdk)
.arg("describe")
.arg("--always")
.arg("--dirty")
.arg("--exclude")
.arg("*")
.arg("--abbrev=40")
.output()
.ok()
{
Some(output) => {
if output.stdout.is_empty() {
"None".to_string()
} else {
String::from_utf8(output.stdout).unwrap_or("None".to_string())
}
}
None => "None".to_string(),
};
let c_sdk_version = match Command::new("git")
.arg("-C")
.arg(c_sdk)
.arg("describe")
.arg("--tags")
.arg("--match")
.arg("v[0-9]*")
.arg("--dirty")
.output()
.ok()
{
Some(output) => {
if output.status.success() {
String::from_utf8(output.stdout).unwrap_or("None".to_string())
} else {
String::from_utf8(output.stderr).unwrap_or("None".to_string())
}
}
None => "None".to_string(),
};
(c_sdk_hash, c_sdk_version)
}
fn retrieve_makefile_infos(c_sdk: &Path) -> Result<(Option<u32>, String), SDKBuildError> {
let makefile =
File::open(c_sdk.join("Makefile.defines")).expect("Could not find Makefile.defines");
let mut api_level: Option<u32> = None;
for line in BufReader::new(makefile).lines().flatten() {
if let Some(value) = line.split(":=").nth(1).map(str::trim) {
if line.contains("API_LEVEL") && api_level.is_none() {
api_level = Some(value.parse().map_err(|_| SDKBuildError::InvalidAPILevel)?);
}
}
if api_level.is_some() {
break;
}
}
let makefile =
File::open(c_sdk.join("Makefile.target")).expect("Could not find Makefile.defines");
let mut sdk_name: Option<String> = None;
for line in BufReader::new(makefile).lines().flatten() {
if let Some(value) = line.split(":=").nth(1).map(str::trim) {
if line.contains("SDK_NAME") && sdk_name.is_none() {
sdk_name = Some(value.to_string().replace('\"', ""));
}
}
if sdk_name.is_some() {
break;
}
}
let sdk_name = sdk_name.ok_or(SDKBuildError::MissingSDKName)?;
Ok((api_level, sdk_name))
}
fn retrieve_target_file_infos(
device: &Device,
c_sdk: &Path,
) -> Result<(String, String), SDKBuildError> {
let prefix = format!("target/{}/", device.name);
let target_file_path = c_sdk.join(format!("{}include/bolos_target.h", prefix));
let target_file =
File::open(target_file_path).map_err(|_| SDKBuildError::TargetFileNotFound)?;
let mut target_id: Option<String> = None;
let mut target_name: Option<String> = None;
for line in BufReader::new(target_file).lines().flatten() {
if target_id.is_none() && line.contains("#define TARGET_ID") {
target_id = Some(
line.split_whitespace()
.nth(2)
.ok_or("err")
.map_err(|_| SDKBuildError::MissingTargetId)?
.to_string(),
);
} else if target_name.is_none()
&& line.contains("#define TARGET_")
&& !line.contains("#define TARGET_ID")
{
target_name = Some(
line.split_whitespace()
.nth(1)
.ok_or("err")
.map_err(|_| SDKBuildError::MissingTargetName)?
.to_string(),
);
}
if target_id.is_some() && target_name.is_some() {
break;
}
}
let target_id = target_id.ok_or(SDKBuildError::MissingTargetId)?;
let target_name = target_name.ok_or(SDKBuildError::MissingTargetName)?;
Ok((target_id, target_name))
}
fn clone_sdk(devicename: &DeviceName) -> PathBuf {
let (repo_url, sdk_branch) = match devicename {
DeviceName::NanoX => (
Path::new("https://github.com/LedgerHQ/ledger-secure-sdk"),
"API_LEVEL_22",
),
DeviceName::NanoSPlus => (
Path::new("https://github.com/LedgerHQ/ledger-secure-sdk"),
"API_LEVEL_22",
),
DeviceName::Stax => (
Path::new("https://github.com/LedgerHQ/ledger-secure-sdk"),
"API_LEVEL_22",
),
DeviceName::Flex => (
Path::new("https://github.com/LedgerHQ/ledger-secure-sdk"),
"API_LEVEL_22",
),
};
let out_dir = env::var("OUT_DIR").unwrap();
let c_sdk = Path::new(out_dir.as_str()).join("ledger-secure-sdk");
if !c_sdk.exists() {
Command::new("git")
.arg("clone")
.arg(repo_url.to_str().unwrap())
.arg("-b")
.arg(sdk_branch)
.arg(c_sdk.as_path())
.output()
.ok();
}
c_sdk
}
fn generate_glyphs(device: &Device) {
let icon2glyph = device.c_sdk.join("lib_nbgl/tools/icon2glyph.py");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let dest_path = out_path.join("glyphs");
if !dest_path.exists() {
fs::create_dir_all(&dest_path).ok();
}
let mut cmd = Command::new(icon2glyph.as_os_str());
cmd.arg("--glyphcheader")
.arg(dest_path.join("glyphs.h").as_os_str())
.arg("--glyphcfile")
.arg(dest_path.join("glyphs.c").as_os_str());
if device.name == DeviceName::NanoSPlus || device.name == DeviceName::NanoX {
cmd.arg("--reverse");
}
for folder in device.glyphs_folders.iter() {
for file in std::fs::read_dir(folder).unwrap() {
let path = file.unwrap().path();
let path_str = path.to_str().unwrap().to_string();
cmd.arg(path_str);
}
}
let _ = cmd.output();
}
fn str2path(c_sdk: &Path, pathlist: &[&str]) -> Vec<PathBuf> {
pathlist
.iter()
.map(|p| c_sdk.join(p))
.collect::<Vec<PathBuf>>()
}
fn header2define(headername: &str) -> Vec<(String, Option<String>)> {
let mut headerfile = File::open(headername).unwrap();
let mut header = String::new();
headerfile.read_to_string(&mut header).unwrap();
header
.lines()
.filter_map(|line| {
if line.trim_start().starts_with("#define") {
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.len() {
2 => Some((parts[1].to_string(), None)),
3 => Some((parts[1].to_string(), Some(parts[2].to_string()))),
_ => None,
}
} else {
None
}
})
.collect()
}