Skip to content
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/chubby-eagles-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

possibility to use an override config file
5 changes: 5 additions & 0 deletions .changeset/curly-dolls-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

remove unnecessary interactive config
5 changes: 5 additions & 0 deletions .changeset/nice-olives-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

remove unnecessary store config
5 changes: 5 additions & 0 deletions .changeset/plain-moons-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

remove unnecessary finalHash config
12 changes: 8 additions & 4 deletions apps/cli/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ export const getMachineHash = (): Hash | undefined => {
return undefined;
};

export const getApplicationConfig = (configPath: string): Config => {
return fs.existsSync(configPath)
? parse(fs.readFileSync(configPath).toString())
: parse("");
export const getApplicationConfig = (configPaths: string[]): Config => {
const tomls = configPaths.map((configPath) => {
if (fs.existsSync(configPath)) {
return fs.readFileSync(configPath).toString();
}
throw new Error(`Config file ${configPath} does not exist`);
});
return parse(tomls);
};

export const getProjectName = (options: { projectName?: string }) => {
Expand Down
67 changes: 26 additions & 41 deletions apps/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import fs from "fs-extra";
import { Listr, type ListrTask } from "listr2";
import path from "node:path";
import tmp from "tmp";
import {
getApplicationConfig,
getContextPath,
getMachineHash,
} from "../base.js";
import { getApplicationConfig, getContextPath } from "../base.js";
import {
buildDirectory,
buildDocker,
Expand Down Expand Up @@ -79,7 +75,8 @@ export const createBuildCommand = () => {
.option(
"-c, --config <config>",
"path to the configuration file",
"cartesi.toml",
(value, prev) => prev.concat([value]),
["cartesi.toml"],
)
.addOption(
new Option(
Expand Down Expand Up @@ -128,43 +125,31 @@ export const createBuildCommand = () => {
});
},
},
{
title: "Build Cartesi machine",
enabled: !drivesOnly, // if only build drives, don't do this task
task: async (ctx, task) => {
const { destination, imageInfo } = ctx;

// path of machine snapshot
const snapshotPath = path.join(
destination,
"image",
);

// create machine snapshot
await bootMachine(config, imageInfo, destination, {
stdout: new WritableStream({
write(chunk) {
task.output = chunk;
},
}),
});

// make snapshot readable by all users, because cartesi-machine sets to 600
await fs.chmod(snapshotPath, 0o755);

// get and display machine hash
const hash = getMachineHash();
if (hash) {
task.title = `Build Cartesi machine ${chalk.cyan(hash)}`;
}
},
rendererOptions: {
outputBar: 5,
},
},
],
{ ctx, renderer: verbose ? "verbose" : "default" },
);
await builds.run();
const result = await builds.run();

// if only build drives, quit here
if (drivesOnly) {
return;
}

// create machine snapshot
await bootMachine(
config,
result.imageInfo,
{
finalHash: true,
store: "image",
},
{
cwd: destination,
stdio: "inherit",
},
);

// make snapshot readable by all users, because cartesi-machine sets to 600
await fs.chmod(path.join(destination, "image"), 0o755);
});
};
32 changes: 12 additions & 20 deletions apps/cli/src/commands/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ExecaError } from "execa";
import fs from "fs-extra";
import path from "node:path";
import { getApplicationConfig, getContextPath } from "../base.js";
import type { ImageInfo } from "../config.js";
import { bootMachine } from "../machine.js";

