Skip to content

Add gleam support to Mix - #14262

Open
Papipo wants to merge 40 commits into
elixir-lang:mainfrom
Papipo:add-gleam-compiler
Open

Add gleam support to Mix#14262
Papipo wants to merge 40 commits into
elixir-lang:mainfrom
Papipo:add-gleam-compiler

Conversation

@Papipo

@Papipo Papipo commented Feb 13, 2025

Copy link
Copy Markdown

This PR adds support for the gleam language.

  • Support for gleam.toml in path deps
  • Support gleam in deps loader.
  • Support for gleam in deps.compile
  • Require specific gleam binary version (hardcoded to 1.9.0 for now, see below)
  • Handle absent gleam binary
  • Support git deps (support for them in gleam is in main already but not yet released)
  • Support for :application option in Mix.ProjectStack.push (thus an application function is not needed)
  • Setup gleam on CI (Otherwise gleam tests will be skipped).

Notes:

  • Packages having a gleam binary version requirements are handled automatically when executing gleam compile-package.
  • The gleam binary version requirement for mix integration is hardcoded because I could just try to run gleam export package-info but I can't know the reason for a bad exit status (maybe the dep path was wrong and the command was run in a dir without a gleam.toml).
  • The required command to export information about gleam packages and their deps is not yet released but has already been merged. Should land on v1.10.0. Was released on v1.10.0
Comment thread lib/mix/lib/mix/gleam.ex Outdated
Comment thread lib/mix/lib/mix/tasks/deps.compile.ex Outdated
@Papipo

Papipo commented Feb 17, 2025

Copy link
Copy Markdown
Author

@josevalim when working on this I was always thinking of erlang target packages, but it would be nice to be able to use javascript ones as well (ie. to use them in Phoenix frontend). Not sure how we can handle that, any hints?

@josevalim

Copy link
Copy Markdown
Member

It should be possible because we use heroicons, a regular JS package, as a dependency on Phoenix applications. So we could probably support a :gleam_targets or similar option that is used by by deps.compile? You will probably need to set app: false true, similar to how heroicons are added.

@lpil

lpil commented Feb 17, 2025

Copy link
Copy Markdown
Contributor

Hello! You shouldn't need to do anything special target-wise as Gleam performs dead code elimination for code that is for other targets.

@josevalim when working on this I was always thinking of erlang target packages, but it would be nice to be able to use javascript ones as well (ie. to use them in Phoenix frontend). Not sure how we can handle that, any hints?

I would not expect Mix to become a front-end build tool by adding Gleam support. I think the workflow should be the same as it is today etc, with the desired build tool (esbuild, webpack, gleam etc) being run as a Phoenix watcher.

Adding frontend dependencies to the BEAM application dependency tree would either be wasted work compiling and managing them, or the programmer would need to go through every dependency in the lock file and add configuration to their mix.exs for each one they don't want to use on the backend.

We are going to #14262 (comment), so I also need the target config, if any. I think that just exporting everything is the best bet.

There is no such config. A Gleam package doesn't have an explicit target, each function in the public API is available on Erlang, JavaScript, or both, and they get either compiled or eliminated as needed. Whether a package is for Erlang or JavaScript is typically a matter of how the dependant is using them, not a matter of the configuration of the dependency.

There is a target flag in gleam.toml, but that is "if I run code from this page (almost always the tests) what target should be used by default, unless otherwise specified".

@Papipo
Papipo force-pushed the add-gleam-compiler branch 3 times, most recently from b91ae71 to c181ecd Compare February 20, 2025 11:34
Comment thread lib/mix/lib/mix/tasks/deps.compile.ex Outdated
Comment thread lib/mix/test/test_helper.exs Outdated
@Papipo
Papipo force-pushed the add-gleam-compiler branch from a18bf89 to 26c8aff Compare March 17, 2025 12:31
@Papipo
Papipo force-pushed the add-gleam-compiler branch 2 times, most recently from 8a7d9fa to 360db23 Compare March 25, 2025 09:14
@Papipo Papipo changed the title [DRAFT] Add gleam support to Mix Mar 26, 2025
@Papipo
Papipo marked this pull request as ready for review March 26, 2025 08:46
@Papipo

Papipo commented Mar 26, 2025

Copy link
Copy Markdown
Author

I am kind of copypasting this from the Gleam discord in case someone wants to try this out before merging into main:

What is this?

These changes add first-class support for Gleam in Elixir's Mix, thus allowing gleam path dependencies in mix.exs.
Those dependencies are expected to be 100% valid gleam projects and as such need to have a gleam.toml file.

