├── .gitignore ├── .eslintrc ├── spec ├── Makefile.withduplicates ├── Makefile └── build-make-spec.js ├── .travis.yml ├── README.md ├── package.json ├── LICENSE.md └── lib └── make.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "atom-build" 4 | } 5 | -------------------------------------------------------------------------------- /spec/Makefile.withduplicates: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "Building all" 3 | 4 | duplicated: all 5 | 6 | duplicated: 7 | @echo "Building duplicated" 8 | -------------------------------------------------------------------------------- /spec/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "Surprising is the passing of time" 3 | @echo "but not so, as the time of passing" 4 | 5 | some_custom: all 6 | @echo "untz" 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | webhooks: 9 | urls: 10 | - https://webhooks.gitter.im/e/de0569306a16f2435ef2 11 | on_success: change 12 | on_failure: always 13 | on_start: false 14 | 15 | before_script: 16 | - npm install 17 | 18 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 19 | 20 | git: 21 | depth: 10 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GNU Make for Atom (via atom-build) 2 | [![Build Status](https://travis-ci.org/AtomBuild/atom-build-make.svg?branch=master)](https://travis-ci.org/AtomBuild/atom-build-make) 3 | [![Gitter chat](https://badges.gitter.im/noseglid/atom-build.svg)](https://gitter.im/noseglid/atom-build) 4 | 5 | Uses the [atom-build](https://github.com/noseglid/atom-build) package to execute 6 | GNU make in the `Atom` editor. 7 | 8 | This package requires [atom-build](https://github.com/noseglid/atom-build) to be installed. 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-make", 3 | "version": "0.13.0", 4 | "description": "Runs GNU Make", 5 | "repository": "https://github.com/AtomBuild/atom-build-make", 6 | "license": "MIT", 7 | "engines": { 8 | "atom": "^1.0.0" 9 | }, 10 | "providedServices": { 11 | "builder": { 12 | "description": "Runs GNU Make", 13 | "versions": { 14 | "2.0.0": "provideBuilder" 15 | } 16 | } 17 | }, 18 | "devDependencies": { 19 | "temp": "^0.8.3", 20 | "fs-extra": "^0.23.1", 21 | "atom-build-spec-helpers": "^0.4.0", 22 | "babel-eslint": "^4.1.5", 23 | "eslint": "^1.10.1", 24 | "eslint-config-atom-build": "^2.0.0" 25 | }, 26 | "keywords": [ 27 | "build", 28 | "compile", 29 | "make", 30 | "productivity", 31 | "gnu" 32 | ], 33 | "main": "lib/make.js", 34 | "dependencies": { 35 | "voucher": "^0.1.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Alexander Olsson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/make.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import os from 'os'; 6 | import { exec } from 'child_process'; 7 | import voucher from 'voucher'; 8 | import { EventEmitter } from 'events'; 9 | 10 | export const config = { 11 | jobs: { 12 | title: 'Simultaneous jobs', 13 | description: 'Limits how many jobs make will run simultaneously. Defaults to number of processors. Set to 1 for default behavior of make.', 14 | type: 'number', 15 | default: os.cpus().length, 16 | minimum: 1, 17 | maximum: os.cpus().length, 18 | order: 1 19 | }, 20 | useMake: { 21 | title: 'Target extraction with make', 22 | description: 'Use `make` to extract targets. This may yield unwanted targets, or take a long time and a lot of resource.', 23 | type: 'boolean', 24 | default: false, 25 | order: 2 26 | } 27 | }; 28 | 29 | export function provideBuilder() { 30 | const gccErrorMatch = '(?([A-Za-z]:[\\/])?[^:\\n]+):(?\\d+):(?\\d+):\\s*(fatal error|error):\\s*(?.+)'; 31 | const gfortranErrorMatch = '(?[^:\\n]+):(?\\d+):(?\\d+):[\\s\\S]+?Error: (?.+)'; 32 | const ocamlErrorMatch = '(?[\\/0-9a-zA-Z\\._\\-]+)", line (?\\d+), characters (?\\d+)-(?\\d+):\\n(?.+)'; 33 | const golangErrorMatch = '(?([A-Za-z]:[\\/])?[^:\\n]+):(?\\d+):\\s*(?.*error.+)'; 34 | const errorMatch = [ 35 | gccErrorMatch, gfortranErrorMatch, ocamlErrorMatch, golangErrorMatch 36 | ]; 37 | 38 | const gccWarningMatch = '(?([A-Za-z]:[\\/])?[^:\\n]+):(?\\d+):(?\\d+):\\s*(warning):\\s*(?.+)'; 39 | const warningMatch = [ 40 | gccWarningMatch 41 | ]; 42 | 43 | return class MakeBuildProvider extends EventEmitter { 44 | constructor(cwd) { 45 | super(); 46 | this.cwd = cwd; 47 | atom.config.observe('build-make.jobs', () => this.emit('refresh')); 48 | } 49 | 50 | getNiceName() { 51 | return 'GNU Make'; 52 | } 53 | 54 | isEligible() { 55 | this.files = [ 'Makefile', 'GNUmakefile', 'makefile' ] 56 | .map(f => path.join(this.cwd, f)) 57 | .filter(fs.existsSync); 58 | return this.files.length > 0; 59 | } 60 | 61 | settings() { 62 | const args = [ `-j${atom.config.get('build-make.jobs')}` ]; 63 | 64 | const defaultTarget = { 65 | exec: 'make', 66 | name: 'GNU Make: default (no target)', 67 | args: args, 68 | sh: false, 69 | errorMatch: errorMatch, 70 | warningMatch: warningMatch 71 | }; 72 | 73 | const promise = atom.config.get('build-make.useMake') ? 74 | voucher(exec, 'make -prRn', { cwd: this.cwd }) : 75 | voucher(fs.readFile, this.files[0]); // Only take the first file 76 | 77 | return promise.then(output => { 78 | return [ defaultTarget ].concat(output.toString('utf8') 79 | .split(/[\r\n]{1,2}/) 80 | .filter(line => /^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/.test(line)) 81 | .map(targetLine => targetLine.split(':').shift()) 82 | .filter( (elem, pos, array) => (array.indexOf(elem) === pos) ) 83 | .map(target => ({ 84 | exec: 'make', 85 | args: args.concat([ target ]), 86 | name: `GNU Make: ${target}`, 87 | sh: false, 88 | errorMatch: errorMatch, 89 | warningMatch: warningMatch 90 | }))); 91 | }).catch(e => [ defaultTarget ]); 92 | } 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /spec/build-make-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import fs from 'fs-extra'; 4 | import temp from 'temp'; 5 | import { vouch } from 'atom-build-spec-helpers'; 6 | import { provideBuilder } from '../lib/make'; 7 | 8 | describe('make', () => { 9 | let directory; 10 | let builder; 11 | const Builder = provideBuilder(); 12 | 13 | beforeEach(() => { 14 | atom.config.set('build-make.useMake', true); 15 | atom.config.set('build-make.jobs', 2); 16 | waitsForPromise(() => { 17 | return vouch(temp.mkdir, 'atom-build-make-spec-') 18 | .then((dir) => vouch(fs.realpath, dir)) 19 | .then((dir) => (directory = `${dir}/`)) 20 | .then((dir) => builder = new Builder(dir)); 21 | }); 22 | }); 23 | 24 | afterEach(() => { 25 | fs.removeSync(directory); 26 | }); 27 | 28 | describe('when makefile exists', () => { 29 | beforeEach(() => { 30 | fs.writeFileSync(directory + 'Makefile', fs.readFileSync(`${__dirname}/Makefile`)); 31 | }); 32 | 33 | it('should be eligible', () => { 34 | expect(builder.isEligible(directory)).toBe(true); 35 | }); 36 | 37 | it('should yield available targets', () => { 38 | waitsForPromise(() => { 39 | return Promise.resolve(builder.settings(directory)).then((settings) => { 40 | expect(settings.length).toBe(4); // default (no target), all, some_custom and Makefile 41 | 42 | const defaultTarget = settings[0]; // default MUST be first 43 | expect(defaultTarget.name).toBe('GNU Make: default (no target)'); 44 | expect(defaultTarget.exec).toBe('make'); 45 | expect(defaultTarget.args).toEqual([ '-j2' ]); 46 | expect(defaultTarget.sh).toBe(false); 47 | 48 | const target = settings.find(setting => setting.name === 'GNU Make: some_custom'); 49 | expect(target.name).toBe('GNU Make: some_custom'); 50 | expect(target.exec).toBe('make'); 51 | expect(target.args).toEqual([ '-j2', 'some_custom' ]); 52 | expect(target.sh).toBe(false); 53 | }); 54 | }); 55 | }); 56 | 57 | it('should yield a subset of all targets if it does not use make to extract targets', () => { 58 | atom.config.set('build-make.useMake', false); 59 | waitsForPromise(() => { 60 | expect(builder.isEligible(directory)).toBe(true); 61 | return Promise.resolve(builder.settings(directory)).then((settings) => { 62 | const targetNames = settings.map(s => s.name).sort(); 63 | expect(targetNames).toEqual([ 'GNU Make: default (no target)', 'GNU Make: all', 'GNU Make: some_custom' ].sort()); 64 | }); 65 | }); 66 | }); 67 | }); 68 | 69 | describe('when makefile contains duplicate targets', () => { 70 | beforeEach(() => { 71 | fs.writeFileSync(directory + 'Makefile', fs.readFileSync(`${__dirname}/Makefile.withduplicates`)); 72 | }); 73 | it('should yield only a single target for each duplicate entry', () => { 74 | atom.config.set('build-make.useMake', false); // Not using make to avoid extracting Makefile as pseudotarget 75 | waitsForPromise(() => { 76 | expect(builder.isEligible(directory)).toBe(true); 77 | return Promise.resolve(builder.settings(directory)).then((settings) => { 78 | const targetNames = settings.map(s => s.name).sort(); 79 | expect(targetNames).toEqual([ 'GNU Make: default (no target)', 'GNU Make: all', 'GNU Make: duplicated' ].sort()); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('when makefile exists but make can not run', () => { 86 | const originalPath = process.env.PATH; 87 | beforeEach(() => { 88 | fs.writeFileSync(directory + 'Makefile', fs.readFileSync(`${__dirname}/Makefile`)); 89 | process.env.PATH = ''; 90 | }); 91 | 92 | afterEach(() => { 93 | process.env.PATH = originalPath; 94 | }); 95 | 96 | it('should be eligible', () => { 97 | expect(builder.isEligible(directory)).toBe(true); 98 | }); 99 | 100 | it('should list the default target', () => { 101 | waitsForPromise(() => { 102 | return Promise.resolve(builder.settings(directory)).then((settings) => { 103 | expect(settings.length).toBe(1); // default (no target) 104 | 105 | const defaultTarget = settings[0]; // default MUST be first 106 | expect(defaultTarget.name).toBe('GNU Make: default (no target)'); 107 | expect(defaultTarget.exec).toBe('make'); 108 | expect(defaultTarget.args).toEqual([ '-j2' ]); 109 | expect(defaultTarget.sh).toBe(false); 110 | }); 111 | }); 112 | }); 113 | }); 114 | 115 | describe('when makefile does not exist', () => { 116 | it('should not be eligible', () => { 117 | expect(builder.isEligible(directory)).toBe(false); 118 | }); 119 | }); 120 | }); 121 | --------------------------------------------------------------------------------