diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java index 9502cdf5..77e3b83c 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/messages/BuildingIssueSink.java @@ -25,7 +25,7 @@ public class BuildingIssueSink extends ReadOnlyCollection { return this; } - public BuildingIssueSink report(final BuildingIssueSink issues) { + public BuildingIssueSink merge(final BuildingIssueSink issues) { hasErrors |= issues.hasErrors(); collection.addAll(issues.collection); return this; diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTable.java index 2220ea5d..17dcb65a 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTable.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTable.java @@ -3,11 +3,13 @@ package p.studio.compiler.source.tables; import p.studio.compiler.source.identifiers.SourceIdentifier; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.function.Function; -public abstract class DenseTable { +public abstract class DenseTable + implements DenseTableReader, DenseTableWriter { private final List values = new ArrayList<>(); private final Function identifierGenerator; @@ -15,19 +17,65 @@ public abstract class DenseTable { this.identifierGenerator = Objects.requireNonNull(identifierGenerator); } + @Override public final int size() { return values.size(); } - public final VALUE get(IDENTIFIER id) { - return values.get(id.getIndex()); + @Override + public final VALUE get(final IDENTIFIER identifier) { + return values.get(identifier.getIndex()); } - public IDENTIFIER register(VALUE value) { + @Override + public IDENTIFIER register(final VALUE value) { final int idx = values.size(); values.add(value); return identifierGenerator.apply(idx); } - protected void clear() { + @Override + public void clear() { values.clear(); } + + @Override + public Iterable identifiers() { + return () -> new Iterator<>() { + + private int current = 0; + + @Override + public boolean hasNext() { + return current < values.size(); + } + + @Override + public IDENTIFIER next() { + return identifierGenerator.apply(current++); + } + }; + } + + @Override + public Iterable values() { + return () -> new Iterator<>() { + + private int current = 0; + + @Override + public boolean hasNext() { + return current < values.size(); + } + + @Override + public VALUE next() { + final var identifier = identifierGenerator.apply(current++); + return get(identifier); + } + }; + } + + @Override + public Iterator iterator() { + return identifiers().iterator(); + } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableReader.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableReader.java new file mode 100644 index 00000000..fa931f92 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableReader.java @@ -0,0 +1,10 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +public interface DenseTableReader extends Iterable { + int size(); + VALUE get(IDENTIFIER identifier); + Iterable identifiers(); + Iterable values(); +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableWriter.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableWriter.java new file mode 100644 index 00000000..84fe2c42 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTableWriter.java @@ -0,0 +1,8 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +public interface DenseTableWriter { + IDENTIFIER register(VALUE value); + void clear(); +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java index 8b3b573d..dcf5422e 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java @@ -8,7 +8,7 @@ import p.studio.utilities.structures.ReadOnlyList; import java.util.*; -public class FileTable extends InternTable { +public class FileTable extends InternTable implements FileTableReader { private final Map> projectFiles = new HashMap<>(); @@ -23,6 +23,7 @@ public class FileTable extends InternTable { return fileId; } + @Override public ReadOnlyList getFiles(final ProjectId projectId) { final var fileIds = projectFiles.get(projectId); if (CollectionUtils.isEmpty(fileIds)) { diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTableReader.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTableReader.java new file mode 100644 index 00000000..cc8f34c2 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTableReader.java @@ -0,0 +1,10 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.models.SourceHandle; +import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.identifiers.ProjectId; +import p.studio.utilities.structures.ReadOnlyList; + +public interface FileTableReader extends DenseTableReader { + ReadOnlyList getFiles(ProjectId projectId); +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTable.java index 7826e612..905169d4 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTable.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTable.java @@ -8,11 +8,12 @@ import java.util.Optional; import java.util.function.Function; public abstract class InternTable - extends DenseTable { + extends DenseTable + implements InternTableReader { private final Map identifierByValue = new HashMap<>(); - public InternTable(Function identifierGenerator) { + public InternTable(final Function identifierGenerator) { super(identifierGenerator); } @@ -21,17 +22,19 @@ public abstract class InternTable return identifierByValue.computeIfAbsent(value, super::register); } - public Optional optional(final VALUE value) { - return Optional.ofNullable(identifierByValue.get(value)); - } - @Override public void clear() { super.clear(); identifierByValue.clear(); } - public boolean containsKey(VALUE value) { + @Override + public Optional optional(final VALUE value) { + return Optional.ofNullable(identifierByValue.get(value)); + } + + @Override + public boolean containsKey(final VALUE value) { return identifierByValue.containsKey(value); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTableReader.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTableReader.java new file mode 100644 index 00000000..2e70b9b8 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTableReader.java @@ -0,0 +1,10 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +import java.util.Optional; + +public interface InternTableReader extends DenseTableReader { + Optional optional(VALUE value); + boolean containsKey(VALUE value); +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTable.java index 058d1e85..940da2c4 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTable.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTable.java @@ -8,7 +8,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -public class ProjectTable extends DenseTable { +public class ProjectTable extends DenseTable implements ProjectTableReader { private final Map projectIdByPath = new HashMap<>(); @@ -27,11 +27,13 @@ public class ProjectTable extends DenseTable { projectIdByPath.clear(); } - public Optional optional(Path pathCanon) { + @Override + public Optional optional(final Path pathCanon) { return optionalId(pathCanon).map(this::get); } - public Optional optionalId(Path pathCanon) { + @Override + public Optional optionalId(final Path pathCanon) { return Optional.ofNullable(projectIdByPath.get(pathCanon)); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTableReader.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTableReader.java new file mode 100644 index 00000000..a8f208b0 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTableReader.java @@ -0,0 +1,12 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.models.ProjectDescriptor; +import p.studio.compiler.source.identifiers.ProjectId; + +import java.nio.file.Path; +import java.util.Optional; + +public interface ProjectTableReader extends DenseTableReader { + Optional optional(Path pathCanon); + Optional optionalId(Path pathCanon); +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java index 89e268af..60439fb0 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java @@ -1,21 +1,57 @@ package p.studio.compiler.utilities; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; +import p.studio.compiler.FrontendRegistryService; import p.studio.compiler.dtos.PrometeuManifestDTO; +import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.models.PrometeuManifest; +import p.studio.compiler.workspaces.phases.DependencyResolver; +import p.studio.utilities.structures.ReadOnlyCollection; +import p.studio.utilities.structures.ReadOnlyList; import java.io.IOException; import java.nio.file.Path; +import java.util.Optional; +@UtilityClass public final class PrometeuManifestUtils { - public static final PrometeuManifestUtils INSTANCE = new PrometeuManifestUtils(); - private final ObjectMapper mapper = new ObjectMapper(); + private final static ObjectMapper MAPPER = new ObjectMapper(); - public PrometeuManifestDTO read(Path manifestPath) { + static PrometeuManifestDTO read(Path manifestPath) { try { - return mapper.readValue(manifestPath.toFile(), PrometeuManifestDTO.class); + return MAPPER.readValue(manifestPath.toFile(), PrometeuManifestDTO.class); } catch (IOException e) { throw new IllegalStateException("failed to read manifest " + manifestPath, e); } } + + public static Optional buildManifest( + final Path mainProjectRootPathCanon, + final Path manifestPathCanon, + final BuildingIssueSink issues) { + final var issuesLocal = BuildingIssueSink.empty(); + final PrometeuManifestDTO dto = read(manifestPathCanon); + if (StringUtils.isBlank(dto.name())) { + issuesLocal.report(builder -> builder + .error(true) + .message("[DEPS]: manifest missing 'name': " + manifestPathCanon)); + } + if (StringUtils.isBlank(dto.version())) { + issuesLocal.report(builder -> builder + .error(true) + .message("[DEPS]: manifest missing 'version': " + manifestPathCanon)); + } + final var language = StringUtils.isBlank(dto.language()) + ? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId() + : dto.language(); + final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal); + if (ReadOnlyCollection.isNotEmpty(issuesLocal)) { + issues.merge(issuesLocal); + return Optional.empty(); + } + return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, ReadOnlyList.wrap(dependencies))); + } } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DependencyResolver.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DependencyResolver.java new file mode 100644 index 00000000..d54df8a8 --- /dev/null +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DependencyResolver.java @@ -0,0 +1,86 @@ +package p.studio.compiler.workspaces.phases; + +import lombok.experimental.UtilityClass; +import org.apache.commons.collections4.CollectionUtils; +import p.studio.compiler.dtos.PrometeuManifestDTO; +import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.workspaces.DependencyReference; +import p.studio.registry.models.ProjectRef; +import p.studio.registry.models.Registry; +import p.studio.registry.utilities.RegistryStore; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +@UtilityClass +public final class DependencyResolver { + public static List resolveDependencies( + final Path mainProjectRootPathCanon, + final List dependencies, + final BuildingIssueSink issues) { + if (CollectionUtils.isEmpty(dependencies)) { + return List.of(); + } + + final var registryMaybe = RegistryStore.INSTANCE.load(mainProjectRootPathCanon); + if (registryMaybe.isEmpty()) { + issues.report(builder -> builder + .error(true) + .message("[DEPS]: failed to load registry from " + mainProjectRootPathCanon)); + return List.of(); + } + final var registry = registryMaybe.get(); + + final var deps = new ArrayList(dependencies.size()); + for (var dependency : dependencies) { + + // Resolves each dependency based on its declaration type + switch (dependency) { + case PrometeuManifestDTO.DependencyDeclaration.Local loc -> resolveLocal(loc, mainProjectRootPathCanon, issues, deps); + case PrometeuManifestDTO.DependencyDeclaration.Registry reg -> resolveRegistry(reg, registry, issues, deps); + default -> issues.report(builder -> builder + .error(true) + .message("[DEPS]: unknown dependency declaration type: " + dependency.getClass().getSimpleName())); + } + } + + return deps; + } + + // Resolves local dependency path; reports canonicalization errors + private static void resolveLocal( + final PrometeuManifestDTO.DependencyDeclaration.Local loc, + final Path rootProjectCanonPath, + final BuildingIssueSink issues, + final List deps) { + final var pathResolve = rootProjectCanonPath.resolve(loc.path()); + try { + final Path pathCanon = pathResolve.toRealPath(); + deps.add(new DependencyReference(pathCanon)); + } catch (IOException e) { + issues.report(builder -> builder + .error(true) + .message("[DEPS]: failed to canonicalize dependency path: " + pathResolve) + .exception(e)); + } + } + + // Looks up the registry for a dependency path; reports missing path errors + private static void resolveRegistry( + final PrometeuManifestDTO.DependencyDeclaration.Registry reg, + final Registry registry, + final BuildingIssueSink issues, + final List deps) { + final var ref = new ProjectRef(reg.name(), reg.version()); + final var canonPath = registry.optional(ref); + if (canonPath.isEmpty()) { + issues.report(builder -> builder + .error(true) + .message("[DEPS]: registry dependency not found: " + ref)); + return; + } + deps.add(new DependencyReference(canonPath.get())); + } +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java index 6d642884..c73e868a 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java @@ -1,180 +1,106 @@ package p.studio.compiler.workspaces.phases; -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.BuildingIssueSink; import p.studio.compiler.models.DependencyContext; import p.studio.compiler.models.ProjectInfo; import p.studio.compiler.models.ProjectInfoId; -import p.studio.compiler.models.PrometeuManifest; import p.studio.compiler.utilities.PrometeuManifestUtils; import p.studio.compiler.workspaces.DependencyPhase; -import p.studio.compiler.workspaces.DependencyReference; -import p.studio.registry.models.ProjectRef; -import p.studio.registry.utilities.RegistryStore; -import p.studio.utilities.structures.ReadOnlyCollection; -import p.studio.utilities.structures.ReadOnlyList; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; public class DiscoverPhase implements DependencyPhase { @Override public BuildingIssueSink run(final DependencyContext ctx) { - final Map projectIndexByDirectory = new HashMap<>(); - final BuildingIssueSink issues = BuildingIssueSink.empty(); + final var projectsSeen = new HashSet(); + final var issues = BuildingIssueSink.empty(); // Discovers projects; registers them; adds dependencies to queue while (!ctx.pending.isEmpty()) { final var rootPathCanon = ctx.pending.pollFirst(); - - if (projectIndexByDirectory.containsKey(rootPathCanon)) { + if (projectsSeen.contains(rootPathCanon)) { continue; } - - final Path manifestPathCanon; - try { - manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath(); - } catch (IOException e) { - issues.report(builder -> builder - .error(true) - .message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon) - .exception(e)); - continue; - } - if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) { - issues.report(builder -> builder - .error(true) - .message("[DEPS]: manifest not found: expected a file " + manifestPathCanon)); - continue; - } - - final var prometeuManifestDTO = PrometeuManifestUtils.INSTANCE.read(manifestPathCanon); - final var manifestMaybe = map(ctx.mainProjectRootPathCanon, prometeuManifestDTO, issues); - - if (manifestMaybe.isEmpty()) { - continue; - } - - final var manifest = manifestMaybe.get(); - - final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language()); - if (frontendSpec.isEmpty()) { - issues.report(builder -> builder - .error(true) - .message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name())); - 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 - .builder() - .rootDirectory(rootPathCanon) - .manifestPath(manifestPathCanon) - .manifest(manifest) - .build(); - final var projectInfoId = ctx.projectInfoTable.register(projectInfo); - projectIndexByDirectory.put(rootPathCanon, projectInfoId); - - manifest.dependencies().forEach(depRef -> ctx.pending.add(depRef.canonPath())); - - ctx.projectNameAndVersions.computeIfAbsent(manifest.name(), ignore -> new HashSet<>()).add(manifest.version()); + discoverOne(ctx, rootPathCanon, issues).ifPresent(projectInfoId -> { + projectsSeen.add(rootPathCanon); + final var projectInfo = ctx.projectInfoTable.get(projectInfoId); + projectInfo.manifest.dependencies().forEach(dep -> ctx.pending.add(dep.canonPath())); + ctx.projectNameAndVersions + .computeIfAbsent(projectInfo.manifest.name(), ignored -> new HashSet<>()) + .add(projectInfo.manifest.version()); + }); } - return issues; } - public static Optional map( - final Path rootPath, - final PrometeuManifestDTO dto, + private Optional discoverOne( + final DependencyContext ctx, + final Path rootPathCanon, final BuildingIssueSink issues) { - - if (StringUtils.isBlank(dto.name())) { - issues.report(builder -> builder - .error(true) - .message("[DEPS]: manifest missing 'name': " + rootPath)); - } - - if (StringUtils.isBlank(dto.version())) { - issues.report(builder -> builder - .error(true) - .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(), issues); - - if (ReadOnlyCollection.isNotEmpty(issues)) { + final var manifestPathCanon = canonicalizeManifestPath(rootPathCanon, issues); + if (manifestPathCanon.isEmpty()) { return Optional.empty(); } - - return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, ReadOnlyList.wrap(dependencies))); - } - - private static List resolveDependencies( - final Path rootProjectCanonPath, - final List dependencies, - final BuildingIssueSink issues) { - if (CollectionUtils.isEmpty(dependencies)) { - return List.of(); + final var manifest = PrometeuManifestUtils.buildManifest(ctx.mainProjectRootPathCanon, manifestPathCanon.get(), issues); + if (manifest.isEmpty()) { + return Optional.empty(); } - - final var registryMaybe = RegistryStore.INSTANCE.load(rootProjectCanonPath); - if (registryMaybe.isEmpty()) { + final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.get().language()); + // Returns empty when language is unknown + if (frontendSpec.isEmpty()) { issues.report(builder -> builder .error(true) - .message("[DEPS]: failed to load registry from " + rootProjectCanonPath)); - return List.of(); + .message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name())); + return Optional.empty(); } - final var registry = registryMaybe.get(); - - final var deps = new ArrayList(dependencies.size()); - for (var dependency : dependencies) { - - // Resolves each dependency based on its declaration type - switch (dependency) { - - case PrometeuManifestDTO.DependencyDeclaration.Local loc -> { - // Resolves local dependency path; reports canonicalization errors - final var pathResolve = rootProjectCanonPath.resolve(loc.path()); - try { - final Path pathCanon = pathResolve.toRealPath(); - deps.add(new DependencyReference(pathCanon)); - } catch (IOException e) { - issues.report(builder -> builder - .error(true) - .message("[DEPS]: failed to canonicalize dependency path: " + pathResolve) - .exception(e)); - } - } - - case PrometeuManifestDTO.DependencyDeclaration.Registry reg -> { - // Looks up the registry for a dependency path; reports missing path errors - final var ref = new ProjectRef(reg.name(), reg.version()); - registry.optional(ref).ifPresent(pathCanon -> deps.add(new DependencyReference(pathCanon))); - } - - default -> { - } - } + // Enforces consistent language across projects + if (Objects.isNull(ctx.frontendSpec)) { + ctx.frontendSpec = frontendSpec.get(); + } else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) { + // Reports language inconsistency across projects + issues.report(builder -> builder + .error(true) + .message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"", + manifest.get().name(), frontendSpec.get().getLanguageId(), ctx.frontendSpec.getLanguageId()))); + return Optional.empty(); } + final var projectInfo = ProjectInfo + .builder() + .rootDirectory(rootPathCanon) + .manifestPath(manifestPathCanon.get()) + .manifest(manifest.get()) + .build(); + final var projectInfoId = ctx.projectInfoTable.register(projectInfo); + return Optional.of(projectInfoId); + } - return deps; + private static Optional canonicalizeManifestPath( + final Path rootPathCanon, + final BuildingIssueSink issues) { + final var manifestPath = rootPathCanon.resolve("prometeu.json"); + final Path manifestPathCanon; + // Resolves manifest path; reports errors on failure + try { + manifestPathCanon = manifestPath.toRealPath(); + } catch (IOException e) { + issues.report(builder -> builder + .error(true) + .message("[DEPS]: manifest canonPath does not exist: " + manifestPath) + .exception(e)); + return Optional.empty(); + } + if (!Files.isRegularFile(manifestPathCanon)) { + issues.report(builder -> builder + .error(true) + .message("[DEPS]: manifest not found: expected a file " + manifestPathCanon)); + return Optional.empty(); + } + return Optional.of(manifestPathCanon); } } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/WireProjectsPhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/WireProjectsPhase.java index 189bfdeb..c0db32d3 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/WireProjectsPhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/WireProjectsPhase.java @@ -25,8 +25,7 @@ public final class WireProjectsPhase implements DependencyPhase { final BuildingIssueSink issues = BuildingIssueSink.empty(); - for (int index = 0; index < ctx.projectInfoTable.size(); index++) { - final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index)); + for (final var projectInfo : ctx.projectInfoTable.values()) { final var projectDescriptor = buildProjectDescriptor(ctx, projectInfo, issues); final var projectId = ctx.projectTable.register(projectDescriptor); ctx.projectIds.add(projectId); @@ -82,7 +81,7 @@ public final class WireProjectsPhase implements DependencyPhase { } if (sourceRootIssues.size() == ctx.frontendSpec.getSourceRoots().size()) { // no source roots were found at all - issues.report(sourceRootIssues); + issues.merge(sourceRootIssues); } return ProjectDescriptor diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/registry/models/ProjectRef.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/registry/models/ProjectRef.java index 04ffbc1e..26898f31 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/registry/models/ProjectRef.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/registry/models/ProjectRef.java @@ -10,4 +10,9 @@ public record ProjectRef(String name, String version) { final var version = ps[1]; return Optional.of(new ProjectRef(name, version)); } + + @Override + public String toString() { + return name + "@" + version; + } }