What's missing?

Probably nothing. Work on the gleam compiler is already merged and work on the Mix side is completed, but there might be rough edges, that's what we need to test.

Instructions

Mix integration will expect you to have gleam >= 1.10.0 installed and that hasn't been released yet. So:

  • Install Gleam using the nightly release.
    • Notice that some package managers might allow you to install it this way.
  • Grab this elixir branch.
  • Edit lib/mix/lib/mix/gleam.ex and replace @required_gleam_version with 1.9.0.
  • Run make in the root folder of the Elixir project and add its bin folder to your $PATH

If you were using mix_gleam, undo its setup.

Usage

After running mix deps.get the Mix workflow doesn't change. deps.compile should compile Gleam path deps.
If you use gleam code from your elixir app, it should just work (as long as you have a gleam.toml file - you might want to move gleam code to a sibling folder or into gleam/src) I think that otherwise the mix.exs file will take precedence over the gleam one.

What needs to be tested?

  • Anything related to Erlang/OTP shenanigans (.app files, extra_applications and ensuring those start, etc.)
  • If you have an existing project using mix_gleam, that's a great opportunity to replace it with this.
  • Gleam Git dependencies.
  • Just try to break it please.

Thanks

Just report back with your findings and thanks 🙇🏽

Note: I hope these instructions are enough and correct. Let me know if that's not the case.

Comment thread lib/mix/lib/mix/tasks/deps.compile.ex Outdated
@josevalim

Copy link
Copy Markdown
Member

@Papipo thanks for all the work so far!

One question: I thought that gleam shipped as a Hex package, so wouldn't it be possible for the user to depend on the gleam package or similar instead of us depending on a system wide gleam installation?

@Papipo

Papipo commented Mar 26, 2025

Copy link
Copy Markdown
Author

@Papipo thanks for all the work so far!

One question: I thought that gleam shipped as a Hex package, so wouldn't it be possible for the user to depend on the gleam package or similar instead of us depending on a system wide gleam installation?

No, gleam is a single binary written in Rust. I don't know if we can provide a hex archive maybe? I don't know how these work.

How is rebar installation handled? I know that mix is able to install it locally, but I am not sure what that means exactly. It's still a binary as well, right?

@josevalim

josevalim commented Mar 26, 2025

Copy link
Copy Markdown
Member

For Rebar, we have some binaries which we upload to hex.pm, and we version control them every year or so. Although I don't think this will work for Gleam, because people want to upgrade it more frequently, and Rebar is only a build tool, not really a compiler. So I guess we need to be clear we are using the system one and it is up to them to enforce the team uses the same version across the board (which is how we deal with make and the elixir executable itself).

@Papipo

Papipo commented Mar 26, 2025

Copy link
Copy Markdown
Author

For Rebar, we have some binaries which we upload to hex.pm, and we version control them every year or so. Although I don't think this will work for Gleam, because people want to upgrade it more frequently, and Rebar is only a build tool, not really a compiler. So I guess we need to be clear we are using the system one and it is up to them to enforce the team uses the same version across the board (which is how we deal with make and the elixir executable itself).

In fact the binary is also the LS, so you definitely want to have full control of the version you are using, etc.

@josevalim

josevalim commented Apr 2, 2025

Copy link
Copy Markdown
Member

Quoting @lpil on the forums:

The Gleam compiler only generates Erlang from Gleam modules, but it doesn’t know what any Elixir modules in the package expand to, so it cannot create .app files unless it owns the whole project and so will be performing Elixir and Erlang compilation. This means that any build tool that uses the Gleam compiler will need to generate the required BEAM .beam and .app files.

Apologies but I am a bit confused. We are using gleam compile-package which emits the .beam files already, right? Here is what we invoke: https://github.com/elixir-lang/elixir/pull/14262/files#diff-da9fa810e81e1fe6ac8d4defde11dc457f0066fc9e442e85a96ea2e182414eacR336 - in this case, doesn't Gleam already own the whole package? We don't invoke anything else so if additional steps are necessary, they are not being executed here.

The .app file has things like app name, version, description, registered processes, app environment, and module names. We would need to lift all of this information from the Gleam .toml and make assumptions on how Gleam wants those be used. Worst case scenario, couldn't Gleam support an --app file on compile-package that generates it, based on the outputs of its --out directory?

@Papipo

Papipo commented Apr 2, 2025

Copy link
Copy Markdown
Author

Yes, compile-package does emit beam files.

From @lpil comment over elixirforum I assume the problem are colocated Elixir files within a Gleam project. The Gleam compiler knows nothing about them so any modules they define can't be written in the .app file, I guess.

