code improvements, add pipeline stages

This commit is contained in:
bQUARKz 2026-02-26 07:13:02 +00:00
parent 47a077bffc
commit 31c59f670e
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
16 changed files with 289 additions and 179 deletions

View File

@ -13,7 +13,7 @@ public class BuilderPipelineContext {
public Path rootProjectPathCanon; public Path rootProjectPathCanon;
public ResolvedWorkspace resolvedWorkspace; public ResolvedWorkspace resolvedWorkspace;
public final FileTable fileTable = new FileTable(); public FileTable fileTable;
private BuilderPipelineContext( private BuilderPipelineContext(
final BuilderPipelineConfig config, final BuilderPipelineConfig config,

View File

@ -4,8 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.exceptions.BuildException; import p.studio.compiler.exceptions.BuildException;
import p.studio.compiler.messages.BuilderPipelineConfig; import p.studio.compiler.messages.BuilderPipelineConfig;
import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.stages.LoadPipelineStage; import p.studio.compiler.workspaces.stages.*;
import p.studio.compiler.workspaces.stages.ResolvePipelineStage;
import p.studio.utilities.logs.LogAggregator; import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyCollection; import p.studio.utilities.structures.ReadOnlyCollection;
@ -17,8 +16,11 @@ public class BuilderPipelineService {
static { static {
final var stages = List.<PipelineStage>of( final var stages = List.<PipelineStage>of(
new ResolvePipelineStage(), new ResolveDepsPipelineStage(),
new LoadPipelineStage() new LoadSourcesPipelineStage(),
new FrontendPhasePipelineStage(),
new LowerToVMPipelineStage(),
new EmitBytecodePipelineStage()
); );
INSTANCE = new BuilderPipelineService(stages); INSTANCE = new BuilderPipelineService(stages);
} }

View File

@ -0,0 +1,15 @@
package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
@Slf4j
public class EmitBytecodePipelineStage implements PipelineStage {
@Override
public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) {
return BuildingIssueSink.empty();
}
}

View File

@ -0,0 +1,15 @@
package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
@Slf4j
public class FrontendPhasePipelineStage implements PipelineStage {
@Override
public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) {
return BuildingIssueSink.empty();
}
}

View File

@ -1,145 +0,0 @@
package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlySet;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
public class LoadPipelineStage implements PipelineStage {
@Override
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
final var issues = BuildingIssueSink.empty();
// Iterates projects; "loads sources"; registers files
ctx.resolvedWorkspace.topologicalOrder().forEach(pId -> {
final var pd = ctx.resolvedWorkspace.graph().projectTable().get(pId);
logs.using(log).info("Project [ " + pd.getName() + " ] source loading...");
final var allowedExtensions = normalize(ctx.resolvedWorkspace.frontendSpec().getAllowedExtensions());
for (final var sourceRootPath : pd.getSourceRoots()) {
logs.using(log).debug("Walking source root [ " + sourceRootPath + " ]");
try {
final List<Path> paths = new ArrayList<>();
Files.walkFileTree(sourceRootPath, new SourceCrawler(allowedExtensions, paths));
paths.sort(Path::compareTo);
for (var path : paths) {
logs.using(log).debug("file tabling [ " + path + " ]");
final var attributes = Files.readAttributes(path, BasicFileAttributes.class);
final var size = attributes.size();
final var lastModified = attributes.lastModifiedTime().toMillis();
final var rawFile = new SourceHandle(pId, path, size, lastModified, ctx.sourceProviderFactory);
// register in dense tables
var fileId = ctx.fileTable.register(rawFile);
}
} catch (IOException e) {
issues.report(builder -> builder
.error(true)
.message("Failed to load project [ " + pd.getName() + " ]")
.exception(e));
}
}
});
return BuildingIssueSink.empty();
}
private static ReadOnlySet<String> normalize(final ReadOnlySet<String> extensions) {
return ReadOnlySet
.wrap(extensions
.map(String::toLowerCase)
.map(s -> s.startsWith(".") ? s.substring(1) : s)
.collect(Collectors.toSet()));
}
private static final class SourceCrawler extends SimpleFileVisitor<Path> {
private static final Set<String> IGNORED_DIRS = Set.of(
".git",
".prometeu",
".workspace",
"target",
"build",
"out",
"node_modules"
);
private final ReadOnlySet<String> allowedExtensions;
private final List<Path> paths;
public SourceCrawler(
final ReadOnlySet<String> allowedExtensions,
final List<Path> paths) {
this.allowedExtensions = allowedExtensions;
this.paths = paths;
}
@Override
public FileVisitResult preVisitDirectory(final Path path, final BasicFileAttributes attrs) {
for (final var directory : path) {
if (!isAllowedPath(directory)) {
return FileVisitResult.SKIP_SUBTREE;
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) {
if (!attrs.isRegularFile()) {
return FileVisitResult.CONTINUE;
}
if (hasAllowedExt(path, allowedExtensions)) {
paths.add(path);
}
return FileVisitResult.CONTINUE;
}
private static boolean isAllowedPath(
final Path path) {
for (Path part : path) {
if (IGNORED_DIRS.contains(part.toString())) {
return false;
}
}
return true;
}
private static boolean hasAllowedExt(
final Path path,
final ReadOnlySet<String> allowedExtensions) {
final var fileName = path.getFileName();
if (Objects.isNull(fileName)) {
return false;
}
final var extension = FilenameUtils.getExtension(fileName.toString()).toLowerCase();
if (StringUtils.isBlank(extension)) {
return false;
}
return allowedExtensions.contains(extension);
}
}
}

View File

@ -0,0 +1,97 @@
package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlySet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class LoadSourcesPipelineStage implements PipelineStage {
@Override
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
final var issues = BuildingIssueSink.empty();
ctx.resolvedWorkspace.stack().topologicalOrder.forEach(pId -> visitProjectSources(pId, ctx, logs, issues));
return issues;
}
private static void visitProjectSources(
final ProjectId pId,
final BuilderPipelineContext ctx,
final LogAggregator logs,
final BuildingIssueSink issues) {
final var pd = ctx.resolvedWorkspace.graph().projectTable().get(pId);
logs.using(log).debug("Project [ " + pd.getName() + " ] source loading...");
final var allowedExtensions = normalize(ctx.resolvedWorkspace.frontendSpec().getAllowedExtensions());
// Iterates source roots; crawls files; registers them
for (final var sourceRootPath : pd.getSourceRoots()) {
logs.using(log).debug("Walking source root [ " + sourceRootPath + " ]");
final List<Path> paths = new ArrayList<>();
final var sourceCrawler = new SourceCrawler(allowedExtensions, paths);
try {
Files.walkFileTree(sourceRootPath, sourceCrawler);
paths.sort(Path::compareTo); // do we really need this for deterministic builds?
} catch (IOException e) {
issues.report(builder -> builder
.error(true)
.message("Failed to load project [ " + pd.getName() + " ]")
.exception(e));
}
registerFiles(pId, pd, paths, ctx, logs, issues);
}
}
private static void registerFiles(
final ProjectId pId,
final ProjectDescriptor pd,
final List<Path> canonPaths,
final BuilderPipelineContext ctx,
final LogAggregator logs,
final BuildingIssueSink issues) {
for (var canonPath : canonPaths) {
final long size;
final long lastModified;
try {
final BasicFileAttributes attributes = Files.readAttributes(canonPath, BasicFileAttributes.class);
size = attributes.size();
lastModified = attributes.lastModifiedTime().toMillis();
} catch (IOException e) {
issues.report(builder -> builder
.error(true)
.message("Failed to read attributes for file: " + canonPath)
.exception(e));
continue;
}
// register in dense tables
final var relativePath = pd.getRootPath().relativize(canonPath);
final var sourceHandle = new SourceHandle(pId, relativePath, canonPath, size, lastModified, ctx.sourceProviderFactory);
logs.using(log).debug("Registering: " + sourceHandle);
ctx.fileTable.register(sourceHandle);
}
}
private static ReadOnlySet<String> normalize(final ReadOnlySet<String> extensions) {
return ReadOnlySet
.wrap(extensions
.map(String::toLowerCase)
.map(s -> s.startsWith(".") ? s.substring(1) : s)
.collect(Collectors.toSet()));
}
}

View File

@ -0,0 +1,15 @@
package p.studio.compiler.workspaces.stages;
import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;
@Slf4j
public class LowerToVMPipelineStage implements PipelineStage {
@Override
public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) {
return BuildingIssueSink.empty();
}
}

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.messages.BuildingIssueSink; import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.messages.DependencyConfig; import p.studio.compiler.messages.DependencyConfig;
import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.source.tables.FileTable;
import p.studio.compiler.workspaces.DependencyService; import p.studio.compiler.workspaces.DependencyService;
import p.studio.compiler.workspaces.PipelineStage; import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator; import p.studio.utilities.logs.LogAggregator;
@ -12,7 +13,7 @@ import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
@Slf4j @Slf4j
public class ResolvePipelineStage implements PipelineStage { public class ResolveDepsPipelineStage implements PipelineStage {
@Override @Override
public BuildingIssueSink run(final BuilderPipelineContext ctx, LogAggregator logs) { public BuildingIssueSink run(final BuilderPipelineContext ctx, LogAggregator logs) {
try { try {
@ -26,6 +27,7 @@ public class ResolvePipelineStage implements PipelineStage {
} }
final var dependencyConfig = new DependencyConfig(ctx.config.explain(), ctx.rootProjectPathCanon); final var dependencyConfig = new DependencyConfig(ctx.config.explain(), ctx.rootProjectPathCanon);
ctx.resolvedWorkspace = DependencyService.INSTANCE.run(dependencyConfig, logs); ctx.resolvedWorkspace = DependencyService.INSTANCE.run(dependencyConfig, logs);
ctx.fileTable = new FileTable(ctx.resolvedWorkspace.graph().projectTable().size());
return BuildingIssueSink.empty(); return BuildingIssueSink.empty();
} }
} }

View File

@ -0,0 +1,86 @@
package p.studio.compiler.workspaces.stages;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import p.studio.utilities.structures.ReadOnlySet;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Objects;
import java.util.Set;
final class SourceCrawler extends SimpleFileVisitor<Path> {
private static final Set<String> IGNORED_DIRS = Set.of(
".git",
".prometeu",
".workspace",
"target",
"build",
"out",
"node_modules"
);
private final ReadOnlySet<String> allowedExtensions;
private final List<Path> paths;
public SourceCrawler(
final ReadOnlySet<String> allowedExtensions,
final List<Path> paths) {
this.allowedExtensions = allowedExtensions;
this.paths = paths;
}
@Override
public FileVisitResult preVisitDirectory(final Path path, final BasicFileAttributes attrs) {
for (final var directory : path) {
if (!isAllowedPath(directory)) {
return FileVisitResult.SKIP_SUBTREE;
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) {
if (!attrs.isRegularFile()) {
return FileVisitResult.CONTINUE;
}
if (hasAllowedExt(path, allowedExtensions)) {
paths.add(path);
}
return FileVisitResult.CONTINUE;
}
private static boolean isAllowedPath(
final Path path) {
for (Path part : path) {
if (IGNORED_DIRS.contains(part.toString())) {
return false;
}
}
return true;
}
private static boolean hasAllowedExt(
final Path path,
final ReadOnlySet<String> allowedExtensions) {
final var fileName = path.getFileName();
if (Objects.isNull(fileName)) {
return false;
}
final var extension = FilenameUtils.getExtension(fileName.toString()).toLowerCase();
if (StringUtils.isBlank(extension)) {
return false;
}
return allowedExtensions.contains(extension);
}
}

View File

@ -9,14 +9,19 @@ import p.studio.compiler.utilities.SourceProviderFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class SourceHandle { public class SourceHandle {
@Getter @Getter
private final ProjectId projectId; private final ProjectId projectId;
@Getter
private final Path relativePath;
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
@Getter @Getter
private final Path path; // canon path to file private final Path canonPath;
@Getter @Getter
private final String filename; private final String filename;
@Getter @Getter
@ -27,16 +32,18 @@ public class SourceHandle {
public SourceHandle( public SourceHandle(
final ProjectId projectId, final ProjectId projectId,
final Path path, final Path relativePath,
final Path canonPath,
final long size, final long size,
final long lastModified, final long lastModified,
final SourceProviderFactory factory) { final SourceProviderFactory factory) {
this.projectId = projectId; this.projectId = projectId;
this.path = path; this.relativePath = relativePath;
this.filename = path.getFileName().toString(); this.canonPath = canonPath;
this.filename = canonPath.getFileName().toString();
this.size = size; this.size = size;
this.lastModified = lastModified; this.lastModified = lastModified;
this.provider = factory.create(path); this.provider = factory.create(canonPath);
} }
public byte[] readBytes() throws IOException { public byte[] readBytes() throws IOException {
@ -46,4 +53,12 @@ public class SourceHandle {
public String readUtf8() throws IOException { public String readUtf8() throws IOException {
return new String(readBytes(), StandardCharsets.UTF_8); return new String(readBytes(), StandardCharsets.UTF_8);
} }
@Override
public String toString() {
return "SourceHandle{ %s, %s, %sKb, mod: %s }".formatted(projectId,
relativePath,
size / 1024,
LocalDateTime.ofInstant(Instant.ofEpochMilli(lastModified), ZoneId.systemDefault()));
}
} }

View File

@ -6,29 +6,37 @@ import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ProjectId; import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import java.util.*; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class FileTable extends InternTable<FileId, SourceHandle> implements FileTableReader { public class FileTable extends InternTable<FileId, SourceHandle> implements FileTableReader {
private final Map<ProjectId, Set<FileId>> projectFiles = new HashMap<>(); private final FileIds[] projectFiles;
public FileTable() { public FileTable(int projectCapacity) {
super(FileId::new); super(FileId::new);
projectFiles = new FileIds[projectCapacity];
Arrays.setAll(projectFiles, ignored -> new FileIds());
} }
@Override @Override
public FileId register(final SourceHandle value) { public FileId register(final SourceHandle value) {
final var fileId = super.register(value); final var fileId = super.register(value);
projectFiles.computeIfAbsent(value.getProjectId(), ignored -> new HashSet<>()).add(fileId); projectFiles[value.getProjectId().getIndex()].set.add(fileId);
return fileId; return fileId;
} }
@Override @Override
public ReadOnlyList<FileId> getFiles(final ProjectId projectId) { public ReadOnlyList<FileId> getFiles(final ProjectId projectId) {
final var fileIds = projectFiles.get(projectId); final var fileIds = projectFiles[projectId.getIndex()];
if (CollectionUtils.isEmpty(fileIds)) { if (CollectionUtils.isEmpty(fileIds.set)) {
return ReadOnlyList.empty(); return ReadOnlyList.empty();
} }
return ReadOnlyList.wrap(fileIds); return ReadOnlyList.wrap(fileIds.set);
}
private static class FileIds {
public final Set<FileId> set = new HashSet<>();
} }
} }

View File

@ -7,7 +7,7 @@ public class BuildStack {
public final ReadOnlyList<ProjectId> topologicalOrder; public final ReadOnlyList<ProjectId> topologicalOrder;
public final ReadOnlyList<ProjectId> reverseTopologicalOrder; public final ReadOnlyList<ProjectId> reverseTopologicalOrder;
public BuildStack(ReadOnlyList<ProjectId> topologicalOrder) { public BuildStack(final ReadOnlyList<ProjectId> topologicalOrder) {
this.topologicalOrder = topologicalOrder; this.topologicalOrder = topologicalOrder;
this.reverseTopologicalOrder = topologicalOrder.invert(); this.reverseTopologicalOrder = topologicalOrder.invert();
} }

View File

@ -2,23 +2,12 @@ package p.studio.compiler.models;
import p.studio.compiler.source.identifiers.ProjectId; import p.studio.compiler.source.identifiers.ProjectId;
import java.util.stream.Stream;
public record ResolvedWorkspace( public record ResolvedWorkspace(
ProjectId projectId, ProjectId mainProjectId,
FrontendSpec frontendSpec, FrontendSpec frontendSpec,
WorkspaceGraph graph, WorkspaceGraph graph,
BuildStack stack) { BuildStack stack) {
public ProjectDescriptor mainProject() { public ProjectDescriptor mainProject() {
return graph.projectDescriptor(projectId); return graph.projectDescriptor(mainProjectId);
}
public Stream<ProjectId> topologicalOrder() {
return stack.topologicalOrder.stream();
}
public Stream<ProjectId> reverseTopologicalOrder() {
return stack.reverseTopologicalOrder.stream();
} }
} }

View File

@ -1,11 +1,11 @@
package p.studio.compiler.models; package p.studio.compiler.models;
import p.studio.compiler.source.identifiers.ProjectId; import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.source.tables.ProjectTable; import p.studio.compiler.source.tables.ProjectTableReader;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
public record WorkspaceGraph( public record WorkspaceGraph(
ProjectTable projectTable, ProjectTableReader projectTable,
ReadOnlyList<ReadOnlyList<ProjectId>> dependenciesByProjectId) { ReadOnlyList<ReadOnlyList<ProjectId>> dependenciesByProjectId) {
public ProjectDescriptor projectDescriptor(final ProjectId projectId) { public ProjectDescriptor projectDescriptor(final ProjectId projectId) {

View File

@ -4,4 +4,6 @@ plugins {
dependencies { dependencies {
api(project(":prometeu-infra")) api(project(":prometeu-infra"))
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
} }

View File

@ -0,0 +1,9 @@
package p.studio.compiler.messages;
import p.studio.compiler.source.tables.FileTableReader;
import p.studio.compiler.source.tables.ProjectTableReader;
public record FrontendPhaseRequest(
ProjectTableReader projectTable,
FileTableReader fileTable) {
}