Skip to content

Commit 206f641

Browse files
committed
Merge pull request #2756 from elixir-lang/emj-archive-shell
Fetch Hex with wget or curl
2 parents 2cafd8c + a2c0424 commit 206f641

File tree

3 files changed

+65
-13
lines changed

3 files changed

+65
-13
lines changed

lib/mix/lib/mix/tasks/archive.install.ex

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
defmodule Mix.Tasks.Archive.Install do
22
use Mix.Task
33

4-
import Mix.Generator, only: [create_file: 2]
5-
64
@shortdoc "Install an archive locally"
75

86
@moduledoc """
@@ -30,7 +28,7 @@ defmodule Mix.Tasks.Archive.Install do
3028
"""
3129
@spec run(OptionParser.argv) :: boolean
3230
def run(argv) do
33-
{opts, argv, _} = OptionParser.parse(argv, switches: [force: :boolean])
31+
{opts, argv, _} = OptionParser.parse(argv, switches: [force: :boolean, shell: :boolean])
3432

3533
if src = List.first(argv) do
3634
%URI{path: path} = URI.parse(src)
@@ -55,10 +53,15 @@ defmodule Mix.Tasks.Archive.Install do
5553

5654
if opts[:force] || should_install?(src, previous) do
5755
remove_previous_versions(previous)
56+
5857
dest = Mix.Local.archives_path()
59-
File.mkdir_p!(dest)
6058
archive = Path.join(dest, basename(src))
61-
create_file archive, Mix.Utils.read_path!(src)
59+
check_file_exists(archive)
60+
61+
File.mkdir_p!(dest)
62+
File.write!(archive, Mix.Utils.read_path!(src, opts))
63+
Mix.shell.info [:green, "* creating ", :reset, Path.relative_to_cwd(archive)]
64+
6265
true = Code.append_path(Mix.Archive.ebin(archive))
6366
else
6467
false
@@ -81,6 +84,18 @@ defmodule Mix.Tasks.Archive.Install do
8184
"Are you sure you want to replace them?")
8285
end
8386

87+
defp check_file_exists(path) do
88+
# OTP keeps loaded archives open, this leads to unfortunate behaviour on
89+
# Windows when trying overwrite loaded archives. remove_previous_versions
90+
# completes successfully even though the file will be first removed after
91+
# the BEAM process is dead. Because of this we ask the user rerun the
92+
# command, which should complete successfully at that time
93+
94+
if File.exists?(path) and match?({:win32, _}, :os.type) do
95+
Mix.raise "Unable to overwrite open archives on Windows. Run the command again"
96+
end
97+
end
98+
8499
defp previous_versions(src) do
85100
app = src
86101
|> Mix.Archive.dir

lib/mix/lib/mix/tasks/local.hex.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defmodule Mix.Tasks.Local.Hex do
1818
"""
1919
@spec run(OptionParser.argv) :: boolean
2020
def run(args) do
21-
Mix.Tasks.Archive.Install.run [@hex_url|args]
21+
Mix.Tasks.Archive.Install.run [@hex_url, "--shell" | args]
2222
end
2323

2424
@doc false

lib/mix/lib/mix/utils.ex

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,22 +359,32 @@ defmodule Mix.Utils do
359359
Used by tasks like `archive.install` and `local.rebar` that support
360360
installation either from a URL or a local file.
361361
362-
Raises if the given path is not a url, nor a file or if the
363-
file or url are invalid.
362+
Raises if the given path is not a URL, nor a file or if the
363+
file or URL are invalid.
364+
365+
## Options
366+
367+
* `:shell` - Forces the use of `wget` or `curl` to fetch the file if the
368+
given path is a URL.
364369
"""
365-
def read_path!(path) do
370+
def read_path!(path, opts \\ []) do
366371
cond do
367-
url?(path) -> read_url(path)
368-
file?(path) -> read_file(path)
369-
:else -> Mix.raise "Expected #{path} to be a url or a local file path"
372+
url?(path) && opts[:shell] ->
373+
read_shell(path)
374+
url?(path) ->
375+
read_httpc(path)
376+
file?(path) ->
377+
read_file(path)
378+
true ->
379+
Mix.raise "Expected #{path} to be a url or a local file path"
370380
end
371381
end
372382

373383
defp read_file(path) do
374384
File.read!(path)
375385
end
376386

377-
defp read_url(path) do
387+
defp read_httpc(path) do
378388
{:ok, _} = Application.ensure_all_started(:ssl)
379389
{:ok, _} = Application.ensure_all_started(:inets)
380390

@@ -420,6 +430,33 @@ defmodule Mix.Utils do
420430
end
421431
end
422432

433+
defp read_shell(path) do
434+
filename = URI.parse(path).path |> Path.basename
435+
out_path = Path.join(System.tmp_dir!, filename)
436+
File.rm(out_path)
437+
438+
cond do
439+
System.find_executable("wget") ->
440+
Mix.shell.cmd(~s(wget -O "#{out_path}" "#{path}"))
441+
System.find_executable("curl") ->
442+
Mix.shell.cmd(~s(curl -L -o "#{out_path}" "#{path}"))
443+
windows? && System.find_executable("powershell") ->
444+
command = ~s[$client = new-object System.Net.WebClient; ] <>
445+
~s[$client.DownloadFile(\\"#{path}\\", \\"#{out_path}\\")]
446+
Mix.shell.cmd(~s[powershell -Command "& {#{command}}"])
447+
true ->
448+
Mix.raise "wget or curl not installed, download manually: #{path}"
449+
end
450+
451+
data = File.read!(out_path)
452+
File.rm!(out_path)
453+
data
454+
end
455+
456+
def windows? do
457+
match?({:win32, _}, :os.type)
458+
end
459+
423460
defp file?(path) do
424461
File.regular?(path)
425462
end

0 commit comments

Comments
 (0)