-
Notifications
You must be signed in to change notification settings - Fork 455
Add doc about making brew package with dune pkg #12744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gridbugs
wants to merge
1
commit into
ocaml:main
Choose a base branch
from
gridbugs:homebrew-package-doc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+101
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| How to make a Homebrew Package with Dune | ||
| ======================================== | ||
|
|
||
| This guide will show you how to make a Homebrew package for an application | ||
| using Dune package management. The only dependency of the Homebrew package will | ||
| be Dune, and all OCaml dependencies (including the OCaml compiler) will be | ||
| installed by Dune while building the Homebrew package. | ||
|
|
||
| To use Dune package management to build a project as a Homebrew package, the | ||
| project must have a source archive hosted online somewhere (e.g. a gzipped | ||
| tarball on the project's Github release page). | ||
|
|
||
| Before making a Homebrew package, it's a good idea to familiarize yourself with | ||
| Homebrew's terminology and packaging conventions `here | ||
| <https://docs.brew.sh/Adding-Software-to-Homebrew>`__. | ||
|
|
||
| Homebrew packages are recommended to be source-based, and for the source code | ||
| to be explicitly versioned, so for this example assume ``my_app`` has a | ||
| versioned archive hosted on Github with version ``0.1.0``. | ||
|
|
||
| Homebrew can generate a starting point for a formula if you point it at a | ||
| source archive hosted on Github: | ||
|
|
||
| .. code:: console | ||
|
|
||
| $ brew create https://github.com/me/my_app/archive/refs/tags/0.1.0.tar.gz | ||
|
|
||
| A source archive like the one in the above command is generated when you | ||
| release a project on Github. The above command will generate a file named | ||
| ``my_app.rb`` in your current tap. All the project metadata will be filled in | ||
| automatically based on the project on Github. All we need to do now is to | ||
| specify dependencies and the commands ``brew`` should run when installing the | ||
| package. | ||
|
|
||
| Here's the complete formula for ``my_app``. Note that the ``test`` section is | ||
| intended to be a sanity check of the core functionality of the package, not a | ||
| complete integration test suite. Read more about Homebrew package tests `here | ||
| <https://docs.brew.sh/Formula-Cookbook#add-a-test-to-the-formula>`__. Note | ||
| however that tests run in an environment without access to build dependencies | ||
| such as Dune, so ``dune runtest`` can't be used to test Homebrew packages. | ||
|
|
||
| .. code:: ruby | ||
|
|
||
| class MyApp < Formula | ||
| desc "My awesome app" | ||
| homepage "https://github.com/me/my_app" | ||
| url "https://github.com/me/my_app/releases/download/0.1.0/0.1.0.tar.gz" | ||
| sha256 "eb8705de406441675747a639351d0d59bffe7b9f5b05ec9b6e11b4c4c9d7a6ee" | ||
| license "MIT" | ||
|
|
||
| depends_on "dune" => :build | ||
|
|
||
| def install | ||
| # Uncomment if the source archive lacks a lockdir: | ||
| # system "dune", "pkg", "lock" | ||
| system "dune", "build", "@install", "--release", "--only-packages", "my_app" | ||
| system "dune", "install", "--prefix=#{prefix}", "my_app" | ||
| end | ||
|
|
||
| test do | ||
| # Test your application here! | ||
| system "my_app", "--version" | ||
| end | ||
| end | ||
|
|
||
| This assumes that the name of the package in the source archive is ``my_app``. | ||
| That is, the archive contains a ``dune-project`` file defining a package named | ||
| ``my_app``, or that the archive contains a ``my_app.opam``. The archive may | ||
| contain multiple packages provided that ``my_app`` is one of them. | ||
|
|
||
| Note the comment at the beginning of the ``install`` method. The commented-out | ||
| code invokes Dune's solver to compute the transitive closure of packages that | ||
| will be built as dependencies of ``my_app``, and stores the result in a | ||
| directory named ``dune.lock`` - a "Lock Directory" or "lockdir" for short. | ||
| Solving dependencies in the ``install`` method should be avoided when possible. | ||
| This is because Dune solves dependencies in the context of the current tip of | ||
| the `Opam Repository <https://github.com/ocaml/opam-repository>`_, which | ||
| changes as new Opam packages are released. This means that the exact solution | ||
| computed by Dune can change as new versions of dependencies come out. Homebrew | ||
| encourages package builds to be `reproducible | ||
| <https://docs.brew.sh/Reproducible-Builds>`_ when possible, but solving | ||
| dependencies each time a package is installed prevents that package from being | ||
| built reproducibly. To allow reproducible builds, always include the project's | ||
| lockdir in its source archive (generate a lockdir by running ``dune pkg lock``) | ||
| when releasing a package. Only solve dependencies in the ``install`` method | ||
| when packaging a project whose source archive lacks a lockdir. | ||
|
|
||
| If there are any packages with external dependencies (i.e. ``depexts``) in | ||
| ``my_app``'s transitive dependency closure, their corresponding Homebrew | ||
| package must be added as a dependency of ``my_app``'s Homebrew packages by | ||
| adding a ``depends_on`` entry for each. List all the external dependencies | ||
| among ``my_app``'s transitive dependency closure by running: | ||
|
|
||
| .. code:: console | ||
|
|
||
| $ dune show depexts | ||
|
|
||
| External dependencies can be platform-specific, so if you're planning to make the | ||
| Homebrew package available for macOS, be sure to run the above command on a Mac | ||
| to determine which external dependencies need to be added to the formula. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding
here could make this work for nearly any OCaml package that uses Dune. To me this seems more practical as it does not require the tarball to ship with lock files that support the platform that the user is trying to package for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem with that approach is that builds won't be reproducible if the source archive doesn't include a lockdir. My preference would be for the guide to instruct how to release a homebrew package that never fails to build due to solver errors, and always builds the same artifacts on any given platform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since reproducible builds are a goal for homebrew encouraging an approach which support reproducibility seems advisable?
However, since presumably homebrew dependencies can be updated between builds, I'm not sure how much this is goal is actually achieved in practice in homebrew.
If we had a flag like
dune build --pkg-enabledor could set the envar withDUNE_CONFIG__PKG_ENABLED=true dune build, then we could have a single instruction set, that would work with a checked in lock dir if present, or fallback to a lockless dependency management otherwise, right?