Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions lib/mix/lib/mix/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -273,18 +273,20 @@ defmodule Mix.Project do
source = Path.expand("ebin")
target = Path.join(app, "ebin")

symlink_options = [relative_symlink: true]

cond do
opts[:symlink_ebin] ->
Mix.Utils.symlink_or_copy(source, target)
Mix.Utils.symlink_or_copy(source, target, symlink_options)
match?({ :ok, _ }, :file.read_link(target)) ->
File.rm_rf!(target)
File.mkdir_p!(target)
true ->
File.mkdir_p!(target)
end

Mix.Utils.symlink_or_copy(Path.expand("include"), Path.join(app, "include"))
Mix.Utils.symlink_or_copy(Path.expand("priv"), Path.join(app, "priv"))
Mix.Utils.symlink_or_copy(Path.expand("include"), Path.join(app, "include"), symlink_options)
Mix.Utils.symlink_or_copy(Path.expand("priv"), Path.join(app, "priv"), symlink_options)
end

@doc """
Expand Down
52 changes: 46 additions & 6 deletions lib/mix/lib/mix/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -317,29 +317,34 @@ defmodule Mix.Utils do
@doc """
Symlink directory `source` to `target` or copy it recursively
in case symlink fails.

## Options

* `:relative_symlink` - If making a symlink, make it relative.
"""
def symlink_or_copy(source, target) do
def symlink_or_copy(source, target, opts // []) do
if File.exists?(source) do
source_list = String.to_char_list!(source)
case :file.read_link(target) do
{ :ok, ^source_list } ->
:ok
{ :ok, _ } ->
File.rm!(target)
do_symlink_or_copy(source, target)
do_symlink_or_copy(source, target, opts)
{ :error, :enoent } ->
do_symlink_or_copy(source, target)
do_symlink_or_copy(source, target, opts)
{ :error, _ } ->
File.rm_rf!(target)
do_symlink_or_copy(source, target)
do_symlink_or_copy(source, target, opts)
end
else
{ :error, :enoent }
end
end

defp do_symlink_or_copy(source, target) do
case :file.make_symlink(source, target) do
defp do_symlink_or_copy(source, target, opts) do
symlink_source = if opts[:relative_symlink], do: make_relative_path(source, Path.expand(target)), else: source
case :file.make_symlink(symlink_source, target) do
:ok -> :ok
{ :error, _ } -> File.cp_r!(source, Path.join(target, "."))
end
Expand Down Expand Up @@ -390,4 +395,39 @@ defmodule Mix.Utils do
defp is_url?(path) do
URI.parse(path).scheme in ["http", "https"]
end

@doc """
Returns the relative path you would need to go from target to source.
Useful to make relative symlinks.

## Examples

iex> Mix.Utils.make_relative_path("/a/b/c", "/a/b/d")
"c"

iex> Mix.Utils.make_relative_path("/a/b/c/d", "/a/b/d/e")
"../c/d"

iex> Mix.Utils.make_relative_path("a/b", "y/z")
"../a/b"

iex> Mix.Utils.make_relative_path("a/b/c/d", "a/e/f/g")
"../../b/c/d"
"""
def make_relative_path(source, target) do
do_make_relative_path(source, target)
end

defp do_make_relative_path(source, target) do
<<source_start :: utf8, source_rest :: binary>> = source
<<target_start :: utf8, target_rest :: binary>> = target

if source_start == target_start do
do_make_relative_path(source_rest, target_rest)
else
back_path = Enum.reduce Path.split(target) |> List.delete_at(-1), "", fn(_, acc) -> Path.join("..", acc) end
[back_path, source] |> Path.join |> Path.relative
end
end

end
10 changes: 5 additions & 5 deletions lib/mix/test/mix/project_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ defmodule Mix.ProjectTest do
config = [app_path: "_build/archive"]
assert Mix.Project.build_structure(config) == :ok
assert File.dir?("_build/archive/ebin")
assert :file.read_link("_build/archive/priv") == { :ok, Path.expand('priv') }
assert :file.read_link("_build/archive/priv") == { :ok, '../../priv' }
end
end

Expand All @@ -92,13 +92,13 @@ defmodule Mix.ProjectTest do
File.mkdir_p!("include")

assert Mix.Project.build_structure(config, symlink_ebin: true) == :ok
assert :file.read_link("_build/archive/ebin") == { :ok, Path.expand('ebin') }
assert :file.read_link("_build/archive/priv") == { :ok, Path.expand('priv') }
assert :file.read_link("_build/archive/include") == { :ok, Path.expand('include') }
assert :file.read_link("_build/archive/ebin") == { :ok, '../../ebin' }
assert :file.read_link("_build/archive/priv") == { :ok, '../../priv' }
assert :file.read_link("_build/archive/include") == { :ok, '../../include' }

assert Mix.Project.build_structure(config) == :ok
assert File.dir?("_build/archive/ebin")
assert :file.read_link("_build/archive/priv") == { :ok, Path.expand('priv') }
assert :file.read_link("_build/archive/priv") == { :ok, '../../priv' }
end
end
end
3 changes: 3 additions & 0 deletions lib/mix/test/mix/utils_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule Mix.UtilsTest do
use MixTest.Case

doctest Mix.Utils, only: [make_relative_path: 2]

test :command_to_module do
assert Mix.Utils.command_to_module("hello", Mix.Tasks) == { :module, Mix.Tasks.Hello }
assert Mix.Utils.command_to_module("unknown", Mix.Tasks) == { :error, :nofile }
Expand Down Expand Up @@ -102,4 +104,5 @@ defmodule Mix.UtilsTest do
assert :file.read_link("_build/archive/ebin") == { :ok, Path.expand('ebin') }
end
end

end