DependencyResolver

Struct DependencyResolver 

Source
pub struct DependencyResolver {
    pub source_manager: SourceManager,
    /* private fields */
}
Expand description

Core dependency resolver that transforms manifest dependencies into lockfile entries.

The DependencyResolver is the main entry point for dependency resolution. It manages source repositories, resolves version constraints, detects conflicts, and generates deterministic lockfile entries using a centralized SHA-based resolution strategy for optimal performance.

§SHA-Based Resolution Workflow

Starting in v0.3.2, the resolver uses VersionResolver for centralized version resolution that minimizes Git operations and maximizes worktree reuse:

  1. Collection: Gather all (source, version) pairs from dependencies
  2. Batch Resolution: Resolve all versions to commit SHAs in parallel
  3. SHA-Based Worktrees: Create worktrees keyed by commit SHA
  4. Deduplication: Multiple refs to same SHA share one worktree

§Configuration

The resolver can be configured in several ways:

§Thread Safety

The resolver is not thread-safe due to its mutable state during resolution. Create separate instances for concurrent operations.

Fields§

§source_manager: SourceManager

Manages Git repository operations, source URL resolution, and authentication.

The source manager handles:

  • Mapping source names to Git repository URLs
  • Git operations (clone, fetch, checkout) for dependency resolution
  • Authentication token management for private repositories
  • Source validation and configuration management

Implementations§

Source§

impl DependencyResolver

Source

pub async fn pre_sync_sources( &mut self, deps: &[(String, ResourceDependency)], ) -> Result<()>

Pre-syncs all sources needed for the given dependencies.

This method implements the first phase of the two-phase resolution architecture. It should be called during the “Syncing sources” phase to perform all Git clone/fetch operations upfront, before actual dependency resolution.

This separation provides several benefits:

  • Clear separation of network operations from version resolution logic
  • Better progress reporting with distinct phases
  • Enables batch processing of Git operations for efficiency
  • Allows the resolve_all() method to work purely with local cached data

After calling this method, the internal VersionResolver will have all necessary source repositories cached and ready for version-to-SHA resolution.

§Arguments
  • deps - A slice of tuples containing dependency names and their definitions. Only dependencies with Git sources will be processed.
§Example
let manifest = Manifest::new();
let cache = Cache::new()?;
let mut resolver = DependencyResolver::with_cache(manifest.clone(), cache);

// Get all dependencies from manifest
let deps: Vec<(String, ResourceDependency)> = manifest
    .all_dependencies()
    .into_iter()
    .map(|(name, dep)| (name.to_string(), dep.clone()))
    .collect();

// Phase 1: Pre-sync all sources (performs Git clone/fetch operations)
resolver.pre_sync_sources(&deps).await?;

// Phase 2: Now sources are ready for version resolution (no network I/O)
let resolved = resolver.resolve().await?;
§Two-Phase Resolution Pattern

This method is part of AGPM’s two-phase resolution architecture:

  1. Sync Phase (pre_sync_sources): Clone/fetch all Git repositories
  2. Resolution Phase (resolve or update): Resolve versions to SHAs locally

This pattern ensures all network operations happen upfront with clear progress reporting, while version resolution can proceed quickly using cached data.

§Errors

Returns an error if:

  • Source repository cloning or fetching fails
  • Network connectivity issues occur
  • Authentication fails for private repositories
  • Source names in dependencies don’t match configured sources
  • Git operations fail due to repository corruption or disk space issues
Source

pub async fn get_available_versions( &self, repo_path: &Path, ) -> Result<Vec<String>>

Get available versions (tags) for a repository.

Lists all tags from a Git repository, which typically represent available versions. This is useful for checking what versions are available for updates.

§Arguments
  • repo_path - Path to the Git repository (typically in the cache directory)
§Returns

A vector of version strings (tag names) available in the repository.

§Examples
use agpm_cli::resolver::DependencyResolver;
use agpm_cli::cache::Cache;

let cache = Cache::new()?;
let repo_path = cache.get_repo_path("community");
let resolver = DependencyResolver::new(manifest, cache, 10)?;

let versions = resolver.get_available_versions(&repo_path).await?;
for version in versions {
    println!("Available: {}", version);
}
Source

pub fn new(manifest: Manifest, cache: Cache) -> Result<Self>

