-
-
Notifications
You must be signed in to change notification settings - Fork 181
/
helpers.ex
145 lines (128 loc) · 4.02 KB
/
helpers.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
defmodule Ash.Mix.Tasks.Helpers do
@moduledoc """
Helpers for Ash Mix tasks.
"""
@doc """
Gets all extensions in use by the current project's domains and resources
"""
def extensions!(argv, opts \\ []) do
if opts[:in_use?] do
Mix.shell().info("Getting extensions in use by resources in current project...")
domains = Ash.Mix.Tasks.Helpers.domains!(argv)
resource_extensions =
domains
|> Enum.flat_map(&Ash.Domain.Info.resources/1)
|> all_extensions()
domains
|> all_extensions()
|> Enum.concat(resource_extensions)
|> Enum.uniq()
|> case do
[] ->
Mix.shell().info("No extensions in use by resources in current project...")
extensions ->
extensions
end
else
Mix.shell().info("Getting extensions in current project...")
apps =
if Code.ensure_loaded?(Mix.Project) do
if apps_paths = Mix.Project.apps_paths() do
apps_paths |> Map.keys() |> Enum.sort()
else
[Mix.Project.config()[:app]]
end
else
[]
end
# for our app, and all dependency apps, we want to find extensions
# the benefit of not just getting all loaded applications is that this
# is actually a surprisingly expensive thing to do for every single built
# in application for elixir/erlang. Instead we get anything w/ a dependency on ash or spark
# this could miss things, but its unlikely. And if it misses things, it actually should be
# fixed in the dependency that is relying on a transitive dependency :)
Mix.Project.deps_tree()
|> Stream.filter(fn {_, nested_deps} ->
Enum.any?(nested_deps, &(&1 == :spark || &1 == :ash))
end)
|> Stream.map(&elem(&1, 0))
|> Stream.concat(apps)
|> Stream.uniq()
|> Task.async_stream(
fn app ->
app
|> :application.get_key(:modules)
|> elem(1)
|> List.wrap()
|> Enum.filter(&Spark.implements_behaviour?(&1, Spark.Dsl.Extension))
end,
timeout: :infinity
)
|> Stream.map(&elem(&1, 1))
|> Stream.flat_map(& &1)
|> Stream.uniq()
|> Enum.to_list()
end
end
@doc """
Get all domains for the current project and ensure they are compiled.
"""
def domains!(argv) do
{opts, _} = OptionParser.parse!(argv, strict: [domains: :string])
domains =
if opts[:domains] && opts[:domains] != "" do
opts[:domains]
|> Kernel.||("")
|> String.split(",")
|> Enum.flat_map(fn
"" ->
[]
domain ->
[Module.concat([domain])]
end)
else
apps =
if Code.ensure_loaded?(Mix.Project) do
if apps_paths = Mix.Project.apps_paths() do
apps_paths |> Map.keys() |> Enum.sort()
else
[Mix.Project.config()[:app]]
end
else
[]
end
Enum.flat_map(apps, &Application.get_env(&1, :ash_domains, []))
end
domains
|> Enum.map(&ensure_compiled(&1, argv))
|> case do
[] ->
raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config"
domains ->
domains
end
end
defp all_extensions(modules) do
modules
|> Enum.flat_map(&Spark.extensions/1)
|> Enum.uniq()
end
defp ensure_compiled(domain, args) do
if Code.ensure_loaded?(Mix.Tasks.App.Config) do
Mix.Task.run("app.config", args)
else
Mix.Task.run("loadpaths", args)
"--no-compile" not in args && Mix.Task.run("compile", args)
end
case Code.ensure_compiled(domain) do
{:module, _} ->
# TODO: We shouldn't need to make sure that the resources are compiled
domain
|> Ash.Domain.Info.resources()
|> Enum.each(&Code.ensure_compiled/1)
domain
{:error, error} ->
Mix.raise("Could not load #{inspect(domain)}, error: #{inspect(error)}. ")
end
end
end