This guide explains why the project has a Nix build, how the files fit together, and what can break. It assumes no prior knowledge of Nix. Build and usage commands remain in the README.
Nix is a package manager and build tool. A Nix recipe lists the source code, build tools, and libraries needed to produce a package. Nix performs the build in an isolated directory instead of using whichever versions happen to be installed on the developer's computer.
This project needs more than Flutter. The application starts COLMAP, OpenMVS, Brush, mvs-texturing, PoissonRecon, and a Python mesh-decimation script while it runs. The Nix packages build or download those programs and place them where the application expects to find them. A developer and GitHub Actions can then check the same outputs with the same command.
The term flake refers to the Nix entry point, flake.nix, together with its
dependency lock file, flake.lock. The flake declares nixpkgs and every direct
source that is not supplied by nixpkgs. Its input URLs name exact source
revisions, and the lock file records their resolved revisions and content
hashes. An unchanged checkout therefore keeps using the same dependency
versions.
flake.nixdefines the public commands for building, developing, formatting, and checking. Keeping these entry points together gives local and CI builds the same recipe.flake.lockrecords the resolved revision and content hash of nixpkgs and each direct source input. Update it only when dependency updates are intended, then run the checks below.nix/package.nixbuilds the Flutter application and assembles either the CPU or CUDA helper programs. Its launcher sets the GPU mode that corresponds to those programs. The extra directory layout exists because the application was written to find them inside an AppImage-style directory.nix/openmvs-cuda.nixadds CUDA to nixpkgs' OpenMVS package. Nixpkgs already supplies a CUDA variant of COLMAP, but its OpenMVS package has no equivalent switch.nix/mvs-texturing.nixandnix/poisson-recon.nixbuild the two programs missing from nixpkgs.flake.nixpasses their locked source inputs into these recipes before Nix enters the network-isolated compilation step..github/workflows/nix.ymlruns the package check on GitHub..envrcoptionally asks direnv to enter the same development shell asnix develop. The generated.direnvdirectory is ignored because it contains machine-local cache files and Nix store links.
The flake exposes two complete packages:
.#cudauses CUDA-enabled builds of COLMAP and OpenMVS. Its launcher setsSIMPLE_PHOTOGRAMMETRY_GPU_TYPE=cuda, so the application initially enables COLMAP's GPU options..#cpuuses CPU-only builds of COLMAP and OpenMVS. Its launcher setsSIMPLE_PHOTOGRAMMETRY_GPU_TYPE=cpu, so the application keeps those options disabled and warns before starting Gaussian splatting.
The default package is .#cuda. Named outputs are preferable in instructions
because they make the hardware choice visible.
The CPU package does not link COLMAP or OpenMVS against the NVIDIA driver and
can start on a computer without an NVIDIA GPU. A CUDA-built COLMAP can perform
feature extraction on the CPU, but CUDA-built OpenMVS has a runtime dependency
on libcuda.so.1. It cannot even start when the NVIDIA driver library is
absent. Changing only the application's GPU setting is therefore not enough to
make the CUDA package a CPU distribution.
The CPU and CUDA labels describe the photogrammetry dependencies and their GUI defaults. Both packages include Brush for Gaussian splatting. Brush uses the graphics adapter separately, so a working CPU photogrammetry pipeline does not promise CPU-only Gaussian splatting.
CUDA lets COLMAP and OpenMVS move suitable numerical work from the CPU to an NVIDIA GPU.
NVIDIA assigns each GPU generation a compute capability. The list in
flake.nix tells the CUDA compiler which generations to include in the
programs. Capabilities 7.5, 8.6, and 8.9 match the existing Linux build; 12.0
supports current Blackwell GPUs. Adding a capability supports another GPU
generation but makes compilation take longer and produces larger binaries.
Nixpkgs marks the CUDA toolkit as unfree because NVIDIA distributes it under
the CUDA license rather than an open-source license. flake.nix opts into
those packages so nix build does not depend on a contributor's personal Nix
settings. The package contains the CUDA runtime but not the NVIDIA driver. The
driver belongs to the host operating system and must support the packaged CUDA
12.9 runtime.
GitHub's runners do not have NVIDIA GPUs. nix flake check builds both package
variants, but CI cannot prove that the CUDA package completes a reconstruction.
Before merging a CUDA packaging change, run the CUDA check and a reconstruction
on an NVIDIA machine as described below.
Pinning prevents dependency versions from changing by accident, but it does not make the build maintenance-free. Failures fall into three groups:
- Application code can start needing a file, program, or build step that the Nix recipe does not provide.
- A deliberate update to
flake.lockor a pinned source revision can expose an upstream change in package names, dependencies, or build instructions. - Services outside the lock file can change. GitHub can update its
ubuntu-latestrunner image. A GitHub Action's major-version tag can move to a new release, and the Nix installer can install a newer Nix version. Nix can also lose access to a cached package or source file needed by the build.
Pull-request and master push checks catch the first two groups when repository
code changes. The scheduled check exercises the third group during weeks with
no commits.
The schedule in .github/workflows/nix.yml runs
at 05:17 UTC every Monday. The non-zero minute avoids GitHub's busiest
scheduling time at the start of an hour.
The scheduled run checks the locked package on a new GitHub runner. It can
reveal a changed runner image, Action update, Nix installer change, or a build
input that Nix can no longer obtain. Nix normally uses ready-built packages from
its caches, so the job does not download and test every original source file. It
also does not update flake.lock or try a newer nixpkgs revision.
GitHub reads the schedule only from the repository's default branch. In a public repository, GitHub also disables scheduled workflows after 60 days without repository activity. If the weekly run disappears, re-enable the workflow on the Actions page and use its Run workflow button to check it immediately.
Failures appear on pull requests and in the repository's Actions page. For pull requests, pushes, and manual runs, GitHub notifies the person who started the run if that person has enabled Actions notifications. Scheduled runs have no person starting them, so GitHub assigns their notifications to the person who last changed the cron line. This makes that person the owner of the weekly check.
Each maintainer who starts workflow runs should open GitHub's notification settings, find System > Actions, choose email or web notifications, and select Only notify for failed workflows. The workflow does not send messages through a separate email or chat service.
Run the package checks from the repository root. This builds both variants:
nix flake check --print-build-logsOn a machine with an NVIDIA GPU, first confirm that the driver's management tool can see it:
nvidia-smiThis is only a first check. nvidia-smi can still work when the part of the
driver used by CUDA programs needs recovery.
Build the CUDA variant, then check that both programs were compiled with CUDA support:
nix build .#cuda
./result/usr/bin/colmap -h
./result/usr/bin/OpenMVS/DensifyPointCloud --helpCOLMAP includes with CUDA in its version line. OpenMVS lists its
--cuda-device option. These messages describe features compiled into the
programs; they do not prove that either program can use the installed GPU.
The following small check makes COLMAP initialize CUDA. It does not need any photographs because GPU initialization happens before COLMAP scans the image directory:
cuda_test_dir="$(mktemp -d)"
mkdir "$cuda_test_dir/images"
./result/usr/bin/colmap feature_extractor \
--database_path "$cuda_test_dir/database.db" \
--image_path "$cuda_test_dir/images" \
--FeatureExtraction.use_gpu 1 \
--FeatureExtraction.gpu_index 0The check must get past CUDA device creation without a CheckCudaDevice error.
The temporary directory can be deleted afterwards. If the command reports an
unknown CUDA error even though nvidia-smi works, inspect the operating
system's NVIDIA driver logs. A driver fault can require a reboot before any
CUDA program, including a correctly packaged one, can use the GPU.
OpenMVS starts its CUDA work while densifying a real scene, so its live test needs reconstruction input. Run a small reconstruction through the application and confirm that it completes the Densifying Point Cloud step. This also tests the hand-off between COLMAP, OpenMVS, and the application, which isolated command checks cannot cover.
Build the CPU variant on a machine without the NVIDIA driver, then start it and run a small reconstruction:
nix build .#cpu
./result/bin/simple_photogrammetry_guiThis exercises the CPU dependency closure as well as the application's CPU default. Hiding a GPU from the CUDA package is not an equivalent test because the NVIDIA driver library is still present on that machine.
To exercise the GitHub Actions workflow itself, start Docker and run act from
the Nix development shell. act creates a local container that imitates
GitHub's Linux runner. Its first run downloads a large container image and an
empty container must download the Nix build dependencies.
nix develop --command act pull_request --job package \
--container-architecture linux/amd64 \
-P ubuntu-latest=catthehacker/ubuntu:act-latest--container-architecture linux/amd64 matches the x86-64 Linux system defined
in flake.nix, including when act runs on an ARM computer. -P tells act
which Docker image to use for the workflow's ubuntu-latest label; the selected
image contains the tools needed to install Nix. The container is still an
approximation of GitHub's runner, so the hosted workflow remains the final
check.
When changing a non-obvious setting, update its nearby comment. The comment should state why the setting exists and name the failure it prevents. A comment that only restates the Nix or YAML syntax does not give the next maintainer the context needed to judge a change.