dev/multi-frontend-provider-contract #14
@ -10,7 +10,7 @@
|
|||||||
{"type":"discussion","id":"DSC-0057","status":"open","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[{"id":"AGD-0060","file":"AGD-0060-multi-frontend-frontend-backend-contract.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0057","status":"open","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[{"id":"AGD-0060","file":"AGD-0060-multi-frontend-frontend-backend-contract.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0056","status":"open","ticket":"multi-frontend-remove-pbs-branches","title":"Remover verificacoes explicitas de PBS do codigo comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[{"id":"AGD-0059","file":"AGD-0059-multi-frontend-remove-pbs-branches.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0056","status":"open","ticket":"multi-frontend-remove-pbs-branches","title":"Remover verificacoes explicitas de PBS do codigo comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[{"id":"AGD-0059","file":"AGD-0059-multi-frontend-remove-pbs-branches.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0055","status":"open","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[{"id":"AGD-0058","file":"AGD-0058-multi-frontend-compiler-vs-language-services.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0055","status":"open","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[{"id":"AGD-0058","file":"AGD-0058-multi-frontend-compiler-vs-language-services.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0054","status":"in_progress","ticket":"multi-frontend-provider-contract","title":"Introduzir provider completo de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","registry","multi-frontend"],"agendas":[{"id":"AGD-0057","file":"AGD-0057-multi-frontend-provider-contract.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0040","file":"DEC-0040-frontendprovider-explicito-como-contrato-de-frontend.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0057"}],"plans":[{"id":"PLN-0103","file":"PLN-0103-specify-frontend-provider-and-registry-contract.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0104","file":"PLN-0104-introduce-frontendprovider-registry-api.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0105","file":"PLN-0105-wrap-pbs-frontend-as-explicit-provider.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0106","file":"PLN-0106-migrate-common-consumers-to-provider-lookup.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0054","status":"in_progress","ticket":"multi-frontend-provider-contract","title":"Introduzir provider completo de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","registry","multi-frontend"],"agendas":[{"id":"AGD-0057","file":"AGD-0057-multi-frontend-provider-contract.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0040","file":"DEC-0040-frontendprovider-explicito-como-contrato-de-frontend.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0057"}],"plans":[{"id":"PLN-0103","file":"PLN-0103-specify-frontend-provider-and-registry-contract.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0104","file":"PLN-0104-introduce-frontendprovider-registry-api.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0105","file":"PLN-0105-wrap-pbs-frontend-as-explicit-provider.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0106","file":"PLN-0106-migrate-common-consumers-to-provider-lookup.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0053","status":"open","ticket":"pbs-lsp-call-and-type-hierarchy","title":"PBS LSP Call and Type Hierarchy","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","call-hierarchy","type-hierarchy"],"agendas":[{"id":"AGD-0056","file":"AGD-0056-pbs-lsp-call-and-type-hierarchy.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0053","status":"open","ticket":"pbs-lsp-call-and-type-hierarchy","title":"PBS LSP Call and Type Hierarchy","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","call-hierarchy","type-hierarchy"],"agendas":[{"id":"AGD-0056","file":"AGD-0056-pbs-lsp-call-and-type-hierarchy.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0052","status":"open","ticket":"pbs-lsp-document-links","title":"PBS LSP Document Links","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","document-links","imports"],"agendas":[{"id":"AGD-0055","file":"AGD-0055-pbs-lsp-document-links.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0052","status":"open","ticket":"pbs-lsp-document-links","title":"PBS LSP Document Links","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","document-links","imports"],"agendas":[{"id":"AGD-0055","file":"AGD-0055-pbs-lsp-document-links.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0051","status":"open","ticket":"pbs-lsp-diagnostics-ux","title":"PBS LSP Diagnostics UX","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","diagnostics","ux"],"agendas":[{"id":"AGD-0054","file":"AGD-0054-pbs-lsp-diagnostics-ux.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0051","status":"open","ticket":"pbs-lsp-diagnostics-ux","title":"PBS LSP Diagnostics UX","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","diagnostics","ux"],"agendas":[{"id":"AGD-0054","file":"AGD-0054-pbs-lsp-diagnostics-ux.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0106
|
id: PLN-0106
|
||||||
ticket: multi-frontend-provider-contract
|
ticket: multi-frontend-provider-contract
|
||||||
title: Migrate common consumers to provider lookup
|
title: Migrate common consumers to provider lookup
|
||||||
status: open
|
status: done
|
||||||
created: 2026-07-15
|
created: 2026-07-15
|
||||||
ref_decisions: [DEC-0040]
|
ref_decisions: [DEC-0040]
|
||||||
tags: [compiler, compiler-general, studio, frontend, registry, multi-frontend]
|
tags: [compiler, compiler-general, studio, frontend, registry, multi-frontend]
|
||||||
|
|||||||
@ -17,13 +17,7 @@ public class FrontendPhasePipelineStage implements PipelineStage {
|
|||||||
@Override
|
@Override
|
||||||
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
|
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
|
||||||
final var frontedSpec = ctx.resolvedWorkspace.frontendSpec();
|
final var frontedSpec = ctx.resolvedWorkspace.frontendSpec();
|
||||||
final var service = FrontendRegistryService.getFrontendPhaseService(frontedSpec.getLanguageId());
|
final var provider = FrontendRegistryService.require(frontedSpec.getLanguageId());
|
||||||
if (service.isEmpty()) {
|
|
||||||
return BuildingIssueSink.empty()
|
|
||||||
.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message("[BUILD]: unable to find a service for frontend phase: " + frontedSpec.getLanguageId()));
|
|
||||||
}
|
|
||||||
final var projectTable = ctx.resolvedWorkspace.graph().projectTable();
|
final var projectTable = ctx.resolvedWorkspace.graph().projectTable();
|
||||||
final var fileTable = ctx.fileTable;
|
final var fileTable = ctx.fileTable;
|
||||||
final var frontendPhaseContext = new FrontendPhaseContext(
|
final var frontendPhaseContext = new FrontendPhaseContext(
|
||||||
@ -36,7 +30,7 @@ public class FrontendPhasePipelineStage implements PipelineStage {
|
|||||||
assetSurfaceContextLoader.load(ctx.resolvedWorkspace.mainProject().getRootPath()));
|
assetSurfaceContextLoader.load(ctx.resolvedWorkspace.mainProject().getRootPath()));
|
||||||
final var diagnostics = DiagnosticSink.empty();
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
final var issues = BuildingIssueSink.empty();
|
final var issues = BuildingIssueSink.empty();
|
||||||
ctx.irBackend = service.get().compile(frontendPhaseContext, diagnostics, logs, issues);
|
ctx.irBackend = provider.compiler().compile(frontendPhaseContext, diagnostics, logs, issues);
|
||||||
logs.using(log).debug("Frontend phase completed successfully!");
|
logs.using(log).debug("Frontend phase completed successfully!");
|
||||||
adaptDiagnostics(diagnostics, issues);
|
adaptDiagnostics(diagnostics, issues);
|
||||||
return issues;
|
return issues;
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
package p.studio.compiler.specs;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
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.fail;
|
||||||
|
|
||||||
|
class FrontendProviderBoundaryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void commonModulesMustNotInstantiatePbsFrontendPhaseServiceDirectly() throws IOException {
|
||||||
|
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
|
||||||
|
final var commonSourceRoots = List.of(
|
||||||
|
repoRoot.resolve("prometeu-compiler/prometeu-build-pipeline/src/main/java"),
|
||||||
|
repoRoot.resolve("prometeu-compiler/prometeu-deps/src/main/java"),
|
||||||
|
repoRoot.resolve("prometeu-studio/src/main/java"),
|
||||||
|
repoRoot.resolve("prometeu-app/src/main/java"));
|
||||||
|
|
||||||
|
for (final var sourceRoot : commonSourceRoots) {
|
||||||
|
if (!Files.exists(sourceRoot)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (final var files = Files.walk(sourceRoot)) {
|
||||||
|
final var violations = files
|
||||||
|
.filter(path -> path.toString().endsWith(".java"))
|
||||||
|
.filter(path -> contains(path, "new PBSFrontendPhaseService("))
|
||||||
|
.toList();
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
fail("common modules must resolve frontend compiler behavior through FrontendProvider: " + violations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(final Path path, final String needle) {
|
||||||
|
try {
|
||||||
|
return Files.readString(path).contains(needle);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("failed to read " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path findRepoRoot(final Path start) {
|
||||||
|
var current = start;
|
||||||
|
while (current != null) {
|
||||||
|
if (Files.exists(current.resolve("settings.gradle.kts"))) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
|
fail("unable to locate repository root from " + start);
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,7 +60,7 @@ public final class PrometeuManifestUtils {
|
|||||||
.message("[DEPS]: invalid 'target' \"" + dto.target() + "\": " + manifestPathCanon));
|
.message("[DEPS]: invalid 'target' \"" + dto.target() + "\": " + manifestPathCanon));
|
||||||
}
|
}
|
||||||
final var language = StringUtils.isBlank(dto.language())
|
final var language = StringUtils.isBlank(dto.language())
|
||||||
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
|
? FrontendRegistryService.getDefaultProvider().specification().getLanguageId()
|
||||||
: dto.language();
|
: dto.language();
|
||||||
final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal);
|
final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal);
|
||||||
if (ReadOnlyCollection.isNotEmpty(issuesLocal)) {
|
if (ReadOnlyCollection.isNotEmpty(issuesLocal)) {
|
||||||
|
|||||||
@ -55,23 +55,24 @@ public class DiscoverPhase implements DependencyPhase {
|
|||||||
ctx.rootStdlib = manifest.get().stdlib();
|
ctx.rootStdlib = manifest.get().stdlib();
|
||||||
ctx.target = manifest.get().target();
|
ctx.target = manifest.get().target();
|
||||||
}
|
}
|
||||||
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.get().language());
|
final var frontendProvider = FrontendRegistryService.getProvider(manifest.get().language());
|
||||||
// Returns empty when language is unknown
|
// Returns empty when language is unknown
|
||||||
if (frontendSpec.isEmpty()) {
|
if (frontendProvider.isEmpty()) {
|
||||||
issues.report(builder -> builder
|
issues.report(builder -> builder
|
||||||
.error(true)
|
.error(true)
|
||||||
.message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name()));
|
.message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name()));
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
final var frontendSpec = frontendProvider.get().specification();
|
||||||
// Enforces consistent language across projects
|
// Enforces consistent language across projects
|
||||||
if (Objects.isNull(ctx.frontendSpec)) {
|
if (Objects.isNull(ctx.frontendSpec)) {
|
||||||
ctx.frontendSpec = frontendSpec.get();
|
ctx.frontendSpec = frontendSpec;
|
||||||
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) {
|
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.getLanguageId())) {
|
||||||
// Reports language inconsistency across projects
|
// Reports language inconsistency across projects
|
||||||
issues.report(builder -> builder
|
issues.report(builder -> builder
|
||||||
.error(true)
|
.error(true)
|
||||||
.message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"",
|
.message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"",
|
||||||
manifest.get().name(), frontendSpec.get().getLanguageId(), ctx.frontendSpec.getLanguageId())));
|
manifest.get().name(), frontendSpec.getLanguageId(), ctx.frontendSpec.getLanguageId())));
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
final var projectInfo = ProjectInfo
|
final var projectInfo = ProjectInfo
|
||||||
|
|||||||
@ -6,7 +6,7 @@ dependencies {
|
|||||||
implementation(project(":prometeu-infra"))
|
implementation(project(":prometeu-infra"))
|
||||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||||
|
|
||||||
implementation(project(":prometeu-compiler:prometeu-frontend-api"))
|
api(project(":prometeu-compiler:prometeu-frontend-api"))
|
||||||
|
|
||||||
implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
|
implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,11 @@ public class FrontendRegistryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FrontendSpec getDefaultFrontendSpec() {
|
public static FrontendSpec getDefaultFrontendSpec() {
|
||||||
return require(PBSDefinitions.PBS.getLanguageId()).specification();
|
return getDefaultProvider().specification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrontendProvider getDefaultProvider() {
|
||||||
|
return require(PBSDefinitions.PBS.getLanguageId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<FrontendSpec> getFrontendSpec(final String languageId) {
|
public static Optional<FrontendSpec> getFrontendSpec(final String languageId) {
|
||||||
|
|||||||
@ -33,4 +33,12 @@ class FrontendRegistryServiceTest {
|
|||||||
assertEquals(1, specs.size());
|
assertEquals(1, specs.size());
|
||||||
assertEquals(PBSDefinitions.PBS.getLanguageId(), specs.getFirst().getLanguageId());
|
assertEquals(PBSDefinitions.PBS.getLanguageId(), specs.getFirst().getLanguageId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void providerListingShouldExposeOnlyPbsByDefault() {
|
||||||
|
final var providers = FrontendRegistryService.listProviders();
|
||||||
|
|
||||||
|
assertEquals(1, providers.size());
|
||||||
|
assertEquals(PBSDefinitions.PBS.getLanguageId(), providers.getFirst().specification().getLanguageId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public final class ProjectLanguageCatalogService {
|
public final class ProjectLanguageCatalogService {
|
||||||
public List<ProjectLanguageTemplate> listLanguageTemplates() {
|
public List<ProjectLanguageTemplate> listLanguageTemplates() {
|
||||||
return FrontendRegistryService.listFrontendSpecs().stream()
|
return FrontendRegistryService.listProviders().stream()
|
||||||
.map(this::toTemplate)
|
.map(provider -> toTemplate(provider.specification()))
|
||||||
.sorted(Comparator.comparing(ProjectLanguageTemplate::languageId, String.CASE_INSENSITIVE_ORDER))
|
.sorted(Comparator.comparing(ProjectLanguageTemplate::languageId, String.CASE_INSENSITIVE_ORDER))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package p.studio.projects;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class ProjectLanguageCatalogServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldListLanguagesFromRegisteredProviders() {
|
||||||
|
final var service = new ProjectLanguageCatalogService();
|
||||||
|
|
||||||
|
final var templates = service.listLanguageTemplates();
|
||||||
|
|
||||||
|
assertEquals(1, templates.size());
|
||||||
|
assertEquals("pbs", templates.getFirst().languageId());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user