adnelson / nixfromnpm

Convert NPM packages into nix expressions
MIT License
49 stars 8 forks source link

nixfromnpm

Build status

Translate NPM packages to Nix expressions.

What it does

Given the name of one or more packages and an output directory, queries NPM repositories for those packages' definitions and those of their dependencies, and generates a set of nix expressions in the given output directory which not only can be used to build the requested packages, but also to build any of their dependencies as desired.

Advantages over npm2nix

npm2nix is another tool which can generate nix expressions from an npm package. However, it has several drawbacks. It generates the entire dependency tree for an npm package, without availing itself of results of previous invocations. This is inefficient, since duplicated packages are built multiple times. The resulting expression is a single monolithic file which is hard to grok, and hard to modify. Furthermore, any modifications performed would have to be done each time the package was regenerated. It also discourages committing of the resulting package into source control, since it's large and has to be continually regenerated whenever changes are made. This means that packages built with it are unlikely to be cached in a nix store or repo.

Installation

Clone the nixfromnpm repo.

$ git clone https://github.com/adnelson/nixfromnpm
$ cd nixfromnpm

Make sure you have nix installed, and nixpkgs is in your NIX_PATH environment variable. Then run:

$ nix-env --install --attr nixfromnpm --file ./release.nix

If you'd like to try out nixfromnpm without installing it, or just hack on it, you can use it in a nix-shell:

$ cd /path/to/nixfromnpm
$ nix-shell
[nix-shell:nixfromnpm]$ cabal run -- <arguments>

Customizing nixpkgs and GHC versions

By default, we pin the version of nixpkgs in order to maintain a reliable build. However, if you'd like to build off of nixpkgs in your NIX_PATH or some other custom location:

$ nix-env -f release.nix -iA nixfromnpm --arg nixpkgs '<nixpkgs>'

The GHC version can also be set explicitly, although of course, the package is not guaranteed to build with any arbitrary GHC version:

$ nix-env -f release.nix -iA nixfromnpm --argstr compiler ghc802

Note that the above options also apply to nix-build, nix-shell, etc.

Usage

Using the nix-node-packages repo

I recommend using the nix-node-packages repo for most applications. This repo contains several thousand node package definitions already, which means whatever you're trying to build might already be defined. It also contains some packages which are hand-written or hand-modified from what had been auto-generated by nixfromnpm (fixing bugs or performing additional build steps). To use the repo, clone it and then specify it as an "output" when calling nixfromnpm. Of course, if the package already exists you can just build it immediately with nix-build.

$ git clone https://github.com/adnelson/nix-node-packages
$ nixfromnpm -o nix-node-packages -p 'the-package-I-need@the-version-I-need'
$ nix-build nix-node-packages -A nodePackages.the-package-I-need_the-version-I-need

See that repo's README.md for information.

If you don't use the repo, the commands below will still work, but some of the packages which the repo provides fixes for might fail to build.

Generating an expression for a package

The most basic usage is providing an -o (--output) flag and one or more -p flags:

$ nixfromnpm -o /some/path -p package_name -p other_package_name

This will build the packages called package_name and other_package_name, and put all of the generated expressions in /some/path. That path will be created if it doesn't exist. If the output path does exist, a package will only be fetched if a nix expression for it doesn't already exist.

You can also specify a version bound on the packages you are fetching, using @:

$ nixfromnpm -p package_name@version_bound -o /some/path

Any NPM version bound is valid; so for example:

$ nixfromnpm -p foo@0.8.6 -o /some/path
$ nixfromnpm -p 'foo@>=0.8 <0.9' -o /some/path
$ nixfromnpm -p 'foo@~1.0.0' -o /some/path

Generating an expression from a package.json file

You can also generate an expression for a project on the local disk by passing in the path to a package.json file, or a directory containing one. The generated expression will be placed in a file called project.nix in the same directory as the package.json file, and a default.nix will be created in the directory as well which calls into project.nix. As with normal usage, the -o flag is used to specify a path to where generated expressions will be placed; however, only the downsteam dependencies will be put here, while the expression itself will be in the project.nix file.

$ nixfromnpm -f /path/to/package.json -o /path/to/dependency/set

You can give multiple -f arguments to build multiple expressions on disk, and it can be used alongside -p arguments as well.

Development Dependencies

NPM packages differentiate between dependencies needed at runtime and dependencies required only when developing on a package (e.g. packages for testing, transpilers for compile-to-javascript languages, etc). A package might have a great number of development dependencies, and there might be circular dependencies with development packages because the packages are not required at runtime. For this reason it's generally better to avoid generating expressions for development dependencies unless they're needed, because they add a lot of extra work and generate a lot of files.

nixfromnpm lets you generate expressions for development dependencies at a maximum depth. For example, depth 0 means don't make any development dependency expressions; depth 1 means create expressions for the package being built, but not any of their development dependencies, etc.

$ nixfromnpm -p package_name -o /some/path --dev-depth 1

Extra registries

For a package in a private registry located at https://my.registry:2345:

$ nixfromnpm -p private_package -o /some/path -r https://my.registry:2345

Github authorization

For npm packages which fetch from git, if an authorization token is required:

$ nixfromnpm -p package -o /some/path --github-token llnl23uinlaskjdno34nedhoaidjn5o48wugn

This can also be set by a GITHUB_TOKEN environment variable.

Private NPM namespaces

NPM offers private packaging, where you can specify a namespace or scope under which a package lives. For example, a package might be designated as @foo/bar, where the package is called bar and the namespace is called foo. nixfromnpm supports this, as long as you have set up your NPM repo to use authorization tokens (see documentation for details). To use this, set an environment variable NPM_AUTH_TOKENS, with the following format:

mynamespace=mytoken:myothernamespace=myothertoken:...

Where tokens are keyed on namespaces. Then when building the expression set, if a namespaced package is encountered, nixfromnpm will look up the namespace in this environment variable to determine what token to use for authentication. The same environment variable is read when the packages are built with nix-build. The path to the package in the generated expressions is slightly different:

$ export NPM_AUTH_TOKENS="foo=a1a1a1a1a1a1a1a1a1a"
$ nixfromnpm -o my_expressions -p '@foo/bar'
$ nix-build my_expressions -A namespaces.foo.bar

Caching of packages

By default, nixfromnpm will discover all existing packages in the specified output directory (provided via tha -o flag). However, if you would like to generate all of these from scratch, you can disable caching with --no-cache.

Troubleshooting a package that doesn't build

There are any number of reasons why a package might not build. Some of the most common ones are:

The good news is that if you patch or otherwise fix a broken package, it will not be overwritten by subsequent invocations of nixfromnpm (although, I highly recommend keeping your expressions in source control in case bad things happen!).

Contributions

This project has gone from what I thought would be a weekend project to a pretty significant undertaking. I think it has the potential to be pretty useful to JS developers who are interested in using nix, and it's already usable, with thousands of packages defined (although there's no guarantee, of course, that these all work). However, there are still a number of issues which need to be addressed. I very heartily welcome contributions, whether error reporting or pull requests.