improving logs by adding BuildingIssues structure

This commit is contained in:
bQUARKz 2026-02-24 18:42:43 +00:00
parent efa8063b57
commit 09d0ce5f5a
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
11 changed files with 113 additions and 138 deletions

View File

@ -1,10 +1,11 @@
package p.studio.compiler.workspaces;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyCollection;
public interface PipelineStage {
ReadOnlyCollection<BuildingIssue> run(BuilderPipelineContext ctx, LogAggregator logs);
BuildingIssues run(BuilderPipelineContext ctx, LogAggregator logs);
}

View File

@ -1,12 +1,11 @@
package p.studio.compiler.workspaces.stages;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.messages.DependencyConfig;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.DependencyService;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyCollection;
import java.io.IOException;
import java.nio.file.Path;
@ -14,21 +13,18 @@ import java.nio.file.Paths;
public class DepsPipelineStage implements PipelineStage {
@Override
public ReadOnlyCollection<BuildingIssue> run(final BuilderPipelineContext ctx, LogAggregator logs) {
public BuildingIssues run(final BuilderPipelineContext ctx, LogAggregator logs) {
final Path rootCanonPath;
try {
rootCanonPath = Paths.get(ctx.config.getRootProjectPath()).toRealPath();
} catch (IOException e) {
return ReadOnlyCollection.with(BuildingIssue
.builder()
.error(true)
.message("[BUILD]: root project directory no found: " + ctx.config.getRootProjectPath())
.build());
return BuildingIssues.empty()
.add(builder -> builder.error(true)
.message("[BUILD]: root project directory no found: " + ctx.config.getRootProjectPath()));
}
final var cfg = new DependencyConfig(false, rootCanonPath);
final var resolvedWorkspace = DependencyService.INSTANCE.run(cfg, logs);
resolvedWorkspace.topologicalOrder().forEach(pd -> logs.info("Project [ " + pd.getName() + " ] read"));
ctx.resolvedWorkspace = resolvedWorkspace;
return ReadOnlyCollection.empty();
ctx.resolvedWorkspace = DependencyService.INSTANCE.run(cfg, logs);
ctx.resolvedWorkspace.topologicalOrder().forEach(pd -> logs.info("Project [ " + pd.getName() + " ] read"));
return BuildingIssues.empty();
}
}

View File

@ -0,0 +1,28 @@
package p.studio.compiler.messages;
import p.studio.utilities.structures.ReadOnlyCollection;
import java.util.ArrayList;
import java.util.function.Consumer;
public class BuildingIssues extends ReadOnlyCollection<BuildingIssue> {
protected BuildingIssues() {
super(new ArrayList<>());
}
public static BuildingIssues empty() {
return new BuildingIssues();
}
public BuildingIssues add(final Consumer<BuildingIssue.BuildingIssueBuilder> consumer) {
final var builder = BuildingIssue.builder();
consumer.accept(builder);
collection.add(builder.build());
return this;
}
public BuildingIssues addAll(final BuildingIssues issues) {
collection.addAll(issues.collection);
return this;
}
}

View File

@ -8,15 +8,15 @@ public record ResolvedWorkspace(
ProjectId projectId,
WorkspaceGraph graph,
BuildStack stack) {
public ProjectDescriptor getMainProjectDescriptor() {
return graph.getProjectDescriptor(projectId);
public ProjectDescriptor mainProject() {
return graph.projectDescriptor(projectId);
}
public Stream<ProjectDescriptor> topologicalOrder() {
return stack.topologicalOrder.stream().map(graph::getProjectDescriptor);
return stack.topologicalOrder.stream().map(graph::projectDescriptor);
}
public Stream<ProjectDescriptor> reverseTopologicalOrder() {
return stack.reverseTopologicalOrder.stream().map(graph::getProjectDescriptor);
return stack.reverseTopologicalOrder.stream().map(graph::projectDescriptor);
}
}

View File

@ -8,11 +8,11 @@ public record WorkspaceGraph(
ProjectTable projectTable,
ReadOnlyList<ReadOnlyList<ProjectId>> dependenciesByProjectId) {
public ProjectDescriptor getProjectDescriptor(final ProjectId projectId) {
public ProjectDescriptor projectDescriptor(final ProjectId projectId) {
return projectTable.get(projectId);
}
public ReadOnlyList<ProjectId> getDependencies(final ProjectId projectId) {
public ReadOnlyList<ProjectId> dependencies(final ProjectId projectId) {
return dependenciesByProjectId.get(projectId.getIndex());
}
}

View File

@ -1,9 +1,8 @@
package p.studio.compiler.workspaces;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.DependencyContext;
import p.studio.utilities.structures.ReadOnlyCollection;
public interface DependencyPhase {
ReadOnlyCollection<BuildingIssue> run(DependencyContext state);
BuildingIssues run(DependencyContext state);
}

View File

@ -4,7 +4,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import p.studio.compiler.FrontendRegistryService;
import p.studio.compiler.dtos.PrometeuManifestDTO;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectInfo;
import p.studio.compiler.models.ProjectInfoId;
@ -23,9 +23,9 @@ import java.util.*;
public class DiscoverPhase implements DependencyPhase {
@Override
public ReadOnlyCollection<BuildingIssue> run(final DependencyContext ctx) {
public BuildingIssues run(final DependencyContext ctx) {
final Map<Path, ProjectInfoId> projectIndexByDirectory = new HashMap<>();
final List<BuildingIssue> issues = new ArrayList<>();
final BuildingIssues issues = BuildingIssues.empty();
while (!ctx.pending.isEmpty()) {
final var rootPathCanon = ctx.pending.pollFirst();
@ -37,22 +37,16 @@ public class DiscoverPhase implements DependencyPhase {
try {
manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath();
} catch (IOException e) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon)
.exception(e)
.build();
issues.add(issue);
.exception(e));
continue;
}
if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon)
.build();
issues.add(issue);
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon));
continue;
}
@ -67,12 +61,9 @@ public class DiscoverPhase implements DependencyPhase {
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language());
if (frontendSpec.isEmpty()) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name())
.build();
issues.add(issue);
.message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name()));
continue;
}
@ -91,39 +82,33 @@ public class DiscoverPhase implements DependencyPhase {
ctx.projectNameAndVersions.computeIfAbsent(manifest.name(), ignore -> new HashSet<>()).add(manifest.version());
}
return ReadOnlyCollection.wrap(issues);
return issues;
}
public static Optional<PrometeuManifest> map(
final Path rootPath,
final PrometeuManifestDTO dto,
final List<BuildingIssue> buildingIssues) {
final BuildingIssues issues) {
if (StringUtils.isBlank(dto.name())) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: manifest missing 'name': " + rootPath)
.build();
buildingIssues.add(issue);
.message("[DEPS]: manifest missing 'name': " + rootPath));
}
if (StringUtils.isBlank(dto.version())) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: manifest missing 'version': " + rootPath)
.build();
buildingIssues.add(issue);
.message("[DEPS]: manifest missing 'version': " + rootPath));
}
final var language = StringUtils.isBlank(dto.language())
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
: dto.language();
final var dependencies = resolveDependencies(rootPath, dto.dependencies(), buildingIssues);
final var dependencies = resolveDependencies(rootPath, dto.dependencies(), issues);
if (CollectionUtils.isNotEmpty(buildingIssues)) {
if (ReadOnlyCollection.isNotEmpty(issues)) {
return Optional.empty();
}
@ -133,7 +118,7 @@ public class DiscoverPhase implements DependencyPhase {
private static List<DependencyReference> resolveDependencies(
final Path rootProjectCanonPath,
final List<PrometeuManifestDTO.DependencyDeclaration> dependencies,
final List<BuildingIssue> buildingIssues) {
final BuildingIssues issues) {
if (CollectionUtils.isEmpty(dependencies)) {
return List.of();
}
@ -146,24 +131,16 @@ public class DiscoverPhase implements DependencyPhase {
final Path dependencyPathCanon = rootProjectCanonPath.resolve(local.path()).toRealPath();
deps.add(new DependencyReference(dependencyPathCanon));
} catch (IOException e) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: failed to canonicalize dependency path: " + local.path() + " from (" + rootProjectCanonPath + ")")
.exception(e)
.build();
buildingIssues.add(issue);
.exception(e));
}
}
case PrometeuManifestDTO.DependencyDeclaration.Git git -> {
final var issue = BuildingIssue
.builder()
.error(true)
.message("[DEPS]: git dependencies are not supported yet: " + git.url() + " from (" + rootProjectCanonPath + ")")
.build();
buildingIssues.add(issue);
}
case PrometeuManifestDTO.DependencyDeclaration.Git git -> issues.add(builder -> builder
.error(true)
.message("[DEPS]: git dependencies are not supported yet: " + git.url() + " from (" + rootProjectCanonPath + ")"));
default -> {
}

