mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 13:48:29 +00:00
feat(build): archive feature, progress UI, and workspace path fixes
This commit is contained in:
@@ -130,7 +130,14 @@ pub(crate) fn link_flat_binary(
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
}
|
||||
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let output = cmd.output().map_err(|err| {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
format!("linker not found: {program} (check [toolchain].ld / PATH)")
|
||||
} else {
|
||||
format!("flat-bin link failed: {err}")
|
||||
}
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
@@ -240,7 +247,15 @@ fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64,
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
}
|
||||
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let output = cmd.output().map_err(|err| {
|
||||
let msg = err.to_string();
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
format!("linker not found: {program} (check [toolchain].ld / PATH)")
|
||||
} else {
|
||||
format!("Build failed: {msg}")
|
||||
}
|
||||
})?;
|
||||
if output.status.success() {
|
||||
Ok(common::elapsed_secs(start_time))
|
||||
} else {
|
||||
|
||||
@@ -16,17 +16,72 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::Instant;
|
||||
|
||||
static OUTPUT_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
static PROGRESS_LABEL: OnceLock<Mutex<Option<String>>> = OnceLock::new();
|
||||
static PROGRESS_DIRTY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn get_output_lock() -> &'static Mutex<()> {
|
||||
OUTPUT_MUTEX.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn progress_label_lock() -> &'static Mutex<Option<String>> {
|
||||
PROGRESS_LABEL.get_or_init(|| Mutex::new(None))
|
||||
}
|
||||
|
||||
pub fn set_progress_label(label: Option<String>) {
|
||||
*progress_label_lock().lock().unwrap() = label;
|
||||
}
|
||||
|
||||
pub fn finish_progress_line() {
|
||||
let was_dirty = PROGRESS_DIRTY.swap(false, Ordering::SeqCst);
|
||||
*progress_label_lock().lock().unwrap() = None;
|
||||
if !was_dirty {
|
||||
return;
|
||||
}
|
||||
if io::stderr().is_terminal() {
|
||||
let _guard = get_output_lock().lock().unwrap();
|
||||
let _ = writeln!(io::stderr());
|
||||
let _ = io::stderr().flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt_progress_for_output() {
|
||||
if !PROGRESS_DIRTY.swap(false, Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
if io::stderr().is_terminal() {
|
||||
let _guard = get_output_lock().lock().unwrap();
|
||||
let _ = writeln!(io::stderr());
|
||||
let _ = io::stderr().flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn report_compile_progress(done: usize, total: usize) {
|
||||
let label = progress_label_lock().lock().unwrap().clone();
|
||||
let Some(label) = label else {
|
||||
return;
|
||||
};
|
||||
if !io::stderr().is_terminal() {
|
||||
return;
|
||||
}
|
||||
let _guard = get_output_lock().lock().unwrap();
|
||||
let msg = format!("{label} [{done}/{total}]");
|
||||
if PROGRESS_DIRTY.load(Ordering::SeqCst) {
|
||||
eprint!("\r{msg:<80}");
|
||||
} else {
|
||||
eprint!("{msg}");
|
||||
}
|
||||
let _ = io::stderr().flush();
|
||||
PROGRESS_DIRTY.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn collect_sources(
|
||||
roots: &[PathBuf],
|
||||
extensions: &[&str],
|
||||
@@ -214,8 +269,13 @@ where
|
||||
};
|
||||
|
||||
let counter = std::sync::atomic::AtomicUsize::new(0);
|
||||
let completed = std::sync::atomic::AtomicUsize::new(0);
|
||||
let err_msg = std::sync::Mutex::new(None);
|
||||
|
||||
if total_tasks > 0 {
|
||||
report_compile_progress(0, total_tasks);
|
||||
}
|
||||
|
||||
std::thread::scope(|s| {
|
||||
for _ in 0..num_threads {
|
||||
s.spawn(|| {
|
||||
@@ -236,6 +296,9 @@ where
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let done = completed.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
|
||||
report_compile_progress(done, total_tasks);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -249,10 +312,21 @@ where
|
||||
}
|
||||
|
||||
pub fn run_command_sync_output(cmd: &mut Command) -> Result<(), String> {
|
||||
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||
let _guard = OUTPUT_MUTEX.get_or_init(|| Mutex::new(())).lock().unwrap();
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let output = cmd.output().map_err(|err| {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
format!("{program} not found (check PATH / [toolchain])")
|
||||
} else {
|
||||
format!("Build failed: {err}")
|
||||
}
|
||||
})?;
|
||||
let has_out = !output.stdout.is_empty() || !output.stderr.is_empty();
|
||||
if has_out {
|
||||
interrupt_progress_for_output();
|
||||
}
|
||||
let _guard = get_output_lock().lock().unwrap();
|
||||
if !output.stdout.is_empty() {
|
||||
print!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
eprint!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
}
|
||||
if !output.stderr.is_empty() {
|
||||
eprint!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
|
||||
@@ -90,7 +90,9 @@ fn run_compile(
|
||||
_rep: &mut dyn BuildReporter,
|
||||
) -> Result<f64, String> {
|
||||
let start_time = Instant::now();
|
||||
match build_project(ctx) {
|
||||
let result = build_project(ctx);
|
||||
common::finish_progress_line();
|
||||
match result {
|
||||
Ok(times) => {
|
||||
let times = if times == 0.0 {
|
||||
((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0
|
||||
@@ -166,7 +168,17 @@ fn build_all(
|
||||
)?;
|
||||
}
|
||||
if let Some(archive) = &config.typed().archive {
|
||||
super::archive::pack_archive(root, archive, profile)?;
|
||||
#[cfg(feature = "archive")]
|
||||
{
|
||||
super::archive::pack_archive(root, archive, profile)?;
|
||||
}
|
||||
#[cfg(not(feature = "archive"))]
|
||||
{
|
||||
let _ = (archive, profile);
|
||||
return Err(
|
||||
"disk image packing requires building dcr with --features archive".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(0.0);
|
||||
}
|
||||
@@ -777,10 +789,12 @@ fn build_project_at(
|
||||
let resolved_cflags: Vec<String> = resolved_cflags
|
||||
.iter()
|
||||
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
||||
.map(|f| absolutize_flag_path(&f, project_root, &["-isystem", "-idirafter", "-I", "-i"]))
|
||||
.collect();
|
||||
let modified_ldflags: Vec<String> = resolved_ldflags
|
||||
.iter()
|
||||
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
||||
.map(|f| absolutize_flag_path(&f, project_root, &["-L", "-T"]))
|
||||
.collect();
|
||||
let merged_include_dirs: Vec<String> = merged_include_dirs
|
||||
.iter()
|
||||
@@ -876,11 +890,11 @@ fn build_project_at(
|
||||
if skip && !debug_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
rep.on_event(BuildEvent::Compiling {
|
||||
name: &project_name,
|
||||
version: &project_version,
|
||||
});
|
||||
if !skip {
|
||||
rep.on_event(BuildEvent::Compiling {
|
||||
name: &project_name,
|
||||
version: &project_version,
|
||||
});
|
||||
run_compile(&ctx, cancel, rep)?;
|
||||
if cancel.load(Ordering::SeqCst) {
|
||||
return Err("Build interrupted".to_string());
|
||||
@@ -906,10 +920,42 @@ 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)?;
|
||||
let out = archive.output.replace("{profile}", profile);
|
||||
rep.on_event(BuildEvent::Packing { path: &out });
|
||||
#[cfg(feature = "archive")]
|
||||
{
|
||||
super::archive::pack_archive(project_root, archive, profile)?;
|
||||
}
|
||||
#[cfg(not(feature = "archive"))]
|
||||
{
|
||||
let _ = (project_root, archive, profile, out);
|
||||
return Err(
|
||||
"disk image packing requires building dcr with --features archive".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> String {
|
||||
for prefix in prefixes {
|
||||
if let Some(rest) = flag.strip_prefix(prefix) {
|
||||
if rest.is_empty() {
|
||||
return flag.to_string();
|
||||
}
|
||||
if rest.starts_with('/') || rest.starts_with('@') {
|
||||
return flag.to_string();
|
||||
}
|
||||
if rest.len() >= 2 && rest.as_bytes()[1] == b':' {
|
||||
return flag.to_string();
|
||||
}
|
||||
let abs = project_root.join(rest);
|
||||
return format!("{prefix}{}", abs.to_string_lossy());
|
||||
}
|
||||
}
|
||||
flag.to_string()
|
||||
}
|
||||
|
||||
fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
||||
let p = Path::new(dir);
|
||||
for ancestor in p.ancestors() {
|
||||
@@ -1094,3 +1140,35 @@ fn is_on_path(cmd: &str) -> bool {
|
||||
std::env::var_os("PATH")
|
||||
.is_some_and(|paths| std::env::split_paths(&paths).any(|dir| dir.join(cmd).is_file()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::absolutize_flag_path;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn absolutize_prefers_long_include_prefixes() {
|
||||
let root = Path::new("/pkg");
|
||||
let prefs = ["-isystem", "-idirafter", "-I", "-i"];
|
||||
assert_eq!(
|
||||
absolutize_flag_path("-isystemvendor/inc", root, &prefs),
|
||||
"-isystem/pkg/vendor/inc"
|
||||
);
|
||||
assert_eq!(
|
||||
absolutize_flag_path("-idiraftervendor/old", root, &prefs),
|
||||
"-idirafter/pkg/vendor/old"
|
||||
);
|
||||
assert_eq!(
|
||||
absolutize_flag_path("-I../inc", root, &prefs),
|
||||
"-I/pkg/../inc"
|
||||
);
|
||||
assert_eq!(
|
||||
absolutize_flag_path("-i../inc", root, &prefs),
|
||||
"-i/pkg/../inc"
|
||||
);
|
||||
assert_eq!(
|
||||
absolutize_flag_path("-I/usr/include", root, &prefs),
|
||||
"-I/usr/include"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/>.
|
||||
|
||||
#[cfg(feature = "archive")]
|
||||
pub mod archive;
|
||||
pub mod builder;
|
||||
pub mod cache;
|
||||
|
||||
@@ -69,6 +69,9 @@ pub enum BuildEvent<'a> {
|
||||
name: &'a str,
|
||||
version: &'a str,
|
||||
},
|
||||
Packing {
|
||||
path: &'a str,
|
||||
},
|
||||
CompilerOutput {
|
||||
stream: &'a str,
|
||||
text: &'a str,
|
||||
|
||||
@@ -120,6 +120,7 @@ pub struct ArchiveConfig {
|
||||
pub layout: Vec<ArchiveLayout>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ArchiveLayout {
|
||||
pub from: String,
|
||||
|
||||
Reference in New Issue
Block a user