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-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-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-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":[]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0106
|
||||
ticket: multi-frontend-provider-contract
|
||||
title: Migrate common consumers to provider lookup
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-07-15
|
||||
ref_decisions: [DEC-0040]
|
||||
tags: [compiler, compiler-general, studio, frontend, registry, multi-frontend]
|
||||
|
||||
@ -17,13 +17,7 @@ public class FrontendPhasePipelineStage implements PipelineStage {
|
||||
@Override
|
||||
public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
|
||||
final var frontedSpec = ctx.resolvedWorkspace.frontendSpec();
|
||||
final var service = FrontendRegistryService.getFrontendPhaseService(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 provider = FrontendRegistryService.require(frontedSpec.getLanguageId());
|
||||
final var projectTable = ctx.resolvedWorkspace.graph().projectTable();
|
||||
final var fileTable = ctx.fileTable;
|
||||
final var frontendPhaseContext = new FrontendPhaseContext(
|
||||
@ -36,7 +30,7 @@ public class FrontendPhasePipelineStage implements PipelineStage {
|
||||
assetSurfaceContextLoader.load(ctx.resolvedWorkspace.mainProject().getRootPath()));
|
||||
final var diagnostics = DiagnosticSink.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!");
|
||||
adaptDiagnostics(diagnostics, 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));
|
||||
}
|
||||
final var language = StringUtils.isBlank(dto.language())
|
||||
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
|
||||
? FrontendRegistryService.getDefaultProvider().specification().getLanguageId()
|
||||
: dto.language();
|
||||
final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal);
|
||||
if (ReadOnlyCollection.isNotEmpty(issuesLocal)) {
|
||||
|
||||
@ -55,23 +55,24 @@ public class DiscoverPhase implements DependencyPhase {
|
||||
ctx.rootStdlib = manifest.get().stdlib();
|
||||
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
|
||||
if (frontendSpec.isEmpty()) {
|
||||
if (frontendProvider.isEmpty()) {
|
||||
issues.report(builder -> builder
|
||||
.error(true)
|
||||
.message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name()));
|
||||
return Optional.empty();
|
||||
}
|
||||
final var frontendSpec = frontendProvider.get().specification();
|
||||
// Enforces consistent language across projects
|
||||
if (Objects.isNull(ctx.frontendSpec)) {
|
||||
ctx.frontendSpec = frontendSpec.get();
|
||||
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) {
|
||||
ctx.frontendSpec = frontendSpec;
|
||||
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.getLanguageId())) {
|
||||
// Reports language inconsistency across projects
|
||||
issues.report(builder -> builder
|
||||
.error(true)
|
||||
.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();
|
||||
}
|
||||
final var projectInfo = ProjectInfo
|
||||
|
||||
@ -6,7 +6,7 @@ dependencies {
|
||||
implementation(project(":prometeu-infra"))
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,11 @@ public class FrontendRegistryService {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@ -33,4 +33,12 @@ class FrontendRegistryServiceTest {
|
||||
assertEquals(1, specs.size());
|
||||
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 List<ProjectLanguageTemplate> listLanguageTemplates() {
|
||||
return FrontendRegistryService.listFrontendSpecs().stream()
|
||||
.map(this::toTemplate)
|
||||
return FrontendRegistryService.listProviders().stream()
|
||||
.map(provider -> toTemplate(provider.specification()))
|
||||
.sorted(Comparator.comparing(ProjectLanguageTemplate::languageId, String.CASE_INSENSITIVE_ORDER))
|
||||
.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