mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
979 lines
37 KiB
Rust
979 lines
37 KiB
Rust
use std::fs::OpenOptions;
|
|
use std::{
|
|
env,
|
|
io::{Read, Write},
|
|
path::{Path, PathBuf},
|
|
process::Command,
|
|
};
|
|
use crate::{DynError, app_manifest::generate_app_menus};
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub(crate) enum BuildStream {
|
|
Debug,
|
|
Release
|
|
}
|
|
impl BuildStream {
|
|
pub fn to_str(&self) -> &str {
|
|
match self {
|
|
BuildStream::Debug => "debug",
|
|
BuildStream::Release => "release",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum CrateSpec {
|
|
/// name of the crate
|
|
Local(String, bool),
|
|
/// crates.io: (name of crate, version)
|
|
CratesIo(String, String, bool),
|
|
/// a prebuilt package: (name of executable, URL for download)
|
|
Prebuilt(String, String, bool),
|
|
/// a prebuilt binary, done using command line tools
|
|
BinaryFile(String, bool),
|
|
/// an empty entry
|
|
None,
|
|
}
|
|
impl CrateSpec {
|
|
pub fn is_xip(&self) -> bool {
|
|
match self {
|
|
CrateSpec::Local(_s, xip) => *xip,
|
|
CrateSpec::CratesIo(_n, _v, xip) => *xip,
|
|
CrateSpec::Prebuilt(_n, _u, xip) => *xip,
|
|
CrateSpec::BinaryFile(_path, xip) => *xip,
|
|
_ => false
|
|
}
|
|
}
|
|
pub fn set_xip(&mut self, xip: bool) {
|
|
*self = match self {
|
|
CrateSpec::Local(s, _xip) => CrateSpec::Local(s.to_string(), xip),
|
|
CrateSpec::CratesIo(n, v, _xip) => CrateSpec::CratesIo(n.to_string(), v.to_string(), xip),
|
|
CrateSpec::Prebuilt(n, u, _xip) => CrateSpec::Prebuilt(n.to_string(), u.to_string(), xip),
|
|
CrateSpec::BinaryFile(path, _xip) => CrateSpec::BinaryFile(path.to_string(), xip),
|
|
CrateSpec::None => CrateSpec::None,
|
|
}
|
|
}
|
|
pub fn name(&self) -> Option<String> {
|
|
match self {
|
|
CrateSpec::Local(s, _xip) => Some(s.to_string()),
|
|
CrateSpec::CratesIo(n, _v, _xip) => Some(n.to_string()),
|
|
CrateSpec::Prebuilt(n, _u, _xip) => Some(n.to_string()),
|
|
CrateSpec::BinaryFile(path, _xip) => Some(path.to_string()),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
impl Clone for CrateSpec {
|
|
fn clone(&self) -> CrateSpec {
|
|
match self {
|
|
CrateSpec::Local(s, xip) => CrateSpec::Local(s.to_string(), *xip),
|
|
CrateSpec::CratesIo(n, v, xip) => CrateSpec::CratesIo(n.to_string(), v.to_string(), *xip),
|
|
CrateSpec::Prebuilt(n, u, xip) => CrateSpec::Prebuilt(n.to_string(), u.to_string(), *xip),
|
|
CrateSpec::BinaryFile(path, xip) => CrateSpec::BinaryFile(path.to_string(), *xip),
|
|
CrateSpec::None => CrateSpec::None,
|
|
}
|
|
}
|
|
}
|
|
impl From<&str> for CrateSpec {
|
|
fn from(spec: &str) -> CrateSpec {
|
|
// remote crates are specified as "name@version", i.e. "xous-names@0.9.9"
|
|
if spec.contains('@') {
|
|
let (name, version) = spec.split_once('@').expect("couldn't parse crate specifier");
|
|
CrateSpec::CratesIo(
|
|
name.to_string(),
|
|
version.to_string(),
|
|
false
|
|
)
|
|
// prebuilt crates are specified as "name#url"
|
|
// i.e. "espeak-embedded#https://ci.betrusted.io/job/espeak-embedded/lastSuccessfulBuild/artifact/target/riscv32imac-unknown-xous-elf/release/"
|
|
} else if spec.contains('#') {
|
|
let (name, url) = spec.split_once('#').expect("couldn't parse crate specifier");
|
|
CrateSpec::Prebuilt(
|
|
name.to_string(),
|
|
url.to_string(),
|
|
false
|
|
)
|
|
// local files are specified as paths, which, at a minimum include one directory separator "/" or "\"
|
|
// i.e. "./local_file"
|
|
// Note that this is after a test for the '#' character, so that disambiguates URL slashes
|
|
// It does mean that files with a '#' character in them are mistaken for URL coded paths, and '@' as remote crates.
|
|
} else if spec.contains('/') || spec.contains('\\') {
|
|
CrateSpec::BinaryFile(spec.to_string(), false)
|
|
} else {
|
|
CrateSpec::Local(spec.to_string(), false)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct Builder {
|
|
loader: CrateSpec,
|
|
loader_features: Vec::<String>,
|
|
loader_disable_defaults: bool,
|
|
loader_key: String,
|
|
kernel: CrateSpec,
|
|
kernel_features: Vec::<String>,
|
|
kernel_disable_defaults: bool,
|
|
kernel_key: String,
|
|
/// crates that are installed in the xous.img, each one running in its own separate process space
|
|
services: Vec::<CrateSpec>,
|
|
/// Apps are different from services in that context menus are auto-generated for apps; furthermore, the apps must
|
|
/// exist in the app manifest JSON file. Aside from that, the Xous kernel treats apps and services identically.
|
|
apps: Vec::<CrateSpec>,
|
|
features: Vec::<String>,
|
|
stream: BuildStream,
|
|
min_ver: String,
|
|
target: Option<String>,
|
|
utra_target: String,
|
|
run_svd2repl: bool,
|
|
locale_override: Option<String>,
|
|
locale_stash: String,
|
|
/// when set to true, hosted mode builds but does not run
|
|
dry_run: bool,
|
|
}
|
|
|
|
impl Builder {
|
|
pub fn new() -> Builder {
|
|
Builder {
|
|
loader: CrateSpec::Local("loader".to_string(), false),
|
|
loader_features: Vec::new(),
|
|
loader_key: "devkey/dev.key".into(),
|
|
loader_disable_defaults: false,
|
|
kernel: CrateSpec::Local("xous-kernel".to_string(), false),
|
|
kernel_features: Vec::new(),
|
|
kernel_key: "devkey/dev.key".into(),
|
|
kernel_disable_defaults: false,
|
|
services: Vec::new(),
|
|
apps: Vec::new(),
|
|
features: Vec::new(),
|
|
stream: BuildStream::Release,
|
|
min_ver: crate::MIN_XOUS_VERSION.to_string(),
|
|
target: Some(crate::TARGET_TRIPLE_RISCV32.to_string()),
|
|
utra_target: format!("precursor-{}", crate::PRECURSOR_SOC_VERSION).to_string(),
|
|
run_svd2repl: false,
|
|
locale_override: None,
|
|
locale_stash: String::new(),
|
|
dry_run: false,
|
|
}
|
|
}
|
|
/// Specify an alternate loader key, as a String that can encode a file name
|
|
/// in the local directory, or a path + filename.
|
|
#[allow(dead_code)]
|
|
pub fn loader_key_file<'a>(&'a mut self, filename: String) -> &'a mut Builder {
|
|
self.loader_key = filename;
|
|
self
|
|
}
|
|
/// Specify an alternate loader key, as a String that can encode a file name
|
|
/// in the local directory, or a path + filename.
|
|
#[allow(dead_code)]
|
|
pub fn kernel_key_file<'a>(&'a mut self, filename: String) -> &'a mut Builder {
|
|
self.kernel_key = filename;
|
|
self
|
|
}
|
|
/// Set the build stream (debug or release)
|
|
#[allow(dead_code)]
|
|
pub fn stream<'a>(&'a mut self, stream: BuildStream) -> &'a mut Builder {
|
|
self.stream = stream;
|
|
self
|
|
}
|
|
/// Disable default features on the loader
|
|
#[allow(dead_code)]
|
|
pub fn loader_disable_defaults<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.loader_disable_defaults = true;
|
|
self
|
|
}
|
|
/// Disable default features on the loader
|
|
#[allow(dead_code)]
|
|
pub fn kernel_disable_defaults<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.kernel_disable_defaults = true;
|
|
self
|
|
}
|
|
/// Set a minimum xous version. This is the minimum Xous version necessary to read
|
|
/// the PDDB that is generated by this build. The purpose of this is so that we can
|
|
/// trim migration code out of the PDDB: when we have a breaking change to the PDDB,
|
|
/// the PDDB contains code to detect the version change and migrate to the latest
|
|
/// version. Eventually (on the order of many months or years), this code gets retired,
|
|
/// otherwise we accumulate rarely-used code ad nauseum.
|
|
#[allow(dead_code)]
|
|
pub fn set_min_xous_ver<'a>(&'a mut self, min_ver_string: &str) -> &'a mut Builder {
|
|
self.min_ver = min_ver_string.to_string();
|
|
self
|
|
}
|
|
|
|
/// specify a locale string to override for the current build
|
|
pub fn override_locale<'a>(&'a mut self, locale: &str) -> &'a mut Builder {
|
|
self.locale_override = Some(locale.into());
|
|
self
|
|
}
|
|
|
|
/// Configure for hosted mode
|
|
pub fn target_hosted<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.loader = CrateSpec::None;
|
|
self.target = None;
|
|
self.stream = BuildStream::Release;
|
|
self.utra_target = "hosted".to_string();
|
|
self.run_svd2repl = false;
|
|
self
|
|
}
|
|
/// Configure for renode targets
|
|
pub fn target_renode<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string());
|
|
self.stream = BuildStream::Release;
|
|
self.run_svd2repl = true;
|
|
self.utra_target = "renode".to_string();
|
|
self.loader = CrateSpec::Local("loader".to_string(), false);
|
|
self.kernel = CrateSpec::Local("xous-kernel".to_string(), false);
|
|
self
|
|
}
|
|
/// Configure for precursor targets. This is the default, but it's good practice
|
|
/// to call it anyways just in case the defaults change. The `soc_version` should
|
|
/// be just the gitrev of the soc version, not the entire feature name.
|
|
pub fn target_precursor<'a>(&'a mut self, soc_version: &str) -> &'a mut Builder {
|
|
self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string());
|
|
self.stream = BuildStream::Release;
|
|
self.utra_target = format!("precursor-{}", soc_version).to_string();
|
|
self.run_svd2repl = false;
|
|
self.loader = CrateSpec::Local("loader".to_string(), false);
|
|
self.kernel = CrateSpec::Local("xous-kernel".to_string(), false);
|
|
self
|
|
}
|
|
|
|
/// Configure for ARM targets
|
|
pub fn target_arm(&mut self) -> &mut Builder {
|
|
self.target = Some(crate::TARGET_TRIPLE_ARM.to_string());
|
|
self.stream = BuildStream::Debug;
|
|
self.utra_target = "atsama5d27".to_string();
|
|
self.run_svd2repl = false;
|
|
self.loader = CrateSpec::Local("loader".to_string(), false);
|
|
self.kernel = CrateSpec::Local("xous-kernel".to_string(), false);
|
|
self
|
|
}
|
|
|
|
/// Configure various Cramium targets
|
|
pub fn target_cramium_fpga<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string());
|
|
self.stream = BuildStream::Release;
|
|
self.utra_target = "cramium-fpga".to_string();
|
|
self.run_svd2repl = false;
|
|
self.loader = CrateSpec::Local("loader".to_string(), false);
|
|
self.kernel = CrateSpec::Local("xous-kernel".to_string(), false);
|
|
self
|
|
}
|
|
pub fn target_cramium_soc<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string());
|
|
self.stream = BuildStream::Release;
|
|
self.utra_target = "cramium-soc".to_string();
|
|
self.run_svd2repl = false;
|
|
self.loader = CrateSpec::Local("loader".to_string(), false);
|
|
self.kernel = CrateSpec::Local("xous-kernel".to_string(), false);
|
|
self
|
|
}
|
|
|
|
/// Override the default kernel. For example, to use the kernel from crates.io, specify as "xous-kernel@0.9.9"
|
|
#[allow(dead_code)]
|
|
pub fn use_kernel<'a>(&'a mut self, spec: &str) -> &'a mut Builder {
|
|
self.kernel = spec.into();
|
|
self
|
|
}
|
|
/// Override the default loader
|
|
#[allow(dead_code)]
|
|
pub fn use_loader<'a>(&'a mut self, spec: &str) -> &'a mut Builder {
|
|
self.loader = spec.into();
|
|
self
|
|
}
|
|
|
|
/// Add just one service
|
|
pub fn add_service<'a>(&'a mut self, service_spec: &str, xip: bool) -> &'a mut Builder {
|
|
let mut spec: CrateSpec = service_spec.into();
|
|
spec.set_xip(xip);
|
|
self.services.push(spec);
|
|
self
|
|
}
|
|
/// Add a list of services
|
|
pub fn add_services<'a>(&'a mut self, service_list: &Vec::<String>) -> &'a mut Builder {
|
|
for service in service_list {
|
|
self.services.push(service.as_str().into());
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Add just one app
|
|
#[allow(dead_code)]
|
|
pub fn add_app<'a>(&'a mut self, app_spec: &str, xip: bool) -> &'a mut Builder {
|
|
let mut spec: CrateSpec = app_spec.into();
|
|
spec.set_xip(xip);
|
|
self.apps.push(spec);
|
|
self
|
|
}
|
|
/// Add a list of apps
|
|
pub fn add_apps<'a>(&'a mut self, app_list: &Vec::<String>) -> &'a mut Builder {
|
|
for app in app_list {
|
|
self.apps.push(app.as_str().into());
|
|
}
|
|
self
|
|
}
|
|
|
|
/// add a feature to be passed on to services
|
|
pub fn add_feature<'a>(&'a mut self, feature: &str) -> &'a mut Builder {
|
|
self.features.push(feature.into());
|
|
self
|
|
}
|
|
/// remove a feature previously added by a previous call
|
|
#[allow(dead_code)]
|
|
pub fn remove_feature<'a>(&'a mut self, feature: &str) -> &'a mut Builder {
|
|
self.features.retain(|x| x != feature);
|
|
self
|
|
}
|
|
/// test if a feature is present
|
|
pub fn has_feature(&self, feature: &str) -> bool {
|
|
self.features.contains(&feature.to_string())
|
|
}
|
|
/// add a feature to be passed on to just the loader
|
|
pub fn add_loader_feature<'a>(&'a mut self, feature: &str) -> &'a mut Builder {
|
|
self.loader_features.push(feature.into());
|
|
self
|
|
}
|
|
/// add a feature to be passed on to just the loader
|
|
#[allow(dead_code)]
|
|
pub fn add_kernel_feature<'a>(&'a mut self, feature: &str) -> &'a mut Builder {
|
|
self.kernel_features.push(feature.into());
|
|
self
|
|
}
|
|
/// only build a hosted target. don't run it. Used exclusively to confirm that hosted mode builds in CI.
|
|
pub fn hosted_build_only<'a>(&'a mut self) -> &'a mut Builder {
|
|
self.dry_run = true;
|
|
self
|
|
}
|
|
|
|
/// The builder sets up all the cargo arguments to build a set of packages with features for a respective
|
|
/// target and stream. It also runs the build as well. It's meant to be called only by the `build()`
|
|
/// method, and it gets called repeatedly to build the kernel, loader, and services.
|
|
fn builder(
|
|
&self,
|
|
packages: &Vec::<CrateSpec>,
|
|
features: &Vec::<String>,
|
|
target: &Option<&str>,
|
|
// the stream is specified separately here because the loader is special-case always release
|
|
stream: BuildStream,
|
|
extra_args: &Vec::<String>,
|
|
no_default_features: bool,
|
|
) -> Result<Vec::<String>, DynError> {
|
|
// list of build artifacts, as full paths specific to the host OS
|
|
let mut artifacts = Vec::<String>::new();
|
|
// set up the list of arguments to cargo
|
|
// we have two streams we could build:
|
|
// - local crates are built with "build"
|
|
// - remote crates are built with "install"
|
|
let mut local_args = vec!["build"];
|
|
let mut remote_args = vec!["install", "--target-dir", "target"];
|
|
remote_args.push("--root");
|
|
let output_root = format!("{}/target/{}{}/",
|
|
project_root().into_os_string().into_string().unwrap(),
|
|
match target {
|
|
Some(t) => format!("{}/", t),
|
|
None => "".to_string(),
|
|
},
|
|
stream.to_str(),
|
|
);
|
|
remote_args.push(&output_root);
|
|
|
|
// modify the stream if release (debug is the default for builds; release is the default for installs)
|
|
match stream {
|
|
BuildStream::Release => {
|
|
local_args.push("--release");
|
|
}
|
|
BuildStream::Debug => {
|
|
remote_args.push("--debug");
|
|
}
|
|
}
|
|
|
|
// add any extra args. These are cargo-specific args, such as "--no-default-features"
|
|
for arg in extra_args.iter() {
|
|
local_args.push(arg);
|
|
remote_args.push(arg);
|
|
}
|
|
|
|
// set the target triple, if specified
|
|
// and determine the location of the build artifacts
|
|
if let Some(t) = target {
|
|
local_args.push("--target");
|
|
local_args.push(t);
|
|
remote_args.push("--target");
|
|
remote_args.push(t);
|
|
}
|
|
|
|
// add the packages
|
|
let mut local_pkgs = Vec::<&str>::new();
|
|
let mut remote_pkgs = Vec::<(&str, &str)>::new();
|
|
for pkg in packages.iter() {
|
|
match pkg {
|
|
CrateSpec::Local(name, _xip) => local_pkgs.push(&name),
|
|
CrateSpec::CratesIo(name, version, _xip) => remote_pkgs.push((&name, &version)),
|
|
_ => {},
|
|
}
|
|
}
|
|
|
|
if local_pkgs.len() > 0 {
|
|
for pkg in local_pkgs {
|
|
local_args.push("--package");
|
|
local_args.push(pkg);
|
|
artifacts.push(format!("{}{}", &output_root, pkg));
|
|
}
|
|
if no_default_features {
|
|
local_args.push("--no-default-features");
|
|
}
|
|
if features.len() > 0 {
|
|
for feature in features {
|
|
local_args.push("--features");
|
|
local_args.push(feature);
|
|
}
|
|
}
|
|
|
|
// emit debug
|
|
print!(" Command: cargo");
|
|
for &arg in local_args.iter() {
|
|
print!(" {}", arg);
|
|
}
|
|
println!();
|
|
// build
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&local_args)
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("Local build failed".into());
|
|
}
|
|
}
|
|
if remote_pkgs.len() > 0 {
|
|
// remote packages are installed one at a time
|
|
if no_default_features {
|
|
local_args.push("--no-default-features");
|
|
}
|
|
if features.len() > 0 {
|
|
for feature in features {
|
|
remote_args.push("--features");
|
|
remote_args.push(feature);
|
|
}
|
|
}
|
|
|
|
for (name, version) in remote_pkgs {
|
|
// emit debug
|
|
print!(" Command: cargo");
|
|
for &arg in remote_args.iter() {
|
|
print!(" {}", arg);
|
|
}
|
|
println!(" {} {}", name, version);
|
|
// build
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args([&remote_args[..], &[name, "--version", version].to_vec()[..]].concat())
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("Remote build failed".into());
|
|
}
|
|
artifacts.push(format!("{}bin/{}", &output_root, name));
|
|
}
|
|
}
|
|
|
|
Ok(artifacts)
|
|
}
|
|
|
|
pub fn split_xip(&self, services: Vec<String>) -> (Vec<String>, Vec<String>) {
|
|
let mut inie = Vec::<String>::new();
|
|
let mut inif = Vec::<String>::new();
|
|
for service in services.iter() {
|
|
let mut found = false;
|
|
for app in self.apps.iter() {
|
|
if let Some(n) = &app.name() {
|
|
if Path::new(service).file_name().unwrap_or_default().to_str().unwrap_or_default() ==
|
|
Path::new(n).file_name().unwrap_or_default().to_str().unwrap_or_default() {
|
|
if app.is_xip() {
|
|
inif.push(service.to_string());
|
|
} else {
|
|
inie.push(service.to_string());
|
|
}
|
|
found = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if found {
|
|
continue;
|
|
}
|
|
for serv in self.services.iter() {
|
|
if let Some(n) = &serv.name() {
|
|
if Path::new(service).file_name().unwrap_or_default().to_str().unwrap_or_default() ==
|
|
Path::new(n).file_name().unwrap_or_default().to_str().unwrap_or_default() {
|
|
if serv.is_xip() {
|
|
inif.push(service.to_string());
|
|
} else {
|
|
inie.push(service.to_string());
|
|
}
|
|
found = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if found {
|
|
continue;
|
|
}
|
|
// the service wasn't found in any of the other lists, mark it as non-xip
|
|
inie.push(service.to_string());
|
|
}
|
|
assert!(inie.len() + inif.len() == services.len());
|
|
(inie, inif)
|
|
}
|
|
|
|
/// Consume the builder and execute the configured build task. This handles dispatching all configurations,
|
|
/// including renode, hosted, and hardware targets.
|
|
pub fn build(mut self) -> Result<(), DynError> {
|
|
if self.apps.len() == 0 && self.services.len() == 0 {
|
|
// no services were specified - don't build anything
|
|
return Ok(())
|
|
}
|
|
|
|
// ------ configure target generation feature flags ------
|
|
let target = if self.utra_target.contains("renode") {
|
|
self.features.push("renode".into());
|
|
self.loader_features.push("renode".into());
|
|
self.kernel_features.push("renode".into());
|
|
Some(crate::TARGET_TRIPLE_RISCV32)
|
|
} else if self.utra_target.contains("hosted") {
|
|
self.features.push("hosted".into());
|
|
// there is no loader in hosed mode
|
|
self.kernel_features.push("hosted".into());
|
|
None
|
|
} else if self.utra_target.contains("precursor") {
|
|
self.features.push("precursor".into());
|
|
self.features.push(format!("utralib/{}", &self.utra_target));
|
|
self.kernel_features.push("precursor".into());
|
|
self.kernel_features.push(format!("utralib/{}", &self.utra_target));
|
|
self.loader_features.push("precursor".into());
|
|
self.loader_features.push(format!("utralib/{}", &self.utra_target));
|
|
Some(crate::TARGET_TRIPLE_RISCV32)
|
|
} else if self.utra_target.contains("atsama5d2") {
|
|
self.kernel_features.push("atsama5d27".into());
|
|
self.loader_features.push("atsama5d27".into());
|
|
Some(crate::TARGET_TRIPLE_ARM)
|
|
} else if self.utra_target.contains("cramium-fpga") {
|
|
self.features.push("cramium-fpga".into());
|
|
self.features.push(format!("utralib/{}", &self.utra_target));
|
|
self.kernel_features.push("cramium-fpga".into());
|
|
self.kernel_features.push(format!("utralib/{}", &self.utra_target));
|
|
self.loader_features.push("cramium-fpga".into());
|
|
self.loader_features.push(format!("utralib/{}", &self.utra_target));
|
|
Some(crate::TARGET_TRIPLE_RISCV32)
|
|
} else if self.utra_target.contains("cramium-soc") {
|
|
self.features.push("cramium-soc".into());
|
|
self.features.push(format!("utralib/{}", &self.utra_target));
|
|
self.kernel_features.push("cramium-soc".into());
|
|
self.kernel_features.push(format!("utralib/{}", &self.utra_target));
|
|
self.loader_features.push("cramium-soc".into());
|
|
self.loader_features.push(format!("utralib/{}", &self.utra_target));
|
|
Some(crate::TARGET_TRIPLE_RISCV32)
|
|
} else {
|
|
return Err("Target unknown: please check your UTRA target".into());
|
|
};
|
|
|
|
crate::utils::ensure_compiler(&self.target.as_ref().map(|s| s.as_str()), false, false)?;
|
|
self.locale_override(); // apply the locale override
|
|
|
|
// ------ build the services & apps ------
|
|
let mut app_names = Vec::<String>::new();
|
|
for app in self.apps.iter() {
|
|
match app {
|
|
CrateSpec::Local(name, _xip) => app_names.push(name.into()),
|
|
CrateSpec::CratesIo(name, _version, _xip) => app_names.push(name.into()),
|
|
_ => {}
|
|
}
|
|
}
|
|
generate_app_menus(&app_names);
|
|
let mut services_path = self.builder(
|
|
&[&self.services[..], &self.apps[..]].concat(),
|
|
&self.features,
|
|
&target,
|
|
self.stream,
|
|
&vec![],
|
|
false
|
|
)?;
|
|
|
|
// ------ either create an image, or launch hosted mode ------
|
|
if self.target.is_none() { // hosted mode doesn't specify a cross-compilation target!
|
|
// throw a warning if prebuilts are specified for hosted mode
|
|
for item in [&self.services[..], &self.apps[..]].concat() {
|
|
match item {
|
|
CrateSpec::Prebuilt(name, _, _xip) => println!("Warning! Pre-built binaries not supported for hosted mode ({})", name),
|
|
_ => {},
|
|
}
|
|
}
|
|
// fixup windows paths
|
|
if cfg!(windows) {
|
|
for service in services_path.iter_mut() {
|
|
service.push_str(".exe")
|
|
}
|
|
}
|
|
let mut hosted_args = vec!["run"];
|
|
match self.stream {
|
|
BuildStream::Release => hosted_args.push("--release"),
|
|
_ => {}
|
|
}
|
|
hosted_args.push("--");
|
|
for service in services_path.iter() {
|
|
hosted_args.push(service);
|
|
}
|
|
// jam in any pre-built local binary files that were specified
|
|
let binary_files_string = self.enumerate_binary_files()?;
|
|
let mut binary_files: Vec<&str> = binary_files_string.iter().map(|s| s.as_ref()).collect();
|
|
hosted_args.append(&mut binary_files);
|
|
|
|
if !self.dry_run {
|
|
let mut dir = project_root();
|
|
dir.push("kernel");
|
|
println!("Starting hosted mode...");
|
|
print!(" Command: cargo");
|
|
for arg in &hosted_args {
|
|
print!(" {}", arg);
|
|
}
|
|
println!();
|
|
let status = Command::new(cargo())
|
|
.current_dir(dir)
|
|
.args(&hosted_args)
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("cargo run failed to launch hosted mode".into());
|
|
}
|
|
} else {
|
|
// confirm the kernel can build before quitting
|
|
let _ = self.builder(
|
|
&vec![CrateSpec::Local("xous-kernel".into(), false)],
|
|
&self.kernel_features,
|
|
&target,
|
|
self.stream,
|
|
&vec![],
|
|
false,
|
|
)?;
|
|
println!("Dry run requested: only building and not running");
|
|
}
|
|
} else {
|
|
// ------ build the kernel ------
|
|
let mut kernel_extra = vec![];
|
|
if self.kernel_disable_defaults {
|
|
kernel_extra.push("--no-default-features".to_string());
|
|
}
|
|
let kernel_path = self.builder(
|
|
&vec![self.kernel.clone()],
|
|
&self.kernel_features,
|
|
&target,
|
|
self.stream,
|
|
&kernel_extra,
|
|
false,
|
|
)?;
|
|
|
|
// ------ build the loader ------
|
|
// stash any LTO settings applied to the kernel; proper layout of the loader
|
|
// block depends on the loader being compact and highly optimized.
|
|
let existing_lto = std::env::var("CARGO_PROFILE_RELEASE_LTO")
|
|
.map(|v| Some(v))
|
|
.unwrap_or(None);
|
|
let existing_codegen_units = std::env::var("CARGO_PROFILE_RELEASE_CODEGEN_UNITS")
|
|
.map(|v| Some(v))
|
|
.unwrap_or(None);
|
|
// these settings will generate the most compact code (but also the hardest to debug)
|
|
std::env::set_var("CARGO_PROFILE_RELEASE_LTO", "true");
|
|
std::env::set_var("CARGO_PROFILE_RELEASE_CODEGEN_UNITS", "1");
|
|
let mut loader_extra = vec![];
|
|
if self.loader_disable_defaults {
|
|
loader_extra.push("--no-default-features".to_string());
|
|
}
|
|
let loader = self.builder(
|
|
&vec![self.loader.clone()],
|
|
&self.loader_features,
|
|
&target,
|
|
BuildStream::Release, // loader doesn't fit if you build with Debug
|
|
&loader_extra,
|
|
true, // loader builds without any default features
|
|
)?;
|
|
// restore the LTO settings
|
|
if let Some(existing) = existing_lto {
|
|
std::env::set_var("CARGO_PROFILE_RELEASE_LTO", existing);
|
|
}
|
|
if let Some(existing) = existing_codegen_units {
|
|
std::env::set_var("CARGO_PROFILE_RELEASE_CODEGEN_UNITS", existing);
|
|
}
|
|
|
|
// ------ if targeting renode, regenerate the Platform file -----
|
|
if self.run_svd2repl {
|
|
Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&[
|
|
"run",
|
|
"-p",
|
|
"svd2repl",
|
|
"--",
|
|
"utralib/renode/renode.svd",
|
|
"emulation/soc/betrusted-soc.repl",
|
|
])
|
|
.status()?;
|
|
}
|
|
|
|
// ---------- extract SVD file path, as computed by utralib ----------
|
|
let svd_spec_path = format!("target/{}/{}/build/SVD_PATH", self.target.as_ref().expect("target"), self.stream.to_str());
|
|
let mut svd_spec_file = OpenOptions::new()
|
|
.read(true)
|
|
.open(&svd_spec_path)?;
|
|
let mut svd_path_str = String::new();
|
|
svd_spec_file.read_to_string(&mut svd_path_str)?;
|
|
let mut svd_paths = Vec::new();
|
|
for line in svd_path_str.lines() {
|
|
svd_paths.push(line.to_owned());
|
|
}
|
|
|
|
// ---------- install any pre-built packages ----------
|
|
services_path.append(&mut self.fetch_prebuilds()?);
|
|
services_path.append(&mut self.enumerate_binary_files()?);
|
|
|
|
// --------- package up and sign a binary image ----------
|
|
let (inie, inif) = self.split_xip(services_path.clone());
|
|
let output_bundle = self.create_image(
|
|
&kernel_path[0],
|
|
&inie,
|
|
&inif,
|
|
svd_paths,
|
|
)?;
|
|
println!();
|
|
println!(
|
|
"Kernel+Init bundle is available at {}",
|
|
output_bundle.display()
|
|
);
|
|
|
|
let mut loader_bin = output_bundle.parent().unwrap().to_owned();
|
|
loader_bin.push("loader.bin");
|
|
let mut loader_presign = output_bundle.parent().unwrap().to_owned();
|
|
loader_presign.push("loader_presign.bin");
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&[
|
|
"run",
|
|
"--package",
|
|
"tools",
|
|
"--bin",
|
|
"copy-object",
|
|
"--",
|
|
&loader[0],
|
|
loader_presign.as_os_str().to_str().unwrap(),
|
|
])
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("cargo build failed".into());
|
|
}
|
|
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&[
|
|
"run",
|
|
"--package",
|
|
"tools",
|
|
"--bin",
|
|
"sign-image",
|
|
"--",
|
|
"--loader-image",
|
|
loader_presign.to_str().unwrap(),
|
|
"--loader-key",
|
|
&self.loader_key,
|
|
"--loader-output",
|
|
loader_bin.to_str().unwrap(),
|
|
"--min-xous-ver",
|
|
&self.min_ver,
|
|
])
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("loader image sign failed".into());
|
|
}
|
|
|
|
let mut xous_img_path = output_bundle.parent().unwrap().to_owned();
|
|
xous_img_path.push("xous.img");
|
|
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&[
|
|
"run",
|
|
"--package",
|
|
"tools",
|
|
"--bin",
|
|
"sign-image",
|
|
"--",
|
|
"--kernel-image",
|
|
output_bundle.to_str().unwrap(),
|
|
"--kernel-key",
|
|
&self.kernel_key,
|
|
"--kernel-output",
|
|
xous_img_path.to_str().unwrap(),
|
|
"--min-xous-ver",
|
|
&self.min_ver,
|
|
// "--defile",
|
|
])
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err("kernel image sign failed".into());
|
|
}
|
|
|
|
println!();
|
|
println!("Signed loader at {}", loader_bin.display());
|
|
println!("Signed kernel at {}", xous_img_path.display());
|
|
}
|
|
self.locale_restore(); // restore the locale if it was overridden
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn create_image(
|
|
&self,
|
|
kernel: &String,
|
|
init: &[String],
|
|
inif: &[String],
|
|
memory_spec: Vec::<String>,
|
|
) -> Result<PathBuf, DynError> {
|
|
let stream = self.stream.to_str();
|
|
let mut args = vec!["run", "--package", "tools", "--bin", "create-image", "--"];
|
|
|
|
let mut output_file = PathBuf::new();
|
|
output_file.push("target");
|
|
output_file.push(self.target.as_ref().expect("target"));
|
|
output_file.push(stream);
|
|
output_file.push("xous_presign.img");
|
|
args.push(output_file.to_str().unwrap());
|
|
|
|
args.push("--kernel");
|
|
args.push(kernel);
|
|
|
|
for i in init {
|
|
args.push("--init");
|
|
args.push(i);
|
|
}
|
|
|
|
for i in inif {
|
|
args.push("--inif");
|
|
args.push(i);
|
|
}
|
|
|
|
if memory_spec.len() == 1 {
|
|
args.push("--svd");
|
|
args.push(&memory_spec[0])
|
|
} else {
|
|
args.push("--svd");
|
|
args.push(&memory_spec[0]);
|
|
for spec in memory_spec[1..].iter() {
|
|
args.push("--extra-svd");
|
|
args.push(&spec);
|
|
}
|
|
}
|
|
|
|
let status = Command::new(cargo())
|
|
.current_dir(project_root())
|
|
.args(&args)
|
|
.status()?;
|
|
|
|
if !status.success() {
|
|
return Err("cargo build failed".into());
|
|
}
|
|
Ok(project_root().join(output_file))
|
|
}
|
|
|
|
fn fetch_prebuilds(&self) -> Result<Vec::<String>, DynError> {
|
|
let mut paths = Vec::<String>::new();
|
|
for item in [&self.services[..], &self.apps[..]].concat() {
|
|
match item {
|
|
CrateSpec::Prebuilt(name, url, _xip) => {
|
|
let exec_name = format!("target/{}/{}/{}", self.target.as_ref().expect("target"), self.stream.to_str(), name);
|
|
println!("Fetching {} executable from build server...", name);
|
|
let mut exec_file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&exec_name)
|
|
.expect("Can't open our version file for writing");
|
|
let mut freader = ureq::get(&url)
|
|
.call()?
|
|
.into_reader();
|
|
std::io::copy(&mut freader, &mut exec_file)?;
|
|
println!(
|
|
"{} pre-built exec is {} bytes",
|
|
name,
|
|
exec_file.metadata().unwrap().len()
|
|
);
|
|
paths.push(exec_name);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(paths)
|
|
}
|
|
|
|
fn enumerate_binary_files(&self) -> Result<Vec::<String>, DynError> {
|
|
let mut paths = Vec::<String>::new();
|
|
for item in [&self.services[..], &self.apps[..]].concat() {
|
|
match item {
|
|
CrateSpec::BinaryFile(path, _xip) => {
|
|
paths.push(path);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(paths)
|
|
}
|
|
|
|
fn locale_override(&mut self) {
|
|
if let Some(locale) = &self.locale_override {
|
|
{ // stash the existing locale
|
|
let mut locale_file = OpenOptions::new()
|
|
.read(true)
|
|
.open("locales/src/locale.rs")
|
|
.expect("Can't open locale file for reading");
|
|
locale_file.read_to_string(&mut self.locale_stash).unwrap();
|
|
}
|
|
|
|
let mut locale_override = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open("locales/src/locale.rs")
|
|
.expect("Can't open locale for modification");
|
|
write!(
|
|
locale_override,
|
|
"pub const LANG: &str = \"{}\";\n",
|
|
locale
|
|
).unwrap();
|
|
}
|
|
}
|
|
|
|
fn locale_restore(&self) {
|
|
if self.locale_override.is_some() {
|
|
let mut locale_restore = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open("locales/src/locale.rs")
|
|
.expect("Can't open locale for modification");
|
|
write!(
|
|
locale_restore,
|
|
"{}",
|
|
self.locale_stash
|
|
).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn cargo() -> String {
|
|
env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
|
|
}
|
|
|
|
pub fn project_root() -> PathBuf {
|
|
Path::new(&env!("CARGO_MANIFEST_DIR"))
|
|
.ancestors()
|
|
.nth(1)
|
|
.unwrap()
|
|
.to_path_buf()
|
|
}
|