Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vast-kids-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

Print logs of the build process when passing --verbose to the build command. It also uses the buildx-metadata generated to recover the image-id not relying on the stdout regardless of the value passed to the --progress flag.
4 changes: 4 additions & 0 deletions apps/cli/src/builder/directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import fs from "fs-extra";
import path from "node:path";
import type { DirectoryDriveConfig } from "../config.js";
import { genext2fs, mksquashfs } from "../exec/index.js";
import type { Reporter } from "../exec/util.js";

export const build = async (
name: string,
drive: DirectoryDriveConfig,
sdkImage: string,
destination: string,
debug: boolean,
reporter?: Reporter,
): Promise<void> => {
const filename = `${name}.${drive.format}`;

Expand All @@ -26,6 +28,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand All @@ -35,6 +38,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand Down
43 changes: 37 additions & 6 deletions apps/cli/src/builder/docker.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { execa } from "execa";
import fs from "fs-extra";
import path from "node:path";
import tmp from "tmp";
import type { DockerDriveConfig } from "../config.js";
import { genext2fs, mksquashfs } from "../exec/index.js";
import type { Reporter } from "../exec/util.js";
import type { BuildxMetadata } from "../types/docker.js";

type ImageBuildOptions = Pick<
DockerDriveConfig,
"buildArgs" | "context" | "dockerfile" | "tags" | "target"
> & { destination: string; dockerfileContent?: string };
> & { destination: string; dockerfileContent?: string; reporter?: Reporter };

type ImageInfo = {
cmd: string[];
Expand All @@ -26,6 +29,7 @@ const buildImage = async (options: ImageBuildOptions): Promise<string> => {
destination,
dockerfile,
dockerfileContent,
reporter,
tags,
target,
} = options;
Expand All @@ -43,7 +47,7 @@ const buildImage = async (options: ImageBuildOptions): Promise<string> => {
"--output",
`type=tar,dest=${destination}`,
"--progress",
"quiet",
reporter ? "plain" : "quiet",
];

// set tags for the image built
Expand All @@ -52,16 +56,38 @@ const buildImage = async (options: ImageBuildOptions): Promise<string> => {
// set build args
args.push(...buildArgs.flatMap((arg) => ["--build-arg", arg]));

// use --metadata-file to capture the image ID from json format file, so stdout can be safely
// ignored regardless of what --progress mode outputs there
const tmpFile = tmp.tmpNameSync();
args.push("--metadata-file", tmpFile);

if (target) {
args.push("--target", target);
}

args.push(context);

const { stdout: imageId } = await execa("docker", args, {
input: dockerfileContent,
});
return imageId;
if (reporter)
reporter(`Building docker image with args: ${args.join(" ")}`);

const proc = execa("docker", args, { input: dockerfileContent });
if (reporter) {
proc.stderr?.on("data", (chunk: Buffer) => {
for (const line of chunk.toString().split("\n")) {
if (line.trim()) reporter(line.trimEnd());
}
});
}
await proc;
const metadata = JSON.parse(
fs.readFileSync(tmpFile, "utf-8"),
) as BuildxMetadata;

return (
metadata["containerimage.config.digest"] ??
metadata["containerimage.digest"] ??
""
);
};

/**
Expand Down Expand Up @@ -101,6 +127,7 @@ export const build = async (
sdkImage: string,
destination: string,
debug: boolean,
reporter?: Reporter,
): Promise<ImageInfo | undefined> => {
const { format } = drive;

Expand All @@ -116,11 +143,13 @@ export const build = async (
...drive,
destination: path.join(destination, tar),
dockerfileContent: `FROM ${drive.image}`,
reporter,
});
} else {
image = await buildImage({
...drive,
destination: path.join(destination, tar),
reporter,
});
}
const imageInfo = await getImageInfo(image);
Expand All @@ -134,6 +163,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand All @@ -143,6 +173,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/src/builder/tar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import fs from "fs-extra";
import path from "node:path";
import type { TarDriveConfig } from "../config.js";
import { genext2fs, mksquashfs } from "../exec/index.js";
import type { Reporter } from "../exec/util.js";

export const build = async (
name: string,
drive: TarDriveConfig,
sdkImage: string,
destination: string,
reporter?: Reporter,
): Promise<void> => {
const tar = `${name}.tar`;
const filename = `${name}.${drive.format}`;
Expand All @@ -23,6 +25,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand All @@ -32,6 +35,7 @@ export const build = async (
output: filename,
cwd: destination,
image: sdkImage,
reporter,
});
break;
}
Expand Down
24 changes: 21 additions & 3 deletions apps/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,20 @@ const buildDriveTask = (
task: async (ctx, task) => {
const { config, debug, destination } = ctx;
const sdk = config.sdk;
const reporter = (line: string) => {
task.output = line;
};

switch (drive.builder) {
case "directory": {
await buildDirectory(name, drive, sdk, destination, debug);
await buildDirectory(
name,
drive,
sdk,
destination,
debug,
reporter,
);
break;
}
case "docker": {
Expand All @@ -43,6 +54,7 @@ const buildDriveTask = (
sdk,
destination,
debug,
reporter,
);
if (imageInfo && name === "root") {
// only set image info for root drive
Expand All @@ -55,7 +67,7 @@ const buildDriveTask = (
break;
}
case "tar": {
await buildTar(name, drive, sdk, destination);
await buildTar(name, drive, sdk, destination, reporter);
break;
}
case "none": {
Expand Down Expand Up @@ -104,7 +116,13 @@ export const createBuildCommand = () => {
await fs.emptyDir(destination); // XXX: make it less error prone

// build context
const ctx = { config, debug, destination, imageInfo: undefined };
const ctx = {
config,
debug,
destination,
verbose,
imageInfo: undefined,
};

// tasks to build drives
const driveTasks = Object.entries(config.drives).map(
Expand Down
12 changes: 6 additions & 6 deletions apps/cli/src/exec/genext2fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const empty = (
output: string;
} & DockerFallbackOptions,
) => {
const { size, output } = options;
const { size, output, reporter } = options;
const blocks = Math.ceil(size / BLOCK_SIZE); // size in blocks
return execaDockerFallback(
"xgenext2fs",
Expand All @@ -32,7 +32,7 @@ export const empty = (
blocks.toString(),
output,
],
options,
{ ...options, reporter },
);
};

Expand All @@ -44,12 +44,12 @@ export const fromDirectory = (
output: string;
} & DockerFallbackOptions,
) => {
const { cwd, extraSize, image, input, output } = options;
const { cwd, extraSize, image, input, output, reporter } = options;
const extraBlocks = Math.ceil(extraSize / BLOCK_SIZE);
return execaDockerFallback(
"xgenext2fs",
[...baseArgs({ extraBlocks }), "--root", input, output],
{ cwd, image },
{ cwd, image, reporter },
);
};

Expand All @@ -61,12 +61,12 @@ export const fromTar = (
output: string;
} & DockerFallbackOptions,
) => {
const { cwd, extraSize, image, input, output } = options;
const { cwd, extraSize, image, input, output, reporter } = options;
const extraBlocks = Math.ceil(extraSize / BLOCK_SIZE);
return execaDockerFallback(
"xgenext2fs",
[...baseArgs({ extraBlocks }), "--tarball", input, output],
{ cwd, image },
{ cwd, image, reporter },
);
};

Expand Down
6 changes: 4 additions & 2 deletions apps/cli/src/exec/mksquashfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ export const fromDirectory = (
output: string;
} & DockerFallbackOptions,
) => {
const { cwd, image, input, output } = options;
const { cwd, image, input, output, reporter } = options;
return execaDockerFallback("mksquashfs", [input, output, ...baseArgs()], {
cwd,
image,
reporter,
});
};

Expand All @@ -38,13 +39,14 @@ export const fromTar = (
output: string;
} & DockerFallbackOptions,
) => {
const { cwd, image, input, output } = options;
const { cwd, image, input, output, reporter } = options;
return execaDockerFallback(
"mksquashfs",
["-", output, "-tar", ...baseArgs()],
{
cwd,
image,
reporter,
inputFile: input, // use stdin in case of tar file
},
);
Expand Down
25 changes: 22 additions & 3 deletions apps/cli/src/exec/util.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { ExecaError, execa, type Options } from "execa";
import os from "node:os";

export type Reporter = (line: string) => void;

export type DockerFallbackOptions =
| { image: string; forceDocker: true; tty?: boolean }
| { image?: string; forceDocker?: false; tty?: boolean };
| { image: string; forceDocker: true; tty?: boolean; reporter?: Reporter }
| {
image?: string;
forceDocker?: false;
tty?: boolean;
reporter?: Reporter;
};

/**
* Calls execa and falls back to docker run if command (on the host) fails
Expand All @@ -13,11 +20,21 @@ export type DockerFallbackOptions =
* @returns return of execa
*/
export type ExecaOptionsDockerFallback = Options & DockerFallbackOptions;

const pipeReporter = (proc: ReturnType<typeof execa>, reporter: Reporter) => {
proc.stderr?.on("data", (chunk: Buffer) => {
for (const line of chunk.toString().split("\n")) {
if (line.trim()) reporter(line.trimEnd());
}
});
};

export const execaDockerFallback = async (
command: string,
args: readonly string[],
options: ExecaOptionsDockerFallback,
) => {
const { reporter } = options;
try {
if (options.forceDocker) {
const error = new ExecaError();
Expand All @@ -41,11 +58,13 @@ export const execaDockerFallback = async (
"--user",
`${userInfo.uid}:${userInfo.gid}`,
];
return await execa(
const proc = execa(
"docker",
["run", ...dockerOpts, options.image, command, ...args],
options,
);
if (reporter) pipeReporter(proc, reporter);
return await proc;
}
}
throw error;
Expand Down
24 changes: 24 additions & 0 deletions apps/cli/src/types/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ export type ServicePublisher = {
};

export type PsResponse = ServiceStatus;

/**
* A partial representation of the metadata produced by `docker buildx build --metadata-file`.
*
*/
export type BuildxMetadata = {
/**
* The manifest digest. SHA256 of the manifest JSON, which describes the image's
* layers and points to the config blob.
*/
"containerimage.digest"?: string | null;

/**
* the image id of the built image. SHA256 of the image config JSON,
* which contains the layer diff IDs, env, entrypoint, cmd, etc.
*/
"containerimage.config.digest"?: string | null;

/**
* The name/tag of the image if specified (e.g., via `-t` or `name=...`).
* This is explicitly null if no image name/tag was designated for the build.
*/
"image.name"?: string | null;
};
Loading
Loading