Let's say you have an umbrella project and that umbrella project has a mix task that wants to write to a tmp/ directory in the root. Well if you're me then you just realized there's no method that gets you this exact value, even though there are a lot of methods that get you close! Of course there are plenty of tricks/hacks you can employ to get the value as well, but the point of a stdlib is to give you foundational elements like this so that a 1 line doesn't get written a million times.
Alright, so here's what we have:
0. `app_path` -> `"/path/to/project/_build/shared/lib/app"`, fails on an umbrella project
0. `apps_paths` -> `%{my_app1: "apps/my_app1", my_app2: "apps/my_app2"}`, this is actually pretty close, but on a non-standard umbrella application it gets really weird. Like what if you have some things in `apps/` and some things in `applications/` due to weird legacy? Not a great solve. It's also very strange that these are not expanded paths like many other functions? ALSO it's not "app_path" of each of your apps, it's really just the `umbrella_app_paths` if anything.
0. `build_path` -> `"/path/to/project/_build/shared"` or `"/path/to/project/_build/dev"` depending on `Mix.env()`, still not very helpful.
0. `compile_path` -> See: `build_path`
0. `config_files` -> `["/path/to/project/_build/dev/.mix/compile.lock", "/path/to/project/config/dev.exs", "/path/to/project/config/config.exs", "/path/to/project/mix.exs"]` Okay a step in the right direction as they're all absolute, but only the last file is really helpful. One trick here would be `config_files() |> List.last |> Path.dirname`, assuming the list is always in order and mix.exs is always last.
0. `consolidation_path` -> See: `build_path`
0. `deps_path` -> `"/path/to/project/deps"` This function gets us the closest: [x] It's absolute, [x] It's just one path, [x] It works in an umbrella application, [x] It can be fed to `Path.dirname`, but [ ] It's not configurable, so it's brittle
0. `deps_paths` -> `%{foo: "deps/foo", bar: "custom/path/dep"}` which isn't even close to useful. The example on this by the way in the docs could be significantly more informative.
0. `manifest_path` -> See: `build_path`
So in summery the closest we get is `deps_path()`, but even then it's kinda shaky. Okay, fine so I thought to myself: How are these functions determining where these pieces are? Well turns out most of them look in one of two places:
0. `MIX_*` (`MIX_BUILD_PATH` for example), which isn't very helpful
0. `Mix.Project.config()`, which is a `list(map)`, where each map is a mix project?
For example:
```
def deps_path(config \\ config()) do
System.get_env("MIX_DEPS_PATH", config[:deps_path]) |> Path.expand
end
```
Despite it being a list of maps, when you go to access a key it just looks at the first project. In this case:
```
Mix.Project.config[:deps_path] # => "deps"
```
and then:
```
Path.expand("deps") # => "/path/to/project/deps"
```
At this point you've probably been screaming: Kurtis! Why aren't you just using File.cwd??? Well, okay, here's why:
```
iex(1)> File.cwd
{:ok, "/path/to/project"}
```
but let's say you have a task in one of your apps that calls `File.cwd`:
```
$ mix print-working-direcotry-task
{:ok, "/path/to/project/apps/example"}
```
The same thing happens when I define the path as a property in `config/config.exs`, despite being in the root. With that said, here's my proposal:
```
@spec umbrella_path() :: Path.t()
def umbrella_path() do
File.cwd!
end
```
"Can't you do that yourself? By defining it in the mix.exs module?" While you can define it alright, it's seemingly inaccessible from inside an app's tasks.
The great thing is that initially I was worried there would be special logic needed for when you talk about `Mix.Project.umbrella_path` inside a app, but you'll find `Mix.Project.deps_path` returns the umbrella project's deps path!