Files
container/Sources/NativeBuilder/docs/ContainerBuildIR/Operations.md
T
Kathryn Baldauf 16f2630126 Add initial native builder code (#399)
We're working on making a pure swift container image build system that
leverages containerization. This PR represents our initial design and
initial work towards this goal.

The native builder is still in active development and most of the
implementation has not been started or completed. We will be opening a
series of issues that represent various (but not necessarily all) pieces
of work that need to be done here.

There are docs included in this PR that describe the overall design of
each component and outline some of our goals. The easiest way to view
the docs by themselves (since this is a massive PR) is to look at the
docs commit in the `Commits` tab.

We'd love any feedback! 

@wlan0

---------

Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
2025-07-31 13:13:20 -07:00

231 lines
6.3 KiB
Markdown

# Operations Design
Operations are the fundamental building blocks of the ContainerBuildIR. This document explains their design, implementation patterns, and the rationale behind key decisions.
## Design Overview
### Operation Protocol
```swift
public protocol Operation: Sendable {
/// Unique type identifier
static var operationKind: OperationKind { get }
/// Instance operation kind
var operationKind: OperationKind { get }
/// Accept a visitor for traversal
func accept<V: OperationVisitor>(_ visitor: V) throws -> V.Result
}
// Operations also conform to Codable, Hashable, and Equatable
// through protocol extensions or direct conformance
```
### Core Operation Types
1. **ExecOperation** - Command execution (RUN)
2. **FilesystemOperation** - File manipulation (COPY, ADD)
3. **ImageOperation** - Base image specification (FROM)
4. **MetadataOperation** - Container metadata (ENV, LABEL, USER)
## Design Philosophy
### 1. Protocol-Based Design
**Why**: Using protocols instead of enums provides:
- Open extensibility for custom operations
- Type safety with associated types
- Clean separation of concerns
**Tradeoff**: Requires visitor pattern for exhaustive handling, but enables third-party extensions.
### 2. Immutable Operations
**Why**: All operations are immutable value types:
- Thread-safe by default (Sendable)
- Predictable behavior
- Easy to reason about
**Tradeoff**: Modifications require creating new instances, but prevents accidental mutations.
### 3. Self-Contained Operations
**Why**: Each operation contains all information needed for execution:
- No external state dependencies
- Simplifies serialization
- Enables operation reuse
**Tradeoff**: Some data duplication possible, but ensures operation independence.
## Implementing Custom Operations
### Step 1: Define the Operation
```swift
public struct CompressOperation: Operation, Codable, Hashable {
public let sourcePath: String
public let algorithm: CompressionAlgorithm
public let level: Int
public let metadata: OperationMetadata?
public static let operationKind = OperationKind(rawValue: "compress")
public var operationKind: OperationKind { Self.operationKind }
public func accept<V: OperationVisitor>(_ visitor: V) throws -> V.Result {
// Custom operations use visitUnknown
return try visitor.visitUnknown(self)
}
}
public enum CompressionAlgorithm: String, Codable, Sendable {
case gzip
case bzip2
case xz
case zstd
}
```
### Step 2: Visitor Pattern
The OperationVisitor protocol provides methods for all built-in operations:
```swift
public protocol OperationVisitor {
associatedtype Result
func visit(_ operation: ExecOperation) throws -> Result
func visit(_ operation: FilesystemOperation) throws -> Result
func visit(_ operation: ImageOperation) throws -> Result
func visit(_ operation: MetadataOperation) throws -> Result
func visitUnknown(_ operation: Operation) throws -> Result
}
```
Custom operations are handled through `visitUnknown`, which provides a default implementation that throws an error for unrecognized operations.
### Step 3: Operation Metadata
All operations can include metadata for debugging and analysis:
```swift
public struct OperationMetadata: Codable, Hashable, Sendable {
public let description: String?
public let location: SourceLocation?
public let annotations: [String: String]?
public let cacheConfig: CacheConfig?
}
public struct SourceLocation: Codable, Hashable, Sendable {
public let file: String?
public let line: Int?
public let column: Int?
}
```
### Step 4: Add Builder Support
```swift
extension StageBuilder {
@discardableResult
public func compress(_ path: String, algorithm: CompressionAlgorithm = .gzip, level: Int = 6) -> Self {
let operation = CompressOperation(
sourcePath: path,
algorithm: algorithm,
level: level
)
addNode(BuildNode(operation: operation))
return self
}
}
```
## Performance Considerations
### Memory Efficiency
Operations are designed to be lightweight:
- Use copy-on-write for collections
- Share common data through references
- Typical operation: 200-500 bytes
### Serialization Performance
- Codable implementation is optimized for speed
- Custom operations should implement efficient coding
- Consider using CodingKeys for stable serialization
## Best Practices
### 1. Keep Operations Focused
Each operation should do one thing well:
```swift
// Good: Single responsibility
ExecOperation(command: .shell("apt-get update"))
ExecOperation(command: .shell("apt-get install -y curl"))
// Avoid: Multiple unrelated commands
ExecOperation(command: .shell("apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*"))
```
### 2. Use Type-Safe Enums
Prefer enums over strings for operation parameters:
```swift
// Good: Type-safe
public enum PackageManager {
case apt
case yum
case apk
}
// Avoid: Stringly-typed
let packageManager = "apt-get"
```
### 3. Provide Meaningful Descriptions
Implement descriptive `description` properties:
```swift
public var description: String {
switch action {
case .copy:
return "Copy \(source.displayName) to \(destination)"
case .add:
return "Add \(source.displayName) to \(destination)"
case .remove:
return "Remove \(destination)"
}
}
```
### 4. Design for Extensibility
Consider future needs when designing operations:
```swift
public struct ExecOperation: Operation {
// Core functionality
public let command: Command
public let environment: Environment
// Extensibility points
public let metadata: [String: Any]? // For future extensions
public let extensions: OperationExtensions? // Type-safe extensions
}
```
## Future Directions
### Potential Enhancements
1. **Operation Macros**: Higher-level operations that expand to multiple primitives
2. **Conditional Operations**: Operations that execute based on runtime conditions
3. **Parallel Operations**: Explicit parallel execution hints
4. **Operation Fragments**: Reusable operation templates
### Maintaining Backward Compatibility
- New operation types can be added without breaking existing code
- Optional properties can be added to existing operations
- The visitor pattern allows graceful handling of unknown operations