Building a "Hello World!" C program fails with "cc1: fatal error: hello.c: No such file or directory"

I am trying to learn nix and I think I am stuck on a very trivial place. I am not sure what I am doing wrong but after 2 days on this I thought I’d finally ask (please forgive me if this is a very simple)

the following is my directory tree (the contents of the files will be at the end):

.
├── default.nix
├── derivation.nix
└── hello.c

0 directories, 3 files

I am trying to build the hello.c program in NixOS with nix-build. But I am getting this strange error:

cc1: fatal error: hello.c: No such file or directory

But the file is right there. :confused:

To test it out but in a different way, I used nix-shell to shell into the environment derivation.nix and build the hello.c program through the terminal:

$ gcc hello.c -o hello

This builds the program normally and the newly built hello executable runs with no problem.

What is causing the error in nix-build?

(P.S. Also would love it if anyone can share any such “hello World” suggestions for learning!)


Contents of the files:

# default.nix

let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-22.11";
  pkgs = import nixpkgs {config = {}; overlays = []; };

in
{
  hello = pkgs.callPackage ./derivation.nix {};
}
# derivation.nix

{ 
  stdenv,
}:

stdenv.mkDerivation rec {
  name = "program-${version}";
  version = "1.0";

  src = ./.;

  dontUnpack = true;

  nativeBuildInputs = [ ];
  buildInputs = [ ];

   buildPhase = ''
     gcc hello.c -o hello
   '';

   installPhase = ''
     mkdir -p $out/bin
     cp hello $out/bin
   '';
}

// hello.c

#include <stdio.h>

int main () {
  printf( "Hello World!\n");
  return 0;
}

set gcc within your buildInputs:

{ 
  stdenv,
}:

stdenv.mkDerivation rec {
  name = "program-${version}";
  version = "1.0";

  src = ./.;

  dontUnpack = true;

  nativeBuildInputs = [ ];
  buildInputs = [ gcc ];

   buildPhase = ''
     gcc hello.c -o hello
   '';

   installPhase = ''
     mkdir -p $out/bin
     cp hello $out/bin
   '';
}

I get the following error:

error: undefined variable 'gcc'

Oh yeah, need to use pkgs.gcc and pass pkgs as a parameter to the derivation.nix

now I am getting this error:

error: undefined variable 'pkgs'

by the way, looking here, it says gcc is included by default? And anything required for compiling should go into nativeBuildInputs?

(quoting from link)
" the dependencies nativeBuildInputs needed to compile the program (gcc is always included by default… so you don’t need to specify anything here)"

dontUnpack will skip the entire process of making a source directory, removing that fixes the issue.

I’d suggest following the nix.dev tutorial, by the way. It also has a tutorial on local files which points out a bunch of issues with src = ./.;

And while we’re at it, this is one of the worst things you can do when writing nix code:

Not only is that NixOS version two years outdated, so full of vulnerabilities, using fetchTarball without a hash will make nix pull a new version of your input every two hours.

Instead of that, please use the nix channels of your system:

# default.nix
let
  pkgs = import <nixpkgs> {
    config = { };
    overlays = [ ];
  };
in {
  hello = pkgs.callPackage ./derivation.nix {};
}

Or pick your poison between niv, flakes, etc. if you want to import nixpkgs declaratively.

4 Likes

The problem is the dontUnpack = true; line. Remove that, and it should work just fine. You are correct in that stdenv.mkDerivation will include a C compiler; that’s why stdenvNoCC exists.

Edit: Ahh, sniped! :slight_smile:

2 Likes

Thank you!

Removing the dontUnpack = true; fixed the issue. (I had placed it there because, before, it was spitting out “error: unpacking” error and I thought since hello.c isn’t a tar file, I thought (wrongly) unpacking wasn’t necessary? But I guess the issue was something else and I guess “unpacking” does happen for .c files?)

Thank you! I actually did follow them for my first derivation but didn’t know about the local files. Will check it out!

Thank you for the heads up! I actually got that code from nix.dev tutorial (sub-heading: Building with nix-build), can someone update that nix.dev page so other learners don’t make this mistake? (or should I fork it and suggest this edit?)

Thank you again!! I will be using that from now on!
By the way, for declarative nixpkgs, which poison did you pick? :smile:
I was thinking of flakes since I am familiar with it for configuring my system.

It’s really referring to “unpacking” the src. More specifically, specifying dontUnpack = true disables the unpackPhase, which is a bit more complex than just unpacking by default.

I could be wrong here and I couldn’t find a reference on short order, but basically I think in this case what winds up effectively happening is that during the unpack phase, the source files are copied out of their store paths (the source files get copied into the Nix store as a result of the ./. path being interpolated to a string, the resulting string after interpolation is a path into the Nix store rather than the path that you pass in) and into the builder’s working directory (inside of the sandbox.) You might use dontUnpack if there was no need to do this, and you could then just reference the $src directly instead. But, you’ll find that your $PWD in the sandbox is mostly empty; you can see this by doing e.g. buildPhase = '' find . ''; or something like that.

It’s a bit complex to be sure.

Personally, I recommend flakes, especially if you have some familiarity. There are two main pitfalls in this use case:

  • There’s a bit of boilerplate needed; since nixpkgs is instantiated purely, the system argument must be passed to nixpkgs somehow. This results in many flakes using a utility library like flake-utils to instantiate your packages multiple times, one for each possible system value, for different architectures. Unfortunately, the exact schema of the outputs is very overloaded so there’s an unnecessarily large number of ways to specify an output that would correspond to the default package, or a package that would get selected by nix build .#hello, and this means you’ll find a bunch of examples that are a bit different, and they’re slightly obfuscated by the helper functions.

  • The new nix commands will use a fetcher to “grab” the source code and put it in the nix store before instantiating anything. It also will use a git fetcher if it detects that your flake is in a git repo. The upshot of this is that anything that isn’t in your git repo, because it is .gitignore’d or not git added, will seem to just not be found. If you run into this, you may want to do something like git add -AN to add files to the index as empty files; the git fetcher will pick up dirty files so as long as they have entries in the Git index. You can also bypass this by explicitly using a path fetcher, e.g. nix build path://$PWD, though it will result in the entire folder including all ignored files being copied into the Nix store on every build.

If you are able to get past this, I find flakes are great for local development, and you can combine it with direnv’s use flake directive to automatically get a proper dev shell upon cding into a source directory, too. (My personal workflow is to use normal tools during development via direnv’s ability to enter a dev shell environment, but use nix build nix run etc. for “proper” builds, CI, etc.)

3 Likes

Thank you for the wonderful and in-depth explanation!

The second half is quite heavy with knowledge and it will take a while for my head to soak all that in but I really really appreciate it! The student inside me is quite happy right now!

That’s neat!

I think I will follow that as well! Thank you again for your tips and explanations! :heart:

1 Like

Huh, I’ll have to go check the reasoning behind that. At the very least, that should not be statically recommending NixOS 22.11 IMO.

I’m personally also partial to flakes, if that helps, simply because that’s the nix-builtin to be, unlike niv which is third party.

They are relatively more complex than just using channels for now though, and also experimental, which is why most official docs don’t cover them. As a new user I’d stick with channels until I’m comfortable.

1 Like

Do let us know what you get to know! Would love to know why they did that :smiley:

Good advice, I guess after playing around a little, I will touch flakes after that. I think I am someone who usually needs help from official docs :smile: So will keep that in mind but would love to dive deeper into nix!