Phase 6: P-Code-Bibliotheken und Linker abschließen, Cross-Buildplan festhalten

This commit is contained in:
2026-09-07 16:06:23 +02:00
parent 60d37ec79d
commit 25176947ba
33 changed files with 2416 additions and 203 deletions

View File

@@ -21,11 +21,12 @@ fn main() -> ExitCode {
match args.first().map(String::as_str) {
Some("run") => cmd_run(&args[1..]),
Some("build") => cmd_build(&args[1..]),
Some("link") => cmd_link(&args[1..]),
Some("check") => cmd_check(&args[1..]),
Some("convert-frm") => cmd_convert_frm(&args[1..]),
_ => {
eprintln!(
"Aufruf: tbc run|build|check <datei.bas|datei.frm|projekt.mak|datei.tbc> | tbc convert-frm <quelle.frm> <ziel.frm>"
"Aufruf: tbc run|build|link|check <datei.bas|datei.frm|projekt.mak|datei.tbc> | tbc convert-frm <quelle.frm> <ziel.frm>"
);
ExitCode::from(1)
}
@@ -79,7 +80,7 @@ fn compile(
path_arg: Option<&String>,
) -> Result<(PathBuf, tb_vm::bytecode::CompiledModule), ExitCode> {
let Some(path) = path_arg else {
eprintln!("Aufruf: tbc run|build|check <datei.bas|datei.frm|projekt.mak|datei.tbc>");
eprintln!("Aufruf: tbc run|build|link|check <datei.bas|datei.frm|projekt.mak|datei.tbc>");
return Err(ExitCode::from(1));
};
let path = PathBuf::from(path);
@@ -118,6 +119,10 @@ fn compile(
}
fn cmd_check(args: &[String]) -> ExitCode {
if args.len() != 1 {
eprintln!("Aufruf: tbc check <Projekt>");
return ExitCode::from(1);
}
match compile(args.first()) {
Ok(_) => ExitCode::SUCCESS,
Err(code) => code,
@@ -125,7 +130,13 @@ fn cmd_check(args: &[String]) -> ExitCode {
}
fn cmd_build(args: &[String]) -> ExitCode {
match build(args) {
cmd_output(args, false)
}
fn cmd_link(args: &[String]) -> ExitCode {
cmd_output(args, true)
}
fn cmd_output(args: &[String], link: bool) -> ExitCode {
match build(args, link) {
Ok(path) => {
println!("{}", path.display());
ExitCode::SUCCESS
@@ -137,9 +148,10 @@ fn cmd_build(args: &[String]) -> ExitCode {
}
}
fn build(args: &[String]) -> anyhow::Result<PathBuf> {
fn build(args: &[String], link: bool) -> anyhow::Result<PathBuf> {
use anyhow::{bail, ensure, Context};
let mut source = None;
let mut inputs = Vec::new();
let mut library = false;
let mut exe = false;
let mut force = false;
let mut output = None;
@@ -148,6 +160,13 @@ fn build(args: &[String]) -> anyhow::Result<PathBuf> {
let mut args = args.iter();
while let Some(arg) = args.next() {
match arg.as_str() {
"--library" => {
ensure!(
!library && !link,
"--library ist nur bei build zulässig und darf nicht doppelt sein"
);
library = true;
}
"--exe" => {
ensure!(!exe, "Doppeltes --exe");
exe = true;
@@ -171,30 +190,74 @@ fn build(args: &[String]) -> anyhow::Result<PathBuf> {
"Doppelte Option {arg}"
);
}
"--" => {
for value in args.by_ref() {
ensure!(
source.replace(value.clone()).is_none(),
"Mehrere Quelldateien angegeben"
);
}
}
"--" => inputs.extend(args.by_ref().cloned()),
value if value.starts_with('-') => bail!("Unbekannte Build-Option: {value}"),
_ => {
ensure!(
source.replace(arg.clone()).is_none(),
"Mehrere Quelldateien angegeben"
);
}
_ => inputs.push(arg.clone()),
}
}
let source = source.context(
"Aufruf: tbc build <Quelle> [--exe --target <Triple> --template <tbrt> -o <Ziel> --force]",
)?;
ensure!(!inputs.is_empty(), "Aufruf: tbc build|link <Quelle> [Bibliothek.tbl ...] [--library | --exe] [-o Ziel] [--force]");
ensure!(!(exe && library), "--library und --exe schließen sich aus");
ensure!(
exe || (target.is_none() && template.is_none() && output.is_none() && !force),
"Native Ausgabeoptionen benötigen --exe"
exe || (target.is_none() && template.is_none()),
"--target/--template benötigen --exe"
);
ensure!(
exe || library || link || output.is_none(),
"Ausgabeoptionen benötigen --exe, --library oder link"
);
let source = &inputs[0];
let path = PathBuf::from(source);
let tbc = tb_vm::project_io::has_extension(&path, "tbc");
ensure!(!tbc || (!library && inputs.len()==1 && (!link || exe)), "Fertiges TBC kann nur ohne zusätzliche TBL mit link --exe verpackt werden; es ist kein Library-Produkt");
let mut protected = vec![path.clone()];
let mut compiler = tb_vm::project::ProjectCompiler::default();
let loaded = if tbc {
None
} else {
let mut project = SourceLoader::default()
.load(&path)
.map_err(anyhow::Error::msg)?;
for input in &inputs[1..] {
project
.add_library(Path::new(input))
.map_err(anyhow::Error::msg)?;
}
protected.extend(project.protected_inputs());
Some(project)
};
let has_libraries = loaded.as_ref().is_some_and(|p| !p.libraries.is_empty());
let diags = |errors: Vec<tb_frontend::Diagnostic>| {
anyhow::anyhow!(errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("\n"))
};
let cancel = export_cancel::ExportCancel::new()?;
if library {
let product = loaded
.as_ref()
.unwrap()
.compile_library(&mut compiler)
.map_err(diags)?;
let bytes = product.to_tbl().map_err(anyhow::Error::msg)?;
let out = output
.map(PathBuf::from)
.unwrap_or_else(|| path.with_extension("tbl"));
tb_export::publish(
&out,
force,
&protected,
&|| cancel.cancelled(),
|temporary| {
std::fs::write(temporary, &bytes)?;
tb_vm::library::Library::from_tbl(&std::fs::read(temporary)?)
.map_err(anyhow::Error::msg)?;
Ok(())
},
)?;
return Ok(out);
}
let selected = if exe {
Some(match target {
Some(s) => tb_export::Target::parse(&s)?,
@@ -203,8 +266,14 @@ fn build(args: &[String]) -> anyhow::Result<PathBuf> {
} else {
None
};
let (path, module) =
compile(Some(&source)).map_err(|_| anyhow::anyhow!("Build fehlgeschlagen"))?;
let module = if let Some(project) = loaded {
project
.compile(&mut compiler, &module_name(&path))
.map_err(diags)?
} else {
tb_vm::bytecode::CompiledModule::from_tbc(&std::fs::read(&path)?)
.map_err(|e| anyhow::anyhow!("{e}"))?
};
if let Some(target) = selected {
let out = output.map(PathBuf::from).unwrap_or_else(|| {
path.with_extension(if target == tb_export::Target::WindowsAmd64 {
@@ -222,7 +291,6 @@ fn build(args: &[String]) -> anyhow::Result<PathBuf> {
.join(target.triple())
.join(target.runtime_name()),
};
let cancel = export_cancel::ExportCancel::new()?;
tb_export::export(
tb_export::Export {
module: &module,
@@ -230,14 +298,37 @@ fn build(args: &[String]) -> anyhow::Result<PathBuf> {
template: &template,
output: &out,
overwrite: force,
protected: &[path],
protected: &protected,
},
&|| cancel.cancelled(),
)?;
Ok(out)
} else {
let out = path.with_extension("tbc");
std::fs::write(&out, module.to_tbc())?;
let legacy = !link && !has_libraries && output.is_none() && !force && inputs.len() == 1;
let out = output
.map(PathBuf::from)
.unwrap_or_else(|| path.with_extension("tbc"));
if legacy
&& !tbc
&& protected.iter().all(|p| {
tb_vm::project_io::identity(p).ok() != tb_vm::project_io::identity(&out).ok()
})
{
std::fs::write(&out, module.to_tbc())?;
} else {
tb_export::publish(
&out,
force,
&protected,
&|| cancel.cancelled(),
|temporary| {
std::fs::write(temporary, module.to_tbc())?;
tb_vm::bytecode::CompiledModule::from_tbc(&std::fs::read(temporary)?)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(())
},
)?;
}
Ok(out)
}
}