diff --git a/lib/mix/lib/mix/project.ex b/lib/mix/lib/mix/project.ex index 081509c5f67..d2309e4eb96 100644 --- a/lib/mix/lib/mix/project.ex +++ b/lib/mix/lib/mix/project.ex @@ -273,9 +273,11 @@ 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) @@ -283,8 +285,8 @@ defmodule Mix.Project do 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 """ diff --git a/lib/mix/lib/mix/utils.ex b/lib/mix/lib/mix/utils.ex index 83c2ff42836..eae8ec6d26b 100644 --- a/lib/mix/lib/mix/utils.ex +++ b/lib/mix/lib/mix/utils.ex @@ -317,8 +317,12 @@ 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 @@ -326,20 +330,21 @@ defmodule Mix.Utils do :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 @@ -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 + <> = 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 diff --git a/lib/mix/test/mix/project_test.exs b/lib/mix/test/mix/project_test.exs index 2306cd66576..447edc908d7 100644 --- a/lib/mix/test/mix/project_test.exs +++ b/lib/mix/test/mix/project_test.exs @@ -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 @@ -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 diff --git a/lib/mix/test/mix/utils_test.exs b/lib/mix/test/mix/utils_test.exs index fa54f833a9d..258652f5ebf 100644 --- a/lib/mix/test/mix/utils_test.exs +++ b/lib/mix/test/mix/utils_test.exs @@ -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 } @@ -102,4 +104,5 @@ defmodule Mix.UtilsTest do assert :file.read_link("_build/archive/ebin") == { :ok, Path.expand('ebin') } end end + end