Document workspace package graphs

This commit is contained in:
sanjin 2026-05-22 13:17:21 +02:00
parent 0c612ad7fd
commit 0337974923
10 changed files with 92 additions and 11 deletions

View File

@ -20,12 +20,14 @@ ABI promise.
- Diagnose missing `default_package` references during workspace loading.
- Diagnose duplicate workspace members after path normalization before package
loading.
- Add workspace package and dependency summaries to `glagol doc`.
- Keep dependency resolution local-path-only and deterministic.
## Acceptance Gates
- `cargo test --test project_mode workspace_default_package`
- `cargo test --test project_mode workspace_package_boundaries`
- `cargo test --test dx_v1_7 doc_generates_workspace_package_summary`
- `cargo fmt --check`
- `git diff --check`

View File

@ -156,8 +156,10 @@ After `1.0.0-beta.4`, `main` is tracking a package/workspace discipline slice.
Local workspaces may declare `[workspace] default_package = "name"` to select
the build/run entry package when multiple packages have entry modules.
Duplicate normalized workspace members and missing default-package references
are now dedicated diagnostics. Remote registries, lockfiles, semantic-version
solving, package publishing, and stable package ABI/layout remain deferred.
are now dedicated diagnostics. `glagol doc <workspace> -o <dir>` includes a
workspace package/dependency summary. Remote registries, lockfiles,
semantic-version solving, package publishing, and stable package ABI/layout
remain deferred.
## Documentation

View File

