Example Scenarios
Scenario 1: Simple Project with Hex Dependencies
rebar.config:
{deps, [
jsx,
{cowboy, "2.9.0"}
]}.
Execution: rebar3 compile (first time, no lock file)
Flow:
-
Parse deps:
jsx(latest),cowboy"2.9.0" -
Query Hex registry for
jsxlatest → "3.1.0" -
Resolve
cowboy→ "2.9.0" -
Get cowboy's dependencies →
[cowlib, ranch] -
Resolve cowlib and ranch versions from cowboy's requirements
-
Build dependency graph:
my_app → [jsx, cowboy] cowboy → [cowlib, ranch] jsx → [] cowlib → [] ranch → [] -
Topological sort →
[cowlib, ranch, jsx, cowboy, my_app] -
Write lock file with all versions
-
Return deps to build:
[jsx, cowlib, ranch, cowboy]
rebar.lock created:
{"1.2.0",
[{<<"cowboy">>, {pkg, <<"cowboy">>, <<"2.9.0">>}, 0},
{<<"cowlib">>, {pkg, <<"cowlib">>, <<"2.11.0">>}, 1},
{<<"jsx">>, {pkg, <<"jsx">>, <<"3.1.0">>}, 0},
{<<"ranch">>, {pkg, <<"ranch">>, <<"1.8.0">>}, 1}]}.
[{pkg_hash, [...]}].
Scenario 2: Circular Dependency Detection
Setup:
app_a/rebar.config: {deps, [app_b]}.
app_b/rebar.config: {deps, [app_c]}.
app_c/rebar.config: {deps, [app_a]}.
Execution: rebar3 compile
Flow:
- Resolve app_a → depends on app_b
- Resolve app_b → depends on app_c
- Resolve app_c → depends on app_a
- Build graph: app_a → app_b → app_c → app_a
- Attempt topological sort → fails
- Find strongly connected components →
[[app_a, app_b, app_c]] - Return error
Error:
Dependency cycle(s) detected:
applications: app_a app_b app_c depend on each other
Scenario 3: Profile-Specific Dependencies
rebar.config:
{deps, [jsx]}.
{profiles, [
{test, [{deps, [meck, proper]}]}
]}.
Execution: rebar3 as test compile
Flow:
- Active profiles:
[default, test] - Level 0, default profile:
[jsx] - Level 0, test profile:
[meck, proper] - Resolve all at level 0
- Continue with transitive deps
- Build graph with all apps
- Topological sort
- Write lock file with ONLY
jsx(default profile only)
rebar.lock:
{"1.2.0",
[{<<"jsx">>, {pkg, <<"jsx">>, <<"3.1.0">>}, 0}]}.
[...].
Note: meck and proper NOT in lock file
Scenario 4: Using Checkout Dependencies
Setup:
my_app/
├── _checkouts/
│ └── my_dep/
│ ├── src/
│ └── rebar.config
├── rebar.config: {deps, [{my_dep, "1.0.0"}]}
└── src/
Execution: rebar3 compile
Flow:
- Parse deps:
my_depversion "1.0.0" - Discover checkout in
_checkouts/my_dep/ - Mark as checkout dependency
- Skip fetching (use local version)
- Resolve transitive deps from checkout's
rebar.config - In locking stage: skip adding to lock file
Output:
App my_dep is a checkout dependency and cannot be locked.
rebar.lock: Does NOT include my_dep
Scenario 5: Dependency Upgrade
Initial rebar.lock:
{"1.2.0",
[{<<"jsx">>, {pkg, <<"jsx">>, <<"3.0.0">>}, 0}]}.
Execution: rebar3 upgrade jsx
Flow:
- Set upgrade flag to
true - Ignore locked version for
jsx - Query Hex for latest version → "3.1.0"
- Fetch new version
- Resolve dependencies normally
- Update lock file with new version
New rebar.lock:
{"1.2.0",
[{<<"jsx">>, {pkg, <<"jsx">>, <<"3.1.0">>}, 0}]}.
Scenario 6: Git Dependency with Transitive Deps
rebar.config:
{deps, [
{my_git_dep, {git, "https://github.com/user/my_git_dep.git", {tag, "v1.0.0"}}}
]}.
my_git_dep/rebar.config:
{deps, [jsx]}.
Flow:
-
Resolve
my_git_depfrom Git -
Fetch and clone the repository
-
Parse
my_git_dep/rebar.config -
Find transitive dependency:
jsx -
Resolve
jsxfrom Hex -
Build graph:
my_app → [my_git_dep] my_git_dep → [jsx] jsx → [] -
Topological sort →
[jsx, my_git_dep, my_app]
rebar.lock:
{"1.2.0",
[{<<"jsx">>, {pkg, <<"jsx">>, <<"3.1.0">>}, 1},
{<<"my_git_dep">>, {git, "https://github.com/user/my_git_dep.git",
{ref, "abc123..."}}, 0}]}.
Note: jsx is level 1 (transitive), my_git_dep is level 0 (direct)