mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-22 08:59:37 +00:00
feat(build): add flat-bin kind and FAT [archive] packing for OS-dev
This commit is contained in:
272
src/core/build/archive.rs
Normal file
272
src/core/build/archive.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
// DCR — Cargo-like C/C++ project manager.
|
||||
//
|
||||
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::core::build_config::ArchiveConfig;
|
||||
use fatfs::{FileSystem, FormatVolumeOptions, format_volume};
|
||||
use glob::glob;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
struct OffsetFile<T> {
|
||||
inner: T,
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
impl<T: Read> Read for OffsetFile<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Write for OffsetFile<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.inner.write(buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Seek> Seek for OffsetFile<T> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
match pos {
|
||||
SeekFrom::Start(n) => {
|
||||
let actual = self.inner.seek(SeekFrom::Start(n + self.offset))?;
|
||||
if actual < self.offset {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok(actual - self.offset)
|
||||
}
|
||||
}
|
||||
SeekFrom::Current(n) => {
|
||||
let actual = self.inner.seek(SeekFrom::Current(n))?;
|
||||
if actual < self.offset {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok(actual - self.offset)
|
||||
}
|
||||
}
|
||||
SeekFrom::End(n) => {
|
||||
let actual = self.inner.seek(SeekFrom::End(n))?;
|
||||
if actual < self.offset {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok(actual - self.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_size(s: &str) -> Result<u64, String> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let mut num_str = String::new();
|
||||
let mut unit = String::new();
|
||||
for c in s.chars() {
|
||||
if c.is_ascii_digit() || c == '.' {
|
||||
num_str.push(c);
|
||||
} else {
|
||||
unit.push(c);
|
||||
}
|
||||
}
|
||||
let val: f64 = num_str
|
||||
.parse()
|
||||
.map_err(|_| format!("invalid size number: {num_str}"))?;
|
||||
let unit = unit.trim().to_lowercase();
|
||||
let multiplier = match unit.as_str() {
|
||||
"k" | "kb" => 1024.0,
|
||||
"m" | "mb" => 1024.0 * 1024.0,
|
||||
"g" | "gb" => 1024.0 * 1024.0 * 1024.0,
|
||||
"" => 1.0,
|
||||
_ => return Err(format!("unknown unit: {unit}")),
|
||||
};
|
||||
Ok((val * multiplier) as u64)
|
||||
}
|
||||
|
||||
pub fn pack_archive(
|
||||
project_root: &Path,
|
||||
config: &ArchiveConfig,
|
||||
profile: &str,
|
||||
) -> Result<(), String> {
|
||||
let output_substituted = config.output.replace("{profile}", profile);
|
||||
let output_path = project_root.join(&output_substituted);
|
||||
if let Some(parent) = output_path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("failed to create output parent dir: {e}"))?;
|
||||
}
|
||||
|
||||
let size = if let Some(sz_str) = &config.size {
|
||||
parse_size(sz_str)?
|
||||
} else {
|
||||
1474560
|
||||
};
|
||||
|
||||
let offset = if let Some(off_str) = &config.offset {
|
||||
parse_size(off_str)?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(&output_path)
|
||||
.map_err(|e| format!("failed to open output file: {e}"))?;
|
||||
|
||||
let current_len = file.metadata().map(|m| m.len()).unwrap_or(0);
|
||||
if current_len < size {
|
||||
file.set_len(size)
|
||||
.map_err(|e| format!("failed to set image file size: {e}"))?;
|
||||
}
|
||||
|
||||
if offset == 0
|
||||
&& let Some(boot_rel) = &config.bootsector
|
||||
{
|
||||
let boot_rel_substituted = boot_rel.replace("{profile}", profile);
|
||||
let boot_path = project_root.join(&boot_rel_substituted);
|
||||
if boot_path.is_file() {
|
||||
let mut boot_file =
|
||||
File::open(&boot_path).map_err(|e| format!("failed to open bootsector: {e}"))?;
|
||||
let mut boot_bytes = vec![0; 512];
|
||||
let _ = boot_file
|
||||
.read(&mut boot_bytes)
|
||||
.map_err(|e| format!("failed to read bootsector: {e}"))?;
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.map_err(|e| format!("failed to seek to start: {e}"))?;
|
||||
file.write_all(&boot_bytes)
|
||||
.map_err(|e| format!("failed to write bootsector: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.map_err(|e| format!("failed to seek to start: {e}"))?;
|
||||
|
||||
let mut wrapper = OffsetFile {
|
||||
inner: file,
|
||||
offset,
|
||||
};
|
||||
|
||||
let label = config.label.as_deref().unwrap_or("VOLUME");
|
||||
let mut label_bytes = [b' '; 11];
|
||||
let bytes_to_copy = label.len().min(11);
|
||||
label_bytes[..bytes_to_copy].copy_from_slice(&label.as_bytes()[..bytes_to_copy]);
|
||||
|
||||
let fat_type = match config.format.to_lowercase().as_str() {
|
||||
"fat12" => Some(fatfs::FatType::Fat12),
|
||||
"fat16" => Some(fatfs::FatType::Fat16),
|
||||
"fat32" => Some(fatfs::FatType::Fat32),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut options = FormatVolumeOptions::new().volume_label(label_bytes);
|
||||
if let Some(t) = fat_type {
|
||||
options = options.fat_type(t);
|
||||
}
|
||||
|
||||
format_volume(&mut wrapper, options)
|
||||
.map_err(|e| format!("failed to format FAT volume: {e}"))?;
|
||||
|
||||
wrapper
|
||||
.seek(SeekFrom::Start(0))
|
||||
.map_err(|e| format!("failed to seek to start after format: {e}"))?;
|
||||
|
||||
let fs = FileSystem::new(wrapper, fatfs::FsOptions::new())
|
||||
.map_err(|e| format!("failed to mount FAT fs: {e}"))?;
|
||||
let root_dir = fs.root_dir();
|
||||
|
||||
for layout in &config.layout {
|
||||
let from_substituted = layout.from.replace("{profile}", profile);
|
||||
let from_pattern = project_root
|
||||
.join(&from_substituted)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let paths = if from_pattern.contains('*') || from_pattern.contains('?') {
|
||||
let mut matched = Vec::new();
|
||||
if let Ok(entries) = glob(&from_pattern) {
|
||||
for path in entries.flatten() {
|
||||
matched.push(path);
|
||||
}
|
||||
}
|
||||
matched
|
||||
} else {
|
||||
let p = project_root.join(&from_substituted);
|
||||
if p.exists() { vec![p] } else { Vec::new() }
|
||||
};
|
||||
|
||||
for path in paths {
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||
if filename.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut target_filename = filename.to_string();
|
||||
let mut target_dir_str = layout.to.trim_matches('/');
|
||||
|
||||
let is_glob = from_pattern.contains('*') || from_pattern.contains('?');
|
||||
if !is_glob
|
||||
&& !layout.to.ends_with('/')
|
||||
&& let Some(last_part) = Path::new(&layout.to).file_name().and_then(|n| n.to_str())
|
||||
&& last_part.contains('.')
|
||||
{
|
||||
target_filename = last_part.to_string();
|
||||
let parent_path = Path::new(&layout.to).parent().unwrap_or(Path::new(""));
|
||||
target_dir_str = parent_path.to_str().unwrap_or("").trim_matches('/');
|
||||
}
|
||||
|
||||
let mut current_dir = root_dir.clone();
|
||||
if !target_dir_str.is_empty() {
|
||||
let parts = target_dir_str.split('/');
|
||||
for part in parts {
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
current_dir = match current_dir.open_dir(part) {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => current_dir
|
||||
.create_dir(part)
|
||||
.map_err(|e| format!("failed to create dir {part}: {e}"))?,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let mut out_file = current_dir
|
||||
.create_file(&target_filename)
|
||||
.map_err(|e| format!("failed to create file {target_filename}: {e}"))?;
|
||||
let mut in_file =
|
||||
File::open(&path).map_err(|e| format!("failed to open input file: {e}"))?;
|
||||
let mut buffer = Vec::new();
|
||||
in_file
|
||||
.read_to_end(&mut buffer)
|
||||
.map_err(|e| format!("failed to read input file: {e}"))?;
|
||||
out_file
|
||||
.write_all(&buffer)
|
||||
.map_err(|e| format!("failed to write to FAT file {target_filename}: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -19,9 +19,130 @@ use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::common;
|
||||
use crate::platform;
|
||||
use crate::utils::build::is_bare_metal_target;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
pub(crate) fn artifact_dir(ctx: &BuildContext) -> PathBuf {
|
||||
match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).to_path_buf(),
|
||||
None => Path::new("./target").join(ctx.profile),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn flat_output_path(ctx: &BuildContext) -> String {
|
||||
let name = ctx.output_filename.unwrap_or(ctx.project_name);
|
||||
let ext = ctx.output_extension.unwrap_or("bin");
|
||||
let file = if ext.is_empty() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{name}.{ext}")
|
||||
};
|
||||
artifact_dir(ctx).join(file).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn flat_source_output_path(ctx: &BuildContext, source: &str) -> String {
|
||||
let stem = Path::new(source)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("out");
|
||||
let ext = ctx.output_extension.unwrap_or("bin");
|
||||
let file = if ext.is_empty() {
|
||||
stem.to_string()
|
||||
} else {
|
||||
format!("{stem}.{ext}")
|
||||
};
|
||||
artifact_dir(ctx).join(file).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
fn resolve_objcopy() -> Result<String, String> {
|
||||
for candidate in ["llvm-objcopy", "objcopy", "gobjcopy"] {
|
||||
if Command::new(candidate)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(candidate.to_string());
|
||||
}
|
||||
}
|
||||
Err(
|
||||
"flat-bin requires llvm-objcopy, objcopy, or gobjcopy in PATH \
|
||||
(install binutils or LLVM tools)"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn objcopy_binary(ctx: &BuildContext, input: &str, output: &str) -> Result<(), String> {
|
||||
if let Some(parent) = Path::new(output).parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| format!("output dir error: {e}"))?;
|
||||
}
|
||||
if !common::needs_rebuild(input, output) {
|
||||
return Ok(());
|
||||
}
|
||||
let tool = resolve_objcopy()?;
|
||||
let mut cmd = Command::new(&tool);
|
||||
cmd.arg("-O").arg("binary").arg(input).arg(output);
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
}
|
||||
common::run_command_sync_output(&mut cmd)
|
||||
}
|
||||
|
||||
pub(crate) fn link_flat_binary(
|
||||
ctx: &BuildContext,
|
||||
objects: &[String],
|
||||
default_linker: &str,
|
||||
start_time: Instant,
|
||||
) -> Result<f64, String> {
|
||||
let out_path = flat_output_path(ctx);
|
||||
if !common::needs_link(objects, &out_path) {
|
||||
return Ok(common::elapsed_secs(start_time));
|
||||
}
|
||||
|
||||
let intermediate = {
|
||||
let dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("obj dir error: {e}"))?;
|
||||
dir.join(format!("{}.flat.elf", ctx.project_name))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or(default_linker));
|
||||
cmd.arg("-nostdlib").arg("-static");
|
||||
for obj in objects {
|
||||
cmd.arg(obj);
|
||||
}
|
||||
for dir in ctx.lib_dirs {
|
||||
cmd.arg(format!("-L{dir}"));
|
||||
}
|
||||
for lib in ctx.libs {
|
||||
cmd.arg(format!("-l{lib}"));
|
||||
}
|
||||
for flag in ctx.ldflags {
|
||||
cmd.arg(flag);
|
||||
}
|
||||
cmd.arg("-o").arg(&intermediate);
|
||||
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
}
|
||||
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
return Err(format!(
|
||||
"flat-bin link failed:\nstdout: {stdout}\nstderr: {stderr}"
|
||||
));
|
||||
}
|
||||
|
||||
objcopy_binary(ctx, &intermediate, &out_path)?;
|
||||
Ok(common::elapsed_secs(start_time))
|
||||
}
|
||||
|
||||
pub(crate) fn archive_static(
|
||||
ctx: &BuildContext,
|
||||
objects: &[String],
|
||||
@@ -56,6 +177,10 @@ pub(crate) fn link_binary(
|
||||
default_linker: &str,
|
||||
start_time: Instant,
|
||||
) -> Result<f64, String> {
|
||||
if crate::utils::build::is_flat_bin(ctx.kind) {
|
||||
return link_flat_binary(ctx, objects, default_linker, start_time);
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or(default_linker));
|
||||
if ctx.kind == "sharedlib" {
|
||||
if cfg!(target_os = "macos") {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::builder::artifact;
|
||||
use crate::core::build::common;
|
||||
use crate::utils::build::is_bare_metal_target;
|
||||
use crate::utils::build::{is_bare_metal_target, is_compile_only, is_flat_bin};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -30,6 +30,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
} else {
|
||||
ctx.compiler
|
||||
};
|
||||
if is_flat_bin(ctx.kind) && ctx.qt {
|
||||
return Err("flat-bin is not supported with build.qt = true".to_string());
|
||||
}
|
||||
let start_time = Instant::now();
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
@@ -55,7 +58,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
)?;
|
||||
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o", qt_include_path)?;
|
||||
|
||||
if ctx.kind == "none" || ctx.kind == "custom" {
|
||||
if is_compile_only(ctx.kind) && !is_flat_bin(ctx.kind) {
|
||||
return Ok(common::elapsed_secs(start_time));
|
||||
}
|
||||
|
||||
@@ -142,7 +145,7 @@ fn build_object(
|
||||
cmd.arg("-fPIC");
|
||||
}
|
||||
|
||||
if (ctx.freestanding || is_bare_metal_target(ctx.target))
|
||||
if (ctx.freestanding || is_bare_metal_target(ctx.target) || is_flat_bin(ctx.kind))
|
||||
&& ctx.language.to_lowercase() != "asm"
|
||||
{
|
||||
cmd.arg("-ffreestanding");
|
||||
@@ -178,8 +181,8 @@ fn build_object(
|
||||
}
|
||||
}
|
||||
|
||||
let use_dcr_defaults =
|
||||
ctx.cflags.is_empty() && !(ctx.freestanding || is_bare_metal_target(ctx.target));
|
||||
let use_dcr_defaults = ctx.cflags.is_empty()
|
||||
&& !(ctx.freestanding || is_bare_metal_target(ctx.target) || is_flat_bin(ctx.kind));
|
||||
if use_dcr_defaults {
|
||||
for flag in default_flags(ctx.profile) {
|
||||
cmd.arg(flag);
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::builder::artifact;
|
||||
use crate::core::build::common;
|
||||
use crate::platform;
|
||||
use crate::utils::build::{is_compile_only, is_flat_bin};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
@@ -33,6 +35,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
if lang.contains("asm") {
|
||||
return Err("MSVC backend does not support build.language with asm".to_string());
|
||||
}
|
||||
if is_flat_bin(ctx.kind) && ctx.qt {
|
||||
return Err("flat-bin is not supported with build.qt = true".to_string());
|
||||
}
|
||||
let start_time = Instant::now();
|
||||
let sources = collect_sources(ctx)?;
|
||||
let obj_dir = match ctx.target_dir {
|
||||
@@ -41,10 +46,14 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
};
|
||||
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "obj")?;
|
||||
|
||||
if ctx.kind == "none" || ctx.kind == "custom" {
|
||||
if is_compile_only(ctx.kind) && !is_flat_bin(ctx.kind) {
|
||||
return Ok(common::elapsed_secs(start_time));
|
||||
}
|
||||
|
||||
if is_flat_bin(ctx.kind) {
|
||||
return link_flat_msvc(ctx, compiler, &objects, &obj_dir, start_time);
|
||||
}
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
if !common::needs_link(&objects, &lib_path) {
|
||||
@@ -145,6 +154,61 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn link_flat_msvc(
|
||||
ctx: &BuildContext,
|
||||
compiler: &str,
|
||||
objects: &[String],
|
||||
obj_dir: &Path,
|
||||
start_time: Instant,
|
||||
) -> Result<f64, String> {
|
||||
let out_path = artifact::flat_output_path(ctx);
|
||||
if !common::needs_link(objects, &out_path) {
|
||||
return Ok(common::elapsed_secs(start_time));
|
||||
}
|
||||
fs::create_dir_all(obj_dir).map_err(|e| format!("obj dir error: {e}"))?;
|
||||
let intermediate = obj_dir
|
||||
.join(format!("{}.flat.exe", ctx.project_name))
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let mut cmd = Command::new(compiler);
|
||||
cmd.arg("/nologo");
|
||||
for obj in objects {
|
||||
cmd.arg(obj);
|
||||
}
|
||||
for flag in ctx.cflags {
|
||||
cmd.arg(flag);
|
||||
}
|
||||
for dir in ctx.lib_dirs {
|
||||
cmd.arg(format!("/LIBPATH:{dir}"));
|
||||
}
|
||||
for lib in ctx.libs {
|
||||
if lib.to_lowercase().ends_with(".lib") {
|
||||
cmd.arg(lib);
|
||||
} else {
|
||||
cmd.arg(format!("{lib}.lib"));
|
||||
}
|
||||
}
|
||||
cmd.arg(format!("/Fe:{intermediate}"));
|
||||
if !ctx.ldflags.is_empty() {
|
||||
cmd.arg("/link");
|
||||
for flag in ctx.ldflags {
|
||||
cmd.arg(flag);
|
||||
}
|
||||
}
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
}
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|e| format!("flat-bin link failed: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err("flat-bin link failed".to_string());
|
||||
}
|
||||
artifact::objcopy_binary(ctx, &intermediate, &out_path)?;
|
||||
Ok(common::elapsed_secs(start_time))
|
||||
}
|
||||
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
let extensions = source_extensions(ctx.language);
|
||||
common::collect_sources(
|
||||
|
||||
@@ -107,6 +107,10 @@ fn build_cache_path(profile: &str, target_dir: Option<&str>) -> PathBuf {
|
||||
}
|
||||
|
||||
fn build_output_path(ctx: &BuildContext) -> String {
|
||||
if crate::utils::build::is_flat_bin(ctx.kind) {
|
||||
return crate::core::build::builder::artifact::flat_output_path(ctx);
|
||||
}
|
||||
|
||||
let name = ctx.output_filename.unwrap_or(ctx.project_name);
|
||||
let ext = ctx.output_extension.unwrap_or("");
|
||||
|
||||
@@ -159,7 +163,13 @@ pub(crate) fn collect_header_files(
|
||||
if !root.exists() {
|
||||
continue;
|
||||
}
|
||||
collect_header_files_rec(&root, &mut out, ctx.exclude_dirs, ctx.include_paths)?;
|
||||
if root.is_file() {
|
||||
if is_header_file(&root) {
|
||||
out.push(root);
|
||||
}
|
||||
} else {
|
||||
collect_header_files_rec(&root, &mut out, ctx.exclude_dirs, ctx.include_paths)?;
|
||||
}
|
||||
}
|
||||
out.sort();
|
||||
out.dedup();
|
||||
|
||||
@@ -35,7 +35,15 @@ pub fn collect_sources(
|
||||
) -> Result<Vec<String>, String> {
|
||||
let mut sources = Vec::new();
|
||||
for root in roots {
|
||||
collect_sources_rec(root, extensions, &mut sources, exclude_dirs, include_paths)?;
|
||||
if root.is_file() {
|
||||
if let Some(ext) = root.extension().and_then(|e| e.to_str())
|
||||
&& extensions.contains(&ext.to_lowercase().as_str())
|
||||
{
|
||||
sources.push(root.to_string_lossy().to_string());
|
||||
}
|
||||
} else {
|
||||
collect_sources_rec(root, extensions, &mut sources, exclude_dirs, include_paths)?;
|
||||
}
|
||||
}
|
||||
sources.sort();
|
||||
Ok(sources)
|
||||
|
||||
@@ -165,6 +165,9 @@ fn build_all(
|
||||
&mut *rep,
|
||||
)?;
|
||||
}
|
||||
if let Some(archive) = &config.typed().archive {
|
||||
super::archive::pack_archive(root, archive, profile)?;
|
||||
}
|
||||
return Ok(0.0);
|
||||
}
|
||||
|
||||
@@ -843,7 +846,10 @@ fn build_project_at(
|
||||
version_suffix: &version_info.suffix,
|
||||
version_suffix_dash: &version_info.suffix_dash,
|
||||
};
|
||||
let steps_dirty = build_steps_need_run(&build_steps, &step_vars)?;
|
||||
let mut steps_dirty = build_steps_need_run(&build_steps, &step_vars)?;
|
||||
if force {
|
||||
steps_dirty = true;
|
||||
}
|
||||
if steps_dirty {
|
||||
clean_generated_files(&build_generated)?;
|
||||
run_build_steps(
|
||||
@@ -884,7 +890,10 @@ fn build_project_at(
|
||||
if ctx.package_type == Some("lib") || ctx.kind == "staticlib" || ctx.kind == "sharedlib" {
|
||||
package_library(&ctx, &headers, project_root)?;
|
||||
}
|
||||
let post_steps_dirty = build_steps_need_run(&build_post_steps, &step_vars)?;
|
||||
let mut post_steps_dirty = build_steps_need_run(&build_post_steps, &step_vars)?;
|
||||
if force {
|
||||
post_steps_dirty = true;
|
||||
}
|
||||
if post_steps_dirty {
|
||||
run_build_steps(
|
||||
&build_post_steps,
|
||||
@@ -896,6 +905,9 @@ fn build_project_at(
|
||||
)?;
|
||||
}
|
||||
verify_expectations(&build_expects, &step_vars)?;
|
||||
if let Some(archive) = &config.typed().archive {
|
||||
super::archive::pack_archive(project_root, archive, profile)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::builder::artifact;
|
||||
use crate::core::build::common;
|
||||
use crate::utils::build::{is_compile_only, is_flat_bin};
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -22,13 +23,63 @@ pub fn build_assembly<F>(
|
||||
extensions: &[&str],
|
||||
build_object: F,
|
||||
) -> Result<f64, String>
|
||||
where
|
||||
F: Fn(&str, &str, &str, &BuildContext) -> Result<(), String> + Sync,
|
||||
{
|
||||
build_assembly_with_mode(
|
||||
ctx,
|
||||
backend_name,
|
||||
default_assembler,
|
||||
extensions,
|
||||
FlatEmit::Direct,
|
||||
build_object,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_assembly_via_objcopy<F>(
|
||||
ctx: &BuildContext,
|
||||
backend_name: &str,
|
||||
default_assembler: &str,
|
||||
extensions: &[&str],
|
||||
build_object: F,
|
||||
) -> Result<f64, String>
|
||||
where
|
||||
F: Fn(&str, &str, &str, &BuildContext) -> Result<(), String> + Sync,
|
||||
{
|
||||
build_assembly_with_mode(
|
||||
ctx,
|
||||
backend_name,
|
||||
default_assembler,
|
||||
extensions,
|
||||
FlatEmit::ViaObjcopy,
|
||||
build_object,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FlatEmit {
|
||||
Direct,
|
||||
ViaObjcopy,
|
||||
}
|
||||
|
||||
fn build_assembly_with_mode<F>(
|
||||
ctx: &BuildContext,
|
||||
backend_name: &str,
|
||||
default_assembler: &str,
|
||||
extensions: &[&str],
|
||||
flat_mode: FlatEmit,
|
||||
build_object: F,
|
||||
) -> Result<f64, String>
|
||||
where
|
||||
F: Fn(&str, &str, &str, &BuildContext) -> Result<(), String> + Sync,
|
||||
{
|
||||
let lang = ctx.language.to_lowercase();
|
||||
if lang.split(',').any(|p| p.trim() != "asm") {
|
||||
if lang.split(',').any(|p| {
|
||||
let t = p.trim();
|
||||
t != "asm" && t != "llvm_ir" && t != "llvm-ir" && t != "ll"
|
||||
}) {
|
||||
return Err(format!(
|
||||
"{backend_name} backend requires build.language = \"asm\""
|
||||
"{backend_name} backend requires build.language = \"asm\" (or llvm_ir for LLC)"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -45,13 +96,48 @@ where
|
||||
ctx.exclude_dirs,
|
||||
ctx.include_paths,
|
||||
)?;
|
||||
|
||||
let flat = is_flat_bin(ctx.kind);
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
let objects = build_objects_common(assembler, &sources, &obj_dir, ctx, &build_object)?;
|
||||
|
||||
if ctx.kind == "none" || ctx.kind == "custom" {
|
||||
let objects = match (flat, flat_mode) {
|
||||
(true, FlatEmit::Direct) => {
|
||||
let outs: Vec<String> = sources
|
||||
.iter()
|
||||
.map(|s| artifact::flat_source_output_path(ctx, s))
|
||||
.collect();
|
||||
common::parallel_build(
|
||||
sources.len(),
|
||||
|i| build_object(assembler, &sources[i], &outs[i], ctx),
|
||||
ctx.codegen_units,
|
||||
)?;
|
||||
outs
|
||||
}
|
||||
(true, FlatEmit::ViaObjcopy) => {
|
||||
let intermediates: Vec<String> = sources
|
||||
.iter()
|
||||
.map(|s| common::object_path(&obj_dir, s, "o"))
|
||||
.collect();
|
||||
common::parallel_build(
|
||||
sources.len(),
|
||||
|i| build_object(assembler, &sources[i], &intermediates[i], ctx),
|
||||
ctx.codegen_units,
|
||||
)?;
|
||||
let mut outs = Vec::with_capacity(sources.len());
|
||||
for (i, src) in sources.iter().enumerate() {
|
||||
let out = artifact::flat_source_output_path(ctx, src);
|
||||
artifact::objcopy_binary(ctx, &intermediates[i], &out)?;
|
||||
outs.push(out);
|
||||
}
|
||||
outs
|
||||
}
|
||||
(false, _) => build_objects_common(assembler, &sources, &obj_dir, ctx, &build_object)?,
|
||||
};
|
||||
|
||||
if is_compile_only(ctx.kind) {
|
||||
return Ok(common::elapsed_secs(start_time));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| "as".to_string());
|
||||
asm::build_assembly(ctx, "GAS", &as_path, &["s"], build_object)
|
||||
asm::build_assembly_via_objcopy(ctx, "GAS", &as_path, &["s"], build_object)
|
||||
}
|
||||
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly(ctx, "MASM", "ml", &["asm"], build_object)
|
||||
asm::build_assembly_via_objcopy(ctx, "MASM", "ml", &["asm"], build_object)
|
||||
}
|
||||
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
|
||||
@@ -50,7 +50,11 @@ fn build_object(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let format = nasm_format(ctx.platform);
|
||||
let format = if crate::utils::build::is_flat_bin(ctx.kind) {
|
||||
"bin"
|
||||
} else {
|
||||
nasm_format(ctx.platform)
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(assembler);
|
||||
cmd.arg("-f")
|
||||
|
||||
@@ -21,7 +21,7 @@ impl Language for LlvmIr {
|
||||
}
|
||||
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly(ctx, "LLVM-IR", "llc", &["ll"], build_object)
|
||||
asm::build_assembly_via_objcopy(ctx, "LLVM-IR", "llc", &["ll"], build_object)
|
||||
}
|
||||
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod archive;
|
||||
pub mod builder;
|
||||
pub mod cache;
|
||||
pub mod common;
|
||||
|
||||
@@ -36,6 +36,7 @@ const VALID_KINDS: &[&str] = &[
|
||||
"elf",
|
||||
"none",
|
||||
"custom",
|
||||
"flat-bin",
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -102,6 +103,27 @@ pub struct DcrConfig {
|
||||
pub toolchain: Option<ToolchainConfig>,
|
||||
#[serde(default)]
|
||||
pub workspace: BTreeMap<String, WorkspaceMemberConfig>,
|
||||
#[serde(default)]
|
||||
pub archive: Option<ArchiveConfig>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ArchiveConfig {
|
||||
pub output: String,
|
||||
pub format: String,
|
||||
pub size: Option<String>,
|
||||
pub offset: Option<String>,
|
||||
pub label: Option<String>,
|
||||
pub bootsector: Option<String>,
|
||||
#[serde(default)]
|
||||
pub layout: Vec<ArchiveLayout>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ArchiveLayout {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user