some refactor and expanded tables functionality
This commit is contained in:
parent
9170104a12
commit
47a077bffc
@ -25,7 +25,7 @@ public class BuildingIssueSink extends ReadOnlyCollection<BuildingIssue> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuildingIssueSink report(final BuildingIssueSink issues) {
|
public BuildingIssueSink merge(final BuildingIssueSink issues) {
|
||||||
hasErrors |= issues.hasErrors();
|
hasErrors |= issues.hasErrors();
|
||||||
collection.addAll(issues.collection);
|
collection.addAll(issues.collection);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@ -3,11 +3,13 @@ package p.studio.compiler.source.tables;
|
|||||||
import p.studio.compiler.source.identifiers.SourceIdentifier;
|
import p.studio.compiler.source.identifiers.SourceIdentifier;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class DenseTable<IDENTIFIER extends SourceIdentifier, VALUE> {
|
public abstract class DenseTable<IDENTIFIER extends SourceIdentifier, VALUE>
|
||||||
|
implements DenseTableReader<IDENTIFIER, VALUE>, DenseTableWriter<IDENTIFIER, VALUE> {
|
||||||
private final List<VALUE> values = new ArrayList<>();
|
private final List<VALUE> values = new ArrayList<>();
|
||||||
private final Function<Integer, IDENTIFIER> identifierGenerator;
|
private final Function<Integer, IDENTIFIER> identifierGenerator;
|
||||||
|
|
||||||
@ -15,19 +17,65 @@ public abstract class DenseTable<IDENTIFIER extends SourceIdentifier, VALUE> {
|
|||||||
this.identifierGenerator = Objects.requireNonNull(identifierGenerator);
|
this.identifierGenerator = Objects.requireNonNull(identifierGenerator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public final int size() { return values.size(); }
|
public final int size() { return values.size(); }
|
||||||
|
|
||||||
public final VALUE get(IDENTIFIER id) {
|
@Override
|
||||||
return values.get(id.getIndex());
|
public final VALUE get(final IDENTIFIER identifier) {
|
||||||
|
return values.get(identifier.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDENTIFIER register(VALUE value) {
|
@Override
|
||||||
|
public IDENTIFIER register(final VALUE value) {
|
||||||
final int idx = values.size();
|
final int idx = values.size();
|
||||||
values.add(value);
|
values.add(value);
|
||||||
return identifierGenerator.apply(idx);
|
return identifierGenerator.apply(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void clear() {
|
@Override
|
||||||
|
public void clear() {
|
||||||
values.clear();
|
values.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<IDENTIFIER> identifiers() {
|
||||||
|
return () -> new Iterator<>() {
|
||||||
|
|
||||||
|
private int current = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current < values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IDENTIFIER next() {
|
||||||
|
return identifierGenerator.apply(current++);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<VALUE> values() {
|
||||||
|
return () -> new Iterator<>() {
|
||||||
|
|
||||||
|
private int current = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current < values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VALUE next() {
|
||||||
|
final var identifier = identifierGenerator.apply(current++);
|
||||||
|
return get(identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<IDENTIFIER> iterator() {
|
||||||
|
return identifiers().iterator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package p.studio.compiler.source.tables;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.identifiers.SourceIdentifier;
|
||||||
|
|
||||||
|
public interface DenseTableReader<IDENTIFIER extends SourceIdentifier, VALUE> extends Iterable<IDENTIFIER> {
|
||||||
|
int size();
|
||||||
|
VALUE get(IDENTIFIER identifier);
|
||||||
|
Iterable<IDENTIFIER> identifiers();
|
||||||
|
Iterable<VALUE> values();
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package p.studio.compiler.source.tables;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.identifiers.SourceIdentifier;
|
||||||
|
|
||||||
|
public interface DenseTableWriter<IDENTIFIER extends SourceIdentifier, VALUE> {
|
||||||
|
IDENTIFIER register(VALUE value);
|
||||||
|
void clear();
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ import p.studio.utilities.structures.ReadOnlyList;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class FileTable extends InternTable<FileId, SourceHandle> {
|
public class FileTable extends InternTable<FileId, SourceHandle> implements FileTableReader {
|
||||||
|
|
||||||
private final Map<ProjectId, Set<FileId>> projectFiles = new HashMap<>();
|
private final Map<ProjectId, Set<FileId>> projectFiles = new HashMap<>();
|
||||||
|
|
||||||
@ -23,6 +23,7 @@ public class FileTable extends InternTable<FileId, SourceHandle> {
|
|||||||
return fileId;
|
return fileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public ReadOnlyList<FileId> getFiles(final ProjectId projectId) {
|
public ReadOnlyList<FileId> getFiles(final ProjectId projectId) {
|
||||||
final var fileIds = projectFiles.get(projectId);
|
final var fileIds = projectFiles.get(projectId);
|
||||||
if (CollectionUtils.isEmpty(fileIds)) {
|
if (CollectionUtils.isEmpty(fileIds)) {
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package p.studio.compiler.source.tables;
|
||||||
|
|
||||||
|
import p.studio.compiler.models.SourceHandle;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.ProjectId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
public interface FileTableReader extends DenseTableReader<FileId, SourceHandle> {
|
||||||
|
ReadOnlyList<FileId> getFiles(ProjectId projectId);
|
||||||
|
}
|
||||||
@ -8,11 +8,12 @@ import java.util.Optional;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class InternTable<IDENTIFIER extends SourceIdentifier, VALUE>
|
public abstract class InternTable<IDENTIFIER extends SourceIdentifier, VALUE>
|
||||||
extends DenseTable<IDENTIFIER, VALUE> {
|
extends DenseTable<IDENTIFIER, VALUE>
|
||||||
|
implements InternTableReader<IDENTIFIER, VALUE> {
|
||||||
|
|
||||||
private final Map<VALUE, IDENTIFIER> identifierByValue = new HashMap<>();
|
private final Map<VALUE, IDENTIFIER> identifierByValue = new HashMap<>();
|
||||||
|
|
||||||
public InternTable(Function<Integer, IDENTIFIER> identifierGenerator) {
|
public InternTable(final Function<Integer, IDENTIFIER> identifierGenerator) {
|
||||||
super(identifierGenerator);
|
super(identifierGenerator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,17 +22,19 @@ public abstract class InternTable<IDENTIFIER extends SourceIdentifier, VALUE>
|
|||||||
return identifierByValue.computeIfAbsent(value, super::register);
|
return identifierByValue.computeIfAbsent(value, super::register);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<IDENTIFIER> optional(final VALUE value) {
|
|
||||||
return Optional.ofNullable(identifierByValue.get(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
identifierByValue.clear();
|
identifierByValue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsKey(VALUE value) {
|
@Override
|
||||||
|
public Optional<IDENTIFIER> optional(final VALUE value) {
|
||||||
|
return Optional.ofNullable(identifierByValue.get(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(final VALUE value) {
|
||||||
return identifierByValue.containsKey(value);
|
return identifierByValue.containsKey(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package p.studio.compiler.source.tables;
|
||||||
|
|
||||||
|
import p.studio.compiler.source.identifiers.SourceIdentifier;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface InternTableReader<IDENTIFIER extends SourceIdentifier, VALUE> extends DenseTableReader<IDENTIFIER, VALUE> {
|
||||||
|
Optional<IDENTIFIER> optional(VALUE value);
|
||||||
|
boolean containsKey(VALUE value);
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class ProjectTable extends DenseTable<ProjectId, ProjectDescriptor> {
|
public class ProjectTable extends DenseTable<ProjectId, ProjectDescriptor> implements ProjectTableReader {
|
||||||
|
|
||||||
private final Map<Path, ProjectId> projectIdByPath = new HashMap<>();
|
private final Map<Path, ProjectId> projectIdByPath = new HashMap<>();
|
||||||
|
|
||||||
@ -27,11 +27,13 @@ public class ProjectTable extends DenseTable<ProjectId, ProjectDescriptor> {
|
|||||||
projectIdByPath.clear();
|
projectIdByPath.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ProjectDescriptor> optional(Path pathCanon) {
|
@Override
|
||||||
|
public Optional<ProjectDescriptor> optional(final Path pathCanon) {
|
||||||
return optionalId(pathCanon).map(this::get);
|
return optionalId(pathCanon).map(this::get);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ProjectId> optionalId(Path pathCanon) {
|
@Override
|
||||||
|
public Optional<ProjectId> optionalId(final Path pathCanon) {
|
||||||
return Optional.ofNullable(projectIdByPath.get(pathCanon));
|
return Optional.ofNullable(projectIdByPath.get(pathCanon));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
package p.studio.compiler.source.tables;
|
||||||
|
|
||||||
|
import p.studio.compiler.models.ProjectDescriptor;
|
||||||
|
import p.studio.compiler.source.identifiers.ProjectId;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ProjectTableReader extends DenseTableReader<ProjectId, ProjectDescriptor> {
|
||||||
|
Optional<ProjectDescriptor> optional(Path pathCanon);
|
||||||
|
Optional<ProjectId> optionalId(Path pathCanon);
|
||||||
|
}
|
||||||
@ -1,21 +1,57 @@
|
|||||||
package p.studio.compiler.utilities;
|
package p.studio.compiler.utilities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import p.studio.compiler.FrontendRegistryService;
|
||||||
import p.studio.compiler.dtos.PrometeuManifestDTO;
|
import p.studio.compiler.dtos.PrometeuManifestDTO;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.PrometeuManifest;
|
||||||
|
import p.studio.compiler.workspaces.phases.DependencyResolver;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyCollection;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
public final class PrometeuManifestUtils {
|
public final class PrometeuManifestUtils {
|
||||||
public static final PrometeuManifestUtils INSTANCE = new PrometeuManifestUtils();
|
|
||||||
|
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
private final static ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
public PrometeuManifestDTO read(Path manifestPath) {
|
static PrometeuManifestDTO read(Path manifestPath) {
|
||||||
try {
|
try {
|
||||||
return mapper.readValue(manifestPath.toFile(), PrometeuManifestDTO.class);
|
return MAPPER.readValue(manifestPath.toFile(), PrometeuManifestDTO.class);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException("failed to read manifest " + manifestPath, e);
|
throw new IllegalStateException("failed to read manifest " + manifestPath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<PrometeuManifest> buildManifest(
|
||||||
|
final Path mainProjectRootPathCanon,
|
||||||
|
final Path manifestPathCanon,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
|
final var issuesLocal = BuildingIssueSink.empty();
|
||||||
|
final PrometeuManifestDTO dto = read(manifestPathCanon);
|
||||||
|
if (StringUtils.isBlank(dto.name())) {
|
||||||
|
issuesLocal.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: manifest missing 'name': " + manifestPathCanon));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(dto.version())) {
|
||||||
|
issuesLocal.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: manifest missing 'version': " + manifestPathCanon));
|
||||||
|
}
|
||||||
|
final var language = StringUtils.isBlank(dto.language())
|
||||||
|
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
|
||||||
|
: dto.language();
|
||||||
|
final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal);
|
||||||
|
if (ReadOnlyCollection.isNotEmpty(issuesLocal)) {
|
||||||
|
issues.merge(issuesLocal);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, ReadOnlyList.wrap(dependencies)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,86 @@
|
|||||||
|
package p.studio.compiler.workspaces.phases;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import p.studio.compiler.dtos.PrometeuManifestDTO;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.workspaces.DependencyReference;
|
||||||
|
import p.studio.registry.models.ProjectRef;
|
||||||
|
import p.studio.registry.models.Registry;
|
||||||
|
import p.studio.registry.utilities.RegistryStore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public final class DependencyResolver {
|
||||||
|
public static List<DependencyReference> resolveDependencies(
|
||||||
|
final Path mainProjectRootPathCanon,
|
||||||
|
final List<PrometeuManifestDTO.DependencyDeclaration> dependencies,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
|
if (CollectionUtils.isEmpty(dependencies)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
final var registryMaybe = RegistryStore.INSTANCE.load(mainProjectRootPathCanon);
|
||||||
|
if (registryMaybe.isEmpty()) {
|
||||||
|
issues.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: failed to load registry from " + mainProjectRootPathCanon));
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
final var registry = registryMaybe.get();
|
||||||
|
|
||||||
|
final var deps = new ArrayList<DependencyReference>(dependencies.size());
|
||||||
|
for (var dependency : dependencies) {
|
||||||
|
|
||||||
|
// Resolves each dependency based on its declaration type
|
||||||
|
switch (dependency) {
|
||||||
|
case PrometeuManifestDTO.DependencyDeclaration.Local loc -> resolveLocal(loc, mainProjectRootPathCanon, issues, deps);
|
||||||
|
case PrometeuManifestDTO.DependencyDeclaration.Registry reg -> resolveRegistry(reg, registry, issues, deps);
|
||||||
|
default -> issues.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: unknown dependency declaration type: " + dependency.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolves local dependency path; reports canonicalization errors
|
||||||
|
private static void resolveLocal(
|
||||||
|
final PrometeuManifestDTO.DependencyDeclaration.Local loc,
|
||||||
|
final Path rootProjectCanonPath,
|
||||||
|
final BuildingIssueSink issues,
|
||||||
|
final List<DependencyReference> deps) {
|
||||||
|
final var pathResolve = rootProjectCanonPath.resolve(loc.path());
|
||||||
|
try {
|
||||||
|
final Path pathCanon = pathResolve.toRealPath();
|
||||||
|
deps.add(new DependencyReference(pathCanon));
|
||||||
|
} catch (IOException e) {
|
||||||
|
issues.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: failed to canonicalize dependency path: " + pathResolve)
|
||||||
|
.exception(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the registry for a dependency path; reports missing path errors
|
||||||
|
private static void resolveRegistry(
|
||||||
|
final PrometeuManifestDTO.DependencyDeclaration.Registry reg,
|
||||||
|
final Registry registry,
|
||||||
|
final BuildingIssueSink issues,
|
||||||
|
final List<DependencyReference> deps) {
|
||||||
|
final var ref = new ProjectRef(reg.name(), reg.version());
|
||||||
|
final var canonPath = registry.optional(ref);
|
||||||
|
if (canonPath.isEmpty()) {
|
||||||
|
issues.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.message("[DEPS]: registry dependency not found: " + ref));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deps.add(new DependencyReference(canonPath.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,180 +1,106 @@
|
|||||||
package p.studio.compiler.workspaces.phases;
|
package p.studio.compiler.workspaces.phases;
|
||||||
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import p.studio.compiler.FrontendRegistryService;
|
import p.studio.compiler.FrontendRegistryService;
|
||||||
import p.studio.compiler.dtos.PrometeuManifestDTO;
|
|
||||||
import p.studio.compiler.messages.BuildingIssueSink;
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
import p.studio.compiler.models.DependencyContext;
|
import p.studio.compiler.models.DependencyContext;
|
||||||
import p.studio.compiler.models.ProjectInfo;
|
import p.studio.compiler.models.ProjectInfo;
|
||||||
import p.studio.compiler.models.ProjectInfoId;
|
import p.studio.compiler.models.ProjectInfoId;
|
||||||
import p.studio.compiler.models.PrometeuManifest;
|
|
||||||
import p.studio.compiler.utilities.PrometeuManifestUtils;
|
import p.studio.compiler.utilities.PrometeuManifestUtils;
|
||||||
import p.studio.compiler.workspaces.DependencyPhase;
|
import p.studio.compiler.workspaces.DependencyPhase;
|
||||||
import p.studio.compiler.workspaces.DependencyReference;
|
|
||||||
import p.studio.registry.models.ProjectRef;
|
|
||||||
import p.studio.registry.utilities.RegistryStore;
|
|
||||||
import p.studio.utilities.structures.ReadOnlyCollection;
|
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
public class DiscoverPhase implements DependencyPhase {
|
public class DiscoverPhase implements DependencyPhase {
|
||||||
@Override
|
@Override
|
||||||
public BuildingIssueSink run(final DependencyContext ctx) {
|
public BuildingIssueSink run(final DependencyContext ctx) {
|
||||||
final Map<Path, ProjectInfoId> projectIndexByDirectory = new HashMap<>();
|
final var projectsSeen = new HashSet<Path>();
|
||||||
final BuildingIssueSink issues = BuildingIssueSink.empty();
|
final var issues = BuildingIssueSink.empty();
|
||||||
// Discovers projects; registers them; adds dependencies to queue
|
// Discovers projects; registers them; adds dependencies to queue
|
||||||
while (!ctx.pending.isEmpty()) {
|
while (!ctx.pending.isEmpty()) {
|
||||||
final var rootPathCanon = ctx.pending.pollFirst();
|
final var rootPathCanon = ctx.pending.pollFirst();
|
||||||
|
if (projectsSeen.contains(rootPathCanon)) {
|
||||||
if (projectIndexByDirectory.containsKey(rootPathCanon)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
discoverOne(ctx, rootPathCanon, issues).ifPresent(projectInfoId -> {
|
||||||
final Path manifestPathCanon;
|
projectsSeen.add(rootPathCanon);
|
||||||
try {
|
final var projectInfo = ctx.projectInfoTable.get(projectInfoId);
|
||||||
manifestPathCanon = rootPathCanon.resolve("prometeu.json").toRealPath();
|
projectInfo.manifest.dependencies().forEach(dep -> ctx.pending.add(dep.canonPath()));
|
||||||
} catch (IOException e) {
|
ctx.projectNameAndVersions
|
||||||
issues.report(builder -> builder
|
.computeIfAbsent(projectInfo.manifest.name(), ignored -> new HashSet<>())
|
||||||
.error(true)
|
.add(projectInfo.manifest.version());
|
||||||
.message("[DEPS]: manifest canonPath does not exist: " + rootPathCanon)
|
});
|
||||||
.exception(e));
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (!Files.exists(manifestPathCanon) || !Files.isRegularFile(manifestPathCanon)) {
|
|
||||||
issues.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var prometeuManifestDTO = PrometeuManifestUtils.INSTANCE.read(manifestPathCanon);
|
|
||||||
final var manifestMaybe = map(ctx.mainProjectRootPathCanon, prometeuManifestDTO, issues);
|
|
||||||
|
|
||||||
if (manifestMaybe.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var manifest = manifestMaybe.get();
|
|
||||||
|
|
||||||
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.language());
|
|
||||||
if (frontendSpec.isEmpty()) {
|
|
||||||
issues.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message("[DEPS]: unknown language " + manifest.language() + " for project " + manifest.name()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Objects.isNull(ctx.frontendSpec)) {
|
|
||||||
ctx.frontendSpec = frontendSpec.get();
|
|
||||||
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) {
|
|
||||||
issues.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"",
|
|
||||||
manifest.name(), frontendSpec.get().getLanguageId(), ctx.frontendSpec.getLanguageId())));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var projectInfo = ProjectInfo
|
|
||||||
.builder()
|
|
||||||
.rootDirectory(rootPathCanon)
|
|
||||||
.manifestPath(manifestPathCanon)
|
|
||||||
.manifest(manifest)
|
|
||||||
.build();
|
|
||||||
final var projectInfoId = ctx.projectInfoTable.register(projectInfo);
|
|
||||||
projectIndexByDirectory.put(rootPathCanon, projectInfoId);
|
|
||||||
|
|
||||||
manifest.dependencies().forEach(depRef -> ctx.pending.add(depRef.canonPath()));
|
|
||||||
|
|
||||||
ctx.projectNameAndVersions.computeIfAbsent(manifest.name(), ignore -> new HashSet<>()).add(manifest.version());
|
|
||||||
}
|
|
||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<PrometeuManifest> map(
|
private Optional<ProjectInfoId> discoverOne(
|
||||||
final Path rootPath,
|
final DependencyContext ctx,
|
||||||
final PrometeuManifestDTO dto,
|
final Path rootPathCanon,
|
||||||
final BuildingIssueSink issues) {
|
final BuildingIssueSink issues) {
|
||||||
|
final var manifestPathCanon = canonicalizeManifestPath(rootPathCanon, issues);
|
||||||
if (StringUtils.isBlank(dto.name())) {
|
if (manifestPathCanon.isEmpty()) {
|
||||||
issues.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message("[DEPS]: manifest missing 'name': " + rootPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(dto.version())) {
|
|
||||||
issues.report(builder -> builder
|
|
||||||
.error(true)
|
|
||||||
.message("[DEPS]: manifest missing 'version': " + rootPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
final var language = StringUtils.isBlank(dto.language())
|
|
||||||
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
|
|
||||||
: dto.language();
|
|
||||||
|
|
||||||
final var dependencies = resolveDependencies(rootPath, dto.dependencies(), issues);
|
|
||||||
|
|
||||||
if (ReadOnlyCollection.isNotEmpty(issues)) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
final var manifest = PrometeuManifestUtils.buildManifest(ctx.mainProjectRootPathCanon, manifestPathCanon.get(), issues);
|
||||||
return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, ReadOnlyList.wrap(dependencies)));
|
if (manifest.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.get().language());
|
||||||
private static List<DependencyReference> resolveDependencies(
|
// Returns empty when language is unknown
|
||||||
final Path rootProjectCanonPath,
|
if (frontendSpec.isEmpty()) {
|
||||||
final List<PrometeuManifestDTO.DependencyDeclaration> dependencies,
|
|
||||||
final BuildingIssueSink issues) {
|
|
||||||
if (CollectionUtils.isEmpty(dependencies)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
final var registryMaybe = RegistryStore.INSTANCE.load(rootProjectCanonPath);
|
|
||||||
if (registryMaybe.isEmpty()) {
|
|
||||||
issues.report(builder -> builder
|
issues.report(builder -> builder
|
||||||
.error(true)
|
.error(true)
|
||||||
.message("[DEPS]: failed to load registry from " + rootProjectCanonPath));
|
.message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name()));
|
||||||
return List.of();
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
// Enforces consistent language across projects
|
||||||
|
if (Objects.isNull(ctx.frontendSpec)) {
|
||||||
|
ctx.frontendSpec = frontendSpec.get();
|
||||||
|
} else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().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())));
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final var projectInfo = ProjectInfo
|
||||||
|
.builder()
|
||||||
|
.rootDirectory(rootPathCanon)
|
||||||
|
.manifestPath(manifestPathCanon.get())
|
||||||
|
.manifest(manifest.get())
|
||||||
|
.build();
|
||||||
|
final var projectInfoId = ctx.projectInfoTable.register(projectInfo);
|
||||||
|
return Optional.of(projectInfoId);
|
||||||
}
|
}
|
||||||
final var registry = registryMaybe.get();
|
|
||||||
|
|
||||||
final var deps = new ArrayList<DependencyReference>(dependencies.size());
|
private static Optional<Path> canonicalizeManifestPath(
|
||||||
for (var dependency : dependencies) {
|
final Path rootPathCanon,
|
||||||
|
final BuildingIssueSink issues) {
|
||||||
// Resolves each dependency based on its declaration type
|
final var manifestPath = rootPathCanon.resolve("prometeu.json");
|
||||||
switch (dependency) {
|
final Path manifestPathCanon;
|
||||||
|
// Resolves manifest path; reports errors on failure
|
||||||
case PrometeuManifestDTO.DependencyDeclaration.Local loc -> {
|
|
||||||
// Resolves local dependency path; reports canonicalization errors
|
|
||||||
final var pathResolve = rootProjectCanonPath.resolve(loc.path());
|
|
||||||
try {
|
try {
|
||||||
final Path pathCanon = pathResolve.toRealPath();
|
manifestPathCanon = manifestPath.toRealPath();
|
||||||
deps.add(new DependencyReference(pathCanon));
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
issues.report(builder -> builder
|
issues.report(builder -> builder
|
||||||
.error(true)
|
.error(true)
|
||||||
.message("[DEPS]: failed to canonicalize dependency path: " + pathResolve)
|
.message("[DEPS]: manifest canonPath does not exist: " + manifestPath)
|
||||||
.exception(e));
|
.exception(e));
|
||||||
}
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
if (!Files.isRegularFile(manifestPathCanon)) {
|
||||||
case PrometeuManifestDTO.DependencyDeclaration.Registry reg -> {
|
issues.report(builder -> builder
|
||||||
// Looks up the registry for a dependency path; reports missing path errors
|
.error(true)
|
||||||
final var ref = new ProjectRef(reg.name(), reg.version());
|
.message("[DEPS]: manifest not found: expected a file " + manifestPathCanon));
|
||||||
registry.optional(ref).ifPresent(pathCanon -> deps.add(new DependencyReference(pathCanon)));
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
return Optional.of(manifestPathCanon);
|
||||||
default -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,8 +25,7 @@ public final class WireProjectsPhase implements DependencyPhase {
|
|||||||
|
|
||||||
final BuildingIssueSink issues = BuildingIssueSink.empty();
|
final BuildingIssueSink issues = BuildingIssueSink.empty();
|
||||||
|
|
||||||
for (int index = 0; index < ctx.projectInfoTable.size(); index++) {
|
for (final var projectInfo : ctx.projectInfoTable.values()) {
|
||||||
final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(index));
|
|
||||||
final var projectDescriptor = buildProjectDescriptor(ctx, projectInfo, issues);
|
final var projectDescriptor = buildProjectDescriptor(ctx, projectInfo, issues);
|
||||||
final var projectId = ctx.projectTable.register(projectDescriptor);
|
final var projectId = ctx.projectTable.register(projectDescriptor);
|
||||||
ctx.projectIds.add(projectId);
|
ctx.projectIds.add(projectId);
|
||||||
@ -82,7 +81,7 @@ public final class WireProjectsPhase implements DependencyPhase {
|
|||||||
}
|
}
|
||||||
if (sourceRootIssues.size() == ctx.frontendSpec.getSourceRoots().size()) {
|
if (sourceRootIssues.size() == ctx.frontendSpec.getSourceRoots().size()) {
|
||||||
// no source roots were found at all
|
// no source roots were found at all
|
||||||
issues.report(sourceRootIssues);
|
issues.merge(sourceRootIssues);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProjectDescriptor
|
return ProjectDescriptor
|
||||||
|
|||||||
@ -10,4 +10,9 @@ public record ProjectRef(String name, String version) {
|
|||||||
final var version = ps[1];
|
final var version = ps[1];
|
||||||
return Optional.of(new ProjectRef(name, version));
|
return Optional.of(new ProjectRef(name, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + "@" + version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user