c04fcbe90755ed5b99637d6af5e2f52ce3c24e38
[platform/upstream/nodejs.git] / deps / npm / lib / build.js
1 // npm build command
2
3 // everything about the installation after the creation of
4 // the .npm/{name}/{version}/package folder.
5 // linking the modules into the npm.root,
6 // resolving dependencies, etc.
7
8 // This runs AFTER install or link are completed.
9
10 var npm = require("./npm.js")
11   , log = require("npmlog")
12   , chain = require("slide").chain
13   , fs = require("graceful-fs")
14   , path = require("path")
15   , lifecycle = require("./utils/lifecycle.js")
16   , readJson = require("read-package-json")
17   , link = require("./utils/link.js")
18   , linkIfExists = link.ifExists
19   , cmdShim = require("cmd-shim")
20   , cmdShimIfExists = cmdShim.ifExists
21   , asyncMap = require("slide").asyncMap
22
23 module.exports = build
24 build.usage = "npm build <folder>\n(this is plumbing)"
25
26 build._didBuild = {}
27 build._noLC = {}
28 function build (args, global, didPre, didRB, cb) {
29   if (typeof cb !== "function") cb = didRB, didRB = false
30   if (typeof cb !== "function") cb = didPre, didPre = false
31   if (typeof cb !== "function") {
32     cb = global, global = npm.config.get("global")
33   }
34   // it'd be nice to asyncMap these, but actually, doing them
35   // in parallel generally munges up the output from node-waf
36   var builder = build_(global, didPre, didRB)
37   chain(args.map(function (arg) { return function (cb) {
38     builder(arg, cb)
39   }}), cb)
40 }
41
42 function build_ (global, didPre, didRB) { return function (folder, cb) {
43   folder = path.resolve(folder)
44   build._didBuild[folder] = true
45   log.info("build", folder)
46   readJson(path.resolve(folder, "package.json"), function (er, pkg) {
47     if (er) return cb(er)
48     chain
49       ( [ !didPre && [lifecycle, pkg, "preinstall", folder]
50         , [linkStuff, pkg, folder, global, didRB]
51         , pkg.name === "npm" && [writeBuiltinConf, folder]
52         , didPre !== build._noLC && [lifecycle, pkg, "install", folder]
53         , didPre !== build._noLC && [lifecycle, pkg, "postinstall", folder]
54         , didPre !== build._noLC
55           && npm.config.get("npat")
56           && [lifecycle, pkg, "test", folder] ]
57       , cb )
58   })
59 }}
60
61 function writeBuiltinConf (folder, cb) {
62   // the builtin config is "sticky". Any time npm installs itself,
63   // it puts its builtin config file there, as well.
64   if (!npm.config.usingBuiltin
65       || folder !== path.dirname(__dirname)) {
66     return cb()
67   }
68   npm.config.save("builtin", cb)
69 }
70
71 function linkStuff (pkg, folder, global, didRB, cb) {
72   // allow to opt out of linking binaries.
73   if (npm.config.get("bin-links") === false) return cb()
74
75   // if it's global, and folder is in {prefix}/node_modules,
76   // then bins are in {prefix}/bin
77   // otherwise, then bins are in folder/../.bin
78   var parent = path.dirname(folder)
79     , gnm = global && npm.globalDir
80     , top = parent === npm.dir
81     , gtop = parent === gnm
82
83   log.verbose("linkStuff", [global, gnm, gtop, parent])
84   log.info("linkStuff", pkg._id)
85
86   shouldWarn(pkg, folder, global, function() {
87     asyncMap( [linkBins, linkMans, !didRB && rebuildBundles]
88             , function (fn, cb) {
89       if (!fn) return cb()
90       log.verbose(fn.name, pkg._id)
91       fn(pkg, folder, parent, gtop, cb)
92     }, cb)
93   })
94 }
95
96 function shouldWarn(pkg, folder, global, cb) {
97   var parent = path.dirname(folder)
98     , top = parent === npm.dir
99     , cwd = process.cwd()
100
101   readJson(path.resolve(cwd, "package.json"), function(er, topPkg) {
102     if (er) return cb(er)
103
104     var linkedPkg = path.basename(cwd)
105       , currentPkg = path.basename(folder)
106
107     // current searched package is the linked package on first call
108     if (linkedPkg !== currentPkg) {
109
110       if (!topPkg.dependencies) return cb()
111
112       // don't generate a warning if it's listed in dependencies
113       if (Object.keys(topPkg.dependencies).indexOf(currentPkg) === -1) {
114
115         if (top && pkg.preferGlobal && !global) {
116           log.warn("prefer global", pkg._id + " should be installed with -g")
117         }
118       }
119     }
120
121     cb()
122   })
123 }
124
125 function rebuildBundles (pkg, folder, parent, gtop, cb) {
126   if (!npm.config.get("rebuild-bundle")) return cb()
127
128   var deps = Object.keys(pkg.dependencies || {})
129              .concat(Object.keys(pkg.devDependencies || {}))
130     , bundles = pkg.bundleDependencies || pkg.bundledDependencies || []
131
132   fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
133     // error means no bundles
134     if (er) return cb()
135
136     log.verbose("rebuildBundles", files)
137     // don't asyncMap these, because otherwise build script output
138     // gets interleaved and is impossible to read
139     chain(files.filter(function (file) {
140       // rebuild if:
141       // not a .folder, like .bin or .hooks
142       return !file.match(/^[\._-]/)
143           // not some old 0.x style bundle
144           && file.indexOf("@") === -1
145           // either not a dep, or explicitly bundled
146           && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
147     }).map(function (file) {
148       file = path.resolve(folder, "node_modules", file)
149       return function (cb) {
150         if (build._didBuild[file]) return cb()
151         log.verbose("rebuild bundle", file)
152         // if file is not a package dir, then don't do it.
153         fs.lstat(path.resolve(file, "package.json"), function (er, st) {
154           if (er) return cb()
155           build_(false)(file, cb)
156         })
157     }}), cb)
158   })
159 }
160
161 function linkBins (pkg, folder, parent, gtop, cb) {
162   if (!pkg.bin || !gtop && path.basename(parent) !== "node_modules") {
163     return cb()
164   }
165   var binRoot = gtop ? npm.globalBin
166                      : path.resolve(parent, ".bin")
167   log.verbose("link bins", [pkg.bin, binRoot, gtop])
168
169   asyncMap(Object.keys(pkg.bin), function (b, cb) {
170     linkBin( path.resolve(folder, pkg.bin[b])
171            , path.resolve(binRoot, b)
172            , gtop && folder
173            , function (er) {
174       if (er) return cb(er)
175       // bins should always be executable.
176       // XXX skip chmod on windows?
177       fs.chmod(path.resolve(folder, pkg.bin[b]), npm.modes.exec, function (er) {
178         if (er || !gtop) return cb(er)
179         var dest = path.resolve(binRoot, b)
180           , src = path.resolve(folder, pkg.bin[b])
181           , out = npm.config.get("parseable")
182                 ? dest + "::" + src + ":BINFILE"
183                 : dest + " -> " + src
184         console.log(out)
185         cb()
186       })
187     })
188   }, cb)
189 }
190
191 function linkBin (from, to, gently, cb) {
192   if (process.platform !== "win32") {
193     return linkIfExists(from, to, gently, cb)
194   } else {
195     return cmdShimIfExists(from, to, cb)
196   }
197 }
198
199 function linkMans (pkg, folder, parent, gtop, cb) {
200   if (!pkg.man || !gtop || process.platform === "win32") return cb()
201
202   var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
203
204   // make sure that the mans are unique.
205   // otherwise, if there are dupes, it'll fail with EEXIST
206   var set = pkg.man.reduce(function (acc, man) {
207     acc[path.basename(man)] = man
208     return acc
209   }, {})
210   pkg.man = pkg.man.filter(function (man) {
211     return set[path.basename(man)] === man
212   })
213
214   asyncMap(pkg.man, function (man, cb) {
215     if (typeof man !== "string") return cb()
216     var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
217       , stem = parseMan[1]
218       , sxn = parseMan[2]
219       , gz = parseMan[3] || ""
220       , bn = path.basename(stem)
221       , manDest = path.join(manRoot, "man" + sxn, bn)
222
223     linkIfExists(man, manDest, gtop && folder, cb)
224   }, cb)
225 }