UNPKG

12.5 kBtext/coffeescriptView Raw
1# # Ninja Configuration Script
2#
3# This file drives the [Ninja](http://martine.github.io/ninja/) build process.
4# Its purpose is to generate the Ninja input file, called `build.ninja` and
5# located at the project root, by analysing the project structure. See the
6# [Ninja manual](http://martine.github.io/ninja/manual.html) for more
7# information about this file syntax.
8#
9# See this project's README.md for more details.
10
11packageJson = require '../package.json'
12ld = require 'lodash'
13path = require 'path'
14glob = require 'glob'
15globule = require 'globule'
16log = require('yadsil')('benbria-configure-ninja')
17ninjaBuilder = require 'ninja-build-gen'
18factories = require './ninjaFactories'
19{findCommand, findLocalCommand, findScript} = require './ninjaCommands'
20{getCoffeelintPaths} = require './index'
21
22# Fix yadsil to behave like other logs
23log.warn = log.warning
24
25# Configuration
26config = {}
27config.ninjaFilePath = process.cwd()
28config.ninjaFile = "build.ninja"
29config.configureNinjaScript = __filename
30config.streamlineVersion = 10
31config.streamlineOpts = "" # --cb _cb
32config.stylusOpts = ""
33
34# Folder and file paths to use across the configuration.
35fp = {}
36fp.assets = 'assets'
37fp.build = 'build'
38fp.buildAssets = "#{fp.build}/#{fp.assets}"
39fp.coffeelint = "#{fp.build}/coffeelint"
40fp.fingerprintFile = "#{fp.buildAssets}/release/fingerprints.json"
41
42# Put on top of the generated Ninja manifest (`build.ninja`).
43#
44warnMessage = "# Auto-generated by `loop-configure-ninja`.\n"
45
46# Generate the Ninja rule, and edge, which builds the `build.ninja` file itself.
47# This edge is always executed first by Ninja, to ensure it has the lastest
48# build graph. `optionString` should contain the arguments to call
49# configure, supposed to stay the same as the original call.
50#
51makeSystemEdges = (ninja, optionString, options) ->
52 ninja.assign 'buildCoffee', findLocalCommand('coffee', config)
53
54 ninja.assign 'node', 'node'
55
56 factories.forActiveFactory config, log, (factory) ->
57 if factory.assignments
58 log.debug "Generating assignments for #{factory.name}"
59 factory.assignments ninja, config
60
61 ninja.assign 'uglifyjs', findLocalCommand("uglifyjs", config)
62 ninja.rule('configure')
63 .run("#{findCommand('loop-configure-ninja', config)}#{optionString}")
64 .description 'CONFIGURE'
65 ninja.edge(config.ninjaFile)
66 .using('configure')
67 .need([findCommand('loop-configure-ninja', config)])
68
69# Make a simple Ninja rule.
70#
71# * `name` - The name of the rule. This is a free-form text string.
72# * `command` - The command to run to compile files that use this rule.
73# * `display` - `makeSimpleRule` will automatically provide a description for your rule of the
74# format `#{all-caps name} $in`. If you provide a `display`, this will replace the "$in".
75# See [ninja docs on rule variables](http://martine.github.io/ninja/manual.html#ref_rule)
76# for a list of what can go here.
77#
78makeSimpleRule = (ninja, {name, command, display}) ->
79 desc = name.toUpperCase() + ' ' + if display? then display else '$in'
80 ninja.rule(name).run(command).description desc
81
82# Add the *rules* used by diverse edges to build the project files. A rule is
83# just a specification of 'how to compile A to B'. The following rules are
84# defined:
85#
86# * `coffeelint`: Call the linter for CoffeeScript files.
87# * `snockets`: Call the Snockets CoffeeScript compiler, generating a
88# dependency file at once, plus the i18n extractor.
89#
90# From ninjaFactories:
91# * `coffee`: Call the simple CoffeeScript compiler.
92# * `stylus`: Call the Stylus compiler, plus the dependency file generator.
93# * `coffeeStreamline` and `jsStreamline`: Call the streamline compiler.
94#
95makeCommonRules = (ninja, options) ->
96 factories.forActiveFactory config, log, (factory) ->
97 if factory.makeRules
98 log.debug "Making rules for #{factory.name}"
99 factory.makeRules ninja, config
100
101 makeSimpleRule ninja, {name: 'copy', command: 'cp $in $out'}
102 makeSimpleRule ninja, {
103 name: 'fingerprint'
104 command: """
105 $node #{findScript "fingerprint.js", config} $cliOptions -b $basePath -o $out $in
106 """
107 display: '$basePath'
108 }
109
110# Generate a mapping using the 'globule' module, creating an edge for each
111# file match. `mappingOptions` should be in the 'globule' format.
112# `callback(edge)` is called for each created edge.
113# Return an array of target files.
114#
115edgeMapping = (ninja, files, mappingOptions, callback) ->
116 ld.map globule.mapping(files, mappingOptions), (match) ->
117 callback ninja.edge(match.dest).from(match.src)
118 match.dest
119
120# Create a simple mapping options object. This is a shorthand to avoid
121# repeating the same option names over and over.
122#
123simpleMapOpt = (srcBase, destBase, ext) ->
124 {srcBase, destBase, ext, extDot: 'last'}
125
126# Make edges in `ninja` to lint the specified `coffeeFiles`.
127# The generated '.coffeelint' are plain fake. They serve no other purpose
128# than to say to Ninja "that fine, this file have been linted already, see
129# there's a .coffeelint for it.".
130#
131# It's not 100% likeable but there's no real other solution with Ninja. We
132# could be using Grunt or the coffeelint bin directly, but in this case it's
133# not even incremental: everything would be relinted at each run.
134#
135makeLintEdges = (ninja, coffeeFiles) ->
136 options = simpleMapOpt '.', '$builddir/coffeelint', '.coffeelint'
137 edgeMapping ninja, coffeeFiles, options, (edge) ->
138 edge.using('coffeelint')
139
140# Make all the edges necessary to compile assets, like Styluses, Coffees, etc.
141# Assets are all contained into the root `/assets` folder.
142#
143makeAssetEdges = (ninja) ->
144 # Note: the patterns with only lowercase `a-z` will ignore all caps files
145 # such as `README.md`
146 assetPaths = {}
147 configNames = ['debug', 'release']
148 for configName in configNames
149 log.debug "Making #{configName} asset edges"
150 assetPaths[configName] = []
151 factories.forActiveFactory config, log, (factory) ->
152 if factory.assetFiles and factory.makeAssetEdge
153 log.debug " #{factory.name}"
154 mappingOptions = simpleMapOpt(
155 fp.assets,
156 path.join(fp.buildAssets, configName),
157 factory.targetExt or '.js')
158
159 # Find the files we need to compile
160 sourceFileNames = globule.find(factory.assetFiles, mappingOptions)
161
162 # Generate edges for each file
163 for match in globule.mapping(sourceFileNames, mappingOptions)
164 edges = factory.makeAssetEdge ninja, match.src, match.dest, configName
165 assetPaths[configName] = assetPaths[configName].concat edges
166
167 ninja.edge('debug-assets').from(assetPaths.debug)
168
169 if assetPaths.release.length > 0
170 log.debug "Making fingerprint edge for #{assetPaths.release.length} release assets"
171 fingerprintFile = makeFingerprintEdge ninja, assetPaths.release
172 ninja.edge('release-assets').from(fingerprintFile)
173 else
174 # No assets means no fingerprint file is required.
175 ninja.edge('release-assets')
176
177# Make edges required for compiling everything in /src into /lib.
178#
179makeSourceEdges = (ninja) ->
180 destFiles = []
181
182 log.debug "Making src edges"
183 factories.forActiveFactory config, log, (factory) ->
184 if factory.files and factory.makeSrcEdge
185 log.debug " #{factory.name}"
186 mappingOptions = simpleMapOpt('src', 'lib', factory.targetExt or '.js')
187
188 # Find the files we need to compile
189 sourceFileNames = globule.find(factory.files, mappingOptions)
190
191 # Generate edges for each file
192 for match in globule.mapping(sourceFileNames, mappingOptions)
193 edges = factory.makeSrcEdge ninja, match.src, match.dest
194 destFiles = destFiles.concat edges
195
196 ninja.edge('lib').from(destFiles)
197
198# Generate the edge in `ninja` to fingerprint assets.
199#
200makeFingerprintEdge = (ninja, assetsEdges) ->
201 ninja.edge(fp.fingerprintFile).using('fingerprint')
202 .from(assetsEdges)
203 .assign('basePath', "#{fp.buildAssets}/release")
204 fp.fingerprintFile
205
206# Generate a proper `build.ninja` file for subsequent Ninja builds.
207#
208makeNinja = (options, done) ->
209
210 ninja = ninjaBuilder('1.3', 'build')
211
212 factories.forEachFactory (factory) ->
213 factory.initialize?(ninja, config, log)
214
215 ninja.header warnMessage
216 ninja.assign 'cliOptions', '--color' if options.ninjaColor
217
218 makeSystemEdges ninja, getOptionString(options), options
219 makeCommonRules ninja, options
220
221 if !options.noLint
222 files = getCoffeelintPaths()
223 ninja.edge('lint').from makeLintEdges(ninja, files)
224 else
225 # Make a dummy lint edge so we can still run 'ninja lint'.
226 ninja.edge('lint')
227
228 # Make 'debug-assets' and 'release-assets' edges.
229 makeAssetEdges ninja
230
231 # Make the 'lib' edge
232 makeSourceEdges ninja
233
234 # Make edges that people actually call into
235 ninja.edge('debug').from(['debug-assets', 'lib'])
236 ninja.edge('release').from(['release-assets', 'lib'])
237 ninja.edge('all').from(['debug-assets', 'release-assets', 'lib'])
238 ninja.byDefault 'all'
239
240 done null, ninja
241
242# Build the option string that was used to run this instance of benbria-configure-ninja.
243getOptionString = (options) ->
244 str = ''
245 str += ' --no-lint' if options.noLint
246 str += ' --ninja-color' if options.ninjaColor
247 str += ' --streamline8' if options.streamline8
248 str += " --streamline-opts '#{options.streamlineOpts}'" if options.streamlineOpts
249 str += " --stylus-opts '#{options.stylusOpts}'" if options.stylusOpts
250 str += " --require '#{options.require}'" if options.require
251 return str
252
253
254# Get configure options.
255#
256getOptions = ->
257 ArgumentParser = require('argparse').ArgumentParser
258 parser = new ArgumentParser
259 version: packageJson.version
260 addHelp: true
261 description: """
262 Generates a ninja.build file for a coffeescript project.
263 """
264
265 parser.addArgument [ '--require' ],
266 help: "Comma delimited list of modules before building."
267 nargs: "?"
268
269 parser.addArgument [ '--verbose' ],
270 help: "Verbose output"
271 nargs: 0
272
273 parser.addArgument [ '--no-lint' ],
274 help: "Disable coffee linting."
275 dest: "noLint"
276 nargs: 0
277
278 parser.addArgument [ '--color' ],
279 help: "Force color display out of a TTY."
280 nargs: 0
281
282 parser.addArgument [ '--ninja-color' ],
283 help: "Force color on ninja scripts."
284 dest: "ninjaColor"
285 nargs: 0
286
287 parser.addArgument [ '--streamline8' ],
288 help: "Use streamline 0.8.x command line arguments (instead of 0.10.x)"
289 nargs: 0
290
291 parser.addArgument [ '--streamline-opts' ],
292 help: "Extra options for streamline compilers"
293 metavar: "opts"
294 dest: "streamlineOpts"
295 defaultValue: ''
296
297 parser.addArgument [ '--stylus-opts' ],
298 help: "Extra options for stylus"
299 metavar: "opts"
300 dest: "stylusOpts"
301 defaultValue: ''
302
303 # ***************************
304 #
305 # If you add arguments here, add them them to getOptionString() above, too!
306 #
307 # ***************************
308
309 options = parser.parseArgs(process.argv[2..])
310
311 if options.ninjaColor then options.color = true
312 log.verbose options.verbose
313 if options.color then log.color options.color
314
315 if log.color()
316 options.ninjaColor = true
317
318 return options
319
320# Entry point. Build the Ninja manifest and save it.
321#
322# `configureNinjaScript` is the full path to the binary to run to configure ninja.
323#
324module.exports = (configureNinjaScript) ->
325 config.configureNinjaScript = configureNinjaScript
326
327 options = getOptions()
328
329 if options.streamline8 then config.streamlineVersion = 8
330 config.streamlineOpts = options.streamlineOpts
331 config.stylusOpts = options.stylusOpts
332
333 if options.require
334 for mod in options.require.split(',')
335 if mod.indexOf('./') == 0 then mod = path.resolve mod
336 require mod
337
338 makeNinja options, (err, ninja) ->
339 throw err if err
340 ninjaFile = path.resolve(config.ninjaFilePath, config.ninjaFile)
341 ninja.save ninjaFile
342 log.info "generated \'#{ninjaFile}\' (#{ninja.ruleCount} rules, #{ninja.edgeCount} edges)"