Current section

Files

Jump to
espec lib mix utils stale_compatible.ex
Raw

lib/mix/utils/stale_compatible.ex

defmodule Mix.Utils.StaleCompatible do
@moduledoc false
# This module was written to deal with the incompatibilies between Elixir <= 1.9
# and the release of Elixir 1.10. It is expected to eventually go away if certain
# private APIs in Elixir become public or we shift away from reliance on
# the private APIs.
require Mix.Compilers.Elixir, as: CE
import Record
alias Mix.Utils.Stale
defrecordp :source,
source: nil,
compile_references: [],
runtime_references: [],
external: []
def parallel_require_callbacks(pid, cwd),
do:
parse_version()
|> parallel_require_callbacks(pid, cwd)
def tests_with_changed_references(test_sources),
do:
parse_version()
|> tests_with_changed_references(test_sources)
## Test changed dependency resolution
cond do
Version.match?(System.version(), "< 1.16.0") ->
def tests_with_changed_references(%Version{major: 1, minor: minor} = version, test_sources)
when minor >= 10 and minor < 16 do
test_manifest = Stale.manifest()
[elixir_manifest] = Mix.Tasks.Compile.Elixir.manifests()
if Mix.Utils.stale?([elixir_manifest], [test_manifest]) do
compile_path = Mix.Project.compile_path()
{elixir_modules, elixir_sources} = apply(CE, :read_manifest, [elixir_manifest])
stale_modules =
for CE.module(module: module) <- elixir_modules,
beam = Path.join(compile_path, Atom.to_string(module) <> ".beam"),
Mix.Utils.stale?([beam], [test_manifest]),
do: module,
into: MapSet.new()
stale_modules =
find_all_dependent_on(version, stale_modules, elixir_sources, elixir_modules)
for module <- stale_modules,
source(source: source, runtime_references: r, compile_references: c) <-
test_sources,
module in r or module in c,
do: source,
into: MapSet.new()
else
MapSet.new()
end
end
true ->
def tests_with_changed_references(%Version{major: 1} = version, test_sources) do
test_manifest = Stale.manifest()
[elixir_manifest] = Mix.Tasks.Compile.Elixir.manifests()
if Mix.Utils.stale?([elixir_manifest], [test_manifest]) do
compile_path = Mix.Project.compile_path()
{elixir_modules, elixir_sources} = apply(CE, :read_manifest, [elixir_manifest])
stale_modules =
for {module, _} <- elixir_modules,
beam = Path.join(compile_path, Atom.to_string(module) <> ".beam"),
Mix.Utils.stale?([beam], [test_manifest]),
do: module,
into: MapSet.new()
stale_modules =
find_all_dependent_on(version, stale_modules, elixir_sources, elixir_modules)
for module <- stale_modules,
source(source: source, runtime_references: r, compile_references: c) <-
test_sources,
module in r or module in c,
do: source,
into: MapSet.new()
else
MapSet.new()
end
end
end
## ParallelRequire callback: Handled differently depending on Elixir version
def parallel_require_callbacks(%Version{major: 1, minor: minor} = version, pid, cwd)
when minor >= 9,
do: [
each_module: &each_module(version, pid, cwd, &1, &2, &3),
each_file: &each_file(version, pid, cwd, &1, &2)
]
def parallel_require_callbacks(%Version{major: 1, minor: minor} = version, pid, cwd)
when minor >= 6 and minor < 9 do
[each_module: &each_module(version, pid, cwd, &1, &2, &3)]
end
def parallel_require_callbacks(_, _, _),
do: {:error, "Your version of Elixir #{System.version()} cannot support the stale feature"}
defp each_module(%Version{major: 1, minor: minor}, pid, cwd, file, module, _binary)
when minor >= 9 do
external = get_external_resources(module, cwd)
if external != [] do
Agent.update(pid, fn sources ->
file = Path.relative_to(file, cwd)
{source, sources} = List.keytake(sources, file, source(:source))
[source(source, external: external ++ source(source, :external)) | sources]
end)
end
:ok
end
defp each_module(%Version{major: 1, minor: minor}, pid, cwd, source, module, _binary)
when minor >= 6 and minor < 9 do
{compile_references, struct_references, runtime_references} =
apply(Kernel.LexicalTracker, :remote_references, [module])
external = get_external_resources(module, cwd)
source = Path.relative_to(source, cwd)
Agent.cast(pid, fn sources ->
external =
case List.keyfind(sources, source, source(:source)) do
source(external: old_external) -> external ++ old_external
nil -> external
end
new_source =
source(
source: source,
compile_references: compile_references ++ struct_references,
runtime_references: runtime_references,
external: external
)
List.keystore(sources, source, source(:source), new_source)
end)
end
defp each_file(%Version{major: 1, minor: minor}, pid, _cwd, file, lexical) when minor >= 10 do
Agent.update(pid, fn sources ->
case List.keytake(sources, file, source(:source)) do
{source, sources} ->
{compile_references, struct_references, runtime_references, _compile_env} =
apply(Kernel.LexicalTracker, :references, [lexical])
source =
source(
source,
compile_references: compile_references ++ struct_references,
runtime_references: runtime_references
)
[source | sources]
nil ->
sources
end
end)
end
defp each_file(%Version{major: 1, minor: 9}, pid, _cwd, file, lexical) do
Agent.update(pid, fn sources ->
case List.keytake(sources, file, source(:source)) do
{source, sources} ->
{compile_references, struct_references, runtime_references} =
apply(Kernel.LexicalTracker, :remote_references, [lexical])
source =
source(
source,
compile_references: compile_references ++ struct_references,
runtime_references: runtime_references
)
[source | sources]
nil ->
sources
end
end)
end
defp get_external_resources(module, cwd) do
for file <- Module.get_attribute(module, :external_resource),
do: Path.relative_to(file, cwd)
end
defp find_all_dependent_on(
version,
modules,
sources,
all_modules,
resolved \\ MapSet.new()
) do
new_modules =
for module <- modules,
module not in resolved,
dependent_module <- dependent_modules(version, module, all_modules, sources),
do: dependent_module,
into: modules
if MapSet.size(new_modules) == MapSet.size(modules) do
new_modules
else
find_all_dependent_on(version, new_modules, sources, all_modules, modules)
end
end
cond do
Version.match?(System.version(), "< 1.16.0") ->
defp dependent_modules(%Version{major: 1, minor: minor}, module, modules, sources)
when minor >= 11 do
for CE.source(
source: source,
runtime_references: r,
compile_references: c,
export_references: s
) <- sources,
module in r or module in c or module in s,
CE.module(sources: sources, module: dependent_module) <- modules,
source in sources,
do: dependent_module
end
true ->
defp dependent_modules(%Version{}, module, modules, sources) do
for {source,
CE.source(
runtime_references: r,
compile_references: c,
export_references: e
)} <- sources,
module in r or module in c or module in e,
{dependent_module, CE.module(sources: sources)} <- modules,
source in sources,
do: dependent_module
end
end
defp parse_version do
System.version()
|> Version.parse()
|> case do
{:ok, version} -> version
:error -> :error
end
end
end