Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build from configuration #77

Merged
merged 4 commits into from
Oct 22, 2024
Merged
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/eight-schools-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": major
---

build based on cartesi.toml
1 change: 1 addition & 0 deletions apps/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules
oclif.manifest.json
src/contracts.ts
src/graphql/
test/builder/output
1 change: 1 addition & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"ora": "^8.1.0",
"progress-stream": "^2.0.0",
"semver": "^7.6.3",
"smol-toml": "^1.3.0",
"tmp": "^0.2.3",
"viem": "^2.21.27"
},
Expand Down
7 changes: 7 additions & 0 deletions apps/cli/src/baseCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fs from "fs";
import path from "path";
import { Address, Hash, getAddress, isHash } from "viem";

import { Config, parse } from "./config.js";
import {
applicationFactoryAddress,
authorityFactoryAddress,
Expand Down Expand Up @@ -53,6 +54,12 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
return path.join(".cartesi", ...paths);
}

protected getApplicationConfig(configPath: string): Config {
return fs.existsSync(configPath)
? parse(fs.readFileSync(configPath).toString())
: parse("");
}

protected getMachineHash(): Hash | undefined {
// read hash of the cartesi machine snapshot, if one exists
const hashPath = this.getContextPath("image", "hash");
Expand Down
45 changes: 45 additions & 0 deletions apps/cli/src/builder/directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import fs from "fs-extra";
import path from "path";
import { DirectoryDriveConfig } from "../config.js";
import { genext2fs, mksquashfs } from "../exec/index.js";

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

// copy directory to destination
const dest = path.join(destination, name);
await fs.mkdirp(dest);
await fs.copy(drive.directory, dest);

try {
switch (drive.format) {
case "ext2": {
await genext2fs.fromDirectory({
extraSize: drive.extraSize,
input: name,
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
case "sqfs": {
await mksquashfs.fromDirectory({
input: name,
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
}
} finally {
// delete copied
await fs.remove(dest);
}
};
138 changes: 138 additions & 0 deletions apps/cli/src/builder/docker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { execa } from "execa";
import fs from "fs-extra";
import path from "path";
import tmp from "tmp";
import { DockerDriveConfig } from "../config.js";
import { crane, genext2fs, mksquashfs } from "../exec/index.js";

type ImageBuildOptions = Pick<
DockerDriveConfig,
"context" | "dockerfile" | "tags" | "target"
>;

type ImageInfo = {
cmd: string[];
entrypoint: string[];
env: string[];
workdir: string;
};

/**
* Build Docker image (linux/riscv64). Returns image id.
*/
const buildImage = async (options: ImageBuildOptions): Promise<string> => {
const { context, dockerfile, tags, target } = options;
const buildResult = tmp.tmpNameSync();
const args = [
"buildx",
"build",
"--file",
dockerfile,
"--load",
"--iidfile",
buildResult,
context,
];

// set tags for the image built
args.push(...tags.map((tag) => ["--tag", tag]).flat());

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

await execa("docker", args, { stdio: "inherit" });
return fs.readFileSync(buildResult, "utf8");
};

/**
* Query the image using docker image inspect
* @param image image id or name
* @returns Information about the image
*/
const getImageInfo = async (image: string): Promise<ImageInfo> => {
const { stdout: jsonStr } = await execa("docker", [
"image",
"inspect",
image,
]);
// parse image info from docker inspect output
const [imageInfo] = JSON.parse(jsonStr);

// validate image architecture (must be riscv64)
if (imageInfo["Architecture"] !== "riscv64") {
throw new Error(
`Invalid image Architecture: ${imageInfo["Architecture"]}. Expected riscv64`,
);
}

const info: ImageInfo = {
cmd: imageInfo["Config"]["Cmd"] ?? [],
entrypoint: imageInfo["Config"]["Entrypoint"] ?? [],
env: imageInfo["Config"]["Env"] || [],
workdir: imageInfo["Config"]["WorkingDir"],
};

return info;
};

export const build = async (
name: string,
drive: DockerDriveConfig,
sdkImage: string,
destination: string,
): Promise<ImageInfo | undefined> => {
const { format } = drive;

const ocitar = `${name}.oci.tar`;
const tar = `${name}.tar`;
const filename = `${name}.${format}`;

// use pre-existing image or build docker image
const image = drive.image || (await buildImage(drive));

// get image info
const imageInfo = await getImageInfo(image);

try {
// create OCI Docker tarball from Docker image
await execa("docker", ["image", "save", image, "-o", ocitar], {
cwd: destination,
});

// create rootfs tar from OCI tar
await crane.exportImage({
stdin: fs.openSync(path.join(destination, ocitar), "r"),
stdout: fs.openSync(path.join(destination, tar), "w"),
image: sdkImage,
});

switch (format) {
case "ext2": {
await genext2fs.fromTar({
extraSize: drive.extraSize,
input: tar,
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
case "sqfs": {
await mksquashfs.fromTar({
input: path.join(destination, tar),
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
}
} finally {
// delete intermediate files
await fs.remove(path.join(destination, ocitar));
await fs.remove(path.join(destination, tar));
}

return imageInfo;
};
31 changes: 31 additions & 0 deletions apps/cli/src/builder/empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fs from "fs-extra";
import path from "path";
import { EmptyDriveConfig } from "../config.js";
import { genext2fs } from "../exec/index.js";

export const build = async (
name: string,
drive: EmptyDriveConfig,
sdkImage: string,
destination: string,
): Promise<void> => {
const filename = `${name}.${drive.format}`;
switch (drive.format) {
case "ext2": {
await genext2fs.empty({
output: filename,
size: drive.size,
cwd: destination,
image: sdkImage,
});
break;
}
case "raw": {
await fs.writeFile(
path.join(destination, filename),
Buffer.alloc(drive.size),
);
break;
}
}
};
5 changes: 5 additions & 0 deletions apps/cli/src/builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { build as buildDirectory } from "./directory.js";
export { build as buildDocker } from "./docker.js";
export { build as buildEmpty } from "./empty.js";
export { build as buildNone } from "./none.js";
export { build as buildTar } from "./tar.js";
17 changes: 17 additions & 0 deletions apps/cli/src/builder/none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import fs from "fs-extra";
import path from "path";
import { ExistingDriveConfig, getDriveFormat } from "../config.js";

export const build = async (
name: string,
drive: ExistingDriveConfig,
destination: string,
): Promise<void> => {
// no need to build, drive already exists
const src = drive.filename;
const format = getDriveFormat(src);
const filename = path.join(destination, `${name}.${format}`);

// just copy it
await fs.copyFile(src, filename);
};
39 changes: 39 additions & 0 deletions apps/cli/src/builder/tar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from "fs-extra";
import path from "path";
import { TarDriveConfig } from "../config.js";
import { genext2fs, mksquashfs } from "../exec/index.js";

export const build = async (
name: string,
drive: TarDriveConfig,
sdkImage: string,
destination: string,
): Promise<void> => {
const tar = `${name}.tar`;
const filename = `${name}.${drive.format}`;

// copy input tar to destination directory (with drive name)
await fs.copy(drive.filename, path.join(destination, tar));

switch (drive.format) {
case "ext2": {
await genext2fs.fromTar({
extraSize: drive.extraSize,
input: tar,
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
case "sqfs": {
await mksquashfs.fromTar({
input: path.join(destination, tar),
output: filename,
cwd: destination,
image: sdkImage,
});
break;
}
}
};
Loading
Loading