What I have done here is use the Mix task to generate the .app file (and inject the two options gleam.toml supports for the erlang target: mod and extra_applications). As you can see we now have a full-fledged .app file.

Let me know if this looks good or if I should revert it. Thanks.

@josevalim

Copy link
Copy Markdown
Member

Yes, for collocated projects that’s the responsibility of Mix, but for packages I would say that’s Gleam responsibility. Otherwise there is even more we need to understand from Gleam TOML’s and we may need to keep track of it as it evolves. So is there a chance for it to emit an .app file either by default or via a flag? On the plus side, if Rebar wants to integrate Gleam as well, they can reuse it to emit apps too. :)

@Papipo
Papipo force-pushed the add-gleam-compiler branch 3 times, most recently from b70f8fd to b64f23a Compare April 3, 2025 10:14
@Papipo

Papipo commented Apr 7, 2025

Copy link
Copy Markdown
Author

Apparently this seems to be working fine except that people sometimes need to compile twice . Could this be code path related maybe? It's probably something stupid I missed.

@inoas

inoas commented Jun 14, 2026

Copy link
Copy Markdown

Is there anything blocking this or something I can help with?
We could really use this in existing gleam projects where we would want to use mix as the build tool and utilize some elixir libraries such as nimble parsec in conjunction with gleam based code.

@josevalim

Copy link
Copy Markdown
Member

Hi @inoas! It is a 100% blocking on us. Both this and #14352 requires us to familiarize with the code and test it under different scenarios, which takes time. Bugs always take higher priority and the remaining of our development time goes almost fully into the type system, which delays other features. We will move them forward once we are able to.

Comment thread .github/workflows/ci.yml
with:
otp-version: ${{ matrix.otp_version }}

gleam-version: "1.11.1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Papipo this could be increased now or whenever there is movement on the PR again, @lpil which version would we chose here? how would we keep this updated? And to what version (latest major stable minus one plus patches?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing all versions seems to much. I guess it would make sense to just keep it up to date.

@spicychickensauce

Copy link
Copy Markdown

I usually don't do this, but I really need this one to be merged soon. Is there really nothing that us outsiders can do to help speed this one up?
I can't use gleam on the server at work as long as there is no nice way to integrate with our existing elixir project.
This is starting to become a problem, as we have already introduced gleam to the frontend. And at this point we have written some gleam library which we would like to also use on the server. I know we can kinda make it work by making our dev and deployment setup more complex by introducing some extra steps, but thats a very poor solution..

@josevalim

josevalim commented Jul 4, 2026

Copy link
Copy Markdown
Member

@spicychickensauce I took 2h from my weekend to take a quick look at the PR. Please see below.

Apologies for the length of the text but technical discussions like these require a lot of considerations. I have added a TL;DR on top for convenience.

TL;DR

  1. I'd say dependency management code is good to go
  2. We should remove the --no-beam flag from gleam compile-package. It will be slower but it could provide better long term compatibility if Gleam can own more of its compilation semantics
  3. We need either an option for gleam to emit an .app.src or, preferrably, we have gleam compile-package --app so Elixir doesn't have to assume what Gleam considers modules and/or configuration

Feedback

  1. We are still depending on gleam.toml configuration, such as "application_start_module". I don't believe Elixir should be responsible for interpreting Gleam configuration (with one exception, see point 2 below). Otherwise, if the configuration changes or new options are added, then it becomes responsibility of the Elixir team to keep up. For example, it seems gleam.toml has a description field which we are not handling. Ideally we want gleam to emit something that Elixir already understands. I agree that asking gleam to emit a mix.exs file is likely too much (and it creates the opposite issue, with gleam depending on Elixir) but emitting an .app file can be a good trade-off (see point 4).

  2. While I said we should not depend on gleam.toml, it is ok to do so for dependency handling. That's an exception we do for Rebar, so it is fair we make a similar exception for Gleam, since there is no standard to describe dependencies in BEAM.

  3. Based on this pull request, it seems you can place .erl files (and .ex files) in your Gleam src/ directory. This pull request supports this by calling both compile.erlang and compile.elixir but now I am worried about subtle differences between how Elixir/Gleam compile .ex/.erl files. If there is an inconsistency, who is wrong? For example, does Gleam support .hrl files? If they do, does it work in this PR? If they don't, they may support it in the future and then Elixir has to play catch up.

    I know we had discussions about gleam compile-package, but given gleam can do more than just convert .gleam files into .erl files, we probably want to rely on gleam to compile as much code as possible. Hopefully this is a matter of removing the --no-beam from gleam compile-package?

  4. Regarding the generation of an .app.src file, it was said that Gleam does not generate one because Gleam does not know which modules are part of a project. However, this makes Elixir's responsibility to decide this, even though Elixir knows less about Gleam projects than Gleam? What Elixir will likely do is to get all modules emitted by gleam compile-package and put them in the .app.src emitted in point 1, but I honestly do not understand why that cannot be done by gleam compile-package, perhaps via an opt-in --app flag. Ideally, we would remove --no-beam, add an --app flag, and Gleam emits an ebin directory ready for use. Which is exactly how we integrate with Rebar3.

