# `MixAffected.Graph`
[🔗](https://github.com/mnox/mix_affected/blob/v0.1.0/lib/mix_affected/graph.ex#L1)

File-level dependency graph of a compiled Mix project.

Every path in a graph is relative to the project root (for an umbrella,
that is the umbrella root, so paths look like `apps/web/lib/web/page.ex`).
An edge `from -> to` of kind `t:kind/0` means `from` depends on `to`:

  * `:compile` - `from` needs `to` at compile time (`use`, `require` plus a
    macro call, `import`, module body calls)
  * `:export` - `from` depends on `to`'s exports, typically its struct
  * `:runtime` - `from` calls `to` at runtime

When xref reports more than one kind for a pair, the strongest is kept
(compile, then export, then runtime), as Mix itself does.

## Building

`build/2` shells out to `mix xref graph` in the project directory with
`MIX_ENV=test` (by default), so files the project compiles only in the test
env, such as `test/support`, are part of the graph. See `build/2` for the
options and `t:error/0` for every way it fails open.

## Selecting

`dependents/2` returns the changed files plus every file that reaches one of
them through any chain of edges of any kind. Cycles are fine.

# `adjacency`

```elixir
@type adjacency() :: %{optional(path()) =&gt; %{optional(path()) =&gt; kind()}}
```

Outgoing edges per file: `%{from => %{to => kind}}`.

# `error`

```elixir
@type error() ::
  {:xref_failed, non_neg_integer(), String.t()}
  | :mix_not_found
  | {:output_unreadable, File.posix()}
  | {:parse_error, term()}
  | :empty_graph
  | {:unresolved_paths, [path()]}
  | {:ambiguous_paths, [path()]}
  | {:unknown_files, [path()]}
```

Why a graph or a selection could not be produced. Every one of them means
"run the full suite"; none is ever an empty selection.

  * `{:xref_failed, exit_status, output}` - `mix xref graph` exited non-zero
    (compile error, unsupported `--format`, missing deps, ...)
  * `:mix_not_found` - no `mix` executable on `PATH`
  * `{:output_unreadable, posix}` - xref succeeded but its output file
    could not be read
  * `{:parse_error, detail}` - xref output was not in the expected shape
  * `:empty_graph` - xref reported no source files at all
  * `{:unresolved_paths, paths}` - xref reported paths that are not files
    under the project root (or, for an umbrella, under any of its apps;
    usually an umbrella built without `:apps_paths`)
  * `{:ambiguous_paths, paths}` - umbrella children share these relative
    paths; xref merges same-named files and drops edges, so the graph
    cannot be trusted
  * `{:unknown_files, paths}` - `dependents/2` was asked about files that
    are not compiled sources in the graph (new, deleted, or non-source files)

# `kind`

```elixir
@type kind() :: :compile | :export | :runtime
```

How `from` depends on `to` in an edge `from -> to`.

# `path`

```elixir
@type path() :: String.t()
```

A project-root-relative file path, e.g. `"lib/my_app/user.ex"`.

# `t`

```elixir
@type t() :: %MixAffected.Graph{
  dependents: %{optional(path()) =&gt; MapSet.t(path())},
  edges: adjacency(),
  files: MapSet.t(path()),
  root: Path.t(),
  source: module() | nil
}
```

  * `:root` - absolute project root every path is relative to
  * `:source` - the `MixAffected.Graph.Source` adapter that produced it
    (`nil` for `new/2`)
  * `:files` - every source file in the graph
  * `:edges` - outgoing edges, `%{from => %{to => kind}}`
  * `:dependents` - reverse edges, `%{to => MapSet of from}`

# `build`

```elixir
@spec build(Path.t(), keyword()) :: {:ok, t()} | {:error, error()}
```

Builds the graph of the compiled Mix project at `project_dir`.

Options:

  * `:source` - the `MixAffected.Graph.Source` adapter; defaults to
    `MixAffected.Graph.Source.default(System.version())`
  * `:apps_paths` - for an umbrella, `%{app => relative_app_path}` as
    returned by `Mix.Project.apps_paths/0`. Defaults to
    `Mix.Project.apps_paths()` when `project_dir` is the current Mix
    project, otherwise `nil` (not an umbrella)
  * `:mix_env` - `MIX_ENV` for the xref run, default `"test"`
  * `:mix_executable` - path to `mix`, default the one on `PATH`

The project is compiled first if it is stale (xref does that itself).

# `dependents`

```elixir
@spec dependents(t(), [Path.t()]) :: {:ok, MapSet.t(path())} | {:error, error()}
```

Returns the changed files plus every file that transitively depends on any
of them, over edges of every kind.

`changed` may be root-relative or absolute paths under the root; the result
is always root-relative. If any changed file is not a source in the graph the
answer is `{:error, {:unknown_files, sorted_paths}}`: callers decide what
non-source changes mean, the graph never guesses.

# `edge`

```elixir
@spec edge(t(), path(), path()) :: kind() | nil
```

The kind of the edge `from -> to`, or `nil` when `from` does not depend on `to` directly.

# `edges`

```elixir
@spec edges(t()) :: [{path(), path(), kind()}]
```

Every edge as `{from, to, kind}`, sorted.

# `files`

```elixir
@spec files(t()) :: MapSet.t(path())
```

Every source file in the graph.

# `member?`

```elixir
@spec member?(t(), Path.t()) :: boolean()
```

Whether `path` (root-relative or absolute under the root) is a source in the graph.

# `new`

```elixir
@spec new(Path.t(), adjacency()) :: t()
```

Builds a graph from root-relative adjacency. Files that only appear as edge
targets become files of the graph too.

# `put_edges`

```elixir
@spec put_edges(t(), [{path(), path(), kind()}]) :: t()
```

Adds edges `{from, to, kind}` (keeping the stronger kind when `from -> to`
exists), for dependencies only the caller knows about.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
