Add post-beta run and scaffold tooling
This commit is contained in:
parent
d83e20b062
commit
ee2b8e0930
40
.llm/BETA_1_TOOLING_HARDENING.md
Normal file
40
.llm/BETA_1_TOOLING_HARDENING.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Beta.1 Tooling Hardening Scope
|
||||||
|
|
||||||
|
This file tracks the first post-`1.0.0-beta` tooling bundle. It is committed on
|
||||||
|
`main` but must not be tagged as `1.0.0-beta.1` until the connected bundle is
|
||||||
|
complete and the full release gate passes near publication.
|
||||||
|
|
||||||
|
## Implemented In This Slice
|
||||||
|
|
||||||
|
- `glagol run <file.slo|project>` builds through the existing native hosted
|
||||||
|
path, executes the result, forwards stdout/stderr, and exits with the program
|
||||||
|
status.
|
||||||
|
- `glagol run` writes to `.slovo/build/<name>` by default and supports `-o
|
||||||
|
<binary>` for an explicit executable path.
|
||||||
|
- `glagol run ... -- <args>` forwards program arguments to the produced
|
||||||
|
executable.
|
||||||
|
- `glagol clean <file.slo|project>` removes generated `.slovo/build` artifacts.
|
||||||
|
- `glagol new --template binary|library|workspace` supports the existing
|
||||||
|
binary scaffold plus checkable/testable library and local workspace
|
||||||
|
scaffolds.
|
||||||
|
- The release gate prints a concise success line after docs, formatting, tests,
|
||||||
|
promotion, binary, and LLVM smoke checks pass.
|
||||||
|
|
||||||
|
## Explicitly Out Of Scope
|
||||||
|
|
||||||
|
- no source-language syntax change
|
||||||
|
- no networking or runtime resource model
|
||||||
|
- no package registry behavior
|
||||||
|
- no stable ABI/layout promise
|
||||||
|
- no stable install layout promise until the install-path portion of this
|
||||||
|
tooling bundle is finished
|
||||||
|
|
||||||
|
## Remaining Before Tagging `1.0.0-beta.1`
|
||||||
|
|
||||||
|
- document and gate public install layout for `glagol`, `runtime/`, and
|
||||||
|
`lib/std`
|
||||||
|
- add a minimal install or packaging command/script if the existing build flow
|
||||||
|
is not enough
|
||||||
|
- rerender publication PDFs only if documentation release text changes
|
||||||
|
- run the full release gate from a clean checkout state
|
||||||
|
|
||||||
19
README.md
19
README.md
@ -72,6 +72,25 @@ Build a native executable when Clang is available:
|
|||||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin
|
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Post-Beta Main Additions
|
||||||
|
|
||||||
|
The `main` branch contains unreleased tooling work intended for a future
|
||||||
|
`1.0.0-beta.1` bundle.
|
||||||
|
|
||||||
|
Build and execute in one step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol run hello
|
||||||
|
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol clean hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Create alternate project shapes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./compiler/target/debug/glagol new numbers --template library
|
||||||
|
./compiler/target/debug/glagol new workspace-demo --template workspace
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Language Manifest](docs/language/MANIFEST.md)
|
- [Language Manifest](docs/language/MANIFEST.md)
|
||||||
|
|||||||
@ -17,7 +17,8 @@ mod types;
|
|||||||
mod unsafe_ops;
|
mod unsafe_ops;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs, io,
|
env, fs,
|
||||||
|
io::{self, Write},
|
||||||
panic::{self, AssertUnwindSafe},
|
panic::{self, AssertUnwindSafe},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{self, Command as ProcessCommand},
|
process::{self, Command as ProcessCommand},
|
||||||
@ -87,6 +88,9 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
|||||||
if invocation.mode == Mode::New {
|
if invocation.mode == Mode::New {
|
||||||
run_new(invocation);
|
run_new(invocation);
|
||||||
}
|
}
|
||||||
|
if invocation.mode == Mode::Clean {
|
||||||
|
run_clean(invocation);
|
||||||
|
}
|
||||||
if invocation.mode == Mode::Doc {
|
if invocation.mode == Mode::Doc {
|
||||||
run_doc(invocation);
|
run_doc(invocation);
|
||||||
}
|
}
|
||||||
@ -94,14 +98,15 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
|||||||
run_fmt_action(invocation);
|
run_fmt_action(invocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_capable_mode = matches!(invocation.mode, Mode::Check | Mode::Build)
|
let project_capable_mode = matches!(invocation.mode, Mode::Check | Mode::Build | Mode::Run)
|
||||||
|| (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test");
|
|| (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test");
|
||||||
if project_capable_mode && project::is_project_input(&invocation.path) {
|
if project_capable_mode && project::is_project_input(&invocation.path) {
|
||||||
match invocation.mode {
|
match invocation.mode {
|
||||||
Mode::Check => run_project_check(invocation),
|
Mode::Check => run_project_check(invocation),
|
||||||
Mode::RunTests => run_project_test(invocation),
|
Mode::RunTests => run_project_test(invocation),
|
||||||
Mode::Build => run_project_build(invocation),
|
Mode::Build => run_project_build(invocation),
|
||||||
_ => unreachable!("project mode is selected only for check/test/build"),
|
Mode::Run => run_project_run(invocation),
|
||||||
|
_ => unreachable!("project mode is selected only for check/test/build/run"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +129,7 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
|||||||
|
|
||||||
match invocation.mode {
|
match invocation.mode {
|
||||||
Mode::Build => run_build(invocation, &source),
|
Mode::Build => run_build(invocation, &source),
|
||||||
|
Mode::Run => run_run(invocation, &source),
|
||||||
mode => run_text_mode(invocation, mode, &source),
|
mode => run_text_mode(invocation, mode, &source),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,6 +203,14 @@ fn run_project_build(invocation: Invocation) -> ! {
|
|||||||
run_build_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
run_build_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_project_run(invocation: Invocation) -> ! {
|
||||||
|
let output = match project::compile_to_llvm(&invocation.path) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(failure) => exit_project_failure(invocation, failure),
|
||||||
|
};
|
||||||
|
run_native_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFailure) -> ! {
|
fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFailure) -> ! {
|
||||||
let rendered = render_source_diagnostics_multi(
|
let rendered = render_source_diagnostics_multi(
|
||||||
&failure.diagnostics,
|
&failure.diagnostics,
|
||||||
@ -235,7 +249,9 @@ fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
|||||||
Mode::CheckTests => driver::check_tests(&invocation.path, source),
|
Mode::CheckTests => driver::check_tests(&invocation.path, source),
|
||||||
Mode::RunTests => unreachable!("test mode is handled separately"),
|
Mode::RunTests => unreachable!("test mode is handled separately"),
|
||||||
Mode::Build => unreachable!("build is handled separately"),
|
Mode::Build => unreachable!("build is handled separately"),
|
||||||
|
Mode::Run => unreachable!("run is handled separately"),
|
||||||
Mode::New => unreachable!("new is handled separately"),
|
Mode::New => unreachable!("new is handled separately"),
|
||||||
|
Mode::Clean => unreachable!("clean is handled separately"),
|
||||||
Mode::Doc => unreachable!("doc is handled separately"),
|
Mode::Doc => unreachable!("doc is handled separately"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -300,7 +316,11 @@ fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_new(invocation: Invocation) -> ! {
|
fn run_new(invocation: Invocation) -> ! {
|
||||||
match scaffold::create_project(&invocation.path, invocation.project_name.as_deref()) {
|
match scaffold::create_project(
|
||||||
|
&invocation.path,
|
||||||
|
invocation.project_name.as_deref(),
|
||||||
|
invocation.project_template,
|
||||||
|
) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
@ -322,6 +342,28 @@ fn run_new(invocation: Invocation) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_clean(invocation: Invocation) -> ! {
|
||||||
|
let build_dir = generated_build_dir(&invocation.path);
|
||||||
|
if build_dir.exists() {
|
||||||
|
if let Err(err) = fs::remove_dir_all(&build_dir) {
|
||||||
|
let message = format!("cannot remove `{}`: {}", build_dir.display(), err);
|
||||||
|
emit_message_diagnostic(
|
||||||
|
&message,
|
||||||
|
"CleanFailed",
|
||||||
|
ExitCode::ArtifactFailure,
|
||||||
|
&invocation,
|
||||||
|
PrimaryOutput::Diagnostics {
|
||||||
|
text: message.as_str(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
fn run_doc(invocation: Invocation) -> ! {
|
fn run_doc(invocation: Invocation) -> ! {
|
||||||
let Some(output_dir) = invocation.output_path.as_deref() else {
|
let Some(output_dir) = invocation.output_path.as_deref() else {
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
@ -589,6 +631,31 @@ fn run_build(invocation: Invocation, source: &str) -> ! {
|
|||||||
run_build_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
run_build_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_run(invocation: Invocation, source: &str) -> ! {
|
||||||
|
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
||||||
|
let llvm_ir = match driver::compile_to_llvm(&invocation.path, source) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(diagnostics) => {
|
||||||
|
let rendered = render_source_diagnostics(&diagnostics, source, invocation.diagnostics);
|
||||||
|
eprint!("{}", rendered.stderr);
|
||||||
|
write_manifest_if_requested_with_foreign_imports(
|
||||||
|
&invocation,
|
||||||
|
false,
|
||||||
|
PrimaryOutput::Diagnostics {
|
||||||
|
text: &rendered.machine_text,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&foreign_imports,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
process::exit(ExitCode::SourceFailure.code());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run_native_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
||||||
|
}
|
||||||
|
|
||||||
fn run_build_from_llvm(
|
fn run_build_from_llvm(
|
||||||
invocation: Invocation,
|
invocation: Invocation,
|
||||||
llvm_ir: String,
|
llvm_ir: String,
|
||||||
@ -607,18 +674,118 @@ fn run_build_from_llvm(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
let output_path = PathBuf::from(output_path);
|
||||||
|
let native = build_native_executable_or_exit(&invocation, llvm_ir, &output_path);
|
||||||
|
let output_display = native.output_path.display().to_string();
|
||||||
|
write_manifest_if_requested_with_foreign_imports(
|
||||||
|
&invocation,
|
||||||
|
true,
|
||||||
|
PrimaryOutput::Path {
|
||||||
|
kind: "native-executable",
|
||||||
|
path: &output_display,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
Some(BuildInfo {
|
||||||
|
clang: &native.clang,
|
||||||
|
runtime: &native.runtime,
|
||||||
|
c_inputs: &invocation.link_c_paths,
|
||||||
|
}),
|
||||||
|
&foreign_imports,
|
||||||
|
project_artifact.as_ref(),
|
||||||
|
);
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(parent) = Path::new(output_path).parent() {
|
fn run_native_from_llvm(
|
||||||
|
invocation: Invocation,
|
||||||
|
llvm_ir: String,
|
||||||
|
project_artifact: Option<project::ProjectArtifact>,
|
||||||
|
foreign_imports: Vec<project::ProjectArtifactCImport>,
|
||||||
|
) -> ! {
|
||||||
|
let output_path = run_output_path(&invocation, project_artifact.as_ref());
|
||||||
|
if invocation.output_path.is_none() {
|
||||||
|
if let Some(parent) = output_path.parent() {
|
||||||
|
if let Err(err) = fs::create_dir_all(parent) {
|
||||||
|
let message = format!("cannot create `{}`: {}", parent.display(), err);
|
||||||
|
emit_message_diagnostic(
|
||||||
|
&message,
|
||||||
|
"OutputWriteFailed",
|
||||||
|
ExitCode::ArtifactFailure,
|
||||||
|
&invocation,
|
||||||
|
PrimaryOutput::Diagnostics {
|
||||||
|
text: message.as_str(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let native = build_native_executable_or_exit(&invocation, llvm_ir, &output_path);
|
||||||
|
let run_output = match ProcessCommand::new(&native.output_path)
|
||||||
|
.args(&invocation.run_args)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(err) => {
|
||||||
|
let message = format!("cannot run `{}`: {}", native.output_path.display(), err);
|
||||||
|
emit_message_diagnostic(
|
||||||
|
&message,
|
||||||
|
"RunFailed",
|
||||||
|
ExitCode::ArtifactFailure,
|
||||||
|
&invocation,
|
||||||
|
PrimaryOutput::Diagnostics {
|
||||||
|
text: message.as_str(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = io::stdout().write_all(&run_output.stdout);
|
||||||
|
let _ = io::stderr().write_all(&run_output.stderr);
|
||||||
|
let stdout = String::from_utf8_lossy(&run_output.stdout).to_string();
|
||||||
|
write_manifest_if_requested_with_foreign_imports(
|
||||||
|
&invocation,
|
||||||
|
run_output.status.success(),
|
||||||
|
PrimaryOutput::Stdout {
|
||||||
|
kind: Mode::Run.output_kind(),
|
||||||
|
text: &stdout,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
Some(BuildInfo {
|
||||||
|
clang: &native.clang,
|
||||||
|
runtime: &native.runtime,
|
||||||
|
c_inputs: &invocation.link_c_paths,
|
||||||
|
}),
|
||||||
|
&foreign_imports,
|
||||||
|
project_artifact.as_ref(),
|
||||||
|
);
|
||||||
|
process::exit(run_output.status.code().unwrap_or(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NativeBuild {
|
||||||
|
output_path: PathBuf,
|
||||||
|
clang: String,
|
||||||
|
runtime: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_native_executable_or_exit(
|
||||||
|
invocation: &Invocation,
|
||||||
|
llvm_ir: String,
|
||||||
|
output_path: &Path,
|
||||||
|
) -> NativeBuild {
|
||||||
|
if let Some(parent) = output_path.parent() {
|
||||||
if !parent.as_os_str().is_empty() && !parent.is_dir() {
|
if !parent.as_os_str().is_empty() && !parent.is_dir() {
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"cannot write `{}`: parent directory does not exist",
|
"cannot write `{}`: parent directory does not exist",
|
||||||
output_path
|
output_path.display()
|
||||||
);
|
);
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -634,7 +801,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -650,7 +817,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"InputReadFailed",
|
"InputReadFailed",
|
||||||
ExitCode::SourceFailure,
|
ExitCode::SourceFailure,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -659,12 +826,11 @@ fn run_build_from_llvm(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = Path::new(output_path);
|
let output_dir = output_path
|
||||||
let output_dir = output
|
|
||||||
.parent()
|
.parent()
|
||||||
.filter(|parent| !parent.as_os_str().is_empty())
|
.filter(|parent| !parent.as_os_str().is_empty())
|
||||||
.unwrap_or_else(|| Path::new("."));
|
.unwrap_or_else(|| Path::new("."));
|
||||||
let stem = output
|
let stem = output_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|name| name.to_str())
|
.and_then(|name| name.to_str())
|
||||||
.unwrap_or("glagol-output");
|
.unwrap_or("glagol-output");
|
||||||
@ -682,7 +848,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -707,7 +873,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -721,7 +887,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -747,7 +913,7 @@ fn run_build_from_llvm(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -758,12 +924,12 @@ fn run_build_from_llvm(
|
|||||||
if let Err(err) = fs::rename(&temp_binary, output_path) {
|
if let Err(err) = fs::rename(&temp_binary, output_path) {
|
||||||
let _ = fs::remove_file(&temp_llvm);
|
let _ = fs::remove_file(&temp_llvm);
|
||||||
let _ = fs::remove_file(&temp_binary);
|
let _ = fs::remove_file(&temp_binary);
|
||||||
let message = format!("cannot write `{}`: {}", output_path, err);
|
let message = format!("cannot write `{}`: {}", output_path.display(), err);
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
&invocation,
|
invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -772,23 +938,11 @@ fn run_build_from_llvm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = fs::remove_file(&temp_llvm);
|
let _ = fs::remove_file(&temp_llvm);
|
||||||
write_manifest_if_requested_with_foreign_imports(
|
NativeBuild {
|
||||||
&invocation,
|
output_path: output_path.to_path_buf(),
|
||||||
true,
|
clang,
|
||||||
PrimaryOutput::Path {
|
runtime,
|
||||||
kind: "native-executable",
|
}
|
||||||
path: output_path,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
Some(BuildInfo {
|
|
||||||
clang: &clang,
|
|
||||||
runtime: &runtime,
|
|
||||||
c_inputs: &invocation.link_c_paths,
|
|
||||||
}),
|
|
||||||
&foreign_imports,
|
|
||||||
project_artifact.as_ref(),
|
|
||||||
);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -809,8 +963,10 @@ struct Invocation {
|
|||||||
command_line: String,
|
command_line: String,
|
||||||
fmt_action: FmtAction,
|
fmt_action: FmtAction,
|
||||||
project_name: Option<String>,
|
project_name: Option<String>,
|
||||||
|
project_template: scaffold::ProjectTemplate,
|
||||||
link_c_paths: Vec<String>,
|
link_c_paths: Vec<String>,
|
||||||
test_filter: Option<String>,
|
test_filter: Option<String>,
|
||||||
|
run_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
@ -843,8 +999,10 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
let mut diagnostics = DiagnosticFormat::TextAndSexpr;
|
let mut diagnostics = DiagnosticFormat::TextAndSexpr;
|
||||||
let mut fmt_action = FmtAction::Stdout;
|
let mut fmt_action = FmtAction::Stdout;
|
||||||
let mut project_name = None;
|
let mut project_name = None;
|
||||||
|
let mut project_template = scaffold::ProjectTemplate::Binary;
|
||||||
let mut link_c_paths = Vec::new();
|
let mut link_c_paths = Vec::new();
|
||||||
let mut test_filter = None;
|
let mut test_filter = None;
|
||||||
|
let mut run_args = Vec::new();
|
||||||
let mut no_color = false;
|
let mut no_color = false;
|
||||||
let command_line = raw_args.join(" ");
|
let command_line = raw_args.join(" ");
|
||||||
let mut iter = raw_args
|
let mut iter = raw_args
|
||||||
@ -855,6 +1013,11 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
.into_iter();
|
.into_iter();
|
||||||
|
|
||||||
while let Some(arg) = iter.next() {
|
while let Some(arg) = iter.next() {
|
||||||
|
if arg == "--" {
|
||||||
|
run_args.extend(iter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-h" | "--help" => return Ok(Args::Help),
|
"-h" | "--help" => return Ok(Args::Help),
|
||||||
"--version" => return Ok(Args::Version),
|
"--version" => return Ok(Args::Version),
|
||||||
@ -987,6 +1150,26 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line: command_line.clone(),
|
command_line: command_line.clone(),
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
|
"--template" => {
|
||||||
|
let value = iter.next().ok_or_else(|| ParseError {
|
||||||
|
message: "`--template` requires one of: binary, library, workspace".to_string(),
|
||||||
|
manifest_path: manifest_path.clone(),
|
||||||
|
diagnostics,
|
||||||
|
command_line: command_line.clone(),
|
||||||
|
})?;
|
||||||
|
let Some(parsed) = scaffold::ProjectTemplate::parse(&value) else {
|
||||||
|
return parse_error(
|
||||||
|
format!(
|
||||||
|
"unsupported project template `{}`; expected binary, library, or workspace",
|
||||||
|
value
|
||||||
|
),
|
||||||
|
manifest_path,
|
||||||
|
diagnostics,
|
||||||
|
command_line,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
project_template = parsed;
|
||||||
|
}
|
||||||
"--manifest" => {
|
"--manifest" => {
|
||||||
if manifest_path.is_some() {
|
if manifest_path.is_some() {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
@ -1027,12 +1210,16 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line: command_line.clone(),
|
command_line: command_line.clone(),
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
"check" | "fmt" | "test" | "build" | "new" | "doc" if path.is_none() => {
|
"check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc"
|
||||||
|
if path.is_none() =>
|
||||||
|
{
|
||||||
let next = match arg.as_str() {
|
let next = match arg.as_str() {
|
||||||
"check" => Mode::Check,
|
"check" => Mode::Check,
|
||||||
"fmt" => Mode::Format,
|
"fmt" => Mode::Format,
|
||||||
"test" => Mode::RunTests,
|
"test" => Mode::RunTests,
|
||||||
"build" => Mode::Build,
|
"build" => Mode::Build,
|
||||||
|
"run" => Mode::Run,
|
||||||
|
"clean" => Mode::Clean,
|
||||||
"new" => Mode::New,
|
"new" => Mode::New,
|
||||||
"doc" => Mode::Doc,
|
"doc" => Mode::Doc,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -1114,9 +1301,18 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !link_c_paths.is_empty() && mode != Mode::Build {
|
if project_template != scaffold::ProjectTemplate::Binary && mode != Mode::New {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
"`--link-c` is only supported with `build`",
|
"`--template` is only supported with `new`",
|
||||||
|
manifest_path,
|
||||||
|
diagnostics,
|
||||||
|
command_line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !link_c_paths.is_empty() && !matches!(mode, Mode::Build | Mode::Run) {
|
||||||
|
return parse_error(
|
||||||
|
"`--link-c` is only supported with `build` and `run`",
|
||||||
manifest_path,
|
manifest_path,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
command_line,
|
command_line,
|
||||||
@ -1132,6 +1328,24 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !run_args.is_empty() && mode != Mode::Run {
|
||||||
|
return parse_error(
|
||||||
|
"`--` program arguments are only supported with `run`",
|
||||||
|
manifest_path,
|
||||||
|
diagnostics,
|
||||||
|
command_line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == Mode::Clean && output_path.is_some() {
|
||||||
|
return parse_error(
|
||||||
|
"`clean` does not support `-o`",
|
||||||
|
manifest_path,
|
||||||
|
diagnostics,
|
||||||
|
command_line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if mode == Mode::Doc && output_path.is_none() {
|
if mode == Mode::Doc && output_path.is_none() {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
"`doc` requires `-o <dir>`",
|
"`doc` requires `-o <dir>`",
|
||||||
@ -1162,8 +1376,10 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line,
|
command_line,
|
||||||
fmt_action,
|
fmt_action,
|
||||||
project_name,
|
project_name,
|
||||||
|
project_template,
|
||||||
link_c_paths,
|
link_c_paths,
|
||||||
test_filter,
|
test_filter,
|
||||||
|
run_args,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1233,8 +1449,10 @@ fn exit_parse_error(err: ParseError, command_line: &str) -> ! {
|
|||||||
},
|
},
|
||||||
fmt_action: FmtAction::Stdout,
|
fmt_action: FmtAction::Stdout,
|
||||||
project_name: None,
|
project_name: None,
|
||||||
|
project_template: scaffold::ProjectTemplate::Binary,
|
||||||
link_c_paths: Vec::new(),
|
link_c_paths: Vec::new(),
|
||||||
test_filter: None,
|
test_filter: None,
|
||||||
|
run_args: Vec::new(),
|
||||||
};
|
};
|
||||||
write_manifest_or_exit(
|
write_manifest_or_exit(
|
||||||
manifest_path,
|
manifest_path,
|
||||||
@ -1267,6 +1485,8 @@ enum Mode {
|
|||||||
CheckTests,
|
CheckTests,
|
||||||
RunTests,
|
RunTests,
|
||||||
Build,
|
Build,
|
||||||
|
Run,
|
||||||
|
Clean,
|
||||||
New,
|
New,
|
||||||
Doc,
|
Doc,
|
||||||
}
|
}
|
||||||
@ -1283,6 +1503,8 @@ impl Mode {
|
|||||||
Self::CheckTests => "check-tests",
|
Self::CheckTests => "check-tests",
|
||||||
Self::RunTests => "test",
|
Self::RunTests => "test",
|
||||||
Self::Build => "build",
|
Self::Build => "build",
|
||||||
|
Self::Run => "run",
|
||||||
|
Self::Clean => "clean",
|
||||||
Self::New => "new",
|
Self::New => "new",
|
||||||
Self::Doc => "doc",
|
Self::Doc => "doc",
|
||||||
}
|
}
|
||||||
@ -1297,6 +1519,8 @@ impl Mode {
|
|||||||
Self::InspectLoweringSurface | Self::InspectLoweringChecked => "lowering-inspector",
|
Self::InspectLoweringSurface | Self::InspectLoweringChecked => "lowering-inspector",
|
||||||
Self::CheckTests | Self::RunTests => "stdout",
|
Self::CheckTests | Self::RunTests => "stdout",
|
||||||
Self::Build => "native-executable",
|
Self::Build => "native-executable",
|
||||||
|
Self::Run => "program-stdout",
|
||||||
|
Self::Clean => "no-output",
|
||||||
Self::New => "no-output",
|
Self::New => "no-output",
|
||||||
Self::Doc => "documentation",
|
Self::Doc => "documentation",
|
||||||
}
|
}
|
||||||
@ -2001,6 +2225,79 @@ fn runtime_path() -> PathBuf {
|
|||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../runtime/runtime.c")
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../runtime/runtime.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_output_path(
|
||||||
|
invocation: &Invocation,
|
||||||
|
project_artifact: Option<&project::ProjectArtifact>,
|
||||||
|
) -> PathBuf {
|
||||||
|
if let Some(output_path) = invocation.output_path.as_deref() {
|
||||||
|
return PathBuf::from(output_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stem = project_artifact
|
||||||
|
.map(|artifact| artifact.project_name.as_str())
|
||||||
|
.or_else(|| {
|
||||||
|
Path::new(&invocation.path)
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|stem| stem.to_str())
|
||||||
|
})
|
||||||
|
.map(sanitize_output_stem)
|
||||||
|
.filter(|stem| !stem.is_empty())
|
||||||
|
.unwrap_or_else(|| "slovo-program".to_string());
|
||||||
|
generated_build_dir(&invocation.path).join(format!("{}{}", stem, env::consts::EXE_SUFFIX))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generated_build_dir(input: &str) -> PathBuf {
|
||||||
|
generated_build_root(input).join(".slovo").join("build")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generated_build_root(input: &str) -> PathBuf {
|
||||||
|
let path = Path::new(input);
|
||||||
|
if project::is_project_input(input) {
|
||||||
|
if path.is_dir() {
|
||||||
|
return path.to_path_buf();
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
.parent()
|
||||||
|
.filter(|parent| !parent.as_os_str().is_empty())
|
||||||
|
.unwrap_or_else(|| Path::new("."))
|
||||||
|
.to_path_buf();
|
||||||
|
}
|
||||||
|
path.parent()
|
||||||
|
.filter(|parent| !parent.as_os_str().is_empty())
|
||||||
|
.unwrap_or_else(|| Path::new("."))
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_output_stem(value: &str) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
let mut previous_dash = false;
|
||||||
|
for ch in value.chars().flat_map(char::to_lowercase) {
|
||||||
|
let mapped = if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
|
||||||
|
Some(ch)
|
||||||
|
} else if ch == '-' || ch == '_' || ch == '.' || ch.is_whitespace() {
|
||||||
|
Some('-')
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let Some(ch) = mapped else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if ch == '-' {
|
||||||
|
if !out.is_empty() && !previous_dash {
|
||||||
|
out.push('-');
|
||||||
|
previous_dash = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push(ch);
|
||||||
|
previous_dash = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while out.ends_with('-') {
|
||||||
|
out.pop();
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
fn c_imports_for_manifest(file: &str, source: &str) -> Vec<project::ProjectArtifactCImport> {
|
fn c_imports_for_manifest(file: &str, source: &str) -> Vec<project::ProjectArtifactCImport> {
|
||||||
let Ok(tokens) = lexer::lex(file, source) else {
|
let Ok(tokens) = lexer::lex(file, source) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@ -2102,6 +2399,6 @@ fn normalized_output_path(path: &str) -> Option<PathBuf> {
|
|||||||
|
|
||||||
fn print_usage() {
|
fn print_usage() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"usage: glagol [check|fmt|test|build] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <file.slo|project>\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>]\n glagol doc <file.slo|project> -o <dir>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] <file.slo>\n glagol --version"
|
"usage: glagol [check|fmt|test|build|run|clean] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <file.slo|project> [-- <program-args>...]\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>] [--template binary|library|workspace]\n glagol doc <file.slo|project> -o <dir>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] <file.slo>\n glagol --version"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,29 @@ use std::{
|
|||||||
|
|
||||||
use crate::diag::Diagnostic;
|
use crate::diag::Diagnostic;
|
||||||
|
|
||||||
pub fn create_project(target: &str, explicit_name: Option<&str>) -> Result<(), Diagnostic> {
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ProjectTemplate {
|
||||||
|
Binary,
|
||||||
|
Library,
|
||||||
|
Workspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectTemplate {
|
||||||
|
pub fn parse(value: &str) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
"binary" => Some(Self::Binary),
|
||||||
|
"library" => Some(Self::Library),
|
||||||
|
"workspace" => Some(Self::Workspace),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_project(
|
||||||
|
target: &str,
|
||||||
|
explicit_name: Option<&str>,
|
||||||
|
template: ProjectTemplate,
|
||||||
|
) -> Result<(), Diagnostic> {
|
||||||
if target.trim().is_empty() {
|
if target.trim().is_empty() {
|
||||||
return Err(Diagnostic::new(
|
return Err(Diagnostic::new(
|
||||||
target,
|
target,
|
||||||
@ -49,6 +71,14 @@ pub fn create_project(target: &str, explicit_name: Option<&str>) -> Result<(), D
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match template {
|
||||||
|
ProjectTemplate::Binary => create_binary_project(root, target, &name),
|
||||||
|
ProjectTemplate::Library => create_library_project(root, target, &name),
|
||||||
|
ProjectTemplate::Workspace => create_workspace_project(root, target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_binary_project(root: &Path, target: &str, name: &str) -> Result<(), Diagnostic> {
|
||||||
let src = root.join("src");
|
let src = root.join("src");
|
||||||
create_dir_all_checked(&src, target)?;
|
create_dir_all_checked(&src, target)?;
|
||||||
write_checked(
|
write_checked(
|
||||||
@ -66,6 +96,56 @@ pub fn create_project(target: &str, explicit_name: Option<&str>) -> Result<(), D
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_library_project(root: &Path, target: &str, name: &str) -> Result<(), Diagnostic> {
|
||||||
|
let src = root.join("src");
|
||||||
|
create_dir_all_checked(&src, target)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("slovo.toml"),
|
||||||
|
&format!(
|
||||||
|
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"lib\"\n",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
write_checked(
|
||||||
|
&src.join("lib.slo"),
|
||||||
|
"(module lib (export answer double))\n\n(fn answer () -> i32\n 42)\n\n(fn double ((value i32)) -> i32\n (+ value value))\n\n(test \"answer is stable\"\n (= (answer) 42))\n\n(test \"double works\"\n (= (double 21) 42))\n",
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_workspace_project(root: &Path, target: &str) -> Result<(), Diagnostic> {
|
||||||
|
let app_src = root.join("packages/app/src");
|
||||||
|
let lib_src = root.join("packages/libutil/src");
|
||||||
|
create_dir_all_checked(&app_src, target)?;
|
||||||
|
create_dir_all_checked(&lib_src, target)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("slovo.toml"),
|
||||||
|
"[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\n",
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("packages/libutil/slovo.toml"),
|
||||||
|
"[package]\nname = \"libutil\"\nversion = \"0.1.0\"\n",
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("packages/libutil/src/libutil.slo"),
|
||||||
|
"(module libutil (export answer label))\n\n(fn answer () -> i32\n 42)\n\n(fn label () -> string\n \"libutil\")\n\n(test \"answer is stable\"\n (= (answer) 42))\n",
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("packages/app/slovo.toml"),
|
||||||
|
"[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nlibutil = { path = \"../libutil\" }\n",
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
write_checked(
|
||||||
|
&root.join("packages/app/src/main.slo"),
|
||||||
|
"(module main)\n\n(import libutil.libutil (answer label))\n\n(fn main () -> i32\n (if (= (answer) 42)\n 0\n 1))\n\n(test \"app uses libutil\"\n (if (= (label) \"libutil\")\n (= (answer) 42)\n false))\n",
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_entries(path: &Path) -> Result<bool, Diagnostic> {
|
fn has_entries(path: &Path) -> Result<bool, Diagnostic> {
|
||||||
let mut entries = fs::read_dir(path).map_err(|err| io_diagnostic(path, err))?;
|
let mut entries = fs::read_dir(path).map_err(|err| io_diagnostic(path, err))?;
|
||||||
match entries.next() {
|
match entries.next() {
|
||||||
|
|||||||
@ -70,6 +70,121 @@ fn new_creates_minimal_valid_project() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_builds_executes_and_clean_removes_generated_artifacts() {
|
||||||
|
let project = unique_path("run-project");
|
||||||
|
|
||||||
|
let new_output = run_glagol(["new".as_ref(), project.as_os_str()]);
|
||||||
|
assert_success("glagol new for run", &new_output);
|
||||||
|
|
||||||
|
let run = run_glagol(["run".as_ref(), project.as_os_str()]);
|
||||||
|
if run.status.success() {
|
||||||
|
assert!(run.stdout.is_empty(), "run wrote stdout");
|
||||||
|
assert!(run.stderr.is_empty(), "run wrote stderr");
|
||||||
|
assert!(
|
||||||
|
project.join(".slovo/build").is_dir(),
|
||||||
|
"run did not create generated build directory"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_stderr_contains("generated project run", &run, "ToolchainUnavailable");
|
||||||
|
fs::create_dir_all(project.join(".slovo/build")).expect("create synthetic build dir");
|
||||||
|
fs::write(project.join(".slovo/build/stale"), "").expect("write stale build file");
|
||||||
|
}
|
||||||
|
|
||||||
|
let clean = run_glagol(["clean".as_ref(), project.as_os_str()]);
|
||||||
|
assert_success("glagol clean", &clean);
|
||||||
|
assert!(clean.stdout.is_empty(), "clean wrote stdout");
|
||||||
|
assert!(clean.stderr.is_empty(), "clean wrote stderr");
|
||||||
|
assert!(
|
||||||
|
!project.join(".slovo/build").exists(),
|
||||||
|
"clean left generated build directory behind"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_forwards_program_arguments_when_host_toolchain_is_available() {
|
||||||
|
let project = write_project(
|
||||||
|
"run-args-project",
|
||||||
|
&[],
|
||||||
|
"(module main)\n\n(import std.process (argc))\n\n(fn main () -> i32\n (if (= (argc) 3)\n 0\n 1))\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
let run = run_glagol([
|
||||||
|
"run".as_ref(),
|
||||||
|
project.as_os_str(),
|
||||||
|
"--".as_ref(),
|
||||||
|
"alpha".as_ref(),
|
||||||
|
"beta".as_ref(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if run.status.success() {
|
||||||
|
assert!(run.stdout.is_empty(), "run args wrote stdout");
|
||||||
|
assert!(run.stderr.is_empty(), "run args wrote stderr");
|
||||||
|
} else {
|
||||||
|
assert_stderr_contains("run args project", &run, "ToolchainUnavailable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_library_template_creates_checkable_testable_library_project() {
|
||||||
|
let project = unique_path("library-template");
|
||||||
|
|
||||||
|
let output = run_glagol([
|
||||||
|
"new".as_ref(),
|
||||||
|
project.as_os_str(),
|
||||||
|
"--template".as_ref(),
|
||||||
|
"library".as_ref(),
|
||||||
|
"--name".as_ref(),
|
||||||
|
"numbers".as_ref(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_success("glagol new --template library", &output);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(project.join("slovo.toml")).expect("read library manifest"),
|
||||||
|
"[project]\nname = \"numbers\"\nsource_root = \"src\"\nentry = \"lib\"\n"
|
||||||
|
);
|
||||||
|
let source = fs::read_to_string(project.join("src/lib.slo")).expect("read library source");
|
||||||
|
assert!(source.contains("(module lib (export answer double))"));
|
||||||
|
|
||||||
|
let check = run_glagol(["check".as_ref(), project.as_os_str()]);
|
||||||
|
assert_success("library template check", &check);
|
||||||
|
let test = run_glagol(["test".as_ref(), project.as_os_str()]);
|
||||||
|
assert_success("library template test", &test);
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&test.stdout),
|
||||||
|
"test \"answer is stable\" ... ok\ntest \"double works\" ... ok\n2 test(s) passed\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_workspace_template_creates_local_package_workspace() {
|
||||||
|
let workspace = unique_path("workspace-template");
|
||||||
|
|
||||||
|
let output = run_glagol([
|
||||||
|
"new".as_ref(),
|
||||||
|
workspace.as_os_str(),
|
||||||
|
"--template".as_ref(),
|
||||||
|
"workspace".as_ref(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_success("glagol new --template workspace", &output);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(workspace.join("slovo.toml")).expect("read workspace manifest"),
|
||||||
|
"[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\n"
|
||||||
|
);
|
||||||
|
assert!(workspace.join("packages/app/slovo.toml").is_file());
|
||||||
|
assert!(workspace.join("packages/libutil/slovo.toml").is_file());
|
||||||
|
|
||||||
|
let check = run_glagol(["check".as_ref(), workspace.as_os_str()]);
|
||||||
|
assert_success("workspace template check", &check);
|
||||||
|
let test = run_glagol(["test".as_ref(), workspace.as_os_str()]);
|
||||||
|
assert_success("workspace template test", &test);
|
||||||
|
let stdout = String::from_utf8_lossy(&test.stdout);
|
||||||
|
assert!(stdout.contains("test \"app uses libutil\" ... ok"));
|
||||||
|
assert!(stdout.contains("test \"answer is stable\" ... ok"));
|
||||||
|
assert!(stdout.contains("2 test(s) passed"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_rejects_non_empty_target_with_structured_diagnostic() {
|
fn new_rejects_non_empty_target_with_structured_diagnostic() {
|
||||||
let project = unique_path("new-non-empty");
|
let project = unique_path("new-non-empty");
|
||||||
|
|||||||
@ -36,6 +36,12 @@ Work:
|
|||||||
- make release gates print a concise final summary
|
- make release gates print a concise final summary
|
||||||
- keep PDF rendering explicit and non-mutating by default
|
- keep PDF rendering explicit and non-mutating by default
|
||||||
|
|
||||||
|
Current main-branch progress after `1.0.0-beta`: `glagol run`,
|
||||||
|
`glagol clean`, `glagol new --template binary|library|workspace`, README
|
||||||
|
coverage, focused DX tests, and a concise release-gate success line are
|
||||||
|
implemented. Install-path polish remains in this tooling bundle before a
|
||||||
|
`1.0.0-beta.1` tag.
|
||||||
|
|
||||||
Why first: it reduces friction for every later feature and gives users a better
|
Why first: it reduces friction for every later feature and gives users a better
|
||||||
way to exercise the beta.
|
way to exercise the beta.
|
||||||
|
|
||||||
@ -199,4 +205,3 @@ complete first:
|
|||||||
- optimizing compiler claims
|
- optimizing compiler claims
|
||||||
- web framework or HTTP server framework
|
- web framework or HTTP server framework
|
||||||
- broad Unicode/string normalization policy
|
- broad Unicode/string normalization policy
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,22 @@ Historical `exp-*` releases listed here are experimental maturity milestones.
|
|||||||
The pushed tag `v2.0.0-beta.1` is historical. It remains an experimental
|
The pushed tag `v2.0.0-beta.1` is historical. It remains an experimental
|
||||||
integration/readiness release, not the first real beta.
|
integration/readiness release, not the first real beta.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
Post-beta main currently contains the first tooling-hardening slice for a
|
||||||
|
future `1.0.0-beta.1` bundle:
|
||||||
|
|
||||||
|
- `glagol run <file.slo|project>` compiles through the existing hosted native
|
||||||
|
build path, runs the produced executable, forwards stdout/stderr, and returns
|
||||||
|
the program exit status
|
||||||
|
- `glagol clean <file.slo|project>` removes generated `.slovo/build` artifacts
|
||||||
|
- `glagol new --template binary|library|workspace` adds library and local
|
||||||
|
workspace scaffolds beside the existing binary default
|
||||||
|
- the release gate prints a concise final success summary
|
||||||
|
|
||||||
|
This is a toolchain workflow slice only. It does not claim a new stable ABI,
|
||||||
|
runtime resource model, networking surface, or package registry.
|
||||||
|
|
||||||
## 1.0.0-beta
|
## 1.0.0-beta
|
||||||
|
|
||||||
Release label: `1.0.0-beta`
|
Release label: `1.0.0-beta`
|
||||||
|
|||||||
@ -13,6 +13,22 @@ final unsigned precursor scope from `exp-125` and promotes the current
|
|||||||
project/package, stdlib-source, collection, composite-data, diagnostics,
|
project/package, stdlib-source, collection, composite-data, diagnostics,
|
||||||
formatter, docs, and governance surface to beta maturity.
|
formatter, docs, and governance surface to beta maturity.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
Post-beta main currently contains tooling hardening intended for a future
|
||||||
|
`1.0.0-beta.1` bundle:
|
||||||
|
|
||||||
|
- `glagol run <file.slo|project>` builds and executes through the hosted native
|
||||||
|
toolchain using `.slovo/build` when `-o` is not provided
|
||||||
|
- `glagol clean <file.slo|project>` removes generated `.slovo/build` artifacts
|
||||||
|
- `glagol new --template binary|library|workspace` scaffolds binary projects,
|
||||||
|
library projects, and local package workspaces using existing manifest rules
|
||||||
|
- the release gate prints a concise final success line after all checks pass
|
||||||
|
|
||||||
|
This unreleased slice does not add source-language syntax, stable ABI/layout
|
||||||
|
guarantees, networking, package registry behavior, or a stable standard-library
|
||||||
|
freeze.
|
||||||
|
|
||||||
## 1.0.0-beta
|
## 1.0.0-beta
|
||||||
|
|
||||||
Release label: `1.0.0-beta`
|
Release label: `1.0.0-beta`
|
||||||
|
|||||||
@ -932,6 +932,27 @@ Glagol release without network, tag, push, or release-publication side effects.
|
|||||||
The normative v1.7 release contract is
|
The normative v1.7 release contract is
|
||||||
`.llm/V1_7_DEVELOPER_EXPERIENCE_HARDENING.md`.
|
`.llm/V1_7_DEVELOPER_EXPERIENCE_HARDENING.md`.
|
||||||
|
|
||||||
|
### 4.4.1 Post-Beta Tooling Additions
|
||||||
|
|
||||||
|
Post-beta main adds tooling-only conveniences intended for a future
|
||||||
|
`1.0.0-beta.1` bundle. These commands do not change source syntax or typed-core
|
||||||
|
semantics.
|
||||||
|
|
||||||
|
`glagol run <file.slo|project>` builds through the same hosted native path as
|
||||||
|
`glagol build`, writes an executable under `.slovo/build` by default, executes
|
||||||
|
it, forwards stdout/stderr, and exits with the program exit status. `-o
|
||||||
|
<binary>` may override the generated executable path.
|
||||||
|
|
||||||
|
`glagol clean <file.slo|project>` removes the generated `.slovo/build`
|
||||||
|
directory for the given source file directory or project root. It does not
|
||||||
|
remove custom `-o` build outputs.
|
||||||
|
|
||||||
|
`glagol new <project-dir> --template binary|library|workspace` supports three
|
||||||
|
scaffold shapes. `binary` is the default project with `src/main.slo`.
|
||||||
|
`library` creates a checkable/testable project with `src/lib.slo`. `workspace`
|
||||||
|
creates a local two-package workspace using the existing `[workspace]`,
|
||||||
|
`[package]`, and local path dependency rules.
|
||||||
|
|
||||||
## 4.5 v2.0.0-beta.1 Experimental Integration Readiness
|
## 4.5 v2.0.0-beta.1 Experimental Integration Readiness
|
||||||
|
|
||||||
Status: current experimental Slovo-side release contract, released 2026-05-17.
|
Status: current experimental Slovo-side release contract, released 2026-05-17.
|
||||||
|
|||||||
@ -60,3 +60,5 @@ cargo test
|
|||||||
cargo test --test promotion_gate -- --ignored
|
cargo test --test promotion_gate -- --ignored
|
||||||
cargo test --test binary_smoke -- --ignored
|
cargo test --test binary_smoke -- --ignored
|
||||||
cargo test --test llvm_smoke -- --ignored
|
cargo test --test llvm_smoke -- --ignored
|
||||||
|
|
||||||
|
echo "release gate passed: docs, fmt, tests, promotion, binary, and LLVM smoke checks completed"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user