add logs and better workflow iteraction with it
This commit is contained in:
parent
5ee28101a3
commit
f61200f8ef
@ -6,6 +6,7 @@ import lombok.Getter;
|
||||
@Builder
|
||||
@Getter
|
||||
public class BuildingIssue {
|
||||
private final boolean error;
|
||||
private final String message;
|
||||
private final Throwable exception;
|
||||
}
|
||||
|
||||
@ -3,8 +3,6 @@ package p.studio.compiler.model;
|
||||
import p.studio.compiler.source.identifiers.ProjectId;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ResolvedGraph(
|
||||
ProjectId root,
|
||||
ReadOnlyList<ProjectDescriptor> projects,
|
||||
|
||||
@ -4,32 +4,59 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import p.studio.compiler.exceptions.BuildException;
|
||||
import p.studio.compiler.model.DependencyPipelineConfig;
|
||||
import p.studio.compiler.model.ResolvedWorkspace;
|
||||
import p.studio.compiler.workspaces.phases.*;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
import p.studio.utilities.structures.ReadOnlyCollection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public final class DependencyPipelineService {
|
||||
public final static DependencyPipelineService INSTANCE;
|
||||
|
||||
static {
|
||||
final var phases = List.of(
|
||||
new SeedPhase(),
|
||||
new DiscoverPhase(),
|
||||
new MaterializePhase(),
|
||||
new LocalizePhase(),
|
||||
new ValidatePhase(),
|
||||
new StackPhase(),
|
||||
new PolicyPhase()
|
||||
);
|
||||
INSTANCE = new DependencyPipelineService(phases);
|
||||
}
|
||||
|
||||
private final List<DependencyPipelinePhase> phases;
|
||||
|
||||
public DependencyPipelineService(List<DependencyPipelinePhase> phases) {
|
||||
DependencyPipelineService(List<DependencyPipelinePhase> phases) {
|
||||
this.phases = phases;
|
||||
}
|
||||
|
||||
public ResolvedWorkspace run(DependencyPipelineConfig config) {
|
||||
public ResolvedWorkspace run(
|
||||
final DependencyPipelineConfig config,
|
||||
final LogAggregator logs) {
|
||||
final var ctx = DependencyPipelineContext.seed(config);
|
||||
|
||||
for (final var dependencyPipelinePhase : phases) {
|
||||
final var issues = dependencyPipelinePhase.run(ctx);
|
||||
var error = false;
|
||||
if (ReadOnlyCollection.isNotEmpty(issues)) {
|
||||
for (final var issue : issues) {
|
||||
log.error(issue.getMessage(), issue.getException());
|
||||
if (issue.isError()) {
|
||||
error = true;
|
||||
logs.using(log).error(issue.getMessage(), issue.getException());
|
||||
continue;
|
||||
}
|
||||
logs.using(log).warn(issue.getMessage());
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
throw new BuildException("issues found during dependency pipeline phase: " + dependencyPipelinePhase.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("dependency pipeline completed successfully, going to resolve workspace");
|
||||
logs.using(log).info("dependency pipeline completed successfully, going to resolve workspace");
|
||||
|
||||
return ctx.toResolvedWorkspace();
|
||||
}
|
||||
|
||||
@ -2,5 +2,5 @@ package p.studio.compiler.workspaces;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record DependencyReference(Path path) {
|
||||
public record DependencyReference(Path canonPath) {
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package p.studio.compiler.workspaces.phases;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import p.studio.compiler.FrontendRegistryService;
|
||||
import p.studio.compiler.model.BuildingIssue;
|
||||
import p.studio.compiler.model.PrometeuManifest;
|
||||
@ -12,10 +11,11 @@ import p.studio.utilities.structures.ReadOnlyCollection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
|
||||
public class DiscoverPhase implements DependencyPipelinePhase {
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@ -29,36 +29,47 @@ public class DiscoverPhase implements DependencyPipelinePhase {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var manifestPath = rootPathCanon.resolve("prometeu.json");
|
||||
if (!Files.exists(manifestPath) || !Files.isRegularFile(manifestPath)) {
|
||||
final Path manifestPathCanon;
|
||||
try {
|
||||
manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath();
|
||||
} catch (IOException e) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: manifest not found: expected a file " + manifestPath + " (" + rootPathCanon + ")")
|
||||
.error(true)
|
||||
.message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon)
|
||||
.exception(e)
|
||||
.build();
|
||||
issues.add(issue);
|
||||
continue;
|
||||
}
|
||||
if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.error(true)
|
||||
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon)
|
||||
.build();
|
||||
issues.add(issue);
|
||||
continue;
|
||||
}
|
||||
|
||||
final var manifest = PrometeuManifest.extract(manifestPath, mapper);
|
||||
final var manifest = PrometeuManifest.extract(manifestPathCanon, mapper);
|
||||
|
||||
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language());
|
||||
if (frontendSpec.isEmpty()) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: unknown language " + manifest.language() + " for project " + manifest.name())
|
||||
.error(true)
|
||||
.message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name())
|
||||
.build();
|
||||
issues.add(issue);
|
||||
continue;
|
||||
}
|
||||
if (ReadOnlyCollection.isEmpty(manifest.dependencies())) {
|
||||
log.warn("dependencies: no dependencies specified for project {}", manifest.name());
|
||||
}
|
||||
|
||||
final long projectIndex = ctx.projectInfos.size();
|
||||
final var projectInfo = ProjectInfo
|
||||
.builder()
|
||||
.rootDirectory(rootPathCanon)
|
||||
.manifestPath(manifestPath)
|
||||
.manifestPath(manifestPathCanon)
|
||||
.manifest(manifest)
|
||||
.frontendSpec(frontendSpec.get())
|
||||
.build();
|
||||
@ -74,7 +85,7 @@ public class DiscoverPhase implements DependencyPipelinePhase {
|
||||
} catch (IOException e) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: dep path does not exist: " + dependencyPath + " (" + rootPathCanon + ")")
|
||||
.message("[DEPS]: dep canonPath does not exist: " + dependencyPath + " (" + manifest.name() + ")")
|
||||
.exception(e)
|
||||
.build();
|
||||
issues.add(issue);
|
||||
@ -82,7 +93,7 @@ public class DiscoverPhase implements DependencyPipelinePhase {
|
||||
} else if (dependencyDeclaration instanceof PrometeuManifest.DependencyDeclaration.Git) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: url dependencies not yet supported " + manifest.name() + " (" + rootPathCanon + ")")
|
||||
.message("[DEPS]: url dependencies not yet supported " + manifest.name() + " (" + rootPathCanon + ")")
|
||||
.build();
|
||||
issues.add(issue);
|
||||
}
|
||||
|
||||
@ -15,12 +15,13 @@ public final class LocalizePhase implements DependencyPipelinePhase {
|
||||
for (int i = 0; i < state.projectNodes.size(); i++) {
|
||||
final var fromProjectNode = state.projectNodes.get(i);
|
||||
for (final var dependencyReference : fromProjectNode.getDependencies()) {
|
||||
final var dependencyReferencePath = dependencyReference.path();
|
||||
final var projectId = state.projectIdByDirectoryRoot.get(dependencyReferencePath);
|
||||
final var dependencyReferenceCanonPath = dependencyReference.canonPath();
|
||||
final var projectId = state.projectIdByDirectoryRoot.get(dependencyReferenceCanonPath);
|
||||
if (Objects.isNull(projectId)) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: dependency not found: " + dependencyReferencePath + " (referenced from " + fromProjectNode.getProjectRootPath() + ")")
|
||||
.error(true)
|
||||
.message("[DEPS]: dependency not found: " + dependencyReferenceCanonPath)
|
||||
.build();
|
||||
issues.add(issue);
|
||||
continue;
|
||||
|
||||
@ -33,7 +33,7 @@ public final class MaterializePhase implements DependencyPipelinePhase {
|
||||
if (rootProjectId == null) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: root project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized")
|
||||
.message("[DEPS]: root project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized")
|
||||
.build();
|
||||
issues.add(issue);
|
||||
return ReadOnlyCollection.wrap(issues);
|
||||
@ -57,7 +57,7 @@ public final class MaterializePhase implements DependencyPipelinePhase {
|
||||
} catch (IOException e) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: source root path does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")")
|
||||
.message("[DEPS]: source root canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")")
|
||||
.exception(e)
|
||||
.build();
|
||||
sourceRootIssues.add(issue);
|
||||
@ -78,7 +78,7 @@ public final class MaterializePhase implements DependencyPipelinePhase {
|
||||
} catch (IOException e) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: local dep path does not exist: " + dependencyPath + " (from " + projectInfo.rootDirectory + ")")
|
||||
.message("[DEPS]: local dep canonPath does not exist: " + dependencyPath + " (from " + projectInfo.rootDirectory + ")")
|
||||
.exception(e)
|
||||
.build();
|
||||
issues.add(issue);
|
||||
@ -86,7 +86,7 @@ public final class MaterializePhase implements DependencyPipelinePhase {
|
||||
} else if (d instanceof PrometeuManifest.DependencyDeclaration.Git g) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: url dependency '" + g.getGit() + "' requires an explicit 'rev' and lock mapping (not supported yet)")
|
||||
.message("[DEPS]: url dependency '" + g.getGit() + "' requires an explicit 'rev' and lock mapping (not supported yet)")
|
||||
.build();
|
||||
issues.add(issue);
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ public final class SeedPhase implements DependencyPipelinePhase {
|
||||
} catch (IOException e) {
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("Failed to canonicalize root directory: " + ctx.config().cacheDir())
|
||||
.message("[DEPS]: failed to canonicalize root directory: " + ctx.config().cacheDir())
|
||||
.build();
|
||||
issues.add(issue);
|
||||
}
|
||||
|
||||
@ -13,18 +13,18 @@ import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
public final class StackPhase implements DependencyPipelinePhase {
|
||||
/**
|
||||
* Implements topological sort; detects dependency cycles; sets build stack
|
||||
*/
|
||||
@Override
|
||||
public ReadOnlyCollection<BuildingIssue> run(DependencyPipelineContext state) {
|
||||
final List<BuildingIssue> issues = new ArrayList<>();
|
||||
|
||||
final int n = state.projectNodes.size();
|
||||
public ReadOnlyCollection<BuildingIssue> run(DependencyPipelineContext ctx) {
|
||||
final int n = ctx.projectNodes.size();
|
||||
final int[] indeg = new int[n];
|
||||
for (int from = 0; from < n; from++) {
|
||||
for (final ProjectId to : state.dependenciesByProject.get(from)) {
|
||||
for (final ProjectId to : ctx.dependenciesByProject.get(from)) {
|
||||
indeg[to.getIndex()]++;
|
||||
}
|
||||
}
|
||||
@ -38,8 +38,8 @@ public final class StackPhase implements DependencyPipelinePhase {
|
||||
// Performs topological sort using Kahn's algorithm
|
||||
while (!q.isEmpty()) {
|
||||
final int u = q.removeFirst();
|
||||
travesalOrder.add(state.projectNodes.get(u).getProjectId());
|
||||
for (final ProjectId v : state.dependenciesByProject.get(u)) {
|
||||
travesalOrder.add(ctx.projectNodes.get(u).getProjectId());
|
||||
for (final ProjectId v : ctx.dependenciesByProject.get(u)) {
|
||||
if (--indeg[(int) v.getId()] == 0) {
|
||||
q.addLast((int) v.getId());
|
||||
}
|
||||
@ -47,15 +47,110 @@ public final class StackPhase implements DependencyPipelinePhase {
|
||||
}
|
||||
|
||||
if (travesalOrder.size() != n) {
|
||||
issues.add(BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: cycle detected in dependency graph")
|
||||
.build());
|
||||
return ReadOnlyCollection.wrap(issues);
|
||||
boolean[] stuck = new boolean[n];
|
||||
for (int i = 0; i < n; i++) stuck[i] = indeg[i] > 0;
|
||||
|
||||
var tarjan = new TarjanScc(n, ctx.dependenciesByProject);
|
||||
final List<List<Integer>> sccs = tarjan.run(stuck);
|
||||
|
||||
final List<List<ProjectId>> cycles = new ArrayList<>();
|
||||
for (var scc : sccs) {
|
||||
if (scc.size() > 1) {
|
||||
final var cycle = scc
|
||||
.stream()
|
||||
.map(i -> ctx.projectNodes.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();
|
||||
boolean selfLoop = false;
|
||||
for (final var pu : ctx.dependenciesByProject.get(u)) {
|
||||
if (pu.getIndex() == u) {
|
||||
selfLoop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selfLoop) {
|
||||
cycles.add(List.of(projectId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.stack = new BuildStack(ReadOnlyList.wrap(travesalOrder));
|
||||
final var msg = "[DEPS]: cycle(s) detected:\n" + cycles.stream()
|
||||
.map(projectIds -> " * " + projectIds.stream()
|
||||
.map(pId -> ctx.projectInfos
|
||||
.get(pId.getIndex())
|
||||
.manifest
|
||||
.name())
|
||||
.collect(joining(" -> "))
|
||||
)
|
||||
.collect(joining("\n"));
|
||||
|
||||
return ReadOnlyCollection.wrap(issues);
|
||||
var issue = BuildingIssue.builder().error(true).message(msg).build();
|
||||
return ReadOnlyCollection.wrap(List.of(issue));
|
||||
}
|
||||
|
||||
ctx.stack = new BuildStack(ReadOnlyList.wrap(travesalOrder));
|
||||
|
||||
return ReadOnlyCollection.empty();
|
||||
}
|
||||
|
||||
static final class TarjanScc {
|
||||
private final int n;
|
||||
private final List<List<ProjectId>> adj;
|
||||
|
||||
private int time = 0;
|
||||
private final int[] disc;
|
||||
private final int[] low;
|
||||
private final boolean[] inStack;
|
||||
private final Deque<Integer> stack = new ArrayDeque<>();
|
||||
|
||||
TarjanScc(int n, List<List<ProjectId>> adj) {
|
||||
this.n = n;
|
||||
this.adj = adj;
|
||||
this.disc = new int[n];
|
||||
this.low = new int[n];
|
||||
this.inStack = new boolean[n];
|
||||
java.util.Arrays.fill(disc, -1);
|
||||
}
|
||||
|
||||
List<List<Integer>> run(boolean[] allowed) {
|
||||
List<List<Integer>> sccs = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (allowed[i] && disc[i] == -1) dfs(i, allowed, sccs);
|
||||
}
|
||||
return sccs;
|
||||
}
|
||||
|
||||
private void dfs(int u, boolean[] allowed, List<List<Integer>> out) {
|
||||
disc[u] = low[u] = time++;
|
||||
stack.push(u);
|
||||
inStack[u] = true;
|
||||
|
||||
for (ProjectId pid : adj.get(u)) {
|
||||
int v = pid.getIndex();
|
||||
if (!allowed[v]) continue;
|
||||
|
||||
if (disc[v] == -1) {
|
||||
dfs(v, allowed, out);
|
||||
low[u] = Math.min(low[u], low[v]);
|
||||
} else if (inStack[v]) {
|
||||
low[u] = Math.min(low[u], disc[v]);
|
||||
}
|
||||
}
|
||||
|
||||
if (low[u] == disc[u]) {
|
||||
List<Integer> scc = new ArrayList<>();
|
||||
while (true) {
|
||||
int w = stack.pop();
|
||||
inStack[w] = false;
|
||||
scc.add(w);
|
||||
if (w == u) break;
|
||||
}
|
||||
out.add(scc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,30 +1,32 @@
|
||||
package p.studio.compiler.workspaces.phases;
|
||||
|
||||
import p.studio.compiler.model.BuildingIssue;
|
||||
import p.studio.compiler.workspaces.*;
|
||||
import p.studio.compiler.exceptions.BuildException;
|
||||
import p.studio.compiler.workspaces.DependencyPipelineContext;
|
||||
import p.studio.compiler.workspaces.DependencyPipelinePhase;
|
||||
import p.studio.utilities.structures.ReadOnlyCollection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class ValidatePhase implements DependencyPipelinePhase {
|
||||
@Override
|
||||
public ReadOnlyCollection<BuildingIssue> run(DependencyPipelineContext state) {
|
||||
final List<BuildingIssue> issues = new ArrayList<>();
|
||||
if (state.root == null) {
|
||||
issues.add(BuildingIssue
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("dependencies: root ProjectId not set")
|
||||
.build());
|
||||
return ReadOnlyCollection.wrap(issues);
|
||||
.message("[DEPS]: root ProjectId not set")
|
||||
.build();
|
||||
return ReadOnlyCollection.wrap(List.of(issue));
|
||||
}
|
||||
|
||||
// Ensure edges list matches number of nodes
|
||||
// Ensure the edges list matches the number of nodes
|
||||
if (state.dependenciesByProject.size() != state.projectNodes.size()) {
|
||||
throw new BuildException("dependencies: internal error: edges list size mismatch");
|
||||
final var issue = BuildingIssue
|
||||
.builder()
|
||||
.message("[DEPS]: internal error: edges list size mismatch")
|
||||
.build();
|
||||
return ReadOnlyCollection.wrap(List.of(issue));
|
||||
}
|
||||
|
||||
return ReadOnlyCollection.wrap(issues);
|
||||
return ReadOnlyCollection.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package p.studio.compiler.workspaces;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.studio.compiler.exceptions.BuildException;
|
||||
import p.studio.compiler.model.DependencyPipelineConfig;
|
||||
import p.studio.compiler.model.ResolvedWorkspace;
|
||||
import p.studio.compiler.workspaces.phases.*;
|
||||
@ -68,7 +67,7 @@ class DependencyPipelineServiceTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}";
|
||||
|
||||
@ -44,7 +44,7 @@ class DiscoverPhaseTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String langField = language == null ? "" : ",\n \"language\": \"" + language + "\"";
|
||||
|
||||
@ -45,7 +45,7 @@ class LocalizePhaseTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}";
|
||||
|
||||
@ -53,7 +53,7 @@ class MaterializePhaseTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}";
|
||||
|
||||
@ -48,7 +48,7 @@ class StackPhaseTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}";
|
||||
|
||||
@ -49,7 +49,7 @@ class ValidateAndPolicyPhasesTest {
|
||||
deps.append("[");
|
||||
for (int i = 0; i < localDeps.length; i++) {
|
||||
if (i > 0) deps.append(",");
|
||||
deps.append("{\"path\":\"").append(localDeps[i]).append("\"}");
|
||||
deps.append("{\"canonPath\":\"").append(localDeps[i]).append("\"}");
|
||||
}
|
||||
deps.append("]");
|
||||
final String json = "{\n \"name\": \"" + name + "\",\n \"version\": \"" + version + "\",\n \"dependencies\": " + deps + "\n}";
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
package p.studio.compiler;
|
||||
|
||||
public class FrontendRegistryService {
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package p.studio.utilities.logs;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface LogAggregator {
|
||||
static LogAggregator with(final Consumer<String> consumer) {
|
||||
return new LogAggregatorImpl(consumer);
|
||||
}
|
||||
|
||||
LogAggregator using(Logger logger);
|
||||
void trace(String msg);
|
||||
void debug(String msg);
|
||||
void info(String msg);
|
||||
void warn(String msg);
|
||||
void error(String msg);
|
||||
void error(String msg, Throwable t);
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package p.studio.utilities.logs;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class LogAggregatorImpl implements LogAggregator {
|
||||
private final static String LINE_PATTERN = "%s %s \n";
|
||||
|
||||
private final Consumer<String> logConsumer;
|
||||
private final Map<String, LogAggregator> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public LogAggregatorImpl(final Consumer<String> logConsumer) {
|
||||
this.logConsumer = logConsumer;
|
||||
}
|
||||
|
||||
private void flush(final String kind, final String msg) {
|
||||
logConsumer.accept(LINE_PATTERN.formatted(kind, msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
flush("[TRACE]", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg) {
|
||||
flush("[DEBUG]", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg) {
|
||||
flush("[INFO]", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg) {
|
||||
flush("[WARN]", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
flush("[ERR]", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg, Throwable t) {
|
||||
error(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogAggregator using(final Logger logger) {
|
||||
return cache
|
||||
.computeIfAbsent(logger.getName(), __ -> buildNewLogAggregator(logger));
|
||||
}
|
||||
|
||||
private LogAggregator buildNewLogAggregator(Logger logger) {
|
||||
final var aggregator = this;
|
||||
return new LogAggregator() {
|
||||
@Override
|
||||
public LogAggregator using(Logger logger) {
|
||||
return aggregator.using(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
logger.trace(msg);
|
||||
aggregator.trace(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg) {
|
||||
logger.debug(msg);
|
||||
aggregator.debug(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg) {
|
||||
logger.info(msg);
|
||||
aggregator.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg) {
|
||||
logger.warn(msg);
|
||||
aggregator.warn(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
logger.error(msg);
|
||||
aggregator.error(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg, Throwable t) {
|
||||
logger.error(msg, t);
|
||||
aggregator.error(msg, t);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":prometeu-infra"))
|
||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||
implementation(project(":prometeu-compiler:prometeu-deps"))
|
||||
implementation(libs.javafx.controls)
|
||||
implementation(libs.javafx.fxml)
|
||||
implementation(libs.richtextfx)
|
||||
|
||||
@ -19,10 +19,15 @@ public enum I18n {
|
||||
TOOLBAR_EXPORT("toolbar.export"),
|
||||
|
||||
WORKSPACE_CODE("workspace.code"),
|
||||
WORKSPACE_ASSETS("workspace.assets"),
|
||||
WORKSPACE_DEBUG("workspace.debug"),
|
||||
|
||||
;
|
||||
WORKSPACE_BUILDER("workspace.builder"),
|
||||
WORKSPACE_BUILDER_LOGS("workspace.builder.logs"),
|
||||
WORKSPACE_BUILDER_BUTTON_RUN("workspace.builder.button.run"),
|
||||
|
||||
WORKSPACE_ASSETS("workspace.assets"),
|
||||
WORKSPACE_DEBUG("workspace.debug");
|
||||
|
||||
|
||||
|
||||
@Getter
|
||||
private final String key;
|
||||
|
||||
@ -4,6 +4,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import p.studio.workspaces.PlaceholderWorkspace;
|
||||
import p.studio.workspaces.WorkspaceHost;
|
||||
import p.studio.workspaces.WorkspaceId;
|
||||
import p.studio.workspaces.builder.BuilderWorkspace;
|
||||
import p.studio.workspaces.editor.EditorWorkspace;
|
||||
|
||||
public final class MainView extends BorderPane {
|
||||
@ -15,7 +16,7 @@ public final class MainView extends BorderPane {
|
||||
|
||||
HOST.register(new EditorWorkspace());
|
||||
HOST.register(new PlaceholderWorkspace(WorkspaceId.ASSETS, "Assets"));
|
||||
HOST.register(new PlaceholderWorkspace(WorkspaceId.BUILD, "Build"));
|
||||
HOST.register(new BuilderWorkspace());
|
||||
HOST.register(new PlaceholderWorkspace(WorkspaceId.DEVICE, "Device"));
|
||||
|
||||
var bar = new WorkspaceBar(HOST::show);
|
||||
|
||||
@ -21,7 +21,7 @@ public final class WorkspaceBar extends VBox {
|
||||
|
||||
addBtn(WorkspaceId.EDITOR, "📝", "Editor", onSelect);
|
||||
addBtn(WorkspaceId.ASSETS, "📦", "Assets", onSelect);
|
||||
addBtn(WorkspaceId.BUILD, "⚙️", "Build", onSelect);
|
||||
addBtn(WorkspaceId.BUILDER, "⚙️", "Builder", onSelect);
|
||||
addBtn(WorkspaceId.DEVICE, "🎮", "Device", onSelect);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,6 @@ package p.studio.workspaces;
|
||||
public enum WorkspaceId {
|
||||
EDITOR,
|
||||
ASSETS,
|
||||
BUILD,
|
||||
BUILDER,
|
||||
DEVICE
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package p.studio.workspaces.builder;
|
||||
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import p.studio.Container;
|
||||
import p.studio.compiler.model.DependencyPipelineConfig;
|
||||
import p.studio.compiler.workspaces.DependencyPipelineService;
|
||||
import p.studio.utilities.i18n.I18n;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
import p.studio.workspaces.Workspace;
|
||||
import p.studio.workspaces.WorkspaceId;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
public class BuilderWorkspace implements Workspace {
|
||||
private final BorderPane root = new BorderPane();
|
||||
private final TextArea logs = new TextArea();
|
||||
private final Button buildButton = new Button();
|
||||
|
||||
@Override
|
||||
public WorkspaceId id() {
|
||||
return WorkspaceId.BUILDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public I18n title() {
|
||||
return I18n.WORKSPACE_BUILDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public BuilderWorkspace() {
|
||||
final var toolbar = buildToolBar();
|
||||
root.setTop(toolbar);
|
||||
|
||||
final var projectArea = buildProjectArea();
|
||||
final var logsPane = buildLogsPane();
|
||||
|
||||
final var split = new SplitPane(projectArea, logsPane);
|
||||
split.setOrientation(Orientation.VERTICAL);
|
||||
split.setDividerPositions(0.5);
|
||||
|
||||
root.setCenter(split);
|
||||
}
|
||||
|
||||
private ToolBar buildToolBar() {
|
||||
buildButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_BUILDER_BUTTON_RUN));
|
||||
buildButton.setOnAction(e -> {
|
||||
final var rootPath = Paths.get("../test-projects/main").toAbsolutePath();
|
||||
final var cfg = new DependencyPipelineConfig(false, rootPath, List.of());
|
||||
final var logAggregator = LogAggregator.with(logs::appendText);
|
||||
final var resolvedWorkspace = DependencyPipelineService.INSTANCE.run(cfg, logAggregator);
|
||||
for (final var p : resolvedWorkspace.graph().projects()) {
|
||||
logs.appendText("Project [ " + p.getName() + " ] read\n");
|
||||
}
|
||||
});
|
||||
return new ToolBar(buildButton);
|
||||
}
|
||||
|
||||
private StackPane buildProjectArea() {
|
||||
final var tmpLabel = new Label("Builder project area (WIP)");
|
||||
return new StackPane(tmpLabel);
|
||||
}
|
||||
|
||||
private TitledPane buildLogsPane() {
|
||||
logs.setEditable(false);
|
||||
logs.setWrapText(true);
|
||||
logs.setMinHeight(350);
|
||||
|
||||
final var pane = new TitledPane();
|
||||
pane.setContent(logs);
|
||||
pane.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_BUILDER_LOGS));
|
||||
pane.setCollapsible(true);
|
||||
pane.setExpanded(true);
|
||||
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
@ -12,5 +12,9 @@ toolbar.play=Play
|
||||
toolbar.stop=Stop
|
||||
toolbar.export=Export
|
||||
workspace.code=Code
|
||||
workspace.builder=Builder
|
||||
workspace.builder.logs=Logs
|
||||
workspace.builder.button.run=Build
|
||||
|
||||
workspace.assets=Assets
|
||||
workspace.debug=Debug
|
||||
@ -12,5 +12,8 @@ toolbar.play=Executar
|
||||
toolbar.stop=Parar
|
||||
toolbar.export=Exportar
|
||||
workspace.code=Código
|
||||
workspace.builder=Construtor
|
||||
workspace.builder.logs=Logs
|
||||
workspace.builder.button.run=Construir
|
||||
workspace.assets=Assets
|
||||
workspace.debug=Depurar
|
||||
16
test-projects/dep1/prometeu.json
Normal file
16
test-projects/dep1/prometeu.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "dep1",
|
||||
"version": "1.0.0",
|
||||
"language": "pbs",
|
||||
"dependencies": [
|
||||
{
|
||||
"path": "../dep2"
|
||||
},
|
||||
{
|
||||
"path": "../sdk"
|
||||
},
|
||||
{
|
||||
"path": "../main"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
test-projects/dep2/prometeu.json
Normal file
13
test-projects/dep2/prometeu.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "dep2",
|
||||
"version": "1.0.0",
|
||||
"language": "pbs",
|
||||
"dependencies": [
|
||||
{
|
||||
"path": "../dep1"
|
||||
},
|
||||
{
|
||||
"path": "../sdk"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
test-projects/main/prometeu.json
Normal file
13
test-projects/main/prometeu.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "main",
|
||||
"version": "1.0.0",
|
||||
"language": "pbs",
|
||||
"dependencies": [
|
||||
{
|
||||
"path": "../dep1"
|
||||
},
|
||||
{
|
||||
"path": "../sdk"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
test-projects/sdk/prometeu.json
Normal file
7
test-projects/sdk/prometeu.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "1.0.0",
|
||||
"language": "pbs",
|
||||
"dependencies": [
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user