forked from bitwalker/distillery
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gen.appup.ex
187 lines (151 loc) · 5.34 KB
/
gen.appup.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
defmodule Mix.Tasks.Distillery.Gen.Appup do
@moduledoc """
Generate appup files for hot upgrades and downgrades
## Examples
# Generate an appup for :test from the last known version to the current version
mix distillery.gen.appup --app=test
# Generate an appup for :test from a specific version to the current version
mix distillery.gen.appup --app=test --upfrom=0.1.0
The generated appup will be written to `rel/appups/<app>/<from>_to_<to>.appup`. You may name
appups anything you wish in this directory, as long as they have a `.appup` extension. When you
build a release, the appup generator will look for missing appups in this directory structure, and
scan all `.appup` files for matching versions. If you have multiple appup files which match the current
release, then the first one encountered will take precedence, which more than likely will depend on the
sort order of the names.
This task will take all of the same flags as `mix distillery.release`, but only uses them to determine the release
configuration to use when determining application locations and versions.
"""
@shortdoc "Generate appup files for hot upgrades and downgrades"
use Mix.Task
alias Distillery.Releases.Shell
alias Distillery.Releases.Config
alias Distillery.Releases.Release
alias Distillery.Releases.Errors
alias Distillery.Releases.Assembler
alias Distillery.Releases.Appup
alias Distillery.Releases.Utils
@spec run(OptionParser.argv()) :: no_return
def run(args) do
# Parse options
primary_opts = Mix.Tasks.Distillery.Release.parse_args(args, strict: false)
secondary_opts = parse_args(args)
opts = Keyword.merge(primary_opts, secondary_opts)
verbosity = Keyword.get(opts, :verbosity)
Shell.configure(verbosity)
# make sure we've compiled latest
Mix.Task.run("compile", [])
# make sure loadpaths are updated
Mix.Task.run("loadpaths", [])
# load release configuration
Shell.debug("Loading configuration..")
case Config.get(opts) do
{:error, {:config, :not_found}} ->
Shell.error("You are missing a release config file. Run the distillery.init task first")
System.halt(1)
{:error, {:config, reason}} ->
Shell.error("Failed to load config:\n #{reason}")
System.halt(1)
{:ok, config} ->
with {:ok, release} <- Assembler.pre_assemble(config),
:ok <- do_gen_appup(release, opts) do
Shell.success(
"You can find your generated appups in rel/appups/<app>/ with the .appup extension"
)
else
{:error, _} = err ->
Shell.error(Errors.format_error(err))
System.halt(1)
end
end
end
defp do_gen_appup(
%Release{profile: %{output_dir: output_dir, appup_transforms: transforms}},
opts
) do
app = opts[:app]
# Does app exist?
case Application.load(app) do
:ok ->
:ok
{:error, {:already_loaded, _}} ->
:ok
{:error, _} ->
Shell.error("Unable to locate an app called '#{app}'")
System.halt(1)
end
v2 =
app
|> Application.spec()
|> Keyword.get(:vsn)
|> List.to_string()
v2_path = Application.app_dir(app)
# Look for app versions in release directory
available_versions =
Path.join([output_dir, "lib", "#{app}-*"])
|> Path.wildcard()
|> Enum.map(fn appdir ->
{:ok, [{:application, ^app, meta}]} =
Path.join([appdir, "ebin", "#{app}.app"])
|> Utils.read_terms()
version =
meta
|> Keyword.fetch!(:vsn)
|> List.to_string()
{version, appdir}
end)
|> Map.new()
|> Map.delete(v2)
sorted_versions =
available_versions
|> Map.keys()
|> Utils.sort_versions()
if map_size(available_versions) == 0 do
Shell.error("No available upfrom versions for #{app}")
System.halt(1)
end
{v1, v1_path} =
case opts[:upgrade_from] do
:latest ->
version = List.first(sorted_versions)
{version, Map.fetch!(available_versions, version)}
version ->
case Map.get(available_versions, version) do
nil ->
Shell.error("Version #{version} of #{app} is not available!")
System.halt(1)
path ->
{version, path}
end
end
case Appup.make(app, v1, v2, v1_path, v2_path, transforms) do
{:error, _} = err ->
err
{:ok, appup} ->
Shell.info("Generated .appup for #{app} #{v1} -> #{v2}")
appup_path = Path.join(["rel", "appups", "#{app}", "#{v1}_to_#{v2}.appup"])
File.mkdir_p!(Path.dirname(appup_path))
:ok = Utils.write_term(appup_path, appup)
end
end
defp parse_args(argv) do
opts = [
switches: [
app: :string
]
]
{flags, _, _} = OptionParser.parse(argv, opts)
defaults = %{
app: nil
}
parse_args(flags, defaults)
end
defp parse_args([], %{app: nil}) do
Shell.error("This task requires --app=<app_name> to be passed")
System.halt(1)
end
defp parse_args([], opts), do: Map.to_list(opts)
defp parse_args([{:app, app} | rest], opts) do
parse_args(rest, Map.put(opts, :app, String.to_atom(app)))
end
defp parse_args([_ | rest], opts), do: parse_args(rest, opts)
end