Resolves references and expands recipes into one flat, acyclic,
de-duplicated step list for Composer.Runner.
Resolving a step reference
A bare string in a recipe names a step:
- In a recipe, a cookbook part that is one of the recipe's own
aliasesis first replaced by the real cookbook name ("lab:auth/passkeys"withlab: "composer_lab"is"composer_lab:auth/passkeys"); errors show the reference as written, with the alias noted. Aliases never apply inside an included recipe, nor to references given on the command line. An alias that is the name of an installed cookbook is an error. "cookbook:path"is looked up exactly. An unknown cookbook is an error that suggests adding the package that ships it.- An unqualified
"path"is looked up first in the referring recipe's own cookbook (on the command line, the current project's cookbook). - Otherwise every cookbook is searched. Exactly one match resolves to
it; more than one is an
:ambiguouserror listing every candidate. No match, for a name without a/, is the Hex package of that name ("oban"is"hex:oban") - unless the name is very close to an existing step id, which is reported as a likely typo instead. "hex:pkg"(or"hex:org.pkg"for a private repo) adds the package tomix.exsand runs its own installer, if it ships one. Its@source ("hex:pkg@path:../pkg", seeComposer.Ref) and the entry's options (argv,only,runtime, a source and git options) are carried into one{:install, package, opts}step, whoseoptsare canonical (seeComposer.Runner). An unqualified name that falls back to Hex keeps its options; one that names a step must not have package options ({:dep_opts_on_step, ...}).
A step in the referring cookbook wins over a Hex package of the same name,
so an unqualified name is never ambiguous with hex:.
{:use, ref} names a recipe, "cookbook:group/name". An unqualified
name without a / ({:use, "base"}) is the recipe of that name in the
same group as the referring recipe; nothing outside the group can match
it. Any other reference is resolved like a step reference, except that
local is searched too and there is no Hex fallback.
Entries
Every leaf becomes an entry:
%{ref: "core:setup/credo", label: "core:setup/credo",
step: {:module, Composer.Core.Steps.Setup.Credo},
origin: "local:phx/app", gates: [:dev_tools], if: nil, meta: %Composer.Step.Meta{}}origin is the recipe the step came from, gates the if: flags of the
includes it was reached through, and if its own flag. step is the
internal form the runner applies. An install's ref is always
"hex:pkg", whatever its source, so requires: ["hex:pkg"] matches it
by name; its label shows the source and installer argv
("hex:my_lib (path ../my_lib)"). alias is the cookbook alias the
reference was written with ("lab"), or nil; ref and label always
use the real cookbook name.
The result also lists the recipes visited, root first, as
%Composer.Recipe{} structs; declared_flags/1 collects their flags.
Rules
- Include-once. A recipe is expanded at most once per resolution; later includes of it are no-ops. A diamond is not a cycle.
- No cycles. Re-entering a recipe that is still being expanded is a
{:cycle, path}error, with the fullcookbook:pathchain. - De-duplication. A step equal to an earlier one is dropped (first
occurrence wins) and reported in
duplicates. Equality uses the resolved step, so"setup/credo"and"core:setup/credo"are duplicates. Steps that differ only in theirif:are distinct. - One install per package. Installs of one package with different
options (argv, source or dep options, or none against some) are a
{:conflicting_install, package, [{origin, label}, ...]}error, whatever theirif:flags: the runner adds one dependency per package. Identical ones are de-duplicated as above.
Flags
flags: :all (the default) follows every gated include, for validation,
listing and building the flag schema. flags: keyword follows a gated
include only when its flag is true; a skipped include is not marked as
expanded, so a later ungated include of it still expands. Leaf if:
flags are applied by active/2.
Summary
Functions
Drops entries whose own if: flag is off in opts.
Checks each of recipe's own entries on its own, as resolving it would,
and returns every error found (one per bad entry), in order: a step or
package reference, with the recipe's aliases expanded and its cookbook
first, or a {:use, ...} target. An alias that hides an installed
cookbook comes first. Included recipes are not followed; see
resolve_recipe/3 for what only the whole tree shows (cycles,
conflicting installs).
Returns the flags declared (in flags:) by every recipe a resolution
visited, root first. With flags: :all, these are all the --flags the
root recipe accepts.
Resolves one recipe reference to its qualified id.
Resolves one step reference to its metadata or Hex package, without
building an entry. Used by mix composer.step.show.
Returns every flag a resolved recipe understands: the include gates plus
the leaf if: flags.
Returns recipe's own {:use, ...} entries as {written, result}, in
order, where result is {:ok, recipe_id} or {:error, reason}.
Prepares cookbooks for lookups, for check_entries/2 and
included/2 over many recipes.
Resolves recipe ref and expands it.
Resolves step references from the command line into entries, in order.
Returns the runner steps of entries.
Resolves every recipe with flags: :all, returning the first error.
Returns advisory warnings for an ordered list of entries
Types
@type entry() :: %{ ref: String.t() | nil, label: String.t(), step: Composer.Runner.step(), origin: String.t() | nil, gates: [atom()], if: atom() | nil, alias: String.t() | nil, meta: Composer.Step.Meta.t() | nil }
@opaque index()
Cookbooks prepared for lookups, from index/1.
@type result() :: %{ root: String.t(), steps: [entry()], duplicates: [entry()], gates: [atom()], recipes: [Composer.Recipe.t()] }
Functions
Drops entries whose own if: flag is off in opts.
@spec check_entries([Composer.Cookbook.t()] | index(), Composer.Recipe.t()) :: [ term() ]
Checks each of recipe's own entries on its own, as resolving it would,
and returns every error found (one per bad entry), in order: a step or
package reference, with the recipe's aliases expanded and its cookbook
first, or a {:use, ...} target. An alias that hides an installed
cookbook comes first. Included recipes are not followed; see
resolve_recipe/3 for what only the whole tree shows (cycles,
conflicting installs).
cookbooks may be an index from index/1, to check many recipes.
Returns the flags declared (in flags:) by every recipe a resolution
visited, root first. With flags: :all, these are all the --flags the
root recipe accepts.
@spec find_recipe([Composer.Cookbook.t()], String.t(), String.t() | nil) :: {:ok, String.t()} | {:error, term()}
Resolves one recipe reference to its qualified id.
@spec find_step([Composer.Cookbook.t()], String.t(), String.t() | nil) :: {:ok, {:step, Composer.Step.Meta.t()} | {:hex, String.t()}} | {:error, term()}
Resolves one step reference to its metadata or Hex package, without
building an entry. Used by mix composer.step.show.
Returns every flag a resolved recipe understands: the include gates plus
the leaf if: flags.
@spec included([Composer.Cookbook.t()] | index(), Composer.Recipe.t()) :: [ {String.t(), {:ok, String.t()} | {:error, term()}} ]
Returns recipe's own {:use, ...} entries as {written, result}, in
order, where result is {:ok, recipe_id} or {:error, reason}.
cookbooks may be an index from index/1.
@spec index([Composer.Cookbook.t()] | index()) :: index()
Prepares cookbooks for lookups, for check_entries/2 and
included/2 over many recipes.
@spec resolve_recipe([Composer.Cookbook.t()], String.t(), keyword()) :: {:ok, result()} | {:error, term()}
Resolves recipe ref and expands it.
Options
:flags-:all(default) or a keyword of flag values:context- the cookbook name an unqualifiedrefis looked up in first, normally the current project's cookbook
@spec resolve_steps([Composer.Cookbook.t()], [String.t()], keyword()) :: {:ok, [entry()]} | {:error, term()}
Resolves step references from the command line into entries, in order.
Options
:context- as forresolve_recipe/3:argv- arguments passed to every step (from--); for a Hex package, its installer's arguments
@spec steps([entry()]) :: [Composer.Runner.step()]
Returns the runner steps of entries.
@spec validate_all( [Composer.Cookbook.t()], keyword() ) :: :ok | {:error, term()}
Resolves every recipe with flags: :all, returning the first error.
Pass only: [cookbook_name] to check some cookbooks' recipes only;
references are still resolved against all of cookbooks.
Returns advisory warnings for an ordered list of entries:
{:phase_order, entry, earlier}-entry's phase comes before the phase ofearlier, a step listed ahead of it{:provides_overlap, capability, entries}- several steps provide the same capability{:conflict, entry, capability, other}-entryconflicts with a capability thatotherprovides{:requires_order, entry, ref}-entryrequires stepref, which is not listed earlier