export const createShellCommand = () => {
Expand All @@ -12,7 +11,8 @@ export const createShellCommand = () => {
.option(
"-c, --config <config>",
"path to the configuration file",
"cartesi.toml",
(value, prev) => prev.concat([value]),
["cartesi.toml"],
)
.option("--run-as-root", "run as root user", false)
.action(async (options) => {
Expand All @@ -34,30 +34,22 @@ export const createShellCommand = () => {
}

// create shell entrypoint
const info: ImageInfo = {
cmd: [],
entrypoint: [command],
env: [],
workdir: "/",
};

// start with interactive mode on
config.machine.interactive = true;

// interactive mode can't have final hash
config.machine.finalHash = false;

// do not store machine in interactive mode
config.machine.store = undefined;
config.machine.entrypoint = command;

// run as root if flag is set
config.machine.user = runAsRoot ? "root" : undefined;

// boot machine
try {
await bootMachine(config, info, destination, {
stdio: "inherit",
});
await bootMachine(
config,
undefined,
{ interactive: true }, // start with interactive mode on
{
cwd: destination,
stdio: "inherit",
},
);
} catch (error: unknown) {
if (error instanceof ExecaError) {
// just continue gracefully
Expand Down
74 changes: 63 additions & 11 deletions apps/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,10 @@ export type MachineConfig = {
assertRollingTemplate?: boolean; // default given by cartesi-machine
bootargs: string[];
entrypoint?: string;
finalHash: boolean;
interactive?: boolean; // default given by cartesi-machine
maxMCycle?: bigint; // default given by cartesi-machine
noRollup?: boolean; // default given by cartesi-machine
ramLength: string;
ramImage: string;
store?: string;
useDockerEnv: boolean; // inject docker image ENV into cartesi-machine ENV
useDockerWorkdir: boolean; // inject docker image WORKDIR into cartesi-machine WORKDIR
user?: string; // default given by cartesi-machine
Expand Down Expand Up @@ -178,13 +175,10 @@ export const defaultMachineConfig = (): MachineConfig => ({
assertRollingTemplate: undefined,
bootargs: [],
entrypoint: undefined,
finalHash: true,
interactive: undefined,
maxMCycle: undefined,
noRollup: undefined,
ramLength: DEFAULT_RAM,
ramImage: DEFAULT_RAM_IMAGE,
store: "image",
useDockerEnv: true,
useDockerWorkdir: true,
user: undefined,
Expand Down Expand Up @@ -375,13 +369,10 @@ const parseMachine = (value: TomlPrimitive): MachineConfig => {
),
bootargs: parseStringArray(toml.boot_args),
entrypoint: parseOptionalString(toml.entrypoint),
finalHash: parseBoolean(toml.final_hash, true),
interactive: undefined,
maxMCycle: parseOptionalNumber(toml.max_mcycle),
noRollup: parseBoolean(toml.no_rollup, false),
ramLength: parseString(toml.ram_length, DEFAULT_RAM),
ramImage: parseString(toml.ram_image, DEFAULT_RAM_IMAGE),
store: "image",
useDockerEnv: parseBoolean(toml.use_docker_env, true),
useDockerWorkdir: parseBoolean(toml.use_docker_workdir, true),
user: parseOptionalString(toml.user),
Expand Down Expand Up @@ -506,8 +497,11 @@ const parseDrives = (config: TomlPrimitive): Record<string, DriveConfig> => {
return drives;
};

export const parse = (str: string): Config => {
const toml = parseToml(str);
export const parse = (str: string[]): Config => {
let toml: TomlTable = {};
for (const s of str) {
toml = mergeTomlTables(toml, parseToml(s));
}

const config: Config = {
drives: parseDrives(toml.drives),
Expand All @@ -520,3 +514,61 @@ export const parse = (str: string): Config => {

return config;
};

/**
* Checks if a value is a plain object (TOML table)
*/
function isTomlTable(value: TomlPrimitive): value is TomlTable {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
!("toISOString" in value)
); // Check for TomlDate (has toISOString method)
}

/**
* Recursively merges two TOML table objects
* Values from 'other' take precedence over 'base'
*
* @param base - The base TOML table
* @param other - The TOML table to merge into base (takes precedence)
* @returns A new merged TOML table
*/
export function mergeTomlTables(base: TomlTable, other: TomlTable): TomlTable {
const result: TomlTable = { ...base };

for (const [key, otherValue] of Object.entries(other)) {
const baseValue = result[key];

// If both values are tables, merge them recursively
if (isTomlTable(baseValue) && isTomlTable(otherValue)) {
result[key] = mergeTomlTables(baseValue, otherValue);
} else {
// For all other cases, other value takes precedence
result[key] = otherValue;
}
}

return result;
}

/**
* Merges two TOML values of any type
*
* @param base - The base TOML value
* @param other - The TOML value to merge into base (takes precedence)
* @returns The merged TOML value
*/
export function mergeTomlValues(
base: TomlPrimitive,
other: TomlPrimitive,
): TomlPrimitive {
// If both are tables, merge recursively
if (isTomlTable(base) && isTomlTable(other)) {
return mergeTomlTables(base, other);
}

// For arrays, replaces entirely
return other;
}
20 changes: 11 additions & 9 deletions apps/cli/src/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,25 @@ const flashDrive = (label: string, drive: DriveConfig): string => {
return `--flash-drive=${vars.join(",")}`;
};

export type BootMachineOptions = {
finalHash?: boolean;
interactive?: boolean;
store?: string;
};

export const bootMachine = (
config: Config,
info: ImageInfo | undefined,
destination: string,
bootOptions: BootMachineOptions,
options?: ExecaOptionsDockerFallback,
) => {
const { machine } = config;
const {
assertRollingTemplate,
finalHash,
interactive,
maxMCycle,
noRollup,
ramLength,
ramImage,
store,
useDockerEnv,
useDockerWorkdir,
user,
Expand Down Expand Up @@ -91,13 +94,13 @@ export const bootMachine = (
if (assertRollingTemplate) {
args.push("--assert-rolling-template");
}
if (finalHash) {
if (bootOptions.finalHash) {
args.push("--final-hash");
}
if (useDockerWorkdir && info?.workdir) {
args.push(`--workdir="${info.workdir}"`);
}
if (interactive) {
if (bootOptions.interactive) {
args.push("-it");
}
if (noRollup) {
Expand All @@ -106,8 +109,8 @@ export const bootMachine = (
if (maxMCycle) {
args.push(`--max-mcycle=${maxMCycle.toString()}`);
}
if (store) {
args.push(`--store=${store}`);
if (bootOptions.store) {
args.push(`--store=${bootOptions.store}`);
}
if (user) {
args.push(`--user=${user}`);
Expand All @@ -116,7 +119,6 @@ export const bootMachine = (
args.push(entrypoint);

return cartesiMachine.boot(args, {
cwd: destination,
image: config.sdk,
...options,
});
Expand Down
Loading
Loading