Initial commit: APT-DNF Bridge workspace

- Core crate: Minimal shell-out implementation
- Advanced crate: Pluggable backends and enhanced features
- Main crate: Re-exports core + optional advanced features
- Feature flags: Users choose complexity level
- Examples: Working demonstrations of both approaches
- Documentation: Clear README explaining the structure

Implements the refined two-crate approach with workspace + feature flags.
This commit is contained in:
robojerk 2025-09-13 20:45:18 -07:00
commit 06cafa0366
24 changed files with 2790 additions and 0 deletions

View file

@ -0,0 +1,63 @@
//! Example demonstrating the atomicity limitations of the APT-DNF Bridge.
//!
//! This example shows how the APT-DNF Bridge provides transaction-like semantics
//! but does NOT provide true system-level atomicity.
use apt_dnf_bridge::{Package, Transaction};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("APT-DNF Bridge - Atomicity Notes Example");
println!("=====================================");
// Create a transaction
let mut tx = Transaction::new();
// Add some operations
let vim = Package::new("vim", "2:8.1.2269-1ubuntu5.14", "amd64");
let nano = Package::new("nano", "2.9.3-2", "amd64");
tx.add_install(vim).await?;
tx.add_install(nano).await?;
println!("Created transaction with {} operations", tx.len());
// Resolve dependencies (this will validate the transaction)
println!("Resolving dependencies...");
tx.resolve().await?;
println!("✓ Dependencies resolved successfully");
// IMPORTANT: The commit() method does NOT provide true atomicity
println!("\n⚠️ IMPORTANT ATOMICITY NOTES:");
println!(" • This transaction provides transaction-like semantics");
println!(" • It does NOT provide true system-level atomicity");
println!(" • If the operation fails midway, the system could be left in a corrupted state");
println!(" • For true atomicity, apt-ostree must handle this at a higher level");
println!(" • This might involve operating on a temporary directory and performing an atomic pivot_root");
println!("\nTransaction operations:");
for (i, op) in tx.operations().iter().enumerate() {
match op {
apt_dnf_bridge::Operation::Install(pkg) => {
println!(" {}: Install {}", i + 1, pkg.name);
}
apt_dnf_bridge::Operation::Remove(pkg) => {
println!(" {}: Remove {}", i + 1, pkg.name);
}
apt_dnf_bridge::Operation::Upgrade(pkg) => {
println!(" {}: Upgrade {}", i + 1, pkg.name);
}
}
}
println!("\nTo commit this transaction (commented out for safety):");
println!(" tx.commit().await?;");
println!("\nFor apt-ostree integration, consider:");
println!(" 1. Operating on a temporary directory");
println!(" 2. Performing all APT operations there");
println!(" 3. Using atomic pivot_root to switch to the new system state");
println!(" 4. Rolling back if any step fails");
Ok(())
}

View file

@ -0,0 +1,105 @@
//! Example demonstrating backend selection and switching.
use apt_dnf_bridge::{
BackendConfig, Package, TransactionV2, PackageDatabaseV2,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("APT-DNF Bridge - Backend Selection Example");
println!("======================================");
// 1. Auto-detect the best available backend
println!("1. Auto-detecting best backend...");
let tx = TransactionV2::new().await?;
let (backend_name, backend_version) = tx.backend_info();
println!(" Selected backend: {} v{}", backend_name, backend_version);
// 2. Create a transaction with shell backend explicitly
println!("\n2. Creating transaction with shell backend...");
let config = BackendConfig {
enable_logging: true,
use_cache: true,
max_cache_size: 1000,
command_timeout: Some(300),
};
let shell_tx = TransactionV2::with_shell_backend(config).await?;
let (shell_name, shell_version) = shell_tx.backend_info();
println!(" Shell backend: {} v{}", shell_name, shell_version);
// 3. Create a package database with mock backend for testing
println!("\n3. Creating package database with mock backend...");
let mock_config = BackendConfig {
enable_logging: true,
use_cache: true,
max_cache_size: 100,
command_timeout: Some(30),
};
let mut mock_db = PackageDatabaseV2::with_mock_backend(mock_config).await?;
let (_mock_name, mock_version) = mock_db.backend_info();
println!(" Mock backend: v{}", mock_version);
// 4. Demonstrate backend capabilities
println!("\n4. Testing backend capabilities...");
// Test mock backend
println!(" Testing mock backend:");
let packages = mock_db.find_packages("vim").await?;
println!(" Found {} packages matching 'vim'", packages.len());
if let Some(pkg_info) = mock_db.get_package_info("vim").await? {
println!(" Package info: {} {}", pkg_info.name, pkg_info.version);
}
// Test shell backend (if available)
println!(" Testing shell backend:");
let mut shell_db = PackageDatabaseV2::with_shell_backend(BackendConfig::default()).await?;
let shell_packages = shell_db.find_packages("vim").await?;
println!(" Found {} packages matching 'vim'", shell_packages.len());
// 5. Demonstrate transaction with different backends
println!("\n5. Testing transactions with different backends...");
let vim = Package::new("vim", "2:8.1.2269-1ubuntu5.14", "amd64");
// Mock transaction
let mut mock_tx = TransactionV2::with_mock_backend(BackendConfig::default()).await?;
mock_tx.add_install(vim.clone()).await?;
println!(" Mock transaction: {} operations", mock_tx.len());
let resolution = mock_tx.resolve().await?;
println!(" Mock resolution: resolvable={}, summary='{}'",
resolution.resolvable, resolution.summary);
// Shell transaction (if available)
let mut shell_tx = TransactionV2::with_shell_backend(BackendConfig::default()).await?;
shell_tx.add_install(vim).await?;
println!(" Shell transaction: {} operations", shell_tx.len());
let shell_resolution = shell_tx.resolve().await?;
println!(" Shell resolution: resolvable={}, summary='{}'",
shell_resolution.resolvable, shell_resolution.summary);
// 6. Show backend statistics
println!("\n6. Backend statistics:");
let mock_stats = mock_db.get_backend_stats().await?;
println!(" Mock backend stats:");
println!(" Commands executed: {}", mock_stats.commands_executed);
println!(" Packages queried: {}", mock_stats.packages_queried);
println!(" Cache hit rate: {:.2}%", mock_stats.cache_hit_rate * 100.0);
let shell_stats = shell_db.get_backend_stats().await?;
println!(" Shell backend stats:");
println!(" Commands executed: {}", shell_stats.commands_executed);
println!(" Packages queried: {}", shell_stats.packages_queried);
println!(" Cache hit rate: {:.2}%", shell_stats.cache_hit_rate * 100.0);
println!("\n✅ Backend selection example completed successfully!");
println!("\n💡 Key benefits of pluggable backends:");
println!(" • Start with simple shell backend (reliable)");
println!(" • Switch to libapt backend when available (more powerful)");
println!(" • Use mock backend for testing (fast, predictable)");
println!(" • Same API works with all backends");
Ok(())
}

