at 16.09-beta 11 kB view raw
1# This file originates from node2nix 2 3{stdenv, python, nodejs, utillinux, runCommand, writeTextFile}: 4 5let 6 # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise 7 tarWrapper = runCommand "tarWrapper" {} '' 8 mkdir -p $out/bin 9 10 cat > $out/bin/tar <<EOF 11 #! ${stdenv.shell} -e 12 $(type -p tar) "\$@" --warning=no-unknown-keyword 13 EOF 14 15 chmod +x $out/bin/tar 16 ''; 17 18 # Function that generates a TGZ file from a NPM project 19 buildNodeSourceDist = 20 { name, version, src, ... }: 21 22 stdenv.mkDerivation { 23 name = "node-tarball-${name}-${version}"; 24 inherit src; 25 buildInputs = [ nodejs ]; 26 buildPhase = '' 27 export HOME=$TMPDIR 28 tgzFile=$(npm pack) 29 ''; 30 installPhase = '' 31 mkdir -p $out/tarballs 32 mv $tgzFile $out/tarballs 33 mkdir -p $out/nix-support 34 echo "file source-dist $out/tarballs/$tgzFile" >> $out/nix-support/hydra-build-products 35 ''; 36 }; 37 38 includeDependencies = {dependencies}: 39 stdenv.lib.optionalString (dependencies != []) 40 (stdenv.lib.concatMapStrings (dependency: 41 '' 42 # Bundle the dependencies of the package 43 mkdir -p node_modules 44 cd node_modules 45 46 # Only include dependencies if they don't exist. They may also be bundled in the package. 47 if [ ! -e "${dependency.name}" ] 48 then 49 ${composePackage dependency} 50 fi 51 52 cd .. 53 '' 54 ) dependencies); 55 56 # Recursively composes the dependencies of a package 57 composePackage = { name, packageName, src, dependencies ? [], ... }@args: 58 let 59 fixImpureDependencies = writeTextFile { 60 name = "fixDependencies.js"; 61 text = '' 62 var fs = require('fs'); 63 var url = require('url'); 64 65 /* 66 * Replaces an impure version specification by * 67 */ 68 function replaceImpureVersionSpec(versionSpec) { 69 var parsedUrl = url.parse(versionSpec); 70 71 if(versionSpec == "latest" || versionSpec == "unstable" || 72 versionSpec.substr(0, 2) == ".." || dependency.substr(0, 2) == "./" || dependency.substr(0, 2) == "~/" || dependency.substr(0, 1) == '/') 73 return '*'; 74 else if(parsedUrl.protocol == "git:" || parsedUrl.protocol == "git+ssh:" || parsedUrl.protocol == "git+http:" || parsedUrl.protocol == "git+https:" || 75 parsedUrl.protocol == "http:" || parsedUrl.protocol == "https:") 76 return '*'; 77 else 78 return versionSpec; 79 } 80 81 var packageObj = JSON.parse(fs.readFileSync('./package.json')); 82 83 /* Replace dependencies */ 84 if(packageObj.dependencies !== undefined) { 85 for(var dependency in packageObj.dependencies) { 86 var versionSpec = packageObj.dependencies[dependency]; 87 packageObj.dependencies[dependency] = replaceImpureVersionSpec(versionSpec); 88 } 89 } 90 91 /* Replace development dependencies */ 92 if(packageObj.devDependencies !== undefined) { 93 for(var dependency in packageObj.devDependencies) { 94 var versionSpec = packageObj.devDependencies[dependency]; 95 packageObj.devDependencies[dependency] = replaceImpureVersionSpec(versionSpec); 96 } 97 } 98 99 /* Replace optional dependencies */ 100 if(packageObj.optionalDependencies !== undefined) { 101 for(var dependency in packageObj.optionalDependencies) { 102 var versionSpec = packageObj.optionalDependencies[dependency]; 103 packageObj.optionalDependencies[dependency] = replaceImpureVersionSpec(versionSpec); 104 } 105 } 106 107 /* Write the fixed JSON file */ 108 fs.writeFileSync("package.json", JSON.stringify(packageObj)); 109 ''; 110 }; 111 in 112 '' 113 DIR=$(pwd) 114 cd $TMPDIR 115 116 unpackFile ${src} 117 118 # Make the base dir in which the target dependency resides first 119 mkdir -p "$(dirname "$DIR/${packageName}")" 120 121 if [ -f "${src}" ] 122 then 123 # Figure out what directory has been unpacked 124 packageDir=$(find . -type d -maxdepth 1 | tail -1) 125 126 # Restore write permissions to make building work 127 find "$packageDir" -type d -print0 | xargs -0 chmod u+x 128 chmod -R u+w "$packageDir" 129 130 # Move the extracted tarball into the output folder 131 mv "$packageDir" "$DIR/${packageName}" 132 elif [ -d "${src}" ] 133 then 134 # Restore write permissions to make building work 135 chmod -R u+w $strippedName 136 137 # Move the extracted directory into the output folder 138 mv $strippedName "$DIR/${packageName}" 139 fi 140 141 # Unset the stripped name to not confuse the next unpack step 142 unset strippedName 143 144 # Some version specifiers (latest, unstable, URLs, file paths) force NPM to make remote connections or consult paths outside the Nix store. 145 # The following JavaScript replaces these by * to prevent that 146 cd "$DIR/${packageName}" 147 node ${fixImpureDependencies} 148 149 # Include the dependencies of the package 150 ${includeDependencies { inherit dependencies; }} 151 cd .. 152 ${stdenv.lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} 153 ''; 154 155 # Extract the Node.js source code which is used to compile packages with 156 # native bindings 157 nodeSources = runCommand "node-sources" {} '' 158 tar --no-same-owner --no-same-permissions -xf ${nodejs.src} 159 mv node-* $out 160 ''; 161 162 # Builds and composes an NPM package including all its dependencies 163 buildNodePackage = { name, packageName, version, dependencies ? [], production ? true, npmFlags ? "", dontNpmInstall ? false, preRebuild ? "", ... }@args: 164 165 stdenv.lib.makeOverridable stdenv.mkDerivation (builtins.removeAttrs args [ "dependencies" ] // { 166 name = "node-${name}-${version}"; 167 buildInputs = [ tarWrapper python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or []; 168 dontStrip = args.dontStrip or true; # Striping may fail a build for some package deployments 169 170 inherit dontNpmInstall preRebuild; 171 172 unpackPhase = args.unpackPhase or "true"; 173 174 buildPhase = args.buildPhase or "true"; 175 176 compositionScript = composePackage args; 177 passAsFile = [ "compositionScript" ]; 178 179 installPhase = args.installPhase or '' 180 # Create and enter a root node_modules/ folder 181 mkdir -p $out/lib/node_modules 182 cd $out/lib/node_modules 183 184 # Compose the package and all its dependencies 185 source $compositionScriptPath 186 187 # Patch the shebangs of the bundled modules to prevent them from 188 # calling executables outside the Nix store as much as possible 189 patchShebangs . 190 191 # Deploy the Node.js package by running npm install. Since the 192 # dependencies have been provided already by ourselves, it should not 193 # attempt to install them again, which is good, because we want to make 194 # it Nix's responsibility. If it needs to install any dependencies 195 # anyway (e.g. because the dependency parameters are 196 # incomplete/incorrect), it fails. 197 # 198 # The other responsibilities of NPM are kept -- version checks, build 199 # steps, postprocessing etc. 200 201 export HOME=$TMPDIR 202 cd "${packageName}" 203 runHook preRebuild 204 npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} rebuild 205 206 if [ "$dontNpmInstall" != "1" ] 207 then 208 npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} install 209 fi 210 211 # Create symlink to the deployed executable folder, if applicable 212 if [ -d "$out/lib/node_modules/.bin" ] 213 then 214 ln -s $out/lib/node_modules/.bin $out/bin 215 fi 216 217 # Create symlinks to the deployed manual page folders, if applicable 218 if [ -d "$out/lib/node_modules/${packageName}/man" ] 219 then 220 mkdir -p $out/share 221 for dir in "$out/lib/node_modules/${packageName}/man/"* 222 do 223 mkdir -p $out/share/man/$(basename "$dir") 224 for page in "$dir"/* 225 do 226 ln -s $page $out/share/man/$(basename "$dir") 227 done 228 done 229 fi 230 ''; 231 }); 232 233 # Builds a development shell 234 buildNodeShell = { name, packageName, version, src, dependencies ? [], production ? true, npmFlags ? "", dontNpmInstall ? false, ... }@args: 235 let 236 nodeDependencies = stdenv.mkDerivation { 237 name = "node-dependencies-${name}-${version}"; 238 239 buildInputs = [ tarWrapper python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or []; 240 241 includeScript = includeDependencies { inherit dependencies; }; 242 passAsFile = [ "includeScript" ]; 243 244 buildCommand = '' 245 mkdir -p $out/lib 246 cd $out/lib 247 source $includeScriptPath 248 249 # Create fake package.json to make the npm commands work properly 250 cat > package.json <<EOF 251 { 252 "name": "${packageName}", 253 "version": "${version}" 254 } 255 EOF 256 257 # Patch the shebangs of the bundled modules to prevent them from 258 # calling executables outside the Nix store as much as possible 259 patchShebangs . 260 261 export HOME=$TMPDIR 262 npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} rebuild 263 264 ${stdenv.lib.optionalString (!dontNpmInstall) '' 265 npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} install 266 ''} 267 268 ln -s $out/lib/node_modules/.bin $out/bin 269 ''; 270 }; 271 in 272 stdenv.lib.makeOverridable stdenv.mkDerivation { 273 name = "node-shell-${name}-${version}"; 274 275 buildInputs = [ python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or []; 276 buildCommand = '' 277 mkdir -p $out/bin 278 cat > $out/bin/shell <<EOF 279 #! ${stdenv.shell} -e 280 $shellHook 281 exec ${stdenv.shell} 282 EOF 283 chmod +x $out/bin/shell 284 ''; 285 286 # Provide the dependencies in a development shell through the NODE_PATH environment variable 287 inherit nodeDependencies; 288 shellHook = stdenv.lib.optionalString (dependencies != []) '' 289 export NODE_PATH=$nodeDependencies/lib/node_modules 290 ''; 291 }; 292in 293{ inherit buildNodeSourceDist buildNodePackage buildNodeShell; }