Package asset not being included

Hello there, everyone.

I am pretty new to the Nix ecosystem, and I have run into an issue.

I am trying to create a Nix package of a program I wrote for myself to keep track of finances in Rust, and I have successfully got it to build all the way to the point where it produces a binary.

However, this program relies on the presence of a schema file that creates an SQLite database, and I cannot get Nix to export both the binary and the sql file.

In the project’s Makefile, I have it set so that it will copy the binary and the SQL file to predetermined locations, which is defined by default to be /usr/local, during the install process.

The schema file is expected to be at /usr/local/share//

From what I could see from searching, I expect to see a folder called share in the output folder, since the binary ends up in result/bin, and the documentations says that there are usually doc and man folders that correspond to locations in

/usr/local
.

The nix files are being currently run on a NixOS VM.

The contents of the nix file for the program are as follows:

{
lib,
libc,
fetchFromGitHub,
rustPlatform,
llvmPackages,
stdenv,
}:

rustPlatform.buildRustPackage (finalAttrs: {
pname = “rcheckbook”;
version = “0.6.4”;

src = fetchFromGitHub {
	owner = "bryceac";
	repo = "rcheckbook";
	rev = "v0.6.4";
	sha256 = "0wkad5l5lzm9sqhvmi95ibxhrv03v0750vwpjwmf92qsb29wjnay";
};

cargoHash = "sha256-bYSZpmG1/myX7EzB1qdjZjQX46J6XoLLTqPfWGKxG30=";

LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib";

nativeBuildInputs = [ rustPlatform.bindgenHook ];

meta = {
	description = "Simple CLI-based Checkbook ledger.";
	homepage = "https://github.com/bryceac/rcheckbook";
	license = lib.licenses.mit;
	maintainers = [];	
};

})

So far, I have tried looking up how to deal with the InstallPhase, but I cannot seem to figure out how to access the repo source.

Does anyone have any ideas of how to resolve this?

Move the file to $out/share/... and either patch your program to look there or make it use an envvar/cli flag to pass the location and wrap it using wrapProgram from makeWrapper.

Or, just embed the schema file into your executable.

1 Like

Nix packages don’t let you place files in global/FHS directories. Instead, all of your package’s files will end up in /nix/store/<hash>-rcheckbook-0.6.4/ with a bin directory for the programs and a share directory for any resources you wish to keep.

The source code should already be copied into the build directory, so you should be able to just copy the file over.

...
nativeBuildInputs = [ rustPlatform.bindgenHook ];

postInstall = ''
  cp src/schema.sql $out/share/rcheckbook/schema.sql
'';
...

This will ensure that the schema is copied into the final output, however your program will need to know where to look for it. In your rust program, you should grab the PREFIX variable an pass it into the compiler by supplying a build.rs file.

use std::env;

fn main() {
    // Default to /usr/local if PREFIX is not provided
    let prefix = env::var("PREFIX").unwrap_or_else(|_| "/usr/local".to_string());
    
    // Pass ONLY the PREFIX to the compiler
    println!("cargo:rustc-env=INSTALL_PREFIX={}", prefix);
    println!("cargo:rerun-if-env-changed=PREFIX");
}

Then in your main.rs the PREFIX is baked in and can be added to resource paths. (env! is evaluated at build time)

fn main() {
    const PREFIX: &str = env!("INSTALL_PREFIX");
    println!("My schema is at {}/share/rcheckbook/schema.sql", PREFIX);
}
4 Likes

Using build.rs and a proxy envvar is a good idea, I have to remember this pattern!

I think I will give this a try, since I’ve been thinking of switching over to using an environment variable.

As for the actual copying, however, I have tried a few variations on what you provided, but it either said the path did not exist, or it didn’t end up anywhere in the symlinked location.

Still, I appreciate the suggestion.

Working Derivation - Nix Pills may be helpful.

The trick: every attribute in the set passed to derivation will be converted to a string and passed to the builder as an environment variable. This is how the builder gains access to coreutils and gcc: when converted to strings, the derivations evaluate to their output paths, and appending /bin to these leads us to their binaries.

The same goes for the src variable. $src is the path to simple.c in the nix store. As an exercise, pretty print the .drv file. You’ll see simple_builder.sh and simple.c listed in the input derivations, along with bash, gcc and coreutils .drv files. The newly added environment variables described above will also appear.

Looking at your repo, probably something like:

mkdir $out/share
cp $src/register.sql $out/share/

Good catch, I had forgotten to mention that you need mkdir first.

You can also use the install command from coreutils to create the full path to the target directory with -D -t and then copy the provided file to it.

postInstall = ''
  install -v -D -t $out/share/rcheckbook $src/register.sql
'';

if you set -v on the copy or install commands, you’ll be able to see in the build log where the file ends up. and if you print the path your program is trying to load, you can see if they line up.

Good luck!

Actually, looking through nixpkgs, it seems like buildRustPackage doesn’t set PREFIX by default :frowning:
You can make it do that by setting the env attribute.

env = {
  PREFIX = builtins.placeholder "out";
};

Or, you could have build.rs consume the out variable, but that is less portable to other build environments.

Thanks for the suggestion.

Right now, I have my program looking for an environment variable called REGISTRY_SCHEMA_DIR and, after fighting with GNU make to it recognize it, that part works out, but would the env above automatically register the new environment variable, or would I do that under the postInstall phase?

I found one place where it said how set an environment variable for a package, but they didn’t make it clear.

The env attrset passed to the derivation sets environment variable for the build script of the derivation only. It does not automatically register the variable anywhere that can be seen at runtime/post-build.

If you absolutely must set an environment variable for the program to run, you must use wrapProgram/makeWrapper from the standard build environment in the postInstall step to replace the program with a shell script that sets the needed environment variable. This is how a lot of programs are fed extra PATH entries they need to spawn sub-processes.

If the value you are passing in is very unlikely to change after install, It is highly encouraged to bake it into the program using compile-time env! macros, which removes the need for wrapper scripts.

1 Like

Thanks for the tip.

The environment variable is not really necessary to run, but on operating systems that Rust considers UNIX, it is needed for operating systems that, like NixOS, do not utilize FHS or FHS-like structures.

Anyway, after testing things out and much searching, I got it working.

Thanks for the help, everyone. :slightly_smiling_face:

1 Like