View File

@ -1,29 +1,26 @@
package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.DependencyContext;
import p.studio.utilities.structures.ReadOnlyCollection;
import p.studio.compiler.workspaces.DependencyPhase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public final class SeedPhase implements DependencyPhase {
@Override
public ReadOnlyCollection<BuildingIssue> run(final DependencyContext ctx) {
final List<BuildingIssue> issues = new ArrayList<>();
public BuildingIssues run(final DependencyContext ctx) {
try {
ctx.mainProjectRootPathCanon = ctx.config().cacheDir().toRealPath();
} catch (IOException e) {
final var issue = BuildingIssue
.builder()
.message("[DEPS]: failed to canonicalize rootProjectId directory: " + ctx.config().cacheDir())
.build();
issues.add(issue);
return BuildingIssues.empty()
.add(builder -> builder
.error(true)
.message("[DEPS]: failed to canonicalize rootProjectId directory: " + ctx.config().cacheDir())
.exception(e));
}
ctx.pending.add(ctx.mainProjectRootPathCanon);
return ReadOnlyCollection.wrap(issues);
return BuildingIssues.empty();
}
}

View File

@ -1,12 +1,11 @@
package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.BuildStack;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectInfoId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.utilities.structures.ReadOnlyCollection;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayDeque;
@ -21,7 +20,7 @@ public final class StackPhase implements DependencyPhase {
* Implements topological sort; detects dependency cycles; sets build stack
*/
@Override
public ReadOnlyCollection<BuildingIssue> run(final DependencyContext ctx) {
public BuildingIssues run(final DependencyContext ctx) {
final int n = ctx.projectTable.size();
final int[] indeg = new int[n];
for (int from = 0; from < n; from++) {
@ -58,16 +57,11 @@ public final class StackPhase implements DependencyPhase {
final List<List<ProjectId>> cycles = new ArrayList<>();
for (var scc : sccs) {
if (scc.size() > 1) {
// final var cycle = scc
// .stream()
// .map(i -> ctx.projectDescriptors.get(i).getProjectId())
// .toList();
final var cycle = scc.stream().map(ProjectId::new).toList();
cycles.add(cycle);
} else {
// size==1: cycle only if self-loop exists
final var u = scc.getFirst();
// final var projectId = ctx.projectDescriptors.get(u).getProjectId();
final var projectId = new ProjectId(u);
boolean selfLoop = false;
for (final var pu : ctx.dependenciesByProject.get(u)) {
@ -89,13 +83,13 @@ public final class StackPhase implements DependencyPhase {
)
.collect(joining("\n"));
var issue = BuildingIssue.builder().error(true).message(msg).build();
return ReadOnlyCollection.with(issue);
return BuildingIssues.empty()
.add(builder -> builder.error(true).message(msg));
}
ctx.stack = new BuildStack(ReadOnlyList.wrap(travesalOrder));
return ReadOnlyCollection.empty();
return BuildingIssues.empty();
}
static final class TarjanScc {

View File

@ -1,31 +1,26 @@
package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.utilities.structures.ReadOnlyCollection;
public final class ValidatePhase implements DependencyPhase {
@Override
public ReadOnlyCollection<BuildingIssue> run(final DependencyContext ctx) {
public BuildingIssues run(final DependencyContext ctx) {
// ensure rootProjectId is set
if (ctx.rootProjectId == null) {
final var issue = BuildingIssue
.builder()
.error(true)
.message("[DEPS]: rootProjectId ProjectId not set")
.build();
return ReadOnlyCollection.with(issue);
return BuildingIssues.empty()
.add(builder -> builder
.error(true)
.message("[DEPS]: rootProjectId ProjectId not set"));
}
// ensure the dependenciesByProject matches the number of projectDescriptors
if (ctx.dependenciesByProject.size() != ctx.projectTable.size()) {
final var issue = BuildingIssue
.builder()
.error(true)
.message("[DEPS]: internal error: dependenciesByProject and projectDescriptors size mismatch")
.build();
return ReadOnlyCollection.with(issue);
return BuildingIssues.empty()
.add(builder -> builder
.error(true)
.message("[DEPS]: internal error: dependenciesByProject and projectDescriptors size mismatch"));
}
// ensure uniformity to version across the same projectIds (associated by name)
@ -33,17 +28,15 @@ public final class ValidatePhase implements DependencyPhase {
final var name = entry.getKey();
final var versions = entry.getValue();
if (versions.size() > 1) {
final var issue = BuildingIssue
.builder()
.error(true)
.message("[DEPS]: inconsistent version for project: " + name + " (" + versions + ")")
.build();
return ReadOnlyCollection.with(issue);
return BuildingIssues.empty()
.add(builder -> builder
.error(true)
.message("[DEPS]: inconsistent version for project: " + name + " (" + versions + ")"));
}
}
// run check over source policy (if any) from here (FrontedSpec)
return ReadOnlyCollection.empty();
return BuildingIssues.empty();
}
}

View File

@ -1,13 +1,12 @@
package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.messages.BuildingIssues;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.ProjectInfo;
import p.studio.compiler.models.ProjectInfoId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.utilities.structures.ReadOnlyCollection;
import p.studio.utilities.structures.ReadOnlyList;
import java.io.IOException;
@ -17,14 +16,14 @@ import java.util.List;
public final class WireProjectsPhase implements DependencyPhase {
@Override
public ReadOnlyCollection<BuildingIssue> run(final DependencyContext ctx) {
public BuildingIssues run(final DependencyContext ctx) {
// to start all over again, we will re-populate the project nodes and dependenciesByProjectId based on the project infos
ctx.rootProjectId = null;
ctx.projectIds.clear();
ctx.projectTable.clear();
ctx.dependenciesByProject.clear();
final List<BuildingIssue> issues = new ArrayList<>();
final BuildingIssues issues = BuildingIssues.empty();
for (int index = 0; index < ctx.projectInfoTable.size(); index++) {
final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index));
@ -35,14 +34,11 @@ public final class WireProjectsPhase implements DependencyPhase {
final var rootProjectId = ctx.projectTable.optionalId(ctx.mainProjectRootPathCanon);
if (rootProjectId.isEmpty()) {
final var issue = BuildingIssue
.builder()
.message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized")
.build();
issues.add(issue);
return ReadOnlyCollection.wrap(issues);
return issues.add(builder -> builder
.message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized"));
}
// since it is not ordered, we have to iterate it over again, but now associating dependencies with projectIds
for (int i = 0; i < ctx.projectInfoTable.size(); i++) {
final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(i));
@ -51,12 +47,9 @@ public final class WireProjectsPhase implements DependencyPhase {
final var dependencyCanonPath = dependency.canonPath();
final var projectId = ctx.projectTable.optionalId(dependencyCanonPath);
if (projectId.isEmpty()) {
final var issue = BuildingIssue
.builder()
issues.add(builder -> builder
.error(true)
.message("[DEPS]: dependency not found: " + dependencyCanonPath)
.build();
issues.add(issue);
.message("[DEPS]: dependency not found: " + dependencyCanonPath));
continue;
}
dependencies.add(projectId.get());
@ -66,13 +59,13 @@ public final class WireProjectsPhase implements DependencyPhase {
ctx.rootProjectId = rootProjectId.get();
return ReadOnlyCollection.wrap(issues);
return issues;
}
private static ProjectDescriptor buildProjectDescriptor(
final ProjectInfo projectInfo,
final List<BuildingIssue> issues) {
final List<BuildingIssue> sourceRootIssues = new ArrayList<>();
final BuildingIssues issues) {
final BuildingIssues sourceRootIssues = BuildingIssues.empty();
final List<Path> sourceRoots = new ArrayList<>();
for (final var sourceRoot : projectInfo.getFrontendSpec().getSourceRoots()) {
final var sourceRootPath = projectInfo.rootDirectory.resolve(sourceRoot);
@ -80,13 +73,10 @@ public final class WireProjectsPhase implements DependencyPhase {
final var sourceRootPathCanon = sourceRootPath.toRealPath();
sourceRoots.add(sourceRootPathCanon);
} catch (IOException e) {
final var issue = BuildingIssue
.builder()
sourceRootIssues.add(builder -> builder
.error(true)
.message("[DEPS]: source project canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")")
.exception(e)
.build();
sourceRootIssues.add(issue);
.exception(e));
}
}
if (sourceRootIssues.size() == projectInfo.getFrontendSpec().getSourceRoots().size()) {