Foundry.Context.ModuleDiscovery (Foundry v0.3.0)

Copy Markdown View Source

Discovers a target project's modules by scanning its compiled BEAM files.

One prefix was never enough

This used to take a single project_name string, derive one OTP application from it, and scan that application's ebin only. That is right for a single Mix project and wrong for every umbrella: an umbrella named Axonn compiles Axonn, AxonnWeb and AshSandbox into three separate ebin directories, and the graph came back holding a third of the system with no indication that the rest existed. all_project_modules/2 now takes a list of module prefixes -- manifest[:module_prefixes], defaulting to [project_name] -- and scans the ebin of each. A prefix whose build directory is absent is reported through missing_prefixes/2 rather than silently contributing nothing (ADR-036 requires absence be stated).

Loading modules is safe here and only here

:code.load_abs/1 puts the target's modules into this VM. ADR-036 confines that to the mix foundry.project.context subprocess, which runs inside the target project and exits when the map is written. The Studio never calls this module; it reads the JSON that subprocess produced.

Summary

Functions

Every module in the target project, across all of its prefixes.

The prefixes with no compiled ebin under project_root.

The module prefixes a manifest declares.

Functions

all_project_modules(project_root, prefixes)

@spec all_project_modules(String.t(), String.t() | [String.t()]) :: [atom()]

Every module in the target project, across all of its prefixes.

Accepts a single prefix string for the common one-app case.

missing_prefixes(project_root, prefixes)

@spec missing_prefixes(String.t(), String.t() | [String.t()]) :: [String.t()]

The prefixes with no compiled ebin under project_root.

A non-empty answer means the target was not fully compiled, or the manifest names an application that does not exist. Either way it is a diagnostic the map must carry, because the alternative is a graph that is quietly partial.

prefixes(manifest)

@spec prefixes(keyword()) :: [String.t()]

The module prefixes a manifest declares.

module_prefixes when it is set, [project_name] otherwise -- so a single-application project needs no new key and an umbrella declares its applications once, in the manifest, rather than having them guessed here.