New API: Mix.Project.umbrella_path() :: Path.t

46 views
Skip to first unread message

Kurtis Rainbolt-Greene

unread,
Jun 23, 2020, 8:31:32 PM6/23/20
to elixir-lang-core
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!

José Valim

unread,
Jun 24, 2020, 3:34:35 AM6/24/20
to elixir-l...@googlegroups.com
Hi Kurtis,

If I understand your description correctly, this is by design.  An application inside an umbrella doesn't know it belongs to an umbrella because you should be able to also run it in isolation. That's why we explicitly have configs tha point to the parent directory.

Your solution is to either have a tmp_path that you introduce to each application that points to this shared path or rely on one of the existing configs that point to the parent, such as build_path. Otherwise, generally speaking, we simply don't know we are inside an umbrella and we generally want to avoid this coupling.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/623ace74-b11c-4c47-9e13-f04b13f03c97o%40googlegroups.com.

Kurtis Rainbolt-Greene

unread,
Jun 24, 2020, 11:24:04 AM6/24/20
to elixir-lang-core
Except doesn't deps_path inside an application that is a part of an umbrella application return the umbrella's deps directory?

José Valim

unread,
Jun 24, 2020, 1:02:31 PM6/24/20
to elixir-l...@googlegroups.com
Yes, because it is configured in the mix.exs of that application to do so. All behavior is opt-in, there is no magic detection. You have to do the same.

Kurtis Rainbolt-Greene

unread,
Jun 24, 2020, 3:13:51 PM6/24/20
to elixir-l...@googlegroups.com
Ah, I see now in the project configuration. I also see that `umbrella?` would only return true inside the umbrella the root project, because it defines `apps_paths`. In theory, a home solution to this would be to simply define a `umbrella_path` in all of my apps. Alright, that definitely makes sense.

On Wed, Jun 24, 2020 at 10:02 AM José Valim <jose....@dashbit.co> wrote:
Yes, because it is configured in the mix.exs of that application to do so. All behavior is opt-in, there is no magic detection. You have to do the same.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.


--
Kurtis Rainbolt-Greene,
Software Developer & Founder of Difference Engineers
Reply all
Reply to author
Forward
0 new messages