add logs and better workflow iteraction with it

This commit is contained in:
bQUARKz 2026-02-23 09:25:15 +00:00
parent 5ee28101a3
commit f61200f8ef
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
31 changed files with 474 additions and 72 deletions

View File

@ -6,6 +6,7 @@ import lombok.Getter;
@Builder
@Getter
public class BuildingIssue {
private final boolean error;
private final String message;
private final Throwable exception;
}

View File

@ -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,

View File

@ -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();
}

View File

@ -2,5 +2,5 @@ package p.studio.compiler.workspaces;
import java.nio.file.Path;
public record DependencyReference(Path path) {
public record DependencyReference(Path canonPath) {
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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}";

View File

@ -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 + "\"";

View File

@ -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}";

View File

@ -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}";

View File

@ -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}";

View File

@ -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}";

View File

@ -1,4 +0,0 @@
package p.studio.compiler;
public class FrontendRegistryService {
}

View File

@ -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);
}

View File

@ -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);
}
};
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -3,6 +3,6 @@ package p.studio.workspaces;
public enum WorkspaceId {
EDITOR,
ASSETS,
BUILD,
BUILDER,
DEVICE
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,16 @@
{
"name": "dep1",
"version": "1.0.0",
"language": "pbs",
"dependencies": [
{
"path": "../dep2"
},
{
"path": "../sdk"
},
{
"path": "../main"
}
]
}

View File

@ -0,0 +1,13 @@
{
"name": "dep2",
"version": "1.0.0",
"language": "pbs",
"dependencies": [
{
"path": "../dep1"
},
{
"path": "../sdk"
}
]
}

View File

@ -0,0 +1,13 @@
{
"name": "main",
"version": "1.0.0",
"language": "pbs",
"dependencies": [
{
"path": "../dep1"
},
{
"path": "../sdk"
}
]
}

View File

@ -0,0 +1,7 @@
{
"name": "sdk",
"version": "1.0.0",
"language": "pbs",
"dependencies": [
]
}