# `Forecastle.Build`
[🔗](https://github.com/ausimian/forecastle/blob/1.0.0/lib/forecastle/build.ex#L1)

Reads application builds and compares their modules.

A build is the library directory from a compiled project or assembled release.
The module reads application versions, BEAM files, `.app` inventories,
behaviours and exports for `mix castle.appup` and `mix castle.appup.gen`.

Invalid library paths, incomplete application directories and broken entries
are errors. An application absent from one valid build remains a meaningful
add or remove result.

Module fingerprints combine `:beam_lib.md5/1` with persisted attributes. This
ignores changes caused only by BEAM stripping or documentation while retaining
explicit `@vsn` and other persisted-attribute changes.

# `fingerprint`

```elixir
@type fingerprint() :: {binary(), keyword()}
```

A module's BEAM md5 and persisted attributes, which the md5 does not cover.

# `side`

```elixir
@type side() :: %{
  vsn: binary(),
  inventory: MapSet.t(module()),
  listed?: boolean(),
  modules: %{required(module()) =&gt; fingerprint()},
  exports: %{required(module()) =&gt; MapSet.t({atom(), arity()})},
  ebin: binary(),
  resource: binary()
}
```

One application in one build.

`vsn` and `inventory` come from the `.app` resource; `modules` comes from the
BEAM files in `ebin`. The BEAM files show what changed, and the inventory is
what `:systools` can resolve.

# `t`

```elixir
@type t() :: %{describe: binary(), lib_dir: binary(), entries: [binary()]}
```

A build's library directory, read once.

`describe` names the build in diagnostics and `entries` lists `lib_dir`. See
`build/2`.

# `app_resource!`

```elixir
@spec app_resource!(binary(), atom()) :: {binary(), MapSet.t(module()), boolean()}
```

Reads an application's version and module inventory from its `.app` file.

The boolean result records whether the inventory is a valid list of atoms. A
missing or malformed inventory becomes empty so coverage checks fail safely;
relup generation remains responsible for full `.app` validation.

# `behaviours`

```elixir
@spec behaviours(side(), module()) :: [atom()]
```

Returns OTP behaviours declared in a module's persisted attributes.

Both `behaviour` and `behavior` attribute spellings are recognised. Missing
modules or attributes return an empty list.

# `build`

```elixir
@spec build(binary(), binary()) :: t()
```

Reads a build library directory.

Raises unless the path contains at least one application directory with an
`ebin` subdirectory. `describe` is used in diagnostics.

# `current!`

```elixir
@spec current!() :: t()
```

Compiles and reads the current project's library directory.

# `ebin`

```elixir
@spec ebin(t(), atom()) :: binary() | nil
```

Returns an application's `ebin` directory, or `nil` when it is absent.

The function supports Mix and release layouts, verifies the `.app` resource,
and rejects ambiguous or incomplete application entries.

# `exports?`

```elixir
@spec exports?(side(), module(), atom(), arity()) :: boolean()
```

Returns whether a module exports a function.

This supports draft warnings about missing callbacks; behaviour attributes,
not exports, determine the drafted instruction.

# `moved`

```elixir
@spec moved(%{required(module()) =&gt; fingerprint()}, %{
  required(module()) =&gt; fingerprint()
}) ::
  {[module()], [module()], [module()]}
```

Returns sorted lists of changed, added and removed modules.

# `resolve!`

```elixir
@spec resolve!(binary(), Forecastle.Baseline.level()) :: t()
```

Resolves a baseline and reads its library directory at the requested level.

# `side!`

```elixir
@spec side!(binary(), atom()) :: side()
```

Reads one application side of a comparison.

The result includes the application version, `.app` module inventory, BEAM
fingerprints, exports, and source paths.

---

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