-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
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
buildMix: fix: initial try #112477
buildMix: fix: initial try #112477
Conversation
@hauleth you might be interested in this. Absolutely no worries if you don't have the time. |
@cw789 you might be interested into this. I want to distill some of the greatness you put in nix_elixir_example in the doc ideally. No worries if you don't have time. |
Initial adding of documentation |
Hi Raphael. First thing I notice, buildMix seems to be a different thing then.
To this trouble - releases always need a writable tmp directory, You can do this also within env.sh (documented within mix release). |
@cw789 Thanks Chris, your help is much appreciated. ❤️ |
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.
Thanks for the PR! Here are small overview comments, I haven't gotten to test it properly.
#### Building in a Shell (for Mix Projects) {#building-in-a-shell} | ||
#### Elixir - Phoenix project | ||
|
||
Here is an example shell.nix |
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.
Is there any way of automating the creation of a nix shell for Elixir projects? Obviously this would be asking for another PR for however wishing to do so, but this seem quite useful, and I think some things can be automated, like setting mix-related variables. Can't this use passthru.env
?
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'm thinking i should have a look at @cw789 's repo on nix_elixir_demo, he has a lot of stuff in there relating to shell and making the environment work.
https://github.com/cw789/elixir_nix_example
I agree with you, it would be way better to define a shell that can be passed through.
env = shell self; | ||
}; | ||
}); | ||
in pkg lib.fix |
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'm new to the lib.fix
function, but shouldn't it be lib.fix pkg
? Am I missing something?
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 just copied that from before. I have to say I'm not sure. I tested both ways and it doesn't seem to make a difference. We'll have to ask somebody more knowledgeable here.
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.
From my understanding, this allows the use of self
, which is used only in passthru.env
. I can confirm that trying to build mobilizon.env
with pkg lib.fix
produces an evaluation error, while with lib.fix pkg
it builds but fails anaway with variable $src or $srcs should point to the source
. In my opinion, we should drop the passthru
and lib.fix pkg
for now and open another PR if the need arises.
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.
Isn't the passthru stuff used for potentially passing the shell ? I'm not sure, just wondering if by removing those, we are loosing the ability to define ourselves the shell so that the user doesn't need do
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 am no sure, as far as I know, nix-shell for a package has always worked for me, whether the package defines an env
. But I am not proposing to remove them indefinitely, though. I think defining the shell there is nice, because we can easily extend it using all the attributes of the package.
As an aside, I would never have thought that you could spin up postgresql like that! For testing I personally would create a nixos VM, but your method is good to have in mind 😃 |
Also, if we are changing the meaning of Also, I have a question that is maybe out of scope for this PR, but might be nice to keep in mind. It seems to me that configuration of Mix projects are given at build time. Does that mean that for future NixOS services of Mix applications, rebuilding your system will mean you will have to rebuild every configured Mix applications? Is there a way around it? |
@minijackson On the mix release comment, unless I misunderstand something, this is using mix releases to pack your application. After @cw789 's comment, here is what I am thinking about.
|
I have added a build.nix file to make the process simpler and it includes a demonstration of the RELEASE_TMP variable. It's working great. |
All things considered, would it be a possibility to ask users to put their DATABASE_URL and SECRET KEY in the build.nix file? |
oops, that's my bad, I forgot to read after the comma. I got the impression that it wasn't when I read that there was no install phase, I figured that it was up to the end-user to choose what to install. In retrospect, that is obviously not the case.
Isn't I've had a bit of thinking and experimenting with your PR. First of all, I managed to compile the Elixir part of Mobilizon without any particular issues 🎉 I was thinking that it would be best if we could specify in the configuration that we want these variables to be resolved at runtime, and fetch them from the environment (instead of fetching the environment at build time). I found this nice feature while looking through the docs: you can specify a With this, I added my custom Mobilizon {
# ...
preBuild = ''
cp "${./runtime.exs}" config/runtime.exs
'';
} With this, here's what I'm proposing for the moment: we add a An even better solution in my opinion would be to generate the But the good news is that I managed to get Mobilizon local instance up and running 🎉, although I had to compile the yarn part manually... |
postUnpack = '' | ||
export HEX_HOME="$TMPDIR/hex" | ||
export MIX_HOME="$TMPDIR/mix" | ||
export MIX_DEPS_PATH="$TMPDIR/deps" |
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.
Is there any particular reason to put mix deps in the directory, and not directly in $src/deps
? I personally find that this makes the environment a bit different than usual, which can surprise users. At least it bite me a little bit while I was fixing the Mobilizon build, although this was definitely not a major issue.
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.
Can you detail a bit what you mean?
I have no problem with changing this, just want to understand where the issue lies.
I was actually thinking of setting the deps_path to where dependencies are already and not even do a copy.
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.
So regarding this, doing a cp on $src will not work
cp: cannot create directory '/nix/store/ycqg5a39gzjcvim7cszk7q7260glw7lm-source/deps': Permission denied
I learned something today as well.
I also tried not copying the dependency folder, but it doesn't work. The compilation of the dependencies will require that the dependency folder is writable. I also learned that today. I added a comment to prevent future people from being tricked.
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.
Can you detail a bit what you mean?
When packaging mobilizon, I had to provide some locale files in a dependency directory, because it was downloading them at build time. My first instinct was to add this in pre-build:
cp -R ${cldrLocales}/* deps/ex_cldr/priv/cldr/locales/
That was because in a "normal" elixir project, the dependencies are under the same directory as the source code. But with the current implementation, this had to be corrected to:
cp -R ${cldrLocales}/* $MIX_DEPS_PATH/ex_cldr/priv/cldr/locales/
So regarding this, doing a cp on $src will not work
What about if you do cp --no-preserve=mode -R "${mixDeps}" ./deps
? I think the $src
is the nix store path of the source, not the current build directory, my bad.
This really is just nitpicking, though, if it's a hassle, you can just forget about it ^^
@minijackson I like your idea of using runtime variables. I personally only use runtime env vars for secret stuff, however I'm concerned about the experience for people starting a new phoenix project. I don't know why, but the phoenix default is to have DATABASE_URL as a compile time dependency. I was thinking to add Then we could define PS: Here is the explanation around RELEASE_TMP and mix release.init.
PPS: out of curiosity, did you use the yarn2nix tool? I'm wondering how well it works, if it works well, I was thinking we could add some documentation about it too. |
After some more tests, here is what I found out.
Here are the tasks that could be improved.
I'm not really sure what to put exactly in the ExecStart command |
What do you mean by user? Do you mean the package maintainer, or nixos module maintainer? If we want to go through with the RFC42-style module, I think it would be best to patch If we want to go through with runtime variables, though, I would personally use
Do you mean that the { ... }:
buildMix {
# ...
DATABASE_URL = "...";
}
I meant the software developers (as in the Mobilizon devs). But as you said, it is not necessary, one less thing to worry about ^^
I think there is no need for that, if they are not there,
Thanks for the clarification, it helped. It seems
I did try to use it, but the "resolutions" feature of
I just did, to test out things ^^ I can also see it being useful for development.
I think your systemd service looks good. Here is some small nitpicks: {
systemd.services.${name} = {
description = "My super service";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
# Raw systemd service options inside serviceConfig
serviceConfig = {
ExecStart = "${pkgs.${name}}/bin/${name} start";
# Encouraging people of having a nice set of privacy defaults,
# but might not be applicable everywhere.
# Best if it used the software developers upstream.
DynamicUser = true;
StateDirectory = "${name}";
# Implied by DynamicUser, but just to emphasize due to RELEASE_TMP
PrivateTmp = true;
EnvironmentFile = "/path/to/my/secrets.env";
# I am not sure about the Restart and ExecReload options,
# does sending a SIGHUP signal does something to the Erlang VM?
};
# Using the nixos declarative way
environment = {
# Set to "/tmp", made private by systemd, although not necessary here
# Maybe this should be in buildMix, set in the wrapper
RELEASE_TMP = "/tmp";
# Maybe an example for the StateDirectory?
MY_DATA_DIR = "/var/lib/${name}";
};
};
} Sorry for the long message, and thanks again! |
''; | ||
preConfigure = '' | ||
# if you have build time environment variable add them here | ||
export MY_ENV_VAR="my_value"; |
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.
Is this the same as the one in the section above? So build environment variables must be exported in both steps? You can make them available to all steps by sending them to the underlying mkDerivation as Nix variables such as MY_ENV_VAR = "my_value";
. You could add an attribute set that you include in the args sent to mkDerivation to make it clearer.
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.
You're right. Adding an attribute called build_env_vars = { MY_ENV_VAR = "my_value";};
then using a merge in the mkDerivation in both the build and the fetch steps to make them available.
Thanks!
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 added a buildEnvVars attribute. Trying to merge this and the default attrs resulted in an error, so I have removed the merging of the attrs with the rest of the variables for mkDerivation. What changes is that you can't override as easily steps for the derivartion. Here in particular, the preConfigure hook is not overridable anymore from passed attrs. It looses a bit of flexibility. Not sure how to proceed otherwise.
Thanks for all the suggestions. I need a couple of days before I can come back to this. I'll reply soon. |
/status awaiting_reviewer |
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 don't know elixier, mix or elang but since we did already quite a long bikeshedding session here I think this is in a good place and if something is broken we (I mean you) can always fix it later when possible someone else used it.
a0e8ba6
to
8fdf5fa
Compare
@SuperSandro2000 Thanks a lot for spending time on this! |
8fdf5fa
to
138d6d3
Compare
@SuperSandro2000 I've taken care of the last comment. (I should have thought about adding it before). |
Reminder: Please review! This Pull Request is awaiting review. If you are the assigned reviewer, please have a look. Try to find another reviewer if necessary. If you can't, please say so. If the status is not accurate, please change it. If nothing happens, this PR Note: This feature is currently broken. The bot will not actually change the status. If you see this message multiple times, please request a status change manually. |
I wonder how we can get this on https://github.com/orgs/NixOS/projects/6#card-56314431 |
@NobbZ great idea! I think it's a matter of asking the release manager right? |
138d6d3
to
1a14eda
Compare
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 have managed to compile two (private) Elixir projects with this, with the only quirk being the .bat
removal. fixupPhase = ":";
mitigates that for now, but it would be nice to see an additional check before removing the bat.
1a14eda
to
481832b
Compare
''; | ||
|
||
dontBuild = true; | ||
|
||
installPhase = '' | ||
installPhase = attrs.installPhase or '' | ||
runHook preInstall | ||
mix deps.get --only ${mixEnv} |
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.
Does it make sense to compile the dependencies here with mix deps.compile
and potentially cache that build for larger (phoenix) projects? Or does elixir/nix not like that?
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.
mix deps.compile
is done in the configure phase of the mixRelease
.
It could be possible to compile dependencies here.
It did not feel natural to have a compilation step into a step to fetch dependencies.
The other concern I had, was that what if you wanted to compile your dependencies in a specific way.
If you wanted to cache the compilation step, you could potentially add another intermediary step.
I have to say I don't know yet the consequences that it would have.
Perhaps it's something that can be tried for 6 months somewhere so we can make a PR about it later?
Curious if anybody has a more definite opinion.
It could be something to ask on an official elixir forum as well.
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.
mix deps.compile doesn't produce deterministic output
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.
Oh right, I forgot about the config part of things 🤔
bummer 😞
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.
@schneebyte thanks for the answer here. I kept on thinking about this.
Do you mean to say that mix deps.compile will produce something different based on the configuration?
I'm just wondering what is producing the non-deterministic behavior, is it some input that we can pass in? Or is it that no matter what we pass in, we will never get deterministic output?
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.
Well there is compile time configuration, https://hexdocs.pm/mime/MIME.html for example.
And also the .mix directories in _build/env/lib/xyz/.mix change, but those could be deleted if you pass --no-deps-check
to next mix command.
I actually got my Elixir project to produce deterministic output, until i added phoenix_live_dashboard to deps, suddenly one .beam file was changing between builds.
I narrowed it down a little. If i compile this a few times the beam file changes.
defmodule Foo do
def bar() do
~r/(?((.+?/){3})).(?(/.){3})/
end
end
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.
Thanks a lot for the precision, very interesting!
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.
Thank you so much @schneebyte for providing a minimal example! I was actually investigating this a while ago, and it seemed like the built in pcre2 library in Erlang sometimes doesn't produce deterministic compiled regex data. I feel like with the minimal example, we should be able to report a bug to the Erlang project
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.
That example was actually wrong, formatting messed it up.
I narrowed it down to the different length of the names of named captures?
But only with the sigil in elixir, not sure how to do it with :re.compile.
~r/(?<xxx>.)(?<yyy>.)/
Is fine because xxx and yyy are both length 3
But with
~r/(?<xxxxx>.)(?<yy>.)/
the beam file differs because xxxxx and yy have different lengths.
Weird stuff.
@balsoft I believe this is ready to merge. No worries at all if you don't want to take the responsability or don't have the time to invest in this. |
@balsoft ❤️ thanks a lot ❤️ @SuperSandro2000 @minijackson @cw789 @NobbZ @schneebyte @afontaine @kalbasit @hauleth |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/announcing-mixnix-build-elixir-projects-with-nix/2444/20 |
Motivation for this change
buildMix is broken
More details on #105002
Things done
sandbox
innix.conf
on non-NixOS linux)nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
./result/bin/
)nix path-info -S
before and after)Here are instructions on how to test.
nix-build ./build.nix
in it.(I have added the instructions in the beam-section to the build.nix file).
(upon your first try, nix will complain that the sha256 is empty, I'm just leaving this empty so that everybody can verify by themselves.)
(please remember that in order to run the release you will need to set the env var RELEASE_TMP to a writable directory)
Here are the questions I have that are open
it can be verified, that whatever sha256 output is given, the resulting hash will always be different.
I have to say I'm a little puzzled as to why that is the case.
.