### Setting Up ContainerEnvironment in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This method implements the `setup` functionality for `ContainerEnvironment`. It logs the setup process and then calls the underlying runtime to create the container instance using its ID, base image, root path, and options, without starting it immediately. ```Rust async fn setup(&self, log: &Log, _storage: &Storage) -> EnvResult<()> { log.info(format!("Setting up container environment: {}", self.container_id)); // Create container but don't start it yet self.runtime.create_container( &self.container_id, &self.base_image, &self.root_path, &self.options ).await?; Ok(()) } ``` -------------------------------- ### Starting ContainerEnvironment in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This method implements the `up` functionality for `ContainerEnvironment`, responsible for starting the container. It logs the action, determines the appropriate network configuration based on the `network_access` setting, and then instructs the runtime to start the container with the specified network options. ```Rust async fn up(&self, log: &Log) -> EnvResult<()> { log.info(format!("Starting container: {}", self.container_id)); // Set up network based on configuration let network_config = match &self.network_access { NetworkAccess::None => "--network none", NetworkAccess::Full => "", NetworkAccess::Limited(hosts) => { // Implementation would handle setting up limited network access // This is simplified for the design document "--network limited" } }; // Start the container self.runtime.start_container( &self.container_id, network_config ).await?; Ok(()) } ``` -------------------------------- ### Configuring Edo Environment Farms with Starlark Plugins Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This Starlark example shows the declaration of a WebAssembly plugin for a Docker environment and its subsequent configuration as an `environment_farm`. It demonstrates how to pass specific parameters like `image`, `network`, and `mounts` to the plugin instance, enabling custom environment setup within the Edo build process. ```Starlark # Plugin declaration plugin( name = "docker-env", kind = "wasm", source = ["//vendor:docker-env-1.2.0.wasm"], ) # Plugin configuration and use environment_farm( name = "docker", plugin = "docker-env", image = "ubuntu:20.04", network = "none", mounts = { "/cache": "//local:cache", }, ) ``` -------------------------------- ### Implementing ContainerFarm Setup in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This method implements the `setup` functionality for `ContainerFarm`. It ensures that the designated temporary directory exists and that the specified base container image is available by pulling it if necessary, preparing the farm for container creation. ```Rust impl Farm for ContainerFarm { async fn setup(&self, log: &Log, storage: &Storage) -> EnvResult<()> { // Create temp directory if it doesn't exist if !self.temp_dir.exists() { std::fs::create_dir_all(&self.temp_dir)?; } // Pull container image if needed log.info(format!("Ensuring container image is available: {}", self.base_image)); self.runtime.pull_image(&self.base_image).await?; Ok(()) } ``` -------------------------------- ### Implementing LocalFarm Setup in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md The `setup` method for `LocalFarm` ensures that the designated temporary directory exists. If the directory does not exist, it creates it, preparing the base for local environment operations. This is a prerequisite for creating new local environments. ```Rust impl Farm for LocalFarm { async fn setup(&self, _log: &Log, _storage: &Storage) -> EnvResult<()> { // Create temp directory if it doesn't exist if !self.temp_dir.exists() { std::fs::create_dir_all(&self.temp_dir)?; } Ok(()) } ``` -------------------------------- ### Installing Edo from Source (Bash) Source: https://github.com/awslabs/edo/blob/main/README.md This snippet provides instructions to clone the Edo repository, build the project using Cargo, and add the resulting binary to the system's PATH. It requires Rust 1.86+ and Git to be installed on the system. ```bash # Clone the repository git clone https://github.com/awslabs/edo.git cd edo # Build the project cargo build --package edo --release # Add to your PATH export PATH="$PATH:$(pwd)/target/release" ``` -------------------------------- ### Installing Edo via Cargo (Bash) Source: https://github.com/awslabs/edo/blob/main/README.md This command installs the Edo build tool directly from its Git repository using Cargo. It's a convenient way to get the latest version of Edo if Rust and Cargo are already set up on the system. ```bash cargo install --git https://github.com/awslabs/edo.git --package edo ``` -------------------------------- ### Lifecycle Methods for LocalEnvironment in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md These methods (`setup`, `up`, `down`) define the lifecycle operations for a local environment. For `LocalEnvironment`, these operations are largely no-ops as a local environment is considered always ready and doesn't require explicit setup, startup, or shutdown procedures beyond initial directory creation. ```Rust async fn setup(&self, _log: &Log, _storage: &Storage) -> EnvResult<()> { // Local environment doesn't need special setup Ok(()) } async fn up(&self, _log: &Log) -> EnvResult<()> { // Local environment is always up Ok(()) } async fn down(&self, _log: &Log) -> EnvResult<()> { // Local environment doesn't need to be brought down Ok(()) } ``` -------------------------------- ### Defining Environment Interface in WIT Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This WIT interface defines resources and functions for managing execution environments. It includes a `command` resource for file system and process operations, an `environment` resource for environment setup, cleanup, and I/O, and a `farm` resource for creating new environments. It depends on `common`, `source`, and `storage` modules. ```WIT // environment.wit interface environment { use common.{ error, reader, writer, id }; use source.{ log }; use storage.{ storage-manager }; // Command interface resource command { set: func(key: string, value: string) -> result<_, error>; chdir: func(path: string) -> result<_, error>; pushd: func(path: string) -> result<_, error>; popd: func(); create-named-dir: func(key: string, path: string) -> result<_, error>; create-dir: func(path: string) -> result<_, error>; remove-dir: func(path: string) -> result<_, error>; remove-file: func(path: string) -> result<_, error>; mv: func(source: string, target: string) -> result<_, error>; copy: func(source: string, target: string) -> result<_, error>; run: func(cmd: string) -> result<_, error>; send: func(path: string) -> result<_, error>; } // Environment interface resource environment { defer-cmd: func(log: borrow, id: borrow) -> command; expand: func(path: string) -> result; create-dir: func(path: string) -> result<_, error>; set-env: func(key: string, value: string) -> result<_, error>; get-env: func(key: string) -> option; setup: func(log: borrow, storage: borrow) -> result<_, error>; up: func(log: borrow) -> result<_, error>; down: func(log: borrow) -> result<_, error>; clean: func(log: borrow) -> result<_, error>; write: func(path: string, data: borrow) -> result<_, error>; unpack: func(path: string, data: borrow) -> result<_, error>; read: func(path: string, writer: borrow) -> result<_, error>; cmd: func(log: borrow, id: borrow, path: string, command: string) -> result; run: func(log: borrow, id: borrow, path: string, command: borrow) -> result; shell: func(path: string) -> result<_, error>; } // Farm interface resource farm { setup: func(log: borrow, storage: borrow) -> result<_, error>; create: func(log: borrow, path: string) -> result; } } ``` -------------------------------- ### Defining the WebAssembly Environment Provider Interface (WIT) Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This WebAssembly Interface Type (WIT) defines the `environment-provider` interface, enabling WebAssembly modules to interact with and manage execution environments. It specifies types for errors, network access, environment options, resource limits, and container options, along with functions for initializing, creating, starting, stopping, executing commands, and performing file I/O within an environment. ```wit // environment-provider.wit package edo:environment; interface environment-provider { // Error type for environment operations enum env-error { init-failed, create-failed, exec-failed, io-error, not-supported, invalid-config, } // Type alias for environment results type env-result = result; // Network access modes enum network-access { none, full, limited(list), } // Environment options record environment-options { working-dir: string, network: network-access, mount-tmp: bool, env-vars: list>, resource-limits: option, } // Resource limits configuration record resource-limits { cpu-cores: option, memory-mb: option, disk-mb: option, } // Container specific options record container-options { image: string, runtime: string, volumes: list>, privileged: bool, } // Initialize the environment provider init: func(options: string) -> env-result<>; // Create an environment create-environment: func(env-options: environment-options) -> env-result; // Start an environment start: func(env-id: string) -> env-result<>; // Stop an environment stop: func(env-id: string) -> env-result<>; // Execute a command in the environment execute: func(env-id: string, command: string, args: list, work-dir: string) -> env-result; // Write a file to the environment write-file: func(env-id: string, path: string, content: list) -> env-result<>; // Read a file from the environment read-file: func(env-id: string, path: string) -> env-result>; } ``` -------------------------------- ### Defining and Configuring a Docker Environment Farm Plugin in Starlark Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This snippet illustrates how to define a WebAssembly plugin for a Docker-based environment farm. It configures an environment farm named "ubuntu-build" using a specific Docker image, installs necessary packages, sets environment variables, and then applies this custom environment to a C++ build process. ```starlark # Define a plugin that implements a Docker environment farm plugin( name = "docker-env", kind = "wasm", source = ["//vendor:docker-env-1.2.0.wasm"], ) # Configure a Docker environment farm environment_farm( name = "ubuntu-build", plugin = "docker-env", image = "ubuntu:20.04", packages = ["build-essential", "cmake", "python3"], env = { "DEBIAN_FRONTEND": "noninteractive", }, ) # Use the environment in a transform cpp_build( name = "my-app", srcs = ["//src:*.cpp"], environment = "ubuntu-build", ) ``` -------------------------------- ### Defining the Farm Trait in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This Rust trait defines the `Farm` abstraction, which acts as a factory for creating build environments. It includes methods for one-time setup of resources (`setup`) and for instantiating new `Environment` instances (`create`), ensuring consistent environment configurations. ```Rust trait Farm { /// Setup can be used for any one time initializations required for an environment farm fn setup(&self, log: &Log, storage: &Storage) -> EnvResult<()>; /// Create a new environment using this farm fn create(&self, log: &Log, path: &Path) -> EnvResult; } ``` -------------------------------- ### Implementing Edo Plugin Components in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This Rust code demonstrates how to implement various Edo plugin components like `storage::Backend` and `source::Source`. It defines structs for custom backends and providers and exports functions (`create-storage`, `create-source`, etc.) as entry points for the Edo core to initialize these components. It also shows how to return errors for unimplemented components. ```Rust use edo_plugin::{abi, common, storage, source, environment, transform}; struct MyStorageBackend { // Implementation details } impl storage::Backend for MyStorageBackend { // Implement storage backend methods } struct MySourceProvider { // Implementation details } impl source::Source for MySourceProvider { // Implement source provider methods } // Export plugin entry points #[export_name = "create-storage"] fn create_storage(addr: &str, node: &common::Node, ctx: &common::Context) -> Result { // Initialize and return a storage backend Ok(MyStorageBackend::new(addr, node, ctx)) } #[export_name = "create-source"] fn create_source(addr: &str, node: &common::Node, ctx: &common::Context) -> Result { // Initialize and return a source provider Ok(MySourceProvider::new(addr, node, ctx)) } // No implementation for other component types #[export_name = "create-farm"] fn create_farm(_addr: &str, _node: &common::Node, _ctx: &common::Context) -> Result { Err(common::Error::new("my-plugin", "Farm implementation not available")) } #[export_name = "create-transform"] fn create_transform(_addr: &str, _node: &common::Node, _ctx: &common::Context) -> Result { Err(common::Error::new("my-plugin", "Transform implementation not available")) } #[export_name = "create-vendor"] fn create_vendor(_addr: &str, _node: &common::Node, _ctx: &common::Context) -> Result { Err(common::Error::new("my-plugin", "Vendor implementation not available")) } ``` -------------------------------- ### Defining EDO Plugin ABI and World in WIT Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This WIT snippet defines the top-level package structure for EDO plugins. It declares various component interfaces (common, storage, source, environment, transform, vendor), the host interface, and the plugin ABI. The ABI specifies functions for creating different plugin backend instances, serving as the primary entry points for plugin implementations. The world definition exports this ABI to the host. ```wit // edo.wit - Package definition package edo:plugin@1.0.0; // Component interfaces interface common { ... } interface storage { ... } interface source { ... } interface environment { ... } interface transform { ... } interface vendor { ... } // Host interface interface host { ... } // Plugin ABI interface abi { use host.{ ... }; // Multiple implementations in one plugin create-storage: func(addr: string, node: borrow, ctx: borrow) -> result; create-farm: func(addr: string, node: borrow, ctx: borrow) -> result; create-source: func(addr: string, node: borrow, ctx: borrow) -> result; create-transform: func(addr: string, node: borrow, ctx: borrow) -> result; create-vendor: func(addr: string, node: borrow, ctx: borrow) -> result; } // World definition world edo { use host.{ ... }; import host; export abi; } ``` -------------------------------- ### Discovering Leaf Nodes with `find_leafs` Method in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/transform.md The `find_leafs` method recursively identifies leaf nodes within a directed acyclic graph (DAG) starting from a given index. It traverses incoming neighbors, collecting leaf nodes from children, and if no children are found, the current node is considered a leaf. This is crucial for identifying starting points in graph processing. ```Rust fn find_leafs(graph: &Dag, String>, index: &NodeIndex) -> Option> { let mut leafs = HashSet::new(); let mut count = 0; for node in graph.neighbors_directed(*index, Direction::Incoming) { if let Some(children) = Self::find_leafs(graph, &node) { for entry in children { leafs.insert(entry); } } count += 1; } if count == 0 { // if we didn't have any leaf nodes discovered by this node's children than this is a leaf node leafs.insert(*index); } if leafs.is_empty() { None } else { Some(leafs) } } ``` -------------------------------- ### Resolving Transform Dependencies in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/transform.md This method retrieves the list of dependencies for a specified transform. It queries the internal registry to get the addresses of all transforms that the given transform depends on, which is crucial for building execution plans. ```Rust pub fn resolve_deps(&self, addr: &Addr) -> TransformResult> { self.registry.get_dependencies(addr) } ``` -------------------------------- ### Defining Storage Interface in WIT Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This WIT interface defines the core components and operations for the storage backend within the EDO project. It includes resources for `layer`, `artifact-config`, `artifact`, `backend`, and `storage-manager`, outlining functions for managing artifact lifecycle, data persistence, and host-provided storage interactions. ```WIT // storage.wit interface storage { use common.{ id, error, reader, writer }; // Layer representation resource layer { media-type: func() -> string; digest: func() -> string; size: func() -> u64; platform: func() -> option; } // Artifact configuration resource artifact-config { constructor(id: borrow, provides: list, metadata: option); id: func() -> id; provides: func() -> list; requires: func() -> list>>>; add-requirement: func(group: string, name: string, version: string); } // Artifact representation resource artifact { constructor(config: borrow); config: func() -> artifact-config; layers: func() -> list; add-layer: func(layer: borrow); } // Storage backend interface resource backend { ls: func() -> result, error>; has: func(id: borrow) -> result; open: func(id: borrow) -> result; save: func(artifact: borrow) -> result<_, error>; del: func(id: borrow) -> result<_, error>; copy: func(source: borrow, target: borrow) -> result<_, error>; prune: func(id: borrow) -> result<_, error>; prune-all: func() -> result<_, error>; read: func(layer: borrow) -> result; start-layer: func() -> result; finish-layer: func(media-type: string, platform: option, writer: borrow) -> result; } // Storage manager interface (host-provided) resource storage-manager { open: func(id: borrow) -> result; read: func(layer: borrow) -> result; start-layer: func() -> result; finish-layer: func(media-type: string, platform: option, writer: borrow) -> result; save: func(artifact: borrow) -> result<_, error>; } } ``` -------------------------------- ### Building Version Database for Package in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/source.md This asynchronous method builds a version database for a given package across all registered vendors. It iterates through vendors, fetches available versions, interns them into a package pool, and updates an internal mapping (`name_to_vs`) to manage version sets, handling both single and union sets. ```Rust /// Build the version database for a package across all vendors pub async fn build_db(&self, name: &str) -> Result<()> { for entry in self.vendors.iter() { let vendor_name = entry.key(); let vendor = entry.value(); // Get all versions from this vendor let version_set = vendor.get_options(name).await?; let name_id = self.pool.intern_package_name(name.to_string()); let mut edo_versions = Vec::new(); for version in version_set { let edo_version = EdoVersion::new(vendor_name, &version); self.pool.intern_solvable(name_id, edo_version.clone()); edo_versions.push(edo_version.clone()); } // Create version set let vsid = self .pool .intern_version_set(name_id, EdoVersionSet::new(edo_versions.as_slice())); // Update version mapping if let Some(entry) = self.name_to_vs.get(&name_id) { let union_id = match entry.value() { Set::Union(union_id) => { let vs_union = self.pool.resolve_version_set_union(*union_id); self.pool.intern_version_set_union(vsid, vs_union) } Set::Single(vs_id) => self .pool .intern_version_set_union(vsid, [*vs_id].iter().cloned()), }; self.name_to_vs.insert(name_id, Set::Union(union_id)); } else { self.name_to_vs.insert(name_id, Set::Single(vsid)); } } Ok(()) } ``` -------------------------------- ### Defining Farm Interface Trait in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This Rust trait defines the `Farm` interface, which acts as a factory for creating environment instances. It requires implementations to support a `setup` method for one-time initialization of shared resources and a `create` method for instantiating new environments with consistent configurations, embodying a factory pattern for environment management. ```Rust /// Defines the interface that implementations of an environment farm must support pub trait Farm: Send + Sync + 'static { /// Setup can be used for any one time initializations required for an environment farm fn setup(&self, log: &Log, storage: &Storage) -> EnvResult<()>; /// Create a new environment using this farm fn create(&self, log: &Log, path: &Path) -> EnvResult; } ``` -------------------------------- ### Implementing ContainerEnvironment Directory Creation in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This method implements the `create_dir` functionality for `ContainerEnvironment`. It expands the provided path to its container equivalent and then executes an `mkdir -p` command inside the container to create the directory, ensuring all necessary parent directories are also created. ```Rust async fn create_dir(&self, path: &Path) -> EnvResult<()> { let container_path = self.expand(path).await?; // Execute mkdir in container self.runtime.exec_command( &self.container_id, "mkdir", &["-p", container_path.to_str().unwrap()] ).await?; Ok(()) } ``` -------------------------------- ### Defining Plugin ABI Interface in WIT Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This WIT interface defines the Application Binary Interface (ABI) for creating component implementations within Edo plugins. It provides functions to create instances of `backend` (storage), `farm` (environment management), `source`, `transform`, and `vendor` components, enabling plugins to extend Edo's core functionalities. It depends on `common`, `storage`, `source`, `environment`, and `transform` modules. ```WIT // abi.wit interface abi { use common.{ node, error, context }; use storage.{ backend }; use source.{ source, vendor }; use environment.{ farm }; use transform.{ transform }; // Create component implementations create-storage: func(addr: string, node: borrow, ctx: borrow) -> result; create-farm: func(addr: string, node: borrow, ctx: borrow) -> result; create-source: func(addr: string, node: borrow, ctx: borrow) -> result; create-transform: func(addr: string, node: borrow, ctx: borrow) -> result; create-vendor: func(addr: string, node: borrow, ctx: borrow) -> result; } ``` -------------------------------- ### Defining the Environment Trait in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This Rust trait defines the core `Environment` abstraction, representing a build environment. It provides methods for lifecycle management (setup, up, down, clean), file system operations (expand, create_dir, write, unpack, read), environment variable management (set_env, get_env), command execution (cmd, run), and interactive shell access (shell). ```Rust trait Environment { /// Expand the provided path to a root absolute path inside of the environment fn expand(&self, path: &Path) -> EnvResult; /// Create a directory inside the environment fn create_dir(&self, path: &Path) -> EnvResult<()>; /// Set environment variable fn set_env(&self, key: &str, value: &str) -> EnvResult<()>; /// Get an environment variable fn get_env(&self, key: &str) -> Option; /// Setup the environment for execution fn setup(&self, log: &Log, storage: &Storage) -> EnvResult<()>; /// Spin the environment up fn up(&self, log: &Log) -> EnvResult<()>; /// Spin the environment down fn down(&self, log: &Log) -> EnvResult<()>; /// Clean the environment fn clean(&self, log: &Log) -> EnvResult<()>; /// Write a file into the environment from a given reader fn write(&self, path: &Path, reader: Reader) -> EnvResult<()>; /// Unpack an archive into the environment from a given reader fn unpack(&self, path: &Path, reader: Reader) -> EnvResult<()>; /// Read or archive a path in the environment to a given writer fn read(&self, path: &Path, writer: Writer) -> EnvResult<()>; /// Run a single command in the environment fn cmd(&self, log: &Log, id: &Id, path: &Path, command: &str) -> EnvResult; /// Run a deferred command in the environment fn run(&self, log: &Log, id: &Id, path: &Path, command: &Command) -> EnvResult; /// Open a shell in the environment fn shell(&self, path: &Path) -> EnvResult<()>; } ``` -------------------------------- ### Defining Host Interface in WIT Source: https://github.com/awslabs/edo/blob/main/docs/components/plugin.md This WIT interface provides access to Edo's core functionality for plugins. It includes a `config` resource for accessing configuration nodes and utility functions (`info`, `warn`, `fatal`) for logging messages. It imports various resources and types from `common`, `storage`, `source`, `environment`, and `transform` modules to expose comprehensive host capabilities. ```WIT // host.wit interface host { use common.{ id, error, node, context, reader, writer }; use storage.{ artifact, layer, artifact-config, storage-manager }; use source.{ log }; use environment.{ environment, command, farm }; use transform.{ transform, handle, transform-status }; // Config access resource config { get: func(name: string) -> option; } // Host-provided utility functions info: func(message: string); warn: func(message: string); fatal: func(message: string); } ``` -------------------------------- ### Defining WebAssembly Transform Provider Interface (WIT) Source: https://github.com/awslabs/edo/blob/main/docs/components/transform.md This WIT (WebAssembly Interface Type) definition outlines the `transform-provider` interface for Edo. It defines enums for `transform-error` and `transform-status`, a type alias for `transform-result`, and records for `transform-options`. It also specifies functions for initializing, getting dependencies, preparing, staging, executing, and cleaning up transform operations, ensuring interoperability between host and Wasm modules. ```WIT // transform-provider.wit package edo:transform; interface transform-provider { // Error type for transform operations enum transform-error { init-failed, dependency-error, execution-failed, artifact-error, environment-error, io-error, not-supported, invalid-config, } // Type alias for transform results type transform-result = result; // Transform status enum transform-status { success(artifact-handle), retryable(option, string), failed(option, string), } // Artifact handle type artifact-handle = string; // Address type type addr = string; // Transform options record transform-options { name: string, description: string, env-farm: addr, debug-allowed: bool, parameters: list>, } // Initialize the transform provider init: func(options: string) -> transform-result<>; // Get transform dependencies get-dependencies: func() -> transform-result>; // Prepare transform prepare: func(sources: list>) -> transform-result<>; // Stage files into environment stage: func(env-handle: string) -> transform-result<>; // Execute transform execute: func(env-handle: string) -> transform-result; // Clean up transform resources cleanup: func() -> transform-result<>; } ``` -------------------------------- ### Defining the Storage Backend Trait in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/storage.md The `Backend` trait defines the interface for various storage backend implementations. It specifies core operations such as listing, checking for existence, opening, saving, deleting, copying, and pruning artifacts. Additionally, it provides methods for managing layers, including reading, starting, and finishing layer writes, ensuring a consistent API for different storage types. ```Rust /// Interface for storage backend implementations pub trait Backend { /// List all the ids stored in this backend fn list(&self) -> StorageResult>; /// Check if the backend has an artifact by this name fn has(&self, id: &Id) -> StorageResult; /// Open an artifact's manifest into memory fn open(&self, id: &Id) -> StorageResult; /// Save an artifact's manifest fn save(&self, artifact: &Artifact) -> StorageResult<()>; /// Delete this artifact and all its layers from the backend fn del(&self, id: &Id) -> StorageResult<()>; /// Copy an artifact to a new id fn copy(&self, from: &Id, to: &Id) -> StorageResult<()>; /// Prune any other artifact with a different digest from the backend fn prune(&self, id: &Id) -> StorageResult<()>; /// Prune any duplicate artifacts from the backend fn prune_all(&self) -> StorageResult<()>; /// Open a reader to a layer fn read(&self, layer: &Layer) -> StorageResult; /// Creates a new layer writer fn start_layer(&self) -> StorageResult; /// Saves and adds a layer to an artifact fn finish_layer(&self, media_type: &MediaType, platform: Option, writer: &Writer) -> StorageResult; } ``` -------------------------------- ### Defining the Environment Trait in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This Rust trait defines the core interface for managing execution environments. It includes methods for lifecycle management (setup, up, down, clean), file system operations (expand, create_dir, write, unpack, read), environment variable handling (set_env, get_env), and command execution (cmd, run, shell). Implementations of this trait provide concrete environment functionalities. ```rust /// Defines the interface that implementations of an environment must support pub trait Environment: Send + Sync + 'static { /// Expand the provided path to a root absolute path inside of the environment fn expand(&self, path: &Path) -> EnvResult; /// Create a directory inside the environment fn create_dir(&self, path: &Path) -> EnvResult<()>; /// Set environment variable fn set_env(&self, key: &str, value: &str) -> EnvResult<()>; /// Get an environment variable fn get_env(&self, key: &str) -> Option; /// Setup the environment for execution fn setup(&self, log: &Log, storage: &Storage) -> EnvResult<()>; /// Spin the environment up fn up(&self, log: &Log) -> EnvResult<()>; /// Spin the environment down fn down(&self, log: &Log) -> EnvResult<()>; /// Clean the environment fn clean(&self, log: &Log) -> EnvResult<()>; /// Write a file into the environment from a given reader fn write(&self, path: &Path, reader: Reader) -> EnvResult<()>; /// Unpack an archive into the environment from a given reader fn unpack(&self, path: &Path, reader: Reader) -> EnvResult<()>; /// Read or archive a path in the environment to a given writer fn read(&self, path: &Path, writer: Writer) -> EnvResult<()>; /// Run a single command in the environment fn cmd(&self, log: &Log, id: &Id, path: &Path, command: &str) -> EnvResult; /// Run a deferred command in the environment fn run(&self, log: &Log, id: &Id, path: &Path, command: &Command) -> EnvResult; /// Open a shell in the environment fn shell(&self, path: &Path) -> EnvResult<()>; } ``` -------------------------------- ### Implementing ContainerFarm Create in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md This method implements the `create` functionality for `ContainerFarm`, responsible for provisioning a new `ContainerEnvironment` instance. It determines the container's root path, ensures the directory exists, generates a unique container ID, and initializes a new `ContainerEnvironment` with the necessary configurations. ```Rust async fn create(&self, log: &Log, path: &Path) -> EnvResult> { let root_path = if path.is_absolute() { path.to_path_buf() } else { self.temp_dir.join(path) }; // Ensure directory exists if !root_path.exists() { log.debug(format!("Creating directory: {:?}", root_path)); std::fs::create_dir_all(&root_path)?; } // Generate a unique container ID let container_id = format!("edo-{}", uuid::Uuid::new_v4()); Ok(Box::new(ContainerEnvironment { container_id, runtime: self.runtime.clone(), base_image: self.base_image.clone(), root_path, options: self.options.clone(), env_vars: BTreeMap::new(), network_access: NetworkAccess::None, // Default to no network })) } } ``` -------------------------------- ### Creating Local Environment in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/environment.md The `create` method in `LocalFarm` is responsible for initializing a new `LocalEnvironment` instance. It determines the root path for the new environment, ensuring the directory exists, and returns a boxed `LocalEnvironment` ready for use. It handles both absolute and relative paths for environment creation. ```Rust async fn create(&self, log: &Log, path: &Path) -> EnvResult> { let root_path = if path.is_absolute() { path.to_path_buf() } else { self.temp_dir.join(path) }; // Ensure directory exists if !root_path.exists() { log.debug(format!("Creating directory: {:?}", root_path)); std::fs::create_dir_all(&root_path)?; } Ok(Box::new(LocalEnvironment { root_path, env_vars: BTreeMap::new(), })) } } ``` -------------------------------- ### Implementing a Basic Compile Transform in Rust Source: https://github.com/awslabs/edo/blob/main/docs/components/transform.md This Rust code defines the `CompileTransform` struct and implements the `Transform` trait for it. It manages compilation sources, dependencies, output naming, compiler details, and environment setup. Key methods include `get_unique_id` for hashing inputs to generate a unique build ID, `prepare` for fetching sources and dependencies, `stage` for unpacking artifacts into the build environment, and `transform` for executing the compilation command and handling build caching. ```Rust pub struct CompileTransform { sources: Vec, dependencies: Vec, output_name: String, compiler: String, flags: Vec, env_farm: Addr, } impl Transform for CompileTransform { async fn environment(&self) -> TransformResult { Ok(self.env_farm.clone()) } async fn get_unique_id(&self, ctx: &Handle) -> TransformResult { // Generate a hash based on inputs let mut hasher = Blake3::new(); // Hash compiler and flags hasher.update(self.compiler.as_bytes()); for flag in &self.flags { hasher.update(flag.as_bytes()); } // Hash source artifacts for src in &self.sources { // Get source artifact let source = ctx.get_sources().fetch_source(src).await?; hasher.update(source.config().id().digest().as_bytes()); } // Hash dependent artifacts for dep in &self.dependencies { let artifact = ctx.get_artifact(dep).await?; hasher.update(artifact.config().id().digest().as_bytes()); } // Finalize hash let hash = hasher.finalize(); // Create ID let id = IdBuilder::default() .name(self.output_name.clone()) .digest(hash.to_hex().as_str()) .build() .map_err(|e| error::IdSnafu { source: e }.build())?; Ok(id) } async fn depends(&self) -> TransformResult> { Ok(self.dependencies.clone()) } async fn prepare(&self, log: &Log, ctx: &Handle) -> TransformResult<()> { // Fetch all source artifacts for src in &self.sources { log.debug(format!("Fetching source: {}", src)); ctx.get_sources().fetch_source(src).await?; } // Ensure all dependencies are available for dep in &self.dependencies { log.debug(format!("Checking dependency: {}", dep)); ctx.get_artifact(dep).await?; } Ok(()) } async fn stage(&self, log: &Log, ctx: &Handle, env: &Environment) -> TransformResult<()> { // Create source directory let src_dir = PathBuf::from("src"); env.create_dir(&src_dir).await.map_err(|e| error::EnvironmentSnafu { source: e }.build())?; // Stage source files for src in &self.sources { log.info(format!("Staging source: {}", src)); let source = ctx.get_sources().fetch_source(src).await?; // Unpack source into src directory let reader = ctx.get_storage().safe_read(&source.layers()[0]).await .map_err(|e| error::StorageSnafu { source: e }.build())?; env.unpack(&src_dir, reader).await .map_err(|e| error::EnvironmentSnafu { source: e }.build())?; } // Create lib directory let lib_dir = PathBuf::from("lib"); env.create_dir(&lib_dir).await.map_err(|e| error::EnvironmentSnafu { source: e }.build())?; // Stage dependency artifacts for dep in &self.dependencies { log.info(format!("Staging dependency: {}", dep)); let artifact = ctx.get_artifact(dep).await?; // Unpack dependency into lib directory let reader = ctx.get_storage().safe_read(&artifact.layers()[0]).await .map_err(|e| error::StorageSnafu { source: e }.build())?; env.unpack(&lib_dir, reader).await .map_err(|e| error::EnvironmentSnafu { source: e }.build())?; } Ok(()) } async fn transform(&self, log: &Log, ctx: &Handle, env: &Environment) -> TransformStatus { // Get unique ID for output let id = match self.get_unique_id(ctx).await { Ok(id) => id, Err(e) => return TransformStatus::Failed(None, e), }; // Check if we already have this artifact in the build cache match ctx.get_storage().find_build(&id, true).await { Ok(Some(artifact)) => { log.info(format!("Using cached artifact: {}", id)); return TransformStatus::Success(artifact); }, Ok(None) => { log.info(format!("Building artifact: {}", id)); }, Err(e) => { log.error(format!("Error checking build cache: {}", e)); } } // Create output directory let out_dir = PathBuf::from("out"); if let Err(e) = env.create_dir(&out_dir).await { let error = error::EnvironmentSnafu { source: e }.build(); return TransformStatus::Failed(None, error); } // Build command let mut command = Command::new(log, &id, env); // Set up compiler flags let mut compile_cmd = format!("{} ", self.compiler); for flag in &self.flags { ``` -------------------------------- ### Edo System Architecture Overview - Mermaid Diagram Source: https://github.com/awslabs/edo/blob/main/docs/design.md This Mermaid diagram illustrates the high-level system architecture of the Edo build tool. It shows the central Build Engine interacting with various managers (Storage, Source, Environment, Transform), which in turn interface with their respective WebAssembly-based plugins. The diagram also depicts the Starlark Engine for parsing build files and the overall Plugin System with its WebAssembly Runtime and Plugin Registry. ```mermaid graph TD CLI[CLI Interface] --> Engine[Build Engine] Engine --> StorageMgr[Storage Manager] Engine --> SourceMgr[Source Manager] Engine --> EnvMgr[Environment Manager] Engine --> TransformMgr[Transform Manager] StorageMgr --> StoragePlugins[Storage Plugins] SourceMgr --> SourcePlugins[Source Plugins] EnvMgr --> EnvPlugins[Environment Plugins] TransformMgr --> TransformPlugins[Transform Plugins] StoragePlugins --> |Implements| StorageBackend[Storage Backend Interface] SourcePlugins --> |Implements| SourceProvider[Source Provider Interface] EnvPlugins --> |Implements| EnvProvider[Environment Provider Interface] TransformPlugins --> |Implements| TransformProvider[Transform Provider Interface] Engine --> StarlarkEngine[Starlark Engine] StarlarkEngine --> BuildFiles[".edo" Build Files] subgraph "Plugin System" WasmRuntime[WebAssembly Runtime] PluginRegistry[Plugin Registry] end StoragePlugins --> WasmRuntime SourcePlugins --> WasmRuntime EnvPlugins --> WasmRuntime TransformPlugins --> WasmRuntime Engine --> PluginRegistry PluginRegistry --> WasmRuntime ```