Task 093: .CML File Format
Task 093: .CML File Format
1. List of All Properties of the .CML File Format Intrinsic to Its File System
The .CML file format refers to the Fuchsia Component Manifest Language, a JSON5-based declarative format used in the Fuchsia operating system to define component structure, capabilities, routing, and execution properties. It is intrinsic to Fuchsia's component architecture and file system, as it compiles to a binary .cm format for runtime use in the component framework, which manages file system interactions, capabilities (e.g., directory access), and hierarchical component trees within the OS file system (e.g., package directories under /pkgfs). The format is text-based (JSON5), human-readable, and supports merging via includes for modular file system declarations.
The format is a single JSON5 object with the following top-level properties (keys). Each is optional unless noted, and sub-properties define the intrinsic structure for components, capabilities, and file system-related routing (e.g., directories, storage). All strings follow strict rules: names (A-Z, a-z, 0-9, _, ., -; max 255 chars, no leading . or -); paths (name sequences delimited by /; max 4095 chars; relative paths no leading /, namespace/outgoing paths must start with /); references (# for child capabilities).
include: Array of strings (file paths to other .cml or .shard.cml files for merging). Supports relative paths from source root or Fuchsia SDK. Intrinsic: Enables modular file system composition without cycles; duplicates are deduped, availability promoted if stronger.
program: Object (for executable components). Sub-properties:
- runner: String (required; e.g., "elf" for ELF binaries; selects execution environment).
- binary: String (required for "elf"; relative path to executable in package file system).
- args: Array of strings (optional; command-line arguments passed at startup).
- Runner-specific properties (e.g., for custom runners like "dart": additional fields like "main_dart").
Intrinsic: Defines entry point in file system for component execution.
children: Array of objects (declares static child components). Each object has:
- name: String (required; child instance name).
- url: String (required; fuchsia-pkg:// URL to child package).
- startup: String (optional; "lazy" default or "eager" for immediate file system loading).
- on_terminate: String (optional; "none" default or "reboot" for fault policy).
- environment: String (optional; assigns to named environment for capability routing).
Intrinsic: Builds hierarchical file system tree of components.
collections: Array of objects (declares dynamic component collections). Each object has:
- name: String (required; collection name).
- durability: String (required; "transient" or "single_run" for lifecycle).
- environment: String (optional; assigns environment).
- allowed_offers: String (optional; "static_only" default or "static_and_dynamic" for offers).
- allow_long_names: Boolean (optional; false default; allows names up to 1024 chars).
- persistent_storage: Boolean (optional; false default; enables persistent file system storage for dynamic children).
Intrinsic: Manages dynamic file system additions/removals in collections.
environments: Array of objects (defines capability routing environments). Each object has:
- name: String (required; environment name).
- extends: String (optional; "realm" default to inherit parent or "none" for isolated).
- runners: Array of objects (optional; each: runner string, from string (parent/self/#child), as string optional).
- resolvers: Array of objects (optional; each: resolver string, from string, scheme string).
- debug: Array of objects (optional; each: protocol string/array, from string, as string optional).
- __stop_timeout_ms: Number (optional; ms to wait on stop; required if extends="none").
Intrinsic: Routes file system capabilities (e.g., directories) across component boundaries.
capabilities: Array of objects (declares provided capabilities from this component). Each object requires exactly one capability type; common sub-properties:
- Capability types (string or array): protocol, service, directory, storage, runner, resolver, event_stream, dictionary, config.
- path: String (optional; defaults to capability name; file system path for directories/storage).
- rights: Array of strings (optional for directory; e.g., "r*", "x*"; file system access rights).
- For storage: from string, backing_dir string, subdir string, storage_id string.
- For config: type string ("string"/"vector"/"map"), max_size number, max_count number, element object, value any, delivery string ("eager"/"on_readable").
Intrinsic: Exposes file system resources (e.g., directories) to the component framework.
use: Array of objects (declares required capabilities for this component). Each object requires exactly one capability type; common sub-properties:
- Capability types (string or array): service, directory, protocol, dictionary, storage, event_stream, runner, config.
- from: String (optional; "parent" default, or "self"/"debug"/"framework"/"#child").
- path: String (optional; target path).
- rights: Array of strings (optional for directory; requested rights).
- For storage: subdir string, scope string ("realm"/"child"/"collection"/"above_root"), filter string.
- For config: dependency string ("strong"/"weak"), availability string ("required"/"optional"), key string, default any.
Intrinsic: Requests file system access (e.g., read directories) from providers.
expose: Array of objects (exposes capabilities to parent or specific targets). Each object has:
- Capability type (string or array): protocol, service, directory, etc. (as in capabilities).
- from: String (required; source: "self"/"#child").
- to: String (optional; "parent" default or "#child").
- path: String (optional; exposed path).
- Other fields as in capabilities/use (e.g., rights, subdir).
Intrinsic: Routes file system capabilities upward in the hierarchy.
offer: Array of objects (offers capabilities to children/collections). Each object has:
- Capability type (string or array): protocol, service, directory, etc.
- from: String (required; "parent"/"self"/"#child"/"framework").
- to: String (required; "#child" or collection name).
- dependency: String (optional; "strong"/"weak").
- availability: String (optional; "required"/"optional").
- Other fields as in capabilities/use (e.g., path, rights, subdir).
Intrinsic: Delegates file system access to child components.
facets: Object (optional; named groups for capabilities/use/expose/offer). Sub-properties:
- Facet name (string): Object containing subsets of capabilities/use/expose/offer arrays.
Intrinsic: Organizes file system-related declarations for conditional or grouped routing.
Additional intrinsic format properties:
- JSON5 syntax (supports comments, unquoted keys, trailing commas; compiles to strict JSON via cmc tool).
- No cycles in includes or references.
- All values are typed (string, number, boolean, array, object); numbers non-negative where specified.
- File extension .cml; compiled to binary .cm for file system storage and runtime.
2. Two Direct Download Links for Files of Format .CML
- https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/examples/components/hello_world/meta/hello_world.cml?format=TEXT (Simple hello_world example component manifest)
- https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/media/audio/audio_core/meta/audio_core.cml?format=TEXT (Audio core component manifest with capabilities and environments)
3. Ghost Blog Embedded HTML JavaScript for Drag n Drop .CML File and Dump Properties
Embed this as a custom HTML card or page in Ghost blog (via admin > Pages/Code Injection or a custom template). It uses HTML5 drag-and-drop API and JSON.parse to read the file as text, parse as JSON, and recursively dump all properties to a element on screen.
Drag and drop a .CML file here to dump its properties.
This script recursively traverses the JSON object, annotating leaf properties with their path (e.g., "program.runner.path: elf"), and writes back by allowing modification of the parsed object before re-stringifying (not implemented here for simplicity; extend dumpProperties to support edits).
4. Python Class for .CML File Handling
This class uses the standard json
module (assumes valid JSON; for full JSON5, use external lib like json5 if needed). It reads the file, parses, prints all properties recursively with paths, and supports writing a modified version back to a new file.
import json
import sys
class CMLHandler:
def __init__(self, file_path):
self.file_path = file_path
self.data = None
def read_and_decode(self):
try:
with open(self.file_path, 'r') as f:
self.data = json.load(f)
self.print_properties(self.data, 'root')
except json.JSONDecodeError as e:
print(f"Error decoding CML: {e}", file=sys.stderr)
except FileNotFoundError:
print(f"File not found: {self.file_path}", file=sys.stderr)
def print_properties(self, obj, path='root'):
if isinstance(obj, dict):
for key, value in obj.items():
full_path = f"{path}.{key}" if path != 'root' else key
print(f"{full_path}: {type(value).__name__}")
if isinstance(value, (dict, list)):
self.print_properties(value, full_path)
elif isinstance(obj, list):
for i, item in enumerate(obj):
full_path = f"{path}[{i}]"
print(f"{full_path}: {type(item).__name__}")
if isinstance(item, (dict, list)):
self.print_properties(item, full_path)
else:
print(f"{path}: {obj} ({type(obj).__name__})")
def write(self, output_path, modified_data=None):
if modified_data is None:
modified_data = self.data
if self.data is None:
print("No data loaded; call read_and_decode first.", file=sys.stderr)
return
with open(output_path, 'w') as f:
json.dump(modified_data, f, indent=2)
print(f"Wrote modified CML to {output_path}")
# Example usage
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python cml_handler.py <input.cml>", file=sys.stderr)
sys.exit(1)
handler = CMLHandler(sys.argv[1])
handler.read_and_decode()
# Example write (unmodified)
handler.write("output.cml")
Run with python cml_handler.py example.cml
to read/print/write.
5. Java Class for .CML File Handling
Uses built-in javax.json
(Java 9+; assume available) for JSON parsing. Reads file, prints properties recursively, writes to new file.
import java.io.*;
import javax.json.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Iterator;
public class CMLHandler {
private String filePath;
private JsonObject data;
public CMLHandler(String filePath) {
this.filePath = filePath;
}
public void readAndDecode() {
try {
String content = new String(Files.readAllBytes(Paths.get(filePath)));
JsonReader reader = Json.createReader(new StringReader(content));
data = reader.readObject();
reader.close();
printProperties(data, "root");
} catch (IOException e) {
System.err.println("Error reading/decoding CML: " + e.getMessage());
}
}
private void printProperties(JsonValue obj, String path) {
if (obj instanceof JsonObject) {
JsonObject jsonObj = (JsonObject) obj;
for (Map.Entry<String, JsonValue> entry : jsonObj.entrySet()) {
String key = entry.getKey();
JsonValue value = entry.getValue();
String fullPath = path.equals("root") ? key : path + "." + key;
System.out.println(fullPath + ": " + getType(value));
if (value instanceof JsonObject || value instanceof JsonArray) {
printProperties(value, fullPath);
}
}
} else if (obj instanceof JsonArray) {
JsonArray jsonArray = (JsonArray) obj;
for (int i = 0; i < jsonArray.size(); i++) {
JsonValue item = jsonArray.get(i);
String fullPath = path + "[" + i + "]";
System.out.println(fullPath + ": " + getType(item));
if (item instanceof JsonObject || item instanceof JsonArray) {
printProperties(item, fullPath);
}
}
} else {
System.out.println(path + ": " + obj.toString() + " (" + getType(obj) + ")");
}
}
private String getType(JsonValue value) {
if (value instanceof JsonObject) return "JsonObject";
if (value instanceof JsonArray) return "JsonArray";
if (value instanceof JsonString) return "String";
if (value instanceof JsonNumber) return "Number";
if (value instanceof JsonValue.TRUE || value instanceof JsonValue.FALSE) return "Boolean";
return "Unknown";
}
public void write(String outputPath) {
if (data == null) {
System.err.println("No data loaded; call readAndDecode first.");
return;
}
try (JsonWriter writer = Json.createWriter(new FileWriter(outputPath))) {
writer.writeObject(data);
System.out.println("Wrote CML to " + outputPath);
} catch (IOException e) {
System.err.println("Error writing CML: " + e.getMessage());
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java CMLHandler <input.cml>");
return;
}
CMLHandler handler = new CMLHandler(args[0]);
handler.readAndDecode();
handler.write("output.cml");
}
}
Compile/run with javac CMLHandler.java && java CMLHandler example.cml
(requires Java 11+ for javax.json).
6. JavaScript Class for .CML File Handling
Node.js class using built-in fs
and JSON.parse
. Reads file, prints properties to console recursively, writes modified data.
const fs = require('fs');
class CMLHandler {
constructor(filePath) {
this.filePath = filePath;
this.data = null;
}
readAndDecode() {
try {
const content = fs.readFileSync(this.filePath, 'utf8');
this.data = JSON.parse(content);
this.printProperties(this.data, 'root');
} catch (err) {
console.error('Error decoding CML:', err.message);
}
}
printProperties(obj, path = 'root') {
if (typeof obj === 'object' && obj !== null) {
if (Array.isArray(obj)) {
obj.forEach((item, i) => {
const fullPath = `${path}[${i}]`;
console.log(`${fullPath}: ${typeof item}`);
if (typeof item === 'object' && item !== null) {
this.printProperties(item, fullPath);
}
});
} else {
Object.entries(obj).forEach(([key, value]) => {
const fullPath = path === 'root' ? key : `${path}.${key}`;
console.log(`${fullPath}: ${typeof value}`);
if (typeof value === 'object' && value !== null) {
this.printProperties(value, fullPath);
}
});
}
} else {
console.log(`${path}: ${obj} (${typeof obj})`);
}
}
write(outputPath, modifiedData = null) {
if (!this.data) {
console.error('No data loaded; call readAndDecode first.');
return;
}
const dataToWrite = modifiedData || this.data;
fs.writeFileSync(outputPath, JSON.stringify(dataToWrite, null, 2));
console.log(`Wrote CML to ${outputPath}`);
}
}
// Example usage
if (require.main === module) {
if (process.argv.length < 3) {
console.error('Usage: node cml_handler.js <input.cml>');
process.exit(1);
}
const handler = new CMLHandler(process.argv[2]);
handler.readAndDecode();
handler.write('output.cml');
}
Run with node cml_handler.js example.cml
.
7. C Class for .CML File Handling
Uses cJSON library (assume included; common for JSON in C). Reads file, parses, prints properties recursively to stdout, writes to new file. Compile with gcc cml_handler.c -o cml_handler -lcjson -lm
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h" // Assume cJSON.h and lib is available
typedef struct {
char* file_path;
cJSON* data;
} CMLHandler;
CMLHandler* cml_handler_new(const char* file_path) {
CMLHandler* handler = malloc(sizeof(CMLHandler));
handler->file_path = strdup(file_path);
handler->data = NULL;
return handler;
}
void cml_handler_free(CMLHandler* handler) {
if (handler) {
free(handler->file_path);
if (handler->data) cJSON_Delete(handler->data);
free(handler);
}
}
void print_properties(cJSON* obj, const char* path) {
if (!obj) return;
if (cJSON_IsObject(obj)) {
cJSON* item = NULL;
cJSON_ArrayForEach(item, obj) {
char* full_path = malloc(strlen(path) + strlen(item->string) + 2);
sprintf(full_path, "%s.%s", path, item->string);
if (strcmp(path, "root") == 0) {
strcpy(full_path, item->string);
}
printf("%s: %s\n", full_path, cJSON_GetTypeName(item));
if (cJSON_IsObject(item) || cJSON_IsArray(item)) {
print_properties(item, full_path);
}
free(full_path);
}
} else if (cJSON_IsArray(obj)) {
int i = 0;
cJSON* item = NULL;
cJSON_ArrayForEach(item, obj) {
char full_path[1024];
sprintf(full_path, "%s[%d]", path, i++);
printf("%s: %s\n", full_path, cJSON_GetTypeName(item));
if (cJSON_IsObject(item) || cJSON_IsArray(item)) {
print_properties(item, full_path);
}
}
} else {
printf("%s: %s (%s)\n", path, cJSON_PrintUnformatted(obj), cJSON_GetTypeName(obj));
}
}
int read_and_decode(CMLHandler* handler) {
FILE* f = fopen(handler->file_path, "r");
if (!f) {
fprintf(stderr, "File not found: %s\n", handler->file_path);
return -1;
}
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
char* content = malloc(len + 1);
fread(content, 1, len, f);
content[len] = '\0';
fclose(f);
handler->data = cJSON_Parse(content);
free(content);
if (!handler->data) {
fprintf(stderr, "Error decoding CML\n");
return -1;
}
print_properties(handler->data, "root");
return 0;
}
void write(CMLHandler* handler, const char* output_path) {
if (!handler->data) {
fprintf(stderr, "No data loaded\n");
return;
}
char* json_str = cJSON_Print(handler->data);
FILE* f = fopen(output_path, "w");
if (f) {
fputs(json_str, f);
fclose(f);
printf("Wrote to %s\n", output_path);
} else {
fprintf(stderr, "Error writing\n");
}
free(json_str);
}
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <input.cml>\n", argv[0]);
return 1;
}
CMLHandler* handler = cml_handler_new(argv[1]);
if (read_and_decode(handler) == 0) {
write(handler, "output.cml");
}
cml_handler_free(handler);
return 0;
}
This C code handles read/print/write; extend for modifications by manipulating cJSON nodes before writing.