├── .coffeelint ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── lib ├── ninja-build-gen.js └── ninja-build-gen.js.map ├── package.json ├── source └── ninja-build-gen.coffee └── test ├── test.coffee ├── test.js └── test.ninja /.coffeelint: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "level": "error" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "no_tabs": { 9 | "level": "error" 10 | }, 11 | "no_trailing_whitespace": { 12 | "level": "error" 13 | }, 14 | "max_line_length": { 15 | "value": 80, 16 | "level": "error" 17 | }, 18 | "camel_case_classes": { 19 | "level": "error" 20 | }, 21 | "indentation": { 22 | "value": 4, 23 | "level": "warn" 24 | }, 25 | "no_implicit_braces": { 26 | "level": "ignore" 27 | }, 28 | "no_trailing_semicolons": { 29 | "level": "error" 30 | }, 31 | "no_plusplus": { 32 | "level": "ignore" 33 | }, 34 | "no_throwing_strings": { 35 | "level": "error" 36 | }, 37 | "no_stand_alone_at": { 38 | "level": "error" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # treat compiled javascript files as binary 2 | lib/**/*.js binary 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | /*.sw* 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test/ 2 | /node_modules/ 3 | /source/ 4 | /.coffeelint 5 | /.editorconfig 6 | /.npmignore 7 | /Gruntfile.coffee 8 | /npm-debug.log 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '4' 5 | - '6' 6 | - node 7 | script: 8 | - npm run prepublish 9 | # make sure there are no changes from running the build scripts 10 | - git diff HEAD --quiet 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.1.3 4 | 5 | * fix #4, '.assign' chain-ability; 6 | * add the 'escape' helper function. 7 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | fs = require 'fs' 3 | 4 | module.exports = (grunt) -> 5 | grunt.initConfig 6 | coffee: 7 | options: 8 | bare: true 9 | sourceMap: true 10 | dist: 11 | files: [{ 12 | expand: true 13 | cwd: 'source' 14 | src: '*.coffee' 15 | dest: 'lib' 16 | ext: '.js' 17 | }] 18 | coffeelint: 19 | files: [ 20 | 'source/*.coffee' 21 | 'test/*.coffee' 22 | 'Gruntfile.coffee' 23 | ] 24 | options: JSON.parse(fs.readFileSync('.coffeelint')) 25 | mochaTest: 26 | test: 27 | options: 28 | reporter: 'spec' 29 | src: 'test/*.js' 30 | 31 | grunt.registerTask 'dist', [ 32 | 'coffeelint' 33 | 'coffee' 34 | 'mochaTest' 35 | ] 36 | 37 | grunt.registerTask 'default', ['dist'] 38 | grunt.loadNpmTasks 'grunt-contrib-coffee' 39 | grunt.loadNpmTasks 'grunt-coffeelint' 40 | grunt.loadNpmTasks 'grunt-mocha-test' 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jean Lauliac 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ninja-build-gen 2 | [![Build Status](https://travis-ci.org/jeanlauliac/ninja-build-gen.svg?branch=master)](https://travis-ci.org/jeanlauliac/ninja-build-gen) 3 | 4 | Create [Ninja](https://ninja-build.org/) build-system manifests from 5 | JavaScript. This can be used for build in any kind of project, though (Ruby, 6 | Python, or even C++, why not...). 7 | 8 | ## Install 9 | 10 | The easiest is to use the mainstream Node.js package manager, `npm`, to handle 11 | the package installation. Most of the time, you'll add it to the 12 | `devDependencies` of your `package.json`, eg: 13 | 14 | ```bash 15 | $ npm install ninja-build-gen --save-dev 16 | ``` 17 | 18 | You may typically want to install the 19 | [ninja-build](https://github.com/undashes/ninja-build) as well. Indeed, this 20 | package does not include the Ninja build system itself. 21 | 22 | Generally, you should not need this package for package distribution (instead 23 | of being in `dependencies`), since it's mostly useful to handle the minimal 24 | compilation of assets, like CoffeeScript files. Published packages should 25 | contain the compiled JS files (or CSS, etc.), not the sources (nor the tests). 26 | 27 | ## Sample Use 28 | 29 | This is typically used creating a `configure.js` script. In this script, let's 30 | first create a new build file for Ninja version 1.3, with the build directory 31 | `build`: 32 | 33 | ```js 34 | ninjaBuildGen = require('ninja-build-gen'); 35 | ninja = ninjaBuildGen('1.3', 'build'); 36 | ``` 37 | 38 | Let's add two rules, for example, one to compile 39 | [CoffeeScript](https://github.com/jashkenas/coffee-script) files, the other to 40 | compile [Stylus](https://github.com/LearnBoost/stylus) files: 41 | 42 | ```js 43 | ninja.rule('coffee').run('coffee -cs < $in > $out') 44 | .description("Compile Coffeescript '$in' to '$out'."); 45 | ninja.rule('stylus').run('stylus $in -o $$(dirname $out)') 46 | .description("Compile Stylus '$in' to '$out'"); 47 | ``` 48 | 49 | Then we can add edges to compile the actual files, for example: 50 | 51 | ```js 52 | ninja.edge('foo.js').from('foo.coffee').using('coffee'); 53 | ninja.edge('bar.js').from('bar.coffee').using('coffee'); 54 | ninja.edge('glo.css').from('glo.stylus').using('stylus'); 55 | ninja.edge('assets').from(['foo.js', 'bar.js', 'glo.cs']); 56 | ninja.byDefault('assets'); 57 | ``` 58 | 59 | Let's save the file to the standard Ninja filename: 60 | 61 | ```js 62 | ninja.save('build.ninja'); 63 | ``` 64 | 65 | That's it! Now you can run the configure script, then ninja, to build the 66 | project: 67 | 68 | ```bash 69 | $ node configure.js 70 | $ ninja 71 | [1/3] Compile Coffeescript 'foo.coffee' to 'foo.js'. 72 | [2/3] Compile Coffeescript 'bar.coffee' to 'bar.js'. 73 | [3/3] Compile Coffeescript 'glo.styl' to 'glo.css'. 74 | Done. 75 | $ ninja 76 | ninja: nothing to do. 77 | ``` 78 | 79 | Thanks to Ninja, you get minimal recompilation: only changed file are 80 | recompiled upon invocation, as you can see for the second execution above. 81 | 82 | ## Limitations 83 | 84 | This package is only here to make easier the creation of Ninja build files, but 85 | it does not provide any high-level features, and those are out of scope. That 86 | is, no wildcards, no globbing and file lookup; just streamlined Ninja build 87 | file generation. 88 | 89 | It is recommended to use a globbing library such as 90 | [glob](https://npmjs.org/package/glob) to create the edges based on 91 | existing files. Also, you can generate the output file names by using the 92 | [globule](https://npmjs.org/package/globule) library. For example: 93 | 94 | ```js 95 | var files = globule.findMapping('*.coffee', {srcBase: 'src', destBase: 'out', 96 | ext: '.js'}); 97 | for (var i = 0; i < files.length; ++i) { 98 | var match = files[i]; 99 | ninja.edge(match.dest).from(match.src).using('coffee'); 100 | } 101 | ``` 102 | 103 | ## API 104 | 105 | Calls can generally be chained, on ``, `` and ``. For 106 | example: `ninja.edge('foo.o').from('foo.c').need('foo.h').using('cc')`. 107 | 108 | ##### `ninjaBuildGen([version], [builddir])` 109 | 110 | Create a `` manifest. `version` specifies the Ninja version required 111 | in the manifest, `builddir` is the building folder where Ninja will put 112 | temporary files. You can refer to it using `$builddir` in Ninja 113 | clauses. 114 | 115 | ##### `ninjaBuildGen.escape(string)` 116 | 117 | Escape the `string` to be suitable in a Ninja file. See the 118 | [Ninja lexical syntax](http://martine.github.io/ninja/manual.html#_lexical_syntax) 119 | for more information on escaping. It escapes characters `$`, `:`, and spaces. 120 | You can use this function to process a list of path when you know you don't 121 | need to access variables. Eg: 122 | 123 | ```js 124 | var paths = ['foo.js', 'bar.js', 'glo.js']; 125 | ninja.edge('concat.js').from(paths.map(ninjaBuildGen.escape)).using('concat'); 126 | ``` 127 | 128 | Otherwise, you need to escape manually. The following statements are 129 | equivalent: 130 | 131 | ```js 132 | ninja.edge('foo$:bar$$glo$ fiz.js'); 133 | ninja.edge(ninjaBuildGen.escape('foo:bar$glo fiz.js')); 134 | ``` 135 | 136 | ### `` 137 | 138 | ##### `.header(value)` 139 | 140 | Add an arbitrary header to the file containing `value`. This is useful to add 141 | some comments on top, such as `# generated from configure.js`. You can't 142 | cumulate several headers. 143 | 144 | ##### `.byDefault(name)` 145 | 146 | Set the default edge to build when Ninja is called without target. There can 147 | be only one default edge. 148 | 149 | ##### `.assign(name, value)` 150 | 151 | Add a global variable assignation in the manifest. The order is important, you 152 | shall call this before adding a rule or edge referencing the variable. 153 | 154 | ##### `.rule(name)` 155 | 156 | Create a ``, add it the manifest, and return it. The `name` 157 | of the rule is then used to reference it from the edges. 158 | 159 | ##### `.edge(targets)` 160 | 161 | Create an ``, add it to the manifest, and return it. The `targets` of the 162 | edge is a `String` or an `Array` of it specifying the files that will 163 | result from the compilation of the edge. Each `String` is a path, that can 164 | be absolute, or relative to the location of the manifest (recommended). 165 | 166 | ##### `.edges` 167 | An `Array` of all the edges added. 168 | 169 | ##### `.rules` 170 | An `Array` of all the rules added. 171 | 172 | ##### `.variables` 173 | An `Array` of all the global variables assigned. 174 | 175 | ##### `.save(path, [callback])` 176 | 177 | Output the manifest to a file at `path`. Call `callback()` once it's done. 178 | 179 | ##### `.saveToStream(stream)` 180 | 181 | Output the manifest to a Node.js 182 | [`stream.Writable`](http://nodejs.org/api/stream.html#stream_class_stream_writable). 183 | It does not 'end' it. It can be used, for example, like this: 184 | 185 | ```js 186 | file = require('fs').createWriteStream('build.ninja'); 187 | ninja.saveToStream(file); 188 | file.end(); 189 | file.on('finish', function() {console.log('manifest created!')}) 190 | ``` 191 | 192 | ### `` 193 | 194 | ##### `.run(command)` 195 | 196 | Execute the specified `command` (a `String`) when the rule is invoked. You can 197 | refer to Ninja variables like everywhere else. 198 | 199 | ##### `.description(desc)` 200 | 201 | Provide a description, displayed by Ninja when executing the rule. 202 | 203 | ##### `.depfile(file)` 204 | 205 | Specify a Makefile-compatible dependency `file` generated by the rule. See the 206 | Ninja documentation for more information on those. 207 | 208 | ##### `.restat(doRestart)` 209 | 210 | Enable or not the 'restat' of output files. It's a `boolean`. 211 | 212 | ##### `.generator(isGenerator)` 213 | 214 | Specifiy if the rule is a 'generator' or not. It's a `boolean`. 215 | 216 | ##### `.write(stream)` 217 | 218 | Write the rule to a steam. Generally you will want to use `.save` 219 | instead. 220 | 221 | ### `` 222 | 223 | ##### `.using(rule)` 224 | 225 | Specify the `rule` (a `String`) used to build the edge. 226 | 227 | ##### `.from(sources)` 228 | 229 | Specify an `Array` or a `String` being the paths of source files for the 230 | edge. Those are directly fed to the rule. 231 | 232 | ##### `.need(dependencies)` 233 | 234 | Specify an `Array` or a `String` being the paths of dependencies for the 235 | edge. Those trigger a recompilation when modified, but are not direct sources 236 | for the rule. 237 | 238 | ##### `.after(orderDeps)` 239 | 240 | Specify an `Array` or a `String` being the paths of order-only dependencies for 241 | the edge. 242 | 243 | ##### `.pool(poolName)` 244 | 245 | Specify a pool for this edges as a `String`. As you can't creat pools yet this 246 | is only really useful for specifying the 'console' pool. See the [pool documentation](https://ninja-build.org/manual.html#ref_pool). 247 | 248 | ##### `.assign(name, value)` 249 | 250 | Add a variable assignation local to the edge. 251 | 252 | ##### `.write(stream)` 253 | 254 | Write the rule to a steam. Generally you will want to use `.save` 255 | instead. 256 | 257 | ## Contribute 258 | 259 | Please, feel free to fork and submit pull requests. 260 | -------------------------------------------------------------------------------- /lib/ninja-build-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var NinjaAssignBuilder, NinjaBuilder, NinjaEdgeBuilder, NinjaRuleBuilder, escape, fs; 3 | 4 | require('source-map-support').install(); 5 | 6 | fs = require('fs'); 7 | 8 | escape = function(s) { 9 | return s.replace(/[ :$]/g, function(match) { 10 | return '$' + match; 11 | }); 12 | }; 13 | 14 | NinjaAssignBuilder = (function() { 15 | function NinjaAssignBuilder(name, value) { 16 | this.name = name; 17 | this.value = value; 18 | } 19 | 20 | NinjaAssignBuilder.prototype.write = function(stream) { 21 | return stream.write("" + this.name + " = " + this.value + "\n"); 22 | }; 23 | 24 | return NinjaAssignBuilder; 25 | 26 | })(); 27 | 28 | NinjaEdgeBuilder = (function() { 29 | function NinjaEdgeBuilder(targets) { 30 | this.targets = targets; 31 | this.assigns = []; 32 | this.rule = 'phony'; 33 | if (typeof this.targets === 'string') { 34 | this.targets = [this.targets]; 35 | } 36 | } 37 | 38 | NinjaEdgeBuilder.prototype.using = function(rule) { 39 | this.rule = rule; 40 | return this; 41 | }; 42 | 43 | NinjaEdgeBuilder.prototype.from = function(sources) { 44 | if (typeof sources === 'string') { 45 | sources = [sources]; 46 | } 47 | if (this.sources == null) { 48 | this.sources = sources; 49 | } else { 50 | this.sources = this.sources.concat(sources); 51 | } 52 | return this; 53 | }; 54 | 55 | NinjaEdgeBuilder.prototype.need = function(dependencies) { 56 | if (typeof dependencies === 'string') { 57 | dependencies = [dependencies]; 58 | } 59 | if (this.dependencies == null) { 60 | this.dependencies = dependencies; 61 | } else { 62 | this.dependencies = this.dependencies.concat(dependencies); 63 | } 64 | return this; 65 | }; 66 | 67 | NinjaEdgeBuilder.prototype.after = function(orderDeps) { 68 | if (typeof orderDeps === 'string') { 69 | orderDeps = [orderDeps]; 70 | } 71 | if (this.orderDeps == null) { 72 | this.orderDeps = orderDeps; 73 | } else { 74 | this.orderDeps = this.orderDeps.concat(orderDeps); 75 | } 76 | return this; 77 | }; 78 | 79 | NinjaEdgeBuilder.prototype.assign = function(name, value) { 80 | this.assigns[name] = value; 81 | return this; 82 | }; 83 | 84 | NinjaEdgeBuilder.prototype.pool = function(pool) { 85 | this._pool = pool; 86 | return this; 87 | }; 88 | 89 | NinjaEdgeBuilder.prototype.write = function(stream) { 90 | var name, value, _ref; 91 | stream.write("build " + (this.targets.join(' ')) + ": " + this.rule); 92 | if (this.sources != null) { 93 | stream.write(' ' + this.sources.join(' ')); 94 | } 95 | if (this.dependencies != null) { 96 | stream.write(' | ' + this.dependencies.join(' ')); 97 | } 98 | if (this.orderDeps != null) { 99 | stream.write(' || ' + this.orderDeps.join(' ')); 100 | } 101 | _ref = this.assigns; 102 | for (name in _ref) { 103 | value = _ref[name]; 104 | stream.write("\n " + name + " = " + value); 105 | } 106 | stream.write('\n'); 107 | if (this._pool != null) { 108 | return stream.write(" pool = " + this._pool + "\n"); 109 | } 110 | }; 111 | 112 | return NinjaEdgeBuilder; 113 | 114 | })(); 115 | 116 | NinjaRuleBuilder = (function() { 117 | function NinjaRuleBuilder(name) { 118 | this.name = name; 119 | this.command = ''; 120 | } 121 | 122 | NinjaRuleBuilder.prototype.run = function(command) { 123 | this.command = command; 124 | return this; 125 | }; 126 | 127 | NinjaRuleBuilder.prototype.description = function(desc) { 128 | this.desc = desc; 129 | return this; 130 | }; 131 | 132 | NinjaRuleBuilder.prototype.depfile = function(file) { 133 | this.dependencyFile = file; 134 | return this; 135 | }; 136 | 137 | NinjaRuleBuilder.prototype.restat = function(doRestat) { 138 | this.doRestat = doRestat; 139 | return this; 140 | }; 141 | 142 | NinjaRuleBuilder.prototype.generator = function(isGenerator) { 143 | this.isGenerator = isGenerator; 144 | return this; 145 | }; 146 | 147 | NinjaRuleBuilder.prototype.pool = function(pool) { 148 | this._pool = pool; 149 | return this; 150 | }; 151 | 152 | NinjaRuleBuilder.prototype.write = function(stream) { 153 | stream.write("rule " + this.name + "\n command = " + this.command + "\n"); 154 | if (this.desc != null) { 155 | stream.write(" description = " + this.desc + "\n"); 156 | } 157 | if (this.doRestat) { 158 | stream.write(" restat = 1\n"); 159 | } 160 | if (this.isGenerator) { 161 | stream.write(" generator = 1\n"); 162 | } 163 | if (this._pool != null) { 164 | stream.write(" pool = " + this._pool + "\n"); 165 | } 166 | if (this.dependencyFile != null) { 167 | stream.write(" depfile = " + this.dependencyFile + "\n"); 168 | return stream.write(" deps = gcc\n"); 169 | } 170 | }; 171 | 172 | return NinjaRuleBuilder; 173 | 174 | })(); 175 | 176 | NinjaBuilder = (function() { 177 | function NinjaBuilder(version, buildDir) { 178 | this.version = version; 179 | this.buildDir = buildDir; 180 | this.edges = []; 181 | this.rules = []; 182 | this.variables = []; 183 | this.edgeCount = 0; 184 | this.ruleCount = 0; 185 | } 186 | 187 | NinjaBuilder.prototype.header = function(value) { 188 | this.headerValue = value; 189 | return this; 190 | }; 191 | 192 | NinjaBuilder.prototype.byDefault = function(name) { 193 | this.defaultRule = name; 194 | return this; 195 | }; 196 | 197 | NinjaBuilder.prototype.assign = function(name, value) { 198 | var clause; 199 | clause = new NinjaAssignBuilder(name, value); 200 | this.variables.push(clause); 201 | return clause; 202 | }; 203 | 204 | NinjaBuilder.prototype.rule = function(name) { 205 | var clause; 206 | clause = new NinjaRuleBuilder(name); 207 | this.rules.push(clause); 208 | this.ruleCount++; 209 | return clause; 210 | }; 211 | 212 | NinjaBuilder.prototype.edge = function(targets) { 213 | var clause; 214 | clause = new NinjaEdgeBuilder(targets); 215 | this.edges.push(clause); 216 | this.edgeCount++; 217 | return clause; 218 | }; 219 | 220 | NinjaBuilder.prototype.saveToStream = function(stream) { 221 | var clause, _i, _len, _ref; 222 | if (this.headerValue != null) { 223 | stream.write(this.headerValue + '\n\n'); 224 | } 225 | if (this.version != null) { 226 | stream.write("ninja_required_version = " + this.version + "\n"); 227 | } 228 | if (this.buildDir != null) { 229 | stream.write("builddir=" + this.buildDir + "\n"); 230 | } 231 | _ref = [].concat(this.rules, this.edges, this.variables); 232 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 233 | clause = _ref[_i]; 234 | clause.write(stream); 235 | } 236 | if (this.defaultRule != null) { 237 | return stream.write("default " + this.defaultRule + "\n"); 238 | } 239 | }; 240 | 241 | NinjaBuilder.prototype.save = function(path, callback) { 242 | var file; 243 | file = fs.createWriteStream(path); 244 | this.saveToStream(file); 245 | if (callback) { 246 | file.on('close', function() { 247 | return callback(); 248 | }); 249 | } 250 | return file.end(); 251 | }; 252 | 253 | return NinjaBuilder; 254 | 255 | })(); 256 | 257 | module.exports = function(version, builddir) { 258 | return new NinjaBuilder(version, builddir); 259 | }; 260 | 261 | module.exports.escape = escape; 262 | 263 | /* 264 | //@ sourceMappingURL=ninja-build-gen.js.map 265 | */ -------------------------------------------------------------------------------- /lib/ninja-build-gen.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "ninja-build-gen.js", 4 | "sourceRoot": "../source/", 5 | "sources": [ 6 | "ninja-build-gen.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ninja-build-gen", 3 | "version": "0.2.2", 4 | "description": "Programmatically create Ninja build-system files", 5 | "keywords": [ 6 | "ninja", 7 | "build", 8 | "system", 9 | "builder", 10 | "generate", 11 | "build.ninja" 12 | ], 13 | "homepage": "https://github.com/jeanlauliac/ninja-build-gen", 14 | "bugs": "https://github.com/jeanlauliac/ninja-build-gen/issues", 15 | "license": { 16 | "type": "MIT", 17 | "url": "https://raw.github.com/jeanlauliac/ninja-build-gen/master/LICENSE" 18 | }, 19 | "author": "Jean Lauliac ", 20 | "contributors": [ 21 | "Tylor Reynolds" 22 | ], 23 | "files": [ 24 | "lib" 25 | ], 26 | "main": "lib/ninja-build-gen.js", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/jeanlauliac/ninja-build-gen.git" 30 | }, 31 | "scripts": { 32 | "prepublish": "grunt", 33 | "test": "mocha" 34 | }, 35 | "dependencies": { 36 | "source-map-support": "^0.4.0" 37 | }, 38 | "devDependencies": { 39 | "coffee-script": "~1.6.3", 40 | "coffeelint": "~0.5.6", 41 | "grunt": "~0.4.1", 42 | "grunt-cli": "^1.2.0", 43 | "grunt-coffeelint": "0.0.7", 44 | "grunt-contrib-coffee": "~0.7.0", 45 | "grunt-mocha": "~0.3.4", 46 | "grunt-mocha-test": "~0.5.0", 47 | "matchdep": "~0.1.1", 48 | "mocha": "~1.12.0" 49 | }, 50 | "engines": { 51 | "node": ">=0.8.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /source/ninja-build-gen.coffee: -------------------------------------------------------------------------------- 1 | # # Ninja-build Generator 2 | # 3 | # This library exports a a set of functions to build a Ninja file 4 | # programmatically. 5 | 'use strict' 6 | require('source-map-support').install() 7 | fs = require 'fs' 8 | 9 | # Escape a string, like a path, to be suitable for Ninja. 10 | # This is to be called explicitely because the user may want to use 11 | # variables like `$foo` in paths, rules, etc. 12 | escape = (s) -> 13 | s.replace /[ :$]/g, (match) -> 14 | '$' + match 15 | 16 | # Represent a Ninja variable assignation (it's more a binding, actually). 17 | class NinjaAssignBuilder 18 | constructor: (@name, @value) -> 19 | 20 | # Write the assignation into a `stream`. 21 | write: (stream) -> 22 | stream.write "#{@name} = #{@value}\n" 23 | 24 | # Represent a Ninja edge, that is, "how to construct this file X from Y". 25 | class NinjaEdgeBuilder 26 | # Construct an edge specifing the resulting files, as `targets`, of the 27 | # edge. 28 | constructor: (@targets) -> 29 | @assigns = [] 30 | @rule = 'phony' 31 | if typeof @targets == 'string' 32 | @targets = [@targets] 33 | 34 | # Define the Ninja `rule` name to use to build this edge. 35 | using: (rule) -> 36 | @rule = rule 37 | this 38 | 39 | # Define one or several direct `sources`, that is, files to be transformed 40 | # by the rule. 41 | from: (sources) -> 42 | if typeof sources == 'string' 43 | sources = [sources] 44 | unless @sources? 45 | @sources = sources 46 | else 47 | @sources = @sources.concat sources 48 | this 49 | 50 | # Define one or several indirect `dependencies`, that is, files needed but 51 | # not part of the compilation or transformation. 52 | need: (dependencies) -> 53 | if typeof dependencies == 'string' 54 | dependencies = [dependencies] 55 | unless @dependencies? 56 | @dependencies = dependencies 57 | else 58 | @dependencies = @dependencies.concat dependencies 59 | this 60 | 61 | # Define one or several order-only dependencies in `orderDeps`, that is, 62 | # this edge should be build after those dependencies are. 63 | after: (orderDeps) -> 64 | if typeof orderDeps == 'string' 65 | orderDeps = [orderDeps] 66 | unless @orderDeps? 67 | @orderDeps = orderDeps 68 | else 69 | @orderDeps = @orderDeps.concat orderDeps 70 | this 71 | 72 | # Bind a variable to a temporary value for the edge. 73 | assign: (name, value) -> 74 | @assigns[name] = value 75 | this 76 | 77 | # Assign this edge to a pool. 78 | # See https://ninja-build.org/manual.html#ref_pool 79 | pool: (pool) -> 80 | @_pool = pool 81 | this 82 | 83 | # Write the edge into a `stream`. 84 | write: (stream) -> 85 | stream.write "build #{@targets.join(' ')}: #{@rule}" 86 | stream.write ' ' + @sources.join(' ') if @sources? 87 | if @dependencies? 88 | stream.write ' | ' + @dependencies.join ' ' 89 | if @orderDeps? 90 | stream.write ' || ' + @orderDeps.join ' ' 91 | for name, value of @assigns 92 | stream.write "\n #{name} = #{value}" 93 | stream.write '\n' 94 | stream.write " pool = #{@_pool}\n" if @_pool? 95 | 96 | # Represent a Ninja rule, that is, a method to "how I build a file of type A 97 | # to type B". 98 | class NinjaRuleBuilder 99 | # Create a rule with this `name`. 100 | constructor: (@name) -> 101 | @command = '' 102 | 103 | # Specify the command-line to run to execute the rule. 104 | run: (command) -> 105 | @command = command 106 | this 107 | 108 | # Provide a description, displayed by Ninja instead of the bare command- 109 | # line. 110 | description: (desc) -> 111 | @desc = desc 112 | this 113 | 114 | # Provide a Makefile-compatible dependency file for the rule products. 115 | depfile: (file) -> 116 | @dependencyFile = file 117 | this 118 | 119 | restat: (doRestat) -> 120 | @doRestat = doRestat 121 | this 122 | 123 | generator: (isGenerator) -> 124 | @isGenerator = isGenerator 125 | this 126 | 127 | pool: (pool) -> 128 | @_pool = pool 129 | this 130 | 131 | # Write the rule into a `stream`. 132 | write: (stream) -> 133 | stream.write "rule #{@name}\n command = #{@command}\n" 134 | stream.write " description = #{@desc}\n" if @desc? 135 | stream.write " restat = 1\n" if @doRestat 136 | stream.write " generator = 1\n" if @isGenerator 137 | stream.write " pool = #{@_pool}\n" if @_pool? 138 | if @dependencyFile? 139 | stream.write " depfile = #{@dependencyFile}\n" 140 | stream.write " deps = gcc\n" 141 | 142 | # Provide helpers to build a Ninja file by specifing high-level rules and 143 | # targets. 144 | class NinjaBuilder 145 | # Create the builder, specifing an optional required Ninja `version`, and a 146 | # build directory (where Ninja put logs and where you can put 147 | # intermediary products). 148 | constructor: (@version, @buildDir) -> 149 | @edges = [] 150 | @rules = [] 151 | @variables = [] 152 | @edgeCount = 0 153 | @ruleCount = 0 154 | 155 | # Set an arbitrary header. 156 | header: (value) -> 157 | @headerValue = value 158 | this 159 | 160 | # Specify the default rule by its `name`. 161 | byDefault: (name) -> 162 | @defaultRule = name 163 | this 164 | 165 | # Add a variable assignation into `name` from the `value`. 166 | assign: (name, value) -> 167 | clause = new NinjaAssignBuilder(name, value) 168 | @variables.push clause 169 | clause 170 | 171 | # Add a rule and return it. 172 | rule: (name) -> 173 | clause = new NinjaRuleBuilder(name) 174 | @rules.push clause 175 | @ruleCount++ 176 | clause 177 | 178 | # Add an edge and return it. 179 | edge: (targets) -> 180 | clause = new NinjaEdgeBuilder(targets) 181 | @edges.push clause 182 | @edgeCount++ 183 | clause 184 | 185 | # Write to a `stream`. It does not end the stream. 186 | saveToStream: (stream) -> 187 | stream.write @headerValue + '\n\n' if @headerValue? 188 | stream.write "ninja_required_version = #{@version}\n" if @version? 189 | stream.write "builddir=#{@buildDir}\n" if @buildDir? 190 | for clause in [].concat(@rules, @edges, @variables) 191 | clause.write stream 192 | stream.write "default #{@defaultRule}\n" if @defaultRule? 193 | 194 | # Save the Ninja file on the filesystem at this `path` and call 195 | # `callback` when it's done. 196 | save: (path, callback) -> 197 | file = fs.createWriteStream(path) 198 | @saveToStream file 199 | if callback 200 | file.on 'close', -> callback() 201 | file.end() 202 | 203 | module.exports = (version, builddir) -> 204 | new NinjaBuilder(version, builddir) 205 | 206 | module.exports.escape = escape 207 | -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | ninjaBuildGen = require '../lib/ninja-build-gen' 3 | fs = require 'fs' 4 | 5 | compareToString = (ninja, targetStr) -> 6 | str = '' 7 | buffer = 8 | write: (value) -> 9 | str += value 10 | ninja.saveToStream buffer 11 | assert.equal str, targetStr 12 | 13 | describe 'ninja', -> 14 | ninja = null 15 | beforeEach -> 16 | ninja = ninjaBuildGen() 17 | 18 | describe 'escape', -> 19 | it 'should escape proper characters', -> 20 | assert.equal ninjaBuildGen.escape('foo:bar$glo fiz.js'), 21 | 'foo$:bar$$glo$ fiz.js' 22 | 23 | describe '#rule', -> 24 | it 'should specify the command line', -> 25 | ninja.rule('coffee').run('coffee -cs < $in > $out') 26 | compareToString ninja, 27 | """ 28 | rule coffee 29 | command = coffee -cs < $in > $out\n 30 | """ 31 | 32 | it 'should create a description', -> 33 | ninja.rule('coffee') 34 | .description('Compile coffee file: $in') 35 | compareToString ninja, 36 | """ 37 | rule coffee 38 | command = \n description = Compile coffee file: $in\n 39 | """ 40 | 41 | it 'should create a depfile binding', -> 42 | ninja.rule('coffee') 43 | .depfile('$out.d') 44 | compareToString ninja, 45 | """ 46 | rule coffee 47 | command = \n depfile = $out.d 48 | deps = gcc\n 49 | """ 50 | 51 | it 'should enable restat', -> 52 | ninja.rule('coffee') 53 | .restat(true) 54 | compareToString ninja, 55 | """ 56 | rule coffee 57 | command = \n restat = 1\n 58 | """ 59 | 60 | it 'should label as generator', -> 61 | ninja.rule('coffee') 62 | .generator(true) 63 | compareToString ninja, 64 | """ 65 | rule coffee 66 | command = \n generator = 1\n 67 | """ 68 | 69 | describe '#edge', -> 70 | it 'should create a simple phony edge', -> 71 | ninja.edge('simple_phony') 72 | compareToString ninja, 'build simple_phony: phony\n' 73 | it 'should create a multi-target phony edge', -> 74 | ninja.edge(['phony1', 'phony2']) 75 | compareToString ninja, 'build phony1 phony2: phony\n' 76 | it 'should specify a rule', -> 77 | ninja.edge('baobab.js').using('coffee') 78 | compareToString ninja, 'build baobab.js: coffee\n' 79 | it 'should bind a variable', -> 80 | ninja.edge('baobab.js').assign 'foobar', 42 81 | compareToString ninja, 'build baobab.js: phony\n foobar = 42\n' 82 | 83 | describe '#from', -> 84 | it 'should specify a source', -> 85 | ninja.edge('dist').from('debug') 86 | compareToString ninja, 'build dist: phony debug\n' 87 | it 'should specify several sources', -> 88 | ninja.edge('dist').from(['debug', 'release']) 89 | compareToString ninja, 'build dist: phony debug release\n' 90 | it 'should specify accumulated sources', -> 91 | ninja.edge('dist').from('debug').from(['release', 'lint']) 92 | compareToString ninja, 'build dist: phony debug release lint\n' 93 | 94 | describe '#need', -> 95 | it 'should specify a requirement', -> 96 | ninja.edge('dist').need('debug') 97 | compareToString ninja, 'build dist: phony | debug\n' 98 | it 'should specify several requirements', -> 99 | ninja.edge('dist').need(['debug', 'release']) 100 | compareToString ninja, 'build dist: phony | debug release\n' 101 | it 'should specify accumulated requirements', -> 102 | ninja.edge('dist').need('debug').need(['release', 'lint']) 103 | compareToString ninja, 104 | 'build dist: phony | debug release lint\n' 105 | 106 | describe '#after', -> 107 | it 'should specify a order-only requirement', -> 108 | ninja.edge('dist').after('debug') 109 | compareToString ninja, 'build dist: phony || debug\n' 110 | it 'should specify several order-only requirements', -> 111 | ninja.edge('dist').after(['debug', 'release']) 112 | compareToString ninja, 113 | 'build dist: phony || debug release\n' 114 | it 'should specify accumulated order-only requirements', -> 115 | ninja.edge('dist').after('debug').after(['release', 'lint']) 116 | compareToString ninja, 117 | 'build dist: phony || debug release lint\n' 118 | 119 | describe '#header', -> 120 | it 'should add a header', -> 121 | ninja.header('foobar\nfizzbuzz') 122 | compareToString ninja, 'foobar\nfizzbuzz\n\n' 123 | 124 | describe '#assign', -> 125 | it 'should bind a variable', -> 126 | ninja.assign 'some_var', 42 127 | compareToString ninja, 'some_var = 42\n' 128 | 129 | describe '#save', -> 130 | it 'should properly save to file', (cb) -> 131 | ninja.assign 'some_var', 42 132 | filePath = "#{__dirname}/test.ninja" 133 | ninja.save filePath, -> 134 | savedStr = fs.readFileSync(filePath, 'utf-8') 135 | assert.equal savedStr, 136 | """ 137 | some_var = 42\n 138 | """ 139 | cb() 140 | it 'should save to file without callback', -> 141 | ninja.assign 'some_var', 42 142 | filePath = "#{__dirname}/test.ninja" 143 | ninja.save filePath 144 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | require('coffee-script'); 2 | require('./test.coffee'); 3 | -------------------------------------------------------------------------------- /test/test.ninja: -------------------------------------------------------------------------------- 1 | some_var = 42 2 | --------------------------------------------------------------------------------