ableos/repbuild/src/main.rs

515 lines
20 KiB
Rust

/*
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
* Copyright (c) 2022, able <abl3theabove@gmail.com>
*
* SPDX-License-Identifier: MPL-2.0
*/
use colored::*;
use std::{
fs::{self, File},
os::fd::AsRawFd,
process::Command,
};
use udisks::{
filesystem::{MountOptions, UnMountOptions},
manager::LoopSetupOptions,
};
struct Options {
pub subcommand: Subcommand,
pub arguments: Vec<String>,
}
enum Subcommand {
BuildImage,
Doc,
Help,
Run,
Empty,
/// Run all tests for all architectures
Test,
Unknown(String),
}
impl Subcommand {
fn from_str<S: AsRef<str>>(str: S) -> Subcommand {
match str.as_ref() {
"build-image" => Subcommand::BuildImage,
"doc" => Subcommand::Doc,
"help" => Subcommand::Help,
"run" | "r" => Subcommand::Run,
"test" | "t" => Subcommand::Test,
"" => Subcommand::Empty,
unknown => Subcommand::Unknown(unknown.to_string()),
}
}
}
enum MachineType {
X86_64,
RiscV64,
AArch64,
Unknown(String),
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = options();
match options.subcommand {
Subcommand::BuildImage => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
match machine(machine_text) {
MachineType::X86_64 => {
// Cleanup
// NOTE: we are not unwrapping these, as we don't want this to fail if they
// don't exist yet, probably not the best idea tho.
// FIXME: figure out a better way to ignore errors about these not existing
#[allow(unused_must_use)]
{
fs::remove_dir_all("./limine");
fs::remove_dir_all("./disk");
fs::remove_file("./disk.img");
}
// Build ableOS in release mode
Command::new("cargo")
.args(["build", "--release"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
// Create disk directory
fs::create_dir("./disk").unwrap();
// Clone limine 3.X binaries
Command::new("git")
.arg("clone")
.arg("https://github.com/limine-bootloader/limine.git")
.arg("--branch=v3.0-branch-binary")
.arg("--depth=1")
.status()
.unwrap();
println!("{}", "Building limine".bold());
Command::new("make")
.args(["-C", "limine"])
.status()
.unwrap();
println!("{}", "Allocating new disk image".bold());
Command::new("fallocate")
.args(["-l", "256M", "disk.img"])
.status()
.unwrap();
println!("{}", "Partitioning disk image".bold());
let dbus_conn = zbus::blocking::Connection::system()?;
// Setup loop device
let disk_img = File::options().read(true).write(true).open("disk.img")?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
// Create MBR
udisks::block::BlockProxyBlocking::builder(&dbus_conn)
.path(&loopdev)?
.build()?
.format("dos", Default::default())?;
// Create and format partition
let filesystem =
udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(&loopdev)?
.build()?
.create_partition_and_format(
0,
0,
"",
"",
Default::default(),
"ext2",
[("take-ownership", true.into())].into_iter().collect(),
)?;
let fsproxy = udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(&filesystem)?
.build()?;
// Obtain mountpoint
let mountpoint = loop {
if let Some(m) = fsproxy.mount_points()?.get(0) {
break m.to_string();
}
};
// copy ./base/* over to ./disk
Command::new("sh")
.arg("-c")
.arg(format!("cp -r ./base/* {mountpoint}"))
.status()?;
// copy ./limine/limine.sys over to ./disk/boot
Command::new("cp")
.args(["./limine/limine.sys", &format!("{mountpoint}/boot")])
.status()?;
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/release/ableos")
.arg(&format!("{mountpoint}/boot/kernel"))
.status()?;
// Unmount the filesystem (and the rest of things will follow)
fsproxy.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
println!("{}", "Deploying limine".bold());
Command::new("./limine/limine-deploy")
.arg("./disk.img")
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
_ => {
eprintln!(
"{}: build-image not implemented for this machine type",
"error".red().bold(),
);
}
}
}
Subcommand::Test => {
Command::new("cargo")
.args(["test", "--target=json_targets/x86_64-ableos.json"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
// panic!("Test Infrastructure missing");
}
Subcommand::Doc => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
match machine(machine_text) {
MachineType::X86_64 => {
Command::new("cargo")
.args(["doc", "--open"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::RiscV64 => {
Command::new("cargo")
.args(["doc", "--open", "--target=riscv64gc-unknown-none-elf"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::AArch64 => {
Command::new("cargo")
.args(["doc", "--open", "--target=json_targets/aarch64-ableos.json"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
}
}
Subcommand::Help => help(),
Subcommand::Run => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
let debug = options.arguments.get(1).cloned().unwrap_or_default();
let debug = matches!(debug.as_str(), "--debug" | "--dbg" | "-d");
match machine(machine_text) {
MachineType::X86_64 if debug => {
// Build ableOS
Command::new("cargo")
.arg("build")
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
// Setup loopback device for disk.img, with partitions
// FIXME: don't do ths if running without changes
// Setup loop device
let disk_img = File::options().read(true).write(true).open("disk.img")?;
let dbus_conn = zbus::blocking::Connection::system()?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
let parts = udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(loopdev)?
.build()?
.partitions()?;
let fsobjpath = parts.get(0).ok_or("missing boot partition")?;
let mountpoint =
udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(fsobjpath)?
.build()?
.mount(MountOptions {
no_user_interaction: true,
fs_type: String::new(),
mount_options: String::new(),
})?;
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/debug/ableos")
.arg(format!("{mountpoint}/boot/kernel"))
.status()
.unwrap();
udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(fsobjpath)?
.build()?
.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
// run qemu with "-S", "-gdb", "tcp:9000"
Command::new("qemu-system-x86_64")
.args(["-device", "piix4-ide,id=ide"])
.arg("-drive")
.arg("file=./disk.img,format=raw,if=none,id=disk")
.args(["-device", "ide-hd,drive=disk,bus=ide.0"])
// .arg("--nodefaults")
.args(["-cpu", "Broadwell-v3"])
.args(["-m", "4G"])
.args(["-serial", "stdio"])
.args(["-smp", "cores=2"])
// .args(["-soundhw", "pcspk"])
// .args(["-device", "VGA"])
// .args(["-device", "virtio-gpu-pci"])
.args(["-device", "vmware-svga"])
.args(["-device", "sb16"])
// .args(["-machine", "pcspk-audiodev=0"])
// .args(["-qmp", "unix:../qmp-sock,server,nowait"])
.args(["-S", "-gdb", "tcp:9000"])
.status()
.unwrap();
}
MachineType::X86_64 => {
// Build ableOS
Command::new("cargo")
.args(["build", "--release"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
// Setup loopback device for disk.img, with partitions
// FIXME: don't do ths if running without changes
let disk_img = File::options().read(true).write(true).open("disk.img")?;
let dbus_conn = zbus::blocking::Connection::system()?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
let parts = udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(loopdev)?
.build()?
.partitions()?;
let fsproxy = udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(&parts[0])?
.build()?;
// Obtain mountpoint
let mountpoint = loop {
if let Some(m) = fsproxy.mount_points()?.get(0) {
break m.to_string();
}
};
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/release/ableos")
.arg(format!("{mountpoint}/boot/kernel"))
.status()
.unwrap();
fsproxy.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
// run qemu
Command::new("qemu-system-x86_64")
.args(["-device", "piix4-ide,id=ide"])
.arg("-drive")
.arg("file=./disk.img,format=raw,if=none,id=disk")
.args(["-device", "ide-hd,drive=disk,bus=ide.0"])
// .arg("--nodefaults")
.args(["-cpu", "Broadwell-v3"])
.args(["-m", "4G"])
.args(["-serial", "stdio"])
.args(["-smp", "cores=2"])
// .args(["-soundhw", "pcspk"])
// .args(["-device", "VGA"])
// .args(["-device", "virtio-gpu-pci"])
.args(["-device", "vmware-svga"])
.args(["-device", "sb16"])
// .args(["-machine", "pcspk-audiodev=0"])
// .args(["-qmp", "unix:../qmp-sock,server,nowait"])
.status()
.unwrap();
}
MachineType::RiscV64 if debug => {
eprintln!(
"{}: debug is not implemented for riscv64",
"error".red().bold()
);
}
MachineType::RiscV64 => {
Command::new("cargo")
.args(["build", "--release", "--target=riscv64gc-unknown-none-elf"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
Command::new("qemu-system-riscv64")
.args(["-machine", "virt"])
.args(["-cpu", "rv64"])
.args(["-smp", "8"])
.args(["-m", "128M"])
.arg("-bios")
.arg("src/arch/riscv/firmwear/opensbi-riscv64-generic-fw_jump.bin")
.arg("-kernel")
.arg("target/riscv64gc-unknown-none-elf/release/ableos")
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::AArch64 if debug => {
eprintln!(
"{}: debug is not implemented for aarch64",
"error".red().bold()
);
}
MachineType::AArch64 => {
Command::new("cargo")
.args([
"build",
"--release",
"--target=json_targets/aarch64-ableos.json",
])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
Command::new("qemu-system-aarch64")
.args(["-machine", "virt"])
.args(["-m", "1024M"])
.args(["-cpu", "cortex-a53"])
.args(["-kernel", "target/aarch64-ableos/release/ableos"])
.args(["-device", "virtio-keyboard"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
}
}
Subcommand::Empty => {
eprintln!("{}: no subcommand passed", "error".red().bold());
help();
}
Subcommand::Unknown(unknown) => {
eprintln!(
"{}: unknown subcommand `{}`",
"error".red().bold(),
unknown.bold()
);
help();
}
}
Ok(())
}
fn options() -> Options {
let subcommand = std::env::args().nth(1).unwrap_or_default();
let arguments = std::env::args().skip(2).collect();
Options {
subcommand: Subcommand::from_str(subcommand),
arguments,
}
}
fn machine<S: AsRef<str>>(text: S) -> MachineType {
match text.as_ref() {
"x86" | "x86_64" => MachineType::X86_64,
"riscv" | "riscv64" => MachineType::RiscV64,
"arm" | "arm64" | "aarch64" => MachineType::AArch64,
"" => {
eprintln!(
"{}: no machine type passed, defaulting to x86_64",
"warning".yellow().bold()
);
MachineType::X86_64
}
unknown => MachineType::Unknown(unknown.to_string()),
}
}
fn help() {
todo!("`help`")
}