Files
Sidhartha Mani 0885cdd6a9 Native Builder: DiffKey and Differ Procol (#482)
This PR introduces the `Differ` with methods:

```swift
// Differ protocol
func diff(base: Snapshot?, target: Snapshot) async throws -> Descriptor
func apply(descriptor: Descriptor, to base: Snapshot?) async throws -> Snapshot
```

It also introduces `DiffKey`, which is a MerkeTree based key for fast
diff computations between two dirs
2025-08-12 11:23:55 -07:00

199 lines
6.8 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerBuildIR
import ContainerizationOCI
import Foundation
/// A protocol for computing and storing filesystem diffs
///
/// The Differ is responsible for:
/// 1. Computing the delta between two filesystem states
/// 2. Serializing that delta into an OCI-compliant layer format
/// 3. Storing the layer to a content store
/// 4. Returning a descriptor that can be used in OCI manifests
public protocol Differ: Sendable {
/// The content store where diffs will be stored
var contentStore: any ContentStore { get }
/// Compute the difference between two snapshots and store it.
///
/// This method performs the complete diff workflow:
/// 1. Computes filesystem changes between base and target
/// 2. Creates a tar archive of those changes
/// 3. Applies the specified compression format
/// 4. Stores the result to the content store
/// 5. Returns a descriptor suitable for OCI manifests
///
/// - Parameters:
/// - base: The base snapshot (nil for initial/scratch layers)
/// - target: The target snapshot to diff against base
/// - format: The compression format to use for the layer
/// - Returns: A Descriptor containing the descriptor and statistics
/// - Throws: If diff computation or storage fails
func diff(
base: Snapshot?,
target: Snapshot
) async throws -> Descriptor
/// Apply a stored diff to a base snapshot to produce a target.
///
/// This is the inverse operation of computeAndStore, used when:
/// - Materializing snapshots from cached layers
/// - Applying patches during incremental builds
/// - Validating diff correctness
///
/// - Parameters:
/// - descriptor: The descriptor of the stored diff
/// - base: The base snapshot to apply the diff to (nil for scratch)
/// - Returns: The resulting snapshot after applying the diff
/// - Throws: If the diff cannot be applied
func apply(
descriptor: Descriptor,
to base: Snapshot?
) async throws -> Snapshot
}
/// Diff represents a filesystem diff entry.
///
/// - Additions and deletions only need the path previously, but we now surface
/// normalized attributes for both additions and modifications to avoid re-reading
/// from the OS during archive creation.
public enum Diff: Sendable, Equatable {
/// Details for an addition entry with surfaced attributes.
public struct Added: Sendable, Equatable {
public let path: BinaryPath
public let node: Modified.Node
public let permissions: FilePermissions?
public let size: Int64?
public let modificationTime: Date?
public let linkTarget: BinaryPath?
public let uid: UInt32?
public let gid: UInt32?
public let xattrs: [String: Data]?
public let devMajor: UInt32?
public let devMinor: UInt32?
public let nlink: UInt64?
public init(
path: BinaryPath,
node: Modified.Node,
permissions: FilePermissions?,
size: Int64?,
modificationTime: Date?,
linkTarget: BinaryPath?,
uid: UInt32?,
gid: UInt32?,
xattrs: [String: Data]?,
devMajor: UInt32?,
devMinor: UInt32?,
nlink: UInt64?
) {
self.path = path
self.node = node
self.permissions = permissions
self.size = size
self.modificationTime = modificationTime
self.linkTarget = linkTarget
self.uid = uid
self.gid = gid
self.xattrs = xattrs
self.devMajor = devMajor
self.devMinor = devMinor
self.nlink = nlink
}
}
/// Details for a modification entry.
public struct Modified: Sendable, Equatable {
/// The kind of modification detected. Derived from FileDiffResult.
public enum Kind: Sendable, Equatable {
case metadataOnly
case contentChanged
case typeChanged
case symlinkTargetChanged
}
/// Kind of filesystem node (target state).
public enum Node: Sendable, Equatable {
case regular
case directory
case symlink
case device
case fifo
case socket
}
public let path: BinaryPath
public let kind: Kind
public let node: Node
public let permissions: FilePermissions?
public let size: Int64?
public let modificationTime: Date?
public let linkTarget: BinaryPath?
public let uid: UInt32?
public let gid: UInt32?
public let xattrs: [String: Data]?
public let devMajor: UInt32?
public let devMinor: UInt32?
public let nlink: UInt64?
public init(
path: BinaryPath,
kind: Kind,
node: Node,
permissions: FilePermissions?,
size: Int64?,
modificationTime: Date?,
linkTarget: BinaryPath?,
uid: UInt32?,
gid: UInt32?,
xattrs: [String: Data]?,
devMajor: UInt32?,
devMinor: UInt32?,
nlink: UInt64?
) {
self.path = path
self.kind = kind
self.node = node
self.permissions = permissions
self.size = size
self.modificationTime = modificationTime
self.linkTarget = linkTarget
self.uid = uid
self.gid = gid
self.xattrs = xattrs
self.devMajor = devMajor
self.devMinor = devMinor
self.nlink = nlink
}
}
case added(Added)
case modified(Modified)
case deleted(path: BinaryPath)
}
/// POSIX file permission bits
public struct FilePermissions: Equatable, Sendable {
public let rawValue: UInt16
public init(rawValue: UInt16) { self.rawValue = rawValue }
}
public protocol ContentHasher: Sendable {
func hash(fileURL: URL) throws -> Data
}