Creates a new resolver using only manifest-defined sources.

This constructor creates a resolver that only considers sources defined in the manifest file. Global configuration sources from ~/.agpm/config.toml are ignored, which may cause resolution failures for private repositories that require authentication.

§Usage

Use this constructor for:

  • Public repositories only
  • Testing and development
  • Backward compatibility with older workflows

For production use with private repositories, prefer new_with_global().

§Errors

Returns an error if the cache cannot be created.

Source

pub async fn new_with_global(manifest: Manifest, cache: Cache) -> Result<Self>

Creates a new resolver with global configuration support.

This is the recommended constructor for most use cases. It loads both manifest sources and global sources from ~/.agpm/config.toml, enabling access to private repositories with authentication tokens.

§Source Priority

When sources are defined in both locations:

  1. Global sources (from ~/.agpm/config.toml) are loaded first
  2. Local sources (from agpm.toml) can override global sources

This allows teams to share project configurations while keeping authentication tokens in user-specific global config.

§Errors

Returns an error if:

  • The cache cannot be created
  • The global config file exists but cannot be parsed
  • Network errors occur while validating global sources
Source

pub fn with_cache(manifest: Manifest, cache: Cache) -> Self

Creates a new resolver with a custom cache.

This constructor is primarily used for testing and specialized deployments where the default cache location (~/.agpm/cache/) is not suitable.

§Use Cases
  • Testing: Isolated cache for test environments
  • CI/CD: Custom cache locations for build systems
  • Containers: Non-standard filesystem layouts
  • Multi-user: Shared cache directories
§Note

This constructor does not load global configuration. If you need both custom cache location and global config support, create the resolver with new_with_global() and manually configure the source manager.

Source

pub async fn resolve(&mut self) -> Result<LockFile>

Resolve all manifest dependencies into a deterministic lockfile.

This is the primary entry point for dependency resolution. It resolves all dependencies from the manifest (including transitive dependencies) and generates a complete lockfile with resolved versions and commit SHAs.

By default, this method enables transitive dependency resolution. Resources can declare their own dependencies via YAML frontmatter (Markdown) or JSON fields, which will be automatically discovered and resolved.

§Transitive Dependency Resolution

When enabled (default), the resolver:

  1. Resolves direct manifest dependencies
  2. Extracts dependency metadata from resource files
  3. Builds a dependency graph with cycle detection
  4. Resolves transitive dependencies in topological order
§Returns

A complete LockFile with all resolved dependencies including:

  • Resolved commit SHAs for reproducible installations
  • Checksums for integrity verification
  • Installation paths for all resources
  • Source repository information
§Errors

Returns an error if:

  • Source repositories cannot be accessed
  • Version constraints cannot be satisfied
  • Circular dependencies are detected
  • Resource files cannot be read or parsed
§Example
let manifest = Manifest::load("agpm.toml".as_ref())?;
let cache = Cache::new()?;
let mut resolver = DependencyResolver::new(manifest, cache)?;

// Resolve all dependencies including transitive ones
let lockfile = resolver.resolve().await?;

lockfile.save("agpm.lock".as_ref())?;
println!("Resolved {} total resources",
         lockfile.agents.len() + lockfile.snippets.len());
Source

pub async fn resolve_with_options( &mut self, enable_transitive: bool, ) -> Result<LockFile>

Resolve dependencies with configurable transitive dependency support.

This method provides fine-grained control over dependency resolution behavior, allowing you to disable transitive dependency resolution when needed. This is useful for debugging, testing, or when you want to install only direct dependencies without their transitive requirements.

§Arguments
  • enable_transitive - Whether to resolve transitive dependencies
    • true: Full transitive resolution (default behavior)
    • false: Only direct manifest dependencies
§Transitive Resolution Details

When enable_transitive is true:

  • Resources are checked for embedded dependency metadata
  • Markdown files (.md): YAML frontmatter between --- delimiters
  • JSON files (.json): Top-level dependencies field
  • Dependency graph is built with cycle detection
  • Dependencies are resolved in topological order

When enable_transitive is false:

  • Only dependencies explicitly declared in agpm.toml are resolved
  • Resource metadata is not extracted or processed
  • Faster resolution for known dependency trees
§Returns