@josevalim

Copy link
Copy Markdown
Member

And before I forget, thank you @Papipo, @eksperimental, @dvic for implementing, reviewing, debugging, etc! ❤️

@spicychickensauce

Copy link
Copy Markdown

Oh my, now I feel bad for making you miss part of your weekend 🥺.
Thank you so much for taking the time, it's hugely appreciated ❤️
@Papipo please let me know if I can help out with testing or anything else, once it comes to that

@Papipo

Papipo commented Aug 31, 2026

Copy link
Copy Markdown
Author

@josevalim I have made the requested changes. For now the minimal required version of gleam is 1.19.0, but I don't know if my changes will make it. I will update the version if that's not the case.

I ran a test with a locally compiled gleam with my fork and all tests still pass. With gleam 1.18 and the latest changes from the last commit in this PR they don't.

Comment thread lib/mix/lib/mix/tasks/compile.app.ex Outdated
vsn: to_charlist(version)
]
|> merge_project_application(project)
|> merge_project_application(project, config[:application])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the changes to this file?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but that added support for this. Let me know if we should remove this altogether or if we want to keep it. I can extract the feature into its own PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we should remove it from here indeed. Please open up a discussion around it on the mailing list with potential use cases!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread lib/mix/lib/mix/tasks/deps.compile.ex Outdated
"--out",
out,
"--lib",
lib

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is WIP but we likely want the --prod flag here too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added it and made the needed adjustments. Support for --prod in gleam is a separate PR but here locally I have everything working fine and tests passing.

Comment thread lib/mix/lib/mix/gleam.ex
def load_config(dir) do
File.cd!(dir, fn ->
with {:ok, output} <-
gleam(~W(export package-information --out /dev/stdout)),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will --out /dev/stdout work on Windows? 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question 😄

@Papipo Papipo Sep 1, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a tmp file I am afraid. So I guess it's plain better to use a tmp file in all platforms, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some Unix tools allow "-" to mean stdin/stdout. Perhaps the same could be added to gleam? There should be a way to export package information to stdout!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we were talking about that on the gleam discord. I'll see what I can do.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread lib/mix/lib/mix/gleam.ex Outdated
Comment on lines +34 to +41
case Map.has_key?(json, "dev_dependencies") do
true ->
Map.get(json, "dev_dependencies", %{})

# Old format, for compatibility purposes
false ->
Map.get(json, "dev-dependencies", %{})
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case Map.has_key?(json, "dev_dependencies") do
true ->
Map.get(json, "dev_dependencies", %{})
# Old format, for compatibility purposes
false ->
Map.get(json, "dev-dependencies", %{})
end
json["dev_dependencies"] || json["dev-dependencies"] || %{}

However, note a package runs in production by default, and for this reason we don't include its dev dependencies. It is likely that we can remove this code altogether.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

dest = MixTest.Case.tmp_path(fixture)
File.mkdir_p!(dest)
File.cp_r!(source, dest)
end)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need these changes? This is setting up fixtures but the fixtures already exist. Copying to tmp happens during the test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't even recall why this was added

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

@josevalim josevalim Aug 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably get rid of this fixture, as we are venturing into testing Gleam behaviour.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

@josevalim

Copy link
Copy Markdown
Member

@Papipo thank you, I did one initial pass!

@josevalim

Copy link
Copy Markdown
Member

Btw, this looks much better, as we depend way less on Gleam internals!

@Papipo

Papipo commented Sep 1, 2026

Copy link
Copy Markdown
Author

@josevalim I am also removing the check for the per-package required gleam version. When mix tries to compile the package, the gleam command will already make this check so I think it's redundant.

@Papipo

Papipo commented Sep 1, 2026

Copy link
Copy Markdown
Author

@josevalim everything has been dealt with except the windows thingy. I'll come back once that is settled.

@Papipo
Papipo force-pushed the add-gleam-compiler branch from 0a1b51f to 33380fd Compare September 1, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

8 participants