use crate::{fs_pack::easy_fs_pack, objcopy, PROJECT, TARGET, TARGET_ARCH};
use crate::utils::Cargo;
use serde_derive::Deserialize;
use std::{collections::HashMap, ffi::OsStr, fs::File, io::Write, path::PathBuf};
#[derive(Deserialize, Default)]
struct Cases {
base: Option<u64>,
step: Option<u64>,
pub cases: Option<Vec<String>>,
}
pub struct CasesInfo {
base: u64,
step: u64,
bins: Vec<PathBuf>,
}
impl Cases {
fn build(&mut self, ch: u8, release: bool, ci: bool, exercise: bool) -> CasesInfo {
if let Some(names) = &self.cases {
let base = self.base.unwrap_or(0);
let step = self.step.filter(|_| self.base.is_some()).unwrap_or(0);
let chapter_env: Option<i8> = if ci {
Some(if exercise { ch as i8 } else { -(ch as i8) })
} else {
None
};
let cases = names
.iter()
.enumerate()
.map(|(i, name)| build_one(chapter_env, name, release, base + i as u64 * step))
.collect();
CasesInfo {
base,
step,
bins: cases,
}
} else {
CasesInfo {
base: 0,
step: 0,
bins: vec![],
}
}
}
}
fn build_one(
chapter_env: Option<i8>,
name: impl AsRef<OsStr>,
release: bool,
base_address: u64,
) -> PathBuf {
let name = name.as_ref();
let binary = base_address != 0;
if binary {
println!("build {name:?} at {base_address:#x}");
}
Cargo::build()
.package("user_lib")
.target(TARGET_ARCH)
.arg("--bin")
.arg(name)
.conditional(chapter_env.is_some(), |cargo| {
cargo.env("CHAPTER", chapter_env.unwrap().to_string());
})
.conditional(release, |cargo| {
cargo.release();
})
.conditional(binary, |cargo| {
cargo.env("BASE_ADDRESS", base_address.to_string());
})
.invoke();
let elf = TARGET
.join(if release { "release" } else { "debug" })
.join(name);
if binary {
objcopy(elf, binary, false)
} else {
elf
}
}
pub fn build_for(ch: u8, release: bool, exercise: bool, ci: bool) {
let cfg = std::fs::read_to_string(PROJECT.join("user/cases.toml")).unwrap();
let key = if exercise {
format!("ch{ch}_exercise")
} else {
format!("ch{ch}")
};
let mut cases = toml::from_str::<HashMap<String, Cases>>(&cfg)
.unwrap()
.remove(&key)
.unwrap_or_default();
let CasesInfo { base, step, bins } = cases.build(ch, release, ci, exercise);
if bins.is_empty() {
return;
}
let asm = TARGET
.join(if release { "release" } else { "debug" })
.join("app.asm");
let mut ld = File::create(asm).unwrap();
writeln!(
ld,
"\
.global apps
.section .data
.align 3
apps:
.quad {base:#x}
.quad {step:#x}
.quad {}",
bins.len(),
)
.unwrap();
(0..bins.len()).for_each(|i| writeln!(ld, " .quad app_{i}_start").unwrap());
writeln!(ld, " .quad app_{}_end", bins.len() - 1).unwrap();
bins.iter().enumerate().for_each(|(i, path)| {
writeln!(
ld,
"
app_{i}_start:
.incbin {path:?}
app_{i}_end:",
)
.unwrap();
});
if ch == 5 {
writeln!(
ld,
"
.align 3
.section .data
.global app_names
app_names:"
)
.unwrap();
bins.iter().for_each(|path| {
writeln!(ld, " .string {:?}", path.file_name().unwrap()).unwrap();
});
} else if ch >= 6 {
easy_fs_pack(
&cases.cases.unwrap(),
TARGET
.join(if release { "release" } else { "debug" })
.into_os_string()
.into_string()
.unwrap()
.as_str(),
)
.unwrap();
}
}