diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/DepsPipelineStage.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/DepsPipelineStage.java index d3cc222a..9a8fec85 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/DepsPipelineStage.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/DepsPipelineStage.java @@ -22,7 +22,7 @@ public class DepsPipelineStage implements PipelineStage { return ReadOnlyCollection.wrap(List.of(BuildingIssue .builder() .error(true) - .message("[DEPS]: root directory no found: " + ctx.getConfig().getRootProjectPath()) + .message("[DEPS]: rootProjectId directory no found: " + ctx.getConfig().getRootProjectPath()) .build())); } final var cfg = new DependencyConfig(false, rootCanonPath); diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/dtos/PrometeuManifestDTO.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/dtos/PrometeuManifestDTO.java new file mode 100644 index 00000000..4c68d14a --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/dtos/PrometeuManifestDTO.java @@ -0,0 +1,44 @@ +package p.studio.compiler.dtos; + +import com.fasterxml.jackson.annotation.*; +import lombok.Getter; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record PrometeuManifestDTO( + String name, + String version, + String language, + List dependencies) { + + @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) + @JsonSubTypes({ + @JsonSubTypes.Type(value = DependencyDeclaration.Local.class), + @JsonSubTypes.Type(value = DependencyDeclaration.Git.class) + }) + public interface DependencyDeclaration { + @Getter + class Local implements DependencyDeclaration { + private final String path; + + @JsonCreator + public Local(@JsonProperty("path") final String path) { + this.path = path; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + class Git implements DependencyDeclaration { + private final String url; + private final String rev; + + @JsonCreator + public Git(@JsonProperty("url") final String url, @JsonProperty("rev") final String rev) { + this.url = url; + this.rev = rev; + } + } + } +} \ No newline at end of file diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java index 23c286f3..eae6933b 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java @@ -10,4 +10,5 @@ public class FrontendSpec { private final String languageId; private final ReadOnlySet allowedExtensions; private final ReadOnlySet sourceRoots; + private final boolean caseSensitive; } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/FileId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/FileId.java index 1472d0f9..a9a82a92 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/FileId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/FileId.java @@ -1,9 +1,9 @@ package p.studio.compiler.source.identifiers; -public class FileId extends AbstractSourceIdentifier { - public static final FileId NONE = new FileId(-1L); +public class FileId extends SourceIdentifier { + public static final FileId NONE = new FileId(-1); - public FileId(long id) { + public FileId(int id) { super(id); } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java index 90ec7812..4f4f8964 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java @@ -1,7 +1,7 @@ package p.studio.compiler.source.identifiers; -public class ModuleId extends AbstractSourceIdentifier { - public ModuleId(long id) { +public class ModuleId extends SourceIdentifier { + public ModuleId(int id) { super(id); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NameId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NameId.java index 94e045d3..288e5cf8 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NameId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NameId.java @@ -1,7 +1,7 @@ package p.studio.compiler.source.identifiers; -public class NameId extends AbstractSourceIdentifier { - public NameId(long id) { +public class NameId extends SourceIdentifier { + public NameId(int id) { super(id); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NodeId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NodeId.java index 2789b807..0008cb7e 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NodeId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/NodeId.java @@ -1,7 +1,7 @@ package p.studio.compiler.source.identifiers; -public class NodeId extends AbstractSourceIdentifier { - public NodeId(long id) { +public class NodeId extends SourceIdentifier { + public NodeId(int id) { super(id); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ProjectId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ProjectId.java index b1759645..8b3cfa05 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ProjectId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ProjectId.java @@ -1,11 +1,7 @@ package p.studio.compiler.source.identifiers; -public class ProjectId extends AbstractSourceIdentifier { +public class ProjectId extends SourceIdentifier { public ProjectId(int id) { - super(Integer.toUnsignedLong(id)); - } - - public ProjectId(long id) { super(id); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/AbstractSourceIdentifier.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SourceIdentifier.java similarity index 55% rename from prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/AbstractSourceIdentifier.java rename to prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SourceIdentifier.java index d988ed11..5350c4ab 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/AbstractSourceIdentifier.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SourceIdentifier.java @@ -3,18 +3,24 @@ package p.studio.compiler.source.identifiers; import lombok.EqualsAndHashCode; import lombok.Getter; +import java.util.Optional; +import java.util.function.Supplier; + @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) -abstract class AbstractSourceIdentifier { +public abstract class SourceIdentifier { @EqualsAndHashCode.Include - private final long id; + private final int id; - AbstractSourceIdentifier(long id) { + public SourceIdentifier(int id) { this.id = id; } public int getIndex() { - return (int) id; + if (id < 0) { + throw new IllegalArgumentException("index should be valid (positive)"); + } + return id; } @Override diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SymbolId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SymbolId.java index ea1d2345..a69eec15 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SymbolId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/SymbolId.java @@ -1,6 +1,6 @@ package p.studio.compiler.source.identifiers; -public class SymbolId extends AbstractSourceIdentifier { +public class SymbolId extends SourceIdentifier { public SymbolId(int id) { super(id); } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeId.java index 33b4433e..0aa9c3c6 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/TypeId.java @@ -1,7 +1,7 @@ package p.studio.compiler.source.identifiers; -public class TypeId extends AbstractSourceIdentifier { - public TypeId(long id) { +public class TypeId extends SourceIdentifier { + public TypeId(int id) { super(id); } } 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 new file mode 100644 index 00000000..2220ea5d --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/DenseTable.java @@ -0,0 +1,33 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +public abstract class DenseTable { + private final List values = new ArrayList<>(); + private final Function identifierGenerator; + + protected DenseTable(final Function identifierGenerator) { + this.identifierGenerator = Objects.requireNonNull(identifierGenerator); + } + + public final int size() { return values.size(); } + + public final VALUE get(IDENTIFIER id) { + return values.get(id.getIndex()); + } + + public IDENTIFIER register(VALUE value) { + final int idx = values.size(); + values.add(value); + return identifierGenerator.apply(idx); + } + + protected void clear() { + values.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 new file mode 100644 index 00000000..e59c26d9 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/FileTable.java @@ -0,0 +1,11 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.FileId; + +import java.nio.file.Path; + +public class FileTable extends InternTable { + public FileTable() { + super(FileId::new); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..a536df0b --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/InternTable.java @@ -0,0 +1,37 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public abstract class InternTable + extends DenseTable { + + private final Map identifierByValue = new HashMap<>(); + + public InternTable(Function identifierGenerator) { + super(identifierGenerator); + } + + @Override + public IDENTIFIER register(final VALUE value) { + return identifierByValue.computeIfAbsent(value, super::register); + } + + public IDENTIFIER get(final VALUE value) { + return identifierByValue.get(value); + } + + @Override + public void clear() { + super.clear(); + identifierByValue.clear(); + } + + public boolean containsKey(VALUE value) { + return identifierByValue.containsKey(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 new file mode 100644 index 00000000..a9643d85 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/ProjectTable.java @@ -0,0 +1,11 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.ProjectId; + +import java.nio.file.Path; + +public class ProjectTable extends InternTable { + public ProjectTable() { + super(ProjectId::new); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/utilities/PrometeuManifestMapper.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/utilities/PrometeuManifestMapper.java new file mode 100644 index 00000000..1f39fda0 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/utilities/PrometeuManifestMapper.java @@ -0,0 +1,19 @@ +package p.studio.compiler.utilities; + +import com.fasterxml.jackson.databind.ObjectMapper; +import p.studio.compiler.dtos.PrometeuManifestDTO; + +import java.io.IOException; +import java.nio.file.Path; + +public final class PrometeuManifestMapper { + private static final ObjectMapper mapper = new ObjectMapper(); + + public static PrometeuManifestDTO read(Path manifestPath) { + try { + return mapper.readValue(manifestPath.toFile(), PrometeuManifestDTO.class); + } catch (IOException e) { + throw new IllegalStateException("failed to read manifest " + manifestPath, e); + } + } +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/DependencyContext.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/DependencyContext.java index 9f66a582..c4fe20be 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/DependencyContext.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/DependencyContext.java @@ -1,8 +1,9 @@ package p.studio.compiler.models; +import p.studio.compiler.exceptions.BuildException; import p.studio.compiler.messages.DependencyConfig; import p.studio.compiler.source.identifiers.ProjectId; -import p.studio.compiler.exceptions.BuildException; +import p.studio.compiler.source.tables.ProjectTable; import p.studio.utilities.structures.ReadOnlyList; import java.nio.file.Path; @@ -15,16 +16,16 @@ public final class DependencyContext { public Path mainProjectRootPathCanon; // Phase 1 (Discover) - public final List projectInfos = new ArrayList<>(); - public final Map projectIndexByDirectory = new HashMap<>(); public final Deque pending = new ArrayDeque<>(); + public final ProjectInfoTable projectInfoTable = new ProjectInfoTable(); - // Phase 2+ - public final List projectNodes = new ArrayList<>(); - public final Map projectIdByDirectoryRoot = new HashMap<>(); + public final ProjectTable projectTable = new ProjectTable(); + + public final Map> projectNameAndVersions = new HashMap<>(); + public final List projectDescriptors = new ArrayList<>(); public final List> dependenciesByProject = new ArrayList<>(); - public ProjectId root; + public ProjectId rootProjectId; public BuildStack stack; private DependencyContext(DependencyConfig config) { @@ -40,32 +41,16 @@ public final class DependencyContext { } public ResolvedWorkspace toResolvedWorkspace() { - if (root == null) { - throw new BuildException("dependencies: internal error: root ProjectId not set"); + if (rootProjectId == null) { + throw new BuildException("dependenciesByProjectId: internal error: rootProjectId ProjectId not set"); } - final var projectDescriptors = ReadOnlyList.wrap(projectNodes - .stream() - .map(n -> { - final var languageId = n.getLanguageId(); - final var sourcePolicy = new SourcePolicy(ReadOnlyList.empty(), true); // TODO: source policy should come from a frontend registry anginst language id - return ProjectDescriptor - .builder() - .projectId(n.getProjectId()) - .name(n.getName()) - .version(n.getVersion()) - .projectDir(n.getProjectRootPath()) - .sourceRoots(n.getSourceRoots()) - .languageId(languageId) - .sourcePolicy(sourcePolicy) - .build(); - }) - .toList()); - final var edges = ReadOnlyList.wrap(this + final var projectDescriptors = ReadOnlyList.wrap(this.projectDescriptors); + final var dependenciesByProject = ReadOnlyList.wrap(this .dependenciesByProject .stream() .map(ReadOnlyList::wrap) .toList()); - final var graph = new ResolvedGraph(root, projectDescriptors, edges); - return new ResolvedWorkspace(root, graph, stack); + final var workspaceGraph = new WorkspaceGraph(projectDescriptors, dependenciesByProject); + return new ResolvedWorkspace(rootProjectId, workspaceGraph, stack); } } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedFile.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedFile.java deleted file mode 100644 index 00426804..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedFile.java +++ /dev/null @@ -1,4 +0,0 @@ -package p.studio.compiler.models; - -public record LoadedFile(String uri, String text) { -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedSources.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedSources.java deleted file mode 100644 index dc125b01..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/LoadedSources.java +++ /dev/null @@ -1,17 +0,0 @@ -package p.studio.compiler.models; - -import java.util.List; - -/** - * Sources already loaded by dependencies (IO happens in dependencies, not in pipeline). - */ -public record LoadedSources( - /** - * For each project in the stack, a list of files (uri + text). - */ - List perProject -) { - public LoadedSources { - perProject = List.copyOf(perProject); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectDescriptor.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectDescriptor.java index 7071b7f9..95cd1f32 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectDescriptor.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectDescriptor.java @@ -3,18 +3,19 @@ package p.studio.compiler.models; import lombok.Builder; import lombok.Getter; import p.studio.compiler.source.identifiers.ProjectId; +import p.studio.compiler.workspaces.DependencyReference; import p.studio.utilities.structures.ReadOnlyList; import java.nio.file.Path; @Builder @Getter -public class ProjectDescriptor { +public final class ProjectDescriptor { private final ProjectId projectId; + private final Path projectRootPath; private final String name; private final String version; - private final Path projectDir; private final ReadOnlyList sourceRoots; - private final String languageId; - private final SourcePolicy sourcePolicy; + private final ReadOnlyList dependencies; + private final FrontendSpec frontendSpec; } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoId.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoId.java new file mode 100644 index 00000000..afd972a9 --- /dev/null +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoId.java @@ -0,0 +1,9 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.identifiers.SourceIdentifier; + +public class ProjectInfoId extends SourceIdentifier { + public ProjectInfoId(int id) { + super(id); + } +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoTable.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoTable.java new file mode 100644 index 00000000..33eecc62 --- /dev/null +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectInfoTable.java @@ -0,0 +1,9 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.tables.DenseTable; + +public class ProjectInfoTable extends DenseTable { + protected ProjectInfoTable() { + super(ProjectInfoId::new); + } +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectNode.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectNode.java deleted file mode 100644 index abebccdc..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package p.studio.compiler.models; - -import lombok.Builder; -import lombok.Getter; -import p.studio.compiler.source.identifiers.ProjectId; -import p.studio.compiler.workspaces.DependencyReference; -import p.studio.utilities.structures.ReadOnlyList; - -import java.nio.file.Path; - -@Builder -@Getter -public final class ProjectNode { - private final FrontendSpec frontendSpec; - private final ProjectId projectId; - private final Path projectRootPath; - private final String name; - private final String version; - private final ReadOnlyList sourceRoots; - private final String languageId; - private final ReadOnlyList dependencies; -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectSources.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectSources.java deleted file mode 100644 index 545c1e6a..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ProjectSources.java +++ /dev/null @@ -1,9 +0,0 @@ -package p.studio.compiler.models; - -import p.studio.compiler.source.identifiers.ProjectId; -import p.studio.utilities.structures.ReadOnlyList; - -public record ProjectSources( - ProjectId projectId, - ReadOnlyList files) { -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuLock.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuLock.java deleted file mode 100644 index 59837279..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuLock.java +++ /dev/null @@ -1,47 +0,0 @@ -package p.studio.compiler.models; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import java.util.List; -import java.util.Optional; - -public record PrometeuLock( - long schema, - List mappings) { - - public PrometeuLock { - mappings = mappings != null ? List.copyOf(mappings) : List.of(); - } - - public static PrometeuLock blank() { - return new PrometeuLock(0, List.of()); - } - - public Optional lookupGitLocalDir( - final String url, - final String rev) { - return mappings - .stream() - .filter(m -> m instanceof LockMapping.Git g && g.url().equals(url) && g.rev().equals(rev)) - .map(m -> ((LockMapping.Git) m).localDir()) - .findFirst(); - } - - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "kind" - ) - @JsonSubTypes({ - @JsonSubTypes.Type(value = LockMapping.Git.class, name = "git") - }) - public interface LockMapping { - record Git( - String url, - String rev, - @JsonProperty("local-dir") String localDir - ) implements LockMapping {} - } -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuManifest.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuManifest.java index f1fac42d..34c32806 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuManifest.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/PrometeuManifest.java @@ -1,88 +1,11 @@ package p.studio.compiler.models; -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import p.studio.compiler.FrontendRegistryService; -import p.studio.compiler.exceptions.BuildException; +import p.studio.compiler.workspaces.DependencyReference; import p.studio.utilities.structures.ReadOnlyList; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - public record PrometeuManifest( String name, String version, String language, - ReadOnlyList dependencies) { - public static PrometeuManifest extract(final Path path, final ObjectMapper mapper) { - try { - final var root = mapper.readTree(path.toFile()); - final var name = text(root, "name"); - final var version = text(root, "version"); - - final var language = Optional - .ofNullable(root.get("language")) - .map(JsonNode::asText) - .orElseGet(() -> FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()); - - final List dependencies = new ArrayList<>(); - final var dependencyNodes = root.get("dependencies"); - if (dependencyNodes != null && dependencyNodes.isArray()) { - for (final var d : dependencyNodes) { - if (d.has("path")) { - dependencies.add(new PrometeuManifest.DependencyDeclaration.Local(d.get("path").asText())); - } else if (d.has("url")) { - final var url = d.get("url").asText(); - final var rev = d.has("rev") ? d.get("rev").asText(null) : null; - dependencies.add(new PrometeuManifest.DependencyDeclaration.Git(url, rev)); - } - } - } - - return new PrometeuManifest(name, version, language, ReadOnlyList.wrap(dependencies)); - } catch (IOException e) { - throw new BuildException("dependencies: failed to read or parse prometeu manifest " + path, e); - } - } - - private static String text(final JsonNode root, final String field) { - JsonNode n = root.get(field); - return n != null ? n.asText() : null; - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - @JsonSubTypes({ - @JsonSubTypes.Type(value = DependencyDeclaration.Local.class), - @JsonSubTypes.Type(value = DependencyDeclaration.Git.class) - }) - public interface DependencyDeclaration { - @Getter - class Local implements DependencyDeclaration { - private final String path; - - @JsonCreator - public Local(@JsonProperty("path") final String path) { - this.path = path; - } - } - - @JsonIgnoreProperties(ignoreUnknown = true) - @Getter - class Git implements DependencyDeclaration { - private final String git; - private final String rev; - - @JsonCreator - public Git(@JsonProperty("url") final String git, @JsonProperty("rev") final String rev) { - this.git = git; - this.rev = rev; - } - } - } - + ReadOnlyList dependencies) { } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedGraph.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedGraph.java deleted file mode 100644 index c056667a..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedGraph.java +++ /dev/null @@ -1,13 +0,0 @@ -package p.studio.compiler.models; - -import p.studio.compiler.source.identifiers.ProjectId; -import p.studio.utilities.structures.ReadOnlyList; - -public record ResolvedGraph( - ProjectId root, - ReadOnlyList projects, - ReadOnlyList> edges) { - public ProjectDescriptor project(ProjectId id) { - return projects.get(id.getIndex()); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedWorkspace.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedWorkspace.java index 7f50c2f9..b9a3603c 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedWorkspace.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/ResolvedWorkspace.java @@ -4,6 +4,6 @@ import p.studio.compiler.source.identifiers.ProjectId; public record ResolvedWorkspace( ProjectId projectId, - ResolvedGraph graph, + WorkspaceGraph graph, BuildStack stack) { } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/SourcePolicy.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/SourcePolicy.java deleted file mode 100644 index 664dc699..00000000 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/SourcePolicy.java +++ /dev/null @@ -1,6 +0,0 @@ -package p.studio.compiler.models; - -import p.studio.utilities.structures.ReadOnlyList; - -public record SourcePolicy(ReadOnlyList extensions, boolean caseSensitive) { -} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/WorkspaceGraph.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/WorkspaceGraph.java new file mode 100644 index 00000000..246dacbd --- /dev/null +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/models/WorkspaceGraph.java @@ -0,0 +1,17 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.identifiers.ProjectId; +import p.studio.utilities.structures.ReadOnlyList; + +public record WorkspaceGraph( + ReadOnlyList projects, + ReadOnlyList> dependenciesByProjectId) { + + public ProjectDescriptor getProjectDescriptor(final ProjectId projectId) { + return projects.get(projectId.getIndex()); + } + + public ReadOnlyList getDependencies(final ProjectId projectId) { + return dependenciesByProjectId.get(projectId.getIndex()); + } +} 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 692ea854..b84cf42e 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,31 +1,35 @@ package p.studio.compiler.workspaces.phases; -import com.fasterxml.jackson.databind.ObjectMapper; +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.models.PrometeuManifest; -import p.studio.compiler.workspaces.DependencyPhase; 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.PrometeuManifestMapper; +import p.studio.compiler.workspaces.DependencyPhase; +import p.studio.compiler.workspaces.DependencyReference; 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.ArrayList; -import java.util.List; +import java.util.*; public class DiscoverPhase implements DependencyPhase { - private final ObjectMapper mapper = new ObjectMapper(); - @Override public ReadOnlyCollection run(final DependencyContext ctx) { + final Map projectIndexByDirectory = new HashMap<>(); final List issues = new ArrayList<>(); while (!ctx.pending.isEmpty()) { final var rootPathCanon = ctx.pending.pollFirst(); - if (ctx.projectIndexByDirectory.containsKey(rootPathCanon)) { + if (projectIndexByDirectory.containsKey(rootPathCanon)) { continue; } @@ -52,7 +56,14 @@ public class DiscoverPhase implements DependencyPhase { continue; } - final var manifest = PrometeuManifest.extract(manifestPathCanon, mapper); + final var prometeuManifestDTO = PrometeuManifestMapper.read(manifestPathCanon); + final var manifestMaybe = map(rootPathCanon, prometeuManifestDTO, issues); + + if (manifestMaybe.isEmpty()) { + continue; + } + + final var manifest = manifestMaybe.get(); final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language()); if (frontendSpec.isEmpty()) { @@ -65,7 +76,6 @@ public class DiscoverPhase implements DependencyPhase { continue; } - final long projectIndex = ctx.projectInfos.size(); final var projectInfo = ProjectInfo .builder() .rootDirectory(rootPathCanon) @@ -73,33 +83,92 @@ public class DiscoverPhase implements DependencyPhase { .manifest(manifest) .frontendSpec(frontendSpec.get()) .build(); - ctx.projectInfos.add(projectInfo); - ctx.projectIndexByDirectory.put(rootPathCanon, projectIndex); + final var projectInfoId = ctx.projectInfoTable.register(projectInfo); + projectIndexByDirectory.put(rootPathCanon, projectInfoId); - for (final var dependencyDeclaration : manifest.dependencies()) { - if (dependencyDeclaration instanceof PrometeuManifest.DependencyDeclaration.Local local) { - final var dependencyPath = rootPathCanon.resolve(local.getPath()); - try { - final var dependencyPathCanon = dependencyPath.toRealPath(); - ctx.pending.add(dependencyPathCanon); - } catch (IOException e) { - final var issue = BuildingIssue - .builder() - .message("[DEPS]: dep canonPath does not exist: " + dependencyPath + " (" + manifest.name() + ")") - .exception(e) - .build(); - issues.add(issue); - } - } else if (dependencyDeclaration instanceof PrometeuManifest.DependencyDeclaration.Git) { - final var issue = BuildingIssue - .builder() - .message("[DEPS]: url dependencies not yet supported " + manifest.name() + " (" + rootPathCanon + ")") - .build(); - issues.add(issue); - } - } + manifest.dependencies().forEach(depRef -> ctx.pending.add(depRef.canonPath())); + + ctx.projectNameAndVersions.computeIfAbsent(manifest.name(), ignore -> new HashSet<>()).add(manifest.version()); } return ReadOnlyCollection.wrap(issues); } + + public static Optional map( + final Path rootPath, + final PrometeuManifestDTO dto, + final List buildingIssues) { + + if (StringUtils.isBlank(dto.name())) { + final var issue = BuildingIssue + .builder() + .error(true) + .message("[DEPS]: manifest missing 'name': " + rootPath) + .build(); + buildingIssues.add(issue); + } + + if (StringUtils.isBlank(dto.version())) { + final var issue = BuildingIssue + .builder() + .error(true) + .message("[DEPS]: manifest missing 'version': " + rootPath) + .build(); + buildingIssues.add(issue); + } + + final var language = StringUtils.isBlank(dto.language()) + ? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId() + : dto.language(); + + final var dependencies = resolveDependencies(rootPath, dto.dependencies(), buildingIssues); + + if (CollectionUtils.isNotEmpty(buildingIssues)) { + 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 List buildingIssues) { + if (CollectionUtils.isEmpty(dependencies)) { + return List.of(); + } + + final var deps = new ArrayList(dependencies.size()); + for (var dependency : dependencies) { + switch (dependency) { + case PrometeuManifestDTO.DependencyDeclaration.Local local -> { + try { + final Path dependencyPathCanon = rootProjectCanonPath.resolve(local.getPath()).toRealPath(); + deps.add(new DependencyReference(dependencyPathCanon)); + } catch (IOException e) { + final var issue = BuildingIssue + .builder() + .error(true) + .message("[DEPS]: failed to canonicalize dependency path: " + local.getPath() + " from (" + rootProjectCanonPath + ")") + .exception(e) + .build(); + buildingIssues.add(issue); + } + } + + case PrometeuManifestDTO.DependencyDeclaration.Git git -> { + final var issue = BuildingIssue + .builder() + .error(true) + .message("[DEPS]: git dependencies are not supported yet: " + git.getUrl() + " from (" + rootProjectCanonPath + ")") + .build(); + buildingIssues.add(issue); + } + + default -> { + } + } + } + return deps; + } } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/LocalizePhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/LocalizePhase.java index b6b82f03..9e926b46 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/LocalizePhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/LocalizePhase.java @@ -2,7 +2,7 @@ package p.studio.compiler.workspaces.phases; import p.studio.compiler.messages.BuildingIssue; import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.*; +import p.studio.compiler.workspaces.DependencyPhase; import p.studio.utilities.structures.ReadOnlyCollection; import java.util.ArrayList; @@ -11,13 +11,13 @@ import java.util.Objects; public final class LocalizePhase implements DependencyPhase { @Override - public ReadOnlyCollection run(final DependencyContext state) { + public ReadOnlyCollection run(final DependencyContext ctx) { final List issues = new ArrayList<>(); - for (int i = 0; i < state.projectNodes.size(); i++) { - final var fromProjectNode = state.projectNodes.get(i); + for (int i = 0; i < ctx.projectDescriptors.size(); i++) { + final var fromProjectNode = ctx.projectDescriptors.get(i); for (final var dependencyReference : fromProjectNode.getDependencies()) { final var dependencyReferenceCanonPath = dependencyReference.canonPath(); - final var projectId = state.projectIdByDirectoryRoot.get(dependencyReferenceCanonPath); + final var projectId = ctx.projectTable.get(dependencyReferenceCanonPath); if (Objects.isNull(projectId)) { final var issue = BuildingIssue .builder() @@ -27,7 +27,7 @@ public final class LocalizePhase implements DependencyPhase { issues.add(issue); continue; } - state.dependenciesByProject.get(fromProjectNode.getProjectId().getIndex()).add(projectId); + ctx.dependenciesByProject.get(fromProjectNode.getProjectId().getIndex()).add(projectId); } } return ReadOnlyCollection.wrap(issues); diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/MaterializePhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/MaterializePhase.java index 4282b056..c34d5b8c 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/MaterializePhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/MaterializePhase.java @@ -1,49 +1,53 @@ package p.studio.compiler.workspaces.phases; import p.studio.compiler.messages.BuildingIssue; -import p.studio.compiler.models.*; +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.*; +import p.studio.compiler.workspaces.DependencyPhase; import p.studio.utilities.structures.ReadOnlyCollection; import p.studio.utilities.structures.ReadOnlyList; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public final class MaterializePhase implements DependencyPhase { @Override public ReadOnlyCollection run(final DependencyContext ctx) { - // start all over again, we will re-populate the project nodes and edges based on the project infos - ctx.projectNodes.clear(); - ctx.projectIdByDirectoryRoot.clear(); + // to start all over again, we will re-populate the project nodes and dependenciesByProjectId based on the project infos + ctx.rootProjectId = null; + ctx.projectDescriptors.clear(); + ctx.projectTable.clear(); ctx.dependenciesByProject.clear(); - ctx.root = null; final List issues = new ArrayList<>(); - for (int index = 0; index < ctx.projectInfos.size(); index++) { - final var projectId = new ProjectId(index); - final var projectInfo = ctx.projectInfos.get(index); - ctx.projectNodes.add(buildProjectNode(projectId, projectInfo, issues)); - ctx.projectIdByDirectoryRoot.put(projectInfo.rootDirectory, projectId); + for (int index = 0; index < ctx.projectInfoTable.size(); index++) { + final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index)); + final var projectId = ctx.projectTable.register(projectInfo.rootDirectory); + ctx.projectDescriptors.add(buildProjectDescriptor(projectId, projectInfo, issues)); ctx.dependenciesByProject.add(new ArrayList<>()); } - final var rootProjectId = ctx.projectIdByDirectoryRoot.get(ctx.mainProjectRootPathCanon); + final var rootProjectId = ctx.projectTable.get(ctx.mainProjectRootPathCanon); if (rootProjectId == null) { final var issue = BuildingIssue .builder() - .message("[DEPS]: root project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized") + .message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized") .build(); issues.add(issue); return ReadOnlyCollection.wrap(issues); } - ctx.root = rootProjectId; + + ctx.rootProjectId = rootProjectId; return ReadOnlyCollection.wrap(issues); } - private static ProjectNode buildProjectNode( + private static ProjectDescriptor buildProjectDescriptor( final ProjectId projectId, final ProjectInfo projectInfo, final List issues) { @@ -57,7 +61,8 @@ public final class MaterializePhase implements DependencyPhase { } catch (IOException e) { final var issue = BuildingIssue .builder() - .message("[DEPS]: source root canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")") + .error(true) + .message("[DEPS]: source rootProjectId canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")") .exception(e) .build(); sourceRootIssues.add(issue); @@ -68,39 +73,15 @@ public final class MaterializePhase implements DependencyPhase { issues.addAll(sourceRootIssues); } - final List dependencyReferencies = new ArrayList<>(); - for (PrometeuManifest.DependencyDeclaration d : projectInfo.manifest.dependencies()) { - if (d instanceof PrometeuManifest.DependencyDeclaration.Local l) { - final var dependencyPath = projectInfo.rootDirectory.resolve(l.getPath()); - try { - final var dependencyPathCanon = dependencyPath.toRealPath(); - dependencyReferencies.add(new DependencyReference(dependencyPathCanon)); - } catch (IOException e) { - final var issue = BuildingIssue - .builder() - .message("[DEPS]: local dep canonPath does not exist: " + dependencyPath + " (from " + projectInfo.rootDirectory + ")") - .exception(e) - .build(); - issues.add(issue); - } - } else if (d instanceof PrometeuManifest.DependencyDeclaration.Git g) { - final var issue = BuildingIssue - .builder() - .message("[DEPS]: url dependency '" + g.getGit() + "' requires an explicit 'rev' and lock mapping (not supported yet)") - .build(); - issues.add(issue); - } - } - - return ProjectNode + return ProjectDescriptor .builder() .projectId(projectId) .projectRootPath(projectInfo.rootDirectory) .name(projectInfo.manifest.name()) .version(projectInfo.manifest.version()) - .languageId(projectInfo.manifest.language()) + .frontendSpec(projectInfo.getFrontendSpec()) .sourceRoots(ReadOnlyList.wrap(sourceRoots)) - .dependencies(ReadOnlyList.wrap(dependencyReferencies)) + .dependencies(projectInfo.manifest.dependencies()) .build(); } } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/SeedPhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/SeedPhase.java index 4c3c9449..b1377cf3 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/SeedPhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/SeedPhase.java @@ -18,7 +18,7 @@ public final class SeedPhase implements DependencyPhase { } catch (IOException e) { final var issue = BuildingIssue .builder() - .message("[DEPS]: failed to canonicalize root directory: " + ctx.config().cacheDir()) + .message("[DEPS]: failed to canonicalize rootProjectId directory: " + ctx.config().cacheDir()) .build(); issues.add(issue); } diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/StackPhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/StackPhase.java index 3e7556c2..75bf0885 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/StackPhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/StackPhase.java @@ -2,6 +2,7 @@ package p.studio.compiler.workspaces.phases; import p.studio.compiler.models.BuildStack; import p.studio.compiler.messages.BuildingIssue; +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; @@ -20,8 +21,8 @@ public final class StackPhase implements DependencyPhase { * Implements topological sort; detects dependency cycles; sets build stack */ @Override - public ReadOnlyCollection run(DependencyContext ctx) { - final int n = ctx.projectNodes.size(); + public ReadOnlyCollection run(final DependencyContext ctx) { + final int n = ctx.projectDescriptors.size(); final int[] indeg = new int[n]; for (int from = 0; from < n; from++) { for (final ProjectId to : ctx.dependenciesByProject.get(from)) { @@ -38,7 +39,7 @@ public final class StackPhase implements DependencyPhase { // Performs topological sort using Kahn's algorithm while (!q.isEmpty()) { final int u = q.removeFirst(); - travesalOrder.add(ctx.projectNodes.get(u).getProjectId()); + travesalOrder.add(ctx.projectDescriptors.get(u).getProjectId()); for (final ProjectId v : ctx.dependenciesByProject.get(u)) { if (--indeg[(int) v.getId()] == 0) { q.addLast((int) v.getId()); @@ -58,13 +59,13 @@ public final class StackPhase implements DependencyPhase { if (scc.size() > 1) { final var cycle = scc .stream() - .map(i -> ctx.projectNodes.get(i).getProjectId()) + .map(i -> ctx.projectDescriptors.get(i).getProjectId()) .toList(); cycles.add(cycle); } else { // size==1: cycle only if self-loop exists final var u = scc.getFirst(); - final var projectId = ctx.projectNodes.get(u).getProjectId(); + final var projectId = ctx.projectDescriptors.get(u).getProjectId(); boolean selfLoop = false; for (final var pu : ctx.dependenciesByProject.get(u)) { if (pu.getIndex() == u) { @@ -80,10 +81,7 @@ public final class StackPhase implements DependencyPhase { final var msg = "[DEPS]: cycle(s) detected:\n" + cycles.stream() .map(projectIds -> " * " + projectIds.stream() - .map(pId -> ctx.projectInfos - .get(pId.getIndex()) - .manifest - .name()) + .map(pId -> ctx.projectInfoTable.get(new ProjectInfoId(pId.getIndex())).manifest.name()) .collect(joining(" -> ")) ) .collect(joining("\n")); diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/ValidatePhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/ValidatePhase.java index c350c0fd..fae7b0b4 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/ValidatePhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/ValidatePhase.java @@ -9,24 +9,40 @@ import java.util.List; public final class ValidatePhase implements DependencyPhase { @Override - public ReadOnlyCollection run(DependencyContext state) { - if (state.root == null) { + public ReadOnlyCollection run(DependencyContext ctx) { + if (ctx.rootProjectId == null) { final var issue = BuildingIssue .builder() - .message("[DEPS]: root ProjectId not set") + .error(true) + .message("[DEPS]: rootProjectId ProjectId not set") .build(); return ReadOnlyCollection.wrap(List.of(issue)); } - // Ensure the edges list matches the number of nodes - if (state.dependenciesByProject.size() != state.projectNodes.size()) { + // Ensure the dependenciesByProjectId list matches the number of project descriptors + if (ctx.dependenciesByProject.size() != ctx.projectDescriptors.size()) { final var issue = BuildingIssue .builder() - .message("[DEPS]: internal error: edges list size mismatch") + .error(true) + .message("[DEPS]: internal error: dependenciesByProjectId list size mismatch") .build(); return ReadOnlyCollection.wrap(List.of(issue)); } + // we should check and ensure uniformity to version across the same projects + for (final var entry : ctx.projectNameAndVersions.entrySet()) { + 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.wrap(List.of(issue)); + } + } + return ReadOnlyCollection.empty(); } } diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DependencyServiceTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DependencyServiceTest.java deleted file mode 100644 index a51a3289..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DependencyServiceTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.ResolvedWorkspace; -import p.studio.compiler.workspaces.phases.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class DependencyServiceTest { - - @Test - void canonical_pipeline_e2e(@TempDir Path tempDir) throws IOException { - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - Files.createDirectories(root.resolve("src")); - Files.createDirectories(depA.resolve("src")); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}); - - final var cfg = new DependencyConfig(false, root, List.of()); - final var phases = List.of( - new SeedPhase(), - new DiscoverPhase(), - new MaterializePhase(), - new LocalizePhase(), - new ValidatePhase(), - new StackPhase(), - new PolicyPhase() - ); - - final var service = new DependencyService(phases); - final ResolvedWorkspace ws = service.run(cfg); - - assertNotNull(ws); - assertNotNull(ws.graph()); - assertEquals(2, ws.graph().projects().size()); - - // Obter ids por nome - var rootDesc = ws.graph().projects().stream().filter(p -> p.getName().equals("root-proj")).findFirst().orElseThrow(); - var depDesc = ws.graph().projects().stream().filter(p -> p.getName().equals("dep-a")).findFirst().orElseThrow(); - final var rootId = rootDesc.getProjectId(); - final var depId = depDesc.getProjectId(); - - // Edges: root -> dep - assertTrue(ws.graph().edges().get(rootId.getIndex()).contains(depId)); - assertTrue(ws.graph().edges().get(depId.getIndex()).isEmpty()); - - // Stack: de acordo com a implementação atual (arestas root->dep), root vem antes - assertEquals(rootId, ws.stack().projects().get(0)); - assertEquals(depId, ws.stack().projects().get(1)); - - // workspace.root é o projeto root - assertEquals(rootId, ws.projectId()); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DiscoverPhaseTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DiscoverPhaseTest.java deleted file mode 100644 index 251d1097..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/DiscoverPhaseTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.DiscoverPhase; -import p.studio.compiler.workspaces.phases.SeedPhase; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class DiscoverPhaseTest { - - @Test - void discover_finds_root_and_local_dependency(@TempDir Path tempDir) throws IOException { - // Estrutura: root/ (prometeu.json) -> deps: ["depA/"] e cria depA/(prometeu.json) - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}, null); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}, null); - - final var cfg = new DependencyConfig(false, root, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - assertTrue(ReadOnlyCollection.isEmpty(new SeedPhase().run(ctx))); - - final var issues = new DiscoverPhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Descoberta deve ocorrer sem issues"); - - assertEquals(2, ctx.projectInfos.size(), "Dois projetos devem ser descobertos"); - assertTrue(ctx.projectIndexByDirectory.containsKey(root.toRealPath()), "Projeto raiz indexado"); - assertTrue(ctx.projectIndexByDirectory.containsKey(depA.toRealPath()), "Projeto depA indexado"); - assertEquals(0L, ctx.projectIndexByDirectory.get(root.toRealPath())); - assertEquals(1L, ctx.projectIndexByDirectory.get(depA.toRealPath())); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps, String language) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String langField = language == null ? "" : ",\n \"language\": \"" + language + "\""; - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + langField + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/LocalizePhaseTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/LocalizePhaseTest.java deleted file mode 100644 index 7f42228f..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/LocalizePhaseTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.*; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class LocalizePhaseTest { - - @Test - void localize_resolves_dependency_references(@TempDir Path tempDir) throws IOException { - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - Files.createDirectories(root.resolve("src")); - Files.createDirectories(depA.resolve("src")); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}); - - final var cfg = new DependencyConfig(false, root, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - assertTrue(ReadOnlyCollection.isEmpty(new SeedPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new DiscoverPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new MaterializePhase().run(ctx))); - - final var issues = new LocalizePhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Localização deve ocorrer sem issues"); - - final var depId = ctx.projectIdByDirectoryRoot.get(depA.toRealPath()); - assertNotNull(depId); - final var rootId = ctx.root; - assertTrue(ctx.dependenciesByProject.get(rootId.getIndex()).contains(depId), "Root deve depender de depA"); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/MaterializePhaseTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/MaterializePhaseTest.java deleted file mode 100644 index f384d2e7..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/MaterializePhaseTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.DiscoverPhase; -import p.studio.compiler.workspaces.phases.MaterializePhase; -import p.studio.compiler.workspaces.phases.SeedPhase; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class MaterializePhaseTest { - - @Test - void materialize_builds_nodes_and_sets_root(@TempDir Path tempDir) throws IOException { - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - Files.createDirectories(root.resolve("src")); - Files.createDirectories(depA.resolve("src")); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}); - - final var cfg = new DependencyConfig(false, root, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - assertTrue(ReadOnlyCollection.isEmpty(new SeedPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new DiscoverPhase().run(ctx))); - - final var issues = new MaterializePhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Materialização sem issues esperadas"); - - assertNotNull(ctx.root, "Root ProjectId deve ser definido"); - assertEquals(2, ctx.projectNodes.size()); - assertEquals(2, ctx.dependenciesByProject.size()); - assertTrue(ctx.projectIdByDirectoryRoot.containsKey(root.toRealPath())); - assertTrue(ctx.projectIdByDirectoryRoot.containsKey(depA.toRealPath())); - - final var rootNode = ctx.projectNodes.get(ctx.root.getIndex()); - assertEquals("root-proj", rootNode.getName()); - assertEquals("0.1.0", rootNode.getVersion()); - assertEquals("pbs", rootNode.getLanguageId()); - assertFalse(rootNode.getSourceRoots().isEmpty(), "Deve haver pelo menos um source root (src)"); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/SeedPhaseTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/SeedPhaseTest.java deleted file mode 100644 index 5c1d23e1..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/SeedPhaseTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.SeedPhase; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class SeedPhaseTest { - - @Test - void seed_initializes_root_and_pending(@TempDir Path tempDir) throws Exception { - final var cfg = new DependencyConfig(false, tempDir, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - - final var issues = new SeedPhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Nenhum issue esperado na SeedPhase"); - - assertNotNull(ctx.mainProjectRootPathCanon, "Caminho canônico do projeto raiz deve ser inicializado"); - assertEquals(1, ctx.pending.size(), "Uma entrada deve ser enfileirada para descoberta inicial"); - assertEquals(tempDir.toRealPath(), ctx.mainProjectRootPathCanon); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/StackPhaseTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/StackPhaseTest.java deleted file mode 100644 index e915bdec..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/StackPhaseTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.*; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class StackPhaseTest { - - @Test - void stack_topologically_sorts_projects(@TempDir Path tempDir) throws IOException { - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - Files.createDirectories(root.resolve("src")); - Files.createDirectories(depA.resolve("src")); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}); - - final var cfg = new DependencyConfig(false, root, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - assertTrue(ReadOnlyCollection.isEmpty(new SeedPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new DiscoverPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new MaterializePhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new LocalizePhase().run(ctx))); - - final var issues = new StackPhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Ordenação sem issues"); - - assertNotNull(ctx.stack); - assertEquals(2, ctx.stack.projects().size()); - final var depId = ctx.projectIdByDirectoryRoot.get(depA.toRealPath()); - final var rootId = ctx.root; - assertEquals(rootId, ctx.stack.projects().get(0), "Root (sem entradas) vem primeiro pelo algoritmo atual"); - assertEquals(depId, ctx.stack.projects().get(1)); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/ValidateAndPolicyPhasesTest.java b/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/ValidateAndPolicyPhasesTest.java deleted file mode 100644 index 70067890..00000000 --- a/prometeu-compiler/prometeu-deps/src/test/java/p/studio/compiler/workspaces/ValidateAndPolicyPhasesTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package p.studio.compiler.workspaces; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.compiler.messages.DependencyConfig; -import p.studio.compiler.models.DependencyContext; -import p.studio.compiler.workspaces.phases.*; -import p.studio.utilities.structures.ReadOnlyCollection; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class ValidateAndPolicyPhasesTest { - - @Test - void validate_checks_consistency_without_issues(@TempDir Path tempDir) throws IOException { - final Path root = tempDir; - final Path depA = root.resolve("depA"); - Files.createDirectories(depA); - Files.createDirectories(root.resolve("src")); - Files.createDirectories(depA.resolve("src")); - - writeManifest(root, "root-proj", "0.1.0", new String[]{"depA"}); - writeManifest(depA, "dep-a", "0.1.0", new String[]{}); - - final var cfg = new DependencyConfig(false, root, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - assertTrue(ReadOnlyCollection.isEmpty(new SeedPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new DiscoverPhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new MaterializePhase().run(ctx))); - assertTrue(ReadOnlyCollection.isEmpty(new LocalizePhase().run(ctx))); - - final var issues = new ValidatePhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "Validação não deve reportar issues no cenário feliz"); - } - - @Test - void policy_is_noop(@TempDir Path tempDir) { - final var cfg = new DependencyConfig(false, tempDir, java.util.List.of()); - final var ctx = DependencyContext.seed(cfg); - final var issues = new PolicyPhase().run(ctx); - assertTrue(ReadOnlyCollection.isEmpty(issues), "PolicyPhase atualmente é no-op"); - } - - private static void writeManifest(Path dir, String name, String version, String[] localDeps) throws IOException { - final StringBuilder deps = new StringBuilder(); - deps.append("["); - for (int i = 0; i < localDeps.length; i++) { - if (i > 0) deps.append(","); - deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}"); - } - deps.append("]"); - final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}"; - Files.writeString(dir.resolve("prometeu.json"), json); - } -} diff --git a/prometeu-infra/build.gradle.kts b/prometeu-infra/build.gradle.kts index 0255beb2..bac3e7f0 100644 --- a/prometeu-infra/build.gradle.kts +++ b/prometeu-infra/build.gradle.kts @@ -4,5 +4,6 @@ plugins { dependencies { api(libs.jackson.databind) + api(libs.apache.commons.lang3) api(libs.apache.commons.collections) } \ No newline at end of file diff --git a/prometeu-infra/src/main/java/p/studio/utilities/logs/LogAggregator.java b/prometeu-infra/src/main/java/p/studio/utilities/logs/LogAggregator.java index 20c9f9a2..d3f3047b 100644 --- a/prometeu-infra/src/main/java/p/studio/utilities/logs/LogAggregator.java +++ b/prometeu-infra/src/main/java/p/studio/utilities/logs/LogAggregator.java @@ -5,6 +5,14 @@ import org.slf4j.Logger; import java.util.function.Consumer; public interface LogAggregator { + static LogAggregator empty() { + return with(s -> {}); + } + + static LogAggregator stdout() { + return with(System.out::println); + } + static LogAggregator with(final Consumer consumer) { return new LogAggregatorImpl(consumer); }