-
Notifications
You must be signed in to change notification settings - Fork 50
Reproducible development environment and preparation for publishing NGS into nix package manager to increase adoption #693
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
base: master
Are you sure you want to change the base?
Conversation
Solution:
- Add nixpkgs-compatible package.nix for upstream contribution:
- Use stdenv.mkDerivation with finalAttrs pattern (package.nix)
- Fetch source from GitHub with verified hash (package.nix)
- Pre-fetch peg 0.1.18 to avoid network during build (package.nix)
- Handle Darwin/Linux differences via lib.optionals (package.nix)
- Wrap binary with NGS_PATH for stdlib discovery (package.nix)
- Include comprehensive meta with GPL-3.0 license (package.nix)
- Add flake.nix for local development workflow:
- Override package.nix to use local source with -dev suffix (flake.nix)
- Filter build artifacts from source tree (flake.nix)
- Provide devShell with cmake, clang-tools, gdb (flake.nix)
- Support all four platforms via flake-utils (flake.nix)
- Add direnv integration for seamless development (.envrc)
- Pin flake inputs for reproducibility (flake.lock)
Technical notes:
- peg version constraint: NGS build-scripts/patch-leg-output.sed patches the leg parser generator output to add location tracking.
These patches modify function signatures (yyDo, yyPush, yyPop, yySet) to accept a 4th location[4] parameter.
Nixpkgs ships peg 0.1.20 which generates code with direct yySet() calls that the sed script doesn't patch, causing compilation errors.
Solution: pre-fetch peg 0.1.18 tarball and populate CMake's ExternalProject download directory in preConfigure.
- NGS_PATH discovery: The NGS binary searches for stdlib.ngs in NGS_PATH.
Without wrapping, it would fail to find the standard library.
The postInstall phase uses wrapProgram to set NGS_PATH=$out/lib/ngs.
- Darwin compatibility: Build scripts use GNU sed extensions (like \+).
On macOS, /usr/bin/sed is BSD sed.
Solution: add gnused to nativeBuildInputs on Darwin and prepend to PATH in preConfigure.
Verified working:
$ nix build && ./result/bin/ngs -p 'sum(1..100)'
4950
$ ./result/bin/ngs -p '[1,2,3,4,5].map(X*2)'
[2,4,6,8,10]
$ ./result/bin/ngs -p '{"a": 1, "b": 2}.mapv(X+10)'
{a=11, b=12}
$ ls ./result/share/man/man1/
na.1.gz ngs.1.gz ngsint.1.gz ngslang.1.gz ngsstyle.1.gz ngstut.1.gz ngswhy.1.gz
Solution: - Use default formatter on Nix files to comply with follow-up changes by other Nix enthusiasts
|
Sounds good. Thanks! I'll need to review it. I plan to do it in the next few weeks. |
|
Started looking. As a person with no Nix experience I'm not fast at this to put it mildly. Any tips to accelerate my understanding of the PR (changed files) are 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.
This change is there purely for ergonomics of people who are using direnv-nix.
"use flake" directive means "dear direnv, when the user enters this directory, find file flake.nix and evaluate it with nix develop".
What happens when you run nix develop is that nix installs all the necessary packages into /nix/store behind their input hashes and then "activates" a mutually-compatible ser of packages, giving access to them in PATH.
| .vagrant/ | ||
|
|
||
| .direnv/ | ||
| result |
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.
Nix creates result/ directory when you run nix build. Symlinks to built artefacts in /nix/store live there
|
|
||
| .vagrant/ | ||
|
|
||
| .direnv/ |
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.
Direnv creates .direnv to maintain its state.
| @@ -0,0 +1,75 @@ | |||
| { | |||
| description = "NGS - Next Generation Shell"; | |||
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's the name of the show!
| description = "NGS - Next Generation Shell"; | ||
|
|
||
| inputs = { | ||
| nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; |
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.
This looks scarier than it is. All the cool kids are on unstable.
I used to use Arch, btw
|
|
||
| inputs = { | ||
| nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; | ||
| flake-utils.url = "github:numtide/flake-utils"; |
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.
Nix build system didn't "find" flake framework of unification of inputs / outputs for Nix expression straight away. Before we used to write simple nix programs which would be [lazily] evaluated top to bottom.
flake-utils is like flake framework stdlib.
It has some convenience functions like generating an attrset per each system supported by default (used here), to flatten a bunch of nested attributes into a flat attrset , etc.
| self, | ||
| nixpkgs, | ||
| flake-utils, | ||
| }: |
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.
This is a function saying "give me a reference to the outputs of this function (recursive), everything that is packaged in whatever the source of nix packages is (unstable "channel" of official nix packages in this case) and flake utils, and I will give you an attrset defined below".
| pkgs = nixpkgs.legacyPackages.${system}; | ||
|
|
||
| # Local source filter - exclude build artifacts | ||
| localSrc = builtins.path { |
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.
Source code is very important to build software!
We only care about the actual source tree, not build artefacts or git object store.
| }; | ||
|
|
||
| # For local development, override the package to use local source | ||
| ngs = (pkgs.callPackage ./package.nix { }).overrideAttrs (oldAttrs: { |
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.
When we buuld ngs for nixpkgs (this is my end goal with this work -- to have ngs included into official nixpkgs unstable), we want to pin the version to a certain place fetchable with some fetcher. We use github fetcher, because github fetcher is so easy to use.
In package.nix you will see that src is remote!
But to build stuff locally, we need to override this source with local source.
| }; | ||
|
|
||
| devShells.default = pkgs.mkShell { | ||
| inputsFrom = [ ngs ]; |
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.
This is where the package is coming from, so we just reuse release dependencies from there. We separate concerns and oly add developmnent dependencies here like valgrind profiler, gdb, and other tools I'm scared to launch.
| pcre, | ||
| # Darwin-specific: GNU sed is required for build scripts | ||
| gnused, | ||
| }: |
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 this is understandable, it just follows the list of dependencies that you have listed in your scripts.
|
|
||
| stdenv.mkDerivation (finalAttrs: { | ||
| pname = "ngs"; | ||
| version = "0.2.17"; |
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.
Apr 5th release
| hash = "sha256-j7OAXHADc2LlabKxVgYiKeDDtLttDVIavhQZSGyPGlE="; | ||
| }; | ||
|
|
||
| # NGS requires peg 0.1.18 specifically due to custom patches in build-scripts/ |
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 was a bit of a pain in the rear to package, but I managed.
I'm lucky that it seems that everything is mutually compatible between nixpkgs-unstable and peg-0.1.18!
| pkg-config, | ||
| pandoc, | ||
| gawk, | ||
| makeWrapper, |
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 guess I need to emphasise this one!
When CMake installs your software, it assumes LFS compliance.
With nix everything lives in /nix/store, thus at runtime software built with Nix often can't find libraries.
makeWrapper replaces global runtime assumptions with explicit, reproducible runtime configuration.
But one picture is better than 1000 words:
sweater in pentavus~/Github/ngs on master via C v21.1.2-clang via △ v4.1.2 via 𝗩 via impure (nix-shell-env) on (us-east-1)
λ nix build
sweater in pentavus~/Github/ngs on master via C v21.1.2-clang via △ v4.1.2 via 𝗩 via impure (nix-shell-env) on (us-east-1)
λ cat result/bin/ngs
#! /nix/store/p0k9r5h8qs7220xdbdihhfgzwjcly70x-bash-5.3p3/bin/bash -e
export NGS_PATH='/nix/store/lhlnv03mjklkc8dwc2gvmxs6axwshbzg-ngs-0.2.17-dev/lib/ngs'
exec -a "$0" "/nix/store/lhlnv03mjklkc8dwc2gvmxs6axwshbzg-ngs-0.2.17-dev/bin/.ngs-wrapped" "$@"
sweater in pentavus~/Github/ngs on master via C v21.1.2-clang via △ v4.1.2 via 𝗩 via impure (nix-shell-env) on (us-east-1)
λ head -n1 result/bin/.ngs-wrapped
����
��� �H__PAGEZERO��__TEXT@@__text__TEXTD0(uD0�__stubs__TEXTl��l�
__const__TEXT �T �__cstring__TEXTt���t�__unwind_info__TEXT$9�$9��__DATA_CONST@@@@�__got__DATA_CONST@�@���__DATA����__data__DATA��S�__common__DATA��
| ''; | ||
|
|
||
| cmakeFlags = [ | ||
| "-DBUILD_MAN=ON" |
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.
Pure cargo cult
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.
(as in, I didn't check that it actually builds manual)
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.
mmm. We don't expect people to man ngs?
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.
No, what I mean is that I just copied this flag without checking it. I don't even know if it's real. I do expect people to man ngs, I plan to even do it myself. Just flagging that I didn't check that it actually works, I just did this:
$ ls result/share/man/man1/
na.1.gz ngs.1.gz ngsint.1.gz ngslang.1.gz ngsstyle.1.gz ngstut.1.gz ngswhy.1.gz
but I don't know if the man pages are generated because of this flag and I don't know if they are populated correctly. :)
|
|
||
| # Set NGS_PATH so the installed binary can find the standard library | ||
| postInstall = '' | ||
| wrapProgram $out/bin/ngs \ |
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.
wrapProgram is coming from makeWrapper package and this is what makes the wrapper that I showed you before.
sweater in pentavus~/Github/ngs on master via C v21.1.2-clang via △ v4.1.2 via 𝗩 via impure (nix-shell-env) on (us-east-1)
λ cat result/bin/ngs
#! /nix/store/p0k9r5h8qs7220xdbdihhfgzwjcly70x-bash-5.3p3/bin/bash -e
export NGS_PATH='/nix/store/lhlnv03mjklkc8dwc2gvmxs6axwshbzg-ngs-0.2.17-dev/lib/ngs'
exec -a "$0" "/nix/store/lhlnv03mjklkc8dwc2gvmxs6axwshbzg-ngs-0.2.17-dev/bin/.ngs-wrapped" "$@"
| homepage = "https://ngs-lang.org/"; | ||
| changelog = "https://github.com/ngs-lang/ngs/blob/v${finalAttrs.version}/CHANGELOG.md"; | ||
| license = licenses.gpl3Only; | ||
| maintainers = with maintainers; [ ]; |
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.
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.
What do I do when something breaks and I don't have enough understanding how to fix this?
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 afraid I will have to be maintainer then, the issue is that I'm currently using YSH and I wanted to evaluate NGS as a better alternative. There is also a potential that someone in a huge nix community wants to take up maintenance of this expression.
We will see!
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 see you like the idea largely. I will put myself as a maintainer then and will remove myself if I feel like I can't do maintenance of it anymore or when I find someone else trustworthy to maintain this set of exprs.
| mkdir -p build/leg-prefix/src | ||
| # Copy peg source to where CMake ExternalProject expects it | ||
| cp ${finalAttrs.pegSrc} build/leg-prefix/src/peg-0.1.18.tar.gz |
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.
Here we are tricking CMake into thinking that it already downloaded the peg code by placing it where it expects.
The reason we have to maintain our patches ourselves is that nix is hermetic and side-effect-free at the build time, meaning it can't just download stuff from the internet willy nilly.
thank you so much, I have written plain English comments to most of things that I know can trip up people who have never seen nix. https://github.com/ngs-lang/ngs/pull/693/changes The only thing my texts assume is that you understand the fundamental principle of Nix:
If you have any follow-up questions, don't hesitate to ask! |
ilyash-b
left a comment
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! I do like the idea!
- We have builds in
.github/workflows/build.ymlfor other platforms. Can you please add Nix build there? Or is there any alternative way to know when we break it for Nix? - See couple of inline comments
| ''; | ||
|
|
||
| cmakeFlags = [ | ||
| "-DBUILD_MAN=ON" |
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.
mmm. We don't expect people to man ngs?
| homepage = "https://ngs-lang.org/"; | ||
| changelog = "https://github.com/ngs-lang/ngs/blob/v${finalAttrs.version}/CHANGELOG.md"; | ||
| license = licenses.gpl3Only; | ||
| maintainers = with maintainers; [ ]; |
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.
What do I do when something breaks and I don't have enough understanding how to fix this?
Understood! I will try to do it this week. I assume I can test it locally with |

Why
NGS deserves better packaging. Right now if someone wants to try NGS on NixOS or with nix, they're out of luck — no
nix-shell -p ngs, no declarative system config, nothing. This is a shame because NGS is exactly the kind of tool that nix users would appreciate: a shell that actually learns from PLT proceedings beyond 1985 ❤️What
Standard cmake-based package with a few quirks handled:
peg version pinning: NGS patches the leg parser output in
build-scripts/patch-leg-output.sedto add location tracking. These patches assume peg 0.1.18's output format.Nixpkgs ships 0.1.20 which generates slightly different code (extra
yySet()calls that don't get patched), causing build failures.Rather than patching NGS's sed scripts and diverging from upstream, we pre-fetch peg 0.1.18 and let CMake's ExternalProject use it — same as the upstream build does when
legisn't found.Darwin support: Build scripts use GNU sed extensions. Added
gnusedto nativeBuildInputs on Darwin and prepend to PATH.NGS_PATH: Binary wrapped to find stdlib at
$out/lib/ngs.Man pages: Enabled via
-DBUILD_MAN=ON, requires pandoc.Testing
Built and tested on darwin-aarch64 (only!):
That's it. If you are happy with it, I will submit the package to
pkgs/by-name/ng/ngs/package.nixso that Nix users can easily benefit from NGS.