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
|
defmodule Mix.Tasks.Archive.Install do
use Mix.Task
@shortdoc "Installs an archive locally"
@moduledoc """
Installs an archive locally.
If no argument is supplied but there is an archive in the project's
root directory (created with `mix archive.build`), then the archive
will be installed locally. For example:
mix do archive.build, archive.install
If an argument is provided, it should be a local path or a URL to a
prebuilt archive, a Git repository, a GitHub repository, or a Hex
package.
mix archive.install archive.ez
mix archive.install path/to/archive.ez
mix archive.install https://example.com/my_archive.ez
mix archive.install git https://path/to/git/repo
mix archive.install git https://path/to/git/repo branch git_branch
mix archive.install git https://path/to/git/repo tag git_tag
mix archive.install git https://path/to/git/repo ref git_ref
mix archive.install github user/project
mix archive.install github user/project branch git_branch
mix archive.install github user/project tag git_tag
mix archive.install github user/project ref git_ref
mix archive.install hex hex_package
mix archive.install hex hex_package 1.2.3
After installation, the tasks in the archive are available locally:
mix some_task
Note that installing via Git/GitHub/Hex fetches the source of the archive
and builds it, while using URL/local path fetches a pre-built archive.
## Command line options
* `--sha512` - checks the archive matches the given SHA-512 checksum. Only
applies to installations via URL or local path
* `--force` - forces installation without a shell prompt; primarily
intended for automation in build systems like Make
* `--submodules` - fetches repository submodules before building archive from
Git or GitHub
* `--app` - specifies a custom app name to be used for building the archive
from Git, GitHub, or Hex
* `--organization` - specifies an organization to use if fetching the package
from a private Hex repository
"""
@behaviour Mix.Local.Installer
@switches [
force: :boolean,
sha512: :string,
submodules: :boolean,
app: :string,
organization: :string
]
def run(argv) do
Mix.Local.Installer.install(__MODULE__, argv, @switches)
end
# Callbacks
def check_install_spec({local_or_url, path_or_url} = _install_spec, _opts)
when local_or_url in [:local, :url] do
if Path.extname(path_or_url) == ".ez" do
:ok
else
{:error, "Expected a local file path or a file URL ending in \".ez\"."}
end
end
def check_install_spec(_, _), do: :ok
def find_previous_versions(src) do
app =
src
|> Mix.Local.archive_name()
|> String.split("-")
|> List.first()
if app do
archives(app) ++ archives(app <> "-*")
else
[]
end
end
def install(basename, contents, previous) do
ez_path = Path.join(Mix.Local.path_for(:archive), basename)
dir_dest = resolve_destination(ez_path, contents)
remove_previous_versions(previous)
File.mkdir_p!(dir_dest)
{:ok, _} = :zip.extract(contents, cwd: dir_dest)
Mix.shell().info([:green, "* creating ", :reset, Path.relative_to_cwd(dir_dest)])
ebin = Mix.Local.archive_ebin(dir_dest)
Mix.Local.check_elixir_version_in_ebin(ebin)
true = Code.append_path(ebin)
:ok
end
def build(_install_spec, _opts) do
src = Mix.Local.name_for(:archive, Mix.Project.config())
previous = find_previous_versions(src)
Enum.each(previous, fn path ->
Code.delete_path(Mix.Local.archive_ebin(path))
end)
Mix.Task.run("archive.build", [])
src
end
### Private helpers
defp resolve_destination(ez_path, contents) do
with {:ok, [_comment, zip_first_file | _]} <- :zip.list_dir(contents),
{:zip_file, zip_first_path, _, _, _, _} = zip_first_file,
[zip_root_dir | _] = Path.split(zip_first_path) do
Path.join(Path.dirname(ez_path), zip_root_dir)
else
_ ->
Mix.raise("Installation failed: invalid archive file")
end
end
defp archives(name) do
Mix.Local.path_for(:archive)
|> Path.join(name)
|> Path.wildcard()
end
defp remove_previous_versions([]), do: :ok
defp remove_previous_versions(previous), do: Enum.each(previous, &File.rm_rf!/1)
end
|