Build composable Camino packages with explicit type and source dependencies
This commit is contained in:
@@ -47,7 +47,7 @@ export type ResolvedCapabilityResource = {
|
||||
source: GitSource;
|
||||
directory: string;
|
||||
lock: QuixosRepositoryLock;
|
||||
resource: CapabilityResource;
|
||||
resource: CapabilityResource | { kind: "build" };
|
||||
dependencies: ReadonlyMap<string, ResolvedCapabilityResource>;
|
||||
};
|
||||
|
||||
@@ -56,13 +56,15 @@ export type CompiledWorkspaceRepository = {
|
||||
plan: CompiledWorkspaceRevision;
|
||||
lock: QuixosRepositoryLock;
|
||||
resources: ResolvedCapabilityResource[];
|
||||
capabilityResources: ResolvedCapabilityResource[];
|
||||
directResources: ReadonlyMap<string, ResolvedCapabilityResource>;
|
||||
};
|
||||
|
||||
export type CompiledCapabilityResourceRepository = {
|
||||
resource: CapabilityResource;
|
||||
resource: CapabilityResource | { kind: "build" };
|
||||
lock: QuixosRepositoryLock;
|
||||
resources: ResolvedCapabilityResource[];
|
||||
capabilityResources: ResolvedCapabilityResource[];
|
||||
directResources: ReadonlyMap<string, ResolvedCapabilityResource>;
|
||||
};
|
||||
|
||||
@@ -79,7 +81,10 @@ const assertImportsMatchLock = (
|
||||
resources: readonly LockedResource[],
|
||||
) => {
|
||||
const authored = [...imports].map(importsKey).sort();
|
||||
const locked = [...resources].map(importsKey).sort();
|
||||
const locked = resources
|
||||
.filter((entry) => entry.kind !== "build")
|
||||
.map(importsKey)
|
||||
.sort();
|
||||
if (authored.length !== locked.length || authored.some((entry, index) => entry !== locked[index])) {
|
||||
throw new Error(
|
||||
`${label} imports do not match quixos.lock:\n` +
|
||||
@@ -137,13 +142,19 @@ const diagnosticsMessage = (
|
||||
})
|
||||
.join("\n")}`;
|
||||
|
||||
const revisionFor = (node: ResolvedCapabilityResource) => node.resource.revision;
|
||||
const revisionFor = (node: ResolvedCapabilityResource) => {
|
||||
if (node.resource.kind === "build") throw Error("Build sources have no capability revision");
|
||||
return node.resource.revision;
|
||||
};
|
||||
|
||||
const resourceClosure = (roots: readonly ResolvedCapabilityResource[]): ResolvedCapabilityResource[] => {
|
||||
const resourceClosure = (
|
||||
roots: readonly ResolvedCapabilityResource[],
|
||||
capabilityOnly = false,
|
||||
): ResolvedCapabilityResource[] => {
|
||||
const result: ResolvedCapabilityResource[] = [];
|
||||
const seen = new Set<string>();
|
||||
const visit = (node: ResolvedCapabilityResource) => {
|
||||
if (seen.has(node.key)) return;
|
||||
if (seen.has(node.key) || (capabilityOnly && node.kind === "build")) return;
|
||||
seen.add(node.key);
|
||||
for (const dependency of node.dependencies.values()) visit(dependency);
|
||||
result.push(node);
|
||||
@@ -155,28 +166,36 @@ const resourceClosure = (roots: readonly ResolvedCapabilityResource[]): Resolved
|
||||
const environmentFor = (
|
||||
direct: readonly [LockedResource, ResolvedCapabilityResource][],
|
||||
closure: readonly ResolvedCapabilityResource[],
|
||||
): CapabilityImportEnvironment => ({
|
||||
interfaces: new Map(
|
||||
direct.flatMap(([locked, node]) =>
|
||||
node.resource.kind === "interface" ? [[locked.binding, node.resource.revision] as const] : [],
|
||||
): CapabilityImportEnvironment => {
|
||||
closure = resourceClosure(
|
||||
direct.map(([, node]) => node),
|
||||
true,
|
||||
);
|
||||
return {
|
||||
interfaces: new Map(
|
||||
direct.flatMap(([locked, node]) =>
|
||||
node.resource.kind === "interface" ? [[locked.binding, node.resource.revision] as const] : [],
|
||||
),
|
||||
),
|
||||
),
|
||||
packages: new Map(
|
||||
direct.flatMap(([locked, node]) =>
|
||||
node.resource.kind === "package" ? [[locked.binding, node.resource.revision] as const] : [],
|
||||
packages: new Map(
|
||||
direct.flatMap(([locked, node]) =>
|
||||
node.resource.kind === "package" ? [[locked.binding, node.resource.revision] as const] : [],
|
||||
),
|
||||
),
|
||||
),
|
||||
interfaceClosure: exactRevisions(
|
||||
closure.flatMap((node) => [
|
||||
...(node.resource.kind === "interface" ? [node.resource.revision] : []),
|
||||
...(node.resource.specializations ?? []),
|
||||
]),
|
||||
),
|
||||
packageClosure: exactRevisions(
|
||||
closure.flatMap((node) => (node.resource.kind === "package" ? [node.resource.revision] : [])),
|
||||
),
|
||||
externalAtoms: exactAtoms(closure.flatMap((node) => node.resource.externalAtoms)),
|
||||
});
|
||||
interfaceClosure: exactRevisions(
|
||||
closure.flatMap((node) => [
|
||||
...(node.resource.kind === "interface" ? [node.resource.revision] : []),
|
||||
...(node.resource.kind === "build" ? [] : (node.resource.specializations ?? [])),
|
||||
]),
|
||||
),
|
||||
packageClosure: exactRevisions(
|
||||
closure.flatMap((node) => (node.resource.kind === "package" ? [node.resource.revision] : [])),
|
||||
),
|
||||
externalAtoms: exactAtoms(
|
||||
closure.flatMap((node) => (node.resource.kind === "build" ? [] : node.resource.externalAtoms)),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const createResourceGraphResolver = (quixosCommit: string, resolveResource: CapabilityRepositoryResolver) => {
|
||||
const resolved = new Map<string, Promise<ResolvedCapabilityResource>>();
|
||||
@@ -218,6 +237,19 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
|
||||
for (const dependency of lockResult.lock.resources) {
|
||||
directPairs.push([dependency, await visit(dependency)]);
|
||||
}
|
||||
if (locked.kind === "build") {
|
||||
return {
|
||||
key,
|
||||
kind: locked.kind,
|
||||
source: locked.source,
|
||||
directory: snapshot.directory,
|
||||
lock: lockResult.lock,
|
||||
resource: { kind: "build" as const },
|
||||
dependencies: new Map(
|
||||
directPairs.map(([dependency, node]) => [bindingKey(dependency.kind, dependency.binding), node]),
|
||||
),
|
||||
};
|
||||
}
|
||||
const dependencyClosure = resourceClosure(directPairs.map(([, node]) => node));
|
||||
const environment = environmentFor(directPairs, dependencyClosure);
|
||||
const manifestName = locked.kind === "interface" ? "interface.qx" : "package.qx";
|
||||
@@ -332,6 +364,7 @@ export const compileCapabilityResourceRepository = async (options: {
|
||||
resource: root.resource,
|
||||
lock: root.lock,
|
||||
resources: await resolver.nodes(),
|
||||
capabilityResources: resourceClosure([root], true),
|
||||
directResources: root.dependencies,
|
||||
};
|
||||
};
|
||||
@@ -376,11 +409,13 @@ export const compileWorkspaceRepository = async (options: {
|
||||
);
|
||||
}
|
||||
assertImportsMatchLock(workspacePath, compiled.imports, rootLock.resources);
|
||||
const availableInterfaceIds = new Set(
|
||||
nodes.flatMap((node) => (node.resource.kind === "interface" ? [node.resource.revision.revisionId] : [])),
|
||||
);
|
||||
const availableInterfaceIds = new Set((environment.interfaceClosure ?? []).map((iface) => iface.revisionId));
|
||||
const availableAtomIds = new Set(compiled.workspace.atoms.map((atom) => atom.id));
|
||||
for (const node of nodes) {
|
||||
for (const node of resourceClosure(
|
||||
directPairs.map(([, node]) => node),
|
||||
true,
|
||||
)) {
|
||||
if (node.resource.kind === "build") continue;
|
||||
for (const requirement of node.resource.externalInterfaces) {
|
||||
if (!availableInterfaceIds.has(requirement.revisionId)) {
|
||||
throw new Error(
|
||||
@@ -430,6 +465,10 @@ export const compileWorkspaceRepository = async (options: {
|
||||
plan: checked.plan,
|
||||
lock: rootLock,
|
||||
resources: nodes,
|
||||
capabilityResources: resourceClosure(
|
||||
directPairs.map(([, node]) => node),
|
||||
true,
|
||||
),
|
||||
directResources: new Map(directPairs.map(([locked, node]) => [bindingKey(locked.kind, locked.binding), node])),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user