Build composable Camino packages with explicit type and source dependencies

This commit is contained in:
Timothy J. Aveni
2026-09-22 14:42:54 -07:00
parent 7173751f37
commit 82a4542746
30 changed files with 736 additions and 420 deletions
+69 -30
View File
@@ -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])),
};
};