@ -4,14 +4,14 @@ use crate::{
ast::Program,
diag::Diagnostic,
lexer, lower,
project::{self, SourceFile, ToolFailure},
project::{self, ProjectArtifact, SourceFile, ToolFailure, WorkspaceArtifact},
sexpr::{Atom, SExpr, SExprKind},
};
pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> {
let docs = if project::is_project_input(input) {
let loaded = project::load_project_sources_for_tools(input)?;
render_project(&loaded.artifact.project_name, &loaded.sources)?
render_project(&loaded.artifact, &loaded.sources)?
} else {
let source = fs::read_to_string(input).map_err(|err| ToolFailure {
diagnostics: vec![Diagnostic::new(
@ -52,11 +52,17 @@ pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> {
})
}
fn render_project(title: &str, sources: &[SourceFile]) -> Result<String, ToolFailure> {
fn render_project(
artifact: &ProjectArtifact,
sources: &[SourceFile],
) -> Result<String, ToolFailure> {
let mut out = String::new();
out.push_str("# Project ");
out.push_str(title);
out.push_str(&artifact.project_name);
out.push_str("\n\n");
if let Some(workspace) = &artifact.workspace {
render_workspace(&mut out, workspace);
}
for source in sources {
let module = document_source(source)?;
render_module(&mut out, &module);
@ -64,6 +70,35 @@ fn render_project(title: &str, sources: &[SourceFile]) -> Result<String, ToolFai
Ok(out)
}
fn render_workspace(out: &mut String, workspace: &WorkspaceArtifact) {
out.push_str("## Workspace\n\n");
render_list(out, "Members", &workspace.members);
let packages = workspace
.packages
.iter()
.map(|package| {
format!(
"{} {} (entry {})",
package.name, package.version, package.entry
)
})
.collect::<Vec<_>>();
render_list(out, "Packages", &packages);
let dependencies = workspace
.dependencies
.iter()
.map(|dependency| {
format!(
"{} -> {} ({}, {})",
dependency.from, dependency.to, dependency.kind, dependency.path
)
})
.collect::<Vec<_>>();
render_list(out, "Package Dependencies", &dependencies);
}
fn render_file(title: &str, sources: &[SourceFile]) -> Result<String, ToolFailure> {
let mut out = String::new();
out.push_str("# File ");

View File

@ -351,6 +351,38 @@ fn doc_generates_markdown_for_file_and_project() {
assert!(project_index.contains("- `math`"));
}
#[test]
fn doc_generates_workspace_package_summary() {
let workspace = unique_path("doc-workspace");
let new_output = run_glagol([
"new".as_ref(),
workspace.as_os_str(),
"--template".as_ref(),
"workspace".as_ref(),
]);
assert_success("doc workspace scaffold", &new_output);
let workspace_docs = unique_path("doc-workspace-out");
let doc_output = run_glagol([
"doc".as_ref(),
workspace.as_os_str(),
"-o".as_ref(),
workspace_docs.as_os_str(),
]);
assert_success("doc workspace", &doc_output);
let index = fs::read_to_string(workspace_docs.join("index.md")).expect("read workspace docs");
assert!(index.contains("## Workspace"));
assert!(index.contains("### Members"));
assert!(index.contains("- `packages/app`"));
assert!(index.contains("- `packages/libutil`"));
assert!(index.contains("### Packages"));
assert!(index.contains("- `app 0.1.0 (entry main)`"));
assert!(index.contains("- `libutil 0.1.0 (entry main)`"));
assert!(index.contains("### Package Dependencies"));
assert!(index.contains("- `app -> libutil (local-path, packages/libutil)`"));
}
#[cfg(unix)]
#[test]
fn project_tooling_rejects_symlinked_module_escape() {

View File

@ -134,7 +134,8 @@ Work:
In progress after `1.0.0-beta.4`: local workspaces can declare
`default_package` to select the build/run entry package when multiple packages
have entry modules. Duplicate normalized member paths and missing default
package references are dedicated diagnostics. Lockfiles, remote registries,
package references are dedicated diagnostics. Workspace documentation now
includes package and local dependency summaries. Lockfiles, remote registries,
semver solving, publishing, optional/dev/target dependencies, and stable
package ABI/layout remain out of scope.

View File

@ -18,6 +18,8 @@ integration/readiness release, not the first real beta.
- Workspace loading now diagnoses a missing `default_package` reference with
`WorkspaceDefaultPackageMissing`, and build/run diagnose a selected package
that lacks its entry module with `WorkspaceDefaultPackageEntryMissing`.
- `glagol doc <workspace> -o <dir>` now includes a deterministic workspace
summary with members, packages, and local package dependency edges.
## 1.0.0-beta.4

View File

@ -33,8 +33,8 @@ diagnostics.
Current unreleased work is the package/workspace discipline slice. It adds
`[workspace] default_package = "name"` for deterministic build/run entry
selection in multi-entry workspaces and tightens workspace-member/default
package diagnostics while keeping registries, lockfiles, semver solving, and
publishing deferred.
package diagnostics. Workspace docs now include package/dependency summaries.
Registries, lockfiles, semver solving, and publishing remain deferred.
The final experimental precursor scope is `exp-125`. Its unsigned direct-value
flow, parse/format runtime lanes, and matching staged stdlib helper breadth

View File

@ -26,6 +26,8 @@ diagnostics bundle.
`WorkspaceDefaultPackageMissing` diagnostic. A selected default package that
lacks its configured entry module is diagnosed as
`WorkspaceDefaultPackageEntryMissing` during build/run.
- Workspace documentation generated by `glagol doc` now includes a
deterministic summary of members, packages, and local dependency edges.
## 1.0.0-beta.4

View File

@ -20,8 +20,8 @@ standard-library stabilization release, entry-specific project/workspace
Current unreleased work is the package/workspace discipline slice. It adds
`[workspace] default_package = "name"` for deterministic build/run entry
selection in multi-entry workspaces and tightens duplicate-member/default
package diagnostics while keeping registries, lockfiles, semver solving, and
publishing deferred.
package diagnostics. Workspace docs now include package/dependency summaries.
Registries, lockfiles, semver solving, and publishing remain deferred.
The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its

View File

@ -1032,6 +1032,11 @@ Workspace member paths are normalized under the workspace root before package
loading. Duplicate normalized member paths are invalid, even if they were
spelled differently in the manifest.
`glagol doc <workspace> -o <dir>` documents the workspace graph in addition to
module structure. The generated `index.md` includes deterministic lists of
workspace members, packages, and local package dependency edges before the
module sections.
This slice does not add remote registries, lockfiles, semantic-version
solving, package publishing, archive formats, optional/dev/target
dependencies, feature flags, package build scripts, or stable package