- Updated line count from ~260 to ~500 lines - Removed thiserror dependency reference (only anyhow now) - Added AptConfig documentation and configuration options - Updated API documentation with all new methods - Added configuration usage examples - Fixed rollback claim (now implemented, not missing) - Added configuration_usage example reference - All examples and tests verified working
8.9 KiB
APT Wrapper
A simple DNF-like API wrapper around APT for porting rpm-ostree to apt-ostree.
Purpose
This library provides a simple transaction interface that mimics DNF's imperative model, making it easier to adapt rpm-ostree code for Debian/Ubuntu systems.
Features
- Simple transaction interface:
add_package(),resolve(),commit(),rollback() - DNF-like API: Easy to port from rpm-ostree
- Version-based rollback: Track versions and restore previous states
- Minimal dependencies: Only
anyhow - ~500 lines total: Focused and maintainable
Quick Start
use apt_wrapper::{AptTransaction, init};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize
init()?;
// Create transaction
let mut tx = AptTransaction::new()?;
// Add packages
tx.add_package("vim")?;
tx.add_package("git")?;
// Resolve dependencies
tx.resolve()?;
// Commit transaction
tx.commit()?;
// If something goes wrong, rollback
// tx.rollback()?;
Ok(())
}
API
AptTransaction
pub struct AptTransaction {
packages: Vec<String>,
newly_installed: HashSet<String>, // packages that weren't installed before
upgraded: HashMap<String, String>, // package -> old_version for upgrades
config: AptConfig, // APT configuration options
}
impl AptTransaction {
pub fn new() -> Result<Self>; // Create new transaction (default config)
pub fn with_config(config: AptConfig) -> Result<Self>; // Create with custom config
pub fn for_testing() -> Result<Self>; // Create for testing (dry run)
pub fn add_package(&mut self, name: &str) -> Result<()>; // Add package
pub fn resolve(&self) -> Result<()>; // Resolve dependencies
pub fn commit(&mut self) -> Result<()>; // Commit transaction
pub fn rollback(&self) -> Result<()>; // Rollback transaction
pub fn packages(&self) -> &[String]; // Get package list
pub fn changed_packages(&self) -> Vec<String>; // Get changed packages
pub fn is_empty(&self) -> bool; // Check if empty
pub fn config(&self) -> &AptConfig; // Get configuration
pub fn set_config(&mut self, config: AptConfig); // Update configuration
pub fn enable_dry_run(&mut self); // Enable dry run mode
pub fn disable_dry_run(&mut self); // Disable dry run mode
pub fn enable_quiet(&mut self); // Enable quiet mode
pub fn disable_quiet(&mut self); // Disable quiet mode
}
Utility Functions
pub fn init() -> Result<()>; // Initialize
pub fn search_packages(query: &str) -> Result<Vec<String>>; // Search packages
pub fn is_package_installed(name: &str) -> Result<bool>; // Check installed
pub fn get_package_info(name: &str) -> Result<AptPackage>; // Get package info
Configuration
pub struct AptConfig {
pub no_install_recommends: bool, // Don't install recommended packages
pub no_install_suggests: bool, // Don't install suggested packages
pub assume_yes: bool, // Assume yes to all prompts
pub quiet: bool, // Quiet output
pub dry_run: bool, // Dry run mode (don't actually execute)
}
impl AptConfig {
pub fn new() -> Self; // Create with default values
pub fn for_testing() -> Self; // Create for testing (dry run + quiet)
}
Installation
Add to your Cargo.toml:
[dependencies]
apt-wrapper = "0.1.0"
Using as a Crate
Adding to Your Project
- Add the dependency to your
Cargo.toml:
[dependencies]
apt-wrapper = "0.1.0"
- Import the crate in your Rust code:
use apt_wrapper::{AptTransaction, AptConfig, init, search_packages, is_package_installed};
- Initialize the library before use:
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Always call init() first
init()?;
// Now you can use the library
let mut tx = AptTransaction::new()?;
// ... rest of your code
}
Crate Features
The crate provides these main components:
AptTransaction: Main transaction interface for package operationsAptConfig: Configuration options for APT behaviorinit(): Initialize the library (required before use)search_packages(): Search for available packagesis_package_installed(): Check if a package is installedget_package_info(): Get detailed package information
Error Handling
The crate uses anyhow::Result for error handling. All functions return Result<T, anyhow::Error>:
use anyhow::Result;
fn example() -> Result<()> {
let mut tx = AptTransaction::new()?; // Returns Result<AptTransaction, anyhow::Error>
tx.add_package("vim")?; // Returns Result<(), anyhow::Error>
tx.commit()?; // Returns Result<(), anyhow::Error>
Ok(())
}
Complete Example
Here's a complete example of using the crate:
use apt_wrapper::{AptTransaction, AptConfig, init, search_packages, is_package_installed};
use anyhow::Result;
fn main() -> Result<()> {
// Initialize the library
init()?;
// Search for packages
let editors = search_packages("editor")?;
println!("Available editors: {:?}", editors);
// Check if vim is installed
if !is_package_installed("vim")? {
println!("vim is not installed, installing...");
// Create transaction
let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.add_package("git")?;
// Resolve and commit
tx.resolve()?;
tx.commit()?;
println!("Packages installed successfully!");
} else {
println!("vim is already installed");
}
Ok(())
}
Building Your Project
Once you've added the dependency, build your project normally:
cargo build
cargo run
The crate will automatically handle APT integration and provide the DNF-like interface for your application.
Usage
Basic Transaction
use apt_wrapper::AptTransaction;
let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.add_package("git")?;
tx.resolve()?;
tx.commit()?;
Search Packages
use apt_wrapper::search_packages;
let packages = search_packages("editor")?;
for package in packages {
println!("Found: {}", package);
}
Check Installation
use apt_wrapper::is_package_installed;
if is_package_installed("vim")? {
println!("vim is installed");
}
Rollback Support
use apt_wrapper::AptTransaction;
let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.add_package("git")?;
tx.resolve()?;
// Commit the transaction
tx.commit()?;
// If something goes wrong later, rollback
tx.rollback()?;
// Check what was changed
println!("Changed packages: {:?}", tx.changed_packages());
Configuration Options
use apt_wrapper::{AptTransaction, AptConfig};
// Create with custom configuration
let config = AptConfig {
no_install_recommends: true,
no_install_suggests: true,
assume_yes: true,
quiet: false,
dry_run: true,
};
let mut tx = AptTransaction::with_config(config)?;
// Or modify configuration at runtime
tx.enable_dry_run();
tx.enable_quiet();
// For testing
let mut test_tx = AptTransaction::for_testing()?;
Testing
cargo test
Examples
cargo run --example simple_usage
cargo run --example configuration_usage
Design Philosophy
This wrapper is designed to be:
- Simple: Minimal API surface, easy to understand
- Focused: Only what's needed for apt-ostree porting
- DNF-like: Familiar interface for rpm-ostree developers
- Minimal: ~500 lines total, no complex abstractions
Differences from DNF
- APT is declarative: Dependencies are resolved automatically
- No complex repo management: APT uses simple text files
- Simpler error handling: APT provides clear error messages
- Transaction rollback: Implemented two-phase rollback (remove new, downgrade upgraded)
OSTree Integration
For atomic operations, use OSTree's native checkpoint/rollback:
// 1. Create OSTree checkpoint
let checkpoint = ostree_create_checkpoint()?;
// 2. Run APT transaction
let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.commit()?;
// 3. Commit or rollback based on result
if success {
ostree_commit_changes()?;
} else {
ostree_rollback_to_checkpoint(checkpoint)?;
}
License
MIT License - see LICENSE file for details.
Documentation
- FFI Bridge Documentation - C++ integration guide
- Rollback Implementation - How rollback functionality works
Contributing
This is a focused tool for apt-ostree. Contributions should maintain simplicity and focus on the core use case.