A LockFile containing all resolved dependencies according to the configuration. When transitive resolution is disabled, the lockfile will only contain direct dependencies from the manifest.

§Errors

Returns an error if:

  • Source repositories are inaccessible or invalid
  • Version constraints conflict or cannot be satisfied
  • Circular dependencies are detected (when enable_transitive is true)
  • Resource files cannot be read or contain invalid metadata
  • Network operations fail during source synchronization
§Performance Considerations

Disabling transitive resolution (enable_transitive = false) can improve performance when:

  • You know all required dependencies are explicitly listed
  • Testing specific dependency combinations
  • Debugging dependency resolution issues
  • Working with large resources that have expensive metadata extraction
§Example
let manifest = Manifest::load("agpm.toml".as_ref())?;
let cache = Cache::new()?;
let mut resolver = DependencyResolver::new(manifest, cache)?;

// Resolve only direct dependencies without transitive resolution
let lockfile = resolver.resolve_with_options(false).await?;

println!("Resolved {} direct dependencies",
         lockfile.agents.len() + lockfile.snippets.len());
§See Also
  • resolve(): Convenience method that enables transitive resolution by default
  • DependencyGraph: Graph structure used for cycle detection and ordering
  • DependencySpec: Specification format for transitive dependencies
Source

pub async fn update( &mut self, existing: &LockFile, deps_to_update: Option<Vec<String>>, ) -> Result<LockFile>

Updates an existing lockfile with new or changed dependencies.

This method performs incremental dependency resolution by comparing the current manifest against an existing lockfile and updating only the specified dependencies (or all if none specified).

§Update Strategy

The update process follows these steps:

  1. Selective Resolution: Only resolve specified dependencies
  2. Preserve Existing: Keep unchanged dependencies from existing lockfile
  3. In-place Updates: Replace matching entries with new versions
  4. New Additions: Append newly added dependencies
§Use Cases
  • Selective Updates: Update specific outdated dependencies
  • Security Patches: Update dependencies with known vulnerabilities
  • Feature Updates: Pull latest versions for active development
  • Manifest Changes: Reflect additions/modifications to agpm.toml
§Parameters
  • existing: Current lockfile to update
  • deps_to_update: Optional list of specific dependencies to update. If None, all dependencies are updated.
  • progress: Optional progress bar for user feedback
§Returns

A new LockFile with updated dependencies. The original lockfile structure is preserved, with only specified entries modified.

§Algorithm Complexity
  • Time: O(u + s·log(t)) where u = dependencies to update
  • Space: O(n) where n = total dependencies in lockfile
§Performance Benefits
  • Network Optimization: Only syncs sources for updated dependencies
  • Cache Utilization: Reuses existing source repositories
  • Parallel Processing: Updates multiple dependencies concurrently
§Errors

Update can fail due to:

  • Network issues accessing source repositories
  • Version constraints that cannot be satisfied
  • Authentication failures for private sources
  • Corrupted or inaccessible cache directories
Source

pub fn verify(&mut self) -> Result<()>

Verifies that all dependencies can be resolved without performing resolution.

This method performs a “dry run” validation of the manifest to detect issues before attempting actual resolution. It’s faster than full resolution since it doesn’t clone repositories or resolve specific versions.

§Validation Steps
  1. Local Path Validation: Verify local dependencies exist (for absolute paths)
  2. Source Validation: Ensure all referenced sources are defined
  3. Constraint Validation: Basic syntax checking of version constraints
§Validation Scope
  • Manifest Structure: Validate TOML structure and required fields
  • Source References: Ensure all sources used by dependencies exist
  • Local Dependencies: Check absolute paths exist on filesystem
§Performance

Verification is designed to be fast:

  • No network operations (doesn’t validate remote repositories)
  • No Git operations (doesn’t check if versions exist)
  • Only filesystem access for absolute local paths
§Parameters
  • progress: Optional progress bar for user feedback
§Returns

Ok(()) if all dependencies pass basic validation.

§Errors

Verification fails if:

  • Local dependencies reference non-existent absolute paths
  • Dependencies reference undefined sources
  • Manifest structure is invalid or corrupted
§Note

Successful verification doesn’t guarantee resolution will succeed, since network issues or missing versions can still cause failures. Use this method for fast validation before expensive resolution operations.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,