Functions & API Calls
rebar_prv_install_deps:do/1
Purpose: Main entry point for dependency resolution
Signature:
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
Arguments:
State(rebar_state:t()): Current state with configuration
Returns:
{ok, State}: Dependencies resolved and state updated{error, Reason}: Resolution failed
Flow:
- Get current profiles
- Get project apps
- Check if upgrade mode is enabled
- Resolve deps per profile via
deps_per_profile/3 - Update state with all deps
- Build and update code paths
- Detect cycles via
find_cycles/1 - Determine compile order via
cull_compile/2 - Update state with
deps_to_build
Called From: Provider system (as dependency of lock and compile)
deps_per_profile/3
Purpose: Collect and resolve dependencies for all active profiles
Signature:
-spec deps_per_profile([atom()], boolean(), rebar_state:t()) ->
{[rebar_app_info:t()], rebar_state:t()}.
Arguments:
Profiles([atom()]): Active profiles (e.g.,[default, test])Upgrade(boolean()): Whether to upgrade dependenciesState(rebar_state:t()): Current state
Returns:
{Apps, State}: Resolved dependencies and updated state
Flow:
- Get locks from state
- For each profile, get parsed dependencies at level 0
- Create
RootSeenset with project app names - Call
handle_profile_level/7for traversal
handle_profile_level/7
Purpose: Level-order traversal of dependency tree across all profiles
Signature:
-spec handle_profile_level(
[{Profile, Deps, Level}],
Apps,
RootSeen,
Seen,
Upgrade,
Locks,
State
) -> {Apps, State} when
Profile :: atom(),
Deps :: [rebar_app_info:t()],
Level :: integer(),
Apps :: [rebar_app_info:t()],
RootSeen :: sets:set(),
Seen :: sets:set(),
Upgrade :: boolean(),
Locks :: [term()],
State :: rebar_state:t().
Arguments:
- Profile/Deps/Level tuples: Dependencies per profile at each level
Apps: Accumulated resolved dependenciesRootSeen: Set of top-level app names (never process as deps)Seen: Set of already-processed dependency namesUpgrade: Whether upgradingLocks: Lock file dataState: Current state
Returns: {Apps, State} with all resolved dependencies
Algorithm:
For each {Profile, Deps, Level}:
For each Dep in Deps:
If Dep is in RootSeen:
Skip (it's a top-level app)
Else if Dep is in Seen:
Check for version conflicts, warn if needed
Else:
Lock the dependency
Fetch the dependency
Parse the dep's own dependencies
Add new deps to next level
If new deps were found:
Append {Profile, NewDeps, Level+1} to queue
Process next level
update_dep/9
Purpose: Process a single dependency
Signature:
-spec update_dep(
AppInfo,
Profile,
Level,
Deps,
Apps,
State,
Upgrade,
Seen,
Locks
) -> {NewDeps, NewApps, NewState, NewSeen} when
AppInfo :: rebar_app_info:t(),
Profile :: atom(),
Level :: integer(),
Deps :: [rebar_app_info:t()],
Apps :: [rebar_app_info:t()],
State :: rebar_state:t(),
Upgrade :: boolean(),
Seen :: sets:set(),
Locks :: [term()].
Arguments:
AppInfo: Dependency to processProfile: Current profileLevel: Current level in dependency treeDeps: Accumulated dependencies for next levelApps: All resolved apps so farState: Current stateUpgrade: Upgrade flagSeen: Set of seen dependency namesLocks: Lock data
Returns: Updated accumulator tuple
Flow:
- Get dependency name
- Check if already seen
- If seen: check for conflicts, possibly warn
- If not seen:
- Lock the dependency
- Fetch/verify the dependency
- Handle the dependency (parse its deps)
- Add to accumulated apps
- Add transitive deps to next level
maybe_lock/5
Purpose: Add dependency to lock list if appropriate
Signature:
-spec maybe_lock(Profile, AppInfo, Seen, State, Level) -> {NewSeen, NewState} when
Profile :: atom(),
AppInfo :: rebar_app_info:t(),
Seen :: sets:set(),
State :: rebar_state:t(),
Level :: integer().
Arguments:
Profile: Current profileAppInfo: Dependency to potentially lockSeen: Set of seen dependenciesState: Current stateLevel: Depth in dependency tree
Returns: {NewSeen, NewState} with updated lock
Logic:
- Skip if checkout dependency
- Skip if not in default profile
- If already in lock at deeper level, replace with shallower
- Otherwise add to lock list
- Always add to seen set
find_cycles/1
Purpose: Detect circular dependencies
Signature:
-spec find_cycles([rebar_app_info:t()]) ->
{no_cycle, Sorted} | {cycles, Cycles} | {error, Error} when
Sorted :: [rebar_app_info:t()],
Cycles :: [[binary()]],
Error :: term().
Arguments:
Apps([rebar_app_info:t()]): All applications (project + deps)
Returns:
{no_cycle, Sorted}: No cycles; sorted topologically{cycles, Cycles}: Circular dependencies detected{error, Error}: Other error
Flow:
- Call
rebar_digraph:compile_order/1 - Which creates digraph and calls
digraph_utils:topsort/1 - If sort succeeds: return sorted list
- If sort fails: find strongly connected components
- Filter components with length > 1 (these are cycles)
- Return cycles
rebar_digraph:compile_order/1
Purpose: Build dependency graph and return topological sort
Signature:
-spec compile_order([rebar_app_info:t()]) ->
{ok, [rebar_app_info:t()]} | {error, no_sort | {cycles, [[binary()]]}}.
Arguments:
Apps([rebar_app_info:t()]): Applications to sort
Returns:
{ok, Sorted}: Topologically sorted applications{error, {cycles, Cycles}}: Circular dependencies found{error, no_sort}: Topological sort failed for other reason
Flow:
- Create new digraph
- For each app:
- Add vertex with app name
- Get all dependencies (from
applicationslist anddepsconfig) - Add edges from app to each dependency
- Call
digraph_utils:topsort/1 - If successful: reverse the list (dependencies first)
- If failed: determine if cyclic, extract cycles
- Delete digraph
- Return result
Example Graph:
my_app → [cowboy, jsx]
cowboy → [cowlib, ranch]
cowlib → []
ranch → []
jsx → []
Sorted: [cowlib, ranch, jsx, cowboy, my_app]
all_apps_deps/1
Purpose: Get all dependencies for an application
Signature:
-spec all_apps_deps(rebar_app_info:t()) -> [binary()].
Arguments:
App(rebar_app_info:t()): Application to analyze
Returns: List of dependency names (binaries)
Flow:
- Get
applicationslist from.appfile (runtime deps) - Get
depslist fromrebar.config(build deps) - Convert all to binaries
- Sort and merge (union)
Why Both Sources:
applications: Runtime dependencies declared in.appdeps: Build-time dependencies fromrebar.config- Union ensures all dependencies are considered for build order
cull_compile/2
Purpose: Filter dependency list to those needing compilation
Signature:
-spec cull_compile([rebar_app_info:t()], [rebar_app_info:t()]) -> [rebar_app_info:t()].
Arguments:
TopSortedDeps([rebar_app_info:t()]): All apps in compile orderProjectApps([rebar_app_info:t()]): Project's own applications
Returns: Dependencies that need compilation
Flow:
- Remove project apps from sorted list (they're compiled separately)
- Drop dependencies from start of list until finding one that needs compile
- Return remaining list
Logic for "needs compile":
- Checkout dependencies always need compile
- Package dependencies from Hex usually don't (pre-compiled)
- Source dependencies (Git, etc.) need compile
rebar_prv_lock:do/1
Purpose: Write dependency locks to rebar.lock
Signature:
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
Arguments:
State(rebar_state:t()): Current state with resolved deps
Returns: {ok, State} with locks saved
Flow:
- Check if running in default profile (only lock in default)
- Get old locks from state
- Build new locks via
build_locks/1 - Sort locks alphabetically
- Write to
rebar.lockif changed - Update state with new locks
- Report useless locks (removed dependencies)
- Report checkout dependencies (can't be locked)
build_locks/1
Purpose: Convert state's lock data to lock file format
Signature:
-spec build_locks(rebar_state:t()) -> [lock_entry()].
Arguments:
State(rebar_state:t()): Current state
Returns: List of lock entries
Lock Entry Format:
{Name :: binary(),
Source :: lock_source(),
Level :: integer()}
Example:
{<<"cowboy">>,
{git, "https://github.com/ninenines/cowboy.git",
{ref, "abc123def456..."}},
0}
Flow:
- Get all locked deps from state
- Filter out checkout dependencies
- For each dep:
- Get name
- Get lock source via
rebar_fetch:lock_source/2 - Get dependency level
- Create tuple