at v206 11 kB view raw
1{ stdenv, runCommand, nodejs, neededNatives}: 2 3{ 4 name, version ? "", src, 5 6 # by default name of nodejs interpreter e.g. "nodejs-${name}" 7 namePrefix ? nodejs.interpreterName + "-", 8 9 # Node package name 10 pkgName ? 11 if version != "" then stdenv.lib.removeSuffix "-${version}" name else 12 (builtins.parseDrvName name).name, 13 14 # List or attribute set of dependencies 15 deps ? {}, 16 17 # List or attribute set of peer depencies 18 peerDependencies ? {}, 19 20 # List or attribute set of optional dependencies 21 optionalDependencies ? {}, 22 23 # List of optional dependencies to skip 24 skipOptionalDependencies ? [], 25 26 # Whether package is binary or library 27 bin ? false, 28 29 # Additional flags passed to npm install 30 flags ? "", 31 32 # Command to be run before shell hook 33 preShellHook ? "", 34 35 # Command to be run after shell hook 36 postShellHook ? "", 37 38 # Same as https://docs.npmjs.com/files/package.json#os 39 os ? [], 40 41 # Same as https://docs.npmjs.com/files/package.json#cpu 42 cpu ? [], 43 44 # Attribute set of already resolved deps (internal), 45 # for avoiding infinite recursion 46 resolvedDeps ? {}, 47 48 ... 49} @ args: 50 51with stdenv.lib; 52 53let 54 self = let 55 sources = runCommand "node-sources" {} '' 56 tar --no-same-owner --no-same-permissions -xf ${nodejs.src} 57 mv $(find . -type d -mindepth 1 -maxdepth 1) $out 58 ''; 59 60 platforms = if os == [] then nodejs.meta.platforms else 61 fold (entry: platforms: 62 let 63 filterPlatforms = 64 stdenv.lib.platforms.${removePrefix "!" entry} or []; 65 in 66 # Ignore unknown platforms 67 if filterPlatforms == [] then (if platforms == [] then nodejs.meta.platforms else platforms) 68 else 69 if hasPrefix "!" entry then 70 subtractLists (intersectLists filterPlatforms nodejs.meta.platforms) platforms 71 else 72 platforms ++ (intersectLists filterPlatforms nodejs.meta.platforms) 73 ) [] os; 74 75 mapDependencies = deps: f: rec { 76 # Convert deps to attribute set 77 attrDeps = if isAttrs deps then deps else 78 (listToAttrs (map (dep: nameValuePair dep.name dep) deps)); 79 80 # All required node modules, without already resolved dependencies 81 # Also override with already resolved dependencies 82 requiredDeps = mapAttrs (name: dep: 83 dep.override { 84 resolvedDeps = resolvedDeps // { "${name}" = self; }; 85 } 86 ) (filterAttrs f (removeAttrs attrDeps (attrNames resolvedDeps))); 87 88 # Recursive dependencies that we want to avoid with shim creation 89 recursiveDeps = filterAttrs f (removeAttrs attrDeps (attrNames requiredDeps)); 90 }; 91 92 _dependencies = mapDependencies deps (name: dep: 93 dep.pkgName != pkgName); 94 _optionalDependencies = mapDependencies optionalDependencies (name: dep: 95 (builtins.tryEval dep).success && 96 !(elem dep.pkgName skipOptionalDependencies) 97 ); 98 _peerDependencies = mapDependencies peerDependencies (name: dep: 99 dep.pkgName != pkgName); 100 101 requiredDependencies = 102 _dependencies.requiredDeps // 103 _optionalDependencies.requiredDeps // 104 _peerDependencies.requiredDeps; 105 106 recursiveDependencies = 107 _dependencies.recursiveDeps // 108 _optionalDependencies.recursiveDeps // 109 _peerDependencies.recursiveDeps; 110 111 patchShebangs = dir: '' 112 node=`type -p node` 113 coffee=`type -p coffee || true` 114 find -L ${dir} -type f -print0 | xargs -0 grep -Il . | \ 115 xargs sed --follow-symlinks -i \ 116 -e 's@#!/usr/bin/env node@#!'"$node"'@' \ 117 -e 's@#!/usr/bin/env coffee@#!'"$coffee"'@' \ 118 -e 's@#!/.*/node@#!'"$node"'@' \ 119 -e 's@#!/.*/coffee@#!'"$coffee"'@' || true 120 ''; 121 122 in stdenv.mkDerivation ({ 123 inherit src; 124 125 configurePhase = '' 126 runHook preConfigure 127 128 ${patchShebangs "./"} 129 130 # Some version specifiers (latest, unstable, URLs, file paths) force NPM 131 # to make remote connections or consult paths outside the Nix store. 132 # The following JavaScript replaces these by * to prevent that: 133 # Also some packages require a specific npm version because npm may 134 # resovle dependencies differently, but npm is not used by Nix for dependency 135 # reslution, so these requirements are dropped. 136 137 ( 138 cat <<EOF 139 var fs = require('fs'); 140 var url = require('url'); 141 142 /* 143 * Replaces an impure version specification by * 144 */ 145 function replaceImpureVersionSpec(versionSpec) { 146 var parsedUrl = url.parse(versionSpec); 147 148 if(versionSpec == "latest" || versionSpec == "unstable" || 149 versionSpec.substr(0, 2) == ".." || dependency.substr(0, 2) == "./" || dependency.substr(0, 2) == "~/" || dependency.substr(0, 1) == '/' || /^[^/]+\/[^/]+$/.test(versionSpec)) 150 return '*'; 151 else if(parsedUrl.protocol == "git:" || parsedUrl.protocol == "git+ssh:" || parsedUrl.protocol == "git+http:" || parsedUrl.protocol == "git+https:" || 152 parsedUrl.protocol == "http:" || parsedUrl.protocol == "https:") 153 return '*'; 154 else 155 return versionSpec; 156 } 157 158 var packageObj = JSON.parse(fs.readFileSync('./package.json')); 159 160 /* Replace dependencies */ 161 if(packageObj.dependencies !== undefined) { 162 for(var dependency in packageObj.dependencies) { 163 var versionSpec = packageObj.dependencies[dependency]; 164 packageObj.dependencies[dependency] = replaceImpureVersionSpec(versionSpec); 165 } 166 } 167 168 /* Replace development dependencies */ 169 if(packageObj.devDependencies !== undefined) { 170 for(var dependency in packageObj.devDependencies) { 171 var versionSpec = packageObj.devDependencies[dependency]; 172 packageObj.devDependencies[dependency] = replaceImpureVersionSpec(versionSpec); 173 } 174 } 175 176 /* Replace optional dependencies */ 177 if(packageObj.optionalDependencies !== undefined) { 178 for(var dependency in packageObj.optionalDependencies) { 179 var versionSpec = packageObj.optionalDependencies[dependency]; 180 packageObj.optionalDependencies[dependency] = replaceImpureVersionSpec(versionSpec); 181 } 182 } 183 184 /* Ignore npm version requirement */ 185 if(packageObj.engines) { 186 delete packageObj.engines.npm; 187 } 188 189 /* Write the fixed JSON file */ 190 fs.writeFileSync("package.json", JSON.stringify(packageObj)); 191 EOF 192 ) | node 193 194 # We do not handle shrinkwraps yet 195 rm npm-shrinkwrap.json 2>/dev/null || true 196 197 mkdir ../build-dir 198 ( 199 cd ../build-dir 200 mkdir node_modules 201 202 # Symlink or copy dependencies for node modules 203 # copy is needed if dependency has recursive dependencies, 204 # because node can't follow symlinks while resolving recursive deps. 205 ${concatMapStrings (dep: 206 if dep.recursiveDeps == [] then '' 207 ln -sv ${dep}/lib/node_modules/${dep.pkgName} node_modules/ 208 '' else '' 209 cp -R ${dep}/lib/node_modules/${dep.pkgName} node_modules/ 210 '' 211 ) (attrValues requiredDependencies)} 212 213 # Create shims for recursive dependenceies 214 ${concatMapStrings (dep: '' 215 mkdir -p node_modules/${dep.pkgName} 216 cat > node_modules/${dep.pkgName}/package.json <<EOF 217 { 218 "name": "${dep.pkgName}", 219 "version": "${getVersion dep}" 220 } 221 EOF 222 '') (attrValues recursiveDependencies)} 223 ) 224 225 export HOME=$PWD/../build-dir 226 runHook postConfigure 227 ''; 228 229 buildPhase = '' 230 runHook preBuild 231 232 # If source was a file, repackage it, so npm pre/post publish hooks are not triggered, 233 if [[ -f $src ]]; then 234 GZIP=-1 tar -czf ../build-dir/package.tgz ./ 235 export src=$HOME/package.tgz 236 else 237 export src=$PWD 238 fi 239 240 # Install package 241 (cd $HOME && npm --registry http://www.example.com --nodedir=${sources} install $src --fetch-retries 0 ${flags}) 242 243 runHook postBuild 244 ''; 245 246 installPhase = '' 247 runHook preInstall 248 249 ( 250 cd $HOME 251 252 # Remove shims 253 ${concatMapStrings (dep: '' 254 rm node_modules/${dep.pkgName}/package.json 255 rmdir node_modules/${dep.pkgName} 256 '') (attrValues recursiveDependencies)} 257 258 mkdir -p $out/lib/node_modules 259 260 # Install manual 261 mv node_modules/${pkgName} $out/lib/node_modules 262 rm -fR $out/lib/node_modules/${pkgName}/node_modules 263 cp -r node_modules $out/lib/node_modules/${pkgName}/node_modules 264 265 if [ -e "$out/lib/node_modules/${pkgName}/man" ]; then 266 mkdir -p $out/share 267 for dir in "$out/lib/node_modules/${pkgName}/man/"*; do 268 mkdir -p $out/share/man/$(basename "$dir") 269 for page in "$dir"/*; do 270 ln -sv $page $out/share/man/$(basename "$dir") 271 done 272 done 273 fi 274 275 # Move peer dependencies to node_modules 276 ${concatMapStrings (dep: '' 277 mv node_modules/${dep.pkgName} $out/lib/node_modules 278 '') (attrValues _peerDependencies.requiredDeps)} 279 280 # Install binaries and patch shebangs 281 mv node_modules/.bin $out/lib/node_modules 2>/dev/null || true 282 if [ -d "$out/lib/node_modules/.bin" ]; then 283 ln -sv $out/lib/node_modules/.bin $out/bin 284 ${patchShebangs "$out/lib/node_modules/.bin/*"} 285 fi 286 ) 287 288 runHook postInstall 289 ''; 290 291 preFixup = '' 292 find $out -type f -print0 | xargs -0 sed -i 's|${src}|${src.name}|g' 293 ''; 294 295 shellHook = '' 296 ${preShellHook} 297 export PATH=${nodejs}/bin:$(pwd)/node_modules/.bin:$PATH 298 mkdir -p node_modules 299 ${concatMapStrings (dep: '' 300 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/ 301 '') (attrValues requiredDependencies)} 302 ${postShellHook} 303 ''; 304 305 # Stipping does not make a lot of sense in node packages 306 dontStrip = true; 307 308 meta = { 309 inherit platforms; 310 maintainers = [ stdenv.lib.maintainers.offline ]; 311 }; 312 313 passthru.pkgName = pkgName; 314 } // (filterAttrs (n: v: all (k: n != k) ["deps" "resolvedDeps" "optionalDependencies"]) args) // { 315 name = namePrefix + name; 316 317 # Run the node setup hook when this package is a build input 318 propagatedNativeBuildInputs = (args.propagatedNativeBuildInputs or []) ++ [ nodejs ]; 319 320 nativeBuildInputs = 321 (args.nativeBuildInputs or []) ++ neededNatives ++ 322 (attrValues requiredDependencies); 323 324 # Expose list of recursive dependencies upstream, up to the package that 325 # caused recursive dependency 326 recursiveDeps = 327 (flatten ( 328 map (dep: remove name dep.recursiveDeps) (attrValues requiredDependencies) 329 )) ++ 330 (attrNames recursiveDependencies); 331 }); 332 333in self