frontend spec by workspace instead

This commit is contained in:
bQUARKz 2026-02-26 05:08:47 +00:00
parent f627914c9f
commit 9170104a12
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
17 changed files with 93 additions and 89 deletions

View File

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

View File

@ -3,7 +3,7 @@ package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.models.SourceHandle; import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.workspaces.PipelineStage; import p.studio.compiler.workspaces.PipelineStage;
@ -26,15 +26,15 @@ import java.util.stream.Collectors;
public class LoadPipelineStage implements PipelineStage { public class LoadPipelineStage implements PipelineStage {
@Override @Override
public BuildingIssues run(final BuilderPipelineContext ctx, final LogAggregator logs) { public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
final var issues = BuildingIssues.empty(); final var issues = BuildingIssueSink.empty();
// Iterates projects; "loads sources"; registers files // Iterates projects; "loads sources"; registers files
ctx.resolvedWorkspace.topologicalOrder().forEach(pId -> { ctx.resolvedWorkspace.topologicalOrder().forEach(pId -> {
final var pd = ctx.resolvedWorkspace.graph().projectTable().get(pId); final var pd = ctx.resolvedWorkspace.graph().projectTable().get(pId);
logs.using(log).info("Project [ " + pd.getName() + " ] source loading..."); logs.using(log).info("Project [ " + pd.getName() + " ] source loading...");
final var allowedExtensions = normalize(pd.getFrontendSpec().getAllowedExtensions()); final var allowedExtensions = normalize(ctx.resolvedWorkspace.frontendSpec().getAllowedExtensions());
for (final var sourceRootPath : pd.getSourceRoots()) { for (final var sourceRootPath : pd.getSourceRoots()) {
logs.using(log).debug("Walking source root [ " + sourceRootPath + " ]"); logs.using(log).debug("Walking source root [ " + sourceRootPath + " ]");
@ -52,14 +52,14 @@ public class LoadPipelineStage implements PipelineStage {
var fileId = ctx.fileTable.register(rawFile); var fileId = ctx.fileTable.register(rawFile);
} }
} catch (IOException e) { } catch (IOException e) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("Failed to load project [ " + pd.getName() + " ]") .message("Failed to load project [ " + pd.getName() + " ]")
.exception(e)); .exception(e));
} }
} }
}); });
return BuildingIssues.empty(); return BuildingIssueSink.empty();
} }
private static ReadOnlySet<String> normalize(final ReadOnlySet<String> extensions) { private static ReadOnlySet<String> normalize(final ReadOnlySet<String> extensions) {

View File

@ -1,7 +1,7 @@
package p.studio.compiler.workspaces.stages; package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.messages.DependencyConfig; import p.studio.compiler.messages.DependencyConfig;
import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.DependencyService; import p.studio.compiler.workspaces.DependencyService;
@ -14,18 +14,18 @@ import java.nio.file.Paths;
@Slf4j @Slf4j
public class ResolvePipelineStage implements PipelineStage { public class ResolvePipelineStage implements PipelineStage {
@Override @Override
public BuildingIssues run(final BuilderPipelineContext ctx, LogAggregator logs) { public BuildingIssueSink run(final BuilderPipelineContext ctx, LogAggregator logs) {
try { try {
ctx.rootProjectPathCanon = Paths.get(ctx.config.rootProjectPath()).toRealPath(); ctx.rootProjectPathCanon = Paths.get(ctx.config.rootProjectPath()).toRealPath();
} catch (IOException e) { } catch (IOException e) {
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder .report(builder -> builder
.error(true) .error(true)
.message("[BUILD]: root project directory no found: " + ctx.config.rootProjectPath()) .message("[BUILD]: root project directory no found: " + ctx.config.rootProjectPath())
.exception(e)); .exception(e));
} }
final var dependencyConfig = new DependencyConfig(ctx.config.explain(), ctx.rootProjectPathCanon); final var dependencyConfig = new DependencyConfig(ctx.config.explain(), ctx.rootProjectPathCanon);
ctx.resolvedWorkspace = DependencyService.INSTANCE.run(dependencyConfig, logs); ctx.resolvedWorkspace = DependencyService.INSTANCE.run(dependencyConfig, logs);
return BuildingIssues.empty(); return BuildingIssueSink.empty();
} }
} }

View File

@ -5,18 +5,18 @@ import p.studio.utilities.structures.ReadOnlyCollection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.function.Consumer; import java.util.function.Consumer;
public class BuildingIssues extends ReadOnlyCollection<BuildingIssue> { public class BuildingIssueSink extends ReadOnlyCollection<BuildingIssue> {
private boolean hasErrors = false; private boolean hasErrors = false;
protected BuildingIssues() { protected BuildingIssueSink() {
super(new ArrayList<>()); super(new ArrayList<>());
} }
public static BuildingIssues empty() { public static BuildingIssueSink empty() {
return new BuildingIssues(); return new BuildingIssueSink();
} }
public BuildingIssues add(final Consumer<BuildingIssue.BuildingIssueBuilder> consumer) { public BuildingIssueSink report(final Consumer<BuildingIssue.BuildingIssueBuilder> consumer) {
final var builder = BuildingIssue.builder(); final var builder = BuildingIssue.builder();
consumer.accept(builder); consumer.accept(builder);
final var issue = builder.build(); final var issue = builder.build();
@ -25,7 +25,7 @@ public class BuildingIssues extends ReadOnlyCollection<BuildingIssue> {
return this; return this;
} }
public BuildingIssues add(final BuildingIssues issues) { public BuildingIssueSink report(final BuildingIssueSink issues) {
hasErrors |= issues.hasErrors(); hasErrors |= issues.hasErrors();
collection.addAll(issues.collection); collection.addAll(issues.collection);
return this; return this;

View File

@ -11,4 +11,8 @@ public class FrontendSpec {
private final ReadOnlySet<String> allowedExtensions; private final ReadOnlySet<String> allowedExtensions;
private final ReadOnlySet<String> sourceRoots; private final ReadOnlySet<String> sourceRoots;
private final boolean caseSensitive; private final boolean caseSensitive;
public String toString() {
return String.format("FrontendSpec(language=%s)", languageId);
}
} }

View File

@ -12,6 +12,5 @@ public final class ProjectDescriptor {
private final Path rootPath; // canon root path private final Path rootPath; // canon root path
private final String name; // project name private final String name; // project name
private final String version; // project version private final String version; // project version
private final ReadOnlyList<Path> sourceRoots; private final ReadOnlyList<Path> sourceRoots; // source roots canon paths for the project
private final FrontendSpec frontendSpec;
} }

View File

@ -1,4 +1,4 @@
package p.studio.compiler.source.diagnostics; package p.studio.compiler.source.diagnostics;
public class DiagnosticBundle { public class DiagnosticSink {
} }

View File

@ -10,21 +10,18 @@ import java.nio.file.Path;
import java.util.*; import java.util.*;
public final class DependencyContext { public final class DependencyContext {
private final DependencyConfig config; public final DependencyConfig config;
// Internal state mirroring Rust ResolverState
public Path mainProjectRootPathCanon;
// Phase 1 (Discover)
public final Deque<Path> pending = new ArrayDeque<>(); public final Deque<Path> pending = new ArrayDeque<>();
public final ProjectInfoTable projectInfoTable = new ProjectInfoTable();
public final ProjectInfoTable projectInfoTable = new ProjectInfoTable();
public final ProjectTable projectTable = new ProjectTable(); public final ProjectTable projectTable = new ProjectTable();
public final Map<String, Set<String>> projectNameAndVersions = new HashMap<>(); public final Map<String, Set<String>> projectNameAndVersions = new HashMap<>();
public final List<ProjectId> projectIds = new ArrayList<>(); public final List<ProjectId> projectIds = new ArrayList<>();
public final List<List<ProjectId>> dependenciesByProject = new ArrayList<>(); public final List<List<ProjectId>> dependenciesByProject = new ArrayList<>();
public Path mainProjectRootPathCanon;
public FrontendSpec frontendSpec;
public ProjectId rootProjectId; public ProjectId rootProjectId;
public BuildStack stack; public BuildStack stack;
@ -36,10 +33,6 @@ public final class DependencyContext {
return new DependencyContext(config); return new DependencyContext(config);
} }
public DependencyConfig config() {
return config;
}
private ReadOnlyList<ReadOnlyList<ProjectId>> buildDependenciesByProject() { private ReadOnlyList<ReadOnlyList<ProjectId>> buildDependenciesByProject() {
return ReadOnlyList.wrap(this return ReadOnlyList.wrap(this
.dependenciesByProject .dependenciesByProject
@ -54,7 +47,7 @@ public final class DependencyContext {
} }
final var dependenciesByProject = buildDependenciesByProject(); final var dependenciesByProject = buildDependenciesByProject();
final var workspaceGraph = new WorkspaceGraph(projectTable, dependenciesByProject); final var workspaceGraph = new WorkspaceGraph(projectTable, dependenciesByProject);
return new ResolvedWorkspace(rootProjectId, workspaceGraph, stack); return new ResolvedWorkspace(rootProjectId, frontendSpec, workspaceGraph, stack);
} }

View File

@ -11,5 +11,4 @@ public final class ProjectInfo {
public final Path rootDirectory; public final Path rootDirectory;
public final Path manifestPath; public final Path manifestPath;
public final PrometeuManifest manifest; public final PrometeuManifest manifest;
public final FrontendSpec frontendSpec;
} }

View File

@ -6,6 +6,7 @@ import java.util.stream.Stream;
public record ResolvedWorkspace( public record ResolvedWorkspace(
ProjectId projectId, ProjectId projectId,
FrontendSpec frontendSpec,
WorkspaceGraph graph, WorkspaceGraph graph,
BuildStack stack) { BuildStack stack) {

View File

@ -1,8 +1,8 @@
package p.studio.compiler.workspaces; package p.studio.compiler.workspaces;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
public interface DependencyPhase { public interface DependencyPhase {
BuildingIssues run(DependencyContext state); BuildingIssueSink run(DependencyContext state);
} }

View File

@ -4,7 +4,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import p.studio.compiler.FrontendRegistryService; import p.studio.compiler.FrontendRegistryService;
import p.studio.compiler.dtos.PrometeuManifestDTO; import p.studio.compiler.dtos.PrometeuManifestDTO;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectInfo; import p.studio.compiler.models.ProjectInfo;
import p.studio.compiler.models.ProjectInfoId; import p.studio.compiler.models.ProjectInfoId;
@ -25,9 +25,9 @@ import java.util.*;
public class DiscoverPhase implements DependencyPhase { public class DiscoverPhase implements DependencyPhase {
@Override @Override
public BuildingIssues run(final DependencyContext ctx) { public BuildingIssueSink run(final DependencyContext ctx) {
final Map<Path, ProjectInfoId> projectIndexByDirectory = new HashMap<>(); final Map<Path, ProjectInfoId> projectIndexByDirectory = new HashMap<>();
final BuildingIssues issues = BuildingIssues.empty(); final BuildingIssueSink issues = BuildingIssueSink.empty();
// Discovers projects; registers them; adds dependencies to queue // Discovers projects; registers them; adds dependencies to queue
while (!ctx.pending.isEmpty()) { while (!ctx.pending.isEmpty()) {
final var rootPathCanon = ctx.pending.pollFirst(); final var rootPathCanon = ctx.pending.pollFirst();
@ -40,14 +40,14 @@ public class DiscoverPhase implements DependencyPhase {
try { try {
manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath(); manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath();
} catch (IOException e) { } catch (IOException e) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon) .message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon)
.exception(e)); .exception(e));
continue; continue;
} }
if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) { if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon)); .message("[DEPS]: manifest not found: expected a file " + manifestPathCanon));
continue; continue;
@ -64,18 +64,27 @@ public class DiscoverPhase implements DependencyPhase {
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language()); final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language());
if (frontendSpec.isEmpty()) { if (frontendSpec.isEmpty()) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name())); .message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name()));
continue; continue;
} }
if (Objects.isNull(ctx.frontendSpec)) {
ctx.frontendSpec = frontendSpec.get();
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) {
issues.report(builder -> builder
.error(true)
.message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"",
manifest.name(), frontendSpec.get().getLanguageId(), ctx.frontendSpec.getLanguageId())));
continue;
}
final var projectInfo = ProjectInfo final var projectInfo = ProjectInfo
.builder() .builder()
.rootDirectory(rootPathCanon) .rootDirectory(rootPathCanon)
.manifestPath(manifestPathCanon) .manifestPath(manifestPathCanon)
.manifest(manifest) .manifest(manifest)
.frontendSpec(frontendSpec.get())
.build(); .build();
final var projectInfoId = ctx.projectInfoTable.register(projectInfo); final var projectInfoId = ctx.projectInfoTable.register(projectInfo);
projectIndexByDirectory.put(rootPathCanon, projectInfoId); projectIndexByDirectory.put(rootPathCanon, projectInfoId);
@ -91,16 +100,16 @@ public class DiscoverPhase implements DependencyPhase {
public static Optional<PrometeuManifest> map( public static Optional<PrometeuManifest> map(
final Path rootPath, final Path rootPath,
final PrometeuManifestDTO dto, final PrometeuManifestDTO dto,
final BuildingIssues issues) { final BuildingIssueSink issues) {
if (StringUtils.isBlank(dto.name())) { if (StringUtils.isBlank(dto.name())) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: manifest missing 'name': " + rootPath)); .message("[DEPS]: manifest missing 'name': " + rootPath));
} }
if (StringUtils.isBlank(dto.version())) { if (StringUtils.isBlank(dto.version())) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: manifest missing 'version': " + rootPath)); .message("[DEPS]: manifest missing 'version': " + rootPath));
} }
@ -121,14 +130,14 @@ public class DiscoverPhase implements DependencyPhase {
private static List<DependencyReference> resolveDependencies( private static List<DependencyReference> resolveDependencies(
final Path rootProjectCanonPath, final Path rootProjectCanonPath,
final List<PrometeuManifestDTO.DependencyDeclaration> dependencies, final List<PrometeuManifestDTO.DependencyDeclaration> dependencies,
final BuildingIssues issues) { final BuildingIssueSink issues) {
if (CollectionUtils.isEmpty(dependencies)) { if (CollectionUtils.isEmpty(dependencies)) {
return List.of(); return List.of();
} }
final var registryMaybe = RegistryStore.INSTANCE.load(rootProjectCanonPath); final var registryMaybe = RegistryStore.INSTANCE.load(rootProjectCanonPath);
if (registryMaybe.isEmpty()) { if (registryMaybe.isEmpty()) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: failed to load registry from " + rootProjectCanonPath)); .message("[DEPS]: failed to load registry from " + rootProjectCanonPath));
return List.of(); return List.of();
@ -148,7 +157,7 @@ public class DiscoverPhase implements DependencyPhase {
final Path pathCanon = pathResolve.toRealPath(); final Path pathCanon = pathResolve.toRealPath();
deps.add(new DependencyReference(pathCanon)); deps.add(new DependencyReference(pathCanon));
} catch (IOException e) { } catch (IOException e) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: failed to canonicalize dependency path: " + pathResolve) .message("[DEPS]: failed to canonicalize dependency path: " + pathResolve)
.exception(e)); .exception(e));

View File

@ -1,6 +1,6 @@
package p.studio.compiler.workspaces.phases; package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.workspaces.DependencyPhase; import p.studio.compiler.workspaces.DependencyPhase;
@ -8,19 +8,19 @@ import java.io.IOException;
public final class SeedPhase implements DependencyPhase { public final class SeedPhase implements DependencyPhase {
@Override @Override
public BuildingIssues run(final DependencyContext ctx) { public BuildingIssueSink run(final DependencyContext ctx) {
try { try {
ctx.mainProjectRootPathCanon = ctx.config().cacheDir().toRealPath(); ctx.mainProjectRootPathCanon = ctx.config.cacheDir().toRealPath();
} catch (IOException e) { } catch (IOException e) {
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder .report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: failed to canonicalize rootProjectId directory: " + ctx.config().cacheDir()) .message("[DEPS]: failed to canonicalize rootProjectId directory: " + ctx.config.cacheDir())
.exception(e)); .exception(e));
} }
ctx.pending.add(ctx.mainProjectRootPathCanon); ctx.pending.add(ctx.mainProjectRootPathCanon);
return BuildingIssues.empty(); return BuildingIssueSink.empty();
} }
} }

View File

@ -1,6 +1,6 @@
package p.studio.compiler.workspaces.phases; package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuildStack; import p.studio.compiler.models.BuildStack;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectInfoId; import p.studio.compiler.models.ProjectInfoId;
@ -20,7 +20,7 @@ public final class StackPhase implements DependencyPhase {
* Implements topological sort; detects dependency cycles; sets build stack * Implements topological sort; detects dependency cycles; sets build stack
*/ */
@Override @Override
public BuildingIssues run(final DependencyContext ctx) { public BuildingIssueSink run(final DependencyContext ctx) {
final int n = ctx.projectTable.size(); final int n = ctx.projectTable.size();
final int[] indeg = new int[n]; final int[] indeg = new int[n];
for (int from = 0; from < n; from++) { for (int from = 0; from < n; from++) {
@ -83,13 +83,13 @@ public final class StackPhase implements DependencyPhase {
) )
.collect(joining("\n")); .collect(joining("\n"));
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder.error(true).message(msg)); .report(builder -> builder.error(true).message(msg));
} }
ctx.stack = new BuildStack(ReadOnlyList.wrap(travesalOrder)); ctx.stack = new BuildStack(ReadOnlyList.wrap(travesalOrder));
return BuildingIssues.empty(); return BuildingIssueSink.empty();
} }
static final class TarjanScc { static final class TarjanScc {

View File

@ -1,24 +1,24 @@
package p.studio.compiler.workspaces.phases; package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.workspaces.DependencyPhase; import p.studio.compiler.workspaces.DependencyPhase;
public final class ValidatePhase implements DependencyPhase { public final class ValidatePhase implements DependencyPhase {
@Override @Override
public BuildingIssues run(final DependencyContext ctx) { public BuildingIssueSink run(final DependencyContext ctx) {
// ensure rootProjectId is set // ensure rootProjectId is set
if (ctx.rootProjectId == null) { if (ctx.rootProjectId == null) {
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder .report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: rootProjectId ProjectId not set")); .message("[DEPS]: rootProjectId ProjectId not set"));
} }
// ensure the dependenciesByProject matches the number of projectDescriptors // ensure the dependenciesByProject matches the number of projectDescriptors
if (ctx.dependenciesByProject.size() != ctx.projectTable.size()) { if (ctx.dependenciesByProject.size() != ctx.projectTable.size()) {
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder .report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: internal error: dependenciesByProject and projectDescriptors size mismatch")); .message("[DEPS]: internal error: dependenciesByProject and projectDescriptors size mismatch"));
} }
@ -28,8 +28,8 @@ public final class ValidatePhase implements DependencyPhase {
final var name = entry.getKey(); final var name = entry.getKey();
final var versions = entry.getValue(); final var versions = entry.getValue();
if (versions.size() > 1) { if (versions.size() > 1) {
return BuildingIssues.empty() return BuildingIssueSink.empty()
.add(builder -> builder .report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: inconsistent version for project: " + name + " (" + versions + ")")); .message("[DEPS]: inconsistent version for project: " + name + " (" + versions + ")"));
} }
@ -37,6 +37,6 @@ public final class ValidatePhase implements DependencyPhase {
// run check over source policy (if any) from here (FrontedSpec) // run check over source policy (if any) from here (FrontedSpec)
return BuildingIssues.empty(); return BuildingIssueSink.empty();
} }
} }

View File

@ -1,6 +1,6 @@
package p.studio.compiler.workspaces.phases; package p.studio.compiler.workspaces.phases;
import p.studio.compiler.messages.BuildingIssues; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectDescriptor; import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.ProjectInfo; import p.studio.compiler.models.ProjectInfo;
@ -16,25 +16,25 @@ import java.util.List;
public final class WireProjectsPhase implements DependencyPhase { public final class WireProjectsPhase implements DependencyPhase {
@Override @Override
public BuildingIssues run(final DependencyContext ctx) { public BuildingIssueSink run(final DependencyContext ctx) {
// to start all over again, we will re-populate the project nodes and dependenciesByProjectId based on the project infos // to start all over again, we will re-populate the project nodes and dependenciesByProjectId based on the project infos
ctx.rootProjectId = null; ctx.rootProjectId = null;
ctx.projectIds.clear(); ctx.projectIds.clear();
ctx.projectTable.clear(); ctx.projectTable.clear();
ctx.dependenciesByProject.clear(); ctx.dependenciesByProject.clear();
final BuildingIssues issues = BuildingIssues.empty(); final BuildingIssueSink issues = BuildingIssueSink.empty();
for (int index = 0; index < ctx.projectInfoTable.size(); index++) { for (int index = 0; index < ctx.projectInfoTable.size(); index++) {
final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index)); final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index));
final var projectDescriptor = buildProjectDescriptor(projectInfo, issues); final var projectDescriptor = buildProjectDescriptor(ctx, projectInfo, issues);
final var projectId = ctx.projectTable.register(projectDescriptor); final var projectId = ctx.projectTable.register(projectDescriptor);
ctx.projectIds.add(projectId); ctx.projectIds.add(projectId);
} }
final var rootProjectId = ctx.projectTable.optionalId(ctx.mainProjectRootPathCanon); final var rootProjectId = ctx.projectTable.optionalId(ctx.mainProjectRootPathCanon);
if (rootProjectId.isEmpty()) { if (rootProjectId.isEmpty()) {
return issues.add(builder -> builder return issues.report(builder -> builder
.message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized")); .message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized"));
} }
@ -47,7 +47,7 @@ public final class WireProjectsPhase implements DependencyPhase {
final var dependencyCanonPath = dependency.canonPath(); final var dependencyCanonPath = dependency.canonPath();
final var projectId = ctx.projectTable.optionalId(dependencyCanonPath); final var projectId = ctx.projectTable.optionalId(dependencyCanonPath);
if (projectId.isEmpty()) { if (projectId.isEmpty()) {
issues.add(builder -> builder issues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: dependency not found: " + dependencyCanonPath)); .message("[DEPS]: dependency not found: " + dependencyCanonPath));
continue; continue;
@ -63,25 +63,26 @@ public final class WireProjectsPhase implements DependencyPhase {
} }
private static ProjectDescriptor buildProjectDescriptor( private static ProjectDescriptor buildProjectDescriptor(
final DependencyContext ctx,
final ProjectInfo projectInfo, final ProjectInfo projectInfo,
final BuildingIssues issues) { final BuildingIssueSink issues) {
final BuildingIssues sourceRootIssues = BuildingIssues.empty(); final BuildingIssueSink sourceRootIssues = BuildingIssueSink.empty();
final List<Path> sourceRoots = new ArrayList<>(); final List<Path> sourceRoots = new ArrayList<>();
for (final var sourceRoot : projectInfo.getFrontendSpec().getSourceRoots()) { for (final var sourceRoot : ctx.frontendSpec.getSourceRoots()) {
final var sourceRootPath = projectInfo.rootDirectory.resolve(sourceRoot); final var sourceRootPath = projectInfo.rootDirectory.resolve(sourceRoot);
try { try {
final var sourceRootPathCanon = sourceRootPath.toRealPath(); final var sourceRootPathCanon = sourceRootPath.toRealPath();
sourceRoots.add(sourceRootPathCanon); sourceRoots.add(sourceRootPathCanon);
} catch (IOException e) { } catch (IOException e) {
sourceRootIssues.add(builder -> builder sourceRootIssues.report(builder -> builder
.error(true) .error(true)
.message("[DEPS]: source project canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")") .message("[DEPS]: source project canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")")
.exception(e)); .exception(e));
} }
} }
if (sourceRootIssues.size() == projectInfo.getFrontendSpec().getSourceRoots().size()) { if (sourceRootIssues.size() == ctx.frontendSpec.getSourceRoots().size()) {
// no source roots were found at all // no source roots were found at all
issues.add(sourceRootIssues); issues.report(sourceRootIssues);
} }
return ProjectDescriptor return ProjectDescriptor
@ -89,7 +90,6 @@ public final class WireProjectsPhase implements DependencyPhase {
.rootPath(projectInfo.rootDirectory) .rootPath(projectInfo.rootDirectory)
.name(projectInfo.manifest.name()) .name(projectInfo.manifest.name())
.version(projectInfo.manifest.version()) .version(projectInfo.manifest.version())
.frontendSpec(projectInfo.getFrontendSpec())
.sourceRoots(ReadOnlyList.wrap(sourceRoots)) .sourceRoots(ReadOnlyList.wrap(sourceRoots))
.build(); .build();
} }

View File

@ -8,7 +8,7 @@ import java.util.Optional;
public class FrontendRegistryService { public class FrontendRegistryService {
private static final FrontendSpec[] FRONTEND_SPECS = { private static final FrontendSpec[] FRONTEND_SPECS = {
PBSDefinitions.PBS, PBSDefinitions.PBS,
}; };
private static final Map<String, FrontendSpec> FRONTENDS = new HashMap<>(); private static final Map<String, FrontendSpec> FRONTENDS = new HashMap<>();
@ -17,7 +17,8 @@ public class FrontendRegistryService {
for (final var frontendSpec : FRONTEND_SPECS) { for (final var frontendSpec : FRONTEND_SPECS) {
FRONTENDS.put(frontendSpec.getLanguageId(), frontendSpec); FRONTENDS.put(frontendSpec.getLanguageId(), frontendSpec);
} }
if (FRONTENDS.size() != FRONTEND_SPECS.length) throw new IllegalStateException("Duplicate frontend specs found"); if (FRONTENDS.size() != FRONTEND_SPECS.length)
throw new IllegalStateException("Duplicate frontend specs found");
} }
public static FrontendSpec getDefaultFrontendSpec() { public static FrontendSpec getDefaultFrontendSpec() {
@ -27,4 +28,4 @@ public class FrontendRegistryService {
public static Optional<FrontendSpec> getFrontendSpec(final String languageId) { public static Optional<FrontendSpec> getFrontendSpec(final String languageId) {
return Optional.ofNullable(FRONTENDS.get(languageId)); return Optional.ofNullable(FRONTENDS.get(languageId));
} }
} }