54
examples/basic_usage.rs Normal file
View file

@ -0,0 +1,54 @@
//! Basic usage example for the APT-DNF Bridge crate.
use apt_dnf_bridge::{Package, Repository, Transaction};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("APT-DNF Bridge - Basic Usage Example");
println!("=================================");
// Create a package
let vim = Package::new("vim", "2:8.1.2269-1ubuntu5.14", "amd64");
println!("Created package: {}", vim.spec());
// Create a transaction with logging enabled
let mut tx = Transaction::new_with_logging();
println!("Created empty transaction with logging enabled");
// Add operations to the transaction
tx.add_install(vim.clone()).await?;
println!("Added install operation for: {}", vim.name);
// Check transaction status
println!("Transaction has {} operations", tx.len());
println!("Transaction is empty: {}", tx.is_empty());
// Note: In a real scenario, you would call:
// tx.resolve().await?; // Resolve dependencies
// tx.commit().await?; // Commit the transaction
// But for this example, we'll just show the structure
println!("Transaction operations:");
for (i, op) in tx.operations().iter().enumerate() {
match op {
apt_dnf_bridge::Operation::Install(pkg) => {
println!(" {}: Install {}", i + 1, pkg.name);
}
apt_dnf_bridge::Operation::Remove(pkg) => {
println!(" {}: Remove {}", i + 1, pkg.name);
}
apt_dnf_bridge::Operation::Upgrade(pkg) => {
println!(" {}: Upgrade {}", i + 1, pkg.name);
}
}
}
// Create a repository
let mut repo = Repository::new("debian", "http://deb.debian.org/debian")?;
repo.add_component("main");
repo.add_component("contrib");
println!("Created repository: {} at {}", repo.name, repo.url);
println!("Components: {:?}", repo.components);
Ok(())
}

60
examples/package_query.rs Normal file
View file

@ -0,0 +1,60 @@
//! Package querying example for the APT-DNF Bridge crate.
use apt_dnf_bridge::PackageDatabase;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("APT-DNF Bridge - Package Query Example");
println!("===================================");
let mut db = PackageDatabase::new_with_logging();
// Search for packages
println!("Searching for packages matching 'vim'...");
let packages = db.find_packages("vim").await?;
println!("Found {} packages:", packages.len());
for (i, pkg) in packages.iter().take(5).enumerate() {
println!(" {}: {}", i + 1, pkg.name);
}
if packages.len() > 5 {
println!(" ... and {} more", packages.len() - 5);
}
// Get detailed information about a specific package
println!("\nGetting detailed info for 'vim'...");
if let Some(pkg_info) = db.get_package_info("vim").await? {
println!("Package: {}", pkg_info.name);
println!("Version: {}", pkg_info.version);
println!("Architecture: {}", pkg_info.architecture);
if let Some(desc) = &pkg_info.description {
println!("Description: {}", desc);
}
if let Some(size) = pkg_info.size {
println!("Size: {} bytes", size);
}
} else {
println!("Package 'vim' not found");
}
// Check if a package is installed
println!("\nChecking if 'vim' is installed...");
let is_installed = db.is_installed("vim").await?;
println!("vim is installed: {}", is_installed);
// List some installed packages
println!("\nListing some installed packages...");
let installed = db.get_installed_packages().await?;
println!("Found {} installed packages", installed.len());
for (i, pkg) in installed.iter().take(10).enumerate() {
println!(" {}: {} {}", i + 1, pkg.name, pkg.version);
}
if installed.len() > 10 {
println!(" ... and {} more", installed.len() - 10);
}